An empty result tells you whether the address never existed or was archived

memhtml

A git repository of semantic HTML files, one fact each, with a rebuildable SQLite index, four ranking arms sharing one scope filter, and a seventeen-phase curation pass that commits to a branch only a person may merge.

Carries 4 of 7 rubric mechanisms. Most systems here carry none or one (41%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

1. Executive Summary

memhtml "stores an agent's long-term memory as a git repository of semantic HTML5 files, one fact per file. A rebuildable SQLite index sits over that tree, retrieval fuses four ranking arms, and a seventeen-phase curation pipeline commits its work to a branch a human reviews before it lands." Apache-2.0, TypeScript, 132,835 lines across 393 files, two binaries and an MCP server.

The design is a division of labour, stated as a rule.

"Three actors share one tree. The agent writes facts, and it resolves only the conflicts it found itself. Sleep curates on a branch when a caller fires it, and it detects conflicts without resolving them. The human owns the gate and every one-way door."

Everything else follows. The nightly pass puts its commits on sleep/<date> and leaves main untouched. Each phase commits separately "so a human reads the curation one phase-shaped diff at a time", and trace-consolidation lands "each distilled memory as its own commit, one per memory, so a reviewer reads one claim at a time." The phases that could decide and choose not to open a task instead, quoting the sentence they found, because "[a] detection is a proposal for a human, never a fact the corpus asserts." memhtml sleep merge fast-forwards main "only after a quality gate that can refuse", and that gate re-runs the retrieval evaluation "so a sleep run that degrades retrieval cannot land."

The evaluation behind that gate is the second thing to take. Its controls are the system's own merge veto read backwards:

"The three predicates that forbid folding two memories together are the three ways to build a plausible impostor, and a control the veto cannot see does not test anything."

Negation, numeric token and variant qualifier become text transforms "that turn a true claim into a high-similarity WRONG one", and the discrimination gate asks "can the retrieval stack rank a memory above its own high-similarity WRONG twin?" Each control is validated against its own family's predicate rather than the disjunction, so one that trips a different rule does not count.

A third piece answers a question most stores leave hanging: what an empty result means. When a scope matches nothing, archivedMatches reports how many archived rows the same scope matches, so an agent can "tell 'never existed' from 'archived'" and follow archived[].supersededBy to the replacement. A corrected memory leaves the default view — "the default scope excludes archived rows" — without leaving the corpus.

And the scope filter is built once for all four arms, with the reason given: "[p]er-arm filters would let a scope apply to three arms and not the fourth, which surfaces as a scoped query returning a result from outside the scope. No type catches that leak."

The gap is that the mutation record is git. The phase trailers and the one-commit-per-claim discipline are real, and they live in a history a rewrite can change, with no append-only table in the store beside them.

2. Mental Model

A memory is one HTML file in a git tree, and the index is rebuildable.

A correction archives the loser and links it to its winner, both ways.

A curation run is a branch of phase-shaped diffs.

A task is what the machine opens when it declines to decide.

An empty result says whether the address never existed or was archived.

Diagram — the agent writes to main, the sleep run curates on a branch it may not merge, a person owns the gate, and the merge is refused by an evaluation whose adversarial controls are generated from the system's own anti-merge predicates
Diagram source
%% caption: the agent writes to main, the sleep run curates on a branch it may not merge, a person owns the gate, and the merge is refused by an evaluation whose adversarial controls are generated from the system's own anti-merge predicates
flowchart TB
    AGENT["the agent — writes facts to main at any hour,<br/>one fact per file, and resolves only<br/>the conflicts it found itself"] --> MAIN[("main: a git tree of semantic HTML5,<br/>one fact per file, PARA directories")]
    HOOK["session hooks: recall and trace indexing<br/>under a hard time bound"] -.->|"NO hook writes a memory —<br/>any failure prints nothing and exits 0,<br/>so a hook can never block a turn"| MAIN
    MAIN --> IDX[("rebuildable SQLite index:<br/>files · chunks · edges · vectors")]
    IDX --> ARMS["four arms: FTS · vector · recency · salience,<br/>fused with reciprocal rank"]
    SCOPE["one scope filter string, built ONCE"] --> ARMS
    SCOPE -.->|"'per-arm filters would let a scope apply to three arms<br/>and not the fourth, which surfaces as a scoped query<br/>returning a result from outside the scope.<br/>No type catches that leak.'"| SAFE["the leak a type cannot catch"]
    ARMS --> RES{"any hits?"}
    RES -->|"yes"| HITS["each hit carries supersededBy,<br/>present-and-nullable"]
    RES -->|"no"| EMPTY["archivedMatches: how many archived rows<br/>this same scope matches"]
    EMPTY -.->|"so an agent can tell 'never existed'<br/>from 'archived', and follow the link<br/>to what replaced it"| ANSWER["an empty result that answers"]
    MAIN --> SLEEP["memhtml sleep run — seventeen phases<br/>on a sleep/date branch, main untouched"]
    SLEEP --> COMMITS["each phase commits separately;<br/>trace-consolidation lands ONE commit per claim"]
    SLEEP -.->|"phases detect contradictions<br/>WITHOUT choosing a winner"| TASKS["a task, quoting its evidence verbatim,<br/>capped at ten a night — 'a proposal for a<br/>human, never a fact the corpus asserts'"]
    COMMITS --> HUMAN["the human reads phase-shaped diffs"]
    HUMAN --> MERGE{"memhtml sleep merge"}
    GATE["the discrimination gate: can retrieval rank a memory<br/>above its own high-similarity WRONG twin?"] --> MERGE
    CTRL["controls generated by reading the merge veto backwards —<br/>negation, numeric token, variant qualifier;<br/>each validated against its OWN family's predicate"] --> GATE
    MERGE -->|"gate refuses"| NOLAND["a run that degrades retrieval cannot land"]
    MERGE -->|"gate passes"| MAIN
    CORR["a correction"] --> ARCH["git mv into archive/ +<br/>memhtml-superseded-by on the loser,<br/>a supersedes edge on the winner, ONE commit"]
    ARCH -.->|"the default scope excludes archived rows;<br/>includeArchived or the asOf lens brings them back"| IDX
    ASOF["asOf lens"] -.->|"coalesce(valid_from, event_at, created_at) bounds the start —<br/>so a memory that declares no validity is valid from<br/>the moment it was written"| IDX

3. Architecture

Area Role
packages/store The git tree, supersession meta, archive moves, the merge driver
packages/index The rebuildable SQLite index, the four arms, and the one scope filter
packages/sleep Seventeen curation phases, each its own commit
packages/eval Adversarial controls and the discrimination gate the merge runs
packages/domain Divergence predicates, retention scoring, confidence decay
packages/integrations Four host installers, and the receipts that make uninstall exact

4. Essential Implementation Paths

packages/index/src/scope.ts:6-20 — one filter for four arms, and the leak no type catches.

packages/index/src/scope.ts:195-200 — the point-in-time predicate and its coalesce fallback.

packages/index/src/retrieval.ts:140-160 — the count that makes an empty result informative.

packages/eval/src/controls.ts:1-20 — the merge veto read backwards into impostors.

packages/eval/src/discriminate.ts:8-12 — the one question the gate asks.

packages/store/src/store.ts:253, :266 — a correction as one commit touching both sides.

5. Memory Data Model

One semantic HTML5 file per fact, carrying the claim, type, entities, validity bounds, confidence and supersession meta, addressed by path inside PARA directories. The SQLite index holds files, chunks, edges and vectors and is rebuildable from the tree, which is what makes the tree the system of record.

6. Retrieval Mechanics

FTS, vector, recency and salience arms fused with reciprocal rank, each receiving the same scope filter. Archived rows are excluded by default and admitted by includeArchived or by the point-in-time lens. Every hit reports whether something superseded it and what.

7. Write Mechanics

The agent writes through the CLI or MCP; hooks never write. Corrections are one commit touching the loser's meta and the winner's edge together. Eviction and compression are git mv into archive/, so nothing is destroyed by curation.

8. Agent Integration

Fifteen MCP tools and three resources over stdio, plus installers for Claude Code, Codex, Cursor and OpenCode that write MCP entries, hooks, an instruction block and a skill — each recorded in a receipt with the SHA-256 of every owned file or fragment, "so uninstall removes exactly what install wrote and nothing you added beside it."

9. Reliability, Safety, and Trust

The strong parts are the actor separation, the refusable merge gate, the single-filter scope, and an empty result that explains itself. The gaps are an audit that is git rather than a table, scope that narrows only what the caller asked, and a validity fallback that silently equates world time with write time for memories that declare neither.

10. Tests, Evals, and Benchmarks

The discrimination gate runs in pnpm check, in CI, and again at merge, with a deterministic fake-embedder mode so it needs no credentials, and a live mode that reports skipped: true rather than passing when it cannot reach a model. Write-path acceptance corpora sit beside it.

11. For Your Own Build

Build your scope filter once and hand the same string to every arm. A scope that holds on three of four retrieval paths is a leak no type system will catch.

Generate your adversarial controls from the rules you already wrote. If you have predicates that forbid merging two records, those predicates describe exactly the impostors your retrieval has to survive.

Make an empty result say why. "Never existed" and "archived last night" are different answers, and only one of them means the agent should stop looking.

Let the machine open a task where it declines to decide, and make the task quote its evidence. A proposal with the sentence attached is reviewable; a flag is not.

12. Open Questions

Whether an append-only record belongs beside the git history. Everything about the curation is designed to be reviewable, and the review record itself lives somewhere a rewrite can edit.

Whether the validity coalesce should be visible in the answer. A historical query whose result rests on created_at because no memory declared a validity is a different claim from one resting on valid_from, and the result does not currently distinguish them.

Appendix: File Index

Path What to read it for
packages/index/src/scope.ts:6-20 One filter for every arm, and the reason
packages/index/src/retrieval.ts:140-160 An empty result that tells you which kind of empty
packages/eval/src/controls.ts:1-20 Adversarial controls derived from your own veto
packages/eval/src/discriminate.ts:8-12 The single question a retrieval gate should ask
README.md:126 Three actors, and which one owns the one-way doors

History

2026-09-164778735a… — first reading, at a commit dated 16 September 2026. Screened before opening, from a shallow clone: no auto-run surfaces, no build-time execution points, one unpinned dependency surface and eighteen dependency files inside the seven-day cooldown, with pnpm-lock.yaml present. AGENTS.md and CLAUDE.md are addressed to a reading agent and were recorded as data. Nothing was installed, built or run, no integration installer was executed, and no AWS credential was present, so the sleep pipeline and the evaluation gate are described from source rather than observed.