From d6e180845c503b745f1df92caf981a2c2b7c7a6b Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 15 Jul 2026 23:03:34 +0200 Subject: [PATCH] =?UTF-8?q?fix(agent):=20silent=20assent=20=E2=80=94=20sto?= =?UTF-8?q?p=20injecting=20confusing=20system=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assent pre-processing injected verbose system notes ('the operator approved... they are now running... you MUST continue...') on top of the replayed user message ('go ahead'). The model saw both, latched onto 'now running', concluded the work was being done for it, and no-op'd (finish_reason=stop, content_len=0) — leaving the session stuck in 'executing'. Root cause: the model already sees 'go ahead' in the replayed history (the user message is saved to the DB before chat() is called, and getRecentMessages replays it). The system note was redundant AND confusing — it told the model work was 'running' when it wasn't. Fix: - len(pending)==0 (plan-proposal approval): open assent window silently. No system note. The model sees 'go ahead' and responds naturally. - len(pending)>0 (actual pending executions): brief note naming the specific execution IDs that were approved ('don't re-request those'). No 'continue the plan' directive — the model knows to continue. VERSION 0.7.1 → 0.7.2 --- VERSION | 2 +- cmd/nomos/agent.go | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index 39e898a..7486fdb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.1 +0.7.2 diff --git a/cmd/nomos/agent.go b/cmd/nomos/agent.go index 1be0a4b..b83ecee 100644 --- a/cmd/nomos/agent.go +++ b/cmd/nomos/agent.go @@ -315,7 +315,14 @@ func (a *agent) chatWith(ctx context.Context, sessionID, message, systemInject s } if len(granted) > 0 { a.openAssentWindow(ctx, sessionID) - note := fmt.Sprintf("[System: the operator approved pending execution(s) %s via chat assent — they are now running. An assent window is now active for 30 minutes: config_mutation commands will auto-run without re-approval. The approved execution(s) are already dispatched — do not re-request THOSE SPECIFIC ones. But you MUST continue executing the REMAINING plan steps: call update_plan_step(seq=N, status=\"running\") then run(...) for each unstarted step. Do not stop and wait for 'continue'. Only surface to the operator for destructive actions (need typed confirmation) or if you're genuinely stuck after trying alternatives.]", strings.Join(granted, ", ")) + // Brief note: only tells the model WHICH specific executions + // were approved (so it doesn't re-request them). Does NOT say + // "continue the plan" — the model already sees "go ahead" in + // the replayed history and knows to continue. The old verbose + // note ("they are now running... you MUST continue...") made + // the model think work was being done for it, causing empty + // responses (finish_reason=stop, content_len=0). + note := fmt.Sprintf("[System: execution(s) %s were approved by the operator and are now running. Do not re-request those — check get_execution_status if you need the outcome.]", strings.Join(granted, ", ")) messages = append(messages, openai.SystemMessage(note)) } if len(blocked) > 0 { @@ -324,16 +331,16 @@ func (a *agent) chatWith(ctx context.Context, sessionID, message, systemInject s } } else if assent && len(pending) == 0 { // The operator said "proceed"/"go ahead"/"yes" but there are no - // pending approvals from the preceding turn — meaning the agent - // proposed a plan (via propose_plan, possibly with pre-plan research - // tool calls) and asked "shall I?" without calling run yet. Inject - // a system note telling the agent the operator approved — go execute - // the plan now. The old check (len(lastAssistantCalls) == 0) was too - // restrictive: it only fired when the assistant had ZERO tool calls, - // but propose_plan + research tools are tool calls. The right check - // is "no pending APPROVALS" (len(pending) == 0), not "no tool calls." - note := "[System: The operator approved your proposed plan. Execute it now — for each step, call update_plan_step(seq=N, status=\"running\") then run(...) for that step's target, then update_plan_step(seq=N, status=\"done\"). The assent window is active: config_mutation commands will auto-run. Do not re-describe the plan or ask for confirmation again. Do not wait for 'continue' — execute all steps in this turn.]" - messages = append(messages, openai.SystemMessage(note)) + // pending approvals — the agent proposed a plan (via propose_plan) + // and asked "shall I?" Open the assent window silently. Do NOT + // inject a system note: the model already sees "go ahead" in the + // replayed history (the user message was saved to the DB before + // chat() was called, and getRecentMessages replays it). The old + // verbose system note ("The operator approved your proposed plan. + // Execute it now — call update_plan_step then run...") was redundant + // with the user's "go ahead" and caused the model to no-op + // (finish_reason=stop, content_len=0) — the model saw "approved" + + // "running" and concluded there was nothing to do. a.openAssentWindow(ctx, sessionID) }