Skip to content

feat(proxy): keep the newest Responses request body beside the events file - #70

Merged
Juliusolsson05 merged 5 commits into
integration/batch-2026-09-27-cxh-vfrom
fix/latest-request-body
Sep 27, 2026
Merged

Juliusolsson05 merged 5 commits into
integration/batch-2026-09-27-cxh-vfrom
fix/latest-request-body

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

For Juliusolsson05/agent-code#1336. App side: Agent Code needs the bump PR in the second list below before the file is reachable for fresh sessions.

Plan: docs/plans/2026-09-27-latest-request-body.md, including review a and steering q96.

Problem

  • What the bundle carries. Agent Code's debug bundle includes the last 5 MiB of proxy-events.jsonl.
  • Why the prompt falls out. On Codex the response chunks are the bulk of that file. After a long stream or a run of /models refreshes, the request event with its body_b64 is out of the tail, and the bundle has no prompt.
  • Measured. In the live corpus, 40 of 65 files had the last inline body more than 5 MiB before EOF, and 10 of 65 after fix(proxy): mirror chunks as base64, as documented (agent-code#372) #53's base64 fix.
  • The Claude precedent. Claude fixed the same loss with a latest-request-body.json sidecar (claude-code-headless#62). Agent Code already appends that file for any provider, but Codex never wrote it.

Change (head 880c6d8)

  • LatestRequestBodySidecar (new src/proxy/latestRequestBody.ts). It keeps the newest main-turn Responses request body beside the events file as one line: {kind:'request-body-latest', requestId, endpoint, body_b64}. It uses the Claude addon's name and kind, and stores the raw on-wire bytes, as in the request event (zstd on current Codex).
  • Which requests (ResponsesProxy): responses* endpoints with a body, excluding requests with an output schema (a heuristic for temporary structured turns such as title generation; a structured MAIN turn, which Agent Code never sends, would leave the file one turn behind), and excluding subagent calls (x-openai-subagent other than compact; review b).
    • request_shape gains has_output_schema (text.format). Codex 0.157 title generation sets it (tui thread_title.rs → codex-api codex_output_schema), and ordinary turns never do.
    • Compaction (responses/compact) is kept.
    • A body whose shape cannot be read is kept.
  • Never an older prompt, at any instant a reader can look:
    1. record() unlinks the public file synchronously, with one metadata call.
    2. A write checks its generation and publishes with renameSync in the same synchronous turn, so no newer record() can slip between check and commit (steering q96). A superseded write discards its temp file.
    3. At most one body is in flight and one pending; older pending bodies are dropped unwritten.
    4. A body over 16 MiB leaves no file.
  • Main-process cost. Base64 is encoded and written asynchronously in 768 KiB slices, one per turn after a drain. The only synchronous calls are the two metadata operations above.
  • Opt-in. The sidecar is created only when eventsFile is set. flushMirror() and stop() also wait for it.
  • Docs. API.md describes the file and has_output_schema. SECURITY.md says that bodies between 2 MiB and 16 MiB are persisted only in the sidecar, and that a crash can leave a .tmp copy.

Tests

  • responsesProxy.latestBody.test.ts sends real requests through the proxy to a local upstream:
    • the newest of two bodies is kept;
    • a /models GET does not replace it;
    • an over-cap body removes it;
    • a title (output-schema) request does not replace the main prompt;
    • compaction is recorded;
    • thread_spawn and review subagent calls do not replace the main prompt, while a compact-tagged request is kept;
    • a body of exactly 16 MiB is kept;
    • a bodied non-Responses POST does not replace the prompt.
  • latestRequestBody.test.ts:
    • a newer record removes the file at once;
    • a superseded in-flight write cannot resurrect an older body;
    • a multi-slice binary body round-trips byte for byte.
  • latestRequestBody.commitFence.test.ts: it holds A's commit, records B, releases A, and reads the public file before B commits. The file is absent or B, never A. It was red on a54cfe7.
  • Mutations killed: the endpoint filter (a bodied /memories/trace_summarize or /alpha/search POST must not replace the prompt; review c found this was NOT pinned before), the schema check, the subagent filter (both directions), the generation check, the unlink, a lowered cap, and the crash-left temp sweep.

Verification

Agent Code side

Review a P1: for a fresh session, the bundle asked only for resume-<providerSessionId>, while the run lives under shell-<paneId>, so no proxy section was bundled at all. That is fixed in the app PR that bumps this package: saveDebugBundle falls back to the pane's own shell- key.

🤖 Generated with Claude Code

Juliusolsson05 and others added 3 commits September 27, 2026 02:52
… file (agent-code#1336)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… slices (review a)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eering q96)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Disposition for review a, plus steering q96 (head aff509f)

Finding Disposition
P1: fresh-session bundles never reach the run App-side, fixed in the Agent Code bump PR. saveDebugBundle asked only for resume-<providerSessionId>, while a fresh run lives under shell-<paneId>. It now falls back to the pane's own shell- key. That is exact provenance, never another session's run. A test fails without the fallback.
P2: stale "latest" after a crash, a failed removal, or a read during a queued write Fixed (a54cfe7, then aff509f). record() unlinks synchronously. The generation check and a renameSync commit run in one turn. At most one body is pending. A directory that refuses the unlink cannot be helped, and that is stated.
q96: a late async rename restored A after B's unlink Fixed (aff509f). The commit is fenced as above. latestRequestBody.commitFence.test.ts holds A's commit, records B, releases A and reads before B commits. It was red on a54cfe7.
P2: title generation replaced the main prompt Fixed (a54cfe7). Requests with an output schema (request_shape.has_output_schema, Codex's codex_output_schema) are skipped. There is a test.
P2: main-process cost and unbounded queue Fixed (a54cfe7). Base64 is encoded in 768 KiB slices, one per turn. At most one body is pending. The only sync calls are two metadata operations.
SECURITY.md said "same prompt text as the mirror" Fixed. Bodies between 2 MiB and 16 MiB are persisted only in the sidecar.
Surviving mutations (compact narrowing, ordering) Fixed. New compaction and superseded-write tests. The narrowing, schema, generation and unlink mutations each fail a test.
zstd has no content-encoding metadata Noted, no change. The bytes equal the request event's body_b64, and readers detect the zstd frame by magic.

The PR body is rewritten for the current design.

… (review b)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Disposition for review b (head d56589b)

  • P1: the issue is not resolved until the app side ships. Agreed, no package change.
    • This PR says "For", not "Fixes", agent-code#1336.
    • The app half is agent-code#1399 (the fresh-session shell- fallback). The gitlink bump follows once agent-code#1366, which moves the same gitlink, and this PR merge.
    • #1336 stays open until both are in.
  • P2: subagent calls replaced the main prompt. Fixed.
    • Requests carrying x-openai-subagent are skipped, except compact, which carries the main conversation.
    • Tests: thread_spawn and review calls after a main prompt leave it in place, and a compact-tagged request is kept. Dropping the filter fails the first test; dropping the compact exception fails the second.
  • 16 MiB cap untested. Fixed. A body of exactly 16 MiB is kept, so lowering the cap to 15 MiB now fails.
  • Crash-left .tmp copies. Documented in SECURITY.md. They are not cleaned on construction; that is noted, not changed.
  • "Never older" is conditional on the unlink succeeding. Stated in the module header, as before.

…ss; honest comments (review c)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Disposition for review c (head 880c6d8)

  • 1 (P2): the endpoint filter had no killing test, and the body's claim was false. Fixed.
    • A new test sends bodied /memories/trace_summarize and /alpha/search POSTs after a main prompt, and the prompt must stay.
    • The startsWith('responses') → true mutation now fails.
    • The body and plan say the filter was not pinned before this round.
  • 2 (P3): has_output_schema is not exclusive to title turns. Comment corrected; no code change.
    • It is a per-turn option upstream. Agent Code sends no output schema on main turns today.
    • A structured main turn would leave the sidecar one turn behind; the body and the CodexRequestShape doc now state that accepted cost.
  • 3 (P3): crash-left .tmp files were only swept after a failure. Fixed. They are now also swept after a successful commit. Test: a planted temp file is gone after the next write, and removing the sweep fails it.
  • 4 (info): the subagent label list. Fixed. It now reads collab_spawn, guardian, … The rule itself is label-agnostic.
  • 5 (info): sequencing with the app half. Agreed. agent-code#1399 is currently held: its fallback can pick a stale run, and a proper launch-time run identity is tracked as agent-code#1405. The package bump waits for #1366 and this PR.

@Juliusolsson05

Copy link
Copy Markdown
Owner Author

READY (W1). Head 880c6d8. Exact-head CI is green, with all six checks. Every reviewer is MERGE-READY: a (verify-report-a), b (verify-report-b), c (verify-report-c). Dispositions for reviews a, b, c and steering q96 are posted above. merge-gate.sh --member: GATE PASS.

App-side sequencing: the Agent Code bump to this package waits for agent-code#1366, which moves the same gitlink. The fresh-session half, agent-code#1399, is held: its fallback can pick a stale run, and the proper fix is agent-code#1405. Merging this package PR alone does not close agent-code#1336.

@Juliusolsson05
Juliusolsson05 changed the base branch from main to integration/batch-2026-09-27-cxh-v September 27, 2026 23:40
@Juliusolsson05
Juliusolsson05 merged commit 04afc42 into integration/batch-2026-09-27-cxh-v Sep 27, 2026
6 checks passed
Juliusolsson05 added a commit that referenced this pull request Sep 27, 2026
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.

1 participant