The full journey of a memory.

From store to archived (or purged) — every state a memory can hold, every transition between them, every MCP tool that drives the change. Six stages. One narrative. The lifecycle that makes "memory that doesn't drown you" possible.

6 stages 11 transitions restore-by-default audit-trailed end-to-end
Timeline

A memory through time.

An illustrative example: a memory enters at Mid tier, gets accessed twice (each access bumps the TTL), gets linked to a related memory, gets consolidated with siblings into a Long-tier retro, the original ages into the archive, and finally gets purged after a 30-day compliance window.

day 0 day 1 day 3 day 7 day 14 day 44 elapsed time → store tier=mid expires_at=+7d access expires_at += 1d link added related_to M2 access #2 consolidate into retro derived_from edge tier promote retro → long expires_at = NULL expired archive_on_gc → archived_memories purged 30d retention irreversible Live Archived Gone
The six stages

From birth to retirement.

stage 1Createdday 0 · memory_store
A new row in memories. Validators run, governance gate runs, federation fanout writes to W of N peers, FTS5 indexes the title+content, HNSW vector inserted (if embedder loaded). The created_at timestamp is permanent — it never updates after this.
{"event": "created", "id": "550e8400-…", "tier": "mid", "expires_at": "+7 days", "namespace": "alphaone/eng"}
stage 2Active — accessed, recalled, linkeddays 0–N · while live
Every memory_get, recall hit, and search hit bumps access_count and last_accessed_at. Mid-tier memories also get their expires_at bumped forward by mid_extend_secs (default 1 day). Memories that get used earn more time. Updates (memory_update) preserve created_at but bump updated_at. KG edges added via memory_link at any time.
{"event": "accessed", "access_count": 3, "last_accessed_at": "day 3", "expires_at": "+1d"} {"event": "link_added", "relation": "related_to", "target_id": "…"} {"event": "updated", "updated_at": "day 5", "changes": ["tags", "priority"]}
stage 3ConsolidatedN source memories → 1 derived
Optional but powerful. memory_consolidate takes 2-100 source memories and produces a derived memory (LLM-summarized at smart/autonomous tier, plain-text-concatenated at lower tiers). Each source gets a derived_from KG edge to the new memory — provenance chain stays intact even though the consolidated row is the canonical one going forward.
{"event": "consolidated", "into": "new-id", "from": ["src-1", "src-2", "src-3", …], "links_created": 12}
stage 4PromotedMid → Long · governance-gated
The most consequential transition. memory_promote moves a memory from Mid to Long, clearing expires_at and routing through governance. Outcomes: Allow → promoted immediately. Pending(id) → queued for approval; replays automatically once approved. Deny(reason) → 403 with the reason.
{"event": "promoted", "from_tier": "mid", "to_tier": "long", "expires_at": null, "governance": {"verdict": "allow", "approver": "alice"}}
stage 5ArchivedTTL expiry · or memory_forget · or memory_delete
The row leaves memories and lands in archived_memories with full metadata + a reason ("ttl_expired" / "manual" / "forget_pattern"). FTS index updated; HNSW vector evicted. Recall + search exclude archived rows. Restorable via memory_archive_restore until purged.
{"event": "archived", "id": "550e8400-…", "reason": "ttl_expired", "archived_at": "day 14"}
stage 6Purgedmemory_archive_purge · IRREVERSIBLE
Final state. The row is removed from archived_memories. No way back. Federation fanout removes peer copies. Useful for compliance regimes that require erasure after a defined retention window — soft-delete first, hard-delete on a schedule.
{"event": "purged", "id": "550e8400-…", "after_days": 30, "by": "auto_purge_archive (cron)"}
Alternate paths

Branches off the happy path.

Restored from archivememory_archive_restore brings an archived row back to memories. expires_at is cleared on restore so a stale timestamp doesn't immediately re-expire. FTS5 + HNSW are rebuilt for the row.
Long tier — never expires — A memory written directly to Long tier (or promoted there) has expires_at = NULL. GC's WHERE expires_at < now() never matches it. Only explicit memory_delete or memory_forget ever removes it.
Hard-deletememory_delete + archive_on_gc=false in config produces irreversible erasure on the spot. Rare; usually only seen in regulated environments where archive itself is considered retention.
Forget patternmemory_forget sweeps a namespace + FTS pattern + optional tier filter. Each match goes through stage 5 (archived). Use the broom when you don't want to wait for TTL.
Federation receive — A memory pushed by a peer arrives at stage 1 on the receiving daemon, but with the original created_at preserved. Validators run again on the receiver — peer trust is at the mTLS layer, but content correctness is locally re-verified.
Audit trail

Every transition leaves evidence.

There is no stage transition that doesn't write something queryable. Operators (or auditors) can reconstruct the full history of any memory by joining a few tables.

// Live row — current state SELECT id, tier, namespace, created_at, updated_at, last_accessed_at, expires_at, access_count, source, metadata FROM memories WHERE id = ?1; // KG edges — who linked to / from this memory SELECT * FROM memory_links WHERE source_id = ?1 OR target_id = ?1; // Promotion / approval history (governance-gated transitions) SELECT id, action_type, status, requested_by, requested_at, decided_by, decided_at, approvals FROM pending_actions WHERE memory_id = ?1 ORDER BY requested_at DESC; // If archived SELECT id, archived_at, reason, namespace, tier, original_metadata FROM archived_memories WHERE id = ?1; // Daemon log captures every error/warn — grep by id: // grep "550e8400-" daemon.log