> ## Documentation Index
> Fetch the complete documentation index at: https://docs.e2b.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Client Protocol (ACP)

> Run goose, OpenCode, Claude Code or any other ACP agent in an E2B sandbox and drive it from your code with an ACP client.

The [Agent Client Protocol](https://agentclientprotocol.com) (ACP) is an open protocol between coding agents and the clients that drive them. With the E2B ACP package (`@e2b/acp` for JavaScript, `e2b-acp` for Python), the agent runs inside an E2B sandbox and your application is the ACP client: it starts sessions, sends prompts, receives the agent's messages and tool calls, and answers permission requests.

The same client code works for all three agents below. Only the sandbox template and the command that starts the agent change.

| Agent | Template | Command | Provider key |
| - | - | - | - |
| [goose](https://github.com/aaif-goose/goose) | `goose` | `goose acp --with-builtin developer` | Set with `GOOSE_PROVIDER`, `GOOSE_MODEL` and the provider's key |
| [OpenCode](https://opencode.ai/docs/acp/) | `opencode` | `opencode acp` | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` or [another provider](https://opencode.ai/docs/config/) |
| [Claude Code](https://github.com/agentclientprotocol/claude-agent-acp) | `claude` | `claude-agent-acp` | `ANTHROPIC_API_KEY` |

Other ACP agents can run the same way once they are installed in a [custom template](https://docs.e2b.dev/template/quickstart.md).

## Install

```bash JavaScript & TypeScript theme={"theme":"css-variables"}
npm install e2b @e2b/acp @agentclientprotocol/sdk
```

```bash Python theme={"theme":"css-variables"}
pip install e2b e2b-acp agent-client-protocol
```

Set `E2B_API_KEY` to your E2B API key, and the provider key for the agent you run.

## Run an agent

Your code creates a sandbox from a template that already has the agent installed. Then `ACPAgent.start()` (`AsyncACPAgent.start()` in Python) runs the agent inside that sandbox and connects your ACP client to it over the agent's stdin and stdout. The agent process runs in the sandbox, not on your machine:

```bash theme={"theme":"css-variables"}
goose acp --with-builtin developer
```

Use `agent.connection` to initialize the agent, create sessions and send prompts. In JavaScript, your client is a `client()` app from `@agentclientprotocol/sdk`, and requests to the agent go through `agent.connection.agent`. In Python, `client` is an ACP client object, or a function that takes the connection and returns one.

```typescript JavaScript & TypeScript theme={"theme":"css-variables"}
import { Sandbox } from 'e2b'
import { ACPAgent } from '@e2b/acp'
import { client, methods, PROTOCOL_VERSION } from '@agentclientprotocol/sdk'

const app = client()
  // Approve tool calls; the agent's tools run inside the sandbox.
  .onRequest(methods.client.session.requestPermission, async ({ params }) => {
    const allow = params.options.find((o) => o.kind === 'allow_once') ?? params.options[0]
    return { outcome: { outcome: 'selected', optionId: allow.optionId } }
  })
  .onNotification(methods.client.session.update, ({ params: { update } }) => {
    if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {
      process.stdout.write(update.content.text)
    }
  })

const sandbox = await Sandbox.create('goose', { timeoutMs: 600_000 })
try {
  const agent = await ACPAgent.start(sandbox, 'goose acp --with-builtin developer', app, {
    envs: {
      GOOSE_PROVIDER: 'openai',
      GOOSE_MODEL: 'gpt-5.6-luna',
      OPENAI_API_KEY: process.env.OPENAI_API_KEY!,
    },
  })
  await agent.connection.agent.request(methods.agent.initialize, { protocolVersion: PROTOCOL_VERSION })
  const { sessionId } = await agent.connection.agent.request(methods.agent.session.new, {
    cwd: '/home/user',
    mcpServers: [],
  })
  await agent.connection.agent.request(methods.agent.session.prompt, {
    sessionId,
    prompt: [{ type: 'text', text: 'Create fizzbuzz.py for 1 to 15 and run it.' }],
  })
  await agent.stop()
} finally {
  await sandbox.kill()
}
```

```python Python theme={"theme":"css-variables"}
import asyncio
import os

import acp
from acp import schema

from e2b import AsyncSandbox
from e2b_acp import AsyncACPAgent


class Client:
    # Approve tool calls; the agent's tools run inside the sandbox.
    async def request_permission(self, session_id, tool_call, options, **kwargs):
        allow = next((o for o in options if o.kind == "allow_once"), options[0])
        return schema.RequestPermissionResponse(
            outcome=schema.AllowedOutcome(outcome="selected", option_id=allow.option_id)
        )

    async def session_update(self, session_id, update, **kwargs):
        if isinstance(update, schema.AgentMessageChunk) and update.content.type == "text":
            print(update.content.text, end="")


async def main():
    sandbox = await AsyncSandbox.create("goose", timeout=600)
    try:
        agent = await AsyncACPAgent.start(
            sandbox,
            "goose acp --with-builtin developer",
            Client(),
            envs={
                "GOOSE_PROVIDER": "openai",
                "GOOSE_MODEL": "gpt-5.6-luna",
                "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"],
            },
        )
        await agent.connection.initialize(protocol_version=acp.PROTOCOL_VERSION)
        session = await agent.connection.new_session(cwd="/home/user", mcp_servers=[])
        await agent.connection.prompt(
            session_id=session.session_id,
            prompt=[acp.text_block('Create fizzbuzz.py for 1 to 15 and run it.')],
        )
        await agent.stop()
    finally:
        await sandbox.kill()


asyncio.run(main())
```

The agent writes and runs `fizzbuzz.py` in the sandbox, so the file is there afterwards for `sandbox.files.read()` or `sandbox.commands.run()`.

OpenCode takes its model from its config. Pass it in the `OPENCODE_CONFIG_CONTENT` environment variable, as the [TypeScript cookbook example](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/acp-agents-js) does with `{"model": "openai/gpt-5.6-luna"}`.

The ACP Python SDK is asynchronous, so use it with `AsyncSandbox`.

## Where the agent's tools run

ACP lets an agent ask its client to read and write files or run terminal commands on the client's machine. The E2B ACP package keeps that work in the sandbox:

* In `initialize`, the client filesystem and terminal capabilities are always turned off, whatever your client sends.
* Agent requests for `fs/*` or `terminal/*` are answered with an error and never reach your client.

The agent uses its own tools instead, so files it edits and commands it runs stay in the sandbox even if your client also works as an editor. The package does not restrict the agent's network access or the MCP servers you pass to it: an MCP server you pass with a command is started by the agent in the sandbox, while a remote MCP server is reached over the network.

Your client still receives permission requests. Whether an agent asks before a tool call depends on the agent's own settings.

## Lifecycle

* `agent.stop()` sends EOF to the agent and kills it if it has not exited within 5 seconds of the call (`timeoutMs` in JavaScript, `timeout` in Python). Messages the agent sends after `stop()` are not delivered. In JavaScript, closing the connection with `agent.connection.close()` also stops the agent, in the background.
* `agent.wait()` returns the exit code once the agent exits. If the agent exits with an error, pending requests fail with the exit code and the end of the agent's stderr in JavaScript, and with a connection error in Python. Pass `onStderr` (`on_stderr` in Python) to receive the agent's logs.
* The agent runs as long as the sandbox does. If your application exits without calling `stop()`, the agent keeps running until the sandbox times out, so set the sandbox `timeoutMs`/`timeout` for the longest task you expect.
* A single message from the agent can be up to 32 MiB. Change it with `maxMessageBytes` (`max_message_bytes` in Python).

## Related guides

> [card] [ACP agents in TypeScript](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/acp-agents-js)
>
> One client for goose, OpenCode and Claude Code

> [card] [ACP agents in Python](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/acp-agents-python)
>
> The Python version of the same example, built on agent-client-protocol

> [card] [OpenCode](https://docs.e2b.dev/agents/opencode.md)
>
> Run OpenCode headless or with its own HTTP server

> [card] [Claude Code](https://docs.e2b.dev/agents/claude-code.md)
>
> Run Claude Code headless with structured output and sessions
