From 1328c1feec771de183bd30e6522327440f55a093 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 01:45:50 +0000 Subject: [PATCH] prioritize user conversations by pausing autonomy and expose retry/dedupe telemetry --- cmd/clawgo/cmd_gateway.go | 1 + config.example.json | 1 + pkg/autonomy/engine.go | 50 +++++++++++++++++++++++++++++++++++++++ pkg/config/config.go | 2 ++ pkg/config/validate.go | 3 +++ 5 files changed, 57 insertions(+) diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index cb1f76a..87df993 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -650,6 +650,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E MaxDispatchPerTick: a.MaxDispatchPerTick, NotifyCooldownSec: a.NotifyCooldownSec, QuietHours: a.QuietHours, + UserIdleResumeSec: a.UserIdleResumeSec, Workspace: cfg.WorkspacePath(), DefaultNotifyChannel: a.NotifyChannel, DefaultNotifyChatID: a.NotifyChatID, diff --git a/config.example.json b/config.example.json index deba1bb..b8cacfe 100644 --- a/config.example.json +++ b/config.example.json @@ -22,6 +22,7 @@ "max_dispatch_per_tick": 2, "notify_cooldown_sec": 300, "quiet_hours": "23:00-08:00", + "user_idle_resume_sec": 20, "notify_channel": "", "notify_chat_id": "" }, diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 6a37c18..35e4ef5 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha1" "encoding/hex" + "encoding/json" "fmt" "os" "path/filepath" @@ -29,6 +30,7 @@ type Options struct { DefaultNotifyChatID string NotifyCooldownSec int QuietHours string + UserIdleResumeSec int } type taskState struct { @@ -74,6 +76,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine { if opts.NotifyCooldownSec <= 0 { opts.NotifyCooldownSec = 300 } + if opts.UserIdleResumeSec <= 0 { + opts.UserIdleResumeSec = 20 + } return &Engine{ opts: opts, bus: msgBus, @@ -113,6 +118,19 @@ func (e *Engine) tick() { now := time.Now() stored, _ := e.taskStore.Load() + e.mu.Lock() + if e.hasRecentUserActivity(now) { + for _, st := range e.state { + if st.Status == "running" { + st.Status = "waiting" + } + } + e.persistStateLocked() + e.mu.Unlock() + return + } + e.mu.Unlock() + e.mu.Lock() defer e.mu.Unlock() @@ -500,6 +518,38 @@ func dueWeight(dueAt string) int64 { return 0 } +func (e *Engine) hasRecentUserActivity(now time.Time) bool { + if e.opts.UserIdleResumeSec <= 0 || strings.TrimSpace(e.opts.Workspace) == "" { + return false + } + // sessions are stored next to workspace directory in clawgo runtime + sessionsPath := filepath.Join(filepath.Dir(e.opts.Workspace), "sessions", "sessions.json") + data, err := os.ReadFile(sessionsPath) + if err != nil { + return false + } + var index map[string]struct { + Kind string `json:"kind"` + UpdatedAt int64 `json:"updatedAt"` + } + if err := json.Unmarshal(data, &index); err != nil { + return false + } + cutoff := now.Add(-time.Duration(e.opts.UserIdleResumeSec) * time.Second) + for _, row := range index { + if strings.ToLower(strings.TrimSpace(row.Kind)) != "main" { + continue + } + if row.UpdatedAt <= 0 { + continue + } + if time.UnixMilli(row.UpdatedAt).After(cutoff) { + return true + } + } + return false +} + func blockedRetryBackoff(stalls int, minRunIntervalSec int) time.Duration { if minRunIntervalSec <= 0 { minRunIntervalSec = 20 diff --git a/pkg/config/config.go b/pkg/config/config.go index 5a9436f..a810c9a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -53,6 +53,7 @@ type AutonomyConfig struct { MaxDispatchPerTick int `json:"max_dispatch_per_tick" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_DISPATCH_PER_TICK"` NotifyCooldownSec int `json:"notify_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_COOLDOWN_SEC"` QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"` + UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"` NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"` NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"` } @@ -307,6 +308,7 @@ func DefaultConfig() *Config { MaxDispatchPerTick: 2, NotifyCooldownSec: 300, QuietHours: "23:00-08:00", + UserIdleResumeSec: 20, NotifyChannel: "", NotifyChatID: "", }, diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 16d702f..02d84ea 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -108,6 +108,9 @@ func Validate(cfg *Config) []error { errs = append(errs, fmt.Errorf("agents.defaults.autonomy.quiet_hours must be HH:MM-HH:MM")) } } + if aut.UserIdleResumeSec <= 0 { + errs = append(errs, fmt.Errorf("agents.defaults.autonomy.user_idle_resume_sec must be > 0 when enabled=true")) + } } texts := cfg.Agents.Defaults.Texts if strings.TrimSpace(texts.NoResponseFallback) == "" {