From a0585508eecfc6c8dd1ec9c0abc1896d58a63bc1 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 03:29:38 +0000 Subject: [PATCH] add autonomy control-file switching and block-reason status analytics --- cmd/clawgo/cmd_gateway.go | 61 ++++++++++++++++++++++++++++++++++++++- cmd/clawgo/cmd_status.go | 20 +++++++++++++ pkg/autonomy/engine.go | 20 ++++++++++++- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 0f939c1..d4712e8 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -3,6 +3,7 @@ package main import ( "bufio" "context" + "encoding/json" "fmt" "os" "os/exec" @@ -44,9 +45,15 @@ func gatewayCmd() { os.Exit(1) } return + case "autonomy": + if err := gatewayAutonomyControlCmd(args[1:]); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + return default: fmt.Printf("Unknown gateway command: %s\n", args[0]) - fmt.Println("Usage: clawgo gateway [run|start|stop|restart|status]") + fmt.Println("Usage: clawgo gateway [run|start|stop|restart|status|autonomy on|off|status]") return } @@ -301,6 +308,58 @@ func gatewayCmd() { } } +func gatewayAutonomyControlCmd(args []string) error { + if len(args) < 1 { + return fmt.Errorf("usage: clawgo gateway autonomy [on|off|status]") + } + cfg, err := loadConfig() + if err != nil { + return err + } + memDir := filepath.Join(cfg.WorkspacePath(), "memory") + if err := os.MkdirAll(memDir, 0755); err != nil { + return err + } + pausePath := filepath.Join(memDir, "autonomy.pause") + ctrlPath := filepath.Join(memDir, "autonomy.control.json") + + switch strings.ToLower(strings.TrimSpace(args[0])) { + case "on": + _ = os.Remove(pausePath) + if err := os.WriteFile(ctrlPath, []byte("{\n \"enabled\": true\n}\n"), 0644); err != nil { + return err + } + fmt.Println("✓ Autonomy enabled") + return nil + case "off": + if err := os.WriteFile(ctrlPath, []byte("{\n \"enabled\": false\n}\n"), 0644); err != nil { + return err + } + if err := os.WriteFile(pausePath, []byte(time.Now().UTC().Format(time.RFC3339)+"\n"), 0644); err != nil { + return err + } + fmt.Println("✓ Autonomy disabled (paused)") + return nil + case "status": + enabled := true + if data, err := os.ReadFile(ctrlPath); err == nil { + var c struct{ Enabled bool `json:"enabled"` } + if json.Unmarshal(data, &c) == nil { + enabled = c.Enabled + } + } + if _, err := os.Stat(pausePath); err == nil { + enabled = false + } + fmt.Printf("Autonomy status: %v\n", enabled) + fmt.Printf("Control file: %s\n", ctrlPath) + fmt.Printf("Pause file: %s\n", pausePath) + return nil + default: + return fmt.Errorf("usage: clawgo gateway autonomy [on|off|status]") + } +} + func summarizeAutonomyChanges(oldCfg, newCfg *config.Config) []string { if oldCfg == nil || newCfg == nil { return nil diff --git a/cmd/clawgo/cmd_status.go b/cmd/clawgo/cmd_status.go index 3062641..bf7d086 100644 --- a/cmd/clawgo/cmd_status.go +++ b/cmd/clawgo/cmd_status.go @@ -138,6 +138,7 @@ func statusCmd() { if nextRetry != "" { fmt.Printf("Autonomy Next Retry: %s\n", nextRetry) } + fmt.Printf("Autonomy Control: %s\n", autonomyControlState(workspace)) } } } @@ -164,6 +165,25 @@ func printTemplateField(name, current, def string) { fmt.Printf(" %s: %s\n", name, state) } +func autonomyControlState(workspace string) string { + memDir := filepath.Join(workspace, "memory") + pausePath := filepath.Join(memDir, "autonomy.pause") + if _, err := os.Stat(pausePath); err == nil { + return "paused (autonomy.pause)" + } + ctrlPath := filepath.Join(memDir, "autonomy.control.json") + if data, err := os.ReadFile(ctrlPath); err == nil { + var c struct{ Enabled bool `json:"enabled"` } + if json.Unmarshal(data, &c) == nil { + if c.Enabled { + return "enabled" + } + return "disabled (control file)" + } + } + return "default" +} + func collectSessionKindCounts(sessionsDir string) (map[string]int, error) { indexPath := filepath.Join(sessionsDir, "sessions.json") data, err := os.ReadFile(indexPath) diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 13fe124..1dc105e 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -416,7 +416,8 @@ func (e *Engine) writeTriggerAudit(action string, st *taskState, errText string) if strings.TrimSpace(e.opts.Workspace) == "" || st == nil { return } - path := filepath.Join(e.opts.Workspace, "memory", "trigger-audit.jsonl") + memDir := filepath.Join(e.opts.Workspace, "memory") + path := filepath.Join(memDir, "trigger-audit.jsonl") _ = os.MkdirAll(filepath.Dir(path), 0755) row := map[string]interface{}{ "time": time.Now().UTC().Format(time.RFC3339), @@ -434,6 +435,23 @@ func (e *Engine) writeTriggerAudit(action string, st *taskState, errText string) _ = f.Close() } } + + statsPath := filepath.Join(memDir, "trigger-stats.json") + stats := struct { + UpdatedAt string `json:"updated_at"` + Counts map[string]int `json:"counts"` + }{Counts: map[string]int{}} + if raw, rErr := os.ReadFile(statsPath); rErr == nil { + _ = json.Unmarshal(raw, &stats) + if stats.Counts == nil { + stats.Counts = map[string]int{} + } + } + stats.Counts["autonomy"]++ + stats.UpdatedAt = time.Now().UTC().Format(time.RFC3339) + if raw, mErr := json.MarshalIndent(stats, "", " "); mErr == nil { + _ = os.WriteFile(statsPath, raw, 0644) + } } func (e *Engine) writeReflectLog(stage string, st *taskState, outcome string) {