Agno's ClaudeAgent and CodexAgent save each attachment as a file under <cwd>/.agno/uploads/<run_id>/ and list its path under the prompt, the way you would hand a file to someone at a terminal. Claude opens each path with its own Read tool, and Codex also receives images as native image inputs.
from pathlib import Path
from agno.agents.claude import ClaudeAgent
from agno.db.sqlite import SqliteDb
from agno.media import File, Image
agent = ClaudeAgent(
name="Analyst",
cwd=".",
allowed_tools=["Read"],
db=SqliteDb(db_file="tmp/agents.db"),
)
agent.run(
"Total the amount column.",
images=[Image(content=Path("chart.png").read_bytes(), format="png")],
files=[File(content=b"item,amount\nA,120\nB,80\n", filename="sales.csv", mime_type="text/csv")],
session_id="attachments",
)We ran the same call with a 40-byte PNG and a stand-in for the Claude Agent SDK. This is the prompt Claude received, and both files existed at those paths while the turn ran.
# real output, with the workspace and run id shortened to <cwd> and <run_id>
Total the amount column.
Attached files. Read them before answering; images and PDFs can be opened with the file tools:
- <cwd>/.agno/uploads/<run_id>/image-1.png (image/png, 40 bytes)
- <cwd>/.agno/uploads/<run_id>/sales.csv (text/csv, 23 bytes)When the run ends, Agno deletes the run's upload folder, and keep_uploads=True leaves it in place. The attachments stay on the run's stored input, so a follow-up turn on another replica gets them back. Agno writes them under that machine's workspace before the harness resumes and tells it when a path has moved.
Over HTTP, the AgentOS runs endpoint takes the same attachments in its files multipart field. Audio and video have no reader in either harness, so AgentOS turns them away before the SDK starts.
# real output: POST /agents/claude/runs with memo.wav (audio/wav) in the files field
400 {"detail":"This agent does not support audio inputs; images and files are placed in the workspace for the harness to read."}Earlier releases answered that request with a 200 and ran the turn on the message text alone.
Staging only ever writes inside the active workspace. A run id that is more than one path component, or an uploads folder that is a symlink, stops the run with MediaStagingError, and a staging that fails partway removes every file it created, even with keep_uploads=True. Files that were already in the folder stay where they are.
# real output: agno.agents._media.stage_media with a traversal run id and a symlinked uploads folder
run_id='../../etc': MediaStagingError: Attachment run_id must be a single nonempty path component
symlinked uploads dir: MediaStagingError: Attachment directories must not be symlinksSee the cookbook, and learn more about attachments for Claude and Codex in the documentation.
Frequently asked questions
Pass Agno Image and File objects to run() or arun() with images=[...] and files=[...], or upload them to /agents/{id}/runs in the files multipart field. For Claude, allow the native Read tool so it can open the files.
Agno stages them under <cwd>/.agno/uploads/<run_id>/ and removes that folder when the run ends. Set keep_uploads=True on the agent to keep the files after the run.
No. Neither harness has a reader for audio or video, so Agno raises UnsupportedMediaError before the SDK starts, and AgentOS answers the request with HTTP 400.
Yes. The attachment is stored on the run input, and before each turn Agno writes attachments from up to ten earlier runs back into the current workspace, on whichever replica serves the turn.




