Aletheia

Aletheia memory model: memories, facts, episodes and rules

Aletheia (aletheia-mem) keeps everything it remembers in one SQLite file, hub.db, in its data directory (~/.aletheia by default). A memory is one stored record. Facts, episodes, entities and rules sit beside memories and point back at the record they came from.

Two layers in one file

The first layer is the memories table. Hooks, aletheia save, the MCP save_memory tool, the REST API and the importers write to it. Search and the session-start context read it. The Knowledge tab of the dashboard shows it.

The second layer is derived. An extraction step reads some memories and builds episodes, facts, entities, generated summaries and links between them. Extraction runs when you run aletheia compress, or in the background while aletheia-server is running. It never runs inside a hook.

Rules, settings, dismissals and an audit log live in the same file.

Kinds of stored things

ThingWhat it isWritten byTable
MemoryOne record: a prompt, tool event, session summary, note or observationhooks, save, MCP, REST, importsmemories
EpisodeOne raw turn or tool step, kept as source text and never editedextraction, import transcripts, watchepisodes
ObservationA derived record with an importance score, a pin and a statusextraction, schema migrationobservations
FactOne statement (subject, predicate, object) with dates and a confidenceextraction from summaries and notesfacts
EntityA named thing inside a project: person, service, file, repo, library, concept or orgextractionentities, entity_aliases, fact_entities
SummaryText built from facts, stored with the ids of the facts it came fromextraction, aletheia summarizesummaries, plus a memory row
RuleA standing instruction you want every tool to followyou, MCP add_rule, approved suggestions, importsrules, rule_evidence

The two layers are not mirrors. A memory you save today does not get a row in observations. Only generated summaries and rows copied during a schema migration do.

Memory kinds

Every memory has a kind that says where it came from.

Memory types

The type column is a label for what the memory is about. It is separate from kind. A note saved with --type decision has kind note and type decision.

The session-start context marks each memory with one letter:

Legend: b=bugfix f=feature r=refactor c=change d=discovery D=decision n=note g=gotcha e=event s=summary ?=other

The store keeps any type string up to 40 characters, and the legend shows ? for a type it does not know. The MCP save_memory tool accepts decision, gotcha, bugfix, feature, refactor, change, discovery and note.

Scope: project and global

Every memory, episode, observation and fact has a project and a scope. The project key is the repository folder name, or <repo>/<worktree> for a git worktree. Hooks derive it from the working directory the host reports. A folder with no git checkout uses its own name.

The scope is project by default. A project read returns that project's rows plus every row promoted to global. Promote a row to make it visible in every project:

aletheia promote memory 6
{
  "type": "memory",
  "id": 6,
  "scope": "global",
  "project": "other"
}

The row keeps its project label and the change is written to the audit log. There is no command to turn a promoted row back into a project row. To stop seeing it, dismiss it. Pass --scope all to aletheia search or aletheia context to read every project at once.

Pin

A pinned memory is listed under ## Pinned in the session-start context, up to 50 of them, whatever the recent-memory limit is. Pinned memories also sort first in the default Knowledge list. Pin when you save, or later from the dashboard:

aletheia save "Use bun, not npm, for every script in this repo" --project demo --type decision --pin
Saved as #1.

The MCP save_memory tool takes pinned: true. PATCH /api/hub/memories/<id> with {"pinned": true} pins an existing row.

Dismiss and restore

Dismiss hides a memory, episode, observation or fact. It deletes nothing. A dismissed row disappears from search, lists and the session-start context. Restore brings it back.

aletheia dismiss 5 --reason "wrong after the rewrite"
aletheia dismissed
aletheia restore memory:5
Dismissed memory #5. Restore with: aletheia restore memory:5
memory:5  2026-10-29T19:50:36.367Z  wrong after the rewrite
Restored memory #5.

A bare number means a memory. Use fact:12, observation:12 or episode:12 for the other types. Only aletheia prune --apply removes dismissed rows for good, and it leaves episodes in place. To delete on purpose and get a signed receipt, use aletheia erase.

Importance score

Observations carry an importance score between 0 and 1. It is a weighted sum, and every input is stored with the score so you can see why it came out as it did.

aletheia importance recompute --project w
{
  "updated": 2,
  "formula": "importance-v1",
  "weights": {
    "type": 0.35,
    "recency": 0.25,
    "reinforcement": 0.2,
    "provenance": 0.1,
    "user": 0.1
  },
  "halfLifeDays": 90
}

The score has two uses. The Facts table shows the linked observation's importance. The retention rule never marks a fact cold while its observation is pinned or scores 0.8 or higher. Search ranking and the order of the session-start context do not use it.

How facts replace or conflict

A fact is one statement with a subject, a predicate and an object, for example "sync service" / "stores" / "its outbox in SQLite". It also carries the text, a valid-from and valid-to date, the date it was recorded, a confidence from 0 to 1, a status (active, superseded or cold), the extractor mode (rules or provider) and the episode it came from. A fact with no episode is rejected.

Only summaries and notes are fact sources. A prompt is an instruction, and an event is tool output, so neither produces facts. A candidate fact must also pass a quality gate before it is stored: balanced quotes and brackets, no code tokens, no narration about the conversation, 3 to 40 words and at most 200 characters.

When a new fact arrives for the same project, subject and predicate:

Nothing is deleted. aletheia search --history shows closed facts, --as-of DATE shows what was valid on a date, and the MCP fact_history tool lists how one subject changed. The quality gate, the confidence floor and aletheia facts clean are in Facts and contradictions.

Where each part lives in hub.db

TablesHolds
memories, memories_fts, memory_files, memory_vectorsmemories, their full-text index, the file paths they touched, optional embeddings
episodesraw turns and tool steps
observationsderived records with importance, pin and status
facts, facts_fts, fact_vectorsfacts, their full-text index, optional embeddings
entities, entity_aliases, fact_entitiesnamed things and the facts that mention them
memory_links, memory_keysprovenance links such as derived_from, supersedes and mentions, and extra search keys
summariesgenerated summaries with their source ids
rules, rule_evidence, rule_suggestions, rule_suggestion_quotesrules, what each rests on, and suggested rules waiting for a decision
dismissals, review_queuehidden rows and items waiting for your review
jobs, fact_usagethe extraction queue and the tokens each extraction used
session_end, settings, auditsession-end marks, saved settings, and who changed what

The schema is in store.mjs.

Next steps