Built on Y Build Build this app yourself — prompt to deployed, on your own domain. Start free
BuildShipCompareThe LabAbout Start building →
The Lab

When an AI agent forgets, test the state layer before the model

Tailscale's SQLite WAL-reset incident is a useful warning for AI products: a fluent answer can hide lost session, workflow, or business state.

Elena TorresShipping and Growth Editor, YBuild Blog
Published Aug 13, 2026
18 min
read
Hero cover · 1200×600
three builds, one stopwatch
Drop in a real screenshot or render here

An AI assistant answers a follow-up as if it has never seen the conversation. A research agent repeats three searches it completed ten minutes ago. A support agent says a refund was approved, but the billing system still shows the original charge. Teams often call all three failures “the model forgot.” Only one may have anything to do with the model.

Tailscale’s newly published account of 19 SQLite corruption incidents across six months is a useful correction to that instinct. The company eventually traced the failures to a rare race between a write transaction and a WAL checkpoint. Some committed data could vanish without the write returning an error. The trigger sat in SQLite for roughly 16 years before production telemetry, transaction replay, a new VFS tracing shim, and help from SQLite’s developers isolated it.

This is not evidence that SQLite is generally unsafe, or that AI applications are suffering from this exact bug. SQLite says the race required WAL mode, multiple connections, simultaneous checkpoint/write activity, and tight timing; it estimates the occurrence rate in ordinary use as extremely low. The bug is fixed in SQLite 3.51.3 and later, with selected backports.

The Build Lab lesson is broader and immediately actionable. AI products distribute “memory” across several state systems: model input history, agent checkpoints, approval records, tool side effects, and user-facing business data. A coherent response proves only that the model received some coherent context. It does not prove that the right action committed, that a retry did not duplicate it, or that a restore preserved the user’s latest decision.

This field note proposes a three-ledger recovery scorecard for one task-shaped crash test. Y Build did not reproduce Tailscale’s incident and did not run the proposed test. Every observation and result field below is intentionally blank. The change for a small team today is simple: before calling an agent memory failure a model problem, reconcile conversation, execution, and business state across one interrupted workflow.

What the Tailscale incident actually established

Tailscale operates its control plane as a set of shards. Each shard uses a single Go process and a SQLite database. The company takes complete snapshots every few minutes and had used the design without incident since early 2023. Then an analytics pipeline found a corrupt backup. Similar failures recurred 19 times over the following six months, sometimes hours apart and sometimes weeks apart.

The operational impact was real but bounded. Tailscale says the databases held control-plane metadata, not private encryption keys or network traffic. Early recovery sometimes lost a small number of recent device additions or configuration changes. Affected shards had to stop during repair or restore, preventing newly connected devices from receiving current coordination information. Existing peer-to-peer connections generally continued.

The investigation matters because the obvious evidence was misleading. There was no recent low-level change to blame, no stable traffic pattern, and no synthetic reproduction. Tailscale added passive production diagnostics, paid for direct SQLite support, automated integrity checks over backups, and created a deterministic transaction log. The transaction log finally showed that data written and committed in one transaction could be invisible to later transactions.

SQLite’s WAL-reset documentation explains the precise sequence. One connection completes a checkpoint. A second checkpoint begins. Another connection resets the WAL and writes new content at just the wrong moment. The second checkpoint retains an incorrect view of what has already been copied, so a later checkpoint skips part of the committed transaction. The main database can then contain references to pages that never arrived.

The key evidence was not merely “no more errors after an upgrade.” Tailscale instrumented the collision condition itself. Two months after deploying the fix, the new warning fired while the database stayed intact. Four more incident-free months followed before publication. That is positive recovery evidence: the dangerous condition occurred, the protection activated, and the invariant held.

Why “agent memory” is too vague to debug

Agent frameworks make persistence convenient, but convenience compresses several meanings into one word. OpenAI’s Agents SDK, for example, documents sessions that retrieve history before a run and store new items afterward. The same documentation includes file-backed SQLite sessions for persistent conversations, Redis and SQLAlchemy implementations, hosted conversation state, and resumable human approvals. These are different storage and consistency choices behind one conversational experience.

LangGraph similarly describes checkpoints saved at each execution step, organized into threads so a graph can resume, support human review, or travel back to earlier state. Its SQLite checkpointer is positioned for experimentation and local workflows, while Postgres is positioned for production. That is useful guidance, not proof that either backend preserves a particular product’s business invariant.

For a release test, separate three ledgers:

  1. Conversation ledger: user messages, model outputs, tool requests, summaries, and the exact context selected for the next model call.
  2. Execution ledger: run IDs, graph checkpoints, pending approvals, tool-attempt IDs, retry counters, leases, and terminal states.
  3. Business ledger: the source-of-truth records users care about—refund status, booking inventory, published content, account permissions, or delivered files.

A fourth stream, the evidence log, links the three. It records versions, hashes, timestamps, database integrity results, restore points, idempotency keys, and reconciliation outcomes. Do not let the agent rewrite this evidence as prose and then treat its summary as the source of truth.

These ledgers may share one physical database or use several services. The logical separation still matters. A conversation can resume perfectly while a refund failed. A workflow checkpoint can say completed while the user-facing record stayed unchanged. The business mutation can succeed while a retry record disappears, causing the agent to perform it twice.

Four failures that look like a bad model

The same user complaint—“it forgot”—can arise from different mechanisms.

Context omission. The history exists, but a truncation, compaction, filter, or session-ID mistake prevents the relevant item from reaching the model. The OpenAI SDK warns teams to choose one continuation strategy per call because mixing client-managed sessions with server-managed response state can duplicate context. This is an input-assembly problem.

Checkpoint rollback. The conversation is intact, but execution resumes from a checkpoint before a tool result or human approval. The agent repeats work, asks for approval again, or contradicts the UI. This is an orchestration-state problem.

Committed-effect loss or mismatch. The execution log says a tool succeeded, but the business record did not persist, a restored backup predates the effect, or a cached read hides it. This is a system-of-record problem.

Duplicate effect after retry. A crash happens after the external action succeeds but before the local terminal state commits. On restart, the agent retries the call without a stable idempotency key. The second refund, reservation, message, or publication is not hallucination. It is a recovery design failure.

SQLite’s official corruption guide says the database is highly resistant, not immune, to corruption. It also documents safer backup approaches and warns that copying a live database file without its active journal can produce an invalid backup. The point is not to make SQLite the suspect in every incident. The point is to keep storage integrity, orchestration recovery, and model behavior as separate hypotheses until evidence joins them.

Use one scenario with an irreversible-looking edge

Create a fictional support product called Northstar Desk. Its agent handles a synthetic order with four steps:

  1. read a customer message requesting cancellation;
  2. propose a $48 test refund and pause for human approval;
  3. call a mock billing endpoint using idempotency key ns-order-1842-refund-v1;
  4. update the ticket, ledger, and user-visible timeline to refunded.

Nothing should reach a real payment provider. The mock endpoint must store every request and return a stable result for repeated calls with the same idempotency key. Create two human decisions: approve for exactly $48 and deny for a second $12 adjustment. Give every event a monotonic sequence number and an immutable test clock.

The scenario has an irreversible-looking edge—the refund—without real financial impact. It also forces the product to reconcile three truths. The conversation must remember what the user asked. The workflow must remember which approval applies to which amount. The business ledger must show whether the refund happened once, zero times, or twice.

Run the same scenario against the persistence mode you actually intend to ship. If a local prototype uses SQLite but production uses Postgres, test both and label the results separately. If the agent framework stores history in one service and approvals in another, preserve both restore coordinates. The purpose is not to crown a database. It is to reveal which recovery guarantees the product truly owns.

Capture the baseline before injecting failure

Record a baseline manifest before the first run:

FieldRequired evidence
Runtimeapplication commit, agent framework version, model snapshot or alias, tool schema version
Persistencedatabase engine/library version, journal mode, connection count, checkpoint policy, storage path
Identityconversation ID, execution ID, order ID, approval ID, idempotency key
Restorebackup timestamp, WAL/journal handling, event-log offset, restore command or service procedure
Invariantsone approval for one amount; at most one refund; terminal UI equals billing record
Diagnosticsdatabase integrity command, trace location, tool-call receipt, reconciliation query

For SQLite, capture the runtime library version rather than trusting a package manifest. A language runtime, operating system, or bundled native dependency may supply a different library than expected. If the database is in WAL mode, record whether more than one connection can access it and who controls checkpointing.

The current SQLite 3.51.3 release note names the WAL-reset corruption fix explicitly. The longer WAL page says fixed branches include 3.51.3 and later, plus selected backports such as 3.44.6 and 3.50.7. Do not turn that into a universal “upgrade to exactly one version” instruction: confirm the vendor-supported patched version in your distribution and rerun application compatibility tests.

Also establish a clean expected trace. The approved run should have one user request, one approval request, one approval decision, one billing attempt, one stable billing result, one business-state transition, and one terminal response. If the happy path cannot produce a trace you can reconcile, fault injection will only create more ambiguity.

Inject six interruptions at state boundaries

Run six independent cases from a clean synthetic setup. These are proposed tests, not results.

CaseInterrupt afterRecovery question
Auser request stored, before model callDoes the same conversation resume without duplicating the request?
Bapproval requested, before human decisionIs the approval still pending and bound to $48?
Capproval stored, before tool callDoes recovery execute once without asking for broader approval?
Dmock billing succeeds, before execution checkpointDoes the idempotency key prevent a second refund?
Eexecution checkpoint commits, before business UI refreshDoes reconciliation repair the projection without repeating billing?
Fbackup/restore occurs after completionDo all three ledgers recover to a mutually consistent terminal state?

Use supported process termination or an injected exception. Do not damage a production database and do not attempt to reproduce SQLite’s rare timing race. Case F is about your documented backup and restore path. If you use SQLite, keep the database and active WAL/journal together or use a supported backup API; SQLite’s corruption guide lists VACUUM INTO, the backup API, and sqlite3_rsync as safe options under their documented conditions.

Repeat each case three times only after the harness produces deterministic evidence. Vary one dimension at a time: runtime restart, host restart, network timeout, then storage restore. A pass on process restart does not imply a pass after restoring an older snapshot. A pass with one database connection does not cover a multi-worker deployment.

Score recovery with three ledgers and five gates

Do not average the score into one percentage. A perfect conversation transcript cannot compensate for a duplicate refund.

GatePass conditionHold condition
Conversation continuitycorrect request, decision, and tool result appear once in reconstructed contextmissing, stale, duplicated, or cross-session context
Execution continuityone legal transition from pending to terminal; retries reuse stable IDsterminal state rolls back, forks, or advances without evidence
Business correctnesssource-of-truth effect occurs exactly once and matches approved parameterszero, duplicate, or wrong-parameter effect
Integrity and restoreintegrity checks pass; restore coordinates and artifacts are completemissing WAL/journal, failed check, unknown backup boundary
Reconciliationautomated query detects and repairs projections without replaying the effectonly the agent’s prose claims success, or repair repeats the action

SQLite’s PRAGMA integrity_check checks low-level formatting and consistency such as missing pages, duplicate page use, malformed records, and missing or surplus index entries. It does not establish that the right refund occurred, and it does not detect foreign-key errors unless the separate foreign-key check is run. Database integrity is one gate, not the verdict.

Likewise, a trace viewer proves that an attempted tool call was recorded, not necessarily that the destination committed it. Preserve the provider’s receipt or query the mock system of record. Every terminal state should be derivable from non-agent evidence.

Make recovery evidence machine-readable

Use one scorecard per case. Leave observations empty until someone runs it.

experiment_id: northstar-state-recovery-v1
status: planned
run_at: null

versions:
  app_commit: null
  agent_framework: null
  model_identifier: null
  database_library: null
  database_journal_mode: null

identities:
  conversation_id: ns-conv-1842
  execution_id: null
  business_record_id: ns-order-1842
  approval_id: null
  idempotency_key: ns-order-1842-refund-v1

interruption:
  case: D
  injected_after: mock_billing_success
  injected_before: execution_checkpoint_commit

restore:
  backup_timestamp: null
  event_log_offset: null
  command_or_procedure: null
  artifacts_present: []

observed:
  conversation_events: null
  execution_transitions: null
  billing_attempts: null
  billing_effects: null
  integrity_check: null
  reconciliation_result: null

gates:
  conversation_continuity: unknown
  execution_continuity: unknown
  business_correctness: unknown
  integrity_and_restore: unknown
  reconciliation: unknown

decision: hold
owner: null
expires_at: null

unknown is not pass. Store the scorecard outside the same database being tested, and hash or sign the final artifact if it will support a launch decision. Link every conclusion to a raw event, query, or integrity output. The agent may summarize the packet for a reviewer, but the summary should never replace it.

Add positive proof, not just an incident-free window

Tailscale’s strongest move came after deployment. It added a warning for the exact overlap that had caused the corruption, then waited until that condition occurred without damage. The absence of incidents alone would have been weak evidence because earlier six-week quiet periods had already occurred.

Copy the pattern at product scale. For each recovery control, define an event that proves it activated:

  • a retry reaches the mock billing endpoint with the same idempotency key and receives the original result;
  • a resumed workflow encounters an already-consumed approval and refuses to broaden it;
  • a restored projection detects that billing is ahead of the ticket and repairs only the ticket;
  • an integrity monitor evaluates a real backup artifact, not merely the live database;
  • a session reconstruction reports the exact range and hash of history supplied to the model.

Then create an alert for impossible combinations: workflow=completed with billing_effect=absent, billing_effect_count>1, approval_amount != effect_amount, or conversation_terminal=true while the business record remains pending. Positive proof turns a quiet dashboard into evidence that the guard encountered realistic conditions and held.

Know where the analogy stops

Tailscale’s workload was unusual. The company manually controlled checkpoints and ran them aggressively. SQLite says the WAL-reset race is unlikely under common use and recommends upgrading without framing the issue as an emergency. Do not use the incident to frighten every team away from SQLite.

Do not claim that a clean integrity_check proves semantic correctness. Do not infer that every agent framework’s SQLite adapter uses WAL mode, multiple connections, or vulnerable versions. Do not infer that Postgres, Redis, hosted conversation state, or an event-sourced system eliminates recovery failures. Different systems move the failure boundary; none removes the need to reconcile effects.

This scorecard is appropriate when an AI product persists multi-turn history, resumes work after interruption, pauses for human approval, or performs tools with external effects. It is especially useful for local-first agents, desktop copilots, research agents, support workflows, and builder products moving from one process to multiple workers.

It is less useful for a stateless one-shot generator whose output is immediately reviewed and has no external action. It is not a security audit, disaster-recovery certification, database benchmark, or proof of a framework defect. Real payments, health records, destructive operations, and regulated data need domain-specific controls beyond this synthetic test.

A 48-hour state-recovery pass for a small team

Hours 0–4: map state. Pick one workflow with an approval and a mock external effect. Name the conversation, execution, and business ledgers. Record versions, IDs, restore coordinates, and terminal invariants.

Hours 4–12: make the happy path auditable. Add stable idempotency keys, explicit transition logs, a business-record query, and a database integrity command. Confirm one clean run can be reconciled without reading the agent’s natural-language answer.

Hours 12–28: inject the six interruptions. Start from clean synthetic state for every case. Preserve raw traces and score each gate. Hold immediately on duplicate effects, parameter drift, or unknown restore boundaries.

Hours 28–38: restore and reconcile. Exercise the documented backup path. Confirm journal artifacts or service restore points are complete. Repair projections from source-of-truth evidence without replaying the external effect.

Hours 38–44: prove controls activate. Trigger one safe duplicate request, one stale projection, and one resumed approval. Capture evidence that idempotency, reconciliation, and approval binding each blocked the wrong outcome.

Hours 44–48: decide. Ship only the tested persistence topology and version set. Record uncovered variants—additional workers, different databases, hosted state, mobile offline mode—as unknown rather than inherited passes. Assign an owner and expiry date to the scorecard.

The release decision is about state, not fluency

The seductive failure mode in AI products is to debug the text first. Change the prompt, add a memory summary, increase the context window, and ask the model to be more consistent. Those changes may improve context omission. They cannot repair a lost transaction, recover a mismatched backup, bind an old approval to the right amount, or prevent an external side effect from executing twice.

Tailscale’s incident shows why reliable systems need more than a plausible root cause and a quiet dashboard. The team built a separate transaction history, replayed it, instrumented the suspect race, upgraded carefully, caught a misleading integrity alarm, and waited for positive evidence that the repaired condition occurred safely.

An AI product team does not need Tailscale’s scale to borrow that method. It needs one task-shaped scenario, three reconciled ledgers, a machine-readable scorecard, and a refusal to call unknown a pass. When the agent appears to forget, first ask a more useful question: which state survived, which effect committed, and what independent evidence connects them?

References

  1. Tailscale — How we tracked down a 16-year-old SQLite bug
  2. SQLite — Write-Ahead Logging and the WAL-reset bug
  3. SQLite — Release 3.51.3
  4. SQLite — How to corrupt an SQLite database file
  5. SQLite — PRAGMA integrity_check
  6. Tailscale — Switching from etcd to SQLite: a database migration story
  7. OpenAI Agents SDK — Sessions
  8. OpenAI Agents SDK — State and conversation management
  9. LangGraph — Persistence
  10. LangGraph — Functional API and durable execution
Liked this teardown?
Get the next experiment the day it drops. One email a week, raw numbers included.
Written by
Elena Torres Shipping and Growth Editor, YBuild Blog

An editorial pen name used by Y Build for shipping, growth, localization, and market-validation field notes.

Author · The Lab
More from Elena →

Keep reading

All of The Lab →
Build your own app
Free · no card
Start free →