pause autonomy during active user chats and expose waiting/retry telemetry

This commit is contained in:
DBT
2026-02-24 01:51:55 +00:00
parent 1328c1feec
commit af9ccbaae3
4 changed files with 51 additions and 9 deletions

View File

@@ -205,7 +205,8 @@ func gatewayCmd() {
reflect.DeepEqual(cfg.Tools, newCfg.Tools) && reflect.DeepEqual(cfg.Tools, newCfg.Tools) &&
reflect.DeepEqual(cfg.Channels, newCfg.Channels) reflect.DeepEqual(cfg.Channels, newCfg.Channels)
changes := summarizeDialogTemplateChanges(cfg, newCfg) templateChanges := summarizeDialogTemplateChanges(cfg, newCfg)
autonomyChanges := summarizeAutonomyChanges(cfg, newCfg)
if runtimeSame { if runtimeSame {
configureLogging(newCfg) configureLogging(newCfg)
@@ -230,8 +231,11 @@ func gatewayCmd() {
sentinelService.Start() sentinelService.Start()
} }
cfg = newCfg cfg = newCfg
if len(changes) > 0 { if len(templateChanges) > 0 {
fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(changes, ", ")) fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(templateChanges, ", "))
}
if len(autonomyChanges) > 0 {
fmt.Printf("↻ Autonomy changes: %s\n", strings.Join(autonomyChanges, ", "))
} }
fmt.Println("✓ Config hot-reload applied (logging/metadata only)") fmt.Println("✓ Config hot-reload applied (logging/metadata only)")
continue continue
@@ -275,8 +279,11 @@ func gatewayCmd() {
continue continue
} }
go agentLoop.Run(ctx) go agentLoop.Run(ctx)
if len(changes) > 0 { if len(templateChanges) > 0 {
fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(changes, ", ")) fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(templateChanges, ", "))
}
if len(autonomyChanges) > 0 {
fmt.Printf("↻ Autonomy changes: %s\n", strings.Join(autonomyChanges, ", "))
} }
fmt.Println("✓ Config hot-reload applied") fmt.Println("✓ Config hot-reload applied")
default: default:
@@ -294,6 +301,34 @@ func gatewayCmd() {
} }
} }
func summarizeAutonomyChanges(oldCfg, newCfg *config.Config) []string {
if oldCfg == nil || newCfg == nil {
return nil
}
o := oldCfg.Agents.Defaults.Autonomy
n := newCfg.Agents.Defaults.Autonomy
changes := make([]string, 0)
if o.Enabled != n.Enabled {
changes = append(changes, "enabled")
}
if o.TickIntervalSec != n.TickIntervalSec {
changes = append(changes, "tick_interval_sec")
}
if o.MinRunIntervalSec != n.MinRunIntervalSec {
changes = append(changes, "min_run_interval_sec")
}
if o.UserIdleResumeSec != n.UserIdleResumeSec {
changes = append(changes, "user_idle_resume_sec")
}
if strings.TrimSpace(o.QuietHours) != strings.TrimSpace(n.QuietHours) {
changes = append(changes, "quiet_hours")
}
if o.NotifyCooldownSec != n.NotifyCooldownSec {
changes = append(changes, "notify_cooldown_sec")
}
return changes
}
func summarizeDialogTemplateChanges(oldCfg, newCfg *config.Config) []string { func summarizeDialogTemplateChanges(oldCfg, newCfg *config.Config) []string {
if oldCfg == nil || newCfg == nil { if oldCfg == nil || newCfg == nil {
return nil return nil

View File

@@ -130,7 +130,7 @@ func statusCmd() {
} }
} }
if summary, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil { if summary, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil {
fmt.Printf("Autonomy Tasks: todo=%d doing=%d blocked=%d done=%d dedupe_hits=%d\n", summary["todo"], summary["doing"], summary["blocked"], summary["done"], dedupeHits) 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)
if nextRetry != "" { if nextRetry != "" {
fmt.Printf("Autonomy Next Retry: %s\n", nextRetry) fmt.Printf("Autonomy Next Retry: %s\n", nextRetry)
} }
@@ -252,7 +252,7 @@ func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return map[string]int{"todo": 0, "doing": 0, "blocked": 0, "done": 0}, "", 0, nil return map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0}, "", 0, nil
} }
return nil, "", 0, err return nil, "", 0, err
} }
@@ -264,7 +264,7 @@ func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error
if err := json.Unmarshal(data, &items); err != nil { if err := json.Unmarshal(data, &items); err != nil {
return nil, "", 0, err return nil, "", 0, err
} }
summary := map[string]int{"todo": 0, "doing": 0, "blocked": 0, "done": 0} summary := map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0}
nextRetry := "" nextRetry := ""
nextRetryAt := time.Time{} nextRetryAt := time.Time{}
totalDedupe := 0 totalDedupe := 0

View File

@@ -123,6 +123,7 @@ func (e *Engine) tick() {
for _, st := range e.state { for _, st := range e.state {
if st.Status == "running" { if st.Status == "running" {
st.Status = "waiting" st.Status = "waiting"
e.writeReflectLog("waiting", st, "paused due to active user conversation")
} }
} }
e.persistStateLocked() e.persistStateLocked()
@@ -204,6 +205,10 @@ func (e *Engine) tick() {
if st.Status == "completed" { if st.Status == "completed" {
continue continue
} }
if st.Status == "waiting" {
st.Status = "idle"
e.writeReflectLog("resume", st, "user conversation idle, autonomy resumed")
}
if st.Status == "blocked" { if st.Status == "blocked" {
if !st.RetryAfter.IsZero() && now.Before(st.RetryAfter) { if !st.RetryAfter.IsZero() && now.Before(st.RetryAfter) {
continue continue
@@ -437,6 +442,8 @@ func (e *Engine) persistStateLocked() {
switch st.Status { switch st.Status {
case "running": case "running":
status = "doing" status = "doing"
case "waiting":
status = "waiting"
case "blocked": case "blocked":
status = "blocked" status = "blocked"
case "completed": case "completed":

View File

@@ -14,7 +14,7 @@ type TaskItem struct {
Content string `json:"content"` Content string `json:"content"`
Priority string `json:"priority"` Priority string `json:"priority"`
DueAt string `json:"due_at,omitempty"` DueAt string `json:"due_at,omitempty"`
Status string `json:"status"` // todo|doing|blocked|done Status string `json:"status"` // todo|doing|waiting|blocked|done
RetryAfter string `json:"retry_after,omitempty"` RetryAfter string `json:"retry_after,omitempty"`
Source string `json:"source"` Source string `json:"source"`
DedupeHits int `json:"dedupe_hits,omitempty"` DedupeHits int `json:"dedupe_hits,omitempty"`