File Attachment Handling#
File attachments in agent chat sessions are handled through a reference-based architecture implemented in Stage 1 of the Agent Multi-Modality project . The core design principle: upload once, reference always β messages and records carry an opaque server-issued attachment_id, never raw bytes or base64.
This contrasts with an earlier pattern where the front end re-sent the full message history (including embedded attachment bytes) on every turn, causing payload growth and repeated re-uploads .
Architecture Overview#
The four implementation layers are :
| Layer | PR | Key Entry Points |
|---|---|---|
| API β storage, quotas, sweep | #5607 | api/oss/src/apis/fastapi/sessions/router.py |
| Runner β materialize + deliver | #5615 | services/runner/src/engines/sandbox_agent/attachments.ts |
| SDK β wire protocol + capabilities | #5617 | sdks/python/agenta/sdk/agents/adapters/vercel/messages.py |
| Frontend β upload + render | #5619 | web/oss/src/components/AgentChatSlice/assets/attachmentTransport.ts |
Upload Flow and Media Type Classification#
Multipart Upload#
Attachments are uploaded via POST /sessions/attachments before the message is sent . The flow is two-phase:
- Pending β file bytes arrive via multipart upload; a record is created in
pendingstate. - Ready β after the attachment is referenced in a message (
POST /sessions/attachments/reference), it transitions toreadyand becomes durable.
The frontend orchestrates this in useAttachmentUploads.ts and blocks the send button until all uploads settle . Upload errors are typed (cap exceeded, quota, invalid format, in-flight) with Retry-After support.
Byte Sniffing / Media Type Classification#
Server-side media type classification lives in api/oss/src/core/sessions/attachments/media.py . The client-supplied MIME type is never trusted β the server reads magic bytes from the file content to canonicalize the media type.
Known limitation: Upload-time byte sniffing cannot fully validate image integrity cheaply. A truncated PNG may still classify as
image/pngand pass upload, only to be rejected by the provider at inference time with a400 invalid_request_error. The runner or frontend should map this class of provider error to a user-friendly message .
Quotas and Limits#
Per-session and per-kind limits are enforced in api/oss/src/core/sessions/attachments/dtos.py :
| Limit | Value |
|---|---|
| Max image size | 10 MB |
| Max audio size | 15 MB |
| Max document size | 10 MB |
| Files per session | 100 |
| Storage per session | 256 MB |
| Pending uploads | 20 |
| Files per turn | 5 |
| Gateway body cap | 32 MB (raised from 10 MB) |
A background sweep task (attachment_sweep.py) reaps unclaimed files after 24 hours and stuck pending uploads after 15 minutes .
Storage: Immutable Originals and Working Copies#
Each attachment has two copies :
- Immutable original β stored in a dedicated session-scoped attachments mount, isolated from generic mount operations via the protected mount policy in
api/oss/src/core/mounts/service.py. Backs model perception, download, inline rendering, and history reconstruction. - Working copy β materialized at
cwd/attachments/<attachment_id>/<filename>inside the agent's sandbox. The agent may read, edit, or delete it; it is re-materialized only when missing (never overwriting agent edits).
The attachment mount is deliberately hidden from generic mount APIs so clients cannot read, overwrite, or enumerate attachments through the general mounts surface .
Runner: Capability Gating and Delivery#
The runner (PR #5615) resolves attachment_id references into native ACP content blocks before passing a turn to the harness . Delivery is gated by three intersecting layers:
- Protocol transport β does the wire protocol support this content type?
- Adapter fidelity β does the harness adapter (e.g.,
claude-agent-acp,pi-acp) pass this block type through? - Model modalities β does the target model's capability set include this input type?
Capability metadata is resolved from the model catalog at the resolver boundary in sdks/python/agenta/sdk/agents/connections/resolver.py .
Key runner behaviors :
- Attachment-only turns (no text) are now valid β
currentUserTurn(request)is the single authority on turn content. - Every attachment gets a mention line in the prompt so a turn is never empty even if all blocks are gated.
- Delivery outcomes emit durable
attachment_deliveryevents with stable reason codes recorded intranscript.ts. - Graceful degradation on cold start: when a session must be rebuilt from records, each attachment is restored individually; missing files show as "no longer available" notices rather than failing the whole turn .
When a modality is unsupported (e.g., audio β blocked on adapter work), the file is attached as a workspace file with a visible notice; it is never silently dropped .
Frontend Transport and Rendering#
The frontend (PR #5619) sends zero base64 in messages . After upload, the composer assembles message parts with:
- A content URL pointing to
GET /sessions/attachments/{id}/content - Metadata carrying
{ attachmentId, size }(no bytes)
Key files:
attachmentTransport.tsβ upload viaPOSTwith axios progress tracking, idempotency key retry, and typed error taxonomyattachments.tsβ builds reference-only message partsAgentConversation.tsxβ renders from content route with authenticated blob fallback for reloaded conversationsComposerAttachments.tsxβ progress/retry/dismiss UI
Paste and drag-to-attach paths are gated on the file-uploads feature flag (PR #5604) . Per-file named delivery notices render both live and on conversation replay.
Key Source Files#
| Component | File |
|---|---|
| API attachment service | api/oss/src/core/sessions/attachments/service.py |
| Media type classification | api/oss/src/core/sessions/attachments/media.py |
| DB migration (attachments table) | api/oss/databases/postgres/migrations/core_oss/versions/oss000000020_add_session_attachments.py |
| Background sweep | api/oss/src/tasks/asyncio/sessions/attachment_sweep.py |
| Runner materialization | services/runner/src/engines/sandbox_agent/attachments.ts |
| Runner transcript / delivery events | services/runner/src/engines/sandbox_agent/transcript.ts |
| Cold-start reconstruction | services/runner/src/sessions/reconstruct.ts |
| SDK wire models | sdks/python/agenta/sdk/agents/wire_models.py |
| SDK Vercel ingress | sdks/python/agenta/sdk/agents/adapters/vercel/messages.py |
| Frontend upload | web/oss/src/components/AgentChatSlice/assets/attachmentTransport.ts |
| Design specification | docs/design/agent-workflows/projects/agent-multi-modality/design.md (PR #5439) |