Skip to main content

memfmt

An agent's memory as Markdown files you own.

Every agent that remembers anything invents its own way to store it: one MEMORY.md that grows until it stops fitting in context, a bespoke JSON blob, a folder of notes with no rules. Nobody can read anybody else's, nothing diffs cleanly, and moving between tools means writing a converter.

memfmt is a small spec and a dependency-free Python library for the boring version of that: memory as plain Markdown, one thing per file, relations as [[wikilinks]]. Git gives you diffs, review and rollback. Obsidian draws the graph with no configuration, because the links are the graph.

memory/
  MEMORY.md                            index — what is in here
  entities/Ali.md                      what is true
  episodes/2026-07-30-deploy-failed.md what happened
  procedures/deploy to Railway.md      how to do it, and whether it works
  profile.md

No account, no server, no network. This library reads and writes files.


Install

pip install memfmt

Use it

memfmt stat ./memory        # what is in here
memfmt validate ./memory    # would any file lose data if a tool rewrote it?
memfmt context ./memory "why did the deploy fail"   # the relevant bits, to pipe into a model
from memfmt import load, serialise, write_dir, canonical

memory = load("./memory")

for p in memory.procedures:
    print(p.name, p.version, p.reliability)   # deploy to Railway 3 92% reliable

write_dir(serialise(memory), "./memory")

memfmt context is the one to try first. It picks the files relevant to a question and prints them, so you can pipe your agent's own memory into a prompt without a database:

memfmt context ./memory "deploy railway pool" | pbcopy

The format

Three kinds of memory, because agents forget in three different ways.

Entities — what is true

memory/entities/<name>.md

---
memfmt_type: entity
entity_type: person
id: e1
---

# Ali

## Facts

- prefers Rust for memory safety
- based in Tokyo

## Relations

- works at → [[Mengram]] — since 2024
- mentored by ← [[Kenji]]

## Knowledge

**[snippet] deploy command** — how the service ships

```
railway up --detach
```

is outgoing, incoming. Text after is a note on the relation. When a name cannot be a filename, the link carries an alias and the real name survives: [[cloud-api.py|cloud/api.py]].

Episodes — what happened

memory/episodes/<date>-<summary>.md

---
memfmt_type: episode
id: ep1
happened: 2026-07-30
outcome: rolled back, raised pool_max
valence: negative
importance: 4
participants:
  - Ali
  - Railway
---

# deploy failed on a cold pool

Two workers booted at once and the session pooler refused the fourth client.

**Outcome** — rolled back, raised pool_max

An event with no outcome teaches nothing, so outcome is the field that earns an episode its place.

Procedures — how to do something, and whether it works

memory/procedures/<name>.md

---
memfmt_type: procedure
id: p1
version: 3
success_count: 11
fail_count: 1
---

# deploy to Railway (v3 · 92% reliable)

**When** — a change lands on main

**Preconditions**

- tests pass
- pool_max is set

## Steps

1. push to main — the webhook does the rest
2. watch the boot log
3. verify /health — expect 200 within 60s

## Evolution

- v1 → v2 (2026-06-02): added the health check
- v2 → v3: wait for the pool before probing

This is the file the format exists for. A workflow on its own is a guess somebody wrote down. With 11 ✓ / 1 ✗ and the revisions that produced it, it is evidence — and an agent can tell the difference between a step that has worked eleven times and one nobody has ever run.


Rules

A short list, because a format nobody can hold in their head gets implemented wrong.

  1. Frontmatter is the source of truth. The (v3 · 92% reliable) in a heading is rendered from it for the reader. Edit the heading and the numbers do not change — the parser reads the frontmatter.
  2. memfmt_type marks a file as ours. Files without it are ignored, so a memory folder can live inside a vault full of somebody's own notes.
  3. Unknown fields are left alone. Nothing is silently dropped for being unrecognised.
  4. Bullets are one line. Facts, steps and relation notes are collapsed to a single line when written, so what a file says and what a parser reads back are the same thing.
  5. A folder is a set, not a list. Reading a directory cannot recover the order of the list that wrote it. Use canonical() to compare two memories, and to keep git diffs to the lines that actually changed.
  6. Round-trip or it is not the format. parse(serialise(m)) == m, and serialising what you parsed is byte-identical. memfmt validate checks exactly this against a real folder.

Why files

Because the alternative is that your agent's memory lives somewhere you cannot read, cannot grep, cannot correct, and cannot take with you.

Files give you the things a database makes hard: git diff on what your agent learned this week, a pull request when it learns something wrong, git revert when it learns something harmful, and a graph view for free. And when the tool that wrote them goes away, the memory does not.

Where files stop being enough

Honestly: at a few hundred of them.

Word overlap is the best memfmt context can do without embeddings, and it starts missing things that are phrased differently. Syncing a folder between machines or a team is a real problem, not a git pull away. Deduplicating facts that contradict each other needs a model.

That is a server's job, and memfmt does not pretend otherwise. If you get there, Mengram writes this format today — mengram export markdown ./memory hands you a tree this library reads — and adds the search, sync and deduplication that files alone cannot do. Syncing a folder back into it is not built yet.

Either way the files stay yours, and if you never need a server, this library does not expire.


Contributing

The test suite is the specification in executable form. If you are proposing a change to the format, the change to tests/test_roundtrip.py is the proposal.

pip install -e ".[dev]"
pytest

MIT licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

memfmt-0.1.0.tar.gz (17.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

memfmt-0.1.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file memfmt-0.1.0.tar.gz.

File metadata

  • Download URL: memfmt-0.1.0.tar.gz
  • Upload date:
  • Size: 17.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.6

File hashes

Hashes for memfmt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8ddf50992bec0cb567368eeb2300fdf2bb908913749bb723e413b152f5bb86ae
MD5 577605e828dd9674b40c64cc76e95e4f
BLAKE2b-256 2e8e5a8b9a79ddf01aa007a7bf2e29a9faf0df1405cdf6536199ca5915eb53e6

See more details on using hashes here.

File details

Details for the file memfmt-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: memfmt-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.6

File hashes

Hashes for memfmt-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5375f045d69ab47d943451efd659a862e1991dc58b4b16ef355b3cc8cee5a490
MD5 7dbf94a22d67d9f4eb71c26c91a6f440
BLAKE2b-256 aaed22a5b0288919d75b5e522e9e00084624cb77575bfd811221e5e6847e7425

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page