Skip to content

memhtml

Memory for Agents, in HTML

MEANING · MEMORY · MARKUP

memhtml stores an agent’s long-term memory as a git repository of small HTML files, one fact per file. A SQLite index over that repository makes the files searchable, and an agent reads and writes the store either through the memhtml command line or through an MCP server that serves the same store over stdio. What the agent remembers is a directory on disk that you can open in a browser, diff, and edit with ordinary tools.

An agent that writes down what it learns needs three answers later: what do I know, where did it come from, and what has changed since. Keeping those facts as rows in a vector database answers the first one well and drops the other two, because a row carries no history and a re-embedding pass rewrites the store with nothing for a reviewer to read.

memhtml answers all three out of git. Each fact is a file, so you read it without a query. Each change to the corpus is exactly one commit, so git log in the store is the history of what the agent learned, and a night of automated curation is a diff a person can review. Retiring a memory moves the file into archive/<YYYY>/ with the original path mirrored beneath it, so git log --follow reads through a memory’s whole life and no correction destroys the fact it corrected. The index holds no facts of its own: delete .memhtml/index.db and memhtml index rebuild reconstructs it from the tree.

Each memory is standard HTML5 that a browser renders with no build step. The <meta> elements in the head carry the typed metadata, one <mark> span in the body carries the claim the file exists to state, and <link> elements carry the relationships an author asserted between memories. One fact per file is what keeps a correction reviewable: memhtml correct writes the replacement and archives the original in one commit, so an interrupted run leaves no pair of live contradicting memories behind.

Two SQLite databases sit under .memhtml/, opened through Node’s built-in node:sqlite driver, and they differ in what a rebuild can recover. index.db holds the projected rows, the full-text index, the embedding vectors, and the edges read from each file’s <link> elements, all of which the indexer regenerates from the tree. state.db holds facts the tree cannot reproduce: access counts, reinforcement counts, and the outcome EWMA. memhtml state export copies those into a committed file, .memhtml/state/access.jsonl.

Retrieval runs four independent ranking arms over one query and then combines their orderings. Full-text search ranks on title, claim, and body text. Vector similarity ranks on the embeddings. Recency ranks an episodic memory by when the fact happened rather than by when someone wrote it down. Salience ranks on how often someone has chosen to open the memory. One SQL statement fuses the four orderings by reciprocal rank fusion, where each arm contributes 1/(rank + 60) under its own weight, and a pass in TypeScript then demotes a hit that nearly duplicates one ranked above it, when both carry embeddings. That fusion is what this site means by four-arm RRF. When an arm’s precondition is missing, memhtml drops that arm, so the search gets narrower and still answers. degraded: true on the response means the vector arm did not fire. A store with no embedder bound is the common case. The MCP search tools ask a running head server first, and a head server ranks with full-text search and vectors alone.

Four doors write into the same tree: memhtml write and memhtml apply on the command line, the write tools on the MCP server, your own file tools against the checkout, and a session. A session collects many writes, judges each against the store’s write bar, and lands them all as one commit, and it’s the door an agent run uses when its runtime exports MEMHTML_SESSION. The store owns git staging, so one call becomes exactly one commit and a caller cannot bundle two unrelated writes into it.

Curation runs as a session on a curate/<date> branch, never in the write path. memhtml curate run lets a model work on that branch within a budget, and memhtml curate merge lands the branch on main only after a gate checks that retrieval didn’t get worse. When a new claim shares a frame key with a live claim it does not supersede, and a lexical check finds they differ, a session commit keeps both live with a contradicts edge; choosing a winner is a one-way door that stays with a writer or a human.

Every command writes one JSON envelope to stdout and nothing else, sends its logs to stderr, and exits 0 for success, 2 for a usage error, and 1 for a runtime failure. memhtml manifest prints every command, flag, response type, error code, and environment variable the binary accepts, and it answers on a machine with no store, no database, and no credentials.

Install the binary and work through the four tutorials in order. One published package, memhtml, carries the whole system, and a clone-and-build path exists for working on it. If you are an AI agent rather than a person, read For agents first: it states the assumptions to drop and the shortest path to a correct first call.

The Reference tier is generated from the same registries the binary parses its own arguments with, so a flag, error code, or response type on those pages is the one the binary ships. Where this site and the binary disagree, the binary is right and a test is missing.

Architectural claims cite the code by repo-relative path and the symbol it declares, as in packages/index/src/retrieval-sql.ts::buildRrfSql. A symbol survives the edits that move lines, and the repository’s check fails when a cited file stops declaring one, so every citation names code the source this site was built from still declares. Every measurement on the site carries the date someone took it.

Section numbers live in the heading text, as in ## 3.2. Edge encoding, so the page, the contents, the search index, and the Markdown this site serves to an agent all agree on which section §3.2 is. Anchors stay unnumbered, so inserting a section does not break an inbound link.

Every page also serves its own Markdown source at the same path with .md appended, and links that twin from its <head>. llms.txt indexes the whole corpus for an agent that would rather read the document than the page.