Skip to content

fix(ai): add canonical Codex turn attribution - #9488

Open
dannote wants to merge 9 commits into
earendil-works:mainfrom
dannote:fix/codex-turn-attribution
Open

dannote wants to merge 9 commits into
earendil-works:mainfrom
dannote:fix/codex-turn-attribution

Conversation

@dannote

@dannote dannote commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Pi does not send Codex's canonical session, thread, turn, window, and request-kind metadata. Requests within one user input group therefore cannot be reliably attributed across tool continuations, retries, steering, and compaction recovery.

Add a provider-neutral requestIdentity to stream options, generated by the agent runtime and propagated through proxy serialization. Native Codex requests project that identity into the official headers and client_metadata for SSE, WebSocket handshakes, and each WebSocket response.create frame.

OpenAI-compatible adapters can opt in explicitly:

"compat": {
  "codexAttribution": "official"
}

Chat Completions sends the canonical headers. Responses sends the same headers plus client_metadata. Unset preserves existing compatible-provider behavior, and explicit caller or extension headers retain final override control. Proxies must forward the supplied attribution to their backend.

One logical turn ID is reused for the initial request, tool-result continuations, steering, provider retries, session retries, and overflow recovery. New prompts and queued follow-ups receive a new ID. Compaction requests are identified separately. Session and root thread identity remain stable across resume. Context-window numbers are derived from committed compactions on the active branch, without a separate persisted counter.

Attribution changes no longer invalidate otherwise valid WebSocket input deltas. Native Codex also retains the first server-provided x-codex-turn-state for each logical turn, replaying it in HTTP requests or WebSocket frames, not automatic WebSocket handshake headers. New turns receive fresh state. Account and endpoint changes discard old routing and cached connections; stale handshakes cannot restore them or trigger fallback for the new owner. Session cleanup releases the state.

Fixes #9481.

Validation: npm run check and ./test.sh pass on the updated branch. All 82 focused AI tests pass, including stale handshake success/failure, account and endpoint changes, cleanup, concurrent connections, routing replay, and cached input. SDK regressions cover compaction success, failure, cancellation, overflow recovery, resume, tree navigation, and forks.

A live native Codex check with gpt-6-astra at medium reasoning verified a tool continuation after forced reconnect reused the turn's routing state and a subsequent new turn received fresh state. This checks observable routing behavior, not backend quota accounting. The final handshake-race correction was validated with deterministic tests; no additional live run or rebuild was performed for it.

AI-assisted implementation and PR description.

@holny

holny commented Sep 14, 2026

Copy link
Copy Markdown

Read through this since it overlaps the tool-call plumbing we've been poking at lately. Header precedence in buildBaseCodexHeaders looks right — attribution goes in first, the caller's additionalHeaders are applied last, so explicit overrides still win. I side-eyed x-client-request-id being threadId instead of a per-request id, but codex-rs does the same (core/src/client.rs sets it from responses_metadata.thread_id), so that's faithful to the official client. Also like that client_metadata is stripped from the WS input-delta comparison before caching a continuation, so the new attribution can't invalidate delta reuse, while response.create frames still merge turn_state into client_metadata. Nothing else jumped out — the attribution test coverage looks thorough.

@gaoanze888 gaoanze888 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full attribution lifecycle at exact head 08fcee626. The focused agent, proxy, Codex routing, compatible-provider, and coding-agent window tests pass: 108 tests across 7 files.

The identity boundaries are consistent: one foreground turn survives tool continuations, steering, provider/session retries, and overflow recovery; follow-ups/new prompts rotate the turn; compaction uses a separate request kind; committed compactions advance the branch-derived window while failed/cancelled attempts do not. Resume preserves the session/thread identity and a fork receives a new one.

Wire behavior is scoped correctly. Native Codex receives the canonical headers/client metadata; compatible OpenAI adapters require explicit codexAttribution: "official"; caller headers retain final override control. Routing state is isolated across account, endpoint, session, thread, and cleanup, and stale handshakes cannot repopulate cleared state. Metadata is opaque identifiers/timestamps rather than conversation content, and header JSON is ASCII escaped. No blocker found.

This comment is AI-generated by /wr

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.

Align Pi with Codex's canonical turn-attribution metadata

3 participants