/** * window/notice — the authored message a DROP leaves behind, and why it has * to exist at all. * * Pattern: Authored envelope with no untrusted payload whatsoever. * Role: core/ layer. Shared by both drop strategies. * Emits: N/A. * * The first reason for this message is the WIRE, not the prose. An agent * window looks like `user, assistant+tool, assistant+tool, …`, so dropping * the oldest turns leaves an ASSISTANT message at the head — and the * providers that care (Anthropic) require the window to open on a user turn. * A silent drop of the window's head therefore produces a request the vendor * rejects. Something must occupy that position. * * Given that we have to author a message there anyway, it should say what * happened rather than be filler. So it does: how many messages left, at * which iteration, by which strategy, where they still are — and, since * 9.57.0, WHICH TOOLS' RESULTS were among them. Unlike the compaction frame * there is no model output involved at ALL — every character below is written * by this library, and the only caller data that reaches it is a tool NAME, * shape-filtered to a plain identifier and dropped outright when it is not * one. So a drop still has no prompt-injection surface to speak of. * * That last sentence is the one this file exists for now. A drop the model is * not told about is how a model came to invent an id: its `whats_here` result * left the window, nothing said so, and it assembled a plausible-looking id * out of an entity name it remembered. "Tool results are among them * (whats_here) — call the tool again" states the absence instead of leaving * it silent. Whether it changes what the model does next is NOT measured: the * archived runs behind 9.57.0 were not re-run with this sentence on. It ships * as honesty (a drop the model is not told about is unfindable from inside the * conversation), not as a behaviour claim. * * It appears ONLY when the removal reaches the front of what may leave. That * is the window's head in the ordinary case; since 9.55.0 it is the position * just after the CURRENT REQUEST when the request was sitting at the head and * refused to go (see currentRequest.ts) — the notice is still the first thing * after the last un-droppable message, and it says that the request was kept. * A removal further in leaves the original opening turn in place, so there is * no wire problem to solve — and splicing a lone `user` message between two * assistant turns is its own risk. The ledger names that removal either way. * * It does not accumulate: the notice is an ordinary oldest turn next time * round, so the next drop absorbs it and files a fresh one. */ import type { LLMMessage } from '../../../adapters/types.js'; export { DROP_NOTICE_PREFIX, isDropNotice } from '../../../lib/saidByPerson.js'; /** * Build the message that takes the head position after a drop. * * `role: 'user'` for the same reason the compaction frame is: it takes the * head of the window, and the head of the window must be a user turn. (When * the CURRENT REQUEST is holding that position it is already a user turn, and * the notice follows it — see below.) * * `currentRequestKept` (9.55.0) is set when the drop stopped short of the * window's head because the message the run is executing was sitting there * and refused to leave — so the notice takes the position AFTER it rather * than the head itself. It says so, because a model reading "3 earlier * messages were dropped" and then finding a request above it should be told * which of the two facts to trust. Omitted, the notice is byte-identical to * the one this library has always written. */ export declare function buildDropNotice(facts: { readonly droppedMessageCount: number; readonly iteration: number; readonly strategy: string; readonly currentRequestKept?: boolean; readonly toolNames?: readonly string[]; }): LLMMessage;