1. Executive Summary
Rememora is "[p]ersistent, cross-agent memory for AI coding agents. One SQLite database, shared by every agent you use" — MIT, Rust, version 1.7.0, 17,313 lines across 56 files with 345 tests, plus a Tauri desktop app and a benchmark harness. The premise is stated as a complaint: Claude Code, Codex and Gemini CLI "each lose context between sessions", and switching agents mid-task starts you from scratch.
The mechanism to take away is how it sizes its guards. Consolidation here is a language model reading a cluster of similar memories and deciding to merge, supersede or keep. Most systems in this corpus that do this bound the work by what the model can handle. Rememora bounds it by what a wrong answer would cost:
"Largest number of memories one consolidation decision may supersede. This is the blast radius of a single LLM call. It is deliberately much smaller than
MAX_CLUSTER_SIZEso that even a fully-hallucinated decision can only retire a handful of records."
MAX_SUPERSEDE_PER_DECISION is five;
MAX_CLUSTER_SIZE is eight. The gap between those two
numbers is the whole idea, and it only exists because someone asked what
happens when the model is confidently wrong rather than when it is
slow.
The cluster limit is a refusal, not a cap. A cluster over the limit is not trimmed and processed — it is skipped before a token is spent, with the reason handed back to the person:
"cluster has {n} memories, over the safe limit of {MAX_CLUSTER_SIZE} — review it by hand"
and the argument for why is written above it: clustering is transitive, so a large cluster "is more likely to be one bad edge chaining unrelated memories than a genuine pile of duplicates". That is a system declining to act on its own weakest inference rather than acting carefully.
Writing is armed, not assumed.
resolve_apply is one line —
!dry_run && (apply_flag || env_armed) — so evolve
is a dry run unless --apply is passed or
REMEMORA_APPLY=1 is set, and --dry-run beats
both. The constant that holds the environment variable calls it the one
that "arms the destructive path".
And the project removed the automation that would have gone
around all of this. The v1.7.0 notes lead with it: Rememora "no
longer wires any automatic Claude Code / Gemini CLI hooks",
setup --apply "self-heals any existing install by stripping
them out", and curation now runs from rememora curate or
rememora dream, "never from an automatic hook". Taking away
a feature that made the product feel autonomous, and shipping code that
uninstalls it from machines that already had it, is a stronger signal
than any flag default.
What retrieval withholds is a supersession.
contexts.superseded_by is a pointer to the memory that
replaced this one, and superseded_by IS NULL is appended to
the full-text search, the vector nearest-neighbour search and the
timeline — all three arms, not the one somebody remembered. The row
survives with its content intact, so the history is readable and the
replacement is named.
The honest limits are three. The epistemic vocabulary is one bit: superseded or not, with nothing for a claim that is disputed or believed on thin evidence, and the bit is set by a model. Reversibility is narrower than the release notes suggest — the undo journal stores and prints the SQL that would reverse a decision, and nothing executes it, which is why the cluster-size constant can still call consolidation "irreversible" without contradicting itself. And the project key does not bind: the search applies its URI prefix only when a project is supplied, in a database the design deliberately shares across every agent on the machine.
2. Mental Model
One database, many agents, and every interesting rule is about what happens when something automated wants to change it.
A memory is a row addressed by a rememora:// URI. Agents
write memories by calling the CLI over Bash. Curation reads session
transcripts and proposes memories; consolidation clusters similar ones
and asks a model what to do with each cluster. Both are commands, both
write to an audit surface, and the second one refuses work it judges
unsafe to hand a model.
Diagram source
%% caption: agents write memories into one shared SQLite database; consolidation clusters similar ones, refuses any cluster over the size limit before calling the model, caps how many memories one decision may retire, and writes an undo-journal row inside the same transaction as the supersession; every retrieval arm filters out superseded rows
flowchart TB
AG["Claude Code · Codex · Gemini CLI<br/>— all calling one CLI over Bash"]
DB[("contexts — one SQLite database<br/>shared by every agent<br/>uri · category · content · importance<br/>superseded_by")]
CUR["rememora curate / dream —<br/>reads session transcripts ON DEMAND,<br/>never from an automatic hook"]
CLOG[("curator_log — APPEND-ONLY<br/>action · context_id · reason · model")]
AG --> DB
CUR --> DB
CUR --> CLOG
EV["rememora evolve"]
ARM{"resolve_apply =<br/>!dry_run AND (--apply OR REMEMORA_APPLY)"}
DRY["dry run: print the plan,<br/>change nothing"]
CL{"cluster size > MAX_CLUSTER_SIZE (8)?"}
SKIP["SKIPPED before a token is spent —<br/>'review it by hand'<br/>(clustering is transitive, so a big cluster<br/>is more likely one bad edge)"]
LLM["the model decides:<br/>merge · supersede · keep"]
VAL{"decision validates?<br/>at most MAX_SUPERSEDE_PER_DECISION (5)"}
REJ["REJECTED whole,<br/>never partially applied"]
TX{"one transaction"}
SUP[("UPDATE contexts<br/>SET superseded_by = the survivor")]
UNDO[("evolve_undo — APPEND-ONLY<br/>retired ids · survivor · created id<br/>undo_sql, the statement that reverses it")]
DB --> EV --> ARM
ARM -->|"not armed"| DRY
ARM -->|"armed"| CL
CL -->|"yes"| SKIP
CL -->|"no"| LLM --> VAL
VAL -->|"no"| REJ
VAL -->|"yes"| TX
TX --> SUP
TX --> UNDO
UNDO -.->|"written inside the decision's own transaction,<br/>so a rollback leaves no row<br/>claiming the decision happened"| TX
UNDO -.->|"stored and PRINTED —<br/>nothing executes it"| HAND["an undo is a person running the SQL"]
Q["search · vector knn · timeline"]
F["every arm appends<br/>superseded_by IS NULL"]
DB --> Q --> F --> OUT["the memories an agent gets back"]
SUP -.->|"the row survives with its content;<br/>the pointer names the replacement"| F3. Architecture
| Area | Role |
|---|---|
src/models/ |
The context record, relations, watermarks and the curator log |
src/migrations/ |
Numbered SQL migrations with their design commentary |
src/search.rs |
Full-text search with boolean grouping and a BM25 noise floor, and the vector arm |
src/propagate.rs |
Graph propagation across relations with a depth bound |
src/evolve.rs, src/commands/evolve.rs |
The clustering limits, the consolidation pass, and the undo journal |
src/curator.rs,
src/commands/curate.rs |
Transcript extraction, on demand |
src/commands/setup.rs |
Installation, and the code that strips the hooks the project removed |
app/ |
The Tauri desktop application |
bench/, tests/eval_retrieval.rs |
Retrieval measurement |
4. Essential Implementation Paths
src/evolve.rs:24-35—MAX_CLUSTER_SIZEandMAX_SUPERSEDE_PER_DECISION, with the blast-radius argument.src/commands/evolve.rs:133-134—resolve_apply, the arming rule.src/commands/evolve.rs:205-228— the oversized-cluster refusal and the discarded-decision path.src/commands/evolve.rs:561-584— the undo table and why it is created inside the decision transaction.src/commands/evolve.rs:691-710—append_undo.src/models/context.rs:161— the supersession write.src/search.rs:178,:403,src/timeline.rs:96—superseded_by IS NULLon all three arms.src/migrations/003_curator.sql:27-40— the curator audit log.src/commands/setup.rs:274-280—strip_rememora_hooks.
5. Memory Data Model
A context carries a uri, an optional
parent_uri, a context_type constrained to
memory, resource, skill or project, an optional category
constrained to preference, entity, decision, event, case or pattern, a
name, an abstract, an overview, the content, tags, the
source_agent and source_session, an
importance, an active_count and
superseded_by.
The schema comment explains the single-table choice: it "outperforms a table-per-kind design for long-tail entity types, and keeps hotness/importance ranking uniform across all memory categories".
category is a write-time genre and
importance is a ranking input, so neither carries the trust
mark; superseded_by is the one field retrieval consults to
withhold. Sessions carry their own status — active, ended
or transferred — which is a session lifecycle rather than a claim about
a memory.
Every timestamp is a record time. There is no validity interval and
no as-of read, so bitemporal is withheld on an absence.
6. Retrieval Mechanics
Three arms, each filtering superseded rows. Full-text search supports grouped boolean expressions and applies a noise floor, with a comment explaining the number: FTS5 clamps non-positive IDF — a term in more than half the documents — to 1e-6, so a hit whose whole score comes from such terms lands around 1e-6 and is not a match. A vector arm searches context embeddings. Propagation walks relations to a bounded depth.
Project scoping is a URI prefix, applied only when a project is passed.
7. Write Mechanics
An agent saves a memory through the CLI. Curation reads transcripts
on demand, tracking how far it has read in a watermarks
table so a re-run does not re-extract, and writing a
curator_log row for every action including
noop.
Consolidation is the pass with the guards. Clusters over eight memories are refused outright; the model is asked about the rest; a decision that fails validation is discarded whole "not partially applied, and the run continues with the next cluster"; an applied decision writes its supersessions and its undo row in one transaction.
8. Agent Integration
The CLI is the interface, callable over Bash by any agent, with a
Tauri desktop app beside it and a Homebrew tap for installation.
agent_invocations and hook_invocations tables
record who called what.
The integration story is mostly about what was removed. v1.7.0
stopped wiring lifecycle hooks into Claude Code and Gemini CLI, and
setup --apply now strips any it previously wrote —
"preserving all non-rememora user content untouched", dropping an event
array that ends up empty rather than leaving it as
"Event": [], and treating a missing file as a no-op rather
than an error.
9. Reliability, Safety, and Trust
Human review — withdrawn on the 2026-09-19 re-read.
Each of the three legs is worth having and none of them is this mark.
The arming is
resolve_apply = !dry_run && (apply_flag || env_armed)
— a flag the caller sets, which the rubric names as the
--force shape: it guards against a slip, not against a
decision. And a sibling command sets it for you:
rememora dream, the command the project points you at for
routine upkeep, builds its EvolveArgs with
apply: !args.dry_run
(src/commands/dream.rs:41), so the destructive pass runs
armed by default there, and the CLI help invites wiring it to "your
own cron/launchd job" (src/main.rs:414-417). The
oversized-cluster refusal is real and is the best thing here — a system
declining to act on its own weakest inference — but it routes nothing:
the skip produces one ActionReport row in this run's
printed summary (src/commands/evolve.rs:209-228), nothing
durable records it, and the next run re-clusters and skips it again, so
"review it by hand" names a queue that is never written down,
let alone drained. The hook removal is the removal of an automation
rather than a check on an actor; after it, nothing runs unattended
unless the operator schedules it, which is a default, not a gate. All
three keep their description in sections 1 and 9.
Audit log — awarded, and bounded. Two append-only records, one of them transactional, covering curation and consolidation. An ordinary save is not journalled, and the mark is awarded on what these two tables do rather than on coverage of every mutation.
Trust state — awarded, and thin. One bit and a pointer, consulted on all three retrieval arms. There is no vocabulary for a disputed or unverified claim, and the bit is a model's judgement.
Negative evaluation — awarded. The search cases assert presence and absence over the same populated result set, which is what stops a must-not assertion passing on an empty answer.
Tombstone — withheld. A superseded memory is retained and its replacement named, which is the right shape for history, but nothing is keyed on the memory's content and no write path consults the retired row. A claim consolidation removed from retrieval can be saved again as a new row.
Scope enforced — withheld. The project prefix is applied only when a project is supplied, in a database shared across every agent on the machine.
10. Tests, Evals, and Benchmarks
345 test attributes, with behaviour tests organised by subject —
behavior_search, behavior_propagation,
behavior_timeline, behavior_worktree,
behavior_hotness — plus a scenarios directory and a written
SCENARIOS.md. tests/eval_retrieval.rs and the
bench/ directory carry retrieval measurement separately
from the assertions.
The consolidation tests are the load-bearing ones for this report:
test_applied_supersession_is_journalled_in_the_database
drives an armed run and checks that the journal row names the retired
and surviving ids and that the recorded SQL "really does put the memory
back".
11. For Your Own Build
- Size the cap by what a wrong answer costs. Five is not five because five memories fit in a prompt; it is five because a hallucinated decision should only be able to retire five records. Set the blast radius below the batch size, deliberately, and write down why.
- Refuse the input you distrust rather than handling it carefully. A cluster built by transitive similarity is exactly where a bad edge hides, so the safe move is to hand it back, not to process it with more prompting.
- Write the undo row in the same transaction as the change. It costs nothing and it buys the property that the journal cannot claim a decision that did not happen.
- Store the statement that reverses it. Even
unexecuted, a recorded
undo_sqlturns "we changed something" into "here is exactly what to run" — though if you say reversible in your release notes, say who runs it. - Removing your own automation is a legitimate safety change. This project shipped code that uninstalls hooks it had previously installed, rather than leaving them and documenting a flag.
12. Open Questions
- Will anything execute
undo_sql? The journal has the statement, the ids and the timestamps; what is missing is the command that applies one and a column recording that it was applied. - Should the project prefix be mandatory in a shared database? The design deliberately shares one store across agents, which is exactly the setting in which an omitted predicate reads across everything.
- Is one bit enough? A memory can be superseded or current, and nothing can say a claim is disputed, unverified, or believed on evidence the curator flagged as thin.
- What stops a consolidated claim returning? Nothing is keyed on content, so the same memory can be saved again after being retired.
Appendix: File Index
| Path | What lives there |
|---|---|
src/evolve.rs |
The cluster and supersession limits with their rationale, and the BM25 noise floor |
src/commands/evolve.rs |
The consolidation pass, the arming rule, the refusals and the undo journal |
src/commands/curate.rs,
src/curator.rs |
Transcript extraction on demand |
src/models/context.rs |
The context record, its loads and the supersession write |
src/models/watermark.rs |
The curator-log insert and the read watermark |
src/search.rs |
Both retrieval arms, each filtering superseded rows |
src/propagate.rs |
Depth-bounded graph propagation |
src/commands/setup.rs |
Installation, and strip_rememora_hooks |
src/migrations/001_initial.sql |
Contexts, sessions and relations, with the single-table argument |
src/migrations/003_curator.sql |
The curator audit log and the consolidation-run record |
tests/behavior_search.rs |
The presence-and-absence search cases |
tests/behavior_propagation.rs |
The superseded-exclusion case |
Searches behind the absence claims above, run from the repository root:
grep -rn 'DELETE FROM curator_log\|UPDATE curator_log' src --include='*.rs' # nothing: append-only
grep -rn 'DELETE FROM evolve_undo\|UPDATE evolve_undo' src --include='*.rs' # nothing: append-only
grep -rn 'undo_sql' src --include='*.rs' # stored and printed; no execute
grep -rn 'valid_from\|valid_to\|as_of' src --include='*.rs' # no validity axis
History
2026-09-19 — re-pinned to 58ac331e….
One commit since the previous pin, touching README.md only,
so every source line below is byte-identical and this is a correction to
our own reading. human_review is withdrawn; three
marks stand. The arming flag is the --force shape
the rubric names, and rememora dream — the command the
project offers for routine upkeep and suggests wiring to cron — passes
apply: !dry_run itself, so the gate is absent on the path
most people will run. The oversized-cluster refusal, the best mechanism
in the tool, hands a cluster back with "review it by hand" as
one printed report row and writes nothing down, so there is no queue for
a person to work. The hook removal is a default rather than an actor
check. audit_log, trust_state and
negative_eval re-verified unchanged. Screened again first:
the same two dependency manifests, several files inside the cooldown,
nothing installed or run.
2026-09-16 — 9fe78eba…
— first reading, at a commit dated 15 September 2026, version 1.7.0.
Screened before opening, from a full clone so dependency ages are the
project's own rather than the clone's: fourteen files scanned, one
auto-run surface, one build-time execution point, two unpinned
dependency surfaces and two dependency files inside the seven-day
cooldown, with app/src-tauri/Cargo.lock unchanged for 143
days and bench/pnpm-lock.yaml for 168.
AGENTS.md and CLAUDE.md are addressed to a
reading agent and were recorded as data. Nothing was installed, built or
run, and no benchmark was reproduced.