Agent Sandbox Registry

python-sdk

Client / Server Alignment: Gap Matrix

docs/en/design/client-server-alignment.md

Client / Server Alignment: Gap Matrix

Status: v2 (reviewed; see fine-grained-capabilities-review.md) — audit of every agent-sandbox-server route against agent-sandbox (api/, protocol/) and the envd (E2B) protocol the client otherwise speaks. The "Status" column is updated when the work lands (see the end of this file for the final tally). Related: fine-grained-capabilities.md.

1. The two data planes

Plane Process Port Who talks to it Protocol
envd (platform-injected, in the official base image) /.fce2b/envd 49983 Sandbox.commands/files/code/get_terminal Connect RPC + GET/POST /files
SandboxServer (agent-sandbox-server, template-authored) agent_sandbox.server 9000 (+ PTY WS 9001) Sandbox.run(name) / list_commands(), plus the typed Sandbox.server client plain HTTP/JSON, SSE, WS

Before this work the client reached exactly two of the ~45 server routes (GET /commands, POST /commands/{name}). Everything else was server-only and unreachable from the SDK. Conversely, the client's envd-oriented surface has features the server does not offer.

Decision: the server is not an envd clone. Instead the client gets a typed ServerClient (Sandbox.server) covering every server route, and the server grows the handful of endpoints needed so that its surface is a functional superset of the envd-facing client modules where that makes sense (watch, background-process IO, PTY control). Deliberate asymmetries are justified in section 4.

2. Server routes -> client coverage

Legend — Status: NEW = added by this work, OK = already aligned, N/A = deliberately one-sided (section 4).

2.1 Core / commands

Server route Endpoint ID Scope Client (before) Client (after) Status
GET /health health — — ServerClient.health() NEW
GET /capabilities capabilities core:read — ServerClient.discover() -> ServerCapabilities NEW
POST /capabilities/check capabilities_check core:read — ServerClient.check() / caps.check_path() NEW (server+client)
GET /commands list_commands commands:list Sandbox.list_commands() server.commands.list() -> ServerCommand[]; Sandbox.list_commands is a thin wrapper NEW typed
POST /commands/{name} run_command commands:run Sandbox.run(name, **kw) server.commands.run(name, **kw); Sandbox.run is a thin wrapper NEW typed + errors

2.2 Files

Server route Endpoint ID envd/client analogue Client (after) Status
GET /files/list files_list files.list (ListDir) server.files.list(path, recursive, max_entries) NEW
GET /files/stat files_stat files.get_info, files.exists server.files.stat(path), .exists(path) NEW
POST /files/mkdir files_mkdir files.make_dir server.files.mkdir(path) NEW
DELETE /files files_delete files.remove server.files.delete(path) NEW
POST /files/move files_move files.move/rename server.files.move(src, dst) NEW
POST /files/search files_search — (no envd analogue) server.files.search(path, pattern, ...) NEW
POST /files/upload-stream files_upload_stream files.write/upload server.files.upload(path, data), .upload_file(local, remote) NEW
GET /files/download-stream files_download_stream files.read/download server.files.download(path), .read_text, .download_file NEW
POST /files/archive files_archive — server.files.archive(paths, format) NEW
GET /files/watch files_watch files.watch (WatchDir) server.files.watch(path) -> async iterator of ServerWatchEvent (a WatchEvent subclass with is_dir) NEW server + client
POST /upload (legacy) upload — server.files.upload_legacy(path, data) NEW (documented legacy)
GET /download (legacy) download — server.files.download_legacy(path) NEW (documented legacy)

2.3 Processes

Server route Endpoint ID envd/client analogue Client (after) Status
POST /shell shell commands.run server.process.shell(command, cwd, env, timeout) NEW; server gains cwd, env, timeout (additive)
POST /shell/stream (SSE) shell_stream commands.stream server.process.shell_stream(...) async iterator of ShellEvent; .run_stream(on_stdout=...) NEW; server gains env (additive)
POST /process/start process_start commands.start (background) server.process.start(command, cwd, env, capture_output, stdin) NEW; server gains env, capture_output, stdin (default off = today)
GET /process/list process_list commands.list server.process.list() NEW
GET /process/{pid} process_detail commands.connect (status part) server.process.get(pid) NEW
POST /process/{pid}/signal process_signal commands.send_signal/kill server.process.signal(pid, sig), .kill(pid) NEW
POST /process/{pid}/stdin process_stdin commands.send_stdin server.process.send_stdin(pid, data) NEW server + client
GET /process/{pid}/output process_output commands.connect (output part) server.process.output(pid, since, wait) NEW server + client

2.4 Terminal

Server route Endpoint ID Client analogue Client (after) Status
POST /pty/sessions pty_create_session Sandbox.get_terminal server.pty.create(...) NEW
GET /pty/sessions pty_list_sessions — server.pty.list() NEW
GET /pty/sessions/{id} pty_detail — server.pty.get(id) NEW server + client
POST /pty/sessions/{id}/resize pty_resize TerminalSession.resize server.pty.resize(id, cols, rows) NEW server + client
DELETE /pty/sessions/{id} pty_delete_session TerminalSession.close server.pty.delete(id) NEW
WS :9001/pty?session_id= (scope terminal:use) TerminalSession speaks the envd /terminal path server.pty.connect(id) -> PtyConnection (async context manager speaking the server's JSON protocol: send, resize, signal, recv, expect, message iteration) NEW

2.5 System

Server route Endpoint ID envd analogue Client (after) Status
GET /system/info system_info — server.system.info() NEW
GET /system/metrics system_metrics envd GET /metrics server.system.metrics() NEW
GET /env env_get envd GET /envs server.system.env(names) NEW
POST /env env_set — server.system.set_env(vars) NEW
GET /ports ports — (Sandbox.network computes URLs locally) server.system.ports() NEW
GET /packages packages — server.system.packages(manager) NEW

2.6 Developer tools and browser (opt-in groups)

Server route Endpoint ID Client analogue Client (after) Status
POST /code/run code_run sandbox.code.run (envd code-interpreter, stateful) server.devtools.run_code(code, language, timeout) NEW (stateless; see N/A-2)
GET /git/status git_status — server.devtools.git_status(path) NEW
GET /git/diff git_diff — server.devtools.git_diff(path, staged, file) NEW
POST /browser/navigate browser_navigate — server.browser.navigate(url, ...) NEW
POST /browser/screenshot browser_screenshot — server.browser.screenshot(...) -> bytes NEW
GET /browser/content browser_content — server.browser.content(type) NEW
POST /browser/click browser_click — server.browser.click(selector) NEW
POST /browser/type browser_type — server.browser.type(selector, text) NEW
POST /browser/evaluate browser_evaluate — server.browser.evaluate(script) NEW
POST /browser/pdf browser_pdf — server.browser.pdf(...) -> bytes NEW
GET /browser/console browser_console — server.browser.console() NEW

3. Client/envd features -> server coverage (the other direction)

Client capability (envd) Server equivalent Action
files.list/exists/get_info/make_dir/remove/move/rename files_* covered (aligned via ServerClient.files)
files.read/read_bytes/write/upload/download files_download_stream / files_upload_stream (base64 JSON) covered
files.watch (WatchDir stream) missing added files_watch (SSE, polling, bounded)
files.upload_url/download_url n/a (pre-signed URL concept is platform-only) N/A-4
commands.run/stream shell, shell_stream covered; added cwd/env/timeout parity
commands.start (background) process_start covered; added env, capture_output, stdin
commands.list process_list covered
commands.connect(pid) missing output attach added process_output (+ ?wait)
commands.send_stdin missing added process_stdin
commands.send_signal/kill process_signal covered
ProcessProtocol.update (pty resize via process) pty_resize added (PTY only)
get_terminal (envd WS /terminal) PTY WS on 9001 with REST session create client server.pty implements the server protocol; envd path unchanged
code.run / contexts (CodeInterpreter RPC) code_run only (stateless) N/A-2
network.get_url/get_host ports (listening ports) covered; independent concepts
sandbox.run(name) commands covered

4. Deliberate one-sided items

5. Cross-cutting alignment

Concern Server Client
Errors {error,type,code,...}, status codes mapped to typed exceptions (fine-grained doc 7)
Auth X-Access-Token / Authorization: Bearer ServerClient(token=...) sends Bearer; gateway headers from Sandbox.network
Discovery GET /capabilities v2 ServerCapabilities
Streaming SSE (shell_stream, files_watch); process_output is cursor-polled (process.follow()) async iterators with deterministic close
Sync — *_sync twin for every public coroutine, same make_sync style
Limits per-endpoint caps.limit(endpoint, key); uploads fail fast on max_bytes

6. Test strategy

7. Changes after review (Draft v1 -> v2)

See fine-grained-capabilities-review.md section 3.

8. Final tally (as implemented)