Skip to content

Exec bytes are always base64, and the protocol is one protocol - #601

Merged
jaredLunde merged 12 commits into
mainfrom
jared/exec-bytes-always-base64
Oct 27, 2026
Merged

jaredLunde merged 12 commits into
mainfrom
jared/exec-bytes-always-base64

Conversation

@jaredLunde

Copy link
Copy Markdown
Contributor

Replaces the bytes design of #587 and removes the version ladder. There is one exec protocol; every endpoint is execd or implements all of it; nothing is negotiated or probed.

The contract

  • One-shot request: { "command", "args", "cwd"?, "timeout_ms", "stdin"? }, stdin standard base64.
  • One-shot response: { "exit_code", "stdout", "stderr" }, stdout and stderr always standard padded base64 (the empty string for none). One shape, no flag; the bytes are exact by construction. The cap is on the decoded bytes (422 unchanged); the timeout note is appended to the stderr bytes.
  • Streaming (stream: true), jobs (job) and job notify are features of the same protocol, not versions.

Deleted

  • output_base64, the output probe, the stream-for-exactness path, exact everywhere (Rust, TS, Python, DRIVER.md, drivers), exact_exec/output_base64 capabilities, fault-v12-text, the lossy scenarios, the mock's honors_output_base64/lossy_output_base64, exec_endpoint/exact.rs.
  • Every protocol probe: stdin (cat echo), streaming, jobs, notify. The argv-chunk write fallback. HttpExecRunner's fall-back-to-baseline jobs. Notify advertisement (EXECD_KEPT_JOBS_SURVIVE_IDLE, N1-N3, N9) and the baseline's notifier.
  • Fault backends that only exercised fallbacks (fault-no-stdin, fault-v11, fault-baseline-jobs, local-no-stdin), and the mock options behind them (honors_stdin, Protocol::V11, jobs, refuses_extensions, notify, kept_jobs_survive_idle).
  • Spec: no version history; the "Exact output" section is a short "Bytes" paragraph.

Environment probe

The tools the file helpers rely on (rg, grep -Z, find -printf, the dd/base64 read window) are checked in one sh -c (script F of FILES-OVER-EXEC.md), lazily on the first operation that needs it, cached per sandbox. A write never probes. Round trips before the first file op, Sandbox::connect then a read: 6 before (five sequential probes, then the read), 2 after; a write: 6 before, 1 after.

Clients

  • Rust: one run returning ExecResult with Vec<u8> output; ExecOutput is code plus bytes; no run_exact. stdout_text() is the one place bytes become text; ShellFs has one Text conversion. CellRunner forwards run only.
  • TypeScript and Python (sync and async): raw exec returns bytes; helpers decode in one place. Both read the shared output.json vectors.
  • The agent's bash appends the raw bytes to its accumulator; the model sees text from there.

Also changed

  • Fixtures renamed without versions: output.json, frames.json, jobs.json, notify-*.json; output.json is rewritten for the new shape (valid answers, invalid answers).
  • corrupts_base64 on the mock, and a verifier test that catches it. The verifier keeps one output check: every byte value 0-255 on stdout and stderr, plus both streams with an exit code.
  • conformance: randomized differential testing across every client #590's differential test: the exec and stdin generators now produce arbitrary bytes, and its normalizer replaces the root in bytes, not text. The limitation note is gone.
  • sandbox-mcp (merged in sandbox-mcp: an MCP server over the sandbox client, with a conformance driver and release #584) adapted: the sandbox://capabilities resource reports search_engine, find_printf, read_window; SANDBOX_JOBS_ROOT and the stdin cap override are gone; the exec tool keeps its text-when-UTF-8-else-base64 rule.
  • exec-live CI target uses docker exec -i (writes send stdin), and the curl notify target and sandbox_jobs_notify are gone.
  • crates/execd: tests/output.rs and tests/timeout.rs replace exact_output.rs/v11_timeout.rs; docs and README updated. Every ARCHITECTURE.md/README that named the old design is updated.

Proof (merged tree)

Matrix, 0 fail on every cell (pass / known-defect / unsupported / skipped):

driver local-plain local-bearer local-grep local-busybox-grep alpine debian-runas readonly tinydisk
rust 468/3/0/65 497/3/0/36 462/0/0/74 444/0/0/92 459/0/0/77 489/0/0/47 501/0/0/35 498/0/0/38
ts 466/5/0/65 495/5/0/36 462/0/0/74 444/0/0/92 459/0/0/77 489/0/0/47 501/0/0/35 498/0/0/38
python, python-async 468/3/0/65 497/3/0/36 462/0/0/74 444/0/0/92 459/0/0/77 489/0/0/47 501/0/0/35 498/0/0/38
mcp 401/3/95/37 402/3/95/36 395/0/95/46 377/0/95/64 364/0/95/77 394/0/95/47 406/0/95/35 403/0/95/38

(mcp reports exec_stream, the job-stream ops and caller-chosen mtime unsupported, as before.) test:differential passes on all five backends with five drivers. cargo clippy --workspace --all-targets -D warnings, cargo fmt, dprint check, TS (tsc, vitest 123) and Python (pytest 157, ruff, mypy --strict) are clean. execd's own tests pass.

Notes

  • Not removed: ShellJobs (jobs over plain commands) remains for non-execd transports (TemplateRunner, this host); it offers no notify.
  • With no advertisement, a sandbox that cannot dial out or is stopped when idle breaks notify rather than falling back; notify is available only when the replica has --notify-origin. The e2e modes Baseline and NoEgress are gone with the advertisement they tested.
  • Requirement tags N2, N3 and N9 are removed from the spec and registry; numbers are not reused.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64

jaredLunde and others added 12 commits October 27, 2026 12:25
A one-shot answer is {exit_code, stdout, stderr} with stdout and stderr always standard
base64, so the bytes are exact by construction; the request carries stdin as base64.
There is no output_base64 flag, no exact flag, no fallback to a stream for exactness, and
no lossy mode. Raw exec returns bytes in the Rust, TypeScript and Python clients; helpers
decode text in one place.

The version ladder is gone with it: no stdin, streaming, jobs or notify probes, no argv
chunk write, no client-side job emulation over an endpoint, and no fault backends whose
only purpose was to exercise those fallbacks. An endpoint is execd or implements the
whole contract. The file helpers look at the sandbox's tools once, in one lazy command.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
… follow the base64 answer

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
… single protocol

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
…point for its own tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
… merged parsers

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
…ppy and fuzz corpus follow

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJRTKuPpNNqCLEdyUwZj64
@jaredLunde
jaredLunde added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 6c53321 Oct 27, 2026
30 checks passed
@jaredLunde
jaredLunde deleted the jared/exec-bytes-always-base64 branch October 27, 2026 22:49
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.

1 participant