1. Executive Summary
Munder Difflin is an Electron desktop harness that runs several terminal coding CLIs — Claude Code, Codex, Gemini, Grok, Kimi, Qwen, OpenCode and others — as a team of agents on one machine, messaging each other through an inbox/outbox bus and drawn as avatars on an office floor. MIT, 666 commits, ~52,000 lines of TypeScript and TSX. Version 0.4.3, badged by its own README as a working prototype.
Its memory is one markdown file per agent, and its own
contribution is what happens when that file gets too big. Each
agent owns
<harnessHome>/hive/agents/<id>/memory.md, a
sequence of dated ## <date> — <title> sections
it maintains under a prompt: "Read your memory.md and drain every
message in your inbox." That much is the editing surface several
harnesses in this atlas ship.
Semantic recall is delegated, and the delegate is a separate
project. MemoryManager (293 lines) shells out to
the MemPalace CLI — already in
this atlas — pointing every agent at one shared palace and mining
each agent's markdown into its own wing, so the team can search by
meaning. It is CLI-only, runs with --no-llm heuristics, and
the header is honest about the failure mode: it "degrades silently
to no-op when the mempalace CLI isn't installed — the
markdown memory still works." Nothing in this tree implements
semantic memory; it wires one in.
MemoryReflector is the mechanism this repository
owns. A memory file has three regions — pinned durable facts
under ## 📌 Durable facts (pinned — never condensed), one
rolling recursive summary, and the newest K verbatim sections. On an
in-process timer, files past a size or section threshold have their tail
evicted, summarised by a cheap headless claude -p, and
folded back in.
The interesting part is that it does not trust the model it
just called. The rewrite goes through backup-first, then a
verify() gate, then an atomic swap, and the contract is
stated as an absolute: "If any check fails the original file is left
byte-for-byte untouched and the only side effect is a
condense-abort log line." Because the backup is a
lossless cold copy taken first, a rejection is a pure no-op.
One mark. audit_log, for the hive's
append-only log.jsonl, which carries condense
with old and new byte counts, evicted, kept and hoisted counts and the
backup path, condense-abort with a named reason, plus
compact, archive and drop.
Withheld and worth naming: scope is deliberately absent between
agents, because the hive is meant to share; nothing records a rejected
value or a correction; the pinned region is a retention rule rather than
an epistemic state; and the repository contains no tests at
all, which lands hardest on the one mechanism most worth
testing.
2. Mental Model
An agent writes what it learned into its own markdown, in dated sections. Nothing grades it, supersedes it or removes it. The file grows until a janitor notices, and then the oldest half is replaced by a summary of itself — recursively, so the summary is a summary of previous summaries — while a pinned block and the newest sections pass through untouched.
The state a memory can be in is therefore positional rather than epistemic: which region of the file it currently sits in.
Diagram source
%% caption: the only transition is downward through the file, and the gate is what stands between a model rewrite and the store
flowchart TD
A["agent writes a dated section"] --> R[("newest K sections<br/>verbatim")]
P[("pinned durable facts<br/>never condensed")]
R -->|"file over budget"| EV["tail evicted"]
EV --> SUM["headless claude -p<br/>summarises the tail"]
C[("rolling recursive summary")]
C --> SUM
SUM --> REB["rebuild the 3-region file"]
BK["backup: lossless cold copy"] --> REB
REB --> G{"verify(): 6 checks"}
G -->|"all pass"| SWAP["atomic swap"]
G -->|"any fail"| KEEP["original kept byte-for-byte<br/>condense-abort logged"]
SWAP --> C
P -.->|"hoist only adds"| P
R -.->|"mtime changed"| MINE["MemPalace miner re-indexes"]3. Architecture
An Electron app. The memory work runs in the main
process, and the reason is recorded rather than assumed:
"launchd-spawned shells are blocked by macOS TCC from
~/Documents; only this process has the folder grant. So the
loop lives alongside memory.start() — never a cron." A
platform permission model decided the scheduler.
State is files: per-agent directories holding memory.md,
inbox/, outbox/, cursor.json and
settings.json, plus a hive registry and
log.jsonl.
4. Essential Implementation Paths
- Condense.
src/main/reflect.ts— threshold check, tail eviction,summarize()viarunHiddenClaude,verify(), atomic swap. - Mine.
src/main/memory.ts—mempalace init/mine/search/wake-up, one shared palace, one wing per agent. - Log.
src/main/hive.ts— the append-onlylog.jsonland the registry. - Read.
hiveMemory(id)over the preload bridge returns raw markdown; the graph insrc/renderer/src/components/memoryGraph/extracts topics from it.
5. Memory Data Model
There is no schema. A memory is a markdown section with a date and a
title, and its only structural property is which of three regions it
occupies. parseMemory splits the file on the pinned heading
and the summary heading; everything after is a list of
Section { heading, body }.
That is the whole model. No id, no status, no provenance beyond the owning agent's directory, no validity time, no supersession pointer.
A third tier, holding what the organisation wrote rather than what the agent did
src/main/knowledge.ts and the pure-JS
kg-core.cjs beneath it add an opt-in enterprise store:
ingest a document, chunk it, index it, and expose search,
list, get, remove and
stats. Agents reach it out of process
through a bundled kg.cjs CLI rather than through the
harness, and the spawn injection bakes the absolute interpreter and
script paths into the prompt for a reason recorded in the source —
$KG_CLI is "POSIX-only and expands to nothing under
cmd.exe/PowerShell", so a shell reference would silently produce a
broken command on Windows. The prompt tells the agent when to reach for
it: "When a task needs that context — company-specific facts, house
style, internal processes — query it instead of guessing."
The boundary between this and memory.md is the
useful part. The per-agent markdown holds what an agent
observed and wrote; this holds documents somebody handed the
organisation. Nothing an agent learns is written back — the ingestion
surface is in-app over IPC, and the agent's CLI is search, list and get.
So the system now has a belief store it maintains and a corpus index it
only reads, kept in separate stores with separate access paths, which is
a cleaner separation than several systems in this atlas manage with one
collection and a type column.
It is not a graph. The name promises structure the
implementation does not have: search tokenizes the query,
scans index.jsonl, and scores chunks by term match. The six
occurrences of "graph" in kg-core.cjs are all in the
docstring naming the feature. That changes nothing about whether it
works and it matters for a reader deciding whether the system carries a
relational memory — it does not.
6. Retrieval Mechanics
Two paths, and neither is implemented here. The agent reads its own
file because the prompt tells it to. Cross-agent semantic recall is
mempalace search and mempalace wake-up against
the shared palace — so retrieval quality, ranking and scoping are
properties of MemPalace, not of this
harness.
A text fallback in src/renderer/src/realtime/tools.ts
greps every agent's memory.md "INCLUDING archived"
ones. Between that and the single shared palace, the design's position
on isolation is explicit: there isn't any, on purpose, because the
premise is a hive that knows collectively.
7. Write Mechanics
Writes are the agent editing its own markdown. The harness adds two things around that.
ensureMineIgnore drops a .gitignore into
each agent directory excluding settings.json,
cursor.json, inbox/ and outbox/,
because mempalace mine honours .gitignore and
those files "swamp the wake-up digest". Keeping non-memory out
of the index by writing an ignore file rather than patching the miner is
a small, correct instinct: it works with the other project's contract
instead of around it.
The condenser is the other. Its budget mirrors the janitor's 128 KB, and its summarizer is given the pinned block "for context only — do not rewrite it".
8. Agent Integration
Terminal CLIs are wrapped rather than replaced, so an agent's memory
is whatever it writes to a file the harness then manages. The memory
graph is a read-and- navigate surface — hiveMemory has no
write counterpart on the preload bridge — so a person inspects memory
here and edits it, if at all, in their own editor.
9. Reliability, Safety, and Trust
verify() is the strongest thing in the tree and
it is worth reproducing as a list, because it is a good model
of what to check when a model rewrites your store:
- The rebuilt text parses back into all three regions.
- Every pinned line survives — a merge may only add.
- The result is actually smaller
(
newBytes < oldBytes * 0.95); a no-op condense is a failure. - It is non-empty and sane — over 200 bytes, with a non-empty summary.
- The kept newest sections round-trip byte-for-byte.
- The summary parsed as valid JSON upstream.
Each failure returns a named reason —
structure-missing-region, pinned-line-dropped,
not-smaller, recent-section-altered — which
lands in the log. The combination of a lossless backup taken first and a
rejection that changes nothing means the worst case of a bad model pass
is a log line.
Against that: there are no tests.
verify() is exported, pure, and takes a plain argument
object — it is the easiest function in the repository to test, and
nothing does. The same holds for parseMemory. A gate whose
whole purpose is to catch a non-deterministic component is a gate that
should be pinned by cases, and the repository has none of any kind.
The other risk is quieter. Condensation is lossy by design and recursive: a summary of summaries drifts, and nothing measures the drift. Pinning is the only defence, and it is manual.
10. Tests, Evals, and Benchmarks
None. No test file, no eval, no benchmark, no paper. Six blog posts
under docs/blog/ and blog/src/posts/ discuss
agent memory — "markdown-first agent memory", "compressing
agent memory", "keep agent semantic memory clean" — and
they are marketing prose about the design rather than evidence about
it.
For a project whose memory story rests on an LLM rewriting a file in
place, the distance between the care in verify() and the
absence of a single case exercising it is the widest gap in the
tree.
11. For Your Own Build
Steal
- Verify the rewrite, not the model. Back up losslessly first, rebuild, then check the result against properties you can state — regions present, protected lines preserved, actually smaller, kept sections byte-identical — and swap only if all pass. A rejection then costs nothing.
- Treat a no-op as a failure.
!(newBytes < oldBytes * 0.95)catches the case where the model returned something plausible that achieved nothing, which a "did it parse" check would pass. - Give every rejection a named reason and log it.
condense-abortwithpinned-line-droppedis debuggable; a boolean is not. - Pin what must never be summarised, and hand it to the summarizer as read-only context. "For context only — do not rewrite it."
- Work with the other tool's contract. Dropping a
.gitignoresomempalace mineskips the inbox is better than forking the miner. - Record why the scheduler is where it is. The macOS TCC note explains a design choice that would otherwise look arbitrary to the next maintainer.
Avoid
- Shipping an unexercised gate. The care in
verify()is real and nothing proves it still works; a checker with no negative control looks identical to a clean one. - A dependency that disappears quietly. Semantic recall degrading to a no-op when a binary is missing means the difference between "the team can recall by meaning" and "it cannot" is invisible at runtime.
- Compaction as the only lifecycle. Nothing here can
mark a line wrong. A false claim in
memory.mdis not corrected; it is eventually summarised, which may preserve it in compressed form.
Fit
Take the condenser's shape if you keep agent memory in markdown and something will eventually rewrite it — the three-region file plus the verification gate is about 400 lines and the most transferable idea here.
Take the harness only if you want the whole product: a desktop multi-agent office with a message bus. Its memory is deliberately shared across agents and has no correction path, so it suits a single operator running a team on their own machine and nothing where one agent's material must stay away from another's.
12. Open Questions
verify()is pure and exported. What has to happen for it to get a test?- Condensation is recursive. What does a summary of summaries look like after fifty cycles, and does anything sample it?
- Semantic recall vanishes silently without the MemPalace binary. Should the UI say which mode the hive is in?
- Nothing can mark a line in
memory.mdwrong. Is a correction meant to arrive as a new dated section that contradicts the old one, and if so what resolves them at condense time? - The text fallback searches archived agents' memory. Is an archived agent's material intended to stay reachable indefinitely?
Appendix: File Index
Memory
src/main/reflect.ts— the three-region model, the condenser,verify()src/main/memory.ts— the MemPalace wrapper,ensureMineIgnoresrc/main/hive.ts— the registry and the append-onlylog.jsonlsrc/renderer/src/components/memoryGraph/— topic extraction and the graphMEMORY_GRAPH_SPEC.md— the graph's design contract
Docs
docs/blog/,blog/src/posts/— six posts about agent memory, prose rather than evidence
History
2026-09-15 — bdf524ec…
— 285 commits on, 2026-09-14, at v0.4.6; most are blog posts and a wave
of merged contributor fixes on 6 September. Screened before reading: no
auto-run surface, one build-time execution point, three unpinned
surfaces and nothing inside the cooldown; nothing was installed or run.
src/main/memory.ts, reflect.ts and
knowledge.ts are byte-identical to the previous pin, so the
condensation gate, the event log and the document store stand as
described. The memory-adjacent changes are operational: malformed outbox
JSON lines recovered in the hive, a stale head lock cleared, abnormal
agent exits recorded to log.jsonl as code, signal and a
path to a gitignored crash tail, and transcript usage parsed once per
file rather than once per querying agent. audit_log
unchanged.
2026-08-22 — 5f7de6e4…
— re-pinned 253 commits and +358,785 lines on, at v0.4.5. Screened
again: no auto-run surface, one build-time execution point, three
unpinned surfaces and three files inside the cooldown; nothing was
installed and nothing was run. audit_log unchanged and it
is still the only mark.
The condensation gate is byte-identical apart from one comment. Every check this report credits — the pinned lines, the byte-for-byte round trip of the newest sections, the backup, the atomic swap and the untouched original on failure — is unchanged at this pin, so the headline mechanism stands without re-derivation.
New in section 6: an opt-in enterprise document store, queried by
agents through a bundled CLI whose absolute path is baked into the
prompt because a $VAR reference "expands to nothing
under cmd.exe/PowerShell". It is a corpus index rather than a
belief store — agents search, list and get, and ingestion is in-app only
— and despite its name it holds no graph: search tokenizes
and scores chunks out of index.jsonl.
Worth recording as a fact about the project's own practice: two commits in this range are titled "the scope-filter comment keeps the reasoning, drops the count" and "the ledger comment keeps the reasoning, drops the figures", and a third makes before-and-after evidence mandatory in pull requests. A codebase deliberately stripping figures out of comments that will outlive them is doing to itself what this atlas has to do to its own pages.
2026-08-17 — 6a09318d…
— First reading, at 666 commits. Screened first: 0 auto-run surfaces, 1
build-time execution path (an npm postinstall running
electron-rebuild and two node-pty patch scripts), 2
manifests inside the seven-day cooldown; nothing was installed, built or
run. One mark, audit_log. Semantic recall is delegated to
the MemPalace CLI, which has its own report,
so retrieval and scoping belong there. Withheld here: no scope key
between agents by design, no rejected-value record, no epistemic state,
and no tests of any kind — including for verify(), which is
exported and pure. No paper.