codex-rs/exec-server/README.md
codex-exec-server is a small standalone JSON-RPC server for spawning
and controlling subprocesses through codex-utils-pty.
This PR intentionally lands only the standalone binary, client, wire protocol, and docs. Exec and filesystem methods are stubbed server-side here and are implemented in follow-up PRs.
It currently provides:
codex-exec-serverExecServerClientThis crate is intentionally narrow. It is not wired into the main Codex CLI or unified-exec in this PR; it is only the standalone transport layer.
The server speaks the shared codex-app-server-protocol message envelope on
the wire.
The standalone binary supports:
ws://IP:PORT (default)Wire framing:
Each connection follows this sequence:
initialize.initialize response.initialized.If the server receives any notification other than initialized, it replies
with an error using request id -1.
If the websocket connection closes, the server terminates any remaining managed processes for that client connection.
initializeInitial handshake request.
Request params:
{
"clientName": "my-client"
}
Response:
{}
initializedHandshake acknowledgement notification sent by the client after a successful
initialize response.
Params are currently ignored. Sending any other notification method is treated as an invalid request.
command/execStarts a new managed process.
Request params:
{
"processId": "proc-1",
"argv": ["bash", "-lc", "printf 'hello\\n'"],
"cwd": "/absolute/working/directory",
"env": {
"PATH": "/usr/bin:/bin"
},
"tty": true,
"outputBytesCap": 16384,
"arg0": null
}
Field definitions:
processId: caller-chosen stable id for this process within the connection.argv: command vector. It must be non-empty.cwd: absolute working directory used for the child process.env: environment variables passed to the child process.tty: when true, spawn a PTY-backed interactive process; when false,
spawn a pipe-backed process with closed stdin.outputBytesCap: maximum retained stdout/stderr bytes per stream for the
in-memory buffer. Defaults to codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP.arg0: optional argv0 override forwarded to codex-utils-pty.Response:
{
"processId": "proc-1",
"running": true,
"exitCode": null,
"stdout": null,
"stderr": null
}
Behavior notes:
processId is rejected.command/exec/write.command/exec/outputDelta.command/exec/exited.command/exec/writeWrites raw bytes to a running PTY-backed process stdin.
Request params:
{
"processId": "proc-1",
"chunk": "aGVsbG8K"
}
chunk is base64-encoded raw bytes. In the example above it is hello\n.
Response:
{
"accepted": true
}
Behavior notes:
processId are rejected.command/exec/terminateTerminates a running managed process.
Request params:
{
"processId": "proc-1"
}
Response:
{
"running": true
}
If the process is already unknown or already removed, the server responds with:
{
"running": false
}
command/exec/outputDeltaStreaming output chunk from a running process.
Params:
{
"processId": "proc-1",
"stream": "stdout",
"chunk": "aGVsbG8K"
}
Fields:
processId: process identifierstream: "stdout" or "stderr"chunk: base64-encoded output bytescommand/exec/exitedFinal process exit notification.
Params:
{
"processId": "proc-1",
"exitCode": 0
}
The server returns JSON-RPC errors with these codes:
-32600: invalid request-32602: invalid params-32603: internal errorTypical error cases:
argvprocessIdThe crate exports:
ExecServerClientExecServerErrorExecServerClientConnectOptionsRemoteExecServerConnectArgsInitializeParams and InitializeResponseDEFAULT_LISTEN_URL and ExecServerListenUrlParseErrorrun_main_with_listen_url()run_main() for embedding the websocket server in a binaryInitialize:
{"id":1,"method":"initialize","params":{"clientName":"example-client"}}
{"id":1,"result":{}}
{"method":"initialized","params":{}}
Start a process:
{"id":2,"method":"command/exec","params":{"processId":"proc-1","argv":["bash","-lc","printf 'ready\\n'; while IFS= read -r line; do printf 'echo:%s\\n' \"$line\"; done"],"cwd":"/tmp","env":{"PATH":"/usr/bin:/bin"},"tty":true,"outputBytesCap":4096,"arg0":null}}
{"id":2,"result":{"processId":"proc-1","running":true,"exitCode":null,"stdout":null,"stderr":null}}
{"method":"command/exec/outputDelta","params":{"processId":"proc-1","stream":"stdout","chunk":"cmVhZHkK"}}
Write to the process:
{"id":3,"method":"command/exec/write","params":{"processId":"proc-1","chunk":"aGVsbG8K"}}
{"id":3,"result":{"accepted":true}}
{"method":"command/exec/outputDelta","params":{"processId":"proc-1","stream":"stdout","chunk":"ZWNobzpoZWxsbwo="}}
Terminate it:
{"id":4,"method":"command/exec/terminate","params":{"processId":"proc-1"}}
{"id":4,"result":{"running":true}}
{"method":"command/exec/exited","params":{"processId":"proc-1","exitCode":0}}