memd 0.3.3__tar.gz → 0.4.2__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.
- {memd-0.3.3 → memd-0.4.2}/PKG-INFO +20 -16
- {memd-0.3.3 → memd-0.4.2}/README.md +19 -15
- {memd-0.3.3 → memd-0.4.2}/memd/__init__.py +8 -2
- {memd-0.3.3 → memd-0.4.2}/memd/cli.py +8 -0
- {memd-0.3.3 → memd-0.4.2}/memd/config.py +11 -2
- memd-0.4.2/memd/curator.py +554 -0
- {memd-0.3.3 → memd-0.4.2}/memd/digest.py +291 -22
- {memd-0.3.3 → memd-0.4.2}/memd/inbox.py +33 -3
- {memd-0.3.3 → memd-0.4.2}/memd/memory.py +75 -22
- {memd-0.3.3 → memd-0.4.2}/memd/sync.py +116 -32
- {memd-0.3.3 → memd-0.4.2}/memd.egg-info/PKG-INFO +20 -16
- {memd-0.3.3 → memd-0.4.2}/memd.egg-info/SOURCES.txt +1 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_antigravity.py +11 -7
- {memd-0.3.3 → memd-0.4.2}/tests/test_brief.py +120 -2
- {memd-0.3.3 → memd-0.4.2}/tests/test_config.py +1 -1
- memd-0.4.2/tests/test_curation_quality.py +275 -0
- memd-0.4.2/tests/test_curator.py +458 -0
- memd-0.4.2/tests/test_digest.py +314 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_inbox.py +59 -3
- memd-0.4.2/tests/test_ingest_health.py +192 -0
- memd-0.4.2/tests/test_model_escalation.py +81 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_sync_e2e.py +72 -0
- memd-0.3.3/memd/curator.py +0 -264
- memd-0.3.3/tests/test_curation_quality.py +0 -184
- memd-0.3.3/tests/test_curator.py +0 -150
- memd-0.3.3/tests/test_digest.py +0 -99
- memd-0.3.3/tests/test_ingest_health.py +0 -118
- {memd-0.3.3 → memd-0.4.2}/LICENSE +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd/errors.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd/hooks.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd/sweep.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd.egg-info/dependency_links.txt +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd.egg-info/entry_points.txt +0 -0
- {memd-0.3.3 → memd-0.4.2}/memd.egg-info/top_level.txt +0 -0
- {memd-0.3.3 → memd-0.4.2}/pyproject.toml +0 -0
- {memd-0.3.3 → memd-0.4.2}/setup.cfg +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_core.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_cursors.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_memory_repo.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_redact.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_sweep.py +0 -0
- {memd-0.3.3 → memd-0.4.2}/tests/test_validate_budget.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Agent-driven project-memory curator
|
|
5
5
|
Author: lowcache
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,8 +31,9 @@ Dynamic: license-file
|
|
|
31
31
|
<img src="https://raw.githubusercontent.com/lowcache/memd/main/assets/memd.png" alt="memd logo" />
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
|
-
[](https://github.com/lowcache/memd/actions/workflows/ci.yml)
|
|
35
|
+
[](https://pypi.org/project/memd/)
|
|
36
|
+
[](https://pypi.org/project/memd/)
|
|
36
37
|
[](LICENSE)
|
|
37
38
|
[](#nix-installation)
|
|
38
39
|
|
|
@@ -54,7 +55,7 @@ Every AI coding session ends the same way: the context window fills up, the sess
|
|
|
54
55
|
|
|
55
56
|
## How It Works
|
|
56
57
|
|
|
57
|
-
Sessions end (or a timer fires), memd collects whatever is new — transcripts, databases, inbox notes — scrubs it for secrets, and hands it to a small "curator" model that
|
|
58
|
+
Sessions end (or a timer fires), memd collects whatever is new — transcripts, databases, inbox notes — scrubs it for secrets, and hands it to a small "curator" model that applies surgical section-level updates to the memory files. Python code, not the model, gets the final say on what's allowed to change.
|
|
58
59
|
|
|
59
60
|
```mermaid
|
|
60
61
|
graph TD
|
|
@@ -123,9 +124,9 @@ For two conventions memd doesn't write — GitHub Copilot's `.github/copilot-ins
|
|
|
123
124
|
|
|
124
125
|
The core of memd doesn't care which agent or model you use: the memory files are plain markdown, the inbox accepts notes from anything that can write a file, `memd brief` prints to stdout for any tool to consume, and the `curator_cmd` config key lets any LLM command do the distilling. The parts that are currently tool-specific:
|
|
125
126
|
|
|
126
|
-
* **Automatic transcript ingestion** understands `claude-code` session logs and `antigravity-cli` databases
|
|
127
|
+
* **Automatic transcript ingestion** understands `claude-code` session logs and `antigravity-cli` databases natively, and digests *any other* CLI's session logs via content sniffing and a best-effort text fallback (common `user:`/`assistant:` markers become turns) — point `extra_sources` at whatever a tool writes, or route facts through the inbox instead.
|
|
127
128
|
* **Lifecycle hooks** (`memd install-hooks`, session-start brief injection) exist only for `claude-code`. Every other tool relies on the periodic sweep timer instead: slower cadence, same result.
|
|
128
|
-
* **The default curator is the `claude` CLI**, and the default `model_small`/`model_large` names (`
|
|
129
|
+
* **The default curator is the `claude` CLI**, and the default `model_small`/`model_large` names (both `sonnet`) assume it. Set `curator_cmd` to distill with a local model, another vendor's CLI, or anything else — [`contrib/curators/`](contrib/curators/) has ready-made wrappers and the backend contract, so you can run memd with no Anthropic dependency at all.
|
|
129
130
|
|
|
130
131
|
---
|
|
131
132
|
|
|
@@ -181,20 +182,22 @@ For complete writer/reader guidelines, see [INBOX-PROTOCOL.md](INBOX-PROTOCOL.md
|
|
|
181
182
|
|
|
182
183
|
Distilling means sending session content to an LLM, so it's worth being precise about what goes where and what it costs.
|
|
183
184
|
|
|
184
|
-
**What gets sent.** Whatever is new in your transcripts since the last run, capped at `digest_cap_chars` (60,000 by default), plus the current
|
|
185
|
+
**What gets sent.** Whatever is new in your transcripts since the last run — as chronologically ordered, timestamped session spans — capped at `digest_cap_chars` (60,000 by default), plus the current memory files (`mistakes.md` only as a heading index; it is append-only). Credentials are scrubbed first — 13 patterns, [detailed below](#built-in-credential-redaction) — but that's shape-matching, not a guarantee.
|
|
185
186
|
|
|
186
187
|
**Where it goes.** To whatever `curator_cmd` points at. Out of the box that's the `claude` CLI, which means your session content reaches Anthropic's API. Point it at a local model and nothing leaves the machine; [`contrib/curators/`](contrib/curators/) has a verified ollama wrapper and the backend contract. `memd status` always prints the backend in use, so you never have to infer it:
|
|
187
188
|
|
|
188
189
|
```
|
|
189
190
|
$ memd status
|
|
190
|
-
curator : claude (headless) [models:
|
|
191
|
+
curator : claude (headless) [models: sonnet/sonnet] — sends session content to the Anthropic API
|
|
191
192
|
```
|
|
192
193
|
|
|
193
|
-
**What it costs.** A distill runs when a session ends, and the sweep timer checks every 30 minutes for projects with new content. Most sweeps do nothing — a project is skipped unless it has unread transcript content or an inbox note, and transcripts touched in the last `quiet_seconds` (10 minutes) are left alone so an active session isn't digested mid-flight. When a distill does run it's one call, on `model_small`
|
|
194
|
+
**What it costs.** A distill runs when a session ends, and the sweep timer checks every 30 minutes for projects with new content. Most sweeps do nothing — a project is skipped unless it has unread transcript content or an inbox note, and transcripts touched in the last `quiet_seconds` (10 minutes) are left alone so an active session isn't digested mid-flight. When a distill does run it's one call, on `model_small` unless the digest exceeds `escalate_chars` (15,000), which promotes it to `model_large`.
|
|
195
|
+
|
|
196
|
+
Both default to `sonnet`, which is a deliberate choice: curation quality *is* the product, and a weak default model is indistinguishable from a weak tool to anyone trying memd for the first time. It costs more than the cheapest option. To spend less, set `model_small` to `haiku` — routine distills drop to it while large backlogs still escalate. To spend nothing, use a local backend.
|
|
194
197
|
|
|
195
198
|
**What memd never does.** No network calls of its own, no telemetry, no phone-home. It reads session transcripts, inbox notes, and any `extra_sources` you configure — not your source code. It never executes anything from a transcript or from curator output. See [SECURITY.md](SECURITY.md).
|
|
196
199
|
|
|
197
|
-
**When ingestion breaks, it says so.** memd reads formats that belong to other tools — `claude-code`'s session logs, `antigravity-cli`'s databases — and those can change without notice. Parsing is deliberately forgiving, so a change doesn't crash anything;
|
|
200
|
+
**When ingestion breaks, it says so.** memd reads formats that belong to other tools — `claude-code`'s session logs, `antigravity-cli`'s databases — and those can change without notice. Parsing is deliberately forgiving, so a change doesn't crash anything; unknown third-party formats simply degrade to the text fallback and keep working. The surviving blindspot is claude-shaped JSONL whose entries stop yielding lines: session content read, nothing usable extracted. `memd status` reports exactly that instead of leaving you to notice months later that memory went stale.
|
|
198
201
|
|
|
199
202
|
```
|
|
200
203
|
! ingestion : 9720 bytes read since 2026-07-30T06:11:27, nothing usable extracted.
|
|
@@ -205,7 +208,7 @@ curator : claude (headless) [models: haiku/sonnet] — sends session cont
|
|
|
205
208
|
Nothing is lost when this happens: read cursors only advance after a successful distill, so the unread content simply replays once parsing works again.
|
|
206
209
|
|
|
207
210
|
### 4. Extra Custom Sources
|
|
208
|
-
Additional
|
|
211
|
+
Additional transcript sources (any text or JSONL log another CLI writes — role tags like `user:`/`assistant:` are detected heuristically) can be registered per-project under the `projects.<path>.extra_sources` array in `config.json`.
|
|
209
212
|
|
|
210
213
|
---
|
|
211
214
|
|
|
@@ -220,10 +223,11 @@ Configuration is stored in `$XDG_CONFIG_HOME/memd/config.json` (defaults to `~/.
|
|
|
220
223
|
| `claude_bin` | `string` | `"claude"` | Name or path of the `claude-code` binary. |
|
|
221
224
|
| `curator_cmd` | `array` | `[]` | Override list of arguments to invoke a custom curator backend (e.g., `["nix", "run", ".#", "--", "status"]`). Substitutes `{model}`. If empty, falls back to the headless `claude_bin` command. |
|
|
222
225
|
| `antigravity_dir` | `string` | `"~/.gemini/antigravity-cli"` | Directory containing native SQLite database conversations. |
|
|
223
|
-
| `model_small` | `string` | `"
|
|
224
|
-
| `model_large` | `string` | `"sonnet"` | Model used
|
|
225
|
-
| `escalate_chars` | `integer` | `15000` |
|
|
226
|
-
| `digest_cap_chars` | `integer` | `60000` | Maximum length of transcript digest fed to the LLM. |
|
|
226
|
+
| `model_small` | `string` | `"sonnet"` | Model used for routine distills. Set to `"haiku"` to cut cost. |
|
|
227
|
+
| `model_large` | `string` | `"sonnet"` | Model used once a digest exceeds `escalate_chars`. |
|
|
228
|
+
| `escalate_chars` | `integer` | `15000` | Digest size above which `model_large` is used, whatever triggered the distill. |
|
|
229
|
+
| `digest_cap_chars` | `integer` | `60000` | Maximum length of transcript digest fed to the LLM. Oversize digests drop whole low-signal sessions (named in an omission marker) rather than truncating mid-story. |
|
|
230
|
+
| `capture_sidechains` | `boolean` | `true` | Include subagent ("sidechain") work from transcripts as indented secondary-signal lines. |
|
|
227
231
|
| `quiet_seconds` | `integer` | `600` | Skip digesting transcripts modified within this cooldown period. |
|
|
228
232
|
| `sweep_jobs` | `integer` | `4` | Number of worker threads for parallel sweeps. |
|
|
229
233
|
| `auto_scaffold` | `boolean` | `true` | Auto-create `.memory/` structure inside detected git repositories. |
|
|
@@ -395,7 +399,7 @@ Symlink the shim onto your `PATH` for a live-updating install: `ln -s "$PWD/memd
|
|
|
395
399
|
|
|
396
400
|
## Testing & Verification
|
|
397
401
|
|
|
398
|
-
|
|
402
|
+
184 automated tests cover the parts that would hurt if they broke: XDG isolation, cursors, redaction, database parsing, atomic inbox publishing (including an 8-writer concurrency stress test), retry policies, curator output parsing (including the malformed JSON small models produce), brief budgeting, memory-repo decoupling + worktree sharing, silent-ingestion-breakage detection, and curation quality. Verified on Python 3.10 through 3.14.
|
|
399
403
|
|
|
400
404
|
To run tests in a Nix-isolated environment:
|
|
401
405
|
```bash
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
<img src="https://raw.githubusercontent.com/lowcache/memd/main/assets/memd.png" alt="memd logo" />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
[](https://github.com/lowcache/memd/actions/workflows/ci.yml)
|
|
8
|
+
[](https://pypi.org/project/memd/)
|
|
9
|
+
[](https://pypi.org/project/memd/)
|
|
9
10
|
[](LICENSE)
|
|
10
11
|
[](#nix-installation)
|
|
11
12
|
|
|
@@ -27,7 +28,7 @@ Every AI coding session ends the same way: the context window fills up, the sess
|
|
|
27
28
|
|
|
28
29
|
## How It Works
|
|
29
30
|
|
|
30
|
-
Sessions end (or a timer fires), memd collects whatever is new — transcripts, databases, inbox notes — scrubs it for secrets, and hands it to a small "curator" model that
|
|
31
|
+
Sessions end (or a timer fires), memd collects whatever is new — transcripts, databases, inbox notes — scrubs it for secrets, and hands it to a small "curator" model that applies surgical section-level updates to the memory files. Python code, not the model, gets the final say on what's allowed to change.
|
|
31
32
|
|
|
32
33
|
```mermaid
|
|
33
34
|
graph TD
|
|
@@ -96,9 +97,9 @@ For two conventions memd doesn't write — GitHub Copilot's `.github/copilot-ins
|
|
|
96
97
|
|
|
97
98
|
The core of memd doesn't care which agent or model you use: the memory files are plain markdown, the inbox accepts notes from anything that can write a file, `memd brief` prints to stdout for any tool to consume, and the `curator_cmd` config key lets any LLM command do the distilling. The parts that are currently tool-specific:
|
|
98
99
|
|
|
99
|
-
* **Automatic transcript ingestion** understands `claude-code` session logs and `antigravity-cli` databases
|
|
100
|
+
* **Automatic transcript ingestion** understands `claude-code` session logs and `antigravity-cli` databases natively, and digests *any other* CLI's session logs via content sniffing and a best-effort text fallback (common `user:`/`assistant:` markers become turns) — point `extra_sources` at whatever a tool writes, or route facts through the inbox instead.
|
|
100
101
|
* **Lifecycle hooks** (`memd install-hooks`, session-start brief injection) exist only for `claude-code`. Every other tool relies on the periodic sweep timer instead: slower cadence, same result.
|
|
101
|
-
* **The default curator is the `claude` CLI**, and the default `model_small`/`model_large` names (`
|
|
102
|
+
* **The default curator is the `claude` CLI**, and the default `model_small`/`model_large` names (both `sonnet`) assume it. Set `curator_cmd` to distill with a local model, another vendor's CLI, or anything else — [`contrib/curators/`](contrib/curators/) has ready-made wrappers and the backend contract, so you can run memd with no Anthropic dependency at all.
|
|
102
103
|
|
|
103
104
|
---
|
|
104
105
|
|
|
@@ -154,20 +155,22 @@ For complete writer/reader guidelines, see [INBOX-PROTOCOL.md](INBOX-PROTOCOL.md
|
|
|
154
155
|
|
|
155
156
|
Distilling means sending session content to an LLM, so it's worth being precise about what goes where and what it costs.
|
|
156
157
|
|
|
157
|
-
**What gets sent.** Whatever is new in your transcripts since the last run, capped at `digest_cap_chars` (60,000 by default), plus the current
|
|
158
|
+
**What gets sent.** Whatever is new in your transcripts since the last run — as chronologically ordered, timestamped session spans — capped at `digest_cap_chars` (60,000 by default), plus the current memory files (`mistakes.md` only as a heading index; it is append-only). Credentials are scrubbed first — 13 patterns, [detailed below](#built-in-credential-redaction) — but that's shape-matching, not a guarantee.
|
|
158
159
|
|
|
159
160
|
**Where it goes.** To whatever `curator_cmd` points at. Out of the box that's the `claude` CLI, which means your session content reaches Anthropic's API. Point it at a local model and nothing leaves the machine; [`contrib/curators/`](contrib/curators/) has a verified ollama wrapper and the backend contract. `memd status` always prints the backend in use, so you never have to infer it:
|
|
160
161
|
|
|
161
162
|
```
|
|
162
163
|
$ memd status
|
|
163
|
-
curator : claude (headless) [models:
|
|
164
|
+
curator : claude (headless) [models: sonnet/sonnet] — sends session content to the Anthropic API
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
**What it costs.** A distill runs when a session ends, and the sweep timer checks every 30 minutes for projects with new content. Most sweeps do nothing — a project is skipped unless it has unread transcript content or an inbox note, and transcripts touched in the last `quiet_seconds` (10 minutes) are left alone so an active session isn't digested mid-flight. When a distill does run it's one call, on `model_small`
|
|
167
|
+
**What it costs.** A distill runs when a session ends, and the sweep timer checks every 30 minutes for projects with new content. Most sweeps do nothing — a project is skipped unless it has unread transcript content or an inbox note, and transcripts touched in the last `quiet_seconds` (10 minutes) are left alone so an active session isn't digested mid-flight. When a distill does run it's one call, on `model_small` unless the digest exceeds `escalate_chars` (15,000), which promotes it to `model_large`.
|
|
168
|
+
|
|
169
|
+
Both default to `sonnet`, which is a deliberate choice: curation quality *is* the product, and a weak default model is indistinguishable from a weak tool to anyone trying memd for the first time. It costs more than the cheapest option. To spend less, set `model_small` to `haiku` — routine distills drop to it while large backlogs still escalate. To spend nothing, use a local backend.
|
|
167
170
|
|
|
168
171
|
**What memd never does.** No network calls of its own, no telemetry, no phone-home. It reads session transcripts, inbox notes, and any `extra_sources` you configure — not your source code. It never executes anything from a transcript or from curator output. See [SECURITY.md](SECURITY.md).
|
|
169
172
|
|
|
170
|
-
**When ingestion breaks, it says so.** memd reads formats that belong to other tools — `claude-code`'s session logs, `antigravity-cli`'s databases — and those can change without notice. Parsing is deliberately forgiving, so a change doesn't crash anything;
|
|
173
|
+
**When ingestion breaks, it says so.** memd reads formats that belong to other tools — `claude-code`'s session logs, `antigravity-cli`'s databases — and those can change without notice. Parsing is deliberately forgiving, so a change doesn't crash anything; unknown third-party formats simply degrade to the text fallback and keep working. The surviving blindspot is claude-shaped JSONL whose entries stop yielding lines: session content read, nothing usable extracted. `memd status` reports exactly that instead of leaving you to notice months later that memory went stale.
|
|
171
174
|
|
|
172
175
|
```
|
|
173
176
|
! ingestion : 9720 bytes read since 2026-07-30T06:11:27, nothing usable extracted.
|
|
@@ -178,7 +181,7 @@ curator : claude (headless) [models: haiku/sonnet] — sends session cont
|
|
|
178
181
|
Nothing is lost when this happens: read cursors only advance after a successful distill, so the unread content simply replays once parsing works again.
|
|
179
182
|
|
|
180
183
|
### 4. Extra Custom Sources
|
|
181
|
-
Additional
|
|
184
|
+
Additional transcript sources (any text or JSONL log another CLI writes — role tags like `user:`/`assistant:` are detected heuristically) can be registered per-project under the `projects.<path>.extra_sources` array in `config.json`.
|
|
182
185
|
|
|
183
186
|
---
|
|
184
187
|
|
|
@@ -193,10 +196,11 @@ Configuration is stored in `$XDG_CONFIG_HOME/memd/config.json` (defaults to `~/.
|
|
|
193
196
|
| `claude_bin` | `string` | `"claude"` | Name or path of the `claude-code` binary. |
|
|
194
197
|
| `curator_cmd` | `array` | `[]` | Override list of arguments to invoke a custom curator backend (e.g., `["nix", "run", ".#", "--", "status"]`). Substitutes `{model}`. If empty, falls back to the headless `claude_bin` command. |
|
|
195
198
|
| `antigravity_dir` | `string` | `"~/.gemini/antigravity-cli"` | Directory containing native SQLite database conversations. |
|
|
196
|
-
| `model_small` | `string` | `"
|
|
197
|
-
| `model_large` | `string` | `"sonnet"` | Model used
|
|
198
|
-
| `escalate_chars` | `integer` | `15000` |
|
|
199
|
-
| `digest_cap_chars` | `integer` | `60000` | Maximum length of transcript digest fed to the LLM. |
|
|
199
|
+
| `model_small` | `string` | `"sonnet"` | Model used for routine distills. Set to `"haiku"` to cut cost. |
|
|
200
|
+
| `model_large` | `string` | `"sonnet"` | Model used once a digest exceeds `escalate_chars`. |
|
|
201
|
+
| `escalate_chars` | `integer` | `15000` | Digest size above which `model_large` is used, whatever triggered the distill. |
|
|
202
|
+
| `digest_cap_chars` | `integer` | `60000` | Maximum length of transcript digest fed to the LLM. Oversize digests drop whole low-signal sessions (named in an omission marker) rather than truncating mid-story. |
|
|
203
|
+
| `capture_sidechains` | `boolean` | `true` | Include subagent ("sidechain") work from transcripts as indented secondary-signal lines. |
|
|
200
204
|
| `quiet_seconds` | `integer` | `600` | Skip digesting transcripts modified within this cooldown period. |
|
|
201
205
|
| `sweep_jobs` | `integer` | `4` | Number of worker threads for parallel sweeps. |
|
|
202
206
|
| `auto_scaffold` | `boolean` | `true` | Auto-create `.memory/` structure inside detected git repositories. |
|
|
@@ -368,7 +372,7 @@ Symlink the shim onto your `PATH` for a live-updating install: `ln -s "$PWD/memd
|
|
|
368
372
|
|
|
369
373
|
## Testing & Verification
|
|
370
374
|
|
|
371
|
-
|
|
375
|
+
184 automated tests cover the parts that would hurt if they broke: XDG isolation, cursors, redaction, database parsing, atomic inbox publishing (including an 8-writer concurrency stress test), retry policies, curator output parsing (including the malformed JSON small models produce), brief budgeting, memory-repo decoupling + worktree sharing, silent-ingestion-breakage detection, and curation quality. Verified on Python 3.10 through 3.14.
|
|
372
376
|
|
|
373
377
|
To run tests in a Nix-isolated environment:
|
|
374
378
|
```bash
|
|
@@ -15,7 +15,7 @@ the invariants the model must not be trusted with: frontmatter, append-only
|
|
|
15
15
|
mistakes.md, shrink guard, size budgets, archive overflow, git audit commits.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "0.
|
|
18
|
+
__version__ = "0.4.2"
|
|
19
19
|
|
|
20
20
|
from memd.errors import ConfigError, CuratorError, DigestError, MemdError
|
|
21
21
|
from memd.config import (
|
|
@@ -87,11 +87,17 @@ from memd.digest import (
|
|
|
87
87
|
digest_ag_db,
|
|
88
88
|
digest_jsonl,
|
|
89
89
|
digest_source,
|
|
90
|
+
digest_text_log,
|
|
90
91
|
project_ag_dbs,
|
|
91
92
|
redact,
|
|
93
|
+
render_span,
|
|
94
|
+
score_span,
|
|
95
|
+
sniff_and_digest,
|
|
96
|
+
sort_spans,
|
|
97
|
+
truncate_spans,
|
|
92
98
|
)
|
|
93
99
|
from memd.inbox import collect_inbox, write_inbox_note
|
|
94
|
-
from memd.curator import CHARTER, build_prompt, call_curator, validate
|
|
100
|
+
from memd.curator import CHARTER, apply_ops, build_prompt, call_curator, validate
|
|
95
101
|
from memd.sync import (
|
|
96
102
|
INGEST_STALL_BYTES,
|
|
97
103
|
encode_claude_dir,
|
|
@@ -54,6 +54,14 @@ def cmd_status(cfg):
|
|
|
54
54
|
print(f" last distill : {m.get('last_sync', 'never')}{detail}")
|
|
55
55
|
if m.get("summary"):
|
|
56
56
|
print(f" summary : {m['summary']}")
|
|
57
|
+
# rolling journal: recent distill events make gaps visible (a project
|
|
58
|
+
# with pending sources but only failed outcomes is quietly blind)
|
|
59
|
+
hist = [h for h in m.get("history", []) if isinstance(h, dict)]
|
|
60
|
+
for h in hist[-2:]:
|
|
61
|
+
outcome = h.get("outcome", "?")
|
|
62
|
+
mark = "!" if outcome == "fail" else " "
|
|
63
|
+
print(f" {mark} {h.get('ts', '?')} [{h.get('trigger', '?')}/"
|
|
64
|
+
f"{h.get('model', '?')}] {str(h.get('summary', ''))[:90]}")
|
|
57
65
|
print(f" sources : {len(srcs)} ({ag} antigravity), "
|
|
58
66
|
f"{len(pending)} with pending content, {inbox} inbox note(s)")
|
|
59
67
|
stalled = m.get("ingest_stalled_bytes")
|
|
@@ -31,10 +31,19 @@ DEFAULT_CONFIG = {
|
|
|
31
31
|
# contain one JSON object (fences/prose ok).
|
|
32
32
|
# Empty -> claude_bin headless invocation.
|
|
33
33
|
"antigravity_dir": os.path.join(HOME, ".gemini", "antigravity-cli"),
|
|
34
|
-
|
|
34
|
+
# Both tiers default to the same capable model, deliberately. Curation is
|
|
35
|
+
# the product, and someone evaluating memd for the first time cannot tell
|
|
36
|
+
# "this tool is mediocre" from "this tool's default model is mediocre" —
|
|
37
|
+
# they just stop using it, having never found the config key. Good distills
|
|
38
|
+
# out of the box are worth more than the token savings. Set model_small to
|
|
39
|
+
# something cheaper (e.g. "haiku") once you trust it.
|
|
40
|
+
"model_small": "sonnet",
|
|
35
41
|
"model_large": "sonnet",
|
|
36
|
-
"escalate_chars": 15000, #
|
|
42
|
+
"escalate_chars": 15000, # digests above this go to model_large
|
|
37
43
|
"digest_cap_chars": 60000, # max transcript digest fed to the model
|
|
44
|
+
# subagent (sidechain) turns ride along in digests as indented " SUB> "
|
|
45
|
+
# lines — delegated work reaches memory instead of vanishing
|
|
46
|
+
"capture_sidechains": True,
|
|
38
47
|
"quiet_seconds": 600, # sweep skips transcripts modified more recently
|
|
39
48
|
"sweep_jobs": 4, # parallel project syncs per sweep (threads;
|
|
40
49
|
# per-project flock still serializes any
|