fix(agent): broaden auto-complete to discovery+writeback path
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

The agent often skips update_plan_step bookkeeping (leaving steps
pending/running) but still does the work + writeback. The strict
allPlanStepsTerminal check missed these cases.

Add path (b): if the agent did discovery (ran `run`) AND wrote back
(update_entity_attributes/create_relationship), auto-complete. D.1 already
enforces writeback before completion — if writeback happened, the work
is done.
This commit is contained in:
2026-07-15 13:21:46 +02:00
parent e3b5fdc358
commit d55bae17b9

View File

@@ -361,10 +361,12 @@ func (a *agent) autoCompleteTrivialTask(ctx context.Context, sessionID, response
// autoCompleteIfPlanDone is the structural safety net for "the agent did the // autoCompleteIfPlanDone is the structural safety net for "the agent did the
// work but forgot to call complete_task" — the #1 remaining model reliability // work but forgot to call complete_task" — the #1 remaining model reliability
// gap after D.1's writeback gate. After a turn ends, if the session has a goal, // gap after D.1's writeback gate. After a turn ends, if the session has a goal,
// the agent never called complete_task this turn, and ALL plan steps are in a // the agent never called complete_task this turn, and either (a) all plan
// terminal state (done/failed/replaced), auto-complete the task. This mirrors // steps are terminal OR (b) the agent did discovery (ran `run`) AND wrote
// autoCompleteTrivialTask but for structured tasks where the work is provably // back (update_entity_attributes/create_relationship), auto-complete. Path (b)
// done — the model just didn't close the loop. // catches the common case where the agent skips update_plan_step bookkeeping
// but still does the actual work + writeback — the D.1 gate already enforces
// writeback before completion, so if writeback happened, the work is done.
func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseText string) { func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseText string) {
if a.store == nil || sessionID == "" || sessionID == "ephemeral" { if a.store == nil || sessionID == "" || sessionID == "ephemeral" {
return return
@@ -375,7 +377,13 @@ func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseT
if err != nil || sess.Status != "executing" { if err != nil || sess.Status != "executing" {
return return
} }
if !a.store.allPlanStepsTerminal(ctx, sessionID) { // Path (a): all plan steps are terminal (done/failed/replaced/...).
// Path (b): agent did discovery + writeback but didn't close the loop.
shouldComplete := a.store.allPlanStepsTerminal(ctx, sessionID)
if !shouldComplete && a.store.hadDiscovery(ctx, sessionID) && a.store.hadEntityWriteback(ctx, sessionID) {
shouldComplete = true
}
if !shouldComplete {
return return
} }
summary := strings.TrimSpace(responseText) summary := strings.TrimSpace(responseText)
@@ -390,6 +398,6 @@ func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseT
if err := a.store.completeTask(ctx, sessionID, "success", summary); err != nil { if err := a.store.completeTask(ctx, sessionID, "success", summary); err != nil {
slog.Error("nomos: auto-complete plan-done task failed", "session", sessionID, "error", err) slog.Error("nomos: auto-complete plan-done task failed", "session", sessionID, "error", err)
} else { } else {
slog.Info("nomos: auto-completed task — all plan steps terminal but agent didn't call complete_task", "session", sessionID) slog.Info("nomos: auto-completed task — agent didn't call complete_task", "session", sessionID)
} }
} }