Storage Architecture¶
Default Locations¶
User-level memory:
Project-level memory:
Temporary exports:
Operational logs:
Scope Resolution¶
Memory lookup should search scopes in this order:
- Active project scope.
- User global scope.
- Optional organization/team scope.
The response must include which scope produced each result.
SQLite Tables¶
The current schema version is 7. Nuzo stores it in SQLite user_version and
rejects databases created by newer unsupported Nuzo versions with the
structured MEMORY_SCHEMA_UNSUPPORTED error.
Initial schema:
CREATE TABLE memories (
id TEXT PRIMARY KEY,
revision INTEGER NOT NULL DEFAULT 1,
scope TEXT NOT NULL,
kind TEXT NOT NULL,
content TEXT NOT NULL,
capture_key TEXT,
tags TEXT NOT NULL DEFAULT '[]',
source TEXT NOT NULL,
confidence REAL NOT NULL DEFAULT 1.0,
confidence_state TEXT,
provenance TEXT,
review_after TEXT,
expires_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
last_used_at TEXT,
archived_at TEXT
);
CREATE TABLE memory_events (
id TEXT PRIMARY KEY,
memory_id TEXT,
event_type TEXT NOT NULL,
actor TEXT NOT NULL,
payload TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE memory_relations (
id TEXT PRIMARY KEY,
source_memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
target_memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
relation TEXT NOT NULL,
reason TEXT,
created_at TEXT NOT NULL,
UNIQUE(source_memory_id, target_memory_id, relation)
);
CREATE VIRTUAL TABLE memories_fts USING fts5(
id UNINDEXED,
scope UNINDEXED,
content,
tags
);
memories, memory_events, and memory_relations are canonical.
memories_fts is a derived search index containing one row for each active
memory. Integrity diagnostics compare its ID, scope, content, and flattened tag
text with canonical rows and separately report missing, orphaned or archived,
duplicate, and mismatched entries.
The local memory integrity repair-fts workflow is deliberately outside the
MCP surface. Preview is read-only at the logical-store level. Apply requires
explicit confirmation and refuses unsupported versions, altered canonical or
FTS schemas, invalid canonical tag JSON, foreign-key violations, symlinked
source or backup files, overlapping SQLite filesets, and existing backup
destinations. Stable intermediate directory aliases are permitted for common
platform paths, while fileset comparisons resolve their real parent paths.
Repair holds BEGIN IMMEDIATE on the original source identity, takes a
WAL-consistent snapshot through a read-only sibling connection, and converts
the isolated snapshot to a self-contained DELETE-journal backup. Only the
backup's derived FTS rows are normalized; its canonical rows remain the exact
pre-repair snapshot. The owner-only backup is validated and published without
replacing an existing path before the source FTS table is rebuilt by
INSERT ... SELECT. Source validation occurs inside the same transaction, so
a failed rebuild rolls back without changing canonical data. The validated
backup remains available when a later source step fails.
Schema version 7 backfills capture_key with the exact-capture normalization
of each memory's content and maintains it on canonical creates and updates. A
partial index covers active-memory scope counts through its scope prefix and
deterministic same-scope duplicate lookup through the complete key:
CREATE INDEX idx_memories_active_capture_key
ON memories(scope, capture_key, id) WHERE archived_at IS NULL;
Capture relationship lookup scans all active records only while the scope has at most 100 records. Larger scopes use SQLite FTS to return at most 20 prefiltered records and explicitly report a non-exhaustive search. Exact duplicates never depend on the FTS candidate cap. This bounds application-row allocation while preserving the fail-closed rule that non-exhaustive evidence cannot establish independence.
Optional Semantic Sidecar¶
Optional semantic vectors are not part of the canonical schema above. They
live in memories.semantic.sqlite, beside the configured canonical store.
The sidecar contains provider fingerprint, build metadata, memory ID, canonical
revision, scope, and derived vector data. It contains no audit log and does not
own memory lifecycle.
The sidecar is derived, disposable, and excluded from export. Deleting it is a safe way to disable or reset semantic retrieval. Rebuilding reads active canonical memory into a temporary sidecar and replaces the previous completed index only after validation. Canonical writes do not invoke an embedding provider and therefore cannot be rolled back by semantic failure.
See Optional Semantic Retrieval for provider, staleness, fallback, and scope contracts.
Audit Log¶
Every write operation creates an event:
memory.createdmemory.updatedmemory.archivedmemory.deletedmemory.importedmemory.exportedmemory.recalledmemory.scope.rehomedmemory.challengedmemory.relation.createdmemory.relation.deleted
Recall events are opt-in because queries may contain sensitive task context and
can grow quickly. Normal CLI and MCP recall do not record query text or update
last_used_at by default. A caller must explicitly request usage recording
through the core API. When enabled, new memory.recalled events retain a
SHA-256 query hash, hash-algorithm marker, score, and scope instead of the full
query. Existing events are not rewritten during migration and may retain the
legacy query field.
Transaction Guarantees¶
SQLite-backed logical mutations commit memory rows, FTS changes, and audit events atomically.
- remember, update, challenge, relate, unrelate, forget, and usage-recording recall use one transaction per command;
- import uses one transaction for the complete planned document and rolls back every item if any persistence step fails;
- bulk forget uses one transaction per matched memory, so an unexpected failure may leave earlier memories committed while the failing memory is rolled back;
- dry runs do not open write transactions.
- FTS repair changes only the derived
memories_ftstable, after publishing a validated recovery backup; canonical rows and audit history are unchanged.
Policy validation happens before write transactions. Import duplicate planning happens inside the write transaction so equivalent imports from multiple local processes serialize deterministically.
SQLite uses WAL mode and a five-second busy timeout so short concurrent writes from multiple local agent processes wait instead of failing immediately.
Memory rows include a monotonically increasing revision. Stateful writes use
compare-and-swap semantics and return MEMORY_REVISION_CONFLICT when another
process commits a newer row before the operation can commit.
Local Permissions¶
Nuzo-created SQLite databases, WAL/SHM sidecars, config files, and exports use
owner-only 0600 permissions. Nuzo-owned memory, export, and log directories
use 0700 when created.
Project config accepts only the portable
.nuzo/memory/memories.sqlite storage path. Absolute paths, traversal, and
symlinked .nuzo paths are rejected so repository-controlled config cannot
redirect writes outside the project.
Secrets And Sensitive Data¶
Nuzo rejects obvious secret-like values:
- API keys;
- private keys;
- passwords;
- auth tokens;
- cookie/session blobs.
The CLI includes nuzo memory doctor, a diagnostic command that reports
whether any memory database or export file is tracked by Git.
Scope Boundary¶
Scopes organize recall and lifecycle operations, but selectors alone are not authorization boundaries. Nuzo can run a restricted core or MCP session with an explicit scope allowlist; cross-scope reads, writes, exports, and destructive operations are then rejected.
An unrestricted local CLI or core session remains an administrator workflow over the store and can access every scope. Use restricted sessions for repository-controlled hosts, and use separate stores when process-level or machine-level isolation is required.