memfmt 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .pytest_cache/
memfmt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ali Baizhanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
memfmt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.5
2
+ Name: memfmt
3
+ Version: 0.1.0
4
+ Summary: An agent's memory as Markdown files you own — read, write and check the format.
5
+ Project-URL: Homepage, https://github.com/alibaizhanov/memfmt
6
+ Project-URL: Source, https://github.com/alibaizhanov/memfmt
7
+ Author: Ali Baizhanov
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,format,llm,markdown,memory,obsidian
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
17
+ Requires-Python: >=3.9
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # memfmt
23
+
24
+ **An agent's memory as Markdown files you own.**
25
+
26
+ Every agent that remembers anything invents its own way to store it: one
27
+ `MEMORY.md` that grows until it stops fitting in context, a bespoke JSON blob,
28
+ a folder of notes with no rules. Nobody can read anybody else's, nothing
29
+ diffs cleanly, and moving between tools means writing a converter.
30
+
31
+ memfmt is a small spec and a dependency-free Python library for the boring
32
+ version of that: memory as plain Markdown, one thing per file, relations as
33
+ `[[wikilinks]]`. Git gives you diffs, review and rollback. Obsidian draws the
34
+ graph with no configuration, because the links *are* the graph.
35
+
36
+ ```
37
+ memory/
38
+ MEMORY.md index — what is in here
39
+ entities/Ali.md what is true
40
+ episodes/2026-07-30-deploy-failed.md what happened
41
+ procedures/deploy to Railway.md how to do it, and whether it works
42
+ profile.md
43
+ ```
44
+
45
+ No account, no server, no network. This library reads and writes files.
46
+
47
+ ---
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install memfmt
53
+ ```
54
+
55
+ ## Use it
56
+
57
+ ```bash
58
+ memfmt stat ./memory # what is in here
59
+ memfmt validate ./memory # would any file lose data if a tool rewrote it?
60
+ memfmt context ./memory "why did the deploy fail" # the relevant bits, to pipe into a model
61
+ ```
62
+
63
+ ```python
64
+ from memfmt import load, serialise, write_dir, canonical
65
+
66
+ memory = load("./memory")
67
+
68
+ for p in memory.procedures:
69
+ print(p.name, p.version, p.reliability) # deploy to Railway 3 92% reliable
70
+
71
+ write_dir(serialise(memory), "./memory")
72
+ ```
73
+
74
+ `memfmt context` is the one to try first. It picks the files relevant to a
75
+ question and prints them, so you can pipe your agent's own memory into a
76
+ prompt without a database:
77
+
78
+ ```bash
79
+ memfmt context ./memory "deploy railway pool" | pbcopy
80
+ ```
81
+
82
+ ---
83
+
84
+ ## The format
85
+
86
+ Three kinds of memory, because agents forget in three different ways.
87
+
88
+ ### Entities — what is true
89
+
90
+ `memory/entities/<name>.md`
91
+
92
+ ````markdown
93
+ ---
94
+ memfmt_type: entity
95
+ entity_type: person
96
+ id: e1
97
+ ---
98
+
99
+ # Ali
100
+
101
+ ## Facts
102
+
103
+ - prefers Rust for memory safety
104
+ - based in Tokyo
105
+
106
+ ## Relations
107
+
108
+ - works at → [[Mengram]] — since 2024
109
+ - mentored by ← [[Kenji]]
110
+
111
+ ## Knowledge
112
+
113
+ **[snippet] deploy command** — how the service ships
114
+
115
+ ```
116
+ railway up --detach
117
+ ```
118
+ ````
119
+
120
+ `→` is outgoing, `←` incoming. Text after ` — ` is a note on the relation.
121
+ When a name cannot be a filename, the link carries an alias and the real name
122
+ survives: `[[cloud-api.py|cloud/api.py]]`.
123
+
124
+ ### Episodes — what happened
125
+
126
+ `memory/episodes/<date>-<summary>.md`
127
+
128
+ ```markdown
129
+ ---
130
+ memfmt_type: episode
131
+ id: ep1
132
+ happened: 2026-07-30
133
+ outcome: rolled back, raised pool_max
134
+ valence: negative
135
+ importance: 4
136
+ participants:
137
+ - Ali
138
+ - Railway
139
+ ---
140
+
141
+ # deploy failed on a cold pool
142
+
143
+ Two workers booted at once and the session pooler refused the fourth client.
144
+
145
+ **Outcome** — rolled back, raised pool_max
146
+ ```
147
+
148
+ An event with no outcome teaches nothing, so `outcome` is the field that earns
149
+ an episode its place.
150
+
151
+ ### Procedures — how to do something, and whether it works
152
+
153
+ `memory/procedures/<name>.md`
154
+
155
+ ```markdown
156
+ ---
157
+ memfmt_type: procedure
158
+ id: p1
159
+ version: 3
160
+ success_count: 11
161
+ fail_count: 1
162
+ ---
163
+
164
+ # deploy to Railway (v3 · 92% reliable)
165
+
166
+ **When** — a change lands on main
167
+
168
+ **Preconditions**
169
+
170
+ - tests pass
171
+ - pool_max is set
172
+
173
+ ## Steps
174
+
175
+ 1. push to main — the webhook does the rest
176
+ 2. watch the boot log
177
+ 3. verify /health — expect 200 within 60s
178
+
179
+ ## Evolution
180
+
181
+ - v1 → v2 (2026-06-02): added the health check
182
+ - v2 → v3: wait for the pool before probing
183
+ ```
184
+
185
+ This is the file the format exists for. A workflow on its own is a guess
186
+ somebody wrote down. With `11 ✓ / 1 ✗` and the revisions that produced it, it
187
+ is evidence — and an agent can tell the difference between a step that has
188
+ worked eleven times and one nobody has ever run.
189
+
190
+ ---
191
+
192
+ ## Rules
193
+
194
+ A short list, because a format nobody can hold in their head gets implemented
195
+ wrong.
196
+
197
+ 1. **Frontmatter is the source of truth.** The `(v3 · 92% reliable)` in a
198
+ heading is rendered from it for the reader. Edit the heading and the
199
+ numbers do not change — the parser reads the frontmatter.
200
+ 2. **`memfmt_type` marks a file as ours.** Files without it are ignored, so a
201
+ memory folder can live inside a vault full of somebody's own notes.
202
+ 3. **Unknown fields are left alone.** Nothing is silently dropped for being
203
+ unrecognised.
204
+ 4. **Bullets are one line.** Facts, steps and relation notes are collapsed to
205
+ a single line when written, so what a file says and what a parser reads
206
+ back are the same thing.
207
+ 5. **A folder is a set, not a list.** Reading a directory cannot recover the
208
+ order of the list that wrote it. Use `canonical()` to compare two memories,
209
+ and to keep git diffs to the lines that actually changed.
210
+ 6. **Round-trip or it is not the format.** `parse(serialise(m)) == m`, and
211
+ serialising what you parsed is byte-identical. `memfmt validate` checks
212
+ exactly this against a real folder.
213
+
214
+ ---
215
+
216
+ ## Why files
217
+
218
+ Because the alternative is that your agent's memory lives somewhere you cannot
219
+ read, cannot grep, cannot correct, and cannot take with you.
220
+
221
+ Files give you the things a database makes hard: `git diff` on what your agent
222
+ learned this week, a pull request when it learns something wrong, `git revert`
223
+ when it learns something harmful, and a graph view for free. And when the tool
224
+ that wrote them goes away, the memory does not.
225
+
226
+ ## Where files stop being enough
227
+
228
+ Honestly: at a few hundred of them.
229
+
230
+ Word overlap is the best `memfmt context` can do without embeddings, and it
231
+ starts missing things that are phrased differently. Syncing a folder between
232
+ machines or a team is a real problem, not a `git pull` away. Deduplicating
233
+ facts that contradict each other needs a model.
234
+
235
+ That is a server's job, and memfmt does not pretend otherwise. If you get
236
+ there, [Mengram](https://mengram.io?utm_source=memfmt&utm_medium=readme)
237
+ writes this format today — `mengram export markdown ./memory` hands you a tree
238
+ this library reads — and adds the search, sync and deduplication that files
239
+ alone cannot do. Syncing a folder back into it is not built yet.
240
+
241
+ Either way the files stay yours, and if you never need a server, this library
242
+ does not expire.
243
+
244
+ ---
245
+
246
+ ## Contributing
247
+
248
+ The test suite is the specification in executable form. If you are proposing a
249
+ change to the format, the change to `tests/test_roundtrip.py` is the proposal.
250
+
251
+ ```bash
252
+ pip install -e ".[dev]"
253
+ pytest
254
+ ```
255
+
256
+ MIT licensed.
memfmt-0.1.0/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # memfmt
2
+
3
+ **An agent's memory as Markdown files you own.**
4
+
5
+ Every agent that remembers anything invents its own way to store it: one
6
+ `MEMORY.md` that grows until it stops fitting in context, a bespoke JSON blob,
7
+ a folder of notes with no rules. Nobody can read anybody else's, nothing
8
+ diffs cleanly, and moving between tools means writing a converter.
9
+
10
+ memfmt is a small spec and a dependency-free Python library for the boring
11
+ version of that: memory as plain Markdown, one thing per file, relations as
12
+ `[[wikilinks]]`. Git gives you diffs, review and rollback. Obsidian draws the
13
+ graph with no configuration, because the links *are* the graph.
14
+
15
+ ```
16
+ memory/
17
+ MEMORY.md index — what is in here
18
+ entities/Ali.md what is true
19
+ episodes/2026-07-30-deploy-failed.md what happened
20
+ procedures/deploy to Railway.md how to do it, and whether it works
21
+ profile.md
22
+ ```
23
+
24
+ No account, no server, no network. This library reads and writes files.
25
+
26
+ ---
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install memfmt
32
+ ```
33
+
34
+ ## Use it
35
+
36
+ ```bash
37
+ memfmt stat ./memory # what is in here
38
+ memfmt validate ./memory # would any file lose data if a tool rewrote it?
39
+ memfmt context ./memory "why did the deploy fail" # the relevant bits, to pipe into a model
40
+ ```
41
+
42
+ ```python
43
+ from memfmt import load, serialise, write_dir, canonical
44
+
45
+ memory = load("./memory")
46
+
47
+ for p in memory.procedures:
48
+ print(p.name, p.version, p.reliability) # deploy to Railway 3 92% reliable
49
+
50
+ write_dir(serialise(memory), "./memory")
51
+ ```
52
+
53
+ `memfmt context` is the one to try first. It picks the files relevant to a
54
+ question and prints them, so you can pipe your agent's own memory into a
55
+ prompt without a database:
56
+
57
+ ```bash
58
+ memfmt context ./memory "deploy railway pool" | pbcopy
59
+ ```
60
+
61
+ ---
62
+
63
+ ## The format
64
+
65
+ Three kinds of memory, because agents forget in three different ways.
66
+
67
+ ### Entities — what is true
68
+
69
+ `memory/entities/<name>.md`
70
+
71
+ ````markdown
72
+ ---
73
+ memfmt_type: entity
74
+ entity_type: person
75
+ id: e1
76
+ ---
77
+
78
+ # Ali
79
+
80
+ ## Facts
81
+
82
+ - prefers Rust for memory safety
83
+ - based in Tokyo
84
+
85
+ ## Relations
86
+
87
+ - works at → [[Mengram]] — since 2024
88
+ - mentored by ← [[Kenji]]
89
+
90
+ ## Knowledge
91
+
92
+ **[snippet] deploy command** — how the service ships
93
+
94
+ ```
95
+ railway up --detach
96
+ ```
97
+ ````
98
+
99
+ `→` is outgoing, `←` incoming. Text after ` — ` is a note on the relation.
100
+ When a name cannot be a filename, the link carries an alias and the real name
101
+ survives: `[[cloud-api.py|cloud/api.py]]`.
102
+
103
+ ### Episodes — what happened
104
+
105
+ `memory/episodes/<date>-<summary>.md`
106
+
107
+ ```markdown
108
+ ---
109
+ memfmt_type: episode
110
+ id: ep1
111
+ happened: 2026-07-30
112
+ outcome: rolled back, raised pool_max
113
+ valence: negative
114
+ importance: 4
115
+ participants:
116
+ - Ali
117
+ - Railway
118
+ ---
119
+
120
+ # deploy failed on a cold pool
121
+
122
+ Two workers booted at once and the session pooler refused the fourth client.
123
+
124
+ **Outcome** — rolled back, raised pool_max
125
+ ```
126
+
127
+ An event with no outcome teaches nothing, so `outcome` is the field that earns
128
+ an episode its place.
129
+
130
+ ### Procedures — how to do something, and whether it works
131
+
132
+ `memory/procedures/<name>.md`
133
+
134
+ ```markdown
135
+ ---
136
+ memfmt_type: procedure
137
+ id: p1
138
+ version: 3
139
+ success_count: 11
140
+ fail_count: 1
141
+ ---
142
+
143
+ # deploy to Railway (v3 · 92% reliable)
144
+
145
+ **When** — a change lands on main
146
+
147
+ **Preconditions**
148
+
149
+ - tests pass
150
+ - pool_max is set
151
+
152
+ ## Steps
153
+
154
+ 1. push to main — the webhook does the rest
155
+ 2. watch the boot log
156
+ 3. verify /health — expect 200 within 60s
157
+
158
+ ## Evolution
159
+
160
+ - v1 → v2 (2026-06-02): added the health check
161
+ - v2 → v3: wait for the pool before probing
162
+ ```
163
+
164
+ This is the file the format exists for. A workflow on its own is a guess
165
+ somebody wrote down. With `11 ✓ / 1 ✗` and the revisions that produced it, it
166
+ is evidence — and an agent can tell the difference between a step that has
167
+ worked eleven times and one nobody has ever run.
168
+
169
+ ---
170
+
171
+ ## Rules
172
+
173
+ A short list, because a format nobody can hold in their head gets implemented
174
+ wrong.
175
+
176
+ 1. **Frontmatter is the source of truth.** The `(v3 · 92% reliable)` in a
177
+ heading is rendered from it for the reader. Edit the heading and the
178
+ numbers do not change — the parser reads the frontmatter.
179
+ 2. **`memfmt_type` marks a file as ours.** Files without it are ignored, so a
180
+ memory folder can live inside a vault full of somebody's own notes.
181
+ 3. **Unknown fields are left alone.** Nothing is silently dropped for being
182
+ unrecognised.
183
+ 4. **Bullets are one line.** Facts, steps and relation notes are collapsed to
184
+ a single line when written, so what a file says and what a parser reads
185
+ back are the same thing.
186
+ 5. **A folder is a set, not a list.** Reading a directory cannot recover the
187
+ order of the list that wrote it. Use `canonical()` to compare two memories,
188
+ and to keep git diffs to the lines that actually changed.
189
+ 6. **Round-trip or it is not the format.** `parse(serialise(m)) == m`, and
190
+ serialising what you parsed is byte-identical. `memfmt validate` checks
191
+ exactly this against a real folder.
192
+
193
+ ---
194
+
195
+ ## Why files
196
+
197
+ Because the alternative is that your agent's memory lives somewhere you cannot
198
+ read, cannot grep, cannot correct, and cannot take with you.
199
+
200
+ Files give you the things a database makes hard: `git diff` on what your agent
201
+ learned this week, a pull request when it learns something wrong, `git revert`
202
+ when it learns something harmful, and a graph view for free. And when the tool
203
+ that wrote them goes away, the memory does not.
204
+
205
+ ## Where files stop being enough
206
+
207
+ Honestly: at a few hundred of them.
208
+
209
+ Word overlap is the best `memfmt context` can do without embeddings, and it
210
+ starts missing things that are phrased differently. Syncing a folder between
211
+ machines or a team is a real problem, not a `git pull` away. Deduplicating
212
+ facts that contradict each other needs a model.
213
+
214
+ That is a server's job, and memfmt does not pretend otherwise. If you get
215
+ there, [Mengram](https://mengram.io?utm_source=memfmt&utm_medium=readme)
216
+ writes this format today — `mengram export markdown ./memory` hands you a tree
217
+ this library reads — and adds the search, sync and deduplication that files
218
+ alone cannot do. Syncing a folder back into it is not built yet.
219
+
220
+ Either way the files stay yours, and if you never need a server, this library
221
+ does not expire.
222
+
223
+ ---
224
+
225
+ ## Contributing
226
+
227
+ The test suite is the specification in executable form. If you are proposing a
228
+ change to the format, the change to `tests/test_roundtrip.py` is the proposal.
229
+
230
+ ```bash
231
+ pip install -e ".[dev]"
232
+ pytest
233
+ ```
234
+
235
+ MIT licensed.
@@ -0,0 +1,32 @@
1
+ """memfmt — an agent's memory as Markdown files you own.
2
+
3
+ from memfmt import load, serialise, write_dir
4
+
5
+ memory = load("./memory")
6
+ memory.procedures[0].reliability # '92% reliable'
7
+ write_dir(serialise(memory), "./memory")
8
+
9
+ Three kinds of memory: entities (what is true), episodes (what happened),
10
+ procedures (how to do something, with the record of it working). Plain
11
+ Markdown, `[[wikilinks]]` for relations, so Obsidian draws the graph with no
12
+ configuration and git gives you diffs, review and rollback for free.
13
+ """
14
+
15
+ from .model import (Entity, Episode, Knowledge, Memory, Procedure, Relation,
16
+ Revision, Step, canonical)
17
+ from .parse import (MemfmtError, file_type, load, parse, parse_frontmatter,
18
+ read_dir, write_dir)
19
+ from .serialize import INDEX, ROOT, TYPE_KEY, serialise, slugify
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ #: American spelling, same function — the CLI and half the world write it this way.
24
+ serialize = serialise
25
+
26
+ __all__ = [
27
+ "Entity", "Episode", "Knowledge", "Memory", "Procedure", "Relation",
28
+ "Revision", "Step", "canonical",
29
+ "load", "parse", "parse_frontmatter", "read_dir", "write_dir", "file_type",
30
+ "serialise", "serialize", "slugify",
31
+ "MemfmtError", "TYPE_KEY", "ROOT", "INDEX", "__version__",
32
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())