# Sandboxed Claude and Codex agents take images and files

> Agno accepts images= and files= on a sandboxed ClaudeAgent or CodexAgent from run(), arun() and the AgentOS run route, up to 16 attachments and 512 KiB per request, and keeps them in the session checkpoint.

- Published: 2026-10-16
- Author: Ashpreet Bedi
- Categories: Changelog
- Canonical: https://www.agno.com/blog/sandboxed-claude-and-codex-agents-take-images-and-files
- Markdown: https://www.agno.com/blog/sandboxed-claude-and-codex-agents-take-images-and-files.md

Agno validates and base64-encodes every `images=` and `files=` item you pass to a sandboxed agent's `run()` or `arun()` before anything reaches the database, so a refused attachment leaves the session's lease, checkpoint and computer as they were. Inside the sandbox the runtime checks the bytes again, and the harness's own staging writes them under `/workspace/.agno/uploads/<run_id>/`.

```python
from pathlib import Path

from agno.agents.claude import ClaudeAgent
from agno.db.postgres import PostgresDb
from agno.media import File, Image
from agno.sandbox.docker import DockerSandbox

agent = ClaudeAgent(
    id="reader",
    db=PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai"),
    sandbox=DockerSandbox("agno-sandbox:local", network="isolated"),
)

agent.run(
    "What colour is the image, and when is the release?",
    session_id="release-review",
    images=[Image(content=Path("chart.png").read_bytes(), format="png")],
    files=[File(content=b"Release date: 2026-11-02\n", filename="release-notes.txt")],
)
```

The staged files go into the checkpoint the turn commits. On the next turn, even on a fresh computer, Claude or Codex can open the same files with no new upload. A turn that fails or is cancelled commits nothing, and the previous checkpoint stays the recovery point.

A request carries at most 16 attachments and 512 KiB of decoded bytes, which fits inside the runtime's 1 MiB request once base64 and JSON overhead are counted. Agno refuses URLs, media references, audio and video. We ran each case through a sandboxed `ClaudeAgent.arun()` with a stand-in coordinator in place of the container and the model.

```text
# real output: one accepted run, then each refusal (stand-in coordinator, no container, no model)
png + notes: COMPLETED; images=[('png', 96)] files=[('release-notes.txt', 25)]
17 attachments: ValueError: Sandbox input accepts at most 16 attachments
image URL: ValueError: Sandbox attachments require inline bytes or explicit local files
600 KiB file: ValueError: Sandbox attachments exceed 512 KiB total
audio: UnsupportedMediaError: This agent does not support audio inputs; images and files are placed in the workspace for the harness to read.
coordinator calls: 1
```

Through AgentOS, `POST /agents/{agent_id}/runs` hands image and file uploads in the `files` multipart field to a sandboxed agent. Audio and video answer 400, and so does a JSON entry with a `filepath`, which fails with `Sandboxed runs accept uploaded bytes; server file paths are not accepted`, so a request can never make the service read its own disk. Queued runs keep the encoded attachments with the submission, and reusing an `Idempotency-Key` with different bytes returns 409.

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/frameworks/sandbox/docker/sandbox_attachments.py), and learn more about [attaching images and files to harness agents](https://docs.agno.com/agent-os/multi-framework/media) in the documentation.
