Writing / 2026
Separate the Event Log from the Context Window
Your session log and your model prompt have opposite invariants. Split the append-only event log from a compiled context view to keep caches warm and safe.
Most production agent harnesses start with a single data structure: a messages slice that holds every user prompt, assistant reply, tool call, and tool output, and gets serialized straight into the next API request. That array is doing two jobs with opposite invariants. As a system of record, it wants completeness: every raw tool payload, error trace, timestamp, and data-lineage tag preserved in the exact order events occurred so you can audit a decision or replay a bug. As a model context window, it wants curation and byte stability: a frozen front prefix that protects your prompt-cache hit rate
, compacted older turns, omitted 80-kilobyte log dumps, and immediate removal of any retrieved document whose read permission was just revoked. When one mutable array serves both jobs, every fix breaks the other side. Edit an earlier message in place to strip a verbose tool output or redact a revoked document and you corrupt the audit trail, bust the KV-cache prefix from that turn forward, and on late-2026 frontier APIs that bind preserved reasoning blocks to prior message prefixes, trigger an HTTP 400 validation error.
The architectural fix is to stop storing the prompt and start compiling it. Treat the session history as an append-only event log and treat the messages array sent to the provider as a disposable, deterministic projection compiled before each turn. Database engineers do not serve queries by mutating the write-ahead log in place; agent runtimes should not govern prefix caches and data permissions by mutating the session transcript.
Start with the system of record. Every user message, tool_use call, tool_result payload, compaction checkpoint, and per-turn sandbox filesystem snapshot (L_0, L_1, ...) is written to an append-only session log, encrypted with tenant-scoped keys so erasure requests can crypto-shred a tenant’s history without rewriting indices. To prevent unauthorized or stale data from surviving across turns
, lineage in this log is monotonic across both conversation and workspace state. When retrieval brings source documents into a session, their contributing_source_ids attach to the retrieval event. Each turn’s copy-on-write sandbox layer L_t inherits the union of its parent layer L_{t-1}’s contributing_source_ids and the compiled context window’s active contributing_source_ids (across raw retrieval events, compaction checkpoints, and earlier tainted turns). Every event produced on that turn, including assistant replies, tool_use calls, and tool_result outputs (including reads from L_t, even when an earlier verbose tool output was omitted from the prompt projection several turns back), records that active source union.
In front of the model sits the context compiler: a pure function compileContext(events[0..n], policySnapshot, compilerVersion) that builds the provider payload before each inference call. For prefix stability, the compiler keeps system instructions and tool declarations byte-identical at the head of the projection and renders older turns in committed order. When a raw tool output ages past its retention window in the active view, or when older turns are summarized into a compaction checkpoint, the compiler substitutes the checkpoint or omission stub at the projection boundary while the raw event stays untouched in the log. Transient per-turn state such as current linter errors or test output never gets interpolated into the system prompt; the compiler attaches it as an ephemeral tail block on the current turn so the committed prefix bytes never shift.
Where this split earns its keep is mid-session permission revocation. Suppose an engineer queries an agent at 10:00 AM, the agent retrieves a confidential acquisition memo, writes an excerpt to a workspace scratch file, compacts the first ten turns at 10:10 AM, and loses access to that memo at 10:12 AM. In a single-array harness, the memo survives inside prior assistant replies, the compaction summary, and the scratch file. Under the split contract, every compaction checkpoint stores the union of contributing_source_ids summarized inside it. When the pre-turn policy check at 10:12 AM flags the memo’s source_id as revoked, the event log retains the immutable audit trail of what happened at 10:00 AM, while the compiler rebuilds the active projection from unrevoked events: it drops or redacts the raw retrieval event and any un-compacted tainted turns together with their paired tool_use and tool_result blocks as an atomic unit (preventing orphaned tool_result blocks that fail API validation), invalidates any checkpoint whose contributing_source_ids contain the revoked ID, and rolls back the first tainted sandbox layer L_k along with every subsequent layer L_{k..t} (or resets to L_0). You pay a one-time cold-cache rebuild when permissions change, and keep full prefix caching on the 99% of turns where access holds steady.
Two operational rules keep this compiler honest. First, retain historical compiler builds and log both the compilerVersion digest and the evaluated policySnapshot decision set (the exact source-permission verdicts at that timestamp) on every turn event; an audit log is only a reliability contract
if you can reconstruct the exact prompt the model saw at 10:00 AM even after permissions change at 10:12 AM. Second, alert on unscheduled prefix invalidations whenever a turn runs within the provider’s cache TTL without a compaction or revocation event, yet reported cached_tokens drops below the previous turn’s stable prefix length (excluding the transient per-turn tail and provider block-size rounding). Ask your team one question in your next architecture review: if access to a source document is revoked on turn eight of a twenty-turn session, can your harness remove it from the model’s view and workspace without falsifying your audit log or breaking your prompt cache?