Skip to content

feat(codex): let session/resume skip the turn history it discards - #24

Merged
sbis04 merged 1 commit into
mainfrom
ayush/codex-resume-exclude-turns
Oct 1, 2026
Merged

sbis04 merged 1 commit into
mainfrom
ayush/codex-resume-exclude-turns

Conversation

@ayushflow

@ayushflow ayushflow commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

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-infos and dart format are clean, and all 104 dart_acp_codex tests pass.

Summary

session/resume never replays history. _openSession(resume: true) keeps only the session state it builds from thread/resume's metadata (thread.id, model, cwd, sandbox, reasoningEffort) and ignores thread.turns. Codex still answers thread/resume with the thread's whole turn history as one message, every command's aggregatedOutput included. 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

  • New CodexAdapterOptions.excludeTurnsOnResume (also on CodexAcpClientOptions, the same type). Default false. When it's on, session/resume sends excludeTurns: true on thread/resume.
  • session/load never sends it, because it replays the history.
  • When it's off, the request is byte-for-byte what it was before.
  • README: a short paragraph under Configuration, next to the approvals-reviewer one.

Measured against a real codex app-server, on throwaway copies of local threads in a scratch CODEX_HOME:

codex-cli thread thread/resume without the flag with excludeTurns: true
0.159.2 57 MB rollout 18,422,773 B (17.57 MiB), 28 turns 4,032 B, 0 turns
0.159.2 80 MB rollout 17,916,022 B (17.09 MiB), 125 turns 4,198 B, 0 turns
0.131.0 52 MB rollout (older format) 60,615 B, 172 turns 1,344 B, 0 turns
0.124.0 same 52 MB rollout 57,347 B, 172 turns 57,349 B, 172 turns (flag ignored, no error)

Driven through CodexAcpClient the 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:

  • On the resumed 57 MB thread (0.159.2), one follow-up turn asked for the PR number and commit hash the assistant had reported in its last reply before the resume. It answered both correctly from memory: the turn was one user message and one assistant message, with no tool calls.
  • A new thread was told a random code word. Then the app server was restarted and the thread was resumed through CodexAcpClient with 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.

Why it's opt-in. With the flag on, 0.131.0 also skips replaying the thread's restored thread/tokenUsage/updated after the response, so the first ACP usage_update only 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

  • ACP surface/version: none.
  • Transport(s): none. With the option on, the Codex app-server thread/resume request made for session/resume carries excludeTurns: true.
  • Public API or wire-format change: additive. One new optional constructor parameter and field on CodexAdapterOptions, default off. No environment variable, matching workspaceWriteApprovalsReviewer.

Verification

  • dart test in packages/dart_acp_codex (after melos bootstrap, so dart_acp_sdk resolves from the path): 101 pass (99 plus 2 new).
    • The new runtime test goes through the real compiled fake app-server process, which now honours excludeTurns the way Codex does. With the option on and a 1 MiB cap, session/resume of a 2 MiB history succeeds, and session/load of the same thread still fails at the cap, so load still asks for the history.
    • With the flag removed from the request, that test fails with JsonRpcRequestException(-32603, Internal error). The existing "fails a line over the configured maximum" test covers the default-off path.
    • A config test checks the default is 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.dart is now advisory-over at 283 lines (guideline 250, it was 239).
  • Coverage: dart test --coverage-path=coverage/vm.info --branch-coverage then tool/check_coverage.dart: 96.03%.
  • Real app-server runs: codex-cli 0.159.2, 0.131.0 and 0.124.0 on macOS arm64, on rollout copies in a throwaway 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.
  • Merged with feat(codex): let embedded clients allow network in the workspace-write sandbox #21 (833411e, which also touches options.dart, codex_agent.dart and the README): no conflicts, and the merged tree passes 104 tests with dart analyze --fatal-infos clean.
  • Not run: codegen/schema checks (no generated sources touched), web compile/tests (VM-only adapter code), examples/benchmarks smoke, publish dry-run. 0.125 to 0.128 and 0.151 to 0.158 were not run live, so their behaviour above comes from source only.
  • No CHANGELOG.md entry, 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

@ayushflow

ayushflow commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

@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 excludeTurnsOnResume so session/resume stops pulling the whole turn history it already discards. On a real 0.159.2 thread that takes the thread/resume response from 17.57 MiB to 4 KB, and the resumed thread keeps its context (checked live on 0.159.2 and 0.131.0). The Codex version matrix and measurements are in the description.

@ayushflow
ayushflow force-pushed the ayush/codex-resume-exclude-turns branch from 8bbff5b to b69e6da Compare September 30, 2026 13:02
`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>
@ayushflow
ayushflow force-pushed the ayush/codex-resume-exclude-turns branch from b69e6da to ff576f1 Compare October 1, 2026 04:33
@ayushflow

Copy link
Copy Markdown
Contributor Author

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.

@sbis04
sbis04 merged commit 0f00349 into FlutterFlow:main Oct 1, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants