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

# Code Interpreter

> Build custom sandbox templates and attach them to agents

## Overview

The Python SDK exposes two namespaces for managing the code-interpreter
sandbox that backs each agent's `bash` and `code_interpreter` tools:

* `client.code_interpreter.templates` — create, get, update, and
  delete custom Docker-based sandbox templates.
* `client.agents.add_code_interpreter` / `get_code_interpreter` /
  `remove_code_interpreter` — attach, inspect, and detach a template
  on an agent (= prompt version).

Environments are managed implicitly. When you attach a template to an
agent, the sandbox env is auto-provisioned by the backend; the returned
`AgentToolBinding.env_id` is informational only.

## Quick Start

```python theme={null}
from budai import BudClient

client = BudClient(api_key="your-key")

# 1. Build a custom template.
tpl = client.code_interpreter.templates.create(
    name="my-py-extra",
    commands=["RUN pip install --no-cache-dir pydantic-ai"],
    cpu_count=2,
    memory_mb=4096,
)
tpl.wait_until_ready(timeout=600)

# 2. Attach it to an agent. The env is created for you.
binding = client.agents.add_code_interpreter(
    "prm_abc",
    template_id=tpl.id,
    network_policy={"type": "filtered", "allow_out": ["pypi.org"], "deny_out": []},
)
```

## Templates

### Create Template

Submit a custom-template build; returns immediately with `status="pending"`.

```python theme={null}
tpl = client.code_interpreter.templates.create(
    name="my-py-extra",
    commands=["RUN pip install --no-cache-dir pydantic-ai"],
    cpu_count=2,
    memory_mb=4096,
)
```

| Parameter   | Type        | Description                                                                                        |
| ----------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `name`      | `str`       | Slug-safe template id (also the sandbox alias).                                                    |
| `commands`  | `list[str]` | Raw Dockerfile instructions appended to the base. See [Dockerfile Commands](#dockerfile-commands). |
| `cpu_count` | `int`       | vCPU count.                                                                                        |
| `memory_mb` | `int`       | Memory in MiB.                                                                                     |

Returns a `Template` handle with `.refresh()` and `.wait_until_ready()`
bound. Status is `"pending"` until the first workflow activity inserts
the row.

### Get Template

Fetch a single template row by id.

```python theme={null}
tpl = client.code_interpreter.templates.get("my-py-extra")
print(f"status={tpl.status} commands={tpl.commands}")
```

Raises `NotFoundError` if the template doesn't exist or is in another
project.

### Update Template

Replace the template's commands and rebuild the same sandbox image.

```python theme={null}
tpl = client.code_interpreter.templates.update(
    "my-py-extra",
    commands=[
        "RUN pip install --no-cache-dir pydantic-ai",
        "RUN pip install --no-cache-dir httpx",
    ],
)
tpl.wait_until_ready()
```

The **same sandbox image** is rebuilt; the previous image is
overwritten. Already-running sandboxes keep their old snapshot until
they're killed or idled out — the next sandbox spawn picks up the new
image.

### Delete Template

Hard-delete the template row and its sandbox image.

```python theme={null}
client.code_interpreter.templates.delete("my-py-extra")
```

Idempotent — a 404 is silently absorbed. Raises if the template is
still bound to an environment.

### Wait Until Ready

Block until the template build completes; raise on failure.

```python theme={null}
try:
    tpl.wait_until_ready(timeout=600)
except BuildFailedError as exc:
    print(f"build failed: {exc.error_message}")
except TimeoutError:
    print("build did not finish in time")
```

| Parameter       | Type    | Default | Description                                          |
| --------------- | ------- | ------- | ---------------------------------------------------- |
| `timeout`       | `float` | `600.0` | Total seconds to wait before raising `TimeoutError`. |
| `poll_interval` | `float` | `3.0`   | Seconds between successive `refresh()` calls.        |

Returns `self` once status reaches `"ready"`. Raises `BuildFailedError`
when the server reports `status="failed"` — the captured stderr tail is
on `error_message`.

### Refresh

Re-fetch the row from the server; mutate `self` in place.

```python theme={null}
import time

while tpl.refresh().status == "building":
    time.sleep(5)
```

Returns `self`, so callers can chain. Tolerates 404 for a short grace
window after create / update — the row may not exist yet because the
first workflow activity hasn't run.

## Agent Bindings

### Attach Code Interpreter

Attach (or update) the code-interpreter tool on an agent version.

```python theme={null}
binding = client.agents.add_code_interpreter(
    "prm_abc",
    template_id="my-py-extra",
    version=1,
    lifespan_seconds=1200,
    network_policy={
        "type": "filtered",
        "allow_out": ["pypi.org", "*.pythonhosted.org"],
        "deny_out": [],
    },
)
```

| Parameter          | Type                    | Default | Description                                                                |
| ------------------ | ----------------------- | ------- | -------------------------------------------------------------------------- |
| `agent_id`         | `str`                   | —       | The agent (prompt) id.                                                     |
| `template_id`      | `str`                   | —       | A builtin id (e.g. `"python-4g"`) or a custom template id from `create()`. |
| `version`          | `int`                   | `1`     | Agent version number.                                                      |
| `lifespan_seconds` | `int`                   | `1200`  | Sandbox idle-kill timeout.                                                 |
| `network_policy`   | `NetworkPolicy \| dict` | `None`  | Egress policy. See [`NetworkPolicy`](#networkpolicy).                      |

Attaching is upsert: calling the method again with different config
replaces the previous binding. The agent (= prompt) must already exist.
Returns an `AgentToolBinding`; `binding.env_id` is the auto-provisioned
sandbox env id (informational only).

### Get Binding

Fetch the agent's current code-interpreter binding.

```python theme={null}
binding = client.agents.get_code_interpreter("prm_abc")
if binding is None:
    print("no binding")
else:
    print(f"env_id={binding.env_id}")
```

Returns an `AgentToolBinding`, or `None` if no binding exists for that
agent version.

### Remove Code Interpreter

Detach the code-interpreter tool from an agent version.

```python theme={null}
client.agents.remove_code_interpreter("prm_abc")
```

Idempotent — a 404 is silently absorbed.

## Dockerfile Commands

`commands` is a `list[str]` of raw Dockerfile instructions appended
verbatim to the platform's base Dockerfile. The base ships the systemd

* Jupyter setup that backs the MCP tools.

### Allowed Instructions

`RUN`, `ENV`, `WORKDIR`, `USER`, `ARG`, `LABEL` — plus the rest of the
Docker instruction set that doesn't break the base. Server-side
validation is deny-list, not allow-list, so harmless no-ops like
`EXPOSE` and `SHELL` are silently accepted.

### Rejected Instructions

The validator hard-rejects five instructions with explicit
per-instruction error messages:

| Instruction          | Why it's rejected                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `FROM`               | Replaces the base image, killing the systemd + Jupyter setup.                                                                     |
| `COPY` / `ADD`       | The SDK doesn't ship a build context. Use a heredoc inside `RUN` for small files, or `RUN curl -fsSL …` for already-hosted files. |
| `CMD` / `ENTRYPOINT` | Overrides systemd as PID 1, so the Jupyter + FastAPI shim never starts.                                                           |

### Typo Catcher

Each line must start with a recognised Docker instruction keyword. A
line like `"pip install pydantic-ai"` returns a 422 with a
`did you forget RUN?` hint immediately, instead of failing 30 seconds
into the sandbox build.

### Structural Limits

* ≤ 64 commands per template.
* ≤ 4 KB per command line.
* No NUL bytes, no bare CRs.

## Response Objects

### Template

```python theme={null}
class Template:
    id: str                         # Template id (also the sandbox alias)
    type: str                       # "custom" | "builtin"
    status: str                     # "pending" | "building" | "ready" | "failed"
    commands: list[str]             # Dockerfile instructions (empty for builtins)
    languages: list[str]            # Available kernel languages
    cpu_count: int                  # vCPU count
    memory_mb: int                  # Memory in MiB
    error_message: str | None       # Captured stderr tail when status == "failed"
    created_at: datetime | None
    updated_at: datetime | None
    project_id: UUID | None
```

Handles returned by `create()`, `get()`, and `update()` carry
`.refresh()` and `.wait_until_ready()` bound to the originating client.

### AgentToolBinding

```python theme={null}
class AgentToolBinding:
    agent_id: str
    version: int
    tool_name: Literal["code_interpreter"]
    env_id: str                     # Auto-provisioned sandbox env id (informational)
    template_id: str | None
    custom_template_id: str | None
    config: dict | None             # Full backend config blob
```

### NetworkPolicy

```python theme={null}
class NetworkPolicy:
    type: Literal["disabled", "open", "filtered"] = "disabled"
    allow_out: list[str] = []
    deny_out: list[str] = []
```

* `"disabled"` — block all egress (default).
* `"open"` — unrestricted egress.
* `"filtered"` — caller-defined allow / deny lists.

`allow_out` / `deny_out` accept IP literals, CIDR ranges, domain names,
wildcard domains (`"*.example.com"`), and the case-insensitive sentinel
`"ALL_TRAFFIC"` (which expands to `0.0.0.0/0`). Allow takes precedence
over deny.

### BuildFailedError

```python theme={null}
class BuildFailedError(BudError):
    message: str                    # Formatted summary including error_message
    template_id: str | None
    error_message: str | None       # Captured stderr tail (≤ 2 KB)
```

Raised by `Template.wait_until_ready()` when the build workflow reports
`status="failed"`.

## Async Client

The same surface exists on `AsyncBudClient`:

```python theme={null}
from budai import AsyncBudClient

async with AsyncBudClient() as client:
    tpl = await client.code_interpreter.templates.create(
        name="my-py-extra",
        commands=["RUN pip install --no-cache-dir pydantic-ai"],
        cpu_count=2,
        memory_mb=4096,
    )
    await tpl.wait_until_ready(timeout=600)
    binding = await client.agents.add_code_interpreter("prm_abc", template_id=tpl.id)
```

## Error Handling

```python theme={null}
from budai.exceptions import BuildFailedError, NotFoundError, ValidationError

try:
    tpl = client.code_interpreter.templates.create(...)
    tpl.wait_until_ready(timeout=600)
except BuildFailedError as exc:
    print(f"Build failed: {exc.error_message}")
except ValidationError as exc:
    print(f"Invalid commands: {exc.errors}")
except NotFoundError:
    print("Template not found")
except TimeoutError:
    print("Build did not finish in time")
```

## Limits

* No file uploads — `COPY` / `ADD` are rejected. Heredoc in `RUN` or
  `RUN curl` are the documented workarounds.
* No template versioning — edits are destructive. To preserve an old
  recipe, create a new template before editing the old one.
* No template list endpoint in the SDK. Keep the id from `create()`.
* No environment CRUD in the SDK. `agents.add_code_interpreter` does
  it for you; project deletion cascades the cleanup.
* Embedded credentials in `RUN pip install --index-url=…` are baked
  into the image. Treat any image you build as you would any artifact:
  if it contains secrets, do not share it.

## Next Steps

<CardGroup cols={2}>
  <Card title="Inference" icon="wand-magic-sparkles" href="/api-sdk/python-sdk/inference">
    Invoke agents that use your code-interpreter binding
  </Card>

  <Card title="Code Examples" icon="code" href="/api-sdk/python-sdk/examples">
    Complete code examples and patterns
  </Card>
</CardGroup>
