From e4e426de7d1e086b6a7fcf2257af1e3605fe29a5 Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 15 Jul 2026 23:49:50 +0200 Subject: [PATCH] fix(agent): auto-complete with partial outcome when writeback missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-complete safety net required hadEntityWriteback to be true, which meant sessions where the agent did the work but forgot to call update_entity_attributes stayed stuck in 'executing' forever. Relax: auto-complete fires if the agent did discovery (ran run), regardless of writeback. If writeback happened → success; if not → partial (honest: work was done but knowledge graph not updated). VERSION 0.7.3 → 0.7.4 --- VERSION | 2 +- cmd/nomos/tasks.go | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index f38fc53..0a1ffad 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.3 +0.7.4 diff --git a/cmd/nomos/tasks.go b/cmd/nomos/tasks.go index 55272dc..26f34bb 100644 --- a/cmd/nomos/tasks.go +++ b/cmd/nomos/tasks.go @@ -362,30 +362,34 @@ func (a *agent) autoCompleteTrivialTask(ctx context.Context, sessionID, response // 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, // the agent never called complete_task this turn, and either (a) all plan -// steps are terminal OR (b) the agent did discovery (ran `run`) AND wrote -// back (update_entity_attributes/create_relationship), auto-complete. Path (b) -// 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. +// steps are terminal OR (b) the agent did discovery (ran `run`), auto-complete. +// Path (b) catches the common case where the agent skips update_plan_step +// bookkeeping but still does the actual work — the D.1 gate already enforces +// writeback before `complete_task`, so if the agent forgot to complete at all, +// we close it out mechanically. If writeback happened → success; if not → +// partial (honest: work was done but knowledge graph wasn't updated). func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseText string) { if a.store == nil || sessionID == "" || sessionID == "ephemeral" { return } - // Only auto-complete if the session is still executing (not already - // terminal — complete_task or a prior auto-complete already ran). sess, err := a.store.getSession(ctx, sessionID) if err != nil || sess.Status != "executing" { return } - // Path (a): all plan steps are terminal (done/failed/replaced/...). - // Path (b): agent did discovery + writeback but didn't close the loop. + discovery := a.store.hadDiscovery(ctx, sessionID) + writeback := a.store.hadEntityWriteback(ctx, sessionID) + // (a) all plan steps terminal, OR (b) agent did discovery (ran `run`). shouldComplete := a.store.allPlanStepsTerminal(ctx, sessionID) - if !shouldComplete && a.store.hadDiscovery(ctx, sessionID) && a.store.hadEntityWriteback(ctx, sessionID) { + if !shouldComplete && discovery { shouldComplete = true } if !shouldComplete { return } + outcome := "success" + if discovery && !writeback { + outcome = "partial" // honest: work done, knowledge graph not updated + } summary := strings.TrimSpace(responseText) summary = strings.SplitN(summary, "\n", 2)[0] const maxLen = 120 @@ -395,9 +399,9 @@ func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseT if summary == "" { summary = "All plan steps completed." } - if err := a.store.completeTask(ctx, sessionID, "success", summary); err != nil { + if err := a.store.completeTask(ctx, sessionID, outcome, summary); err != nil { slog.Error("nomos: auto-complete plan-done task failed", "session", sessionID, "error", err) } else { - slog.Info("nomos: auto-completed task — agent didn't call complete_task", "session", sessionID) + slog.Info("nomos: auto-completed task — agent didn't call complete_task", "session", sessionID, "outcome", outcome) } }