Memory
Five stores, each holding a different kind of thing, and none of them asking the model to remember to remember. Recall is system-owned: relevant facts are pulled into every turn whether the model thinks to look or not.
The five systems
| Memory | Store | Role |
|---|---|---|
| Working | the live window | What the model sees this turn. See Context window. |
| Episodic | events.jsonl | Append-only source of truth, crash-safe. |
| Semantic | Typesense (managed) | Curated cross-session facts, deduped, with provenance. |
| Codebase | SQLite graph | Symbols and call edges. See Code intelligence. |
| Procedural | ~/.crv/skills/*.md | Markdown skills, loaded on trigger. See Skills. |
Distillation, compiled not prompted
At every turn close the summary pass runs against a grammar compiled from a JSON schema. The grammar is passed to the sampler, not asked for in the prompt — prose is not a shape the model can produce.
grammar, err := tools.SchemaToGBNF(turnCloseSchema)
reply, _, err := l.llm.Complete(ctx, messages, nil, grammar, 1024)
Four fields come back:
Asking politely for JSON fails eventually. A grammar cannot. Every memory is a typed record the moment it is written, so it can be searched, merged and superseded later without anything downstream parsing prose.
The curator
New knowledge that contradicts old does not pile up beside it. curator.Write() takes a candidate and decides:
An explicit correction marks the previous document superseded and links its replacement by id. Search then filters on superseded:=false, so last month's mistake is not competing with this month's fact. The chain is preserved rather than deleted, which is what makes provenance answerable.
The dedup gate
Every candidate is embedded and run k-NN against existing documents. The thresholds are explicit:
| Similarity | Action |
|---|---|
| > 0.9 | Merge into the existing document: bump confidence and last_seen, add a source reference. |
| 0.7 – 0.9 | Create it, and link related_to. Surfaced in the panel for review. |
| < 0.7 | A new document. |
Four properties that make it trustworthy
superseded_by chain, so the history of a belief survives.pending_semantic episodic events and drain through the dedup gate on recovery. A write never dies because a dependency was briefly missing.Recall is system-owned
The agent never has to remember to search. Before each turn the harness pulls relevant facts and past events and formats them:
## Recalled memory (auto, system-owned)
- [evt_004411] the retry cap matches the upstream timeout
- [evt_004583 (live tail)] working on the swarm trigger
Every pull carries its event id, so a claim can be traced back to the turn that produced it. Live-tail entries are recent events from the running session rather than the long-term store.
<system-reminder> envelope, and any closing tag inside a stored document is escaped first. Memory holds whatever it was given, including text off a web page — the envelope keeps it usable without letting it speak as you. See Safety guard.
Hybrid search
Typesense does keyword search on its own. With the embedder sidecar running, search is hybrid — keyword plus vector — which is what finds a fact you described one way in June and another way in August. Without the embedder it degrades to keyword-only rather than failing.
The episodic log
Append-only events.jsonl per session under ~/.crv/sessions/. It is the source of truth: every message, every tool call and its full output, every error. Pointer demotion works precisely because this exists — the window can throw text away safely when the disk still has it.
It is also what the compaction briefing is assembled from, and what GET /api/sessions/{id}/events replays. See HTTP API.
The write-behind indexer
An indexer keeps a cursor on the last indexed event. If Typesense goes down the cursor stalls while events keep appending; when it comes back, indexing replays from the cursor. Self-healing, because the file is the source of truth and the index is only ever a derived view of it.
The same recovery path drains any pending_semantic candidates that parked while the store was unreachable.
memory_type. Recall filters by tag or queries across all of them. For episodic it is a rebuildable index — events.jsonl is the truth. For semantic it is the primary home, snapshotted to semantic.jsonl for git-friendly backup.
Instant sessions
Scratch sessions never promote to long-term semantic memory. They are swept after 24 hours idle, at boot and every 30 minutes thereafter. Use them for a question you do not want colouring the store.
Reviewing what it kept
Memory is inspectable, not a black box. The panel has a memory view backed by these routes: