The schema, table by table.

Every SQLite table ai-memory creates, every column, every index, every foreign key. Postgres mirror via the SAL adapter (--features sal,sal-postgres; Apache AGE Cypher graph backend). Schema version: v57 at v0.7.0 (was v33 at v0.6.4; ladder v34 → v57 includes V-4 cross-row hash chain, recursive-learning depth columns, Batman Form-4/5 provenance + confidence, optimistic-concurrency version column at v45, federation_push_dlq table at v48, archive_memories +14 columns at v49, per-namespace K8 quota dimension extension at v50 #1156, federation_nonce_cache persistence at v51 #1255, transcript_line_dedup idempotency table at v52 #1389, memories_au FTS5 trigger scoping at v53 #1418, tier-default expiry backfill onto legacy NULL-expiry rows at v54 #1466, and the sargable list_memories_updated_since federation-catchup rewrite + idx_memories_updated_at at v55 #1476, composite list-ordering indexes at v56 + the postgres stored tsvector at v57 — both #1579). Migration is automatic on first daemon start.

26 tables 65+ indexes v57 schema SQLite + Postgres mirror
Table-relationship map

26 first-class tables at v0.7.0.

Every relation flows from the central memories table. Foreign-key cascade-on-delete protects against orphan rows. memory_links is the KG primitive; pending_actions is the governance queue; sync_state is the federation cursor table; audit_log + signed_events are the V-4 tamper-evident chain; signed_events_dlq + federation_push_dlq are the v0.7.0 reliability DLQs (Track D #933). Table list authoritative against src/storage/migrations.rs CREATE TABLE statements at CURRENT_SCHEMA_VERSION = 57.

memories id (PK) · 26 fields tier · namespace · title memory_kind · entity_id reflection_depth · version citations · confidence_* memory_links source_id (FK) target_id (FK) relation (6 variants) valid_from / valid_until signature · attest_level archived_memories id (PK) · mirror shape archived_at · archive_reason +14 cols @ v49 (#1025) entity_aliases PK (entity_id, alias) alias (idx) created_at pending_actions id (PK) action_type · namespace requested_by approvals (JSON) status · decided_by namespace_meta namespace (PK) standard_id (FK) parent_namespace · updated_at subscriptions id (PK) url · events · event_types secret_hash dispatch/failure_count subscription_events id · subscription_id (FK) event_type · payload delivered_at · delivery_status sync_state agent_id · peer_id (PK) last_pulled_at last_pushed_at audit_log id · event_type agent_id · requested_family granted · attestation_tier · timestamp signed_events id · sequence · prev_hash payload_hash · signature (Ed25519) V-4 chain (v34) signed_events_dlq id · failure_reason agent_id · failed_at signed-event drainer DLQ federation_push_dlq peer-rejected writes (v48) confidence_shadow_observations memory_id (FK) · observed_at Form 5 calibration sweep schema_version version = 57 (v0.7.0) FK source_id / target_id → memories.id archive ← memories (mirror shape) shadow memory_id FK → memories.id aliases → entity memory.id FK standard_id → memories.id events → subscriptions.id

Plus governance + identity + lifecycle tables introduced across the v34–v57 ladder (and visible via SELECT name FROM sqlite_master WHERE type='table' on a fresh DB): agent_quotas (K8 per-agent quotas; per-namespace PK dimension at v50 #1156), governance_rules (K11 substrate rules engine), skills + skill_resources (Agent Skills, L1-5), recall_observations (Form-5 recall ledger), offloaded_blobs (QW-3 context-offload), memory_transcripts + memory_transcript_links (sidechain transcripts, I2), subscription_dlq (K7 webhook DLQ), federation_nonce_cache (v51 #1255 replay-prevention persistence), transcript_line_dedup (v52 #1389 L4 idempotency), plus migration tracking (schema_version shown above). Agents, entities, personas, and namespace standards are NOT side tables — they are memories (reserved namespaces / standard memories) resolved via namespace_meta + entity_aliases. Verified against src/storage/migrations.rs CREATE TABLE census at v0.7.0 release HEAD.

Core tables

memories & memory_links · the heart.

memories ▸ CORE · ALL TIERS since v0.1
The primary memory store. One row per stored memory. tier determines lifecycle (short / mid / long / archived). FTS5 virtual table mirrors title+content for keyword search.
ColumnTypeNotes
PKidTEXTUUIDv4 stringified. Stable across federation.
tierTEXT NOT NULLOne of short · mid · long (canonical Tier enum in src/models/memory.rs; archived rows move to the archived_memories table, there is no archived tier value). Drives TTL + visibility.
namespaceTEXT NOT NULLHierarchical path with / delimiter (v0.6.3 Stream A). Max length 512 chars, max depth 8 segments.
titleTEXT NOT NULLHuman-readable summary, ≤ 512 chars (MAX_TITLE_LEN). FTS5-indexed.
contentTEXT NOT NULLBody text. Up to 65,536 bytes (MAX_CONTENT_SIZE = 64 KiB). FTS5-indexed.
tagsTEXTJSON array. Validator caps count at 50 (MAX_TAGS_COUNT), length at 128 bytes each (MAX_TAG_LEN).
priorityINTEGER1-10 scale. Drives auto-promote eligibility.
confidenceREAL0.0-1.0. Displayed as % when < 1.0.
sourceTEXTOrigin tag (VALID_SOURCES in src/validate.rs): user · nhi (v0.7 vendor-neutral default) · claude (deprecated, back-compat) · hook · api · cli · import · consolidation · system · chaos · notify.
access_countINTEGERBumped on each recall hit. Triggers auto-promote at ≥ 5.
created_atTEXTRFC3339 UTC. Immutable.
updated_atTEXTRFC3339 UTC. Bumped on update.
last_accessed_atTEXT NULLRFC3339. Touched on recall. Drives recency-decay scoring.
expires_atTEXT NULLRFC3339. NULL = never. GC sweep purges past-due.
embeddingBLOB NULLf32 array — 384 dims (all-MiniLM-L6-v2, local candle) or 768 dims (nomic-embed-text-v1.5 via Ollama); declared per row by embedding_dim. Set if semantic+ tier.
embedding_dimINTEGER NULLv0.6.3.1 data-integrity guard — declared dimension of the stored embedding (384 or 768) so mixed-model rows can't cross-contaminate cosine scoring.
metadataTEXTJSON. Holds agent_id, scope, governance policy, chunked_from.
v0.7reflection_depthINTEGERRecursive-learning Task 1/8 — depth in the reflection recursion tree; 0 for caller-minted rows.
v0.7memory_kindTEXT NULLBatman Form-6 vocabulary: Observation / Reflection / Persona / Concept / Entity / Claim / Relation / Event / Conversation / Decision.
v0.7atomised_into / atom_ofTEXT NULLWT-1 atomisation provenance (parent → atoms, atom → parent).
v0.7entity_id / persona_versionTEXT / INTEGER NULLQW-2 persona-as-artifact columns.
v0.7citations / source_uri / source_spanTEXT NULLForm-4 fact provenance.
v0.7confidence_source / confidence_signals / confidence_decayed_atTEXT NULLForm-5 confidence calibration + decay.
v0.7mentioned_entity_idTEXT NULLEntity-mention back-reference.
v0.7encrypted_envelopeBLOB NULLSchema v44 (#228) — E2E content encryption at rest: X25519 ephemeral pubkey + ChaCha20-Poly1305 AEAD-sealed ciphertext envelope.
v0.7versionBIGINTSchema v45 Gap-1 optimistic concurrency for memory_update; defaults to 1 on legacy rows.
▸ idx_memories_namespace · ▸ idx_memories_tier · ▸ idx_memories_priority · ▸ idx_memories_expires · ▸ idx_memories_updated_at (v55) · ▸ idx_memories_title_ns · ▸ idx_memories_agent_id · ▸ memories_fts (FTS5 virtual)
memory_links ▸ KNOWLEDGE GRAPH extended v0.6.3 (temporal) + v0.7.0 (6 variants, Ed25519 populated)
Typed directed links between memories. The KG primitive. v0.6.3 added temporal-validity columns (valid_from, valid_until, observed_by, signature); v0.7.0 populates signature with Ed25519 per-link attestation (was placeholder pre-v0.7). 6 link variants at v0.7.0 (was 4 at v0.6.x).
ColumnTypeNotes
FKsource_idTEXT NOT NULLmemories(id) ON DELETE CASCADE
FKtarget_idTEXT NOT NULLmemories(id) ON DELETE CASCADE
relationTEXT NOT NULL6 variants at v0.7.0: related_to, supersedes, contradicts, derived_from, reflects_on (v0.7 recursive learning Task 1/8), derives_from (v0.7 atomisation atom→parent). Canonical enum in src/models/link.rs::MemoryLinkRelation.
created_atTEXT NOT NULLRFC3339 UTC.
valid_fromTIMESTAMP NULLv0.6.3: when the assertion became true. Backfilled to source's created_at on migration.
valid_untilTIMESTAMP NULLv0.6.3: when the assertion was superseded. NULL = still valid.
observed_byTEXT NULLv0.6.3: agent_id of the observer. Powers per-agent visibility filter.
v0.7signatureBLOB NULLv0.7.0 POPULATED: Ed25519 signature for attested provenance (was v0.6.3 placeholder). Per-row Ed25519 sig population gated on the resolved daemon agent_id having a *.priv keypair on disk.
v0.7attest_levelTEXT NULLv0.7.0 (H4 AttestLevel enum, src/models/link.rs): one of unsigned · self_signed · peer_attested · signed_by_peer · daemon_signed.
▸ idx_links_temporal_src (source_id, valid_from, valid_until) · ▸ idx_links_temporal_tgt · ▸ idx_links_relation
schema_version ▸ MIGRATION
Single-row table. Tracks current schema version. Daemon checks at startup; runs forward migrations idempotently. Current version: 57 at v0.7.0 (was 33 at v0.6.4 — the v34 → v57 ladder spans 24 migrations; v56 = composite list/archive ordering indexes, v57 = postgres stored generated tsvector, both #1579).
versionINTEGER NOT NULLCurrently 57 (v0.7.0). Canonical: CURRENT_SCHEMA_VERSION = 57 in src/storage/migrations.rs. Bumped only on additive migrations. v34 → v57 covers V-4 cross-row hash chain, recursive-learning depth columns, Batman Form-4/5 provenance + confidence, optimistic-concurrency version column at v45, federation_push_dlq table at v48, archive_memories +14 columns at v49, per-namespace K8 quota dimension extension at v50 (#1156: agent_quotas PK (agent_id)(agent_id, namespace); pre-v50 rows backfill to the _global sentinel), federation_nonce_cache persistence at v51 (#1255), transcript_line_dedup idempotency table at v52 (#1389), memories_au FTS5 trigger scoping at v53 (#1418), tier-default expiry backfill onto legacy NULL-expiry mid/short rows at v54 (#1466, in-code arm, no new .sql), and the sargable list_memories_updated_since federation-catchup rewrite + idx_memories_updated_at at v55 (#1476; postgres version-stamp no-op), composite list/archive ordering indexes at v56 (#1579 A2+B6d; postgres no-op), and the postgres stored generated tsvector + memories_tsv_gin at v57 (#1579 B2; sqlite no-op).
Archive & metadata

Soft-delete + namespace policy.

archived_memories ▸ SOFT DELETE TIER
Same shape as memories table. Memories transition here on memory_delete or curator soft-delete. Excluded from default reads. Restorable.
Mirrors memories schema column-for-column, plus:
archived_atTEXT NOT NULLRFC3339 UTC. Drives archive purge --older-than N.
archived_reasonTEXT NULLFree-form (e.g. "user_deleted", "consolidation_supersede", "auto_curator_dedup").
namespace_meta ▸ POLICY
Per-namespace policy attached via a "standard memory" — a special memory whose JSON metadata holds the governance policy for that namespace and its descendants.
ColumnTypeNotes
PKnamespaceTEXTHierarchical path. Inheriting children walk up.
FKstandard_idTEXT NULLmemories(id). The memory holding the policy in metadata.
parentTEXT NULLAncestor namespace (computed for fast walk).
created_atTEXT NOT NULLRFC3339 UTC.
entity_aliases ▸ ENTITY REGISTRY v0.6.3 Stream B
Alias → canonical-entity resolution side table. Each entity is a memory (in any namespace); this table holds zero-or-more alternate names per entity.
ColumnTypeNotes
PKentity_idTEXT NOT NULLmemories(id) (the canonical entity memory).
PKaliasTEXT NOT NULLAlternate name. Composite PK (entity_id, alias).
created_atTEXT NOT NULLRFC3339 UTC.
▸ idx_entity_aliases_alias (alias) — fast reverse lookup
Governance & federation

pending_actions · sync_state · subscriptions.

pending_actions ▸ GOVERNANCE QUEUE
When governance policy says "Approve", the requested action lands here instead of executing immediately. Cleared on approve (executes the action) or reject.
ColumnTypeNotes
PKidTEXTUUIDv4 of the pending action.
action_typeTEXT NOT NULLOne of store · delete · promote · reflect (the GovernedAction enum; reflect added at v0.7.0 L1-8 for the memory_reflect approval gate).
namespaceTEXT NOT NULLTarget namespace (drives policy lookup).
requested_byTEXT NOT NULLagent_id of the caller.
requested_atTEXT NOT NULLRFC3339 UTC.
payloadTEXT NOT NULLJSON. The full memory + context to execute on approval.
approvalsTEXT NOT NULLJSON array of {agent_id, approved_at}. Consensus mode counts entries.
decisionTEXT NULLNULL = pending · "approved" · "rejected".
decided_atTEXT NULLRFC3339 UTC when decision finalized.
sync_state ▸ FEDERATION CURSOR
Per-(local-agent, peer-URL) sync cursors. Drives the catchup loop. last_pulled_at advances after successful /api/v1/sync/since pulls.
ColumnTypeNotes
PKagent_idTEXT NOT NULLLocal node's agent_id.
PKpeer_urlTEXT NOT NULLFederation peer's HTTPS URL.
last_pulled_atTEXT NULLRFC3339 UTC of last successful pull from this peer.
last_pushed_atTEXT NULLRFC3339 UTC of last push.
subscriptions ▸ WEBHOOKS
Outbound webhook subscriptions. Each row spawns a dispatch thread on matching event. HMAC-SHA256 signed bodies. Retry-on-5xx with bounded attempts.
ColumnTypeNotes
PKidTEXTUUIDv4.
urlTEXT NOT NULLWebhook endpoint. Validated against SSRF guard (validate_url_dns).
eventsTEXT NOT NULLComma-separated or *. e.g. store,delete,contradiction.
namespace_filterTEXT NULLGlob pattern. NULL = all namespaces.
agent_filterTEXT NULLFilter by metadata.agent_id. NULL = all agents.
secret_hashTEXT NULLSHA-256 of the plaintext shared secret. Plaintext returned once on subscribe.
created_byTEXT NULLagent_id of subscriber.
created_atTEXT NOT NULLRFC3339 UTC.
dispatch_countINTEGERSuccessful dispatches.
failure_countINTEGERFailed dispatches (non-2xx OR connect timeout).
Migrations

Schema is versioned. Migrations are idempotent.

Every schema change is a forward migration applied at daemon startup. Idempotent so re-runs are safe. v15 was the v0.6.3 Stream B addition (KG temporal-validity columns + entity_aliases). v0.6.3.1 added the v17/v18/v19 ladder: v17 adds governance.inherit backfill (`migrations/sqlite/0012_governance_inherit.sql`), v18 adds embedding_dim guard + archive lossless (`0011_v0631_data_integrity.sql`), v19 adds webhook event_types column + index (`0013_webhook_event_types.sql`). Older versions (≤ v14) remain inline in src/storage/migrations.rs::migrate.

Version timeline
VersionReleaseChange
1-3v0.1-0.4memories table; basic CRUD; tier enum; FTS5 virtual.
4-7v0.4-0.5memory_links; tags JSON; expires_at; embedding BLOB.
8-10v0.5archived_memories; subscriptions; webhook dispatch_count.
11-13v0.6.0-0.6.1pending_actions; sync_state; namespace_meta; governance policy in metadata; agent_id resolution.
14v0.6.2SAL adapter prep; postgres_schema.sql; chunked_from metadata.
15v0.6.3Hierarchy + KG additions. 4 new memory_links columns; entity_aliases table; 3 temporal indexes. Migration file →
16-19v0.6.3.1governance.inherit backfill; embedding_dim guard; archive lossless; webhook event_types column.
20-33v0.6.4Hooks pipeline (25 events); sidechain transcripts (zstd-3 BLOB); subscription DLQ; consolidated_from_agents array; KG temporal-index v2; tier-promotion metadata; A2A correlation; smart-load veto; agent quotas; signed_events audit table.
34v0.7.0V-4 closeout (#698): signed_events.prev_hash + sequence cross-row SHA-256 hash chain (tamper-evident audit log).
35-37v0.7.0Shadow retention columns on calibration tables; memory_kind Batman Form-6 vocabulary column on memories; entity_id + persona_version (QW-2 persona artefact); citations + source_uri + source_span (Form-4 fact provenance).
38-44v0.7.0reflection_depth column on memories (recursive-learning Task 1/8); atomised_into + atom_of columns (atomisation links); confidence_source + confidence_signals + confidence_decayed_at (Form-5 auto-confidence + shadow + decay); mentioned_entity_id column; link attest_level enum column.
45v0.7.0Gap-1 optimistic concurrency: version BIGINT column on memories (defaults to 1 on legacy rows via SQL DEFAULT + serde default). Powers memory_update expected_version race-detection.
46-47v0.7.0Per-namespace transcript lifecycle TTL + auto-extract policy columns; KG temporal-index v3; subscription HMAC-signed-only enforcement (R3-S1.HMAC; unsigned dispatch DISABLED).
48v0.7.0federation_push_dlq table (Track D #933): captures peer-rejected federation writes with retry-with-backoff replay. Pairs with memory_subscription_dlq_list / memory_subscription_replay MCP tools.
49v0.7.0 releasearchived_memories +14 nullable columns (#1025): reflection_depth, atomised_into, atom_of, memory_kind, entity_id, persona_version, citations, source_uri, source_span, confidence_source, confidence_signals, confidence_decayed_at, mentioned_entity_id, version. Archive → restore is now LOSSLESS for the full v0.7.0 Memory shape on both backends.
50v0.7.0 releasePer-namespace K8 quotas (#1156): agent_quotas PRIMARY KEY extended (agent_id)(agent_id, namespace); pre-v50 rows backfill to the _global sentinel namespace.
51v0.7.0 releasefederation_nonce_cache table (#1255): peer-replay-prevention nonces persist across daemon restarts.
52v0.7.0 releasetranscript_line_dedup table (#1389 L4): (host_pubkey_b64, line_sha256) composite key backing memory_capture_turn idempotency + the L2 recover_from_transcript replay path.
53v0.7.0 releasememories_au FTS5 trigger scoping (#1418): sync trigger scoped to (title, content, tags) so non-FTS column updates no longer fire a needless sync.
54v0.7.0 releaseTier-default expiry backfill (#1466): legacy NULL-expiry mid/short rows get created_at + tier TTL, closing the TTL-leak immortal-rows class. In-code arm, no new .sql file.
55v0.7.0 releaseSargable federation catchup (#1476): list_memories_updated_since predicate split for a true index range scan; SQLite adds idx_memories_updated_at; postgres is a version-stamp no-op (bootstrap already ships memories_updated_at_idx).
56v0.7.0 releaseComposite list/archive ordering indexes (#1579 A2 + B6d): idx_memories_list_order, idx_memories_ns_list_order, idx_archived_ns_archived_at paired with the sargable storage::list rewrite (141 ms → 0.06 ms at 100k rows); postgres is a version-stamp no-op.
57v0.7.0 releasePostgres stored generated tsvector (#1579 B2): tsv GENERATED ALWAYS STORED column + memories_tsv_gin GIN index; match AND rank read the column (kills the per-matched-row recompute, ~305 of 306 ms at 8k rows); legacy expression index dropped; ACCESS EXCLUSIVE table rewrite on apply. SQLite is a version-stamp no-op. Current = CURRENT_SCHEMA_VERSION = 57.