Repository navigation
Description
Every turn on a raw engine starts a fresh conversation — continuity is one preset string, and no preset has it
Filed from the #249 assessment, recommendation item 3 — "Continuity as configuration… Zero code.
Memo item 2, still open." #249 cannot close leaving the proposed ADR as its only record.
The mechanism
Claude is the only persistent engine: the runner keeps one process alive and speaks structured
stream-json to it across turns. Every other engine is one-shot
(packages/browser-runner/src/coding/headless.ts:378-380, :463-482):
private get oneShot(): boolean { return this.mode === "raw"; }
…
private runOneShot(text: string): void {
const proc = spawn(this.cmdBin, [...this.cmdArgs, text], { cwd: …, env: mergeEnv(…) });with the contract stated deliberately at :466-468:
"the preset command is a prefix and the turn text is appended as the final argument"
So the Pilot's step 7 instruction is codex exec --sandbox danger-full-access "<step 7>" — a brand
new process that has never seen steps 1–6. The working tree carries state; the conversation does
not. A step that says "now do the same for the other two files" has no antecedent.
docs/coding-engines.md:7-27 documents the prefix contract precisely and correctly, and says
nothing about what it costs across turns.
None of the shipped presets carries a resume flag
workers/api/src/lib/coding-engines.ts:253-267 — the defaults an instance that has saved nothing gets:
| id | command | persistent? |
|---|---|---|
claude |
claude --dangerously-skip-permissions |
yes — stream-json, --resume handled by buildClaudeArgs |
codex |
codex exec --sandbox danger-full-access |
no |
gemini |
gemini --approval-mode yolo --skip-trust --prompt |
no |
grok |
grok --permission-mode bypassPermissions -p |
no |
local |
ollama run llama3 |
no |
Claude's continuity is not a preset — it is the runner re-spawning with --resume <session id>
against ~/.claude (headless.ts:394-397, and buildClaudeArgs strips a user-supplied --resume
because it carries the wire protocol). Nothing equivalent exists for a raw engine, and nothing needs
to: a CLI that offers a resume subcommand gains multi-turn memory by editing one text field.
What this ticket is, and is not
It is not a request to change the runner. The mechanism above is deliberate and docs/adr's
proposed ADR 0002 states it as a consequence rather than a defect: "Multi-turn memory for raw
engines is a preset string, not a backend property." The gap is that the platform never says so, so
the trade is invisible at exactly the moment a user picks an engine.
Stated honestly: I have not run a multi-turn codex/gemini/grok session. The claim above is
reconstructed from the spawn path and the shipped preset strings, both of which are unambiguous. What
I cannot tell you from the code is how badly it degrades a real 10-step objective — that needs a run,
and a run spends the owner's tokens.
What to do, cheapest first
- Say it where the choice is made. The ⚙ CLI engines panel (
EnginesModal.tsx) is where a
preset is edited; one line per engine — "one-shot: each turn is a fresh process, with no memory
of the previous one" vs "keeps its session between turns" — costs nothing and is the whole
user-facing gap.deriveClientType(command)already decides which of the two a command is, so
the label is derivable rather than hardcoded. - Document it in
docs/coding-engines.md, next to the prefix contract it is a consequence of.
The doc currently explains that everything after the binary passes through verbatim, which is
exactly the sentence a reader needs in order to act on this — it just does not connect the two. - Ship resume-capable presets where the vendor has one. This is the item that needs a
decision and a check per CLI, because a wrong flag is worse than none: a preset that dies on an
unknown flag takes the whole session with it, which is precisely what happened togrok --prompt(coding-engines.ts:262-263: "grok has--prompt-fileand--prompt-jsonbut no
--prompt, so the old preset died on an unknown/ambiguous flag before reaching the model").
Verify against the installed binary, not against a memory of its docs. - Consider a second preset rather than a changed one — e.g.
codexandcodex (continues)—
so the existing default is untouched for anyone relying on a clean-slate turn. Cheap, reversible,
and it makes the trade a visible choice instead of a silent default change.
Alternatives considered and rejected
- Keep a persistent process for raw engines too. Rejected, and already tried:
start()used to
spawn one and it produced an instant "exited with code 1" (headless.ts:387-392) — a
non-interactive binary has nothing to keep alive. It is also what [bug] A one-shot raw engine is judged idle by a 1.5s quiet timer and killed mid-turn — process exit is already the exact signal #391 fixed at the other end, by
making process exit the authoritative turn boundary for one-shot engines. - Have the Pilot re-send the whole transcript as the prompt each turn. Rejected: it pays the
entire history in tokens on every turn against a CLI that is itself paying for its own context, and
the Pilot's decision loop is deliberately snapshot→decide→act rather than transcript-carrying. If
context is genuinely needed the engine's own resume is the mechanism the vendor built for it. - Restore tmux so a session survives between turns. Rejected in Decide: should tmux return as a selectable Coder engine backend? (real PTY + attach) #249's assessment: tmux gives up
metering (Coding Engine spend is invisible in the usage ledger — the largest real cost isn't recorded #267), the acts audit (A delegated run records its outcome but not its consequential acts — merging to main is invisible #294) and merge authority (migration 0091), and a screen buffer is
not a conversation anyway. - Detect resume support automatically (probe
--help). Rejected: parsing a vendor's help text is
a new drift surface per CLI per release, for a value that belongs in a text field the user already
owns.
Acceptance criteria
- The engines panel states, per preset, whether turns share a session — derived from
deriveClientType, not restated per engine. -
docs/coding-engines.mdsays the one-shot spawn means no cross-turn memory, and that a
vendor resume subcommand is the fix, in the same section as the prefix contract. - If resume presets ship: a two-turn session on that engine where turn 2 references turn 1 gets a
correct answer, and the run id is recorded on this ticket.
Regression risk
- A resume flag changes what the engine costs and what it remembers. A session that resumes into
the wrong prior conversation is a worse failure than starting clean — verify the vendor's resume
selects the session the runner intends, not "the last one on this machine", before shipping it as a
default. ENGINE_WRITE_FLAGS(coding-engines.ts:274-285) asserts no shipped default forgets its
write flag. A new preset must appear there too, or an engine ships that cannot edit files.- Presets are per-instance and user-editable; changing
DEFAULT_ENGINESonly affects instances that
have saved nothing (readEnginesfalls back only when the stored list is empty/invalid). Do not
assume a default change reaches existing users — it does not.
Files: packages/browser-runner/src/coding/headless.ts:378-397,463-500,
workers/api/src/lib/coding-engines.ts:253-285, docs/coding-engines.md:1-40,
agents/coder/web/src/EnginesModal.tsx.
Related: #249 (the assessment that asks for this, item 3, and its proposed ADR 0002),
#391 (process exit as the turn boundary for one-shot engines), #248 (the console does not
report what actually resolved), #433 (the docs that should describe the split).
Activity
feat(coding): every engine preset says whether it remembers the previ…
serge-ivo commentedon Aug 8, 2026
Shipped in
91e2928, deployed — bullets 1 and 2 in full. Bullets 3 and 4 deliberately NOT shipped, and the measurement is why.Verified in the deployed console bundle
Fetched
https://proagentstore.online/console/and grepped the served markup — both strings are live:How the label is derived — not hardcoded per engine
EnginesModal.tsxalready importsisClaudeEnginefrom@proagentstore/sdk/ui, andpackages/sdk/src/engine-posture.tsis an explicit, documented mirror of the API'sderiveClientType(same launcher list, sameFOO=bar/npxskipping). The new pure moduleagents/coder/web/src/engine-continuity.tsconsumes that single existing derivation, so no thirdopinion was created and no per-keystroke round trip was added. No API change was needed — the
engines-list response is untouched. A blank command renders nothing.
The line is muted, not
text-warning: one-shot is the design, and shouting it beside the genuineread-only warning teaches people to ignore both.
docs/coding-engines.mdgains the consequence directly under the prefix contract it follows from —which was the whole gap: the doc already explained that everything after the binary passes through
verbatim, and never connected that to what it costs across turns.
Bullets 3 and 4: verified against the installed binaries, and shipping a resume preset would be wrong
codex exec resume --last-c, --continue-r, --resume latest|<index>codex exec resume --lastgenuinely works — turn 2 asked what word turn 1 had said and answeredcorrectly, on session
019fe339-13eb-7540…. Then one unrelatedcodex execwas run in the samedirectory, and the next
resume --lastfollowed that session and answered from the wrongconversation.
That is exactly the failure this ticket's own regression-risk section says must be ruled out first:
"a session that resumes into the wrong prior conversation is a worse failure than starting clean."
And it cannot be pinned — the prefix contract appends only the turn text, so
resume <SESSION_ID>has no slot. On a local-path repo the user's real checkout is the cwd, so the hijacked session
can be one the human started themselves.
Two further findings recorded so nobody rediscovers them:
codex exec resumeaccepts--dangerously-bypass-approvals-and-sandboxbut not--sandbox <mode>, so the shipped writeflag does not carry over; and grok's
-p/--singleis explicitly single-turn and cannot combine with-c. No preset was added, soENGINE_WRITE_FLAGSneeded no change and the third acceptancecriterion ("record the run id") is moot.
The command field is the user's. On a repo only ever driven by the agent the trade may be worth
making — the platform declines to make it for them, and now says enough for them to make it.
Not done
it means fixture plumbing in
e2e/console.spec.ts, a hot shared file two other lanes were editing.The invariants that matter (derived not hardcoded; unknown binary → one-shot; blank → silent) are
unit-tested instead.
docs/adr/holds only 0001. Nothing herecollides with 0001. Whether [enhancement] A raw engine forgets every previous turn — one-shot spawn is the design, and no preset, panel or doc says so #449 or Decide: should tmux return as a selectable Coder engine backend? (real PTY + attach) #249 owns writing it is a call for the analyst — flagging, not
filing.
Full bar green, including
pnpm test:e2echromium + webkit (347 passed).fix(metering): a Codex drive records the absence it cannot measure, a…