Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions codex-rs/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,15 @@ struct ExecServerCommand {
#[arg(long = "listen", value_name = "URL", conflicts_with = "remote")]
listen: Option<String>,

/// Register this exec-server as a remote executor using the given base URL.
#[arg(long = "remote", value_name = "URL", requires = "executor_id")]
/// Register this exec-server as a remote environment using the given base URL.
#[arg(long = "remote", value_name = "URL", requires = "environment_id")]
remote: Option<String>,

/// Executor id to attach to when registering remotely.
#[arg(long = "executor-id", value_name = "ID")]
executor_id: Option<String>,
/// Environment id to attach to when registering remotely.
#[arg(long = "environment-id", value_name = "ID")]
environment_id: Option<String>,

/// Human-readable executor name.
/// Human-readable environment name.
#[arg(long = "name", value_name = "NAME")]
name: Option<String>,

Expand Down Expand Up @@ -1491,21 +1491,24 @@ async fn run_exec_server_command(
arg0_paths.codex_linux_sandbox_exe.clone(),
)?;
if let Some(base_url) = cmd.remote {
let executor_id = cmd
.executor_id
.ok_or_else(|| anyhow::anyhow!("--executor-id is required when --remote is set"))?;
let environment_id = cmd
.environment_id
.ok_or_else(|| anyhow::anyhow!("--environment-id is required when --remote is set"))?;
let auth_provider = load_exec_server_remote_auth_provider(
root_config_overrides,
config_profile,
cmd.use_agent_identity_auth,
)
.await?;
let mut remote_config =
codex_exec_server::RemoteExecutorConfig::new(base_url, executor_id, auth_provider)?;
let mut remote_config = codex_exec_server::RemoteEnvironmentConfig::new(
base_url,
environment_id,
auth_provider,
)?;
if let Some(name) = cmd.name {
remote_config.name = name;
}
codex_exec_server::run_remote_executor(remote_config, runtime_paths).await?;
codex_exec_server::run_remote_environment(remote_config, runtime_paths).await?;
return Ok(());
}
let listen_url = cmd
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/tests/suite/rmcp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1808,7 +1808,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
.await;

// Phase 2: start the Streamable HTTP MCP test server in the active
// placement. In full CI this may be the remote executor container; locally
// placement. In full CI this may be the remote environment container; locally
// it is a host process.
let expected_env_value = "propagated-env-http";
let Some(http_server) =
Expand Down
16 changes: 8 additions & 8 deletions codex-rs/exec-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ the wire.
The CLI entrypoint supports:

- `ws://IP:PORT` (default)
- `--remote URL --executor-id ID [--name NAME]`
- `--remote URL --environment-id ID [--name NAME]`

Remote mode registers the local exec-server with the executor registry,
then reconnects to the service-provided rendezvous websocket as the executor.
Remote mode registers the local exec-server with the environment registry,
then reconnects to the service-provided rendezvous websocket as the environment.
It uses the standard Codex ChatGPT sign-in state; run `codex login` first when
remote registration needs authentication. Containerized callers that receive an
Agent Identity JWT in `CODEX_ACCESS_TOKEN` can opt into that auth path with
Expand All @@ -39,7 +39,7 @@ Wire framing:

## Remote Relay Message Format

In remote mode, the harness and executor communicate through rendezvous using
In remote mode, the harness and environment communicate through rendezvous using
`codex.exec_server.relay.v1.RelayMessageFrame`; the checked-in schema is in
`src/proto/codex.exec_server.relay.v1.proto`. The relay frame carries stream
identity plus endpoint-owned reliability metadata:
Expand All @@ -58,8 +58,8 @@ next_seq // resume only: next sender seq
reason // reset only: reset reason
```

`stream_id` identifies one virtual harness/executor JSON-RPC session on the
executor websocket. The harness generates a UUIDv4 `stream_id`; the executor
`stream_id` identifies one virtual harness/environment JSON-RPC session on the
environment websocket. The harness generates a UUIDv4 `stream_id`; the environment
demuxes frames by `stream_id` and runs an independent `ConnectionProcessor` per
stream.

Expand Down Expand Up @@ -375,12 +375,12 @@ The crate exports:
- `DEFAULT_LISTEN_URL` and `ExecServerListenUrlParseError`
- `ExecServerRuntimePaths`
- `run_main()` for embedding the websocket server
- `RemoteExecutorConfig` and `run_remote_executor()` for embedding remote
- `RemoteEnvironmentConfig` and `run_remote_environment()` for embedding remote
registration mode

Callers must pass `ExecServerRuntimePaths` to `run_main()`. The top-level
`codex exec-server` command builds these paths from the `codex` arg0 dispatch
state. `RemoteExecutorConfig::new(...)` also takes the auth provider that
state. `RemoteEnvironmentConfig::new(...)` also takes the auth provider that
remote registration should use; the CLI builds that provider from Codex auth
state before starting remote mode.

Expand Down
24 changes: 12 additions & 12 deletions codex-rs/exec-server/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ struct Inner {
// need serialization so concurrent register/remove operations do not
// overwrite each other's copy-on-write updates.
sessions_write_lock: Mutex<()>,
// Once the transport closes, every executor operation should fail quickly
// Once the transport closes, every environment operation should fail quickly
// with the same canonical message. This client never reconnects, so the
// latch only moves from unset to set once.
disconnected: OnceLock<String>,
Expand Down Expand Up @@ -261,18 +261,18 @@ pub enum ExecServerError {
Protocol(String),
#[error("exec-server rejected request ({code}): {message}")]
Server { code: i64, message: String },
#[error("executor registry request failed ({status}{code_suffix}): {message}", code_suffix = .code.as_ref().map(|code| format!(", {code}")).unwrap_or_default())]
ExecutorRegistryHttp {
#[error("environment registry request failed ({status}{code_suffix}): {message}", code_suffix = .code.as_ref().map(|code| format!(", {code}")).unwrap_or_default())]
EnvironmentRegistryHttp {
status: reqwest::StatusCode,
code: Option<String>,
message: String,
},
#[error("executor registry configuration error: {0}")]
ExecutorRegistryConfig(String),
#[error("executor registry authentication error: {0}")]
ExecutorRegistryAuth(String),
#[error("executor registry request failed: {0}")]
ExecutorRegistryRequest(#[from] reqwest::Error),
#[error("environment registry configuration error: {0}")]
EnvironmentRegistryConfig(String),
#[error("environment registry authentication error: {0}")]
EnvironmentRegistryAuth(String),
#[error("environment registry request failed: {0}")]
EnvironmentRegistryRequest(#[from] reqwest::Error),
}

impl ExecServerClient {
Expand Down Expand Up @@ -495,7 +495,7 @@ impl ExecServerClient {
{
// Reject new work before allocating a JSON-RPC request id. MCP tool
// calls, process writes, and fs operations all pass through here, so
// this is the shared low-level failure path after executor disconnect.
// this is the shared low-level failure path after environment disconnect.
if let Some(error) = self.inner.disconnected_error() {
return Err(error);
}
Expand Down Expand Up @@ -720,7 +720,7 @@ impl Inner {
session: Arc<SessionState>,
) -> Result<(), ExecServerError> {
let _sessions_write_guard = self.sessions_write_lock.lock().await;
// Do not register a process session that can never receive executor
// Do not register a process session that can never receive environment
// notifications. Without this check, remote MCP startup could create a
// dead session and wait for process output that will never arrive.
if let Some(error) = self.disconnected_error() {
Expand Down Expand Up @@ -795,7 +795,7 @@ async fn fail_all_sessions(inner: &Arc<Inner>, message: String) {
for (_, session) in sessions {
// Sessions synthesize a closed read response and emit a pushed Failed
// event. That covers both polling consumers and streaming consumers
// such as executor-backed MCP stdio.
// such as environment-backed MCP stdio.
session.set_failure(message.clone()).await;
}
}
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/exec-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ pub use protocol::TerminateResponse;
pub use protocol::WriteParams;
pub use protocol::WriteResponse;
pub use protocol::WriteStatus;
pub use remote::RemoteExecutorConfig;
pub use remote::run_remote_executor;
pub use remote::RemoteEnvironmentConfig;
pub use remote::run_remote_environment;
pub use runtime_paths::ExecServerRuntimePaths;
pub use server::DEFAULT_LISTEN_URL;
pub use server::ExecServerListenUrlParseError;
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/exec-server/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct StartedExecProcess {
/// stdout, stderr, or pty bytes. `Exited` reports the process exit status, while
/// `Closed` means all output streams have ended and no more output events will
/// arrive. `Failed` is used when the process session cannot continue, for
/// example because the remote executor connection disconnected.
/// example because the remote environment connection disconnected.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecProcessEvent {
Output(ProcessOutputChunk),
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/exec-server/src/relay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ where
}
}

pub(crate) async fn run_multiplexed_executor<S>(
pub(crate) async fn run_multiplexed_environment<S>(
stream: WebSocketStream<S>,
processor: ConnectionProcessor,
) where
Expand Down Expand Up @@ -337,7 +337,7 @@ pub(crate) async fn run_multiplexed_executor<S>(
continue;
}
Some(Err(err)) => {
debug!("multiplexed executor websocket read failed: {err}");
debug!("multiplexed environment websocket read failed: {err}");
break;
}
}
Expand Down
Loading
Loading