memoryfield 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.
- memoryfield-0.1.0/.gitignore +114 -0
- memoryfield-0.1.0/LICENSE +21 -0
- memoryfield-0.1.0/PKG-INFO +248 -0
- memoryfield-0.1.0/README.md +218 -0
- memoryfield-0.1.0/mf/__init__.py +8 -0
- memoryfield-0.1.0/mf/claim.py +62 -0
- memoryfield-0.1.0/mf/cli.py +668 -0
- memoryfield-0.1.0/mf/confidence.py +90 -0
- memoryfield-0.1.0/mf/consolidate.py +138 -0
- memoryfield-0.1.0/mf/db.py +90 -0
- memoryfield-0.1.0/mf/embed_backend.py +80 -0
- memoryfield-0.1.0/mf/embedder.py +153 -0
- memoryfield-0.1.0/mf/embedding.py +61 -0
- memoryfield-0.1.0/mf/hooks.py +160 -0
- memoryfield-0.1.0/mf/importers.py +250 -0
- memoryfield-0.1.0/mf/indexer.py +194 -0
- memoryfield-0.1.0/mf/lint.py +318 -0
- memoryfield-0.1.0/mf/mcp_server.py +129 -0
- memoryfield-0.1.0/mf/models.py +92 -0
- memoryfield-0.1.0/mf/pack.py +368 -0
- memoryfield-0.1.0/mf/page.py +222 -0
- memoryfield-0.1.0/mf/query_prep.py +59 -0
- memoryfield-0.1.0/mf/raw.py +67 -0
- memoryfield-0.1.0/mf/read.py +157 -0
- memoryfield-0.1.0/mf/schema.py +110 -0
- memoryfield-0.1.0/mf/search.py +327 -0
- memoryfield-0.1.0/mf/tokens.py +25 -0
- memoryfield-0.1.0/mf/write.py +226 -0
- memoryfield-0.1.0/pyproject.toml +51 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# OS — macOS (primary target), Linux, Windows
|
|
2
|
+
# macOS
|
|
3
|
+
.DS_Store
|
|
4
|
+
.DS_Store?
|
|
5
|
+
._*
|
|
6
|
+
.Spotlight-V100
|
|
7
|
+
.Trashes
|
|
8
|
+
.fseventsd
|
|
9
|
+
.AppleDouble
|
|
10
|
+
.LSOverride
|
|
11
|
+
ehthumbs.db
|
|
12
|
+
Thumbs.db
|
|
13
|
+
Desktop.ini
|
|
14
|
+
Icon?
|
|
15
|
+
ehthumbs.db
|
|
16
|
+
|
|
17
|
+
# Editors / IDEs
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
*.sublime-project
|
|
24
|
+
*.sublime-workspace
|
|
25
|
+
.fleet/
|
|
26
|
+
.cursor/
|
|
27
|
+
|
|
28
|
+
# Python
|
|
29
|
+
__pycache__/
|
|
30
|
+
*.py[cod]
|
|
31
|
+
*$py.class
|
|
32
|
+
*.so
|
|
33
|
+
.Python
|
|
34
|
+
build/
|
|
35
|
+
develop-eggs/
|
|
36
|
+
dist/
|
|
37
|
+
downloads/
|
|
38
|
+
eggs/
|
|
39
|
+
.eggs/
|
|
40
|
+
lib/
|
|
41
|
+
lib64/
|
|
42
|
+
parts/
|
|
43
|
+
sdist/
|
|
44
|
+
var/
|
|
45
|
+
wheels/
|
|
46
|
+
*.egg-info/
|
|
47
|
+
.installed.cfg
|
|
48
|
+
*.egg
|
|
49
|
+
MANIFEST
|
|
50
|
+
|
|
51
|
+
# Virtual envs (project-local)
|
|
52
|
+
.venv/
|
|
53
|
+
venv/
|
|
54
|
+
env/
|
|
55
|
+
.python-version
|
|
56
|
+
|
|
57
|
+
# uv
|
|
58
|
+
uv.lock
|
|
59
|
+
.uv-cache/
|
|
60
|
+
|
|
61
|
+
# mypy / ruff / pytest
|
|
62
|
+
.mypy_cache/
|
|
63
|
+
.ruff_cache/
|
|
64
|
+
.pytest_cache/
|
|
65
|
+
.coverage
|
|
66
|
+
htmlcov/
|
|
67
|
+
.tox/
|
|
68
|
+
|
|
69
|
+
# Jupyter
|
|
70
|
+
.ipynb_checkpoints/
|
|
71
|
+
|
|
72
|
+
# Logs and local data
|
|
73
|
+
*.log
|
|
74
|
+
logs/
|
|
75
|
+
local/
|
|
76
|
+
tmp/
|
|
77
|
+
scratch/
|
|
78
|
+
|
|
79
|
+
# Repo-packing tool output (repomix), not project content
|
|
80
|
+
repomix-output.xml
|
|
81
|
+
.repomixignore
|
|
82
|
+
|
|
83
|
+
# Generated indexes and caches — never commit
|
|
84
|
+
mf.sqlite3
|
|
85
|
+
mf.sqlite3-journal
|
|
86
|
+
mf.sqlite3-wal
|
|
87
|
+
mf.sqlite3-shm
|
|
88
|
+
*.idx/
|
|
89
|
+
.cache/
|
|
90
|
+
indexes/
|
|
91
|
+
|
|
92
|
+
# Memoryfield derived data
|
|
93
|
+
# (canonical pages are committed; built indexes are not)
|
|
94
|
+
**/__pycache__/
|
|
95
|
+
**/.mfgpt/
|
|
96
|
+
|
|
97
|
+
# Model weights — large, never commit
|
|
98
|
+
models/
|
|
99
|
+
*.onnx
|
|
100
|
+
*.bin
|
|
101
|
+
*.safetensors
|
|
102
|
+
*.gguf
|
|
103
|
+
*.pt
|
|
104
|
+
*.pth
|
|
105
|
+
*.h5
|
|
106
|
+
*.tflite
|
|
107
|
+
*.pb
|
|
108
|
+
|
|
109
|
+
# Eval results (kept local until reported)
|
|
110
|
+
results/raw/
|
|
111
|
+
*.eval.json
|
|
112
|
+
|
|
113
|
+
# External fixtures fetched by eval/fetch_soapstones.py (pinned, not committed)
|
|
114
|
+
eval/fixtures/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 whit3rabbit
|
|
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.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: memoryfield
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Memoryfield: a search-first, tiered-read memory tool for coding agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/whit3rabbit/memoryfield
|
|
6
|
+
Project-URL: Repository, https://github.com/whit3rabbit/memoryfield
|
|
7
|
+
Author: whit3rabbit
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: fastembed>=0.8
|
|
21
|
+
Requires-Dist: pyyaml>=6
|
|
22
|
+
Requires-Dist: sqlite-vec>=0.1
|
|
23
|
+
Provides-Extra: eval
|
|
24
|
+
Requires-Dist: fastembed>=0.8; extra == 'eval'
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp<3,>=2.1; extra == 'mcp'
|
|
27
|
+
Provides-Extra: mlx
|
|
28
|
+
Requires-Dist: mlx-embedding-models>=0.0.11; (sys_platform == 'darwin' and platform_machine == 'arm64') and extra == 'mlx'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# mf
|
|
32
|
+
|
|
33
|
+
Search-first memory for coding agents: plain Markdown pages, SQLite
|
|
34
|
+
search, and no LLM in the loop.
|
|
35
|
+
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](pyproject.toml)
|
|
38
|
+
|
|
39
|
+
[Install](#install) · [Quickstart](#quickstart) · [Design decisions](#design-decisions) · [Agents](#using-it-with-an-agent) · [Documentation](#documentation)
|
|
40
|
+
|
|
41
|
+
A field is a directory of Markdown pages with frontmatter. mf indexes
|
|
42
|
+
it into SQLite and answers a question with stubs, not pages: the agent
|
|
43
|
+
reads a one-line summary first and opens the body only when it needs
|
|
44
|
+
to.
|
|
45
|
+
|
|
46
|
+
The page format is Cal Paterson's
|
|
47
|
+
[memoryfield](https://calpaterson.com/memoryfields.html) spec
|
|
48
|
+
([vendored copy](docs/upstream/SPEC.md)). Any spec field loads
|
|
49
|
+
unchanged, `mf pack --spec` writes one back out, and everything mf adds
|
|
50
|
+
on top is its own measured design.
|
|
51
|
+
|
|
52
|
+
Session-injected memory costs the same on every task, whether or not it
|
|
53
|
+
gets used. mf moves that cost to lookup time: about 100 tokens for a
|
|
54
|
+
default search and 55 for a point lookup, measured over 20 real agent
|
|
55
|
+
tasks ([Benchmarks, section 4](docs/BENCHMARKS.md#4-token-cost-benchmarks)).
|
|
56
|
+
Most lookups end at the stub.
|
|
57
|
+
|
|
58
|
+
## Why mf
|
|
59
|
+
|
|
60
|
+
- **Stubs, not pages.** A search returns uuid, title, and a summary
|
|
61
|
+
written as the answer. Reads tier up only when the stub is not
|
|
62
|
+
enough.
|
|
63
|
+
- **A confidence line you can act on.** `high`, `low`, or `none` before
|
|
64
|
+
every result, from a gate calibrated on blind phrasing to demote
|
|
65
|
+
rather than overclaim.
|
|
66
|
+
- **A write path with a dedup gate.** `mf write` validates, checks for
|
|
67
|
+
near-duplicates, copies in, and indexes in one step.
|
|
68
|
+
- **Plain files, spec-compatible.** Pages stay Markdown you can read,
|
|
69
|
+
diff, and edit, and any memoryfield reader can load them.
|
|
70
|
+
- **Measured, not assumed.** Every ranking, gate, and default was run
|
|
71
|
+
through the real pipeline on queries written without seeing the
|
|
72
|
+
corpus before it was hardcoded.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
Python 3.11 or newer, installed through [uv](https://docs.astral.sh/uv/):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv tool install . # from a checkout
|
|
80
|
+
uv tool install git+https://github.com/whit3rabbit/memoryfield
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The first search downloads the embedding model (default
|
|
84
|
+
`snowflake-arctic-embed-xs`, 384-d, about 170 MB). The model is pinned
|
|
85
|
+
per field at `mf init`. Alternatives, and when to pick one:
|
|
86
|
+
[docs/models.md](docs/models.md).
|
|
87
|
+
|
|
88
|
+
`mf mcp` (an MCP server for `search`/`read`/`write`/`raw_add`) needs an
|
|
89
|
+
extra: `uv tool install ".[mcp]"`. It's optional because most usage is
|
|
90
|
+
the CLI directly or the Claude Code skill, and the MCP stack (roughly
|
|
91
|
+
a dozen extra packages) isn't worth pulling in for those.
|
|
92
|
+
|
|
93
|
+
## Quickstart
|
|
94
|
+
|
|
95
|
+
Using this repo's eval corpus as sample pages:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
$ mf init ~/field
|
|
99
|
+
Initialized empty field at ~/field/mf.sqlite3 (model snowflake-arctic-embed-xs, 384-d)
|
|
100
|
+
|
|
101
|
+
$ cp eval/corpus/codebase/*.md ~/field/ && mf index ~/field
|
|
102
|
+
75 upserted, 0 unchanged, 0 deleted
|
|
103
|
+
|
|
104
|
+
$ mf search "how do we roll back a deploy" --field ~/field
|
|
105
|
+
confidence: low
|
|
106
|
+
- [code-deploy-rollback-cmd] Deploy: how to roll back a bad release
|
|
107
|
+
`kubectl rollout undo deployment/<service>`; rollback is a forward operation and takes ~90 seconds end-to-end.
|
|
108
|
+
- [code-deploy-pre-checklist] Deploy: pre-deploy checklist
|
|
109
|
+
Tests green, migrations applied to staging, dashboards reviewed, on-call notified, rollback plan documented.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Read the confidence line before the results:
|
|
113
|
+
|
|
114
|
+
- `high`: the stub is safe to cite.
|
|
115
|
+
- `low`: a strong lead. `mf read <uuid>` for the page's answer section
|
|
116
|
+
before quoting it.
|
|
117
|
+
- `none`: do not cite it.
|
|
118
|
+
|
|
119
|
+
The gate errs toward demotion, not overclaiming.
|
|
120
|
+
|
|
121
|
+
When the stub is not enough, `mf read <uuid>` returns the page's answer
|
|
122
|
+
section, and `--tier L2` or `<uuid>#section` returns more.
|
|
123
|
+
|
|
124
|
+
New pages go
|
|
125
|
+
in through `mf write <draft> --field <dir>`, drafted outside the field.
|
|
126
|
+
Exit 2 means a near-duplicate was flagged. The calling contract an
|
|
127
|
+
agent should follow is in [docs/agents.md](docs/agents.md), and every
|
|
128
|
+
flag is in [docs/CLI.md](docs/CLI.md#mf-write).
|
|
129
|
+
|
|
130
|
+
## Design decisions
|
|
131
|
+
|
|
132
|
+
Each choice below was measured on a 157-page corpus, blind phrasing
|
|
133
|
+
sets, and one field this project did not write. The numbers live
|
|
134
|
+
behind the links, not here, so they cannot drift.
|
|
135
|
+
|
|
136
|
+
- **Dense-first ranking.** The vector index ranks. FTS runs on every
|
|
137
|
+
query as a gate signal and a fallback, never as the primary ranker,
|
|
138
|
+
because fusing the two averaged keyword noise into good semantic
|
|
139
|
+
rankings. [Benchmarks, section 2](docs/BENCHMARKS.md#2-ranking-architecture-benchmarks)
|
|
140
|
+
- **A three-signal confidence gate.** A BM25 floor alone demoted nearly
|
|
141
|
+
half of the answerable blind queries and collapsed on small fields.
|
|
142
|
+
The gate now combines a dense distance floor, the BM25 score, and
|
|
143
|
+
top-1 agreement. [Benchmarks, section 3](docs/BENCHMARKS.md#3-confidence-gate-benchmarks)
|
|
144
|
+
- **Lean stubs by default.** Two stubs and no neighbors, because the
|
|
145
|
+
original five stubs and three neighbors cost more tokens than
|
|
146
|
+
exploring raw files did. [Benchmarks, section 4](docs/BENCHMARKS.md#4-token-cost-benchmarks)
|
|
147
|
+
- **A write-time dedup gate.** Cosine distance on title, summary, and
|
|
148
|
+
first section, with the threshold set on a labeled paraphrase set.
|
|
149
|
+
It catches copies and light rewordings, not thorough rewrites.
|
|
150
|
+
[Architecture, section 5](docs/architecture.md#5-write)
|
|
151
|
+
- **A small default embedder, pinned per field.** A 384-d model that
|
|
152
|
+
matched the larger ones on blind accuracy at a fraction of the load
|
|
153
|
+
time and storage. [docs/models.md](docs/models.md)
|
|
154
|
+
- **No LLM and no reranker inside the tool.** The host agent already in
|
|
155
|
+
context does extraction and judgment. mf stays deterministic, local,
|
|
156
|
+
and sub-second. [Architecture, "Stack"](docs/architecture.md#stack)
|
|
157
|
+
|
|
158
|
+
## Using it with an agent
|
|
159
|
+
|
|
160
|
+
A Claude Code skill that teaches the lean calls, the confidence
|
|
161
|
+
contract, and the write path ships in
|
|
162
|
+
[.claude/skills/mf](.claude/skills/mf). Copy it into your project's
|
|
163
|
+
`.claude/skills/` to use mf there.
|
|
164
|
+
|
|
165
|
+
Two hooks, `mf hook stop` and `mf hook session-end`, ask the agent to
|
|
166
|
+
capture what it learned before it finishes and stage a transcript
|
|
167
|
+
pointer for later consolidation. Setup, the hooks snippet, and the
|
|
168
|
+
calling contract: [docs/agents.md](docs/agents.md).
|
|
169
|
+
|
|
170
|
+
## Commands
|
|
171
|
+
|
|
172
|
+
Full arguments, flags, exit codes, and JSON outputs are documented in
|
|
173
|
+
[docs/CLI.md](docs/CLI.md).
|
|
174
|
+
|
|
175
|
+
| Command | What it does |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `mf init [DIR]` | create `mf.sqlite3` in a field, pinning model and dimension |
|
|
178
|
+
| `mf index [DIR]` | scan the field's pages into the index |
|
|
179
|
+
| `mf search "<query>"` | stub-first lookup with the confidence gate |
|
|
180
|
+
| `mf read <uuid>[#section] ...` | read the answer section, one section, or L2 |
|
|
181
|
+
| `mf write <draft>` | validate, dedup-check, copy in, and index a draft |
|
|
182
|
+
| `mf raw add` | stage a freeform session extract under `raw/` |
|
|
183
|
+
| `mf lint [DIR]` | check writing conventions and index drift, `--check` for CI |
|
|
184
|
+
| `mf pack` / `mf unpack` | reproducible archive plus sha256 sidecar, verified extraction, `--spec` for other memoryfield readers |
|
|
185
|
+
| `mf import claude-memory <dir>` | turn a Claude Code memory directory into pages |
|
|
186
|
+
| `mf import wiki <dir>` | turn an index.md-style wiki into pages |
|
|
187
|
+
| `mf hook stop` / `mf hook session-end` | Claude Code hook handlers |
|
|
188
|
+
| `mf model list` | list available embedding models, dimensions, speeds, and cache status |
|
|
189
|
+
| `mf model install <name>` | download and cache an embedding model ahead of time |
|
|
190
|
+
| `mf claim <slug> --by <writer>` | atomically claim a slug before creating a page (multi-writer) |
|
|
191
|
+
| `mf consolidate --plan` | propose create/review actions from `raw/` entries |
|
|
192
|
+
| `mf mcp` | run an MCP server exposing `search`/`read`/`write`/`raw_add` over stdio |
|
|
193
|
+
|
|
194
|
+
## Documentation
|
|
195
|
+
|
|
196
|
+
| Guide | What you can do |
|
|
197
|
+
|---|---|
|
|
198
|
+
| [Agents](docs/agents.md) | Wire mf into Claude Code: the skill, the hooks, and the lean-call contract. |
|
|
199
|
+
| [CLI reference](docs/CLI.md) | Look up every flag, exit code, and JSON shape. |
|
|
200
|
+
| [Models](docs/models.md) | Pick, pin, and pre-download an embedding model. |
|
|
201
|
+
| [Fields](docs/fields.md) | Write pages, lint, wire git hooks, import notes, and exchange fields with other memoryfield tools. |
|
|
202
|
+
| [Architecture](docs/architecture.md) | See the schema, how a search is ranked and gated, and the record of each decision. |
|
|
203
|
+
| [Benchmarks](docs/BENCHMARKS.md) | Read the numbers behind the design decisions. |
|
|
204
|
+
| [Docs index](docs/README.md) | Start from a task and find the right guide. |
|
|
205
|
+
|
|
206
|
+
## Eval harness
|
|
207
|
+
|
|
208
|
+
The repo ships a 157-page labeled corpus, a 458-query set plus blind
|
|
209
|
+
vocabulary-mismatch sets, and six baselines (grep, FTS5, TF-IDF, nomic,
|
|
210
|
+
BGE-large, and hybrid).
|
|
211
|
+
|
|
212
|
+
The in-vocabulary scores sit near ceiling
|
|
213
|
+
because the queries share an authoring process with the corpus. Read
|
|
214
|
+
[docs/M0.5_REPORT.md](docs/M0.5_REPORT.md) with that in mind, and
|
|
215
|
+
[docs/BENCHMARKS.md](docs/BENCHMARKS.md) section 5 for the soapstones
|
|
216
|
+
field, the first corpus outside that process.
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
uv sync --extra eval # fastembed into a local venv
|
|
220
|
+
uv sync --extra eval --extra mlx # optional, Apple Silicon MLX variants
|
|
221
|
+
uv run python3 -m eval.run_baselines # 45+ minutes wall time
|
|
222
|
+
uv run python3 -m eval.report # render the report
|
|
223
|
+
uv run python3 eval/fetch_soapstones.py # pinned foreign-field fixture
|
|
224
|
+
uv run python3 -m eval.calibrate_confidence_blind soapstones # ranking and gate on it
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv sync --extra eval --group dev
|
|
231
|
+
uv run pytest tests/
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`uv sync` calls do not compose: each one resets the venv to exactly
|
|
235
|
+
what that call specifies. Pass `--extra eval` and `--group dev`
|
|
236
|
+
together, in one invocation.
|
|
237
|
+
|
|
238
|
+
## Status
|
|
239
|
+
|
|
240
|
+
Read path, write path, and hooks/imports are built and tested. In
|
|
241
|
+
progress: multi-writer support (`mf claim`, `mf consolidate --plan`).
|
|
242
|
+
The per-item record of what was built, measured, and changed is in
|
|
243
|
+
[ROADMAP.md](ROADMAP.md). CLAUDE.md is the map for anyone working in
|
|
244
|
+
the repo.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# mf
|
|
2
|
+
|
|
3
|
+
Search-first memory for coding agents: plain Markdown pages, SQLite
|
|
4
|
+
search, and no LLM in the loop.
|
|
5
|
+
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](pyproject.toml)
|
|
8
|
+
|
|
9
|
+
[Install](#install) · [Quickstart](#quickstart) · [Design decisions](#design-decisions) · [Agents](#using-it-with-an-agent) · [Documentation](#documentation)
|
|
10
|
+
|
|
11
|
+
A field is a directory of Markdown pages with frontmatter. mf indexes
|
|
12
|
+
it into SQLite and answers a question with stubs, not pages: the agent
|
|
13
|
+
reads a one-line summary first and opens the body only when it needs
|
|
14
|
+
to.
|
|
15
|
+
|
|
16
|
+
The page format is Cal Paterson's
|
|
17
|
+
[memoryfield](https://calpaterson.com/memoryfields.html) spec
|
|
18
|
+
([vendored copy](docs/upstream/SPEC.md)). Any spec field loads
|
|
19
|
+
unchanged, `mf pack --spec` writes one back out, and everything mf adds
|
|
20
|
+
on top is its own measured design.
|
|
21
|
+
|
|
22
|
+
Session-injected memory costs the same on every task, whether or not it
|
|
23
|
+
gets used. mf moves that cost to lookup time: about 100 tokens for a
|
|
24
|
+
default search and 55 for a point lookup, measured over 20 real agent
|
|
25
|
+
tasks ([Benchmarks, section 4](docs/BENCHMARKS.md#4-token-cost-benchmarks)).
|
|
26
|
+
Most lookups end at the stub.
|
|
27
|
+
|
|
28
|
+
## Why mf
|
|
29
|
+
|
|
30
|
+
- **Stubs, not pages.** A search returns uuid, title, and a summary
|
|
31
|
+
written as the answer. Reads tier up only when the stub is not
|
|
32
|
+
enough.
|
|
33
|
+
- **A confidence line you can act on.** `high`, `low`, or `none` before
|
|
34
|
+
every result, from a gate calibrated on blind phrasing to demote
|
|
35
|
+
rather than overclaim.
|
|
36
|
+
- **A write path with a dedup gate.** `mf write` validates, checks for
|
|
37
|
+
near-duplicates, copies in, and indexes in one step.
|
|
38
|
+
- **Plain files, spec-compatible.** Pages stay Markdown you can read,
|
|
39
|
+
diff, and edit, and any memoryfield reader can load them.
|
|
40
|
+
- **Measured, not assumed.** Every ranking, gate, and default was run
|
|
41
|
+
through the real pipeline on queries written without seeing the
|
|
42
|
+
corpus before it was hardcoded.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
Python 3.11 or newer, installed through [uv](https://docs.astral.sh/uv/):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install . # from a checkout
|
|
50
|
+
uv tool install git+https://github.com/whit3rabbit/memoryfield
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The first search downloads the embedding model (default
|
|
54
|
+
`snowflake-arctic-embed-xs`, 384-d, about 170 MB). The model is pinned
|
|
55
|
+
per field at `mf init`. Alternatives, and when to pick one:
|
|
56
|
+
[docs/models.md](docs/models.md).
|
|
57
|
+
|
|
58
|
+
`mf mcp` (an MCP server for `search`/`read`/`write`/`raw_add`) needs an
|
|
59
|
+
extra: `uv tool install ".[mcp]"`. It's optional because most usage is
|
|
60
|
+
the CLI directly or the Claude Code skill, and the MCP stack (roughly
|
|
61
|
+
a dozen extra packages) isn't worth pulling in for those.
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
Using this repo's eval corpus as sample pages:
|
|
66
|
+
|
|
67
|
+
```console
|
|
68
|
+
$ mf init ~/field
|
|
69
|
+
Initialized empty field at ~/field/mf.sqlite3 (model snowflake-arctic-embed-xs, 384-d)
|
|
70
|
+
|
|
71
|
+
$ cp eval/corpus/codebase/*.md ~/field/ && mf index ~/field
|
|
72
|
+
75 upserted, 0 unchanged, 0 deleted
|
|
73
|
+
|
|
74
|
+
$ mf search "how do we roll back a deploy" --field ~/field
|
|
75
|
+
confidence: low
|
|
76
|
+
- [code-deploy-rollback-cmd] Deploy: how to roll back a bad release
|
|
77
|
+
`kubectl rollout undo deployment/<service>`; rollback is a forward operation and takes ~90 seconds end-to-end.
|
|
78
|
+
- [code-deploy-pre-checklist] Deploy: pre-deploy checklist
|
|
79
|
+
Tests green, migrations applied to staging, dashboards reviewed, on-call notified, rollback plan documented.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Read the confidence line before the results:
|
|
83
|
+
|
|
84
|
+
- `high`: the stub is safe to cite.
|
|
85
|
+
- `low`: a strong lead. `mf read <uuid>` for the page's answer section
|
|
86
|
+
before quoting it.
|
|
87
|
+
- `none`: do not cite it.
|
|
88
|
+
|
|
89
|
+
The gate errs toward demotion, not overclaiming.
|
|
90
|
+
|
|
91
|
+
When the stub is not enough, `mf read <uuid>` returns the page's answer
|
|
92
|
+
section, and `--tier L2` or `<uuid>#section` returns more.
|
|
93
|
+
|
|
94
|
+
New pages go
|
|
95
|
+
in through `mf write <draft> --field <dir>`, drafted outside the field.
|
|
96
|
+
Exit 2 means a near-duplicate was flagged. The calling contract an
|
|
97
|
+
agent should follow is in [docs/agents.md](docs/agents.md), and every
|
|
98
|
+
flag is in [docs/CLI.md](docs/CLI.md#mf-write).
|
|
99
|
+
|
|
100
|
+
## Design decisions
|
|
101
|
+
|
|
102
|
+
Each choice below was measured on a 157-page corpus, blind phrasing
|
|
103
|
+
sets, and one field this project did not write. The numbers live
|
|
104
|
+
behind the links, not here, so they cannot drift.
|
|
105
|
+
|
|
106
|
+
- **Dense-first ranking.** The vector index ranks. FTS runs on every
|
|
107
|
+
query as a gate signal and a fallback, never as the primary ranker,
|
|
108
|
+
because fusing the two averaged keyword noise into good semantic
|
|
109
|
+
rankings. [Benchmarks, section 2](docs/BENCHMARKS.md#2-ranking-architecture-benchmarks)
|
|
110
|
+
- **A three-signal confidence gate.** A BM25 floor alone demoted nearly
|
|
111
|
+
half of the answerable blind queries and collapsed on small fields.
|
|
112
|
+
The gate now combines a dense distance floor, the BM25 score, and
|
|
113
|
+
top-1 agreement. [Benchmarks, section 3](docs/BENCHMARKS.md#3-confidence-gate-benchmarks)
|
|
114
|
+
- **Lean stubs by default.** Two stubs and no neighbors, because the
|
|
115
|
+
original five stubs and three neighbors cost more tokens than
|
|
116
|
+
exploring raw files did. [Benchmarks, section 4](docs/BENCHMARKS.md#4-token-cost-benchmarks)
|
|
117
|
+
- **A write-time dedup gate.** Cosine distance on title, summary, and
|
|
118
|
+
first section, with the threshold set on a labeled paraphrase set.
|
|
119
|
+
It catches copies and light rewordings, not thorough rewrites.
|
|
120
|
+
[Architecture, section 5](docs/architecture.md#5-write)
|
|
121
|
+
- **A small default embedder, pinned per field.** A 384-d model that
|
|
122
|
+
matched the larger ones on blind accuracy at a fraction of the load
|
|
123
|
+
time and storage. [docs/models.md](docs/models.md)
|
|
124
|
+
- **No LLM and no reranker inside the tool.** The host agent already in
|
|
125
|
+
context does extraction and judgment. mf stays deterministic, local,
|
|
126
|
+
and sub-second. [Architecture, "Stack"](docs/architecture.md#stack)
|
|
127
|
+
|
|
128
|
+
## Using it with an agent
|
|
129
|
+
|
|
130
|
+
A Claude Code skill that teaches the lean calls, the confidence
|
|
131
|
+
contract, and the write path ships in
|
|
132
|
+
[.claude/skills/mf](.claude/skills/mf). Copy it into your project's
|
|
133
|
+
`.claude/skills/` to use mf there.
|
|
134
|
+
|
|
135
|
+
Two hooks, `mf hook stop` and `mf hook session-end`, ask the agent to
|
|
136
|
+
capture what it learned before it finishes and stage a transcript
|
|
137
|
+
pointer for later consolidation. Setup, the hooks snippet, and the
|
|
138
|
+
calling contract: [docs/agents.md](docs/agents.md).
|
|
139
|
+
|
|
140
|
+
## Commands
|
|
141
|
+
|
|
142
|
+
Full arguments, flags, exit codes, and JSON outputs are documented in
|
|
143
|
+
[docs/CLI.md](docs/CLI.md).
|
|
144
|
+
|
|
145
|
+
| Command | What it does |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `mf init [DIR]` | create `mf.sqlite3` in a field, pinning model and dimension |
|
|
148
|
+
| `mf index [DIR]` | scan the field's pages into the index |
|
|
149
|
+
| `mf search "<query>"` | stub-first lookup with the confidence gate |
|
|
150
|
+
| `mf read <uuid>[#section] ...` | read the answer section, one section, or L2 |
|
|
151
|
+
| `mf write <draft>` | validate, dedup-check, copy in, and index a draft |
|
|
152
|
+
| `mf raw add` | stage a freeform session extract under `raw/` |
|
|
153
|
+
| `mf lint [DIR]` | check writing conventions and index drift, `--check` for CI |
|
|
154
|
+
| `mf pack` / `mf unpack` | reproducible archive plus sha256 sidecar, verified extraction, `--spec` for other memoryfield readers |
|
|
155
|
+
| `mf import claude-memory <dir>` | turn a Claude Code memory directory into pages |
|
|
156
|
+
| `mf import wiki <dir>` | turn an index.md-style wiki into pages |
|
|
157
|
+
| `mf hook stop` / `mf hook session-end` | Claude Code hook handlers |
|
|
158
|
+
| `mf model list` | list available embedding models, dimensions, speeds, and cache status |
|
|
159
|
+
| `mf model install <name>` | download and cache an embedding model ahead of time |
|
|
160
|
+
| `mf claim <slug> --by <writer>` | atomically claim a slug before creating a page (multi-writer) |
|
|
161
|
+
| `mf consolidate --plan` | propose create/review actions from `raw/` entries |
|
|
162
|
+
| `mf mcp` | run an MCP server exposing `search`/`read`/`write`/`raw_add` over stdio |
|
|
163
|
+
|
|
164
|
+
## Documentation
|
|
165
|
+
|
|
166
|
+
| Guide | What you can do |
|
|
167
|
+
|---|---|
|
|
168
|
+
| [Agents](docs/agents.md) | Wire mf into Claude Code: the skill, the hooks, and the lean-call contract. |
|
|
169
|
+
| [CLI reference](docs/CLI.md) | Look up every flag, exit code, and JSON shape. |
|
|
170
|
+
| [Models](docs/models.md) | Pick, pin, and pre-download an embedding model. |
|
|
171
|
+
| [Fields](docs/fields.md) | Write pages, lint, wire git hooks, import notes, and exchange fields with other memoryfield tools. |
|
|
172
|
+
| [Architecture](docs/architecture.md) | See the schema, how a search is ranked and gated, and the record of each decision. |
|
|
173
|
+
| [Benchmarks](docs/BENCHMARKS.md) | Read the numbers behind the design decisions. |
|
|
174
|
+
| [Docs index](docs/README.md) | Start from a task and find the right guide. |
|
|
175
|
+
|
|
176
|
+
## Eval harness
|
|
177
|
+
|
|
178
|
+
The repo ships a 157-page labeled corpus, a 458-query set plus blind
|
|
179
|
+
vocabulary-mismatch sets, and six baselines (grep, FTS5, TF-IDF, nomic,
|
|
180
|
+
BGE-large, and hybrid).
|
|
181
|
+
|
|
182
|
+
The in-vocabulary scores sit near ceiling
|
|
183
|
+
because the queries share an authoring process with the corpus. Read
|
|
184
|
+
[docs/M0.5_REPORT.md](docs/M0.5_REPORT.md) with that in mind, and
|
|
185
|
+
[docs/BENCHMARKS.md](docs/BENCHMARKS.md) section 5 for the soapstones
|
|
186
|
+
field, the first corpus outside that process.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
uv sync --extra eval # fastembed into a local venv
|
|
190
|
+
uv sync --extra eval --extra mlx # optional, Apple Silicon MLX variants
|
|
191
|
+
uv run python3 -m eval.run_baselines # 45+ minutes wall time
|
|
192
|
+
uv run python3 -m eval.report # render the report
|
|
193
|
+
uv run python3 eval/fetch_soapstones.py # pinned foreign-field fixture
|
|
194
|
+
uv run python3 -m eval.calibrate_confidence_blind soapstones # ranking and gate on it
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
uv sync --extra eval --group dev
|
|
201
|
+
uv run pytest tests/
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`uv sync` calls do not compose: each one resets the venv to exactly
|
|
205
|
+
what that call specifies. Pass `--extra eval` and `--group dev`
|
|
206
|
+
together, in one invocation.
|
|
207
|
+
|
|
208
|
+
## Status
|
|
209
|
+
|
|
210
|
+
Read path, write path, and hooks/imports are built and tested. In
|
|
211
|
+
progress: multi-writer support (`mf claim`, `mf consolidate --plan`).
|
|
212
|
+
The per-item record of what was built, measured, and changed is in
|
|
213
|
+
[ROADMAP.md](ROADMAP.md). CLAUDE.md is the map for anyone working in
|
|
214
|
+
the repo.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""`mf claim` -- atomic conditional insert into the `claims` table.
|
|
2
|
+
|
|
3
|
+
Per PLAN.md's "Write" layer and ROADMAP.md 4.3: two writers racing to
|
|
4
|
+
create a page for the same topic should degrade to one create and one
|
|
5
|
+
update, not two pages. `claim slug` is the primitive that makes that
|
|
6
|
+
possible without a coordinator -- whichever writer's INSERT lands first
|
|
7
|
+
wins the slug, and the loser gets back the winner's identity so it can
|
|
8
|
+
look up the resulting page and `write --update` it instead.
|
|
9
|
+
|
|
10
|
+
Slug is the filename stem (ROADMAP.md 4.3's proposal, decided here):
|
|
11
|
+
that's the thing two writers actually collide on when they both title a
|
|
12
|
+
new page for the same topic without seeing each other's draft. See
|
|
13
|
+
mf.page.Page.slug.
|
|
14
|
+
|
|
15
|
+
SQLite serializes concurrent writers at the file level, so the
|
|
16
|
+
INSERT ... ON CONFLICT DO NOTHING below is atomic across processes, not
|
|
17
|
+
just within one connection: a second process's INSERT blocks until the
|
|
18
|
+
first commits, then sees the row already there.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from datetime import UTC, datetime
|
|
24
|
+
from sqlite3 import Connection
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ClaimResult:
|
|
29
|
+
slug: str
|
|
30
|
+
claimed: bool
|
|
31
|
+
claimed_by: str
|
|
32
|
+
claimed_at: str
|
|
33
|
+
|
|
34
|
+
def as_dict(self) -> dict:
|
|
35
|
+
return {
|
|
36
|
+
"slug": self.slug,
|
|
37
|
+
"claimed": self.claimed,
|
|
38
|
+
"claimed_by": self.claimed_by,
|
|
39
|
+
"claimed_at": self.claimed_at,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def claim_slug(conn: Connection, slug: str, claimed_by: str) -> ClaimResult:
|
|
44
|
+
"""Try to claim `slug` for `claimed_by`. Returns claimed=True if this
|
|
45
|
+
call now holds it (either it won the race, or it already held it --
|
|
46
|
+
re-claiming your own slug is a no-op, not an error). claimed=False
|
|
47
|
+
means someone else got there first; `claimed_by`/`claimed_at` name
|
|
48
|
+
who and when, so the caller can look that page up and update it
|
|
49
|
+
instead of creating a duplicate.
|
|
50
|
+
"""
|
|
51
|
+
now = datetime.now(UTC).isoformat()
|
|
52
|
+
conn.execute(
|
|
53
|
+
"INSERT INTO claims (slug, claimed_by, claimed_at) VALUES (?, ?, ?) "
|
|
54
|
+
"ON CONFLICT(slug) DO NOTHING",
|
|
55
|
+
(slug, claimed_by, now),
|
|
56
|
+
)
|
|
57
|
+
conn.commit()
|
|
58
|
+
row = conn.execute(
|
|
59
|
+
"SELECT claimed_by, claimed_at FROM claims WHERE slug = ?", (slug,)
|
|
60
|
+
).fetchone()
|
|
61
|
+
assert row is not None, "just-inserted-or-existing row must be there"
|
|
62
|
+
return ClaimResult(slug=slug, claimed=(row[0] == claimed_by), claimed_by=row[0], claimed_at=row[1])
|