From 35fe28d9f47b584b0ca10c7363c0f9c7bb0b17c6 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 04:41:20 +0000 Subject: [PATCH] tune proactive dialog pacing with configurable same-reason cooldown --- cmd/clawgo/cmd_gateway.go | 1 + config.example.json | 5 +++-- pkg/autonomy/engine.go | 20 +++++++++++++++++--- pkg/config/config.go | 6 ++++-- pkg/config/validate.go | 3 +++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 23445ae..e0a6739 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -777,6 +777,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E MaxConsecutiveStalls: a.MaxConsecutiveStalls, MaxDispatchPerTick: a.MaxDispatchPerTick, NotifyCooldownSec: a.NotifyCooldownSec, + NotifySameReasonCooldownSec: a.NotifySameReasonCooldownSec, QuietHours: a.QuietHours, UserIdleResumeSec: a.UserIdleResumeSec, WaitingResumeDebounceSec: a.WaitingResumeDebounceSec, diff --git a/config.example.json b/config.example.json index a6fdd56..5605950 100644 --- a/config.example.json +++ b/config.example.json @@ -21,6 +21,7 @@ "max_consecutive_stalls": 3, "max_dispatch_per_tick": 2, "notify_cooldown_sec": 300, + "notify_same_reason_cooldown_sec": 900, "quiet_hours": "23:00-08:00", "user_idle_resume_sec": 20, "waiting_resume_debounce_sec": 5, @@ -41,8 +42,8 @@ "runtime_compaction_note": "[runtime-compaction] removed %d old messages, kept %d recent messages", "startup_compaction_note": "[startup-compaction] removed %d old messages, kept %d recent messages", "autonomy_important_keywords": ["urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"], - "autonomy_completion_template": "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”", - "autonomy_blocked_template": "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。" + "autonomy_completion_template": "✅ 已完成:%s\n回复“继续 %s”可继续下一步。", + "autonomy_blocked_template": "⚠️ 任务受阻:%s(%s)\n回复“继续 %s”我会重试。" }, "context_compaction": { "enabled": true, diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 306e669..f600de9 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -30,6 +30,7 @@ type Options struct { DefaultNotifyChannel string DefaultNotifyChatID string NotifyCooldownSec int + NotifySameReasonCooldownSec int QuietHours string UserIdleResumeSec int WaitingResumeDebounceSec int @@ -86,6 +87,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine { if opts.UserIdleResumeSec <= 0 { opts.UserIdleResumeSec = 20 } + if opts.NotifySameReasonCooldownSec <= 0 { + opts.NotifySameReasonCooldownSec = 900 + } if opts.WaitingResumeDebounceSec <= 0 { opts.WaitingResumeDebounceSec = 5 } @@ -391,7 +395,7 @@ func (e *Engine) sendCompletionNotification(st *taskState) { if !e.isHighValueCompletion(st) { return } - if !e.shouldNotify("done:" + st.ID) { + if !e.shouldNotify("done:"+st.ID, "") { return } tpl := strings.TrimSpace(e.opts.CompletionTemplate) @@ -408,7 +412,7 @@ func (e *Engine) sendCompletionNotification(st *taskState) { func (e *Engine) sendFailureNotification(st *taskState, reason string) { e.writeReflectLog("blocked", st, reason) e.writeTriggerAudit("blocked", st, reason) - if !e.shouldNotify("blocked:" + st.ID) { + if !e.shouldNotify("blocked:"+st.ID, reason) { return } tpl := strings.TrimSpace(e.opts.BlockedTemplate) @@ -422,7 +426,7 @@ func (e *Engine) sendFailureNotification(st *taskState, reason string) { }) } -func (e *Engine) shouldNotify(key string) bool { +func (e *Engine) shouldNotify(key string, reason string) bool { if strings.TrimSpace(e.opts.DefaultNotifyChannel) == "" || strings.TrimSpace(e.opts.DefaultNotifyChatID) == "" { return false } @@ -435,6 +439,16 @@ func (e *Engine) shouldNotify(key string) bool { return false } } + r := strings.ToLower(strings.TrimSpace(reason)) + if r != "" { + rk := key + ":reason:" + strings.ReplaceAll(r, " ", "_") + if last, ok := e.lastNotify[rk]; ok { + if now.Sub(last) < time.Duration(e.opts.NotifySameReasonCooldownSec)*time.Second { + return false + } + } + e.lastNotify[rk] = now + } e.lastNotify[key] = now return true } diff --git a/pkg/config/config.go b/pkg/config/config.go index ac52846..95bafc7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -52,6 +52,7 @@ type AutonomyConfig struct { 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"` + NotifySameReasonCooldownSec int `json:"notify_same_reason_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_SAME_REASON_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"` @@ -311,6 +312,7 @@ func DefaultConfig() *Config { MaxConsecutiveStalls: 3, MaxDispatchPerTick: 2, NotifyCooldownSec: 300, + NotifySameReasonCooldownSec: 900, QuietHours: "23:00-08:00", UserIdleResumeSec: 20, WaitingResumeDebounceSec: 5, @@ -331,8 +333,8 @@ func DefaultConfig() *Config { RuntimeCompactionNote: "[runtime-compaction] removed %d old messages, kept %d recent messages", StartupCompactionNote: "[startup-compaction] removed %d old messages, kept %d recent messages", AutonomyImportantKeywords: []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"}, - AutonomyCompletionTemplate: "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”", - AutonomyBlockedTemplate: "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。", + AutonomyCompletionTemplate: "✅ 已完成:%s\n回复“继续 %s”可继续下一步。", + AutonomyBlockedTemplate: "⚠️ 任务受阻:%s(%s)\n回复“继续 %s”我会重试。", }, ContextCompaction: ContextCompactionConfig{ Enabled: true, diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 3fb88df..200a204 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -102,6 +102,9 @@ func Validate(cfg *Config) []error { if aut.NotifyCooldownSec <= 0 { errs = append(errs, fmt.Errorf("agents.defaults.autonomy.notify_cooldown_sec must be > 0 when enabled=true")) } + if aut.NotifySameReasonCooldownSec <= 0 { + errs = append(errs, fmt.Errorf("agents.defaults.autonomy.notify_same_reason_cooldown_sec must be > 0 when enabled=true")) + } if qh := strings.TrimSpace(aut.QuietHours); qh != "" { parts := strings.Split(qh, "-") if len(parts) != 2 {