mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-06-11 19:13:09 +08:00
autonomy persistence: keep task lifecycle records with parent links, attempts, and memory/audit references instead of transient queue semantics
This commit is contained in:
@@ -336,6 +336,7 @@ func (e *Engine) tick() {
|
|||||||
e.releaseLocksLocked(st.ID)
|
e.releaseLocksLocked(st.ID)
|
||||||
if outcome == "success" || outcome == "suppressed" {
|
if outcome == "success" || outcome == "suppressed" {
|
||||||
st.Status = "completed"
|
st.Status = "completed"
|
||||||
|
e.appendTaskAttemptLocked(st.ID, "done", "autonomy:"+st.ID, "run outcome success")
|
||||||
e.writeReflectLog("complete", st, "marked completed by run outcome")
|
e.writeReflectLog("complete", st, "marked completed by run outcome")
|
||||||
e.enqueueInferredNextTasksLocked(st)
|
e.enqueueInferredNextTasksLocked(st)
|
||||||
continue
|
continue
|
||||||
@@ -356,6 +357,7 @@ func (e *Engine) tick() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
st.BlockReason = "last_run_error"
|
st.BlockReason = "last_run_error"
|
||||||
|
e.appendTaskAttemptLocked(st.ID, "blocked", "autonomy:"+st.ID, "run outcome error")
|
||||||
st.RetryAfter = now.Add(blockedRetryBackoff(st.ConsecutiveStall+1, e.opts.MinRunIntervalSec))
|
st.RetryAfter = now.Add(blockedRetryBackoff(st.ConsecutiveStall+1, e.opts.MinRunIntervalSec))
|
||||||
e.sendFailureNotification(st, "last run ended with error")
|
e.sendFailureNotification(st, "last run ended with error")
|
||||||
continue
|
continue
|
||||||
@@ -416,6 +418,7 @@ func (e *Engine) tick() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e.dispatchTask(st)
|
e.dispatchTask(st)
|
||||||
|
e.appendTaskAttemptLocked(st.ID, "running", "autonomy:"+st.ID, "dispatch")
|
||||||
st.Status = "running"
|
st.Status = "running"
|
||||||
st.WaitAttempts = 0
|
st.WaitAttempts = 0
|
||||||
st.BlockReason = ""
|
st.BlockReason = ""
|
||||||
@@ -733,7 +736,7 @@ func (e *Engine) enqueueInferredNextTasksLocked(st *taskState) {
|
|||||||
}
|
}
|
||||||
id := hashID(candidate)
|
id := hashID(candidate)
|
||||||
e.state[id] = &taskState{ID: id, Content: candidate, Priority: "normal", Status: "idle"}
|
e.state[id] = &taskState{ID: id, Content: candidate, Priority: "normal", Status: "idle"}
|
||||||
items = append(items, TaskItem{ID: id, Content: candidate, Priority: "normal", Status: "todo", Source: "autonomy_infer", UpdatedAt: now})
|
items = append(items, TaskItem{ID: id, ParentTaskID: st.ID, Content: candidate, Priority: "normal", Status: "todo", Source: "autonomy_infer", UpdatedAt: now})
|
||||||
existing[candidate] = true
|
existing[candidate] = true
|
||||||
added++
|
added++
|
||||||
}
|
}
|
||||||
@@ -765,7 +768,7 @@ func (e *Engine) enqueueAutoRepairTaskLocked(st *taskState, errSig string) {
|
|||||||
}
|
}
|
||||||
id := hashID(content)
|
id := hashID(content)
|
||||||
e.state[id] = &taskState{ID: id, Content: content, Priority: "high", Status: "idle"}
|
e.state[id] = &taskState{ID: id, Content: content, Priority: "high", Status: "idle"}
|
||||||
items = append(items, TaskItem{ID: id, Content: content, Priority: "high", Status: "todo", Source: "autonomy_repair", UpdatedAt: nowRFC3339()})
|
items = append(items, TaskItem{ID: id, ParentTaskID: st.ID, Content: content, Priority: "high", Status: "todo", Source: "autonomy_repair", UpdatedAt: nowRFC3339()})
|
||||||
_ = e.taskStore.Save(items)
|
_ = e.taskStore.Save(items)
|
||||||
e.writeReflectLog("infer", st, "generated auto-repair task due to repeated error signature")
|
e.writeReflectLog("infer", st, "generated auto-repair task due to repeated error signature")
|
||||||
}
|
}
|
||||||
@@ -816,8 +819,50 @@ func (e *Engine) appendMemoryIncidentLocked(st *taskState, errSig string, reason
|
|||||||
_, _ = f.WriteString(line + "\n")
|
_, _ = f.WriteString(line + "\n")
|
||||||
}
|
}
|
||||||
dayPath := filepath.Join(e.opts.Workspace, "memory", now.Format("2006-01-02")+".md")
|
dayPath := filepath.Join(e.opts.Workspace, "memory", now.Format("2006-01-02")+".md")
|
||||||
|
memoryMain := filepath.Join(e.opts.Workspace, "MEMORY.md")
|
||||||
appendIfDue(dayPath)
|
appendIfDue(dayPath)
|
||||||
appendIfDue(filepath.Join(e.opts.Workspace, "MEMORY.md"))
|
appendIfDue(memoryMain)
|
||||||
|
items, _ := e.taskStore.Load()
|
||||||
|
for i := range items {
|
||||||
|
if items[i].ID != st.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items[i].MemoryRefs = append(items[i].MemoryRefs, dayPath, memoryMain)
|
||||||
|
if len(items[i].MemoryRefs) > 10 {
|
||||||
|
items[i].MemoryRefs = items[i].MemoryRefs[len(items[i].MemoryRefs)-10:]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = e.taskStore.Save(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) appendTaskAttemptLocked(taskID, status, session, note string) {
|
||||||
|
taskID = strings.TrimSpace(taskID)
|
||||||
|
if taskID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items, _ := e.taskStore.Load()
|
||||||
|
for i := range items {
|
||||||
|
if items[i].ID != taskID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items[i].Attempts = append(items[i].Attempts, TaskAttempt{
|
||||||
|
Time: time.Now().UTC().Format(time.RFC3339),
|
||||||
|
Status: strings.TrimSpace(status),
|
||||||
|
Session: strings.TrimSpace(session),
|
||||||
|
Note: shortTask(note),
|
||||||
|
})
|
||||||
|
if len(items[i].Attempts) > 30 {
|
||||||
|
items[i].Attempts = items[i].Attempts[len(items[i].Attempts)-30:]
|
||||||
|
}
|
||||||
|
items[i].AuditRefs = append(items[i].AuditRefs, time.Now().UTC().Format(time.RFC3339))
|
||||||
|
if len(items[i].AuditRefs) > 60 {
|
||||||
|
items[i].AuditRefs = items[i].AuditRefs[len(items[i].AuditRefs)-60:]
|
||||||
|
}
|
||||||
|
items[i].UpdatedAt = nowRFC3339()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = e.taskStore.Save(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) sendFailureNotification(st *taskState, reason string) {
|
func (e *Engine) sendFailureNotification(st *taskState, reason string) {
|
||||||
@@ -978,6 +1023,11 @@ func inQuietHours(now time.Time, spec string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) persistStateLocked() {
|
func (e *Engine) persistStateLocked() {
|
||||||
|
existing, _ := e.taskStore.Load()
|
||||||
|
existingMap := map[string]TaskItem{}
|
||||||
|
for _, it := range existing {
|
||||||
|
existingMap[it.ID] = it
|
||||||
|
}
|
||||||
items := make([]TaskItem, 0, len(e.state))
|
items := make([]TaskItem, 0, len(e.state))
|
||||||
for _, st := range e.state {
|
for _, st := range e.state {
|
||||||
status := "todo"
|
status := "todo"
|
||||||
@@ -1001,19 +1051,28 @@ func (e *Engine) persistStateLocked() {
|
|||||||
if !st.LastPauseAt.IsZero() {
|
if !st.LastPauseAt.IsZero() {
|
||||||
lastPauseAt = st.LastPauseAt.UTC().Format(time.RFC3339)
|
lastPauseAt = st.LastPauseAt.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
prev := existingMap[st.ID]
|
||||||
|
source := prev.Source
|
||||||
|
if strings.TrimSpace(source) == "" {
|
||||||
|
source = "memory_todo"
|
||||||
|
}
|
||||||
items = append(items, TaskItem{
|
items = append(items, TaskItem{
|
||||||
ID: st.ID,
|
ID: st.ID,
|
||||||
|
ParentTaskID: prev.ParentTaskID,
|
||||||
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: source,
|
||||||
DedupeHits: st.DedupeHits,
|
DedupeHits: st.DedupeHits,
|
||||||
ResourceKeys: append([]string(nil), st.ResourceKeys...),
|
ResourceKeys: append([]string(nil), st.ResourceKeys...),
|
||||||
LastPauseReason: st.LastPauseReason,
|
LastPauseReason: st.LastPauseReason,
|
||||||
LastPauseAt: lastPauseAt,
|
LastPauseAt: lastPauseAt,
|
||||||
|
MemoryRefs: append([]string(nil), prev.MemoryRefs...),
|
||||||
|
AuditRefs: append([]string(nil), prev.AuditRefs...),
|
||||||
|
Attempts: append([]TaskAttempt(nil), prev.Attempts...),
|
||||||
UpdatedAt: nowRFC3339(),
|
UpdatedAt: nowRFC3339(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,31 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TaskAttempt struct {
|
||||||
|
Time string `json:"time"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Session string `json:"session,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type TaskItem struct {
|
type TaskItem struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Content string `json:"content"`
|
ParentTaskID string `json:"parent_task_id,omitempty"`
|
||||||
Priority string `json:"priority"`
|
Content string `json:"content"`
|
||||||
DueAt string `json:"due_at,omitempty"`
|
Priority string `json:"priority"`
|
||||||
Status string `json:"status"` // todo|doing|waiting|blocked|done
|
DueAt string `json:"due_at,omitempty"`
|
||||||
BlockReason string `json:"block_reason,omitempty"`
|
Status string `json:"status"` // todo|doing|waiting|blocked|done|paused|canceled
|
||||||
RetryAfter string `json:"retry_after,omitempty"`
|
BlockReason string `json:"block_reason,omitempty"`
|
||||||
Source string `json:"source"`
|
RetryAfter string `json:"retry_after,omitempty"`
|
||||||
DedupeHits int `json:"dedupe_hits,omitempty"`
|
Source string `json:"source"`
|
||||||
ResourceKeys []string `json:"resource_keys,omitempty"`
|
DedupeHits int `json:"dedupe_hits,omitempty"`
|
||||||
LastPauseReason string `json:"last_pause_reason,omitempty"`
|
ResourceKeys []string `json:"resource_keys,omitempty"`
|
||||||
LastPauseAt string `json:"last_pause_at,omitempty"`
|
LastPauseReason string `json:"last_pause_reason,omitempty"`
|
||||||
UpdatedAt string `json:"updated_at"`
|
LastPauseAt string `json:"last_pause_at,omitempty"`
|
||||||
|
MemoryRefs []string `json:"memory_refs,omitempty"`
|
||||||
|
AuditRefs []string `json:"audit_refs,omitempty"`
|
||||||
|
Attempts []TaskAttempt `json:"attempts,omitempty"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskStore struct {
|
type TaskStore struct {
|
||||||
@@ -65,7 +76,7 @@ func (s *TaskStore) Save(items []TaskItem) error {
|
|||||||
func normalizeStatus(v string) string {
|
func normalizeStatus(v string) string {
|
||||||
s := strings.ToLower(strings.TrimSpace(v))
|
s := strings.ToLower(strings.TrimSpace(v))
|
||||||
switch s {
|
switch s {
|
||||||
case "todo", "doing", "blocked", "done":
|
case "todo", "doing", "waiting", "blocked", "done", "paused", "canceled":
|
||||||
return s
|
return s
|
||||||
default:
|
default:
|
||||||
return "todo"
|
return "todo"
|
||||||
|
|||||||
Reference in New Issue
Block a user