#986·morphic

Pasted content is replayed in full on every turn, outside every history guard

Author: miurlaCreated Aug 21, 2026Updated Aug 25, 2026
Labelsautomated

Problem

data-pastedContent parts are converted to model input verbatim and replayed on every later turn of a thread, exactly like a file attachment. Unlike a file attachment, nothing bounds them.

The prepare pipeline in lib/streaming/create-chat-stream-response.ts is compactHistoricalMessages -> capHistoricalAttachments -> dedupeAttachments. Every one of them branches on isFilePart (or on cited-source context). A pasted blob is neither, so:

  • the count cap (HISTORY_ATTACHMENT_REPLAY_LIMIT) and the weight budget (HISTORY_ATTACHMENT_TOKEN_BUDGET) in capHistoricalAttachments never see it,
  • dedupeAttachments cannot collapse two identical pastes,
  • compactHistoricalMessages bounds cited source excerpts (MAX_SOURCE_CONTEXT_CHARS = 800) and nothing else.

The only remaining bound is truncateMessages at the context-window ceiling, which is a last-resort whole-message drop rather than a weight policy.

This is the argument #950 made for attachments, applied to the part type those guards do not recognize. It matters because the composer routes any sufficiently large paste into this part type, so pasting a file's contents instead of uploading it is an ordinary path, not an edge case, and it is the path with no guard on it.

Evidence

  • lib/streaming/helpers/convert-data-part.ts emits the full pasted text wrapped in nonce delimiters. Its own doc comment states the converter "runs over the entire history on every request", so the blob is re-sent every turn for the life of the thread.
  • lib/streaming/helpers/cap-historical-attachments.ts and lib/streaming/helpers/dedupe-attachments.ts both filter with isFilePart; a data-* part is invisible to them.
  • Reproduction: paste a large text blob in turn 1, continue the thread for several turns, and inspect the converted model messages. The blob is present in full in every request, and no [Attachment omitted from history: or [Duplicate attachment omitted: placeholder is ever produced, at any thread length.
  • Related observability gap: the root research span records the turn's input through describeTurnInput, which summarizes structured parts only when the turn carries no typed text. A turn that carries both typed text and a paste records the text alone, so the carried paste weight cannot be separated from attachment weight in tracing today.

Proposed direction

These options are not equivalent and the choice needs a decision.

  1. Generalize capHistoricalAttachments into a carried-context cap that also weighs data-pastedContent / data-quotedContext / data-noteContext, replacing dropped blocks with a placeholder naming them the way attachments already are. One policy, one knob, and it inherits the block quantization that keeps the prompt-cache prefix intact.
  2. Give pasted content its own budget separate from attachments. Different content class, different natural threshold, at the cost of a second knob.
  3. Collapse successive near-identical pastes instead of dropping the oldest. This fits the iterate-on-one-file pattern better, but "near-identical" needs a definition and is not free to compute over a long history.

Whichever is chosen: the newest user message must stay uncapped, and the boundary must move in quantized blocks. A boundary that moves every turn rewrites the prompt prefix and voids the provider cache on exactly the long threads this is meant to make cheaper, which is what #970 fixed for the attachment budget.

Acceptance criteria

  • A thread carrying a large pasted blob through many turns stops growing its replayed context without bound, while the most recent paste still reaches the model in full.
  • A dropped block becomes a placeholder naming it, so the model can ask for a re-paste rather than silently losing the reference.
  • Prefix stability between boundary crossings is asserted on the converted messages, in the same shape as the existing capHistoricalAttachments tests.
  • Tracing can attribute a turn's carried weight to attachments and to pasted text separately, so the effect of any of the three options can be read after deploy.