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.
Files changed (42) hide show
  1. {memd-0.3.3 → memd-0.4.2}/PKG-INFO +20 -16
  2. {memd-0.3.3 → memd-0.4.2}/README.md +19 -15
  3. {memd-0.3.3 → memd-0.4.2}/memd/__init__.py +8 -2
  4. {memd-0.3.3 → memd-0.4.2}/memd/cli.py +8 -0
  5. {memd-0.3.3 → memd-0.4.2}/memd/config.py +11 -2
  6. memd-0.4.2/memd/curator.py +554 -0
  7. {memd-0.3.3 → memd-0.4.2}/memd/digest.py +291 -22
  8. {memd-0.3.3 → memd-0.4.2}/memd/inbox.py +33 -3
  9. {memd-0.3.3 → memd-0.4.2}/memd/memory.py +75 -22
  10. {memd-0.3.3 → memd-0.4.2}/memd/sync.py +116 -32
  11. {memd-0.3.3 → memd-0.4.2}/memd.egg-info/PKG-INFO +20 -16
  12. {memd-0.3.3 → memd-0.4.2}/memd.egg-info/SOURCES.txt +1 -0
  13. {memd-0.3.3 → memd-0.4.2}/tests/test_antigravity.py +11 -7
  14. {memd-0.3.3 → memd-0.4.2}/tests/test_brief.py +120 -2
  15. {memd-0.3.3 → memd-0.4.2}/tests/test_config.py +1 -1
  16. memd-0.4.2/tests/test_curation_quality.py +275 -0
  17. memd-0.4.2/tests/test_curator.py +458 -0
  18. memd-0.4.2/tests/test_digest.py +314 -0
  19. {memd-0.3.3 → memd-0.4.2}/tests/test_inbox.py +59 -3
  20. memd-0.4.2/tests/test_ingest_health.py +192 -0
  21. memd-0.4.2/tests/test_model_escalation.py +81 -0
  22. {memd-0.3.3 → memd-0.4.2}/tests/test_sync_e2e.py +72 -0
  23. memd-0.3.3/memd/curator.py +0 -264
  24. memd-0.3.3/tests/test_curation_quality.py +0 -184
  25. memd-0.3.3/tests/test_curator.py +0 -150
  26. memd-0.3.3/tests/test_digest.py +0 -99
  27. memd-0.3.3/tests/test_ingest_health.py +0 -118
  28. {memd-0.3.3 → memd-0.4.2}/LICENSE +0 -0
  29. {memd-0.3.3 → memd-0.4.2}/memd/errors.py +0 -0
  30. {memd-0.3.3 → memd-0.4.2}/memd/hooks.py +0 -0
  31. {memd-0.3.3 → memd-0.4.2}/memd/sweep.py +0 -0
  32. {memd-0.3.3 → memd-0.4.2}/memd.egg-info/dependency_links.txt +0 -0
  33. {memd-0.3.3 → memd-0.4.2}/memd.egg-info/entry_points.txt +0 -0
  34. {memd-0.3.3 → memd-0.4.2}/memd.egg-info/top_level.txt +0 -0
  35. {memd-0.3.3 → memd-0.4.2}/pyproject.toml +0 -0
  36. {memd-0.3.3 → memd-0.4.2}/setup.cfg +0 -0
  37. {memd-0.3.3 → memd-0.4.2}/tests/test_core.py +0 -0
  38. {memd-0.3.3 → memd-0.4.2}/tests/test_cursors.py +0 -0
  39. {memd-0.3.3 → memd-0.4.2}/tests/test_memory_repo.py +0 -0
  40. {memd-0.3.3 → memd-0.4.2}/tests/test_redact.py +0 -0
  41. {memd-0.3.3 → memd-0.4.2}/tests/test_sweep.py +0 -0
  42. {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.3
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
- [![Release](https://img.shields.io/badge/version-0.3.3-blue.svg)](https://github.com/lowcache/memd)
35
- [![Python Version Compatibility](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-brightgreen.svg)](#installation)
34
+ [![CI](https://github.com/lowcache/memd/actions/workflows/ci.yml/badge.svg)](https://github.com/lowcache/memd/actions/workflows/ci.yml)
35
+ [![PyPI](https://img.shields.io/pypi/v/memd.svg)](https://pypi.org/project/memd/)
36
+ [![Python Versions](https://img.shields.io/pypi/pyversions/memd.svg)](https://pypi.org/project/memd/)
36
37
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
37
38
  [![Nix Flake](https://img.shields.io/badge/Nix-Flake-blueviolet.svg)](#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 rewrites the memory files. Python code, not the model, gets the final say on what's allowed to change.
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. Other agents' transcripts can be wired in per-project via `extra_sources` (plain text / JSONL), or their facts routed through the inbox — there is no built-in parser for them yet.
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 (`haiku`/`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
+ * **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 contents of the four memory files. Credentials are scrubbed first — 13 patterns, [detailed below](#built-in-credential-redaction) — but that's shape-matching, not a guarantee.
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: haiku/sonnet] — sends session content to the Anthropic API
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` (haiku) unless the digest exceeds `escalate_chars` (15,000), which promotes it to `model_large` (sonnet). To spend less: raise `quiet_seconds`, lengthen the timer interval, or set both models to something cheap. To spend nothing, use a local backend.
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; the risk is the opposite one, that memd keeps running and quietly stops learning. So it watches for exactly that: session content read, nothing usable extracted. `memd status` reports it instead of leaving you to notice months later that memory went stale.
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 text transcript sources (e.g. custom JSONL logs) can be registered per-project under the `projects.<path>.extra_sources` array in `config.json`.
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` | `"haiku"` | Model used for small/routine transcript distills. |
224
- | `model_large` | `string` | `"sonnet"` | Model used for large/complex session distills. |
225
- | `escalate_chars` | `integer` | `15000` | Character count above which digests are run with `model_large`. |
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
- 180 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.
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
- [![Release](https://img.shields.io/badge/version-0.3.3-blue.svg)](https://github.com/lowcache/memd)
8
- [![Python Version Compatibility](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-brightgreen.svg)](#installation)
7
+ [![CI](https://github.com/lowcache/memd/actions/workflows/ci.yml/badge.svg)](https://github.com/lowcache/memd/actions/workflows/ci.yml)
8
+ [![PyPI](https://img.shields.io/pypi/v/memd.svg)](https://pypi.org/project/memd/)
9
+ [![Python Versions](https://img.shields.io/pypi/pyversions/memd.svg)](https://pypi.org/project/memd/)
9
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
10
11
  [![Nix Flake](https://img.shields.io/badge/Nix-Flake-blueviolet.svg)](#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 rewrites the memory files. Python code, not the model, gets the final say on what's allowed to change.
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. Other agents' transcripts can be wired in per-project via `extra_sources` (plain text / JSONL), or their facts routed through the inbox — there is no built-in parser for them yet.
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 (`haiku`/`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
+ * **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 contents of the four memory files. Credentials are scrubbed first — 13 patterns, [detailed below](#built-in-credential-redaction) — but that's shape-matching, not a guarantee.
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: haiku/sonnet] — sends session content to the Anthropic API
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` (haiku) unless the digest exceeds `escalate_chars` (15,000), which promotes it to `model_large` (sonnet). To spend less: raise `quiet_seconds`, lengthen the timer interval, or set both models to something cheap. To spend nothing, use a local backend.
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; the risk is the opposite one, that memd keeps running and quietly stops learning. So it watches for exactly that: session content read, nothing usable extracted. `memd status` reports it instead of leaving you to notice months later that memory went stale.
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 text transcript sources (e.g. custom JSONL logs) can be registered per-project under the `projects.<path>.extra_sources` array in `config.json`.
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` | `"haiku"` | Model used for small/routine transcript distills. |
197
- | `model_large` | `string` | `"sonnet"` | Model used for large/complex session distills. |
198
- | `escalate_chars` | `integer` | `15000` | Character count above which digests are run with `model_large`. |
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
- 180 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.
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.3.3"
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
- "model_small": "haiku",
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, # session-end digests above this go to model_large
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