Skip to content

Commit ff68823

Browse files
isolate codex continuation state per Claude agent
Claude Code SubAgents share a session ID while carrying distinct Agent IDs. Session-only continuation ownership allowed sibling Agents to consume or replace one another’s continuation and reusable WebSocket state. Carry validated conversation identity through HTTP ingress and key Codex continuation and socket ownership by Main or direct Agent identity. Require the exact live origin socket for response continuation, with one safe full-context recovery when provenance cannot be established. Preserve cancellation and retry cleanup, and keep malformed and auto-review requests stateless. Previous-response continuation remains disabled by default.
1 parent f4b22e5 commit ff68823

14 files changed

Lines changed: 6319 additions & 896 deletions

File tree

docs/src/content/docs/providers/codex.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ WebSocket is the default transport. Set `CCP_CODEX_TRANSPORT=http` for HTTP SSE,
5454

5555
WebSocket setup honors `HTTP_PROXY` for `ws://`, `HTTPS_PROXY` for the default `wss://` endpoint, `ALL_PROXY` as a fallback, and `NO_PROXY` exclusions. A normal HTTP proxy can therefore carry the default WebSocket connection with CONNECT; TUN mode is not required. Set proxy variables before starting the process and restart after changing them. For example, setting `HTTPS_PROXY` to `http://127.0.0.1:7890` sends HTTPS/WSS destinations through the HTTP proxy at port 7890; it does not require an `https://` proxy URL.
5656

57-
`CCP_CODEX_PREVIOUS_RESPONSE_ID=1` enables append-only WebSocket continuation. It reuses a session connection and sends `previous_response_id` only when the translated request shape and transcript extension are safe. State is in memory, keyed by Claude Code session ID.
57+
`CCP_CODEX_PREVIOUS_RESPONSE_ID=1` enables append-only WebSocket continuation. A valid identity containing only a Claude Code session ID owns the Main continuation for that session. Each valid direct Agent ID owns an independent continuation and reusable WebSocket within the same session. Nested Agents are keyed by their direct child ID; the parent ID is validated but does not become part of the owner key. The proxy sends `previous_response_id` only when the translated request shape and transcript extension are safe, and only on the exact live WebSocket that produced that response.
58+
59+
An absent, malformed, or ambiguous identity does not reject the HTTP request; that request proceeds without continuation or WebSocket reuse. If the originating socket is missing, dead, or has been replaced, the proxy retries once with the full translated input and without the stale response ID. Continuation and connection state is held only in memory and is lost when the proxy restarts.
60+
61+
Detected auto-review classifier subrequests are intentionally stateless even when valid session and Agent headers are present. They neither consume nor publish continuation or WebSocket ownership.
5862

5963
## Server compaction
6064

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod project;
99
pub mod provider;
1010
pub mod providers;
1111
pub mod registry;
12+
pub mod request_identity;
1213
pub mod retry;
1314
pub mod server;
1415
pub mod session;

src/provider.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::anthropic::schema::MessagesRequest;
22
use crate::monitor::MonitorHandle;
3+
use crate::request_identity::ConversationIdentity;
34
use crate::traffic::TrafficCapture;
45
use anyhow::Result;
56
use async_trait::async_trait;
@@ -26,6 +27,17 @@ pub trait Provider: Send + Sync {
2627
fn supported_models(&self) -> Vec<String>;
2728
fn cli(&self) -> &'static dyn CliHandlers;
2829
async fn handle_messages(&self, body: MessagesRequest, ctx: RequestContext) -> Response;
30+
31+
async fn handle_messages_with_conversation_identity(
32+
&self,
33+
body: MessagesRequest,
34+
ctx: RequestContext,
35+
conversation_identity: Option<ConversationIdentity>,
36+
) -> Response {
37+
let _ = conversation_identity;
38+
self.handle_messages(body, ctx).await
39+
}
40+
2941
async fn handle_count_tokens(&self, body: MessagesRequest, ctx: RequestContext) -> Response;
3042

3143
async fn generate_anthropic_stream(

src/providers/codex/client.rs

Lines changed: 1242 additions & 161 deletions
Large diffs are not rendered by default.

src/providers/codex/compaction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub async fn request_compaction(
8383
compaction_request.include = Some(vec!["reasoning.encrypted_content".to_string()]);
8484

8585
let response = client
86-
.post_codex(&compaction_request, ctx, None)
86+
.post_codex_for_owner(&compaction_request, ctx, None)
8787
.await
8888
.map_err(CompactionError::Upstream)?;
8989
let compaction = parse_compaction_response(&response.body)?;

0 commit comments

Comments
 (0)