---
type: Axis
title: persistence (durability axis)
description: The persistence/durability architecture axis — how durably state survives (in-memory → file → embedded → networked → replicated). A per-entity-with-default axis whose commitment surface is the persistent-data schema; worked example is the AlertLog in-memory → sqlite move (#354).
tags: [axis, architecture, persistence, durability, storage, rfc-75, deep]
timestamp: 2026-06-18T00:00:00Z
---

# Persistence (durability axis)

> **DEEP axis (5A.2).** Full §6 depth with a worked example from the skill's own
> history — the AlertLog in-memory → sqlite durability climb (#354).

## Concept

Persistence is the **durability** axis (RFC #75 §3): how durably an entity's
state survives process and host loss. The ladder runs in-memory (lost on
restart) → file (survives restart, single host, no concurrency story) → embedded
(a local DB engine: transactions, queries — sqlite) → networked (a separate DB
server: shared by many processes) → replicated (multiple copies: survives host
loss). Its **commitment surface is the persistent-data schema** (§4): once state
is durable, the shape + access path are committed. Its **scope is
per-entity-with-default** (§5): a system-wide default ("entities persist to the
embedded canonical DB"), with per-entity deviations (ephemeral runtime state
stays in-memory; a hot counter may stay file-level).

## Why it is an axis (discriminator test — §2)

1. **Ordered tiers** — each rung adds a stronger durability guarantee.
2. **Cross-cutting bundle** — a rung dictates the store engine, the access
   layer, the concurrency story, the backup path, and the durability tests.
3. **Asymmetric migration cost** — moving up means migrating the data and
   rewriting the access layer; the data written at a lower rung before the move
   is only as durable as that rung was.
4. **Requirement-keyed selection** — falsifiable probes pick the rung.

## Schema view (the §6 entry — validated by `axis_catalog`)

```yaml
id: persistence
kind: architecture-axis
scope: per-entity-with-default
default_tier: embedded
maintenance_discipline: >-
  entities persist to the embedded canonical DB by default; ephemeral
  runtime/session state stays in-memory by deliberate exception (it is NOT an
  entity — RFC #75 §3.1). A per-entity move UP the ladder migrates the existing
  data and rewrites the access path; the discipline forbids treating a lower-rung
  store (a process-local cache) as if it were durable. Per-entity deviations live
  in the entity record.
selection:
  probes:
    - "Must the state survive a process restart? → at least file."
    - "Do concurrent readers/writers need transactions + queries? → embedded."
    - "Must many separate processes/hosts share the same state? → networked."
    - "Must the state survive loss of a single host? → replicated."
    - "Is the state in fact ephemeral (a session, an in-flight buffer)? → in-memory is correct (and it is not an entity)."
composes_with:
  - "CAP positioning = consistency × distribution (durability sets the floor)"
applies_when: []
tiers:
  - tier: in-memory
    commitment_delta:
      surface: durable-runtime-state
      delta: "state lives only in process memory; lost on restart. Reconstruction (if any) is from a more-durable source."
    admin_tool: "none — inspect via a debug endpoint."
    enforcement:
      - "nothing durable is assumed; restart is a clean-slate event."
    tests:
      - "state is correctly reconstructed after a simulated restart (or is acceptably lost)."
    forecloses: "durability across restart."
    migration_cost_to_next: "low — serialize to a file on write."
  - tier: file
    commitment_delta:
      surface: persistent-data-schema
      delta: "state serialized to a file; survives restart on one host. No transactions, no concurrent-writer story."
    admin_tool: "the file itself + a load/dump tool."
    enforcement:
      - "writes are atomic (write-temp + rename) so a crash mid-write doesn't corrupt."
    tests:
      - "state reloads intact after restart; an interrupted write doesn't corrupt the file."
    forecloses: "safe concurrent writers; transactional reads."
    migration_cost_to_next: "medium — adopt an embedded DB engine + access layer."
  - tier: embedded
    commitment_delta:
      surface: persistent-data-schema
      delta: "a local DB engine (sqlite): transactions, queries, indexes, a single-host concurrency model (WAL)."
    admin_tool: "DB console + backup/restore; migration runner."
    enforcement:
      - "all access goes through the DB layer; transactions wrap multi-row writes."
    tests:
      - "concurrent readers + a writer see a consistent snapshot; a transaction rolls back atomically."
    forecloses: "cross-host sharing of the live store."
    migration_cost_to_next: "high — move to a networked DB server + connection model."
  - tier: networked
    commitment_delta:
      surface: persistent-data-schema
      delta: "a separate DB server reachable over the network; many processes/hosts share one authoritative store."
    admin_tool: "server admin console; connection-pool + failover config."
    enforcement:
      - "access is via pooled connections; the network failure mode is handled."
    tests:
      - "two hosts read/write the same store consistently; a connection drop is recovered."
    forecloses: "single-host simplicity; the network is now a dependency."
    migration_cost_to_next: "high — replicate for host-loss survival."
  - tier: replicated
    commitment_delta:
      surface: persistent-data-schema
      delta: "multiple synchronized copies; the store survives loss of a single host (and serves reads from replicas)."
    admin_tool: "replication-lag + failover console; replica promotion tool."
    enforcement:
      - "writes reach a quorum/leader; replica lag is bounded + monitored."
    tests:
      - "killing a replica/host does not lose committed data; a promoted replica serves correctly."
    forecloses: "single-copy reasoning; reads may be stale (a consistency interaction)."
    migration_cost_to_next: "n/a — top of the ladder."
examples:
  - "The skill's own climb: the AlertLog moved from an in-memory ring buffer to sqlite (#354) — alerts were lost on daemon restart at in-memory, so durability was raised to embedded (a row per alert, queryable). The canonical store + action-log are both embedded sqlite; ephemeral session/SSE state stays in-memory by design (it is reconstructed, not an entity)."
```

## Selection procedure (§8)

Bidirectional check per entity under the embedded default. Recorded in the
entity record; the systems-analysis D-checks flag a durable entity left at
in-memory when a requirement says it must survive restart.

## Composition (§5)

Persistence sets the floor for **CAP positioning = consistency × distribution**:
you cannot offer a stronger consistency guarantee than your durability tier
supports. Rule authored in 5A.3.
