tune proactive dialog pacing with configurable same-reason cooldown

This commit is contained in:
DBT
2026-02-24 04:41:20 +00:00
parent 791eb3b96c
commit 35fe28d9f4
5 changed files with 28 additions and 7 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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 {