media_storage and the bytes go to object storage instead, leaving a MediaReference in the row.
media_storage works the same way on Agent, Team, and Workflow.
Backends
Each backend has an async twin:
AsyncLocalMediaStorage, AsyncS3MediaStorage, AsyncGCSMediaStorage. A sync backend also works inside arun(), where the upload runs in a worker thread to keep the event loop free.
What Gets Offloaded
Every media object on a run, whichever direction it came from:How It Works
- Media is uploaded to the backend before the run is written to the database, including any background status row or mid-run checkpoint written before the final one.
- The row stores a
MediaReference(key, bucket, mime type, size, SHA-256) instead of the bytes. - On a later run, the media is read back from the backend so the model sees it as before.
RunOutput you are handed keeps its bytes. Only the persisted copy carries the pointer.
media_storage requires store_media=True, which is the default. With store_media=False no new media is persisted, and an Agent or Team still reads back and deletes media stored earlier. A Workflow resuming a paused run needs store_media=True to refresh its executor’s media.Teams and Workflows
Setmedia_storage on the team or workflow, not on its members. The parent owns the write, so its backend uploads the whole run including member and step rows. A member pointed at a different bucket cannot resolve the parent’s references and its media is skipped on the next turn.
A member with store_media=False has its media dropped before the parent uploads anything. A restriction travels down the tree; store_media=True on the parent does not override a member that turned it off.
URL-only Media
Media that arrives as a bare URL is skipped during offload. Agno stores the URL and never downloads the file. Setpersist_remote_urls=True on the backend to fetch the URL from your process and store the bytes as well. Enable it only for URLs you trust, since the fetch runs with your network reach.
Deleting Media
Offloaded media outlives the session by default. The reference in the row is the only record of which object belongs to which session, so deleting rows first leaves orphaned objects. Passdelete_media=True to read the keys off the rows before deleting them, then sweep the objects.
Agent, Team, and Workflow, in both sync and async variants. It is opt-in: a plain delete_session() leaves every object in the backend.
Forking a session re-uploads the media under the fork’s own keys, so either session can be deleted without affecting the other.
A
MediaReference records the backend and bucket that minted it. Media stored elsewhere is not read back, not deleted, and not served, so changing bucket leaves earlier objects reachable only by the old configuration.AgentOS
AgentOS serves stored media at/sessions/{session_id}/media/{storage_key}, scoped to the caller’s session ownership. The backend is discovered from your agents, teams, and workflows.
media_storage on AgentOS when your entities use different backends, otherwise the first one found serves every request.
The route streams the bytes by default, which keeps the bucket private and leaves one CORS surface. Pass redirect=true to get a 307 to a freshly-signed URL instead, which is the cheaper path for embedding media in a page. Backends that sign nothing still stream: local storage always, and GCS with no service-account key.
The same delete_media flag works over HTTP, on one session or a batch: