A small profile, an unlimited archive

There are two memories, deliberately. The file memory — MEMORY.md and USER.md — is a stable ~2 KB "profile" that sits in the prompt. The semantic memory is the archive: every record is a self-contained fact (50–500 characters recommended) with a scope — agent for shared knowledge, user for facts about the client, run for session episodes. Confidence and importance scores, tags, typed edges and a full change journal sit on top, all in SQLite with FTS5 and embeddings.

Append-only, with receipts

Facts are never overwritten. When something changes, the new version is a new record linked to the old one with a supersedes edge; contradictions stay visible from both sides through a contradicts edge. Reading by id has three modes: active, latest (follow the chain to the newest version) and full_history — the whole evolution of a fact. An audit trail is not a feature here; it is the storage model.

Absorb, not create

Writing goes through a pipeline, not an insert. Validation first, then secret detection — API keys, tokens and PEM blocks are rejected outright. Nearby facts inside a batch are consolidated (N→1 at similarity 0.85), exact duplicates die by hash, and cosine similarity pulls up candidates. A classifier — the LLM when available, a 0.97-threshold heuristic otherwise — assigns each incoming fact exactly one of five outcomes: duplicate (skip), supersede, contradict, related or new. A dry_run mode prints the plan without writing anything.

Hybrid search with decay

Retrieval mixes two signals: score = 0.7·cosine + 0.3·BM25. Then a linear freshness decay — score × max(0, 1 − 0.01·age_in_days), one percent per day — keeps stale facts from outranking current ones. An optional second pass ([memory] rerank = true) lets the LLM re-order the expanded result set (top_k×3). In benchmarks the hybrid search has a median latency around 2 ms.

A frozen snapshot keeps the cache warm

Before the session starts, the top agent receives a deterministic digest in its system prompt: relevant memories plus open TODOs plus recent records — with real ids, so the agent can verify anything via memory_search. Crucially, the snapshot is frozen for the whole session: writes are persisted immediately, but they never mutate the prompt. That keeps the LLM's prefix cache intact for the entire run; the snapshot refreshes at the next session. Memory that is cheap to read is memory that actually gets used.

A graph of the real world

Alongside the facts, the store maintains an entity graph: nodes — person, company, project, technology, role, location, event, product — deduplicated by (name + type), so "Ivan Petrov" and "ivan petrov" are one node. Edges are typed — works_at, leads, founded, located_in — and multi-hop BFS up to 4 hops answers questions like "who leads X" or "who works at companies in Kazan". When an absorbed fact carries entity metadata, its nodes and edges are created with it, automatically.

Nothing is ever deleted. Distillation compresses, GC archives, and the full history stays reachable by id — a memory you can audit is a memory you can trust.— Parallel Research, memory design notes

Housekeeping without deletion

Nightly-style consolidation is distill: run-facts from sessions are re-absorbed into durable agent knowledge, duplicates filtered out, originals archived. gc is a conservative offline sweep in three stages — expire rows past their TTL, archive stale never-accessed run-facts, and compact overgrown scope groups into consolidated summaries. Both are available from the CLI and over HTTP, both support --dry-run, and neither ever destroys a record. Absorb takes 49–521 µs per fact and re-absorbing 100 already-known facts is a 2.2 ms dedup fast path — the store is cheap enough to be always on.