fix(session): drop the clarification option from the run-mode nudge and apply the completion contract to every agent in run mode

Draft
RA
2d ago
+123-37

Issue for this PR

Closes #1640

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Headless altimate-code run runs could end on a question to the user, who isn't there to answer. In an eight-pass headless evaluation, all six turn-ending questions came right after the post-compaction nudge, whose option (2) was "stop and ask for clarification". This PR fixes that and a second gap that let the same thing happen.

1. The run-mode nudge no longer offers a clarification stop (session/termination.ts).
COMPLETION_NUDGE is sent only when ALTIMATE_RUN_MODE is on (compaction.ts already branches on the flag; interactive compaction uses its own continuation string). It now reads:

Context was compacted; the summary above is the record of the work so far. No one can answer questions during this run, so do not end your turn with a question. Choose exactly one: (1) if concrete next steps remain toward the original task, continue with them; if you are unsure how to proceed, pick the most reasonable interpretation, state your assumption, and continue; (2) if the deliverable is complete and verified, reply with DONE alone on the final line and stop.

The interactive continuation ("Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.") is not touched; it is now pinned byte-for-byte by tests.

2. Every agent gets the completion contract in run mode (session/termination.ts, session/prompt.ts).
completionInstruction() was gated by COMPLETION_CONTRACT_AGENTS = {"builder"}, so custom agents and subagents started by the task tool ran without the "end with DONE" contract. The allowlist and the agent input are removed; the function now depends only on run mode. Run mode is the existing Flag.ALTIMATE_RUN_MODE signal (set by run via cli/cmd/run/run-mode.ts); no new global. Task subagents are prompted through the same SessionPrompt loop in the same process, so they pick it up with no change to tool/task.ts. Interactive sessions still get no contract for any agent.

Behaviour change to review: in run mode, the subagent's final text now ends with a DONE line, and that text is what the task tool returns inside <task_result>. The parent's completion detection only reads the parent's own text parts, so this does not end the parent run, but the parent model will see the token in the tool result.

Not in this PR: a "blocked: report and stop" option for genuinely blocked runs (left out on purpose; the eval showed the escape option was the problem), and stripping DONE from task results.

How did you verify your code works?

  • termination.test.ts: the run-mode nudge has two options, no "clarification", says not to end on a question, and still instructs DONE; completionInstruction({ runMode: true }) returns the contract and { runMode: false } returns nothing; a source guard checks that prompt.ts passes only the run-mode flag and that the agent allowlist is gone; the interactive continuation string in compaction.ts is pinned byte-for-byte.
  • compaction-summarizer-integrity.test.ts: a run-mode compaction injects the new nudge with no "clarification"; an interactive compaction (ALTIMATE_RUN_MODE=0) injects the unchanged interactive string, checked byte-for-byte.
  • New loop-level test in prompt.test.ts: a custom reviewer subagent (configured as a subagent, started from a subtask part and run through the task tool) is driven against the mock LLM server. In run mode every request, the child's and the parent's, carries the contract in its system messages; with ALTIMATE_RUN_MODE=0 none does. Neither mode's requests contain "stop and ask for clarification". The test does not force a compaction inside the subagent; the compaction nudge itself is covered at the session level by the integrity test above.
  • The run-mode tests fail against main (checked by reverting the source files) and pass on this branch; the interactive pins pass on both.
  • bun test (packages/opencode): 20,098 pass, 14 fail, 1 error between tests. All are unrelated: the 7 mcp.headers / mcp HttpApi status failures fail identically on main; the 3 --time-budget cases in json-output.test.ts are the known timing-sensitive ones; budget-finalization, session-diff-missing-patch (2), v2 pty HttpApi and the between-tests error (database is locked in session-diff) are load failures that pass when run alone.
  • Typecheck clean; script/check-tracker-leaks.ts and analyze.ts --markers --strict pass.

No real-gateway run; mock server only.

Cost side effects

Small increase per run-mode request for agents other than builder: the completion contract adds one fixed sentence (about 50 tokens) to the system prompt of custom agents and subagents. It is constant for the session, so it is cached with the rest of the prefix. Interactive sessions are unchanged. Runs that would have ended on an unanswerable question should now finish, which is the expected direction for cost per completed task.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

šŸ¤– Generated with Claude Code


Summary by cubic

Fixes headless altimate-code run sessions ending on a question nobody can answer, and makes the run-mode completion contract (DONE) apply to every agent instead of just builder.

The post-compaction nudge no longer offers a "stop and ask for clarification" option; it now instructs agents to pick the most reasonable interpretation and continue, or reply DONE. The completion contract is injected for every agent in run mode — custom agents and task subagents included — while interactive sessions still receive nothing. Behavior change to review: in run mode a subagent's final text now ends with a DONE line, which appears inside the <task_result> the parent sees; parent completion detection only reads the parent's own text parts, so a parent run still ends normally.