diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index bcab3b2..7236f37 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -384,6 +384,9 @@ func summarizeAutonomyChanges(oldCfg, newCfg *config.Config) []string { if o.UserIdleResumeSec != n.UserIdleResumeSec { changes = append(changes, "user_idle_resume_sec") } + if o.WaitingResumeDebounceSec != n.WaitingResumeDebounceSec { + changes = append(changes, "waiting_resume_debounce_sec") + } if strings.TrimSpace(o.QuietHours) != strings.TrimSpace(n.QuietHours) { changes = append(changes, "quiet_hours") } @@ -750,6 +753,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E NotifyCooldownSec: a.NotifyCooldownSec, QuietHours: a.QuietHours, UserIdleResumeSec: a.UserIdleResumeSec, + WaitingResumeDebounceSec: a.WaitingResumeDebounceSec, Workspace: cfg.WorkspacePath(), DefaultNotifyChannel: a.NotifyChannel, DefaultNotifyChatID: a.NotifyChatID, diff --git a/cmd/clawgo/cmd_status.go b/cmd/clawgo/cmd_status.go index 0c6b7e4..cbd0413 100644 --- a/cmd/clawgo/cmd_status.go +++ b/cmd/clawgo/cmd_status.go @@ -132,6 +132,7 @@ func statusCmd() { fmt.Printf(" - %s\n", key) } } + fmt.Printf("Autonomy Config: idle_resume=%ds waiting_debounce=%ds\n", cfg.Agents.Defaults.Autonomy.UserIdleResumeSec, cfg.Agents.Defaults.Autonomy.WaitingResumeDebounceSec) if summary, prio, reasons, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil { fmt.Printf("Autonomy Tasks: todo=%d doing=%d waiting=%d blocked=%d done=%d dedupe_hits=%d\n", summary["todo"], summary["doing"], summary["waiting"], summary["blocked"], summary["done"], dedupeHits) fmt.Printf("Autonomy Priority: high=%d normal=%d low=%d\n", prio["high"], prio["normal"], prio["low"]) diff --git a/config.example.json b/config.example.json index b8cacfe..87e8090 100644 --- a/config.example.json +++ b/config.example.json @@ -23,6 +23,7 @@ "notify_cooldown_sec": 300, "quiet_hours": "23:00-08:00", "user_idle_resume_sec": 20, + "waiting_resume_debounce_sec": 5, "notify_channel": "", "notify_chat_id": "" }, diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index e99c844..4619e10 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -20,18 +20,19 @@ import ( ) type Options struct { - Enabled bool - TickIntervalSec int - MinRunIntervalSec int - MaxPendingDurationSec int - MaxConsecutiveStalls int - MaxDispatchPerTick int - Workspace string - DefaultNotifyChannel string - DefaultNotifyChatID string - NotifyCooldownSec int - QuietHours string - UserIdleResumeSec int + Enabled bool + TickIntervalSec int + MinRunIntervalSec int + MaxPendingDurationSec int + MaxConsecutiveStalls int + MaxDispatchPerTick int + Workspace string + DefaultNotifyChannel string + DefaultNotifyChatID string + NotifyCooldownSec int + QuietHours string + UserIdleResumeSec int + WaitingResumeDebounceSec int } type taskState struct { @@ -82,6 +83,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine { if opts.UserIdleResumeSec <= 0 { opts.UserIdleResumeSec = 20 } + if opts.WaitingResumeDebounceSec <= 0 { + opts.WaitingResumeDebounceSec = 5 + } return &Engine{ opts: opts, bus: msgBus, @@ -227,7 +231,7 @@ func (e *Engine) tick() { } if st.Status == "waiting" { // Debounce waiting/resume flapping - if !st.WaitingSince.IsZero() && now.Sub(st.WaitingSince) < 5*time.Second { + if !st.WaitingSince.IsZero() && now.Sub(st.WaitingSince) < time.Duration(e.opts.WaitingResumeDebounceSec)*time.Second { continue } reason := st.BlockReason diff --git a/pkg/config/config.go b/pkg/config/config.go index a810c9a..15b1a42 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -45,17 +45,18 @@ type AgentDefaults struct { } type AutonomyConfig struct { - Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ENABLED"` - TickIntervalSec int `json:"tick_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TICK_INTERVAL_SEC"` - MinRunIntervalSec int `json:"min_run_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MIN_RUN_INTERVAL_SEC"` - MaxPendingDurationSec int `json:"max_pending_duration_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_PENDING_DURATION_SEC"` - MaxConsecutiveStalls int `json:"max_consecutive_stalls" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_CONSECUTIVE_STALLS"` - 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"` + Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ENABLED"` + TickIntervalSec int `json:"tick_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TICK_INTERVAL_SEC"` + MinRunIntervalSec int `json:"min_run_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MIN_RUN_INTERVAL_SEC"` + MaxPendingDurationSec int `json:"max_pending_duration_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_PENDING_DURATION_SEC"` + MaxConsecutiveStalls int `json:"max_consecutive_stalls" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_CONSECUTIVE_STALLS"` + 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"` + WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_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"` } type AgentTextConfig struct { @@ -309,6 +310,7 @@ func DefaultConfig() *Config { NotifyCooldownSec: 300, QuietHours: "23:00-08:00", UserIdleResumeSec: 20, + WaitingResumeDebounceSec: 5, NotifyChannel: "", NotifyChatID: "", }, diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 02d84ea..1c574fc 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -111,6 +111,9 @@ func Validate(cfg *Config) []error { if aut.UserIdleResumeSec <= 0 { errs = append(errs, fmt.Errorf("agents.defaults.autonomy.user_idle_resume_sec must be > 0 when enabled=true")) } + if aut.WaitingResumeDebounceSec <= 0 { + errs = append(errs, fmt.Errorf("agents.defaults.autonomy.waiting_resume_debounce_sec must be > 0 when enabled=true")) + } } texts := cfg.Agents.Defaults.Texts if strings.TrimSpace(texts.NoResponseFallback) == "" {