The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Darwin Memo listing page.
Memory for LLM agents that dies unless it earns its keep. Every entry pays energy upkeep and earns only from measured outcomes: bytes actually freed on a real disk, tests actually passing. Poisoned advice gets executed by the environment it damaged. Useless trivia starves. There is no reward model, no LLM judge, and no human curation anywhere.

Watch a poisoned entry go extinct in your own terminal, one command, no keys, no checkout:
Use darwin-memo where a conserved, measurable outcome exists to settle decisions against: coding-agent lesson stores settled by CI pass counts (the primary target, see the integration guide), storage and artifact retention, cache and dedup advisors, spend-cap automation.
Do not use it for chat-preference memory, RAG over documentation, or
personal assistants. Those have no conserved resource pushing back, and
upkeep would starve the long tail of correct-but-rarely-used knowledge.
mem0, Zep, and Letta serve that market; darwin-memo deliberately does
not. The honest rule: if your verify would be a model scoring an
answer, this package is wrong for you, by design.
The demo corpus contains an ops runbook, platform notes, and one poisoned document: a forum post claiming database files are "redundant and safe to remove". Before selection pressure exists, retrieval confidently repeats the poison, because it has no reason to doubt it.
Then 30 survival cycles run against StorageEnv, a disk cleanup
sandbox where the selection signal is actual bytes on an actual disk.
Deleting a disposable file frees its size. Deleting a protected file
triggers a restore that costs three times the size. Nothing grades the
answers, the filesystem just responds:
Three death modes show up in the graveyard, and the distinction matters:
Attacking the Curator: Curation-Targeted Attacks on Agent Memory, and What Survives Them. An adversary that corrupts the settlement signal rather than injecting poison — denial of memory — measured against six curation mechanisms across attack budgets and seeds, with exact paired permutation tests and Holm-Bonferroni correction.
It reports its negative results as prominently as its positive ones. Absent an attacker the ledger buys leanness and cost, not accuracy; and across 2,115 evaluated SWE-Bench-CL tasks, no memory arm beat carrying no memory at all.
A practical mix of two papers. MeMo says what memory is, the survival paper says what gets to stay in it.
| Paper | What this repo takes from it |
|---|---|
| MeMo: Memory as a Model (Quek et al.) | Keep the main LLM frozen and put knowledge in a dedicated memory. The reflection-QA encoding pipeline and the three-stage query protocol (grounding, entity identification, answer seeking). |
| Survival is the Only Reward (Dodgson et al.) | Environment-mediated selection. The only signal is a conserved, physically measurable resource delta. Behaviors that persist get reinforced, everything else is pruned. There is no proxy to hack. |
Requires Python 3.10+. The core has zero dependencies; everything below runs offline.
The demo carries its own corpus. To point the same machinery at yours, three commands and no Python:
encode splits each document into self-contained QA pairs and reports
what it made:
query answers with provenance, and stays quiet when nothing clears
the relevance floor. Both outcomes are the point:
Nothing has died yet, because nothing has been measured yet: entries
only start paying upkeep once you settle decisions against a real
outcome. That is the next section. For a walkthrough on your own
directory, including how to read a wrong-but-confident answer, see
examples/09_your_own_corpus.py.
A MemoryEntry is a self-contained QA pair (.question, .answer,
.sources, .energy). The store retrieves, the protocol answers with
provenance, the environment measures, credit flows back.
Real outcomes arrive late. The Ledger decouples the three moments: decide now, settle whenever the measurement lands, tick on your own cadence. Entries with unsettled tickets are escrowed: they keep paying upkeep but cannot be buried or merged until their verdict arrives.
doctor reads the event log and names which failure mode a store hit
instead of leaving several of them looking identical. On a store nothing
has measured yet it says so, rather than reporting a clean bill of
health.
ui is the same data as a working surface: the living population with
each entry's balance, runway and flags; open tickets with their ids; the
graveyard split by cause of death, where every id opens that entry's
whole life; the event log, filterable to one entry; and the energy
accounting kept visibly separate from your resource unit, because the
two are not comparable. It also writes — pin, unpin, forget, abandon,
add, tick and settle — so the store you are reading is the store you can
act on.
Loopback-only, and a write additionally needs a loopback Origin, a JSON
content type and a per-process token embedded in the page.
One thing there is deliberately different in kind. Settling from a
browser means typing a delta, and a typed number is the human judgment
this package exists to exclude. It is not refused; it is marked. The
event log and every per-entry note record source: "operator", why
and audit show it, and doctor raises operator_settled once
hand-entered deltas outweigh measured ones. A store curated by hand
keeps working and stops being evidence, visibly.
From a source checkout the dashboard needs building once
(cd ui && npm install && npm run build); released wheels ship it.
The agent gets fourteen tools, in three groups.
Use it. memory_query returns an answer, a ticket id, and the
entry ids credit will flow to. memory_settle reports the measured
delta later, and says plainly when a settlement did NOT land.
memory_abandon releases a ticket you chose not to act on.
memory_add writes a lesson. memory_tick advances time.
Inspect it. memory_stats for the population, memory_top for
what the memory is made of, memory_pending for open tickets with
their ids, memory_obituary for one entry's credit history,
memory_audit for the event log, and memory_doctor to name the
failure mode behind a store that is not earning.
Curate it. memory_forget buries a lesson that is wrong but inert
— selection only removes what it measures, so an entry nothing acts on
never gets settled and starves only slowly. memory_pin and
memory_unpin exempt an entry from starvation and merges; pin
sparingly, since a pin suspends the only mechanism that removes bad
memory.
The full state, including open tickets, persists across sessions and restarts, so a ticket opened today settles correctly from tomorrow's process.
The Ollama client and embedder speak the native localhost API over
stdlib urllib, so the complete stack (encoding, the 3-stage protocol,
real embeddings, the measuring environment) runs on one machine with no
third-party packages and no keys:
examples/07_local_stack.py runs it end to end, and
darwin-memo query memory.json "..." --model ollama:llama3.2 does it
from the shell. The selection loop is call-hungry (cycles x tasks), so
free local inference is what makes LLM-mode experiments economically
sane; python -m bench.run --suite llm is the at-home recipe for the
LLM-mode benchmark question the docs flag as open. The survival
mechanics stay deterministic; the sampled model does not, which is why
that suite never runs in CI.
pip install "darwin-memo[anthropic]" and set ANTHROPIC_API_KEY; the
examples pick it up automatically.
In any LLM mode the memory snippets are numbered and the model cites
which it used, so credit flows to the entries that actually shaped the
answer (even spread over everything consulted is the fallback, and
<think> blocks from reasoning models are stripped before citations
are parsed).
The environment is the whole trick, and yours is probably better than
the demos. Implement two methods, and keep the one rule: verify must
measure, never grade.
Good conserved resources: tests passing, bytes freed, requests served under budget, rows deduplicated, dollars of spend avoided. Bad ones: anything a model scored.
Three silent failure modes catch every new environment, and they all end the same way (the whole population starving around cycle 20 with every delta at zero). The loop's summary now warns about each, but know them up front:
decision_polarity's built-in markers
speak delete/remove and apply/keep, the bundled environments'
dialects. "Safe to cancel" reads as silence unless you pass
extra_positive/extra_negative markers for your verbs.LexicalRetriever(min_coverage=0.25).
Your task phrasing must share vocabulary with your corpus, or use an
embedding retriever. Silence beats guessing, but silence earns zero.Two more failure modes, how to pick a conserved resource, how to price a
mistake from a real cost, and how to table-test verify before running
any loop are in
docs/custom-environments.md — the full
guide this section condenses, with two worked environments to read.
Retrieval is pluggable through the Retriever protocol; the store stays
the single owner of the energy ledger, and no retriever may read energy
when scoring (selection pressure comes from outcomes, never from
retrieval preferring incumbents).
text -> list[float] function
(sentence-transformers, an API endpoint). Vectors persist inside
memory.json so paid embeddings are never recomputed on load.Honest scaling note: ranking is pure-Python O(population x dims), fine
to a few thousand entries. Past that you want numpy or an ANN index,
which is out of scope for the zero-dependency core. With cosine
retrievers, raise merge_threshold to roughly 0.85 or unrelated
entries will consolidate.
Survival selection culls a stale entry only after it causes damage, so every consult surface carries the time dimension instead of waiting for the world to hurt:
store.retrieve(..., half_life=20), --half-life 20 on query and
ledger decide, half_life on the MCP memory_query tool) and
scores halve for every half-life since an entry last settled. A pure
ranking concern: balances and credit assignment never see it.kind and source filters (--kind, --source) narrow the
candidate population before ranking and compose with everything
above.Survival is benchmarked against five baselines across 10 seeds, with
ablations and a scaling probe, all reproducible offline from bench/.
The sharpest comparison is random_matched: identical per-cycle
eviction counts, random victims.
| arm | kill rate | kill cycle (med) | damage before kill | tail delta | cum delta |
|---|---|---|---|---|---|
| survival | 1.00 | 0 | -394k | +437k | +12.6M |
| random_matched | 0.80 | 19 | -10.7M | +38k | -7.67M |
| keep_everything | 0.00 | never | -12.1M | -236k | -9.08M |
(Rounded from the full tables; regenerate both with the commands in the benchmarks doc, and if the numbers ever disagree, the generated doc wins.)
Same pruning rate, 27x the damage, runs that end 7.7M underwater:
outcome direction is the active ingredient, not eviction itself. The
harness also runs the baseline that keeps us honest:
evict_on_negative, a one-line "evict whatever erred" heuristic, ties
survival on outcomes in this deterministic environment (officially: a
paired permutation test cannot tell them apart); the ledger's measured
edge here is leanness (4 surviving entries vs 15).
Forgiveness is no longer asserted, it is measured: a noisy suite makes measurements lie deterministically and scores everyone on the truth. At 5% flaky-CI noise (good changes reporting red), survival's true outcomes are byte-identical to its noise-free run in every seed (29 of 30 seeds at 10-20%) while every strike counter collapses (k=1 loses essentially all benign capability by 5%; the strongest variant, strikes-reset-on-success, halves by 10%; every gap holds at adjusted p < 0.005). The suite also publishes the costs: lying rewards delay the poison's execution (median kill cycle 0 to 3 as symmetric noise rises to the half-lies extreme, where 2 of 30 seeds never kill it), and past roughly one lie in three the ledger itself degrades hard, benign capability down to 0.26 at 50%. A paraphrase probe set, scored by provenance rather than keywords, quantifies how the demo degrades outside its own vocabulary, and an embedding-retriever arm shows the mechanism does not depend on the lexical-match path. Full tables, every baseline's best metric stated plainly, and honest caveats: docs/benchmarks.md.
.darwin-memo/lessons.json is curated by
memory.yml on every merged PR.darwin-memo render projects a store that has been
pruned by measured outcomes into the file your agent already reads.darwin-memo render projects the store into the auto-memory MEMORY.md Claude
Code reads at session start, inside its 200-line / 25KB ceiling, or
into an index plus topic files with --split-dir.agent_end outcomes.DarwinMemoSession implements the SDK's Session
protocol (transcript replay as honest JSONL) and adds the long-term
layer the SDK leaves vacant: opt-in consult/settle against a
lesson store, deltas always measured by the host.EvmSettler, zero dependencies):
on-chain balance deltas and gas are judge-free settlement signals,
readable with no API key (the snapshot flow needs no archive node;
the module docstring names public endpoints that lie about
history).An adaptive, brain-like layer, complete through Phase 4: memories connected by
relevance-weighted links, shrinking to a gist when unused and expanding to full
detail on recall, with a recall spreading one hop and strengthening the links it
travels — all on earned/measured signals, no judge. OrganicMemory(store)
is the facade; store_related(store, entry_id, k) is the one-shot primitive.
Phases 1–3 are additive and read-only with respect to survival: relatedness is
mechanical cosine, value is still earned by the ledger. Phase 4 (earned
importance) is the exception and is opt-in — it biases ranking by default,
and slows upkeep only if you pass om.upkeep_scale() to charge_upkeep. That
makes usage a retention signal, which this repo's own salience_matched arm
measured at a 0.20 poison kill rate against random eviction's 0.80; read
docs/organic.md before wiring it. Zero-dependency by
default; pip install darwin-memo[organic] adds a turbovec ANN backend for
scale.
The docs index links everything. The operator set: the tuning guide (the load-bearing knobs, failure symptoms, evidence-backed starting points per profile), the API reference (Python surface, CLI, MCP tools, exceptions), and the store format (the on-disk JSON, the event log and its rotation, the sidecars, the compatibility policy).
09 is the one to read when the demo works and your own files do not:
it takes a directory, and it shows the retrieval floor rejecting a
question phrased in structural words rather than hiding it.
Five environments ship. Three measure a resource: StorageEnv (bytes
on a real disk), TestSuiteEnv (passing tests in a generated
micro-project, with destructive patches dressed as cleanup), and
VerifiableQAEnv (exact containment, the weakest grounding but still a
measurement). Two price inaction, which the other three score at
zero: RentedStorageEnv and RentedTestSuiteEnv charge for holding on
rather than only for acting, because several conclusions here rest on
inaction being free and that is a property of the world, not of
curation.
Writing your own is the load-bearing task, and it has a guide: docs/custom-environments.md.
To distill survivors into an actual parametric memory model (MeMo's
native form), training/train_memory_model.py fine-tunes a small model
on the surviving QA pairs with LoRA, conditioning on questions only.
The distill benchmark arm (python -m bench.run --suite distill,
opt-in, needs torch/transformers/peft/datasets) turns this into
measured evidence: it distills the energy-ledger survivor set, the
unfiltered raw set, and the LLM-judge-kept set into separate LoRA
models and scores each by containment — good_recall (does the model
recall the surviving facts?) and poison_reproduction (does it emit the
buried poison?). The result is survival selection working as a data
filter for parametric memory: the survivor-distilled model recalls the
good facts and reproduces none of the poison, while the raw-distilled
model reproduces it — because the poison was in its training set. See
docs/benchmarks.md.
0.6 * tanh(delta / resource_scale) when they decide a task
(supporting entries get 25% of that), and are capped at 5.0. Death is at
zero. All tunable via MemoryStore and SurvivalConfig.StorageEnv keeps
the file: the safe reading of an irreversible action. A side effect
worth knowing: protective knowledge ("never delete X") eventually
starves because it is redundant with that default. The population
converges to exactly the knowledge that changes behavior.The full concept-to-code mapping, including honest deviations from both papers, is in docs/paper-to-code.md. The story of why this exists: docs/launch-post.md.
The load-bearing tests: poisoned advice must die and useful advice must survive across seeds and across two environment families, ledger escrow must hold verdicts open, and hypothesis property tests pin the conservation laws (energy pools exactly on merge, caps hold, retrieval never reads energy), all with no labels anywhere.
To cite darwin-memo itself, or the paper it ships:
Both entries are provisional: there is no archival deposit yet, so
neither carries a DOI. CITATION.cff is the machine-readable version and
says the same thing.
This repo is an independent practical interpretation, not the official code of either source paper. If you build on the ideas, cite the originals too:
MIT