fix(agent): silent assent — stop injecting confusing system notes
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 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
This commit is contained in:
2026-07-15 23:03:34 +02:00
parent 7ef8446825
commit d6e180845c
2 changed files with 19 additions and 12 deletions

View File

@@ -1 +1 @@
0.7.1 0.7.2

View File

@@ -315,7 +315,14 @@ func (a *agent) chatWith(ctx context.Context, sessionID, message, systemInject s
} }
if len(granted) > 0 { if len(granted) > 0 {
a.openAssentWindow(ctx, sessionID) 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)) messages = append(messages, openai.SystemMessage(note))
} }
if len(blocked) > 0 { 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 { } else if assent && len(pending) == 0 {
// The operator said "proceed"/"go ahead"/"yes" but there are no // The operator said "proceed"/"go ahead"/"yes" but there are no
// pending approvals from the preceding turn — meaning the agent // pending approvals the agent proposed a plan (via propose_plan)
// proposed a plan (via propose_plan, possibly with pre-plan research // and asked "shall I?" Open the assent window silently. Do NOT
// tool calls) and asked "shall I?" without calling run yet. Inject // inject a system note: the model already sees "go ahead" in the
// a system note telling the agent the operator approved — go execute // replayed history (the user message was saved to the DB before
// the plan now. The old check (len(lastAssistantCalls) == 0) was too // chat() was called, and getRecentMessages replays it). The old
// restrictive: it only fired when the assistant had ZERO tool calls, // verbose system note ("The operator approved your proposed plan.
// but propose_plan + research tools are tool calls. The right check // Execute it now — call update_plan_step then run...") was redundant
// is "no pending APPROVALS" (len(pending) == 0), not "no tool calls." // with the user's "go ahead" and caused the model to no-op
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.]" // (finish_reason=stop, content_len=0) — the model saw "approved" +
messages = append(messages, openai.SystemMessage(note)) // "running" and concluded there was nothing to do.
a.openAssentWindow(ctx, sessionID) a.openAssentWindow(ctx, sessionID)
} }