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

# E2B

> Guardrails and durable execution for a coding agent working in an E2B sandbox

[E2B](https://e2b.dev) runs code in isolated cloud sandboxes. A coding agent's
tools work in a sandbox checkout while its model loop runs in the Rebuno agent.
Each tool call passes through [policy](/policy) and becomes a recorded step.

The workspace is a [resource](/sdk/python/resources): later dispatches reopen
it, and forks create separate sandboxes from its checkpoints. It pauses while
a call waits for approval.

## Register the workspace

The handler registers a sandbox with a checkpoint policy:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
from rebuno import CheckpointPolicy, resource
from workspace_resource import E2BResource

workspace = await resource(
    "workspace",
    driver=E2BResource(REPO, token),
    checkpoints=CheckpointPolicy(every_steps=5),
)
```

The example's [`E2BResource`](https://github.com/rebuno/rebuno/blob/main/examples/integrations/sandbox/e2b/workspace_resource.py)
implements three operations:

* `create(checkpoint_ref=None)` creates a sandbox from the selected snapshot
  or a fresh checkout, and configures its Git branch.
* `open(binding)` connects to the sandbox ID recorded for this execution.
* `checkpoint(handle)` creates a snapshot and replaces the handle's client,
  since [E2B snapshots](https://docs.e2b.dev/sandbox/snapshots) drop active connections.

The checkpoint policy is optional. Without it, dispatches and session turns
still reopen the same sandbox, but forks start from a fresh checkout. With it,
the SDK captures a baseline, then checkpoints after every fifth live tool call
that declares a workspace change, and on completion. Rebuno stores the sandbox
binding and snapshot references; E2B stores the files and memory.

Each new sandbox gets a unique Git branch, and shell commands push the current
branch. Reopening a workspace preserves its branch.

`on_timeout: "pause"` preserves the sandbox when a worker dies. Missing
sandboxes or selected snapshots fail the execution.

## Wrap the tools

`shell` and `write_file` declare workspace changes. `read_file` uses the default
of none:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
from rebuno import tool


@tool("shell", resources=["workspace"])
async def shell(command: str) -> str:
    """Run a shell command in the repository and return its exit code and output."""
    ...


@tool("read_file")
async def read_file(path: str) -> str:
    """Return a file's contents. The path is relative to the repository root."""
    return await workspace.files.read(f"{WORKDIR}/{path}")


@tool("write_file", resources=["workspace"])
async def write_file(path: str, content: str) -> str:
    """Replace a file's contents, creating it if needed. The path is relative to the repository root."""
    await workspace.files.write(f"{WORKDIR}/{path}", content)
    return f"wrote {path}"
```

The model also uses `shell` to commit and push the branch. These tools use
`safe_to_retry`, so an interrupted command can run again. See
[idempotency](/sdk/python/tools#idempotency) when adding commands with effects
that must not repeat.

## Keep credentials out of the sandbox

Cloning and pushing need a GitHub token. The driver adds a network rule that
sets the `Authorization` header on requests to `github.com` at E2B's egress
proxy:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
credentials = base64.b64encode(f"x-access-token:{token}".encode()).decode()
await sandbox.update_network(
    {
        "rules": {
            "github.com": [
                {"transform": {"headers": {"Authorization": f"Basic {credentials}"}}}
            ]
        }
    }
)
```

git works as usual inside the sandbox, and nothing in it can read the token.
The driver sets the rule when creating or opening a sandbox, so resumed
workspaces use the token the worker currently holds.

Opening the pull request is a separate tool that calls GitHub's API from the
agent:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
@tool("open_pr", idempotency="at_most_once")
async def open_pr(title: str, body: str) -> str:
    """Open a pull request from the pushed branch."""
    ...
```

The rule covers only `github.com`, so the sandbox can push a branch but cannot
use that token to call the API. Protect the default branch with a GitHub ruleset
that requires a pull request.

## Pause while waiting

The handler pauses the workspace when the model loop finishes or waits for
approval:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
from rebuno import Blocked, execution

result = None
try:
    result = await graph.ainvoke({"messages": [*history, {"role": "user", "content": task}]})
finally:
    if result is not None or isinstance(execution().suspension, Blocked):
        await workspace.pause()
```

The check reads `execution().suspension` because a framework can catch `Blocked`
inside its loop. After approval, the next dispatch replays up to the held call
and the driver opens the same sandbox. A dispatch superseded by another worker
leaves the sandbox running for that worker. A completion checkpoint briefly
resumes a paused sandbox and pauses it again after capture.

## Sessions

The handler reads the previous completed turn with `previous()` and returns its
conversation in `Result.state`:

```python theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
from langchain_core.messages import messages_to_dict
from rebuno import Result, previous

prior = await previous() or {}
...
return Result(
    output={"answer": result["messages"][-1].text},
    state={
        "messages": messages_to_dict(result["messages"]),
    },
)
```

Executions in the same session reuse the workspace and Git branch, so later
pushes update the same pull request. See [Sessions](/sdk/python/agents#sessions).

## Fork the workspace

```bash theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
rebuno exec fork <execution-id> --at <event-seq> --session fix-tests-fork
```

A fork restores the selected checkpoint into a separate sandbox. At an
uncovered point it uses the newest earlier checkpoint; copied tool results
still replay through the requested event and may describe changes missing from
that sandbox. See [Fork coverage](/sdk/python/resources#fork-coverage).

Snapshots restore sandbox state; GitHub branches and pull requests persist.

## Write the policy

```yaml theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
default_action: deny
rules:
  - id: allow-llm
    when:
      step_kind: llm_call
    then:
      decision: allow

  - id: allow-sandbox
    when:
      targets: [shell, read_file, write_file]
    then:
      decision: allow

  - id: open-pr
    when:
      target: open_pr
    then:
      decision: require_approval
      reason: opening a pull request needs approval
```

Sandbox tools are allowed. Opening a pull request waits for approval.

## Run it

[`examples/integrations/sandbox/e2b`](https://github.com/rebuno/rebuno/tree/main/examples/integrations/sandbox/e2b)
has the full agent, resource driver, policy, and dev kernel config.

Set `E2B_API_KEY`, `REPO` (the repository as `owner/name`), `GITHUB_TOKEN`,
`LLM_MODEL`, `LLM_BASE_URL`, and `LLM_API_KEY`. The token needs read and write
access to the repository's contents and pull requests. Start the kernel and
agent from that directory:

```bash theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
cd examples/integrations/sandbox/e2b
rebuno dev --config rebuno.yaml
```

```bash theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
pip install rebuno e2b httpx2 langchain langchain-openai
python agent.py
```

Create an execution:

```bash theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
rebuno exec create e2b '{"task": "The tests are failing. Fix the bug and open a pull request."}' --session fix-tests
```

The `open_pr` call appears in `rebuno exec watch`. See
[Approvals](/policy#approvals) to approve it. Continue the same session with:

```bash theme={"theme":{"light":"min-light","dark":"material-theme-ocean"}}
rebuno exec create e2b '{"task": "Also add a test for the edge case."}' --session fix-tests
```

To see a re-dispatch, stop the agent after a few tool calls and start it again.
The kernel dispatches the execution once its lease expires, two minutes by
default. [`lease_timeout_seconds`](/agents) sets a shorter lease for the agent.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.