Skip to content

Message admission has no aggregate attachment budget; tool-result images have no size check at all #529

Description

@FredKSchott

Problem

The runtime budgets tokens, never bytes. The only byte check in the attachment pipeline is the per-attachment cap (MAX_IMAGE_DATA_LENGTH, 14 MiB base64) in packages/runtime/src/runtime/schemas.ts. Around it:

  • A kind: 'user' message accepts an unbounded array of ≤14 MiB attachments — DeliveredAttachmentSchema is applied per element; nothing caps the count or the summed bytes per message, on either transport (parseDeliveredMessage serves both dispatch() and the direct HTTP route).
  • session.prompt({ images }) / task() images go through assertImagesWithinLimit (persisted-images.ts) — per-image only, count unbounded.
  • Tool-result images have no size check whatsoever: persistToolResultContentpersistCanonicalAttachmentsAttachmentStore.put enforces integrity (digest/size match) but no ceiling.
  • parseJsonBody (errors.ts) buffers the entire request body as one string before any validation runs. Workers accepts request bodies far larger than a 128 MB isolate can parse, so N × 14 MiB attachments OOM at JSON.parse before the schema ever sees them.

Consequence: a single well-formed message can take down a Durable Object isolate, and image-heavy workloads accumulate unboundedly toward the memory ceiling with no admission-time backpressure (see the companion issues for what happens after admission).

Proposed direction

  • Aggregate per-message caps (count + summed bytes) enforced in parseDeliveredMessage so both transports reject identically with a structured 413-shaped error before anything durable exists.
  • The same aggregate applied in assertImagesWithinLimit for prompt/task images.
  • Tool-result images: same caps, but failing as a tool error outcome (the model authored the oversized output and should get the chance to retry smaller), never a submission failure.
  • parseJsonBody: reject on content-length over a generous ceiling before buffering, and read chunked bodies through a byte-counting loop with the same cap.
  • Compaction's token estimator should also stop counting user-message image blocks as zero tokens (compaction.ts counts only text in user messages while tool-result images count ~1200), so the keep-window math stops being blind to exactly the content that dominates memory.

Framework constants first; per-agent configurability can come later (admission runs pre-render, so any config must be static).

Related: the post-admission halves of the same design gap are tracked separately (submission-payload byte residency; model-context image materialization).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions