Metadata-Version: 2.5
Name: memfmt
Version: 0.1.0
Summary: An agent's memory as Markdown files you own — read, write and check the format.
Project-URL: Homepage, https://github.com/alibaizhanov/memfmt
Project-URL: Source, https://github.com/alibaizhanov/memfmt
Author: Ali Baizhanov
License-Expression: MIT
License-File: LICENSE
Keywords: agent,format,llm,markdown,memory,obsidian
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# 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

```bash
pip install memfmt
```

## Use it

```bash
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
```

```python
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:

```bash
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`

````markdown
---
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`

```markdown
---
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`

```markdown
---
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](https://mengram.io?utm_source=memfmt&utm_medium=readme)
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.

```bash
pip install -e ".[dev]"
pytest
```

MIT licensed.
