autonomy M1 phase3: persist pause/resume reasons and durations for active-user idle-run auditing

This commit is contained in:
DBT
2026-02-28 06:19:34 +00:00
parent 058b215704
commit 9bf90b65c3
2 changed files with 40 additions and 13 deletions

View File

@@ -55,6 +55,8 @@ type taskState struct {
DedupeHits int DedupeHits int
ResourceKeys []string ResourceKeys []string
WaitAttempts int WaitAttempts int
LastPauseReason string
LastPauseAt time.Time
} }
type Engine struct { type Engine struct {
@@ -145,6 +147,8 @@ func (e *Engine) tick() {
st.Status = "waiting" st.Status = "waiting"
st.BlockReason = "manual_pause" st.BlockReason = "manual_pause"
st.WaitingSince = now st.WaitingSince = now
st.LastPauseReason = "manual_pause"
st.LastPauseAt = now
e.writeReflectLog("waiting", st, "paused by manual switch") e.writeReflectLog("waiting", st, "paused by manual switch")
e.writeTriggerAudit("waiting", st, "manual_pause") e.writeTriggerAudit("waiting", st, "manual_pause")
} }
@@ -160,6 +164,8 @@ func (e *Engine) tick() {
st.Status = "waiting" st.Status = "waiting"
st.BlockReason = "active_user" st.BlockReason = "active_user"
st.WaitingSince = now st.WaitingSince = now
st.LastPauseReason = "active_user"
st.LastPauseAt = now
e.writeReflectLog("waiting", st, "paused due to active user conversation") e.writeReflectLog("waiting", st, "paused due to active user conversation")
e.writeTriggerAudit("waiting", st, "active_user") e.writeTriggerAudit("waiting", st, "active_user")
} }
@@ -186,6 +192,8 @@ func (e *Engine) tick() {
status := "idle" status := "idle"
retryAfter := time.Time{} retryAfter := time.Time{}
resourceKeys := deriveResourceKeys(t.Content) resourceKeys := deriveResourceKeys(t.Content)
lastPauseReason := ""
lastPauseAt := time.Time{}
if old, ok := storedMap[t.ID]; ok { if old, ok := storedMap[t.ID]; ok {
if old.Status == "blocked" { if old.Status == "blocked" {
status = "blocked" status = "blocked"
@@ -198,8 +206,14 @@ func (e *Engine) tick() {
if len(old.ResourceKeys) > 0 { if len(old.ResourceKeys) > 0 {
resourceKeys = append([]string(nil), old.ResourceKeys...) resourceKeys = append([]string(nil), old.ResourceKeys...)
} }
lastPauseReason = old.LastPauseReason
if old.LastPauseAt != "" {
if pt, err := time.Parse(time.RFC3339, old.LastPauseAt); err == nil {
lastPauseAt = pt
}
}
} }
e.state[t.ID] = &taskState{ID: t.ID, Content: t.Content, Priority: t.Priority, DueAt: t.DueAt, Status: status, RetryAfter: retryAfter, DedupeHits: t.DedupeHits, ResourceKeys: resourceKeys} e.state[t.ID] = &taskState{ID: t.ID, Content: t.Content, Priority: t.Priority, DueAt: t.DueAt, Status: status, RetryAfter: retryAfter, DedupeHits: t.DedupeHits, ResourceKeys: resourceKeys, LastPauseReason: lastPauseReason, LastPauseAt: lastPauseAt}
continue continue
} }
st.Content = t.Content st.Content = t.Content
@@ -256,7 +270,11 @@ func (e *Engine) tick() {
st.Status = "idle" st.Status = "idle"
st.BlockReason = "" st.BlockReason = ""
st.WaitingSince = time.Time{} st.WaitingSince = time.Time{}
e.writeReflectLog("resume", st, "autonomy resumed from waiting") pausedFor := 0
if !st.LastPauseAt.IsZero() {
pausedFor = int(now.Sub(st.LastPauseAt).Seconds())
}
e.writeReflectLog("resume", st, fmt.Sprintf("autonomy resumed from waiting (reason=%s paused_for=%ds)", reason, pausedFor))
e.writeTriggerAudit("resume", st, reason) e.writeTriggerAudit("resume", st, reason)
} }
if st.Status == "blocked" { if st.Status == "blocked" {
@@ -302,6 +320,7 @@ func (e *Engine) tick() {
st.WaitAttempts = 0 st.WaitAttempts = 0
st.BlockReason = "" st.BlockReason = ""
st.WaitingSince = time.Time{} st.WaitingSince = time.Time{}
st.LastPauseReason = ""
st.LastRunAt = now st.LastRunAt = now
st.LastAutonomyAt = now st.LastAutonomyAt = now
e.writeReflectLog("dispatch", st, "task dispatched to agent loop") e.writeReflectLog("dispatch", st, "task dispatched to agent loop")
@@ -727,18 +746,24 @@ func (e *Engine) persistStateLocked() {
if !st.RetryAfter.IsZero() { if !st.RetryAfter.IsZero() {
retryAfter = st.RetryAfter.UTC().Format(time.RFC3339) retryAfter = st.RetryAfter.UTC().Format(time.RFC3339)
} }
lastPauseAt := ""
if !st.LastPauseAt.IsZero() {
lastPauseAt = st.LastPauseAt.UTC().Format(time.RFC3339)
}
items = append(items, TaskItem{ items = append(items, TaskItem{
ID: st.ID, ID: st.ID,
Content: st.Content, Content: st.Content,
Priority: st.Priority, Priority: st.Priority,
DueAt: st.DueAt, DueAt: st.DueAt,
Status: status, Status: status,
BlockReason: st.BlockReason, BlockReason: st.BlockReason,
RetryAfter: retryAfter, RetryAfter: retryAfter,
Source: "memory_todo", Source: "memory_todo",
DedupeHits: st.DedupeHits, DedupeHits: st.DedupeHits,
ResourceKeys: append([]string(nil), st.ResourceKeys...), ResourceKeys: append([]string(nil), st.ResourceKeys...),
UpdatedAt: nowRFC3339(), LastPauseReason: st.LastPauseReason,
LastPauseAt: lastPauseAt,
UpdatedAt: nowRFC3339(),
}) })
} }
_ = e.taskStore.Save(items) _ = e.taskStore.Save(items)

View File

@@ -20,6 +20,8 @@ type TaskItem struct {
Source string `json:"source"` Source string `json:"source"`
DedupeHits int `json:"dedupe_hits,omitempty"` DedupeHits int `json:"dedupe_hits,omitempty"`
ResourceKeys []string `json:"resource_keys,omitempty"` ResourceKeys []string `json:"resource_keys,omitempty"`
LastPauseReason string `json:"last_pause_reason,omitempty"`
LastPauseAt string `json:"last_pause_at,omitempty"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
} }