diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 1acafbc..7bfd6cf 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -589,6 +589,9 @@ func summarizeAutonomyChanges(oldCfg, newCfg *config.Config) []string { if o.WaitingResumeDebounceSec != n.WaitingResumeDebounceSec { changes = append(changes, "waiting_resume_debounce_sec") } + if o.IdleRoundBudgetReleaseSec != n.IdleRoundBudgetReleaseSec { + changes = append(changes, "idle_round_budget_release_sec") + } if strings.TrimSpace(o.QuietHours) != strings.TrimSpace(n.QuietHours) { changes = append(changes, "quiet_hours") } @@ -967,6 +970,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E MaxRoundsWithoutUser: a.MaxRoundsWithoutUser, TaskHistoryRetentionDays: a.TaskHistoryRetentionDays, WaitingResumeDebounceSec: a.WaitingResumeDebounceSec, + IdleRoundBudgetReleaseSec: a.IdleRoundBudgetReleaseSec, AllowedTaskKeywords: a.AllowedTaskKeywords, ImportantKeywords: cfg.Agents.Defaults.Texts.AutonomyImportantKeywords, CompletionTemplate: cfg.Agents.Defaults.Texts.AutonomyCompletionTemplate, diff --git a/config.example.json b/config.example.json index 07297fc..8610129 100644 --- a/config.example.json +++ b/config.example.json @@ -27,6 +27,7 @@ "max_rounds_without_user": 12, "task_history_retention_days": 3, "waiting_resume_debounce_sec": 5, + "idle_round_budget_release_sec": 1800, "allowed_task_keywords": [], "ekg_consecutive_error_threshold": 3 }, diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 314c188..0059507 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -36,6 +36,7 @@ type Options struct { QuietHours string UserIdleResumeSec int WaitingResumeDebounceSec int + IdleRoundBudgetReleaseSec int MaxRoundsWithoutUser int TaskHistoryRetentionDays int AllowedTaskKeywords []string @@ -111,6 +112,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine { if opts.MaxRoundsWithoutUser < 0 { opts.MaxRoundsWithoutUser = 0 } + if opts.IdleRoundBudgetReleaseSec < 0 { + opts.IdleRoundBudgetReleaseSec = 0 + } if opts.TaskHistoryRetentionDays <= 0 { opts.TaskHistoryRetentionDays = 3 } @@ -302,8 +306,15 @@ func (e *Engine) tick() { continue } if st.BlockReason == "idle_round_budget" && e.opts.MaxRoundsWithoutUser > 0 && e.roundsWithoutUser >= e.opts.MaxRoundsWithoutUser { - // Stay waiting until user activity resets round budget. - continue + // Optional auto-release without user dialog: allow one round after configured cooldown. + if e.opts.IdleRoundBudgetReleaseSec > 0 && !st.WaitingSince.IsZero() && now.Sub(st.WaitingSince) >= time.Duration(e.opts.IdleRoundBudgetReleaseSec)*time.Second { + e.roundsWithoutUser = e.opts.MaxRoundsWithoutUser - 1 + e.writeReflectLog("resume", st, fmt.Sprintf("autonomy auto-resumed from idle round budget after %ds", e.opts.IdleRoundBudgetReleaseSec)) + e.writeTriggerAudit("resume", st, "idle_round_budget_auto_release") + } else { + // Stay waiting until user activity resets round budget. + continue + } } // Debounce waiting/resume flapping if !st.WaitingSince.IsZero() && now.Sub(st.WaitingSince) < time.Duration(e.opts.WaitingResumeDebounceSec)*time.Second { diff --git a/pkg/config/config.go b/pkg/config/config.go index 99b35ce..0348803 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -58,6 +58,7 @@ type AutonomyConfig struct { MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"` TaskHistoryRetentionDays int `json:"task_history_retention_days" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TASK_HISTORY_RETENTION_DAYS"` WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"` + IdleRoundBudgetReleaseSec int `json:"idle_round_budget_release_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_IDLE_ROUND_BUDGET_RELEASE_SEC"` AllowedTaskKeywords []string `json:"allowed_task_keywords" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ALLOWED_TASK_KEYWORDS"` EKGConsecutiveErrorThreshold int `json:"ekg_consecutive_error_threshold" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_EKG_CONSECUTIVE_ERROR_THRESHOLD"` // Deprecated: kept for backward compatibility with existing config files. @@ -378,6 +379,7 @@ func DefaultConfig() *Config { MaxRoundsWithoutUser: 12, TaskHistoryRetentionDays: 3, WaitingResumeDebounceSec: 5, + IdleRoundBudgetReleaseSec: 1800, AllowedTaskKeywords: []string{}, EKGConsecutiveErrorThreshold: 3, }, diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 73f15ff..ec0ff9d 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -117,6 +117,9 @@ func Validate(cfg *Config) []error { if aut.WaitingResumeDebounceSec <= 0 { errs = append(errs, fmt.Errorf("agents.defaults.autonomy.waiting_resume_debounce_sec must be > 0 when enabled=true")) } + if aut.IdleRoundBudgetReleaseSec < 0 { + errs = append(errs, fmt.Errorf("agents.defaults.autonomy.idle_round_budget_release_sec must be >= 0 when enabled=true")) + } if aut.TaskHistoryRetentionDays <= 0 { errs = append(errs, fmt.Errorf("agents.defaults.autonomy.task_history_retention_days must be > 0 when enabled=true")) }