fix(agent): mark a task failed when its resume permanently gives up (B3)
Fix B3 of plans/2026-07-11-nomos-agent-code-review.md. resumeSession's retry loop (used by both auto-continuation and panel-answered questions) already retried once on a transient LLM failure, but if BOTH attempts came back empty/erroring, the code just logged and returned — the task was left at whatever status it already had (typically 'executing' or 'awaiting_input') with no outcome, no operator-visible signal beyond an inert error line buried in the transcript, and no way to tell a genuinely stuck task apart from one quietly still working. On permanent failure, now calls store.completeTask(outcome='failure', a summary built from the error) so the task board reflects reality instead of showing a task that looks perpetually in-progress. Uses context.Background() for that write, matching resumeSession's own persistence pattern, since the context that led to the failure may itself be in a bad state. This doesn't prevent the operator from continuing to work the task via a fresh chat message afterward — it only replaces silent hanging with a real status. A full live induction of a permanent LLM outage would require breaking the model/API-key config for the whole nomos container — too invasive for this fix's priority. Verified instead that the new branch stays correctly dormant on the happy path: ran a real ask_operator → panel-answer → resume cycle end-to-end and confirmed the task landed at status='executing' with no outcome set, proving the failure-handling code doesn't false-positive on a normal successful resume. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,21 @@ func (a *agent) resumeSession(ctx context.Context, sessionID, note string) {
|
||||
|
||||
if errText != "" && finalText == "" {
|
||||
slog.Error("nomos: resume produced no response after retry", "session", sessionID, "error", errText)
|
||||
// Give the task a real, operator-visible terminal state instead of
|
||||
// leaving it silently stuck at whatever status it was in (typically
|
||||
// 'executing' or 'awaiting_input') forever. Before this, a
|
||||
// permanently-failed resume was invisible beyond a log line — the
|
||||
// task board just showed a task that never changed, with nothing
|
||||
// telling the operator it needed attention. Marking it failed here
|
||||
// doesn't prevent the operator from continuing to work the task via
|
||||
// a fresh chat message afterward; it just stops the silent hang.
|
||||
summary := fmt.Sprintf("Auto-resume failed after retrying: %s", errText)
|
||||
if len(summary) > 200 {
|
||||
summary = summary[:200] + "…"
|
||||
}
|
||||
if cerr := a.store.completeTask(context.Background(), sessionID, "failure", summary); cerr != nil {
|
||||
slog.Error("nomos: failed to mark task failed after resume gave up", "session", sessionID, "error", cerr)
|
||||
}
|
||||
}
|
||||
persist() // final state — same row, updated one last time with the concluding text
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user