Skip to content

code-mode: define process host wire protocol - #29804

Merged
cconger merged 4 commits into
mainfrom
cconger/process-owned-session-protocol
Jun 25, 2026
Merged

code-mode: define process host wire protocol#29804
cconger merged 4 commits into
mainfrom
cconger/process-owned-session-protocol

Conversation

@cconger

@cconger cconger commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Why

The process-owned code mode implementation needs an explicit, bounded wire contract before either side depends on it. Keeping framing and message semantics in codex-code-mode-protocol gives the client and sidecar one shared source of truth and makes compatibility failures detectable during connection setup.

What changed

  • adds a versioned client/host handshake with required and optional capabilities
  • defines operation requests and responses for session lifecycle and cell control
  • defines reverse delegate request, response, cancellation, and cell-closure messages
  • adds a four-byte little-endian length-prefixed JSON codec with a hard frame cap
  • rejects malformed frames, unknown fields, invalid identifiers, and unsupported protocol states
  • locks the wire representation down with explicit JSON round-trip tests

Testing

  • just test -p codex-code-mode-protocol

Stack

Part 1 of 6. Followed by #29805.

use tokio::io::AsyncWriteExt;

/// Maximum JSON payload size accepted for one IPC frame.
pub const MAX_FRAME_BYTES: usize = 8 * 1024 * 1024;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is low no? Looks lower than current code mode

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Bumped to 64MiB to match exec-server protocol. I chose something small because the usual token limit for output is around 10_000 tokens ~40kb so was just padding for large tool calls. We can go higher even if needed.

#[serde(rename = "execute/initialResponse")]
InitialResponse {
id: RequestId,
result: WireResult<RuntimeResponse>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we avoid putting the session/domain types directly on the V1 wire? These nested types use their default permissive Serde forms, so the outer deny_unknown_fields does not apply and internal changes can silently alter V1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, sorry. dumb miss.

@@ -94,16 +100,36 @@ fn handshake_wire_contract_is_explicit_and_round_trips() {
fn session_lifecycle_wire_contract_is_explicit_and_round_trips() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we pin every V1 variant here?

@cconger
cconger requested a review from jif-oai June 24, 2026 20:39
@cconger
cconger marked this pull request as ready for review June 24, 2026 20:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc071daeec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/code-mode-protocol/src/host/message.rs

@jif-oai jif-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit but lgtm

/// The complete wait request shape supported by protocol V1.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct WireWaitRequest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do we replay a canceled/lost wait response? Once the host receives the frontier it has consumed that output, and retrying advances to the next frontier
I think this is out of scope but we will need to answer this one day

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

out of scope for now, we crash on this today.

I think we can either do 3 way handshake here to confirm transfer and release resources on the sender. Alternative we keep a ringbuffer and read offsets. But yeah lets think about this some more.

pub enabled_tools: Vec<WireToolDefinition>,
pub source: String,
pub yield_time_ms: Option<u64>,
pub max_output_tokens: Option<usize>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit can you use i32 here please? For a lot of reason we should avoid unsigned int (should be very small to fix)

use serde::Serializer;
use serde::de::Error as _;

pub type RequestId = u64;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

type aliases are dangerous. Either keep just i64 (please no unsigned int when possible) or use something like

#[serde(transparent)]
pub struct RequestId(u64);

@cconger cconger closed this Jun 24, 2026
@cconger cconger reopened this Jun 24, 2026
@cconger
cconger force-pushed the cconger/process-owned-session-protocol branch from e0fac5a to 125a59b Compare June 24, 2026 22:26
@cconger
cconger force-pushed the cconger/process-owned-session-protocol branch from 125a59b to c52c04b Compare June 25, 2026 01:08
@cconger
cconger merged commit b3e1c33 into main Jun 25, 2026
31 checks passed
@cconger
cconger deleted the cconger/process-owned-session-protocol branch June 25, 2026 03:03
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants