Repository navigation
feat: add e2b-acp for running ACP agents in a sandbox - #2061
OndrejDrapalik wants to merge 27 commits into
Conversation
Python counterpart of @e2b/acp: newline-delimited JSON framing with a validated per-message size limit, the initialize capability rewrite (also inside a batch) that turns off client fs and terminal, and method-not-found replies for agent fs/* and terminal/* requests.
connect_acp_agent(sandbox, command, client) starts the agent in an AsyncSandbox and returns an AcpAgent with the agent-client-protocol connection, close(), wait() and kill(). Async only: the ACP Python SDK is asynchronous. Messages reach receive() through a deque, so a cancelled receive never loses one. Writes, replies and EOF share one lock. close() bounds the whole shutdown from the call, kills an agent whose output stream failed without a known exit, keeps buffered messages and drops later ones, also when the reader waited for room in the queue.
Run the e2b-acp suite when the package or the Python SDK changes, and include it in the release preflight, tests and publish.
kill() now releases agent output that waits for room in the read queue, so the command handle can see the exit and wait() returns.
The line splitter kept the unfinished line as one string and split it again on every chunk, which made a long message quadratic to read. It now keeps the chunks and their byte count and joins them once.
…op() connect_acp_agent() takes its optional settings as keyword arguments only. stop() ends the agent process that connect_acp_agent() started, and its default timeout is the exported DEFAULT_STOP_TIMEOUT.
A failed output stream, for example after on_stderr raises, left the agent running with no working connection. The transport now kills it; receive() and wait() still raise the original error.
…e size error at max_message_bytes Also document the defaults of stop() and max_message_bytes, export DEFAULT_MAX_MESSAGE_BYTES and StderrHandler, and describe the Python SDK streaming hook as internal in the changeset.
…ption #2044 added retain_output to commands.run and commands.connect, so the private Commands._start hook this branch carried is gone. The transport starts the agent with run(background=True, retain_output=False) and the changeset no longer bumps the Python SDK. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace connect_acp_agent() with the AcpAgent.start() classmethod and import the ClientSideConnection type from acp.core, so importing e2b_acp no longer raises a DeprecationWarning.
The client can be a function that creates it from the agent connection, as acp.connect_to_agent and acp.spawn_agent_process allow. The agent is killed if the factory raises.
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🦋 Changeset detectedLatest commit: 265e519 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Package ArtifactsBuilt from a12914d. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-dockerfile-utils-0.1.2-int-155-acp-python.0.tgz ./e2b-2.62.1-int-155-acp-python.0.tgzCLI ( npm install ./e2b-cli-2.23.1-int-155-acp-python.0.tgzCode Interpreter JS SDK ( npm install ./e2b-code-interpreter-2.9.1-int-155-acp-python.0.tgzDesktop JS SDK ( npm install ./e2b-desktop-2.5.1-int-155-acp-python.0.tgzPython SDK ( pip install ./e2b_dockerfile_utils-0.1.1+int.155.acp.python-py3-none-any.whl ./e2b-2.62.0+int.155.acp.python-py3-none-any.whlCode Interpreter Python SDK ( pip install ./e2b_code_interpreter-2.11.6+int.155.acp.python-py3-none-any.whlDesktop Python SDK ( pip install ./e2b_desktop-2.7.2+int.155.acp.python-py3-none-any.whl |
AcpAgent is async-only, so it takes the Async prefix (TASTE T-2), and ACP keeps its casing like the JS names (T-12). wait() now documents that a non-zero exit is returned, not raised.
Mirrors ACPAgentStopOpts in @e2b/acp, and marks the AsyncACPAgent constructor as internal.
|
@OndrejDrapalik two things I can't do from my side (no push or rerun rights on e2b-dev/E2B):
|
Summary
Adds
e2b-acp(packages/acp-python) for running Agent Client Protocol agents (goose, OpenCode, Claude Code through its ACP adapter) inside an E2B sandbox and driving them with the official Python ACP client,agent-client-protocol.AsyncACPAgent.start(sandbox, command, client, *, ...)starts the agent with stdin open, speaks newline-delimited JSON-RPC over its stdin/stdout through anacpTransport(send/receive/close), and returns anAsyncACPAgentwith the ACP connection (fromacp.connect_to_agent) pluspid,wait(),stop()andkill(). The client filesystem and terminal capabilities are turned off ininitialize, and agent requests forfs/*/terminal/*are rejected in the transport, so the agent edits files and runs commands with its own tools inside the sandbox. Network access and MCP servers passed to the agent are not restricted. The ACP Python SDK is async-only, so the package takes anAsyncSandbox.clientis an ACP client or a function that creates one from the connection, like inacp.connect_to_agent.The agent is started with
commands.run(..., background=True, retain_output=False)from #2044 (SDK-407), so a long-running agent does not grow the handle's buffers. No core changes. SDK-408 tracks dropping retained output for background commands in the next major.Split out of #1992 together with the JS package (#2060). The two PRs are independent and can merge in either order.
Usage
Review guide
The diff is +2.8k lines, but
uv.lock(1.3k, generated), tests and CI make up most of it.Worth reading (~589 lines):
packages/acp-python/e2b_acp/transport.pypackages/acp-python/e2b_acp/utils.pyinitializerewrite,fs/*/terminal/*rejectionpackages/acp-python/e2b_acp/__init__.pyAsyncACPAgentandAsyncACPAgent.startSkim:
.github/workflows/acp_python_tests.ymland the ACP Python entries insdk_tests.yml/release.yml(mirror code-interpreter),pyproject.toml,package.json, README,.changeset/acp-python-package.md.For automated reviewers
Behaviour the code is meant to guarantee, and the test that fails if it breaks. Offline tests use a fake command handle and need no API key; live tests start a scripted Python agent in a sandbox.
initializeforces them off (also inside a batch), agentfs/*/terminal/*requests get-32601, notifications are droppedrestrict_client_capabilities,filter_agent_messagetest_utils.py; end to end intest_transport.py_write_lockstop(timeout=…): EOF and exit are bounded bytimeoutfrom the call; then kill unless an exit status is known; raises if the kill failsACPTransport.closekill()releases output waiting on a full read queue, so the handle sees the exit andwait()returnsACPTransport.killstop(), messages already queued stay readable and later ones are dropped_closingchecks in_handle_stdoutand_deliverreceive()never loses a message_readable/_spaceeventsLineSplitterConnection closed, so callers usewait()andon_stderr_watch_exiton_stderr) kills the agent and keeps the original error_watch_exite2b_acpemits noDeprecationWarningAsyncACPAgent.startOut of scope: restricting network or MCP, batch semantics beyond the boundary check, a sync API, reconnecting to a running agent.
Testing
e2b-acp: 37 tests (unit, offline, live), ruff and ty on this branch rebased on currentmain(includes feat(sdk): add retainOutput option to command handles #2044).AsyncACPAgent.start: goose withgpt-5.6-lunaran the README example end to end (file written, exit 0, noDeprecationWarningunder-W error).claude-agent-acp0.90.0) wrote and ran a file in the sandbox. The client received nofs/*orterminal/*calls, and the agent process was gone after it was stopped.Release notes for maintainers
pnpm run versionsetse2b-acp'se2brequirement to the core version being released, because the package needsretain_outputfrom feat(sdk): add retainOutput option to command handles #2044, which is not released yet.e2b-acpneeds trusted publishing configured before the first release.pkg_artifacts.ymlpreview builds and staging jobs insdk_tests.yml. Happy to add either if you want them.Co-authored-by: Maksim Obukhov maksimobukhov@sudolabs.io
Co-authored-by: Mish Ushakov 10400064+mishushakov@users.noreply.github.com
Co-authored-by: Ondrej Drapalik 43215019+OndrejDrapalik@users.noreply.github.com