v0.23.0 · 2026-10-08 · pre-1.0 · open source
Typed project knowledge for AI agents.
Memstead is a local engine for knowledge your agents maintain: typed markdown files in your repository or in git, read and written over MCP and the command line. A schema decides what each kind of note must hold, and the engine refuses a write that does not fit, naming what is missing.
If your agents already keep notes or a wiki in markdown, this is that habit with a schema in front of it, typed links between the notes and a recorded writer for every change.
Build something on it, and tell us what breaks.
Every push runs 3,716 Rust test functions on Linux and macOS, feeds the parsers that read outside files 25,500 generated inputs, and drives the release binaries from outside. A coverage-guided fuzzer runs on demand, and every release archive carries signed build provenance (how it is tested). It is pre-1.0, so commands and file formats can still change between releases: pin a version when you build on it. Pick an idea below, or bring your own.
- Install
curl -sSf https://memstead.com/install.sh | sh - In a repository
memstead quickstart --repo .
How it works
Required sections and fields, allowed values, which links may connect which types. A write that breaks the schema is refused with a typed code and what is missing, and nothing reaches disk.
An edge has a type the schema declares, with allowed endpoint types and, where declared, no cycles. A link to a note that does not exist yet becomes a visible placeholder.
Markdown with frontmatter in a folder of your project, or a git repository the engine writes with one commit per change. Every change records who made it and, when given, why.
memstead-mcp serves 21 tools to Claude Code, Cursor, Gemini CLI, Codex or any MCP client. memstead has 93 commands and subcommands, with the same results as JSON.
Three refusals, from simple to strict
A small plan schema written for this example: steps that depend on other steps, and criteria that decide when a step is done. Everything below is the 0.22.0 binary's own output, recorded on 2026-10-01; long payloads are cut where marked.
- A note lacks what its type requires.
The rule a linter could check too: a criterion must say how to run it.
memstead: ERROR [MISSING_REQUIRED_SECTION]: missing 1 required section(s) for type 'criterion': - 'check' (Check) — write_rules: The command or observation that passes or fails; concrete enough for someone else to run [trimmed]
- A link would make the plan go in circles.
A rule over two notes: dependencies may not loop, and a criterion, not a step, may decide a step.
$ memstead relate release--migrate-the-store DEPENDS_ON release--ship-the-export-command memstead: ERROR [RELATIONSHIP_CYCLE]: creating edge DEPENDS_ON from 'release--migrate-the-store' to 'release--ship-the-export-command' would close a cycle in the DEPENDS_ON subgraph — existing path: release--ship-the-export-command → release--migrate-the-store; remove an edge along this path to break the cycle, then retry [details trimmed] $ memstead relate release--ship-the-export-command VERIFIES release--migrate-the-store memstead: ERROR [INVALID_REL_SHAPE]: relationship 'VERIFIES' from type 'step' to type 'step' violates declared shape — allowed sources: criterion; allowed targets: step. Suggested rel-type: 'DEPENDS_ON'.
- A step is marked done before someone else checked it.
A rule over content, history and identity. The author's own check does not count. The reviewer's does, until the author quietly weakens the criterion; then it is stale, and the step stays open until the reviewer checks again.
# agent-a wrote the step and its criterion, and checked the criterion itself $ memstead update release--ship-the-export-command --metadata status=done memstead: ERROR [CONSTRAINT_UNSATISFIED]: write refused: […] transition_requires_checks: status=done requires a fresh confirming check record on every entity related via [VERIFIES] — unconfirmed: 'release--export-round-trips-byte-identically' (self_checked) # reviewer checks it; then agent-a rewrites the criterion's check to something easier $ memstead update release--ship-the-export-command --metadata status=done memstead: ERROR [CONSTRAINT_UNSATISFIED]: write refused: […] unconfirmed: 'release--export-round-trips-byte-identically' (check_stale) # reviewer checks the new wording $ memstead update release--ship-the-export-command --metadata status=done # Updated `release--ship-the-export-command` - Metadata: status (set)
The rule that does this, from the schema's
steptype:constraints: - kind: transition_requires_checks field: status to_value: done relationships: [VERIFIES] direction: incoming min_related: 1 severity: block
The identities (agent-a, reviewer) are declared by the caller, not authenticated: the rule keeps an honest team honest and leaves a tamper-evident trail; it is not access control. Lab commands also carried --role and --auto-hash, left out above.
Get started
- 1 · Install
curl -sSf https://memstead.com/install.sh | sh - 2 · In a repository
memstead quickstart --repo . - 3 · Thenrestart your agent and ask it to record a decision and its reason
Ideas to build
Ideas for what to build on the engine. Two of them run in this project's own work; the rest wait for someone to try them.
Gaps worth building on top of the engine, and what it leaves out on purpose
- A bridge to the open knowledge format other wiki tools write (OKF): read a folder of OKF notes in as entities through the checked write path, write a mem back out.
- A duplicate and contradiction finder. The engine ships none; search, typed links and the change feed give such a tool its material.
- A graph view of a mem. The engine ships no renderer by design and leaves drawing to exports:
memstead export --format jsonhas the nodes and edges. - A watcher for cited web pages. The engine never touches the network; a script fetches the pages on a schedule and feeds what it saw to
memstead verify-anchors --observations, which is the intended route.
Left out on purpose
- Semantic search. No embeddings and no vector index, by design: the store is the exact layer and the agent reading it is the semantic layer. Search is BM25 over words with typed filters.
- A one-shot import of a notes folder. Every note enters through a checked write. The declared path for an existing source is a binding plus the Claude Code plugin's
/ingestskill, which builds the mem batch by batch. - Calculation. The engine holds the inputs and names what is missing; it never computes a sum or fills a form.
What is in the box
The engine's features in ten families, each with the release it arrived in and what it leaves out.
Who builds it, and where it stands
Built by
Memstead is built by one developer, Björn Bösenberg in Berlin, who directs AI agents and writes no code by hand. The engine also carries Björn's other projects: installable packages of curated, cited reference knowledge; a document analysis that models what a body of records says and what follows from it; the project knowledge behind a client website; and Trustillery, a public project that keeps evidence as checkable collections anyone can challenge with a proposal.
The engine's own work plans are mems. Each step is a note that the schema lets close only after a different identity has recorded a check against its current content, and an unattended agent loop picks its next step from the open gates.
Status
Pre-1.0: commands, schemas and file formats can still change between releases. One developer, no outside review yet. Windows builds are published and untested. Every measurement, with its method and its limits, is in the reference below; so is how the engine is built and tested.
Not claimed, not included
| Retrieval | Not the engine's job. It works on the write side; how good an answer is depends on the agent that reads the mem, and the engine ships no retrieval layer of its own. The read-side measurements are in the evidence. |
|---|---|
| Truth | The schema checks shape, not truth. A note that fits can still be wrong. |
| Automatic memory | None. Nothing enters a mem by itself; every note is a write that passed the schema. |
| Simultaneous writers | On 0.22.0, two writes from separate processes at the same moment can both be accepted, and the later one overwrites the earlier. Found on 2026-10-01; the fix is in the source and not yet released. |
| Database, vectors | None. The store is markdown and git; search is full text and structure, with no embeddings. |
| Registry, hosting | None. No account, no hosted service, no public catalogue of mems. |
Join in
Open an issue with what you ran and what came back. A refusal you did not understand counts. GitHub issues
Post it under Show and tell, with the schema if you wrote one. GitHub Discussions
Pull requests go through the same machine checks as the maintainer's own work. CONTRIBUTING.md
Questions worth asking first
Is this a database?
No. There is no server process holding the data, no query language, no transaction across mems and no replication. The store is markdown files or git objects, and the search index is rebuilt in memory at every load. One database idea does carry over: the schema refuses a write that breaks it, as a table's constraints do. The records, though, are prose documents a person reviews as a diff.
What happens when two agents write at once?
Each update names the hash of the version it read. If another write has completed in between, the update is refused with HASH_MISMATCH and the current hash, and nothing is merged. Two writes racing at the same moment from separate processes are not yet serialized on 0.22.0: both can be accepted, and the later one overwrites the earlier (fixed in the source, not yet released). Before every operation the engine checks whether another process changed the mem, reloads it, and says so in the response (MEM_RELOADED), so the agent knows its cached hashes are stale. Across two machines nothing merges by itself: pull refuses a diverged branch, and a second line of work goes through memstead mem fork and a reviewed memstead proposal merge.
Why not let the agent read the code?
For questions about how the code behaves, it should. A mem is for what the source does not contain: why a decision went the way it did, which rule applies where, what a part is meant to do. The project measured the difference on its own engine (§6).
Who builds this, and how?
One developer, Björn Bösenberg in Berlin, who directs AI agents and writes no code by hand. No person reads each change before it lands; the checks in §5 stand in for that review, and the contribution guide applies the same gates to outside pull requests. The project's own decisions are public as a mem in engineering/. By the project's own account, work began in mid-February 2026; the public history starts on 2026-07-01.
How is this different from an instruction file such as CLAUDE.md or AGENTS.md?
An instruction file is free text the agent loads at the start of a session, and nothing checks what goes into it. A mem is typed: the schema refuses a malformed write, links are checked edges, every change is recorded, and the agent searches it rather than loading all of it. The two work side by side.
Which agents does it work with?
Any agent that speaks MCP. memstead quickstart writes the wiring for the ones you pick among Claude Code, Cursor and Gemini CLI, and prints the one command for Codex; any other client points at the memstead-mcp binary.
Does it work offline?
Yes. The engine, the CLI and the MCP server run on your machine and read files you control. The network is needed to download the binaries, and by the commands that talk to a git remote or a registry you name.
Who owns my data?
You do. Mems are markdown files in your project or in a git repository you hold. There is no account and no hosted service, and the files stay readable if you stop using Memstead.
How big can a mem get?
It is designed for 1,000 to 5,000 entities, with no hard cap. At 5,000 entities a cold command took about a quarter of a second on the measured machine, and the cost grows linearly beyond that (§4).
Is it safe to mount someone else's mem?
A mem is data your agent queries, not instructions it should obey. The engine mounts an installed mem read-only, serves its schema as structure only, withholds the schema's instruction prose, and tags every entity it serves as third-party. It cannot stop an agent from acting on the text, so treat installed content as quoted data.
Can I share a mem?
As a file: memstead export --format mem seals it into one .mem file, and memstead install <file> mounts it read-only. The CLI can also publish to a registry you name with --registry; there is no public registry.
Reference
The specification, the method, every measurement and the history, folded so the page stays short; every word is in the page source. Terms are defined in GLOSSARY.md.
§1ContractWhat a write is guaranteed, and what it is not.
What a write is guaranteed, and what it is not.
A schema works on your agent's notes the way constraints work on a table: a write that breaks it is refused, with the reason. The comparison with a database ends there (Questions).
Guaranteed
| Every write | is checked against the mem's schema before anything reaches disk: the entity type, required sections and fields, field types, allowed values and patterns, relationship types and their endpoint types, acyclic relationships, and every further rule the schema marks as blocking. A write that fails is refused with a typed code and a details payload naming what is missing or allowed. Nothing is written, and the CLI exits 5. |
|---|---|
| Every update | names the content hash of the version it read. If another write has completed since, the write is refused with HASH_MISMATCH and the current hash (exit 4), and nothing is merged. Two writes racing at the same moment are another case (see below). Over MCP the hash is required. On the CLI, --auto-hash and --force relax the check, each only when you pass it. |
| Every link | is a typed edge checked against the schema. A relationship type the schema does not allow, a wrong endpoint type, a cycle in an acyclic relationship, or a link into another mem that the workspace configuration does not grant is refused. A link to an entity that does not exist yet creates a stub, an empty placeholder, and reports it as a warning. A delete is refused while other entities still point at the target. |
| Every change | is recorded. In a mem-repo, a git repository the engine writes with one branch per mem, each change is one git commit carrying the operation, the acting client and, when given, a role, an identity and the writer's note. In a folder mem, a plain folder in your project, it is one line in the mem folder's .memstead/changes.jsonl. |
| Every batch | batch-create, batch-update and batch-relate (mem-repo workspaces) land completely or not at all, and a refused batch names every failing entry. |
| Every read | is deterministic. No model is called on the query path, so the same files give the same answer. |
Not guaranteed
| Truth | The schema checks shape, not truth. A write that conforms can still be wrong. In the blind battery of 2026-09-06, answers drawn from a mem that had passed every write check still carried claims the source code refuted (§6). |
|---|---|
| Retrieval | Not a goal. The schema's job is to refuse a malformed write; answer quality depends on the reading agent, and in a controlled eval typed and equally curated plain notes scored alike (§6). |
| Automatic capture | None. Nothing enters a mem by itself: the engine records nothing from conversations and imports no folder of notes. Every entity is a write that passed the schema. |
| Outside edits | A folder mem sees changes made through the engine. A file edited by another tool goes unnoticed, and reads serve the old content until a reload (OUT_OF_BAND_EDITS_UNDETECTED). A mem-repo compares its branch head before every operation. |
| Simultaneous writers | On 0.22.0 the hash check and the write are not atomic across processes. Two writes racing at the same moment with the same hash can both be accepted, and the later one silently overwrites the earlier. This was found on 2026-10-01 with parallel writers on both workspace shapes; the fix is in the source and not yet released. |
| Two repositories | A change that touches mems in two git repositories is two commits that can fail independently. |
| Who wrote it | --role and --identity are declared by the caller and not verified. History makes them tamper-evident, not true. |
memstead: ERROR [MISSING_REQUIRED_SECTION]: missing 2 required section(s) for type 'principle':
- 'statement' (Statement) — write_rules: One or two sentences. The rule, law, or constraint. Must be testable: can you check whether something violates it?
- 'scope' (Scope) — write_rules: Where this principle applies. Domain, context, conditions. A principle without scope implies universality — state it explicitly if so.--- type: principle created_date: 2026-09-30T11:29:17Z last_modified: 2026-09-30T11:29:17Z authority: proposed universality: contextual --- # Every write carries an idempotency key ## Statement Every mutating endpoint requires an idempotency key, so a retried payment never charges twice. ## Scope The public payments API and every internal service that writes to the ledger.
The first lines of the engine's refusal, verbatim, and the file it writes once the same note carries its two sections.
§2FitWhere a mem pays off: the material the code does not say, kept free of malformed writes.
Where a mem pays off.
A mem is meant for what the code does not say: decisions and their reasons, rules and where they apply, intent, how the parts are meant to fit. The engine keeps that material in a form every writer must follow, with a recorded writer for every change.
On the write side, a pre-registered ten-round comparison with a plain markdown folder found the schema-checked knowledge base free of duplicates and contradictions (0 per 100 items, against 8.13) while it held twice as many items. Its mechanism is not identified: the engine ships no duplicate or contradiction detector, and the control arm with a plain linter was not run.
The read side is not what the engine sets out to improve, and the measurements agree. Typed entities did not help an agent answer better than equally curated notes (0.973 against 0.983, scores from 0 to 1). In the same ten-round comparison the schema-checked side answered its question battery worse (0.286 against 0.575), and a substantial part of that gap is material its writers never recorded. For questions about how code behaves, reading the code did better than reading the mem: on eleven fixed questions about Memstead itself, 9 to 2. Method and limits of every figure are in §6.
The schema checks shape, not truth. A conforming entity can still be wrong, and the engine's answer to that is review rather than validation: every change is a diff with a recorded writer.
§3PathFour steps from install to an answer that names its sources.
Four steps from install to an answer that names its sources.
- Install the binaries.curl -sSf https://memstead.com/install.sh | shInstalls
memsteadandmemstead-mcpinto~/.cargo/bin. The script runs the two installers each release publishes, and each checks the SHA-256 of the archive it downloads. Unless~/.cargo/binis already on yourPATH, the installers add it through your shell's startup files (.profile,.bashrc,.zshrc, the fish configuration);INSTALLER_NO_MODIFY_PATH=1leaves them alone.sh -s -- --version <tag>pins a release. To remove Memstead, delete the two binaries and the install receipts under~/.config/memstead-cliand~/.config/memstead-mcp. - Start a workspace in your repository.memstead quickstart --repo .Creates
.memstead/with the workspace state and a binding, the declaration that names the repository as the mem's source so the engine can report when the two drift apart; a mem in a folder of its own holding one seed entity and pinned to the built-indefaultschema; and the MCP wiring for the agents you pick when it asks:.mcp.jsonfor Claude Code,.cursor/mcp.jsonfor Cursor,.gemini/settings.jsonfor Gemini CLI. For Codex it prints thecodex mcp addcommand instead.--agentskips the question; without a terminal it wires Claude Code. It reads no source file and adopts none of your files as entities. In an empty folder,memstead quickstartdoes the same without the binding. - Restart the agent, then let it write.Restart the agent session afterwards: a session that is already running does not attach an MCP server added while it runs. Then ask the agent to record something the code does not say: a decision and its reason, a rule, how two parts are meant to fit. A write that does not conform comes back refused with what is missing (§1), and the agent can write it again with the fix.
- Ask in a new session.The new session finds the entities over MCP. Ask it to name the entities it used: each id maps to a markdown file you can open, and
memstead entity <id> --provenanceshows which client created it and which last changed it.
§4SpecificationThe engine, as a specification.
The engine, as a specification.
Data model
- A mem is a graph of typed markdown entities about one subject. It pins one schema by exact version,
name@version. - An entity is one markdown file: YAML frontmatter for metadata, level-two headings for sections, a
## Relationshipslist for typed edges. Its id is<mem>--<slug>. Its_hash, the first 16 hex characters of a SHA-256 over its markdown, is the token an update must present. - A schema is a YAML package. For each entity type it declares sections (required or optional, optionally with a markdown shape), metadata fields (string, number, date or boolean, with allowed values and patterns) and constraints. For the mem it declares the relationship vocabulary: endpoint types, acyclic relationships, and which relationships may point into another mem. The built-in schema
default@1.3.0has 10 entity types and 37 relationship types;memstead typelists the types of the schema a mem pins. - A workspace mounts mems. One engine process serves one workspace, and a link from one mem into another needs a grant in the workspace policy.
- concept 10 · 13.5 links
- guide 10 · 7.6 links
- claim 7 · 4.7 links
- limit 6 · 3.5 links
- principle 6 · 11.5 links
- behavior 5 · 6.2 links
- stance 3 · 7 links
- meta 2 · 8.5 links
- story 1 · 7 links
fig. 1 · The plan of a real mem, read when this page was built: Memstead's own introduction, 50 entities of 9 types. That mem itself is not published. One room per entity type: area by entity count, shade by the average number of typed links per entity of that type.
Two storage shapes
| Folder mem | Mem-repo | |
|---|---|---|
| Created by | memstead quickstart, memstead init | memstead mem-repo init, then memstead mem init per mem |
| Mems per workspace | one | many |
| Entity files | .md files in the mem's folder | files on the branch <mem> of mem-repo/.git |
| Change record | one JSON line per change in the mem folder's .memstead/changes.jsonl; your own git versions the files | one git commit per change, with trailers |
Atomic batch-* commands, recover | refused (UNSUPPORTED_WORKSPACE_SHAPE) | available |
| A change by another process is noticed by | the change log's last entry (engine writes only) | the branch head |
| Moving between machines | your own git | memstead push, fetch, and pull (fast-forward only) |
Concurrency
Writes are optimistic. An update carries the hash it read; if another write has completed since, HASH_MISMATCH returns the current hash and nothing is written. On 0.22.0 the check and the write are not atomic across processes, so two writers racing with the same hash can both succeed (see Not guaranteed, §1). Before every operation the engine compares each mem's head with what it loaded; when another process has written, it reloads that mem and attaches a MEM_RELOADED warning and a mem_changed notice listing what changed.
The limits: a folder mem notices only engine-made changes; mems in two git repositories change in two independent commits; pull refuses a branch that has diverged. A second line of work goes through memstead mem fork and a reviewed memstead proposal merge. An entity edited on both sides is shown in all three versions and never merged automatically.
Reads
No model is called on the read path. Search ranks with BM25 plus title and per-section weights, itemised on every hit, and filters by type, metadata and graph distance. There are no embeddings and no vector index. The search index is built in memory at load and never written to disk. memstead overview groups entities into clusters with Louvain community detection, within a token budget. memstead health reports orphans, stubs, stale entities, dangling links and schema constraints; --strict exits 1 on any unacknowledged finding.
Interfaces
- CLI,
memstead: 93 commands and subcommands.--jsonreturns the same shape as the MCP server. Exit codes: 0 success, 1 other failure, 2 usage error, 3 not found, 4 hash conflict, 5 refusal, 6 findings in a gated verify. - MCP server,
memstead-mcp: 21 tools over JSON-RPC on stdio, one process per client. It finds its workspace by walking up from the directory it starts in. - Error codes: 263, each listed with the surfaces that emit it and where in the source.
- Claude Code plugin (MIT): skills that set up a workspace, grow a mem from a source and keep it current, and hooks that stop direct edits to a folder mem's entity files.
- Rust crates on crates.io, published as dependencies of the two binaries, with no API promise.
- WebAssembly build: reads a sealed mem in the browser, without search. It is built from the source tree; the npm package stopped at 0.18.1.
Moving a mem
memstead export --format mem seals a mem into one .mem file, a zip archive with the entity files, the config, the schema and per-entity provenance. Two exports of an unchanged mem are byte-identical. memstead install <file> mounts one read-only. A mem also exports as JSON, as one self-contained HTML page with no scripts, or as one markdown document (--format llms-txt).
Keeping a mem true to a source
A binding records that a source, such as a codebase or a folder of documents, belongs in a mem. Entities carry anchors to the files they describe. memstead projection verify reports coverage, anchors whose files changed, and freshness as three separate numbers, never one score; --fail-on-findings exits 6 so CI can tell drift from a failed run. The engine never writes entities from a source. An agent does, through the same checked write path.
Measured limits
A mem is designed for 1,000 to 5,000 entities; the engine sets no hard cap. At 5,000 entities a cold CLI command, which pays one full load of the workspace, took about a quarter of a second. Measured on 2026-09-10 and 2026-09-11 with engine 0.20.0 on an Apple M5 Max, release binaries, a synthetic mem-repo of spec entities with about three edges each:
| Entities | 500 | 2,500 | 5,000 | 7,500 |
|---|---|---|---|---|
| Cold CLI boot | 59 ms | 145 ms | 255 ms | 371 ms |
| Warm MCP search | 0.43 ms | 0.49 ms | 0.62 ms | 0.69 ms |
| Warm entity read with relations | 0.82 ms | 1.08 ms | 1.39 ms | 1.71 ms |
| Warm overview | 1.6 ms | 6.3 ms | 12.6 ms | 19.2 ms |
The cost grows linearly with the entity count. The shape carries to other machines; the milliseconds are this one's. One batch-create of 7,500 entities took about 4.7 s. The harness is cargo run -p xtask -- sizing-curve; the first measurement, on 2026-08-06, was not linear (§6).
§5MethodAI agents write the code. Machine checks stand in for review.
AI agents write the code. Machine checks stand in for review.
Memstead is written by AI agents working for one developer, Björn Bösenberg in Berlin, who writes no code by hand. No person reads each change before it lands. The contribution guide states the terms for everyone: an agent judges a change against the machine checks, and the question asked of it is which check covered it, not whether a human understood it. Of the 926 commits in the public history up to v0.22.0 (30 September 2026), 867 carry a Co-Authored-By: Claude trailer.
The engine is written in Rust so that the compiler and the borrow checker carry part of the review a single developer gives up. The chain below carries the rest. Counts are read from the engine source this page was built from.
- One definition of green.
run-tests.shdecides whether a tree passes, and CI runs that script, not a copy of it. It runs every leg and reports every failure rather than stopping at the first. The legs:cargo fmt --check;cargo clippywith every warning as an error; guards that keep private paths and internal references out of the public tree; 3,716 Rust test functions, none marked ignored and none retried on failure; the doc tests; the Claude Code plugin's Node tests; a check that everymemsteadcommand and flag in the shell examples of the public documentation exists in the binary just built; the documented CI example forprojection verify, run in both outcomes; and a self-test of the blind-battery instrument. - CI on every push. Every push to
mainand every pull request runsrun-tests.shon x86-64 Linux and on macOS. Separate jobs run the WebAssembly build's tests in Node, audit the dependencies against the RustSec advisory database (with one documented exception), and check that the workspace builds on its declared minimum Rust version, 1.91. - The binaries, driven from outside. A separate workflow builds the release binaries on every push and drives them over JSON-RPC: a mem-repo round trip (create, update, relate, read, search, change feed), the schema refusals
UNKNOWN_SECTION,INVALID_ENUM_VALUEandMISSING_REQUIRED_SECTION, and the conflict path withHASH_MISMATCH. - Untrusted input. Four parsers take input from outside the engine: entity markdown and frontmatter,
.memarchives, schema content expressions and search queries. Every test run feeds the markdown, archive and expression parsers 25,500 inputs generated from fixed seeds (counted in the source on 2026-09-30) and asserts that nothing panics and that parsing and writing back reach a fixed point. Search must return the same hits whichever Unicode normalization form a query arrives in. A coverage-guided libFuzzer tier with 4 targets runs when someone dispatches it. An input it finds is fixed at the parser, never by accepting more, and joins the corpus: 16 inputs that once crashed the frontmatter parser are replayed in every test run. The changelog records 15 parser defects found by these two tiers: 3 in 0.10.0, 11 in 0.12.0, and 1 in 0.19.0, found by two inputs. - Reference generated from the code. The CLI reference (93 commands and subcommands), the MCP tool reference (21 tools) and the error-code index (263 codes) are generated from the source at every documentation build and never committed. A page without its generation stamp does not deploy.
- A release is a tag.
cargo run -p xtask -- release <version>refuses a dirty tree, an empty[Unreleased]section, manifests whose versions disagree, or a redrun-tests.sh, and changes nothing in git itself. Pushing the tag builds 7 targets, attaches a SHA-256 checksum and a signed build-provenance attestation to every archive, publishes to GitHub Releases, Homebrew and crates.io, and then asks each channel which version it serves; the run fails if one lags. For 0.21.0 the Homebrew job itself failed, so the run was red, the check never ran, and the formulas were pushed by hand; the cause is fixed in 0.22.0.
Not covered
- Windows. The Windows build is published and never tested. Of the 7 release targets, CI tests x86-64 Linux and one macOS runner; the others are built without a test run.
- Coverage. No code-coverage figure has ever been measured. The test count is a count.
- Deeper analysis. No mutation testing, no Miri, no sanitizers outside the fuzz tier.
- Fuzzing on a schedule. The long tier runs when someone dispatches it and is never a required check. Between 2026-08-24 and 2026-09-09 it ran 13 times in CI and found failing inputs in 9 of those runs; 0.20.0, 0.21.0 and 0.22.0 shipped without a fuzz run. A run over all four targets, dispatched on 2026-09-30 after 0.22.0 shipped, found no failing input in 15 minutes per target.
- Required checks. The rule in the contribution guide is that nothing lands past a red
run-tests.sh: the author runs it before pushing, and no machine enforces that.mainhas no branch protection, so CI runs after a commit lands, and a red CI run is repaired by a later commit. Waiting for green CI before tagging is a step in the release runbook, not a machine gate. - Code signing. The binaries carry no platform signature: the macOS binary has the linker's ad-hoc signature, and Windows signing is not configured. The provenance attestation is the check that exists.
- Performance regressions. No CI job measures speed. The sizing curve is rerun by hand; its current figures are from engine 0.20.0.
- Supply chain. No SBOM and no dependency licence policy.
- Outside review. No independent audit or review of the engine has been published.
Check a release yourself
Every release archive has a .sha256 file beside it and a signed SLSA build-provenance attestation. With the GitHub CLI:
gh release download v0.23.0 -R memstead/memstead -p 'memstead-cli-<target>.tar.xz*' shasum -a 256 -c memstead-cli-<target>.tar.xz.sha256 gh attestation verify memstead-cli-<target>.tar.xz -R memstead/memstead
<target> is a Rust target name such as aarch64-apple-darwin or x86_64-unknown-linux-gnu. The attestation ties the archive's digest to the release.yml run at that tag and to the commit it built. Checked for v0.22.0's x86_64-unknown-linux-gnu archive on 2026-09-30: workflow release.yml@refs/tags/v0.22.0, commit ea69139e.
§6EvidenceWhat was measured, including what went against the engine.
What was measured, including what went against the engine.
Each run below is published with its method and data in docs/proof/ or docs/sizing-curve.md. Each is one run, not a benchmark, and the project ran all of them itself.
Divergence campaign July 2026 · pre-registered · record
- Question
- Does refusing non-conforming writes pay off as a knowledge base grows over ten rounds of an evolving source repository?
- Method
- Two arms were filled by writer sessions that started fresh each round, from byte-identical digests of a public repository's history: a markdown folder that accepted every write, and a mem that refused non-conforming ones. A twelve-question battery, three trials per arm, a blinded judge, and three blinded integrity audits per arm. The decision bands were fixed before the first run and applied mechanically.
- Result
- Integrity, positive: after round 10 the mem held 0 duplicates or contradictions per 100 items across three audits, the folder 8.13, and the mem held 81 items to the folder's 41. Accuracy, negative: the mem scored 0.286, the folder 0.575, and the gap widened at every checkpoint.
- Added later
- Not pre-registered (2026-08-25). Five of the twelve questions ask about bug records. The mem's writers recorded 26 distinct bug identifiers to the folder's 244 and used the schema's
incidenttype zero times. A substantial part of the accuracy gap is material that was never written, which counts against the engine, not for it. - Limits
- Neither result can be assigned to typing or to the engine's read tools: each arm was read with its own tools, and the cell that would separate them was not run. The integrity result has no identified mechanism, since the engine ships no duplicate or contradiction detector, and the control arm with a plain linter was not run. It is one campaign, on one source repository, with one model. At the round-5 audit the order was reversed (the mem 1.52, the folder 0.00). No session recorded proof that it reached the engine.
Substrate eval 2026-07-08 · record
- Question
- Given the same source material, do schema-typed entities let an agent answer better than equally curated plain notes?
- Method
- One model captured both forms from the same corpus, a text on Memstead's own design. Each arm answered with its whole substrate in context under an 8,000-token budget, so retrieval played no part. A blind judge scored three trials. A screen removed any task the model could answer without the corpus (one of six).
- Result
- Typed 0.973, plain notes 0.983: a difference of -0.010 ± 0.006, no measurable difference. Both covered 22 of 22 facts.
- Limits
- Any claim that typing makes answers better is unsupported. It is one run with one model on a corpus that fits in context. It says nothing about corpora larger than the context, and nothing about refusing bad writes, scale, or who may write what.
Blind battery 2026-09-04 and 2026-09-06 · record
- Question
- On eleven fixed questions about how Memstead works, does an agent that may read only the engine's own mem answer as well as one that may read only the Rust source?
- Method
- Two fresh readers per question, one large model and one small; citations stripped; a seeded random A/B order. A third fresh agent grades each pair against the source and by running the release binary. The instrument is
ci/blind_battery.py, whose mechanics self-test in everyrun-tests.sh. - Result
- 2026-09-04, the mem at its version 0.2.2, before any sync: source 7, mem 4. 2026-09-06, after a sync that walked every claim in the mem: source 9, mem 2, with margin sums of 15 to 3. The mem won on what an installed mem can and cannot do, and on one question about concurrent writes answered by the small model.
- Limits
- No general verdict on mems against code follows. It is one mem, about its own source, graded by agents. The mem under test is not published; the second run's prompts, answers, pairs and verdicts are. The first run's record was not kept beyond its verdicts.
Reconstruction 2026-07-03 · record
- Question
- How many tokens does a fresh agent need to rebuild a working picture of a subject from its mem, compared with the conversations that wrote the mem?
- Method
- A fresh agent read a 287-entity mem through the CLI only and wrote a brief that cites an entity for every claim. Its transcript and the 22 authoring conversations were counted with the same script and tokenizer.
- Result
- 65,161 against 852,773 content tokens, about 1/13.
- Limits
- No advantage of typing follows. It compares a curated mem with raw research conversations, so it measures curation; the substrate eval, which isolates typing, found none. One run, no variance, and understanding is shown by the brief, not scored. The raw authoring transcripts are withheld; their hashes and token counts are published.
Read-surface headroom pre-registered 2026-08-25 · never run · record
- Question
- Was the divergence campaign's accuracy loss caused by the typed files or by the engine's read tools?
- Method
- Five read surfaces over the same frozen corpora, with the campaign's battery, judge and rubric.
- Result
- None. Three smoke passes found failures in the apparatus. In one, the engine arm never reached the engine, answered with file tools, and still returned a plausible score; a guard now aborts such a run. The full run has not started, and whether it will is open.
- Limits
- The question stays open, and the divergence accuracy result cannot be assigned to typing or to the tools.
Sizing curve 2026-08-06, re-measured 2026-09-10 and 2026-09-11 · record
- Question
- What do everyday operations cost as a workspace grows from 500 to 7,500 entities?
- Method
cargo run -p xtask -- sizing-curvebuilds synthetic workspaces through the CLI and times cold commands (median of 3) and warm MCP calls (median of 20) with release binaries on one machine, an Apple M5 Max.- Result
- Linear. A cold command costs one workspace load, 371 ms at 7,500 entities. Warm reads stay under 2 ms, except the overview at 19 ms for 7,500 entities.
- First measurement
- On 2026-08-06 the same harness found the cold cost growing faster than linear: 5,647 ms at 7,500 entities. That curve stood for five weeks. On 2026-09-10 a profile put 96% of the boot time in reading each entity through a freshly opened repository tree; the fix, one tree walk per mem, landed the same day and shipped in 0.20.0. The old curve stays in the document as a dated record.
- Limits
- Absolute times hold for one machine and synthetic content only. The figures are from engine 0.20.0, and no CI job re-measures them.
§7HistoryHow the engine got here, dated.
How the engine got here, dated.
Before the public repository
- Feb 2026Development begins, part-time.
- 2026-03-18First commit of the private repository. It already describes a JavaScript engine with an MCP server of 17 tools, a schema package, a Claude Code plugin and a desktop app.
- 2026-04-12A rewrite in Rust begins; it has 160 test functions on its first day.
- 2026-04-13The Rust engine is compared tool by tool with the JavaScript engine on a small real mem: 7 of 10 tools give identical output, and 3 differences are documented. The JavaScript engine is retired the same day.
- 2026-04-18The MCP tool list is cut from 21 to 15 in two steps.
- 2026-04-301,298 test functions.
- May 2026The storage layer is rebuilt. No commit from 2026-05-23 to 2026-06-07; a planned launch date, 2026-05-31, passed in that gap.
- 2026-06-12The name Memstead replaces the working names.
The public line
- 2026-07-01The public repository starts with one commit of 562 files and a fresh history. That night the unit noun becomes "mem", with no alias kept for the old one.
- 2026-07-020.1.0, with binaries for macOS, Linux and Windows and 2,347 test functions. It was tagged 71 minutes before the documented quickstart landed, and 0.2.0 shipped it on 2026-07-04.
- 2026-07-13The engineering decision record becomes public as a mem, with 41 entries.
- 2026-07-200.4.0 rewrites VISION.md to drop a read-side claim that the project's own substrate eval had falsified.
- 0.5.0Never cut. The number was skipped so that the engine and the Claude Code plugin, already at 0.5.0, share one version.
- 2026-08-190.9.0 is cut and never published; its content ships in 0.10.0 on 2026-08-23.
- 2026-08-230.10.0 adds the seeded adversarial tests and the libFuzzer tier. The two tiers found 3 parser defects for that release and 11 more by 0.12.0.
- 2026-09-090.19.0 consolidates: one build instead of two.
- 2026-09-100.20.0 makes loading a mem-repo linear in its size.
- 2026-09-300.22.0: the CLI stops naming a registry, and the installer moves to memstead.com.
23 published releases from 2026-07-02 to 2026-10-08, and 3,716 test functions in the source this page was built from. Read that as a cadence, not an age: expect changes between releases.
What was removed, and when
- 2026-04-13The JavaScript engine (private record).
- 2026-04-186 of 21 MCP tools (private record).
- 2026-07-11
memstead stats, replaced bymemstead status(0.3.0). The same day, the plugin's hooks that committed into the user's repository: "The user's project repo is exclusively the user's business." - 2026-07-20A claim in VISION.md that an agent reading typed specs "understands a domain immediately" (0.4.0).
- 2026-08-18The native macOS app. Its binding crate left the workspace in 0.10.0.
- 2026-08-23The six
memstead_workspace_*MCP tools (0.10.0): workspace policy belongs to the operator, not to the agent it constrains. - 2026-09-05The npm package of the WebAssembly build.
- 2026-09-09The second build flavour with its 212
cfgsites, and the converters for two retired binding formats (0.19.0). - 2026-09-30The CLI's built-in registry host (0.22.0). The registry service was stopped the same day (private record).
Every release
| 0.23.0 | 2026-10-08 | Closes a command injection through |
| 0.22.0 | 2026-09-30 | The engine names no registry any moreinstall, publish and login take one only when you point the CLI at it, login learns its OAuth app from that registry, the installer moved to memstead.com, and the engine's commits carry memstead.com author and committer addresses. |
| 0.21.0 | 2026-09-27 | Statically linked Linux binaries for both architectures, so a container without glibc runs the engine from a pinned releasea mem's check records travel with its pushed branch on a ledger ref, so an export made on another machine seals them; and the owner's side of a hosted proposal path,mem fork --remote --adopt, mounts a contributor's branch from a plain git remote. Beside it: a proposal merge that rejects everything still lands its record, and a plan step with a gate closes only on a check recorded in a checker's role. |
| 0.20.0 | 2026-09-10 | A mem-repo boot is linear in the mem sizethe git-branch backend reads a whole mem in one tree walk, so a 7,500-entity workspace boots in under 0.4 s, where a profile of the old boot on the sizing corpus measured 6.2 s, and the sizing curve is re-measured on the fixed engine. Beside it: a sealed.mem carries a provenance record for every entity, prune proposes and never merges (the guarantee vocabulary is gone), and projection advance refuses to drop an authored exclusion or to advance over anchors that still drift. |
| 0.19.0 | 2026-09-09 | One build instead of twothe engine ships as a single build with the git-branch backend always on, the retired split's names and the migration paths for retired binding formats are gone, and every help text, tool description and public page describes the tree as it is. Underneath:search --json carries the same hit shape as the MCP envelope, a server started before its first mem now mounts it without a restart, the binding-intent check reads only relationship-shaped tokens, the boot no longer panics without a data directory, and the frontmatter splitter reads every section the way the generator writes it, closing the gap two fuzz inputs found. |
| 0.18.1 | 2026-09-03 | Fixes for the two problems the first outside tester of 0.18.0 hit on the documented patha mem installed from the registry into a quickstart workspace now resolves its sealed schema from the archive itself instead of refusing, and a freshcargo add of the published crates compiles again, with the broken tinyvec release held off by a resolver constraint. |
| 0.18.0 | 2026-09-03 | A released binary can be told apart from a local build of the same versiona clean tagged build reports bare semver while every other build keeps its+g<sha>, so the plugin's capability gate stops reading a pre-release dev build as capable and its record follows the binary across an upgrade. Around that repair land the bundle of engine fixes it shipped with: a short entity id names its missing mem prefix instead of failing on an empty one, create --from --mem stops dropping the flag, the health coverage line reports the verdict it actually polices, a completed schema migration re-stamps the version marker, the lite schema skeleton renders every legality flag, and a bare projection verify writes nothing while coverage marks the artifacts excluded on purpose. |
| 0.17.0 | 2026-09-02 | The maintenance report names what is overdue, and two built-in schemas get new versionsproject@0.5.0 puts milestones on the due axis and engineering@0.4.0 sanctions the dated in-place amendment on decisions. Around them: memstead push --all publishes the whole mem-repo, mem membership follows reload-before-operation, an unreadable anchors sidecar is one typed condition on every surface, export redacts private patterns in the archive's provenance, health gains the vital_signs axis and a --mem filter, and the check that nobody approves their own work compares who executed a step, not who wrote its criterion. |
| 0.16.0 | 2026-09-02 | An entity can change its type in placememstead retype keeps the id, the file and every incoming edge, validates the sections against the target type with a report-all refusal, and re-checks every edge against the target's relationship pins. Beside it: export --root --via renders one chain of the graph, check records carry a structured finding and accept an open x- kind, url anchors become observable through observer-supplied rows, schema migrate rewrites an authoring package's retired keys into the current schema language, and /sync --sweep walks a list of mems in order. |
| 0.15.0 | 2026-09-01 | A second maintenance skill repairs how a mem is cutthe new/remodel skill repairs what a mem IS (one home entity per obligation, substance in its declared sections) where /sync repairs what entities say, and the sync skill adopts the benchmark campaign's disciplines. The measured frictions land too: check --from batches verdicts in one boot, export carries anchors, a section takes several patches per call, and sections_unset closes a section, with unwritten optional sections now absent instead of scaffold headings. |
| 0.14.0 | 2026-08-29 | The entity type has one field name everywhere, and a plan can refuse a status changethe entity envelope servesentity_type everywhere (the retired spelling is gone, not aliased), the mem config's format is modeled and gated, the planning schema gains gated status transitions with a gates brief that renders every transition's standing, verify learns --fail-on-inconclusive, and an undeclared cross-schema wiki-link now warns instead of silently staying prose. |
| 0.13.0 | 2026-08-28 | Every report says what it examinedtwo wire spellings converge (one edge shape on every input;commit_sha becomes write_id and stops posing as a change cursor), semantic conformance becomes a recordable schema-bound check, quarantined mems can no longer disappear from status or type, health's human rendering serves the findings it gathers, and a fleet of new gates holds descriptions to the code that backs them. |
| 0.12.0 | 2026-08-24 | Eleven parser defects found by the long fuzz tier are fixedeleven cases where parsing a mem's markdown and generating it back changed the document, each traced to the CommonMark referee and the engine's own reconstruction reading a line differently, each now pinned by its triggering input in the shared corpus. Alongside them, the two problems the first sealed test of the install path found:export --format mem and publishing work on every workspace quickstart creates, and every page that teaches an install says to restart the agent session. |
| 0.11.0 | 2026-08-24 | Anchors drift on meaning, not on formattingthe first three preparations decide what counts as a change in a source, entity-load-bearing (a notes-only edit no longer drifts a dependent's anchor), dated-entries (a chronological file delivers as stamp-ordered units), and code-map (a code file's anchor hashes its interface digest, measured at 4.6% of the raw source on a 214-file corpus). |
| 0.10.0 | 2026-08-23 | Schemas can state more kinds of rules, and a fuzzing tier guards the parsersconditional edge requirements, reachability obligations, acyclic relation sets, aggregate signals, and grounded labelling over a declared attack set. A new adversarial fuzzing tier guards the three trust-boundary parsers and already found and fixed three parser defects. |
| 0.9.0 | 2026-08-19 | Cut, never published. Its content, including the removal of the native macOS app's binding crate, shipped in 0.10.0. |
| 0.8.1 | 2026-08-18 | Every edge in the entity envelope states its direction and a read can include incoming edgesthe CLI's--json reads carry the trust origin; folder mems can be re-attached. |
| 0.8.0 | 2026-08-15 | The HTML export renders declared section headings, docs pages name the revision they were built from, and each bootstrap receipt states its workspace shape and one limit. |
| 0.7.0 | 2026-08-14 | Three of four published mems could not be installed until help texts now name the refusals a newcomer met unannounced. |
| 0.6.0 | 2026-08-10 | Titles become display text, fields and sections are optional unless marked required, and schema validation reports every violation at oncememstead due and export --format html arrive. |
| 0.4.0 | 2026-07-20 | VISION drops a read-side claim the project's own substrate eval had falsifiedbreaking: each pipeline becomes one versioned binding record; folder mems join cross-process change detection. |
| 0.3.0 | 2026-07-11 | The projection-pipeline releasea versioned binding replaces the ingest config store, anchors record which source artifact an entity describes, andmemstead status replaces memstead stats. Breaking. |
| 0.2.0 | 2026-07-04 | Ships memstead quickstart and memstead schema new, which 0.1.0 missed by 71 minutes, plus the git transport commands fetch, pull, push, branch-reset and remote-add. |
| 0.1.0 | 2026-07-02 | First tagged releasethe schema layer, the in-memory store, the folder and git-branch backends, thememstead CLI and the memstead-mcp server, with binaries for macOS, Linux and Windows. |
The decision record
The reasons behind the engine's design are public as a mem: engineering/ in the repository holds 187 decisions, principles and memos, with superseded ones kept beside their successors. Each entry is a markdown file, and the folder's README says how to mount it and query it with the engine.
§8FilesYour mems stay markdown in your git, readable without Memstead.
Your mems stay markdown in your git, readable without Memstead.
A folder mem is a folder of markdown files in your project. Memstead never commits to your repository; you version the files with your own git, on your terms.
A mem-repo is a git repository the engine writes: one branch per mem, one commit per change. Plain git reads it: git -C mem-repo show <mem>:<file>.md. memstead push sends a mem's branch to a git remote you configure. Inside another repository, mem-repo init adds mem-repo/ to that repository's .gitignore.
Either way the files read without Memstead: YAML frontmatter, level-two sections, a relationships list. Schemas are YAML files, and a sealed .mem file is a zip archive. Leaving Memstead means keeping the files.
What can change before 1.0. APIs, schemas, file formats, CLI flags and MCP wire shapes may change without notice, and the library crates on crates.io carry no API promise. A retired format can lose its converter: 0.19.0 removed the one for two retired binding formats, whose files now refuse to load and name the command that re-creates them. The changelog labels breaking changes as such; 0.22.0 has two. To hold a version still, pin it at install: curl -sSf https://memstead.com/install.sh | sh -s -- --version <tag>.
Security. Report a vulnerability to security@memstead.com, never as a public issue. The policy promises an acknowledgement within 7 days and triage within 14, with coordinated disclosure usually 30 to 90 days after triage; fixes go to the latest tagged release. No advisory had been published by 2026-09-30, so these times have no track record yet. A mem you install from someone else is untrusted input: the engine withholds its schema's instruction prose and tags every entity it serves as third-party, but it cannot stop an agent from acting on the text. The full policy is SECURITY.md.
Licence. The engine, the CLI, the MCP server and the .mem format are MIT or Apache-2.0, at your option. The Claude Code plugin is MIT.