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