memory_facts table is an index rebuilt from the drive. For routes see the
Memory Drive API; for the user guide see Memory Drive.
Where things live
Repos live at
<brains_dir>/<owner>/<brain_id>.git, the same layout as
hosted brains, so the existing smart-HTTP route serves clones and
pushes. At startup the API records brains_dir for its database
(memory_drive_config). A database without that record (unit tests, offline tools)
keeps the legacy row writers.
Format
sourceis a product link (/?session=<id>), anhttpslink, an opaque label (gizzi:session/<id>,claude-code:session/<id>,allternit:observation/<id>), orimported:unknown.javascript:,data:,file:and similar schemes are refused.idis an Allternit extension. External lines without it get a deterministic id (entry-+ sha256 of text, source and date).- The server regenerates
## Indexfrom the topic files on every write. - Limits: 64 KiB per file, 2 MiB per drive, 256 files, regular non-executable markdown only, no raw HTML or images, no transcripts or logs.
Writing
memory_drive_writer::commit_facts(db, owner, adds, retire, message) is the only
writer for personal memory. Retain (explicit and the model extractor, including its
rule-based fallback), fact edits, deletes, adapters and reconstruction all call it.
- Skip empty text, possible credentials and duplicates of an active memory.
- Build entries: new stable id, today’s date, source from the observation’s session
when that session exists on this node and isn’t incognito, otherwise
allternit:observation/<id>. - Map
retirefact ids to entry ids (DeleteEntry); legacy rows are retired in the archive. - Apply everything as one commit against the current head; on a compare-and-swap conflict re-read and retry (up to 6 times).
- Reindex. If the commit fails for any other reason, the intent goes to
memory_drive_pendingand is retried on the next write or read;/memory/drive/infoshowspending_writes.
The index
reindex snapshots the drive inside an IMMEDIATE SQLite transaction and projects
every active entry into memory_facts with a deterministic id
(drive_ + sha256 of drive id and entry id) through memory_drive_entries. Retrieval
counts and graph edges survive edits because the fact id follows the entry id. Entries
removed from the drive are retired softly (valid_until), like the kernel’s own
supersession. Not indexed: status: proposed|rejected, scope: twin*,
memory_type: not_memory, questions and answers. Every read checks the drive head
against indexed_revision and repairs a stale index.
/memory/v2/recall and /memory/v2/facts are unchanged; they read the index.
Import
import_plan (dry run) lists every active legacy fact for the user, its target topic
file and why any row is skipped (possible secret, foreign or incognito session,
proposed twin). It creates nothing. import_apply writes one Import existing memory
commit plus a receipt (imports/kernel.md), so a crash between the git commit and the
database marker is recovered on the next reindex and a rerun makes no new commit.
Converted rows are retired at once (kept, never deleted); skipped rows stay visible
for 30 days, then hide_expired_archives retires them.
Pushes
Smart-HTTP auth accepts legacy owner tokens (unchanged) and drive-scoped tokens (git_tokens.brain_id, access read or write). A read token gets 401/403 on
git-receive-pack. For a Memory Drive the API installs hooks/pre-receive, which runs
the API binary in validation mode before any config or database is loaded. It reads the
quarantined objects and refuses: refs other than refs/heads/main, deletes, non
fast-forwards, more than 200 commits, any commit whose tree fails the format, path,
mode, size or secret checks, and any change under twin/. After the push
the API repairs the index from the actual head (a refused push still answers HTTP 200
with a pack-protocol error, so the status code is not trusted).
Nightly Dream
memory_dream::spawn checks every 15 minutes and runs one Dream per personal drive with
dreaming_enabled when it is 02:00–06:00 in the drive’s saved time zone
(memory_drives.timezone, V243; server time when unset). The
UNIQUE(drive_id, date) row in memory_dreams is the lease; a run stuck for an hour
or a failed run (up to 3 attempts) is retried.
- Snapshot the drive at the base revision (up to 300 entries, with use counts from
the index) and read the last day’s turns from
memory_observations(up to 80, 600 characters each, incognito excluded). - Ask the model (through
gizzi_completion, the same route as memory extraction, with a JSON schema) for merges, contradictions, lessons, prunes and twin proposals. - Check every suggestion in code (
memory_dream::check): contradiction fixes, lessons and twin proposals must cite turn ids that were actually shown; prunes only remove entries never used and at least 30 days old; each entry is touched once; text must pass the entry format and secret checks. - Apply as one
Dream YYYY-MM-DDcommit byDream via Allternit, and store a report built from what was applied. Twin facts go totwin_persona::proposeand wait for the owner. - No answer or an unusable answer marks the run
failedand changes nothing.
Undo Dream YYYY-MM-DD commit. If a line the Dream touched was edited later, undo
refuses and lists the files instead of guessing.
The lexical memory_consolidation merge skips drive-backed facts. Gizzi’s local
autoDream stays off while the server Dream is on.
Delete forever
MemoryDrive::purge rebuilds every commit without the chosen entry ids (keeping messages,
authors and dates), moves the branch with compare-and-swap, expires the reflog and prunes
objects. The ids and a digest of each removed text go to allternit-memory-purged in the
repo; apply_batch and validate_push refuse them afterwards. memory_drive_scopes::purge
remaps stored revisions and deletes the personal index rows. Gizzi replays only work made
since its last sync (refs/allternit/last-synced) and prunes its local history when the
server’s history was rewritten.
Monitoring
memory_drive_events_total{event} on /metrics counts commit, commit_failed,
pending_retry_failed, push_accepted, push_refused, dream_applied, dream_failed,
dream_no_changes, purge and near_limit (a drive at 80% of its size or file limit).
Shared drives
memory_drives.kind is personal, team, project, bot or swarm, with
scope_id the workspace, project or agent id. The marker owner (the scope’s owner) owns
the repo; access is computed on every request and every git call
(memory_drive_scopes::access):
Shared drives are separate repos and are not indexed into anyone’s personal recall.
Gizzi and other agents mount them next to the personal drive (
GET /memory/drive/mounts).
- Questions board.
questions.mdholds questions (kind: question,status) and answers (kind: answer,parent), each with a stable id. Ask, answer and resolve re-read and retry on conflict, so concurrent agents never lose each other’s lines. - Twin mirror. After any twin change,
twin/active.mdandtwin/proposed.mdare rewritten fromtwin_memorywith status, visibility and provenance on every line.twin_memorystays the only place a fact becomes active. - Bot and project memory. A bot’s or project’s drive is the source of truth for its
memory (
memory.md);cowork_memory_entriesrows for that scope are an index rebuilt from it (memory_drive_cowork), each marked withdrive_id. Store, forget, promote and the weekly curation commit to the drive first. Legacy rows are imported once; rows that can’t be imported (text that looks like a credential) stay as untouched archive rows. Unscoped rows (user-level entries, cowork turn summaries, bots that exist only on one device) stay rows.
Other memory stores
gizzi-code
Gizzi keeps a checkout of the personal drive (and any mounts) and writes with the same format. User-scope agent memory lives in the drive atagents/<agent>/memory.md; offline
commits are replayed one by one, keeping their messages: commit, push, and on a rejected push re-read, re-apply the same entry id once
and push again, never force. Signed out, it uses a local repo per account and merges it
up on first sign-in. See Memory Drive.