autonomy M2: add session-idle round budget control and surface source/idle metadata for audit

This commit is contained in:
DBT
2026-02-28 06:35:49 +00:00
parent 9788f9f7de
commit aca66ea9f4
3 changed files with 22 additions and 3 deletions

View File

@@ -949,6 +949,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E
NotifySameReasonCooldownSec: a.NotifySameReasonCooldownSec,
QuietHours: a.QuietHours,
UserIdleResumeSec: a.UserIdleResumeSec,
MaxRoundsWithoutUser: a.MaxRoundsWithoutUser,
WaitingResumeDebounceSec: a.WaitingResumeDebounceSec,
AllowedTaskKeywords: a.AllowedTaskKeywords,
ImportantKeywords: cfg.Agents.Defaults.Texts.AutonomyImportantKeywords,

View File

@@ -34,6 +34,7 @@ type Options struct {
QuietHours string
UserIdleResumeSec int
WaitingResumeDebounceSec int
MaxRoundsWithoutUser int
AllowedTaskKeywords []string
ImportantKeywords []string
CompletionTemplate string
@@ -69,6 +70,7 @@ type Engine struct {
state map[string]*taskState
lastNotify map[string]time.Time
lockOwners map[string]string
roundsWithoutUser int
}
func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine {
@@ -99,6 +101,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine {
if opts.WaitingResumeDebounceSec <= 0 {
opts.WaitingResumeDebounceSec = 5
}
if opts.MaxRoundsWithoutUser < 0 {
opts.MaxRoundsWithoutUser = 0
}
return &Engine{
opts: opts,
bus: msgBus,
@@ -153,6 +158,7 @@ func (e *Engine) tick() {
e.writeTriggerAudit("waiting", st, "manual_pause")
}
}
e.roundsWithoutUser = 0
e.persistStateLocked()
e.mu.Unlock()
return
@@ -306,6 +312,14 @@ func (e *Engine) tick() {
}
}
if e.opts.MaxRoundsWithoutUser > 0 && e.roundsWithoutUser >= e.opts.MaxRoundsWithoutUser {
st.Status = "waiting"
st.BlockReason = "idle_round_budget"
st.WaitingSince = now
e.writeReflectLog("waiting", st, fmt.Sprintf("paused by idle round budget (%d)", e.opts.MaxRoundsWithoutUser))
e.writeTriggerAudit("waiting", st, "idle_round_budget")
continue
}
if !e.tryAcquireLocksLocked(st) {
st.Status = "waiting"
st.BlockReason = "resource_lock"
@@ -325,6 +339,7 @@ func (e *Engine) tick() {
st.LastAutonomyAt = now
e.writeReflectLog("dispatch", st, "task dispatched to agent loop")
e.writeTriggerAudit("dispatch", st, "")
e.roundsWithoutUser++
dispatched++
}
e.persistStateLocked()

View File

@@ -54,9 +54,10 @@ type AutonomyConfig struct {
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"`
AllowedTaskKeywords []string `json:"allowed_task_keywords" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ALLOWED_TASK_KEYWORDS"`
UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"`
MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"`
WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"`
AllowedTaskKeywords []string `json:"allowed_task_keywords" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ALLOWED_TASK_KEYWORDS"`
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"`
}
@@ -324,7 +325,9 @@ func DefaultConfig() *Config {
NotifySameReasonCooldownSec: 900,
QuietHours: "23:00-08:00",
UserIdleResumeSec: 20,
MaxRoundsWithoutUser: 12,
WaitingResumeDebounceSec: 5,
AllowedTaskKeywords: []string{},
NotifyChannel: "",
NotifyChatID: "",
},