Repository navigation
feat(codex): let session/resume skip the turn history it discards - #24
Conversation
|
@Salakar could you review this when you get a chance? (I can't request reviewers on this repo from the fork.) It's stacked on #22, so only the last commit (b69e6da) is new. It adds an opt-in |
8bbff5b to
b69e6da
Compare
`session/resume` never replays history: `_openSession` keeps only the session state built from `thread/resume`'s metadata. But `thread/resume` still answers with the thread's whole turn history as one message, every command's `aggregatedOutput` included, so a long-lived thread's resume is tens of MiB that the adapter decodes only to drop. cfe3d50 made that survivable. This lets a client not ask for it. - `CodexAdapterOptions.excludeTurnsOnResume` (also on `CodexAcpClientOptions`), default false. When set, `session/resume` sends `excludeTurns: true` on `thread/resume`. `session/load`, which replays the history, never sends it. When unset the request is unchanged. Measured against a real `codex app-server` on throwaway copies of local threads in a scratch CODEX_HOME: - codex-cli 0.159.2: a 57 MB rollout resumes as 18,422,773 bytes (28 turns) without the flag and 4,032 bytes (0 turns) with it. Through `CodexAcpClient`, session/resume takes 599 ms vs 45 ms. - A follow-up turn on that resumed thread, with the flag, answered a question only the pre-resume history could answer, with no tool calls. A new thread told a code word, restarted, and resumed with the flag, returned the word on both 0.159.2 and 0.131.0. Across versions, from the Codex source (openai/codex#19014 added the flag, #20499 marked it experimental, #40673 made it stable) and checked live where marked: - before 0.125: unknown field, ignored. 0.124.0 (live) returns the full history with or without it, no error. - 0.125 to 0.128: stable. - 0.129 to 0.150: experimental. 0.131.0 (live) honors it because this adapter declares `experimentalApi`; without that capability it returns `-32600 thread/resume.excludeTurns requires experimentalApi capability`. - 0.151 onward: stable. 0.159.2 (live) also stops sending the "full-history hydration is deprecated" notice. One behavior difference: 0.131.0 also skips replaying the restored `thread/tokenUsage/updated` when the flag is set, so the first `usage_update` arrives with the next turn. 0.159.2 still sends it. That is why this is opt-in. Verified: 101 tests pass in `dart_acp_codex` (2 new), `dart analyze --fatal-infos` clean, `dart format` clean, coverage 96.03%. The new runtime test resumes a 2 MiB history under a 1 MiB cap through the real fake app-server process; with the flag removed from the request it fails with `-32603 Internal error`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b69e6da to
ff576f1
Compare
|
Rebased onto main now that #21 and #22 are in. The squash merges left #22's original commit in this branch, which caused the conflict. It's a single commit now (ff576f1), with exactly the change you approved; the +/- lines are identical to b69e6da. Analyze and format are clean and all 104 dart_acp_codex tests pass. |
Rebased onto main after #21 and #22 merged. It's one commit now (ff576f1), with the same change as the approved b69e6da. The rebase applied with no conflicts.
dart analyze --fatal-infosanddart formatare clean, and all 104dart_acp_codextests pass.Summary
session/resumenever replays history._openSession(resume: true)keeps only the session state it builds fromthread/resume's metadata (thread.id,model,cwd,sandbox,reasoningEffort) and ignoresthread.turns. Codex still answersthread/resumewith the thread's whole turn history as one message, every command'saggregatedOutputincluded. For a long-lived thread that's tens of MiB, decoded only to be thrown away. #22 made it survivable by raising the line cap. This PR lets a client not ask for it at all.This follows up the "not in this PR" note on #22, and a review of the FlutterFlow side, which asked for it because resume "still decodes the whole history on the UI isolate only to discard it".
Change
CodexAdapterOptions.excludeTurnsOnResume(also onCodexAcpClientOptions, the same type). Defaultfalse. When it's on,session/resumesendsexcludeTurns: trueonthread/resume.session/loadnever sends it, because it replays the history.Measured against a real
codex app-server, on throwaway copies of local threads in a scratchCODEX_HOME:thread/resumewithout the flagexcludeTurns: trueDriven through
CodexAcpClientthe way an embedded client resumes, with a stdio proxy logging the wire, the 57 MB thread on 0.159.2 takes 599 ms without the flag and 45 ms with it.Does the resumed thread keep its context? Yes. The model's context comes from the rollout, not from this response. Checked live, with the flag on:
CodexAcpClientwith the flag on (0 turns in the response). Asked for the word, the model returned it exactly, on both 0.159.2 and 0.131.0.Codex versions. From the Codex source, with the versions marked "live" also checked against real binaries. Release boundaries come from tag ancestry.
ThreadResumeParamsdoesn'tdeny_unknown_fields, so it's ignored. 0.124.0 (live) returns the full history with or without it, with no error.experimentalApi. Without that capability it returns-32600 thread/resume.excludeTurns requires experimentalApi capability.deprecationNotice("Full-history hydration is deprecated for paginated threads; useexcludeTurns: true…").codex execitself resumes this way (Skip restored token usage replay for exec resumes openai/codex#35621).Why it's opt-in. With the flag on, 0.131.0 also skips replaying the thread's restored
thread/tokenUsage/updatedafter the response, so the first ACPusage_updateonly arrives with the next turn. 0.159.2 still sends it. A client that renders context usage right after resume might care, so the default leaves behaviour unchanged.Protocol impact
thread/resumerequest made forsession/resumecarriesexcludeTurns: true.CodexAdapterOptions, default off. No environment variable, matchingworkspaceWriteApprovalsReviewer.Verification
dart testinpackages/dart_acp_codex(aftermelos bootstrap, sodart_acp_sdkresolves from the path): 101 pass (99 plus 2 new).excludeTurnsthe way Codex does. With the option on and a 1 MiB cap,session/resumeof a 2 MiB history succeeds, andsession/loadof the same thread still fails at the cap, so load still asks for the history.JsonRpcRequestException(-32603, Internal error). The existing "fails a line over the configured maximum" test covers the default-off path.false.dart analyze --fatal-infos(package): no issues.dart format --set-exit-if-changed(package): clean.dart run tool/check_reference_test_matrix.dart: pass.dart run tool/check_file_sizes.dart:runtime_test.dartis now advisory-over at 283 lines (guideline 250, it was 239).dart test --coverage-path=coverage/vm.info --branch-coveragethentool/check_coverage.dart: 96.03%.CODEX_HOME, deleted afterwards. The 0.124.0 binary is signed with a Developer ID certificate that macOS now reports as revoked (CSSMERR_TP_CERT_REVOKED), so it ran as an ad-hoc re-signed scratch copy.options.dart,codex_agent.dartand the README): no conflicts, and the merged tree passes 104 tests withdart analyze --fatal-infosclean.CHANGELOG.mdentry, matching fix(codex): reopen a thread whose history outgrew 16 MiB #22 and the other changes since 0.1.5. Happy to add one.🤖 Generated with Claude Code