1. Executive Summary
SimpleMem is an academic memory system from AIMING Lab, MIT-licensed,
and the repository holds three products rather than
one: SimpleMem (text, the arXiv:2601.02553 paper),
Omni-SimpleMem (multimodal, v2.0), and EvolveMem (v3.0, self-evolving
retrieval). Roughly 114,000 lines of Python, of which the
simplemem/ package is 49,782.
The idea worth the read is in 433 lines. MemoryBuilder
runs an LLM over windows of dialogue and emits MemoryEntry
objects whose defining property is context
independence: the field is called
lossless_restatement and its schema documents two
transforms — Φ_coref, no pronouns, and Φ_time,
absolute timestamps — so a stored unit reads "Alice discussed the
marketing strategy for new product XYZ with Bob at Starbucks in Shanghai
on November 15, 2025 at 14:30" rather than "she told him about
it there". Almost every system in this atlas stores the second kind
and hopes retrieval supplies the missing context. Resolving it at write
is cheap, one-time, and makes every later retrieval independent of the
turn it came from.
Then the store it goes into. VectorStore
(simplemem/core/database/vector_store.py) exposes
add_entries, three searches, get_all_entries,
optimize and clear. There is
no delete and no update — not for a memory, not for a fact, not for a
user. Wiping the whole store is the only removal verb the text pillar
has, which is the finding this atlas recorded for AutoGen's protocol, arriving here in a system
that ships the implementation rather than an interface.
MemoryEntry has no user id, no session id and no scope key,
so there is also nothing to delete by.
The claims are the other half of the report. The
README makes six specific quantitative claims — a 26.4% average F1 gain
on LoCoMo with roughly 30× less inference-time token consumption; LoCoMo
F1 = 0.613 (+47%) and Mem-Gallery F1 = 0.810 (+51%) for Omni-SimpleMem;
+25.7% on LoCoMo and +18.9% on MemBench for EvolveMem — and a "Benchmark
& Reproduce" section with a runner per pillar. A search of the
repository at this commit returns zero .json,
.jsonl or .csv files of any kind. Not
a scored result, not a raw prediction dump, not a metrics table. The
harness is committed and the results are not, which is the same
traceability gap the atlas records for Memvid,
MemoryOS and FiFA — here at six figures
rather than three.
2. Mental Model
The text pillar has no epistemic model whatsoever. A memory becomes true by being extracted, and there is no path by which it stops being true: no status, no confidence, no supersession, no delete. The system's entire answer to "the world changed" is that a later dialogue produces a later entry, and both are now in the index, both retrievable, with nothing marking which is current.
Because MemoryEntry.timestamp is the event's
time — the moment described, not the moment recorded — the store cannot
even sort by recency of belief. This is worth naming precisely, because
it is the mirror image of the corpus. Almost every system here tracks
record time and no validity time, which is why the bi-temporal column
is thin. SimpleMem tracks validity time and no record
time. It knows when things happened and not when it learned
them, so "what did we believe last March" is unanswerable from the other
direction, and the mark is withheld for the same reason: one clock is
one clock.
EvolveMem, the third pillar, has the model the first one lacks —
status taking active, archived or
superseded, an importance and a
confidence float, feedback that moves importance, and a
supersession event naming what replaced a memory. It is a lifecycle
rather than an epistemology: archived and
superseded say a record is out of service, not that a claim
is doubted, and the confidence default of 0.7 is never
revised by corroboration. No trust_state mark, and the
near-miss is that the field exists and answers a different question.
Diagram source
%% caption: extraction resolves pronouns and absolutises timestamps so the stored entry is self-contained, and the only removal verb wipes everything because no entry carries a key to delete by
flowchart TB
Dlg["Dialogue window"] --> Ext["LLM extraction<br/>Φ_coref: pronouns resolved<br/>Φ_time: timestamps absolutised"]
Ext --> Entry["MemoryEntry<br/>semantic + lexical + symbolic layers<br/>timestamp = when it HAPPENED"]
Entry --> Store[("LanceDB<br/>add_entries only")]
Store --> Recall["Query analysis → parallel search<br/>→ adequacy check → more rounds"]
Store -.->|"the only removal verb"| Clear["clear()<br/>wipes everything"]
Entry -.->|"no user id, no scope key,<br/>so nothing to delete BY"| Store3. Architecture
The text pillar needs a vector database and a model provider.
VectorStore wraps LanceDB with a pluggable backend module
beside it, and the whole storage layer is 1,784 lines including the
retriever. There is no service, no queue and no background worker:
extraction happens when you call it, retrieval happens when you ask, and
both are synchronous LLM calls.
EvolveMem is a different shape: SQLite with seven tables —
memories, memory_events,
memory_links, memory_watches,
memory_annotations, scope_access,
stats_snapshots — plus a schema_version table
and FTS. Its store.py is 1,798 lines and its
manager.py is 5,064, which is three times the entire text
pillar.
Around them the repository ships an MCP server, an HTTP server with
per-user API keys and a user_store, a Claude skill, a
Dockerfile and compose file, and a 102-line frontend that is a landing
page rather than a console.
4. Essential Implementation Paths
simplemem/core/models/memory_entry.py— the unit, and the two transforms that define it.simplemem/core/memory_builder.py(433) — windowed extraction, parallel batches, the prompt.simplemem/core/hybrid_retriever.py(969) — query analysis, parallel search across three indexes, adequacy check, further rounds.simplemem/core/database/vector_store.py— the add-only store.simplemem/evolver/store.py(1,798) — the schema, the event log, scope filtering, supersession.simplemem/evolver/manager.py(5,064) — the EvolveMem surface.simplemem/evolver/evolution.py(1,727) — the evaluate/diagnose/propose loop.
5. Memory Data Model
MemoryEntry is a Pydantic model with three declared
index layers: semantic (the restatement, dense-embedded), lexical
(keywords, for BM25-style match), and symbolic
(timestamp, location, persons,
entities, topic, as metadata constraints). The
tripartite split is clean and the docstrings cite the paper's section
numbers, which makes the code readable against the method.
What the model does not carry is as informative: no scope key, no
source or provenance pointer back to the dialogue it came from, no
record timestamp, no status, no confidence, no version. An entry is a
uuid4 and six content fields.
EvolveMem's memories table carries the rest —
scope_id NOT NULL, importance,
confidence, status, created_at,
updated_at, tags — and its memory_annotations
table has an author column, which is the only place in the
repository where a human is modelled as a distinct writer.
6. Retrieval Mechanics
HybridRetriever.retrieve is the paper's "intent-aware
retrieval planning" and it is LLM-heavy. _analyze_query
asks a model what the query needs; _generate_search_queries
expands it; _execute_parallel_searches runs semantic,
keyword and structured searches across threads;
_merge_and_ deduplicate_entries folds them;
_check_answer_adequacy asks a model whether the assembled
context can answer the question, and if not,
_generate_additional_queries fires another round.
That is a genuinely careful retrieval design — the adequacy check is a form of retrieval hysteresis applied to sufficiency rather than to churn, and few systems here re-query on their own judgement. It is also three or more model calls before an answer is attempted, which is the cost side of the 30× token claim: fewer tokens in the context window, more calls to get there. Nothing in the repository measures the tradeoff in latency or in provider spend.
Scope is absent from this path entirely. No scope
key is stored, so none is filtered. The scope_enforced mark
on this report comes from EvolveMem, where
WHERE scope_id = ? appears on every read of
memories, memory_events and the stats tables,
backed by a scope_access table keyed on
(scope_id, principal, permission) with an
admin permission that satisfies any check.
7. Write Mechanics
Writes block. add_dialogue(dialogue, auto_process=True)
accumulates into a window and process_window calls the
model; add_dialogues_parallel shards large batches across a
worker pool. Extraction is one LLM call per window, and the memory is
retrievable as soon as it is written — no queue, no lag, no background
consolidation. For a benchmark harness this is the right shape; for a
deployed agent it means a conversation turn pays the extraction cost
inline.
Nothing rewrites the store. There is no consolidation pass, no decay, no promotion between tiers, and no compaction — the "online semantic synthesis" of the paper happens within a window at write time, not across the store afterwards. That is a real simplification and the source of most of the system's clarity.
8. Agent Integration
An MCP server, an HTTP server with API-key-per-user auth and session
objects, a CLI, and a packaged Claude skill
(SimpleMem.skill, SKILL/). The HTTP layer is
the only place user_id appears in the text pillar, and it
scopes sessions and API keys, not memories —
delete_session removes a session, and the entries that
session produced stay in the shared vector store with nothing linking
them back.
9. Reliability, Safety, and Trust
memory_events earns the audit_log
mark, and it is the best-built thing in the repository after the memory
unit. The table is created with the comment "Event log for
audit trail", carries event_id autoincrement, timestamp,
type, memory id, scope and detail, and is written by a single
_log_event helper at seven call sites: create,
share, merge, archive,
pin, feedback and supersede. No
DELETE FROM memory_events or
UPDATE memory_events appears anywhere in the repository, so
it is append-only in fact as well as in intention, and
share and merge — the two events most likely
to lose provenance — record their source ids in detail.
The gap is that it audits the pillar nobody benchmarks. The text store that the papers, the claims and the skill are about writes no events, because it has no mutations to write: nothing there ever changes after it is added.
EvolveMem is not this repository's own code.
simplemem/evolver/store.py, consolidator.py
and models.py are byte-identical to
metaclaw/metaclaw/memory/ in MetaClaw, the same lab's other repository — same
MemoryStatus enum, same supersede, same
_log_event, same garbage_collect. So two
reports in this atlas describe one engine, and whatever is true of it is
true once rather than twice. Read the two together as one piece of
evidence, not as corroboration.
That matters here for one finding in particular, which the MetaClaw
reading turned up on 2026-09-17 and which applies verbatim:
garbage_collect reads the half of the supersession
lineage that almost nothing writes. It builds its
referenced set from active rows' forward
supersedes lists, and that list is written in exactly one
place — the merge path at store.py:651,
supersedes=[id_a, id_b]. The three supersede()
calls in consolidator.py write only the backward
superseded_by. So every memory the consolidator supersedes
is an orphan by the collector's own definition and is hard-deleted on
the next collection, with no event logged for the deletion — in a store
whose audit log is otherwise the best-built thing in it.
trust_state is earned, and was missed at the
first reading. The same three listings that carry
scope_id = ? carry status = 'active', so a
superseded or archived unit cannot reach a caller, and both non-default
states have live writers. The rubric asks for a discrete status with at
least one state that withholds a memory from being treated as true; this
has two.
No tombstone, and the shape of the miss is unusual.
EvolveMem supersedes by status and logs supersede with
by=<id>, which is record-keyed like every
supersession here. But the text pillar cannot even express removal, so a
value a user rejects is not merely re-assertable — it was never
retractable in the first place. clear() is the whole
vocabulary.
Annotations are not a review surface, and the
distinction matters because the table looks like one.
add_annotation(memory_id, content, author) inserts a note;
get_annotations reads it back; nothing in retrieval,
ranking or lifecycle consults annotations at all. A person can write in
the margin and the system will never read it.
record_feedback(memory_id, helpful) does have an effect —
±0.03/0.05 on importance, clamped — but it moves a ranking
float, not a status, so a memory a user marked unhelpful ten times still
surfaces, slightly lower. human_review is withheld.
10. Tests, Evals, and Benchmarks
311 test functions across roughly twenty files, none run here. The
distribution is the story: tests/ at the repository root
holds 636 lines covering the vector store and its backend, and the rest
live under cross/tests/, OmniSimpleMem/tests/
and MCP/. For 114,000 lines and three products this is a
thin posture, and the components carrying the paper's contribution — the
extraction prompt, the restatement transforms, the adequacy check — are
not where the tests are.
test_locomo10.py is committed at the root, with a second
copy under MCP/reference/, and
EvolveMem/run_benchmark.py and
run_evolution.py beside it. The harnesses are real. What is
missing is any artifact they produced: no scored output, no per-question
predictions, no results table in docs/. Every one of the
six headline numbers in the README is therefore a claim about a run that
is not in the repository, and this atlas has no way to check any of
them.
No negative evaluation exists — nothing asserts that particular material must not be retrieved — which follows from there being no deletion, no scope on the text path, and no rejection to assert about.
11. For Your Own Build
Steal
- Resolve coreference and absolutise time at write. A memory that reads correctly with no surrounding turn is worth more than a better retriever over memories that do not. This is one prompt and it is the single most portable idea in the repository.
- Declare your index layers and keep them separate. Semantic, lexical, symbolic, with the unit carrying the fields each needs, means a structured filter and a fuzzy match are not fighting over one embedding.
- Let the retriever judge its own sufficiency.
_check_answer_adequacyfollowed by another query round is a cheap way to stop returning k chunks and hoping. Budget it, because it is a model call. - One
_log_eventhelper, called at every mutation site. Seven event types, one insert path, no update or delete anywhere in the file. That is how an audit log stays an audit log.
Avoid
- A store whose only removal verb is
clear(). It is defensible in a benchmark harness and indefensible the moment a real user says "forget that". Ship a delete before you ship a skill. - Publishing the harness and withholding the results. Six numbers with a reproduce section and no committed artifact puts the entire verification cost on every reader, and it is the failure the atlas collects under published benchmark numbers without committed artifacts.
- Annotations nothing reads. An
authorcolumn and a note that never reaches ranking, retrieval or lifecycle is a comment field, and calling it review would be generous. - Two memory subsystems where the governed one is not the one you benchmark. The scope key, status field and audit trail are all in EvolveMem; the paper, the claims and the packaged skill are all the text pillar.
Fit
This is research code, and it should be read as the source of one very good idea rather than as a memory layer to deploy. Take the restatement transform into your own extractor — it is a prompt and a schema, and it will improve any retrieval you already have. Take the event-log discipline from EvolveMem if you need an audit trail and do not want an event-sourcing framework.
Do not deploy the text pillar behind a user-facing agent. It cannot delete, it cannot scope, and it cannot correct, so the first support request you cannot answer is "remove what it learned about me". EvolveMem is the part with the governance apparatus, and it is also the newest, least documented and least tested of the three.
12. Open Questions
- Do any of the six figures reproduce? The harness is
committed and the results are not. Running
test_locomo10.pyagainst a LoCoMo checkout is the obvious check and was not done here. - What does the reflection loop cost? Query analysis, expansion, parallel search and an adequacy check are three-plus model calls per retrieval. The 30× token claim is about context, not calls, and the two are not the same budget.
- Is
Φ_corefmeasured? The correctness of the restatement — whether the extractor resolves pronouns to the right person — decides everything downstream, and no test or metric in the repository touches it. - Are the three pillars converging? They share a name and a repository and almost no code, and EvolveMem's SQLite schema knows nothing of the text pillar's LanceDB entries.
Appendix: File Index
| Path | Lines | What it holds |
|---|---|---|
simplemem/evolver/manager.py |
5,064 | The EvolveMem API surface |
simplemem/evolver/store.py |
1,798 | Seven-table SQLite schema, _log_event, scope
filtering |
simplemem/evolver/evolution.py |
1,727 | Evaluate, diagnose, propose, guard |
simplemem/multimodal/orchestrator.py |
1,431 | Omni-SimpleMem's four-modality pipeline |
simplemem/core/hybrid_retriever.py |
969 | Query planning, parallel search, adequacy check |
simplemem/evolver/diagnosis.py |
955 | Per-question failure analysis |
simplemem/core/memory_builder.py |
433 | Windowed extraction into MemoryEntry |
simplemem/core/answer_generator.py |
153 | Answer synthesis over retrieved entries |
simplemem/core/models/memory_entry.py |
~70 | The unit, and the two transforms |
tests/test_vector_store*.py |
636 | The root test suite, in full |
History
2026-09-19 — db80b6a7…
— trust_state re-tested at the unchanged pin; the mark
holds and the count is now on the record — the active predicate appears
eight times across store.py, not on a single read. What is
worth drawing out is the arrangement rather than the count:
WHERE scope_id = ? AND status = 'active' puts the scope
clause and the status clause side by side in the same query, one
answering whose memory this is and the other whether it may still be
believed, and the two marks this report carries rest on the two halves
of that line. Both non-active states have live writers —
store.supersede from three consolidator sites and the merge
path, and the archival updates — so the vocabulary is exercised rather
than declared. Unlike the systems in this corpus that redirect a
superseded row to its replacement, supersession here is plain exclusion:
the successor is found by the ordinary query because it is active, and
the predecessor is simply not returned. Nothing was installed and no
suite was run.
2026-09-18 — re-read at the same commit, confirmed
still the tip by git ls-remote before a
--depth 1 clone. Nothing could have moved, so this reading
audited the first one. Screened again: no auto-run surface, three
build-time execution paths including a pytest conftest.py,
seven unpinned manifests, nothing inside the cooldown; nothing was
installed or run.
The finding is that EvolveMem is not this repository's
code. simplemem/evolver/store.py,
consolidator.py and models.py are
byte-identical to metaclaw/metaclaw/memory/ in the same
lab's MetaClaw, which was re-read the
previous night. Two reports in this atlas therefore describe one engine.
Everything the MetaClaw reading found in that engine applies here
without re-derivation — including the defect worth repeating:
garbage_collect builds its live set from active rows'
forward supersedes lists, which only the merge path writes,
so every unit the consolidator supersedes is an orphan by the
collector's definition and is hard-deleted, with no event logged.
trust_state is added, for the same
reason and on the same code as MetaClaw's: MemoryStatus is
active | superseded | archived, the listings filter
status = 'active' beside scope_id = ?, and
both non-default states have live writers — store.supersede
from three consolidator sites and the merge, four
UPDATE memories SET status = ? statements writing
archived in the manager. All three marks now carry evidence
records.
Everything else held: the text pillar still has no scope key, no
status, no delete and no update, and clear() is its whole
removal vocabulary. The nearest thing to a negative retrieval assertion
in the suite is test_lancedb_backend_escapes_filter_values,
which passes a filter value of finance' OR TRUE and asserts
the result is empty — an injection that must not widen a result rather
than named material that must not be returned, so
negative_eval stays withheld.
stack_source moves from seeded to
reviewed. Marks go from two to three.
2026-07-30 — db80b6a7…
— first reading.