From d55bae17b9ffa4c99b12ba10d5fdfdc8d50c6c6b Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 15 Jul 2026 13:21:46 +0200 Subject: [PATCH] fix(agent): broaden auto-complete to discovery+writeback path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- cmd/nomos/tasks.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cmd/nomos/tasks.go b/cmd/nomos/tasks.go index cf61eda..55272dc 100644 --- a/cmd/nomos/tasks.go +++ b/cmd/nomos/tasks.go @@ -361,10 +361,12 @@ func (a *agent) autoCompleteTrivialTask(ctx context.Context, sessionID, response // autoCompleteIfPlanDone is the structural safety net for "the agent did the // 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 ALL plan steps are in a -// terminal state (done/failed/replaced), auto-complete the task. This mirrors -// autoCompleteTrivialTask but for structured tasks where the work is provably -// done — the model just didn't close the loop. +// 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. func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseText string) { if a.store == nil || sessionID == "" || sessionID == "ephemeral" { return @@ -375,7 +377,13 @@ func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseT if err != nil || sess.Status != "executing" { 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 } 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 { slog.Error("nomos: auto-complete plan-done task failed", "session", sessionID, "error", err) } 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) } }