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>/.
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.
# 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: 1Through 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, and learn more about attaching images and files to harness agents in the documentation.
Frequently asked questions
Up to 16 images and files together, with at most 512 KiB of decoded bytes in total. A 17th attachment or a larger total raises a ValueError before the run is queued.
No. Only bytes cross into the sandbox, so Agno refuses URLs and media references, and the AgentOS route answers 400 to a JSON entry that names a file path on the server. A Python caller can still pass a regular local file, which Agno reads into bytes first. Audio and video are refused too.
Yes. The staged files are part of the checkpoint the turn commits, so a later turn restored on a new computer finds them under /workspace/.agno/uploads/<run_id>/.
The queue stores the encoded attachments with the submission, so the retry sends the same bytes. Reusing an Idempotency-Key with different attachment bytes returns 409.



