You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/08/24 09:39:29 UTC
(casbin-gateway) 01/01: feat(agent): monitor and record every agent by default
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit cdda2b7c0f2934d291b4837b9578305e3d985417
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Mon Aug 24 17:39:21 2026 +0800
feat(agent): monitor and record every agent by default
---
agentpatch/auto.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++
agentpatch/patch.go | 11 +++++--
conf/app.conf | 5 +--
conf/conf.go | 10 +++---
controllers/agent.go | 4 +++
main.go | 10 ++++++
6 files changed, 117 insertions(+), 9 deletions(-)
diff --git a/agentpatch/auto.go b/agentpatch/auto.go
new file mode 100644
index 0000000..244f809
--- /dev/null
+++ b/agentpatch/auto.go
@@ -0,0 +1,86 @@
+// Copyright 2026 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package agentpatch
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/apache/casbin-gateway/agent"
+)
+
+// Monitoring is on by default: an installation Gateway finds is patched without
+// waiting for a click. Turning one off by hand leaves an opt-out marker beside
+// its manifest, so the next scan leaves that installation alone.
+
+func optOutPath(target Target) string {
+ return filepath.Join(stateDir(), targetKey(target)+".off")
+}
+
+func isOptedOut(target Target) bool {
+ _, err := os.Stat(optOutPath(target))
+ return err == nil
+}
+
+func setOptOut(target Target, off bool) {
+ if !off {
+ _ = os.Remove(optOutPath(target))
+ return
+ }
+ if err := os.MkdirAll(stateDir(), 0o700); err != nil {
+ return
+ }
+ _ = os.WriteFile(optOutPath(target), []byte(time.Now().Format(time.RFC3339)), 0o600)
+}
+
+// EnsurePatched turns monitoring on for one installation, unless it cannot be
+// monitored, already is, or was turned off by hand.
+func EnsurePatched(target Target) error {
+ patcher, ok := patchers[target.AgentId]
+ if !ok || !patcher.Supported() || isOptedOut(target) {
+ return nil
+ }
+
+ status, err := patcher.Status(target)
+ if err != nil {
+ return err
+ }
+ if status.Patched {
+ return nil
+ }
+ return patcher.Patch(target)
+}
+
+// EnableAll turns monitoring on for every agent installed on this host. The
+// failures are joined so one installation that cannot be patched neither hides
+// the others nor stops them.
+func EnableAll() error {
+ installations, err := agent.Scan(false)
+ if err != nil {
+ return err
+ }
+
+ failures := []error{}
+ for _, installation := range installations {
+ target := Target{AgentId: installation.AgentId, Path: installation.Path, Owner: installation.Owner}
+ if err := EnsurePatched(target); err != nil {
+ failures = append(failures, fmt.Errorf("%s: %w", installation.AgentId, err))
+ }
+ }
+ return errors.Join(failures...)
+}
diff --git a/agentpatch/patch.go b/agentpatch/patch.go
index bd9f214..98fdb90 100644
--- a/agentpatch/patch.go
+++ b/agentpatch/patch.go
@@ -90,15 +90,22 @@ func Patch(target Target) error {
if err != nil {
return err
}
- return patcher.Patch(target)
+ if err := patcher.Patch(target); err != nil {
+ return err
+ }
+ setOptOut(target, false)
+ return nil
}
-// Unpatch disables monitoring and restores files changed by Patch.
+// Unpatch disables monitoring and restores files changed by Patch. Monitoring
+// being on by default, the choice is remembered so no later scan turns it back
+// on.
func Unpatch(target Target) error {
patcher, err := patcherFor(target)
if err != nil {
return err
}
+ setOptOut(target, true)
return patcher.Unpatch(target)
}
diff --git a/conf/app.conf b/conf/app.conf
index 7ceba26..9c580ee 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -104,8 +104,9 @@ agentMonitorPollSeconds = 5
; LLM request records. "off" keeps nothing, "metadata" records who called which
; model with which outcome, and "full" also stores the sanitized request body.
-; Anything beyond "off" is opt-in because prompts can carry sensitive data.
-llmRecordMode = "off"
+; The default is "full": the records are what the dashboards are made of, and
+; they stay on this machine. Set it to "metadata" or "off" to keep less.
+llmRecordMode = "full"
llmRecordQueueCapacity = 1000
llmRecordRetentionDays = 30
llmRecordMaxRecords = 10000
diff --git a/conf/conf.go b/conf/conf.go
index d68639a..c7defb4 100644
--- a/conf/conf.go
+++ b/conf/conf.go
@@ -300,16 +300,16 @@ const (
)
// GetLlmRecordMode reports whether LLM requests are recorded, and whether the
-// request body is kept along with the metadata. Prompts are sensitive, so the
-// default is to keep nothing.
+// request body is kept along with the metadata. Recording is on by default, so
+// anything but an explicit "off" or "metadata" keeps the whole request.
func GetLlmRecordMode() string {
switch strings.ToLower(strings.Trim(GetConfigString("llmRecordMode"), `"' `)) {
+ case LlmRecordOff:
+ return LlmRecordOff
case LlmRecordMetadata:
return LlmRecordMetadata
- case LlmRecordFull:
- return LlmRecordFull
default:
- return LlmRecordOff
+ return LlmRecordFull
}
}
diff --git a/controllers/agent.go b/controllers/agent.go
index 62bc600..fd8fc6a 100644
--- a/controllers/agent.go
+++ b/controllers/agent.go
@@ -68,6 +68,10 @@ func (c *ApiController) GetAgents() {
result := make([]*discoveredAgent, 0, len(installations))
for _, installation := range installations {
target := targetOf(installation)
+ // Monitoring is on by default, so an installation that is not monitored
+ // yet is patched here instead of waiting for the switch to be flipped.
+ // A failure is left to the status below, which says what is wrong.
+ _ = agentpatch.EnsurePatched(target)
item := &discoveredAgent{
Installation: installation,
Status: agentpatch.StatusOf(target),
diff --git a/main.go b/main.go
index 7a8d467..fe6c708 100644
--- a/main.go
+++ b/main.go
@@ -22,6 +22,7 @@ import (
"github.com/apache/casbin-gateway/agenthook"
"github.com/apache/casbin-gateway/agentmonitor"
+ "github.com/apache/casbin-gateway/agentpatch"
"github.com/apache/casbin-gateway/casdoor"
"github.com/apache/casbin-gateway/conf"
"github.com/apache/casbin-gateway/ip"
@@ -93,6 +94,15 @@ func main() {
}
defer agentmonitor.Stop()
+ // Monitoring is on by default, so the agents already on this host are
+ // patched without anyone opening the UI. The scan walks the disk, hence the
+ // goroutine.
+ go func() {
+ if err := agentpatch.EnableAll(); err != nil {
+ beego.Error("agent monitoring could not be enabled:", err)
+ }
+ }()
+
// An update ends this process without unwinding main, so what the deferred
// calls above would have flushed has to be flushed there instead.
version.BeforeRestart = func() {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org