HomeJournalThis post

AI Agent Event Sourcing for Resumable Runs

Model agent runs as immutable decisions and effects, rebuild projections deterministically, snapshot for speed, and resume without duplicating work.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

AI agent event sourcing makes a run recoverable by storing immutable agent events instead of trusting one mutable status row. The goal is not an infinite transcript: agent run snapshots accelerate a deterministic record that can rebuild state, prove effects, and resume from one verified boundary.

AI agent event sourcing records decisions, not noise

Begin with a run aggregate whose events describe durable facts: RunStarted, ModelRequested, ModelCompleted, ToolPlanned, ToolDispatched, ToolEffectConfirmed, ApprovalRecorded, RunPaused, and RunFinished. AI agent event sourcing should not persist every token delta or debug line as a domain event. High-volume telemetry can live elsewhere with correlation IDs.

Each immutable agent event carries run ID, sequence, event ID, type, schema version, occurred-at time, causation ID, correlation ID, actor, and a payload small enough to validate. Sequence belongs to the aggregate, not the wall clock, because two workers can observe time differently. The AI agent activity log remains the human audit view; the event stream is the machine reconstruction contract underneath it.

Synthetic event fixture (not a recovered run): begin with 1 RunStarted(run-74), 2 ModelCompleted(answer hash m1), 3 ToolPlanned(reserve_quote, effect key rq-9), and 4 ToolDispatched(rq-9, provider request p-312). It stores neither token deltas nor mutable status text. The expected projection at revision 4 is phase tool_pending with no confirmed effects.

Separate intent, dispatch, and confirmed effect

One ToolCalled event is too ambiguous for recovery. Record the proposed intent, validated arguments hash, authorization decision, dispatch attempt, provider request ID, and confirmed effect as distinct transitions. If a process dies after a payment commits but before local acknowledgment, AI agent event sourcing must reveal that confirmation is unknown rather than inviting a blind retry.

Give every effect an idempotency key derived from stable run and action identities, never from a retry count. A recovery worker first reconciles with the provider, then emits ToolEffectConfirmed or ToolEffectRejected. This ledger aligns with durable AI agent execution: a checkpoint says where computation paused, while events show which externally visible facts already happened.

Synthetic crash fixture (not a provider incident): define p-312 as committed, then stop the worker before confirmation is appended. The constructed log ends at ToolDispatched, so expected recovery labels the effect unknown rather than failed. A provider stub returns receipt q-884 for rq-9; only then should the worker append revision 5 ToolEffectConfirmed without repeating the reservation command.

AI agent event sourcing operating modelAppend means Facts; Fold means State; Snapshot means Verify; Resume means Reconcile.AppendFactsFoldStateSnapshotVerifyResumeReconcile
  • Append: Facts
  • Fold: State
  • Snapshot: Verify
  • Resume: Reconcile
Figure 1: AI agent event sourcing connects Append → Fold → Snapshot → Resume as one inspectable argument.

Define an append contract with optimistic concurrency

Append events with expected stream revision so two workers cannot both advance the same run from version 14. The Microsoft event sourcing pattern describes append-only storage, materialized views, and the consistency tradeoff. A concurrency conflict means reload and decide again; it is not permission to renumber a losing event.

Store payload hashes and reject reused event IDs with different bytes. Keep the append transaction finite: validate envelope, compare expected revision, insert event, update a minimal stream head, commit. AI agent event sourcing benefits from ordered facts only if the store can prove there is one accepted successor for each aggregate revision.

Synthetic concurrency fixture (not an observed race): give two recovery workers stale views at revision 4. Expected Worker A behavior appends confirmation with expected revision 4; expected Worker B behavior receives a conflict, reloads revision 5, sees q-884 confirmed, and exits without a duplicate fact. The losing event ID remains unused rather than being renumbered to appear successful.

Rebuild projections as pure folds

A projection starts with an empty RunState and applies events in sequence. It may derive current phase, pending approvals, completed tools, open messages, budget consumed, and the next resumable action. Event replay projection code must not call models, tools, clocks, random generators, or live policy services. Historical facts already contain the outputs those dependencies produced.

Unknown event types or unsupported schema versions fail closed with the last understood revision. Do not skip them and continue to a plausible state. AI agent event sourcing is trustworthy when the same event bytes and projection version always yield the same state hash. Store that hash in replay tests so a refactor that changes meaning becomes an explicit migration decision.

Synthetic replay fixture (not an archived process trace): folding revisions 1–4 is expected to yield phase tool_pending, next reconcile, no effects, and state hash 4b7c. Folding revision 5 should yield phase tool_complete, next synthesize, effect q-884, and hash 91ae. The implementation contract requires those fixture bytes to reproduce the same hashes without calling a model, clock, or provider.

Snapshot state without replacing history

Snapshots accelerate long streams but are disposable caches, never alternative truth. An agent run snapshot should include run ID, included revision, projection name and version, state bytes, state hash, event-head hash, and creation time. Load the newest compatible snapshot, verify it, then fold subsequent events. If verification fails, discard it and replay from the beginning.

The AWS event sourcing guidance notes that rebuilding state can become expensive as streams grow. Measure replay latency and snapshot only when evidence justifies the operational complexity. AI agent event sourcing should retain the full audit stream even when a compact snapshot makes normal reads fast.

Synthetic snapshot fixture (not a stored run snapshot): define revision 3 with a projection hash, event-head hash through ToolPlanned, and projector version 2. Expected recovery verifies both before folding revisions 4 and 5. A one-byte corruption fixture must change the snapshot hash, force a five-event replay, and still produce expected state 91ae.

Observed stateEvent evidenceRecoveryEffect
RequestedIntentRetry policyNone
DispatchedProvider IDReconcileUnknown
ConfirmedReceiptDo not retryCommitted
Figure 2: The AI agent event sourcing decision matrix compares Requested, Dispatched, Confirmed without hiding the operating trade-off.

Evolve event schemas with explicit upcasters

Events outlive the code that wrote them. Add a schema_version and pure upcasters that transform old payload shapes into the current in-memory representation. Preserve original bytes and test every historical version. The agent tool schema evolution discipline applies directly: a renamed field can change authorization or resume behavior even when TypeScript still compiles.

Avoid rewriting old streams in place. If a semantic correction is necessary, append a compensating or correction event with provenance and rebuild projections. AI agent event sourcing gains credibility from history that remains inspectable. A migration that silently edits the past can make a current projection look clean while destroying the evidence needed to explain an earlier effect.

Synthetic schema fixture (not historical customer data): version 1 calls the effect quote_id while version 2 calls it reservation_id. The expected upcaster maps the old field in memory and preserves the supplied fixture bytes. Its proposed golden case includes q-884 so a rename cannot drop the confirmed effect and make the resume planner believe another reservation is legal.

Resume from a declared decision boundary

A resume worker acquires a lease, loads the stream, verifies the snapshot, folds remaining events, reconciles ambiguous effects, evaluates current policy, and only then emits RunResumed. Never resume from a UI status such as “thinking.” Resume agent workflow logic should identify the next command whose prerequisites are satisfied and whose effect key has not been confirmed.

If the model response was recorded, reuse it; if only ModelRequested exists, policy decides whether a new inference is allowed and records a new attempt. If approval expired, pause again. AI agent event sourcing makes these branches explicit, which prevents recovery from duplicating a tool call simply because the original worker vanished before updating a mutable row.

Synthetic resume fixture (not an operational decision): for run-74, define the expected decision as “do not dispatch rq-9; append confirmation from stub receipt q-884; continue at synthesis using fixture model result m1.” The worker is expected to append RunResumed with basis revision 5 and policy version 12. A UI label saying “working” has no authority over that branch.

Run the bounded teaching fixture before adapting the pattern to production.

Runnable artifact — agent-event-replay.test.mjs

import assert from "node:assert/strict";const fold=(s,e)=>e.type==="planned"?{...s,pending:e.id}:e.type==="confirmed"?{...s,pending:null,done:[...s.done,e.id]}:s;const events=[{type:"planned",id:"t1"},{type:"confirmed",id:"t1"},{type:"planned",id:"t2"}];const initial={pending:null,done:[]};const full=events.reduce(fold,initial);const snapshot=events.slice(0,2).reduce(fold,initial);const resumed=events.slice(2).reduce(fold,snapshot);assert.deepEqual(resumed,full);console.log("PASS: replay and snapshot produce the same state");

Run node agent-event-replay.test.mjs. Expected receipt: PASS: replay and snapshot produce the same state.

Test replay, snapshots, and crash cuts

Generate a golden stream and assert state hashes after every revision. Then insert a crash after each append and each external-effect boundary, restart from no snapshot and from several snapshot revisions, and compare the final projection. The agent context compaction audit is a useful adjacent check because reconstructed state must not imply that summarized context equals original evidence.

Use the projection contract as an operational boundary rather than a requirement to adopt one database product. AI agent event sourcing can live on several stores; the proof is deterministic order, concurrency control, schema handling, and effect reconciliation. Corrupt one snapshot deliberately and verify clean fallback.

Synthetic crash matrix (not completed incident testing): cut after each of five fixture appends plus a stub provider commit. Expected restarts reach one of two legal states—reconcile unknown rq-9 or continue with confirmed q-884—with zero duplicate provider requests. The proposed assertions require snapshot and full-replay hashes to match and unsupported version 99 to stop at its preceding revision.

Operate the log as recovery infrastructure

Monitor append conflicts, event lag, snapshot hit rate, full replay time, projection hash mismatches, ambiguous effects, lease expiry, and resume outcomes. Retention policy must preserve domain events and encryption-key availability for the promised audit period. Keep sensitive model content out of event payloads when a reference and integrity hash are sufficient.

Run a recovery drill from a copied production-shaped stream with side effects disabled. Ask a second engineer to explain the last confirmed action and next legal command from the receipt alone. AI agent event sourcing is finished only when the log supports that explanation, the projector rebuilds deterministically, and a resume cannot manufacture a fact that the immutable history never contained.

Proposed run-74 receipt (not an existing drill archive): generate a five-event JSONL stream, revision-3 snapshot, before-crash state, after-reconciliation state, provider stub response, and resume command. Those committed fixture outputs should let a reader reconstruct why q-884 is reused. The compact machine-readable example, rather than a mutable “resumed” badge, is the intended recovery contract.

  1. 1Load

    Verify head and snapshot

  2. 2Fold

    Replay pure projections

  3. 3Reconcile

    Resolve uncertain effects

  4. 4Resume

    Append the next legal fact

Figure 3: The AI agent event sourcing proof runs Load → Fold → Reconcile → Resume before it can claim a result.