foldcrumbs 0.3.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.
Files changed (34) hide show
  1. foldcrumbs-0.3.0/LICENSE +26 -0
  2. foldcrumbs-0.3.0/PKG-INFO +211 -0
  3. foldcrumbs-0.3.0/README.md +190 -0
  4. foldcrumbs-0.3.0/foldcrumbs/__init__.py +9 -0
  5. foldcrumbs-0.3.0/foldcrumbs/__main__.py +6 -0
  6. foldcrumbs-0.3.0/foldcrumbs/audit.py +117 -0
  7. foldcrumbs-0.3.0/foldcrumbs/cli.py +358 -0
  8. foldcrumbs-0.3.0/foldcrumbs/config.py +226 -0
  9. foldcrumbs-0.3.0/foldcrumbs/distill.py +376 -0
  10. foldcrumbs-0.3.0/foldcrumbs/hooks/__init__.py +0 -0
  11. foldcrumbs-0.3.0/foldcrumbs/hooks/_common.py +185 -0
  12. foldcrumbs-0.3.0/foldcrumbs/hooks/_state.py +57 -0
  13. foldcrumbs-0.3.0/foldcrumbs/hooks/_worker.py +57 -0
  14. foldcrumbs-0.3.0/foldcrumbs/hooks/context_monitor.py +74 -0
  15. foldcrumbs-0.3.0/foldcrumbs/hooks/post_compact.py +62 -0
  16. foldcrumbs-0.3.0/foldcrumbs/hooks/session_end.py +43 -0
  17. foldcrumbs-0.3.0/foldcrumbs/hooks/session_start.py +82 -0
  18. foldcrumbs-0.3.0/foldcrumbs/install.py +361 -0
  19. foldcrumbs-0.3.0/foldcrumbs/llm.py +200 -0
  20. foldcrumbs-0.3.0/foldcrumbs/mcp_server.py +222 -0
  21. foldcrumbs-0.3.0/foldcrumbs/profile.py +75 -0
  22. foldcrumbs-0.3.0/foldcrumbs/redact.py +44 -0
  23. foldcrumbs-0.3.0/foldcrumbs/schema.py +254 -0
  24. foldcrumbs-0.3.0/foldcrumbs/store.py +265 -0
  25. foldcrumbs-0.3.0/foldcrumbs.egg-info/PKG-INFO +211 -0
  26. foldcrumbs-0.3.0/foldcrumbs.egg-info/SOURCES.txt +32 -0
  27. foldcrumbs-0.3.0/foldcrumbs.egg-info/dependency_links.txt +1 -0
  28. foldcrumbs-0.3.0/foldcrumbs.egg-info/entry_points.txt +3 -0
  29. foldcrumbs-0.3.0/foldcrumbs.egg-info/requires.txt +4 -0
  30. foldcrumbs-0.3.0/foldcrumbs.egg-info/top_level.txt +1 -0
  31. foldcrumbs-0.3.0/pyproject.toml +38 -0
  32. foldcrumbs-0.3.0/setup.cfg +4 -0
  33. foldcrumbs-0.3.0/tests/test_foldcrumbs.py +549 -0
  34. foldcrumbs-0.3.0/tests/test_mcp.py +97 -0
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vincenzo Ingrosso
4
+
5
+ Portions of this software (typed-memory schema and trust/decay logic,
6
+ extraction prompts and parser, lifecycle-hook plumbing, and context-block
7
+ rendering) are derived from MEMANTO (https://github.com/moorcheh-ai/memanto),
8
+ Copyright (c) Moorcheh / Edge AI Innovations, licensed under the MIT License.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: foldcrumbs
3
+ Version: 0.3.0
4
+ Summary: Persistent cross-session memory for coding agents — file-based, no vector DB, no external service.
5
+ Author: Vincenzo Ingrosso
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/vcnngr/foldcrumbs
8
+ Keywords: claude-code,codex,opencode,agent-memory,llm,hooks,mlx
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: dev
18
+ Requires-Dist: ruff>=0.5; extra == "dev"
19
+ Requires-Dist: mypy>=1.10; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # foldcrumbs
23
+
24
+ [![tests](https://github.com/vcnngr/foldcrumbs/actions/workflows/test.yml/badge.svg)](https://github.com/vcnngr/foldcrumbs/actions/workflows/test.yml)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
26
+
27
+ Persistent cross-session memory for coding agents — **no Docker, no vector DB, no external service**.
28
+
29
+ `/clear` and compaction wipe Claude Code's knowledge every session. foldcrumbs keeps a small
30
+ folder of typed memory files so the agent reopens already knowing your decisions, conventions
31
+ and codebase facts. It also fights context rot: around 45% context it checkpoints memory in the
32
+ background and nudges you to `/compact` or `/clear` — nothing is lost.
33
+
34
+ ## How it works
35
+
36
+ ```
37
+ STORE markdown files + MEMORY.md index in
38
+ ~/.claude/projects/<project>/memory/
39
+ RECALL Claude Code's own Grep/Read (no LLM, no vector DB)
40
+ + SessionStart injects the index
41
+ DISTILL async, local LLM only (MLX/Ollama/OpenRouter via env)
42
+ at ~45% context and at session end → gated, dedup'd
43
+ ANTI-ROT PostToolUse monitor → checkpoint + reminder (no forced compaction)
44
+ PostCompact → re-inject index after compaction
45
+ HANDOFF each checkpoint also writes a live working-state snapshot, re-injected
46
+ at SessionStart → resume the exact task after a /clear
47
+ ```
48
+
49
+ The retrieval engine is the agent itself: it greps the folder when relevant. The LLM is used
50
+ **only** for async distillation — so recall is instant and never depends on a model being up.
51
+
52
+ Pure Python stdlib: hook scripts never fail on a missing import.
53
+
54
+ The `MEMORY.md` index is written in a **deterministic order** (by immutable
55
+ creation time, newest first within each type), so a trust bump, re-touch or
56
+ re-distillation never reshuffles existing entries. Only adding or removing a
57
+ memory changes the file. This keeps the SessionStart-injected prefix identical
58
+ across sessions — so it rides the agent's own prompt cache instead of busting it
59
+ — and keeps the file diff-clean for sync tools like Syncthing.
60
+
61
+ ## What's different from memanto
62
+
63
+ foldcrumbs started from ideas in [memanto](https://github.com/moorcheh-ai/memanto), but takes a
64
+ deliberately different shape:
65
+
66
+ | | memanto | foldcrumbs |
67
+ |--|--|--|
68
+ | Retrieval | Moorcheh engine (closed) | the agent's own grep — no engine |
69
+ | Footprint | Docker + engine + LLM + REST API | a folder + hooks |
70
+ | LLM | required for retrieval & answers | async distillation only; recall never needs it |
71
+ | Anti-rot | — | context monitor + checkpoint near 45% |
72
+ | Deps | service stack | zero runtime deps (stdlib) |
73
+ | Scope | tool-agnostic service | per-project memory, agent-side |
74
+
75
+ The original work here is the architecture: grep-based recall, the file store + index, the
76
+ anti-rot monitor, the merge-safe installer, the hooks and CLI. See **Credits** for the parts
77
+ adapted from memanto.
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ foldcrumbs install # Claude Code, global (~/.claude/settings.json)
83
+ foldcrumbs install --local # Claude Code, project (.claude/settings.json)
84
+ foldcrumbs install --agent codex # Codex: hooks.json + prints the config.toml MCP snippet
85
+ foldcrumbs install --agent opencode # OpenCode: opencode.json MCP + plugin + AGENTS.md block
86
+ ```
87
+ The installer is merge-safe and idempotent: it appends its own hook groups and leaves existing
88
+ hooks (GSD, graphify, …) untouched. A `.foldcrumbs-bak` backup is written first.
89
+
90
+ On a TTY, install asks **how to distill** (recall never uses an LLM):
91
+
92
+ ```
93
+ 1) claude-cli Claude subscription — `claude -p`, no API key
94
+ 2) codex Codex subscription — `codex exec`, no API key
95
+ 3) openai OpenAI-compatible HTTP endpoint (local server or remote gateway)
96
+ 4) none no LLM — keyword heuristic only (last resort)
97
+ ```
98
+
99
+ The choice is saved per-machine in `~/.foldcrumbs` (not synced), so a shared store can have one
100
+ indexer with a local model and others using their own CLI subscription. Skip the prompt with
101
+ `foldcrumbs install --backend codex` (or `--no-backend-prompt`), and change it anytime with
102
+ `foldcrumbs backend <name>` (`foldcrumbs backend` alone shows the current one).
103
+
104
+ All agents share **one** memory store per project, so a decision recorded in Claude Code is
105
+ recalled in Codex and OpenCode.
106
+
107
+ ## Configure (env)
108
+
109
+ | var | default | meaning |
110
+ |-----|---------|---------|
111
+ | `FOLDCRUMBS_LLM_ENDPOINT` | `http://localhost:8081` | OpenAI-compatible endpoint (MLX server) |
112
+ | `FOLDCRUMBS_LLM_MODEL` | `gemma-4-26b-a4b` | model name |
113
+ | `FOLDCRUMBS_LLM_API_KEY` | – | optional bearer token |
114
+ | `FOLDCRUMBS_CONTEXT_BUDGET` | `200000` | context window size (tokens) for the monitor |
115
+ | `FOLDCRUMBS_CONTEXT_PCT` | `0.45` | fraction at which to checkpoint + nudge |
116
+ | `FOLDCRUMBS_MIN_CONFIDENCE` | `0.7` | write gate floor |
117
+ | `FOLDCRUMBS_DIR` | derived from cwd | override the memory directory |
118
+
119
+ Swap the LLM for a remote gateway or OpenRouter by changing `FOLDCRUMBS_LLM_ENDPOINT` — recall is
120
+ unaffected.
121
+
122
+ ## CLI
123
+
124
+ ```bash
125
+ python3 -m foldcrumbs status
126
+ python3 -m foldcrumbs remember "Recall is grep, no vector DB" --type decision --tag arch
127
+ python3 -m foldcrumbs recall "vector db"
128
+ python3 -m foldcrumbs index
129
+ python3 -m foldcrumbs distill transcript.txt # distil durable memories (LLM)
130
+ python3 -m foldcrumbs checkpoint transcript.txt # write a resume handoff (LLM)
131
+ python3 -m foldcrumbs handoff # print the current handoff
132
+ python3 -m foldcrumbs answer "how does recall work?"
133
+ ```
134
+
135
+ ## Surviving `/clear` and `/compact`
136
+
137
+ Two layers cross the context switch:
138
+
139
+ - **Durable memories** (decisions, rules, preferences, facts) — always re-injected via
140
+ the `MEMORY.md` index at SessionStart / PostCompact.
141
+ - **Working-state handoff** — a single overwritten snapshot of the *current* task, files
142
+ in flight and next steps, written at each checkpoint and re-injected so you resume the
143
+ exact task after a hard `/clear`.
144
+
145
+ At ~45% context foldcrumbs nudges you; pick `/compact` (keep working) or `/clear` (fresh start) —
146
+ either way the next turn is re-primed. Force a snapshot anytime with `foldcrumbs checkpoint`.
147
+
148
+ ## Local LLM
149
+
150
+ Distillation needs any OpenAI-compatible chat endpoint — point `FOLDCRUMBS_LLM_ENDPOINT`
151
+ at whatever you run. It's used only for async distillation, so a cold model load is
152
+ invisible to the editor, and **recall needs no model at all**.
153
+
154
+ Common local servers (all expose `/v1/chat/completions`):
155
+
156
+ ```bash
157
+ # MLX — Apple Silicon only, fastest on Mac
158
+ mlx_lm.server --model <gemma-mlx-repo> --port 8081 # or mlx_vlm.server for VLMs
159
+
160
+ # Ollama — cross-platform (macOS / Linux / Windows)
161
+ ollama serve # endpoint :11434/v1
162
+
163
+ # llama.cpp / LM Studio / vLLM — also OpenAI-compatible
164
+ ```
165
+
166
+ Then e.g. `export FOLDCRUMBS_LLM_ENDPOINT=http://localhost:11434 FOLDCRUMBS_LLM_MODEL=qwen2.5`.
167
+ A remote gateway or OpenRouter works the same way — only the env var changes.
168
+
169
+ ## Tests
170
+
171
+ ```bash
172
+ python3 -m unittest discover -s tests -v
173
+ ```
174
+
175
+ ## MCP server
176
+
177
+ foldcrumbs ships a minimal MCP server (stdio, stdlib only — no `mcp` SDK dependency) exposing
178
+ `remember`, `recall` and `answer` to any MCP client:
179
+
180
+ ```bash
181
+ foldcrumbs-mcp # or: python3 -m foldcrumbs.mcp_server
182
+ ```
183
+ Codex and OpenCode are wired to it by `foldcrumbs install --agent …`. Use it directly from any
184
+ MCP-speaking tool by registering the command above.
185
+
186
+ ## How each agent is wired
187
+
188
+ | Agent | Inject at start | Capture | Notes |
189
+ |-------|-----------------|---------|-------|
190
+ | Claude Code | SessionStart hook | PostToolUse monitor + SessionEnd | full lifecycle hooks |
191
+ | Codex | SessionStart hook (`additionalContext`) | Stop + PostToolUse hooks | same scripts; + MCP for in-session tool calls |
192
+ | OpenCode | AGENTS.md → agent calls `recall` (MCP) | plugin `session.idle`/`session.compacted` | no inject-capable hook, so prompt-driven recall |
193
+
194
+ ## Roadmap
195
+
196
+ - **Phase 1 ✓** — Claude Code: file store, grep recall, distillation, anti-rot.
197
+ - **Phase 2 ✓** — Codex + OpenCode on the same store via a stdlib MCP server + installers.
198
+ - **Phase 3** — embeddings + open vector DB only if scale outgrows grep; document ingest via OCR.
199
+
200
+ ## Credits
201
+
202
+ foldcrumbs adapts a few utilities from [memanto](https://github.com/moorcheh-ai/memanto)
203
+ (MIT, © Moorcheh / Edge AI Innovations): the typed-memory categories and confidence/decay
204
+ model, the session-distillation approach, the transcript-reading helper, and the context-block
205
+ rendering idea. These are reimplemented here against a file store; the Moorcheh retrieval engine
206
+ is not used. Full notice in [LICENSE](LICENSE). Thanks to the memanto authors for releasing it
207
+ under MIT.
208
+
209
+ ## License
210
+
211
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,190 @@
1
+ # foldcrumbs
2
+
3
+ [![tests](https://github.com/vcnngr/foldcrumbs/actions/workflows/test.yml/badge.svg)](https://github.com/vcnngr/foldcrumbs/actions/workflows/test.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
6
+ Persistent cross-session memory for coding agents — **no Docker, no vector DB, no external service**.
7
+
8
+ `/clear` and compaction wipe Claude Code's knowledge every session. foldcrumbs keeps a small
9
+ folder of typed memory files so the agent reopens already knowing your decisions, conventions
10
+ and codebase facts. It also fights context rot: around 45% context it checkpoints memory in the
11
+ background and nudges you to `/compact` or `/clear` — nothing is lost.
12
+
13
+ ## How it works
14
+
15
+ ```
16
+ STORE markdown files + MEMORY.md index in
17
+ ~/.claude/projects/<project>/memory/
18
+ RECALL Claude Code's own Grep/Read (no LLM, no vector DB)
19
+ + SessionStart injects the index
20
+ DISTILL async, local LLM only (MLX/Ollama/OpenRouter via env)
21
+ at ~45% context and at session end → gated, dedup'd
22
+ ANTI-ROT PostToolUse monitor → checkpoint + reminder (no forced compaction)
23
+ PostCompact → re-inject index after compaction
24
+ HANDOFF each checkpoint also writes a live working-state snapshot, re-injected
25
+ at SessionStart → resume the exact task after a /clear
26
+ ```
27
+
28
+ The retrieval engine is the agent itself: it greps the folder when relevant. The LLM is used
29
+ **only** for async distillation — so recall is instant and never depends on a model being up.
30
+
31
+ Pure Python stdlib: hook scripts never fail on a missing import.
32
+
33
+ The `MEMORY.md` index is written in a **deterministic order** (by immutable
34
+ creation time, newest first within each type), so a trust bump, re-touch or
35
+ re-distillation never reshuffles existing entries. Only adding or removing a
36
+ memory changes the file. This keeps the SessionStart-injected prefix identical
37
+ across sessions — so it rides the agent's own prompt cache instead of busting it
38
+ — and keeps the file diff-clean for sync tools like Syncthing.
39
+
40
+ ## What's different from memanto
41
+
42
+ foldcrumbs started from ideas in [memanto](https://github.com/moorcheh-ai/memanto), but takes a
43
+ deliberately different shape:
44
+
45
+ | | memanto | foldcrumbs |
46
+ |--|--|--|
47
+ | Retrieval | Moorcheh engine (closed) | the agent's own grep — no engine |
48
+ | Footprint | Docker + engine + LLM + REST API | a folder + hooks |
49
+ | LLM | required for retrieval & answers | async distillation only; recall never needs it |
50
+ | Anti-rot | — | context monitor + checkpoint near 45% |
51
+ | Deps | service stack | zero runtime deps (stdlib) |
52
+ | Scope | tool-agnostic service | per-project memory, agent-side |
53
+
54
+ The original work here is the architecture: grep-based recall, the file store + index, the
55
+ anti-rot monitor, the merge-safe installer, the hooks and CLI. See **Credits** for the parts
56
+ adapted from memanto.
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ foldcrumbs install # Claude Code, global (~/.claude/settings.json)
62
+ foldcrumbs install --local # Claude Code, project (.claude/settings.json)
63
+ foldcrumbs install --agent codex # Codex: hooks.json + prints the config.toml MCP snippet
64
+ foldcrumbs install --agent opencode # OpenCode: opencode.json MCP + plugin + AGENTS.md block
65
+ ```
66
+ The installer is merge-safe and idempotent: it appends its own hook groups and leaves existing
67
+ hooks (GSD, graphify, …) untouched. A `.foldcrumbs-bak` backup is written first.
68
+
69
+ On a TTY, install asks **how to distill** (recall never uses an LLM):
70
+
71
+ ```
72
+ 1) claude-cli Claude subscription — `claude -p`, no API key
73
+ 2) codex Codex subscription — `codex exec`, no API key
74
+ 3) openai OpenAI-compatible HTTP endpoint (local server or remote gateway)
75
+ 4) none no LLM — keyword heuristic only (last resort)
76
+ ```
77
+
78
+ The choice is saved per-machine in `~/.foldcrumbs` (not synced), so a shared store can have one
79
+ indexer with a local model and others using their own CLI subscription. Skip the prompt with
80
+ `foldcrumbs install --backend codex` (or `--no-backend-prompt`), and change it anytime with
81
+ `foldcrumbs backend <name>` (`foldcrumbs backend` alone shows the current one).
82
+
83
+ All agents share **one** memory store per project, so a decision recorded in Claude Code is
84
+ recalled in Codex and OpenCode.
85
+
86
+ ## Configure (env)
87
+
88
+ | var | default | meaning |
89
+ |-----|---------|---------|
90
+ | `FOLDCRUMBS_LLM_ENDPOINT` | `http://localhost:8081` | OpenAI-compatible endpoint (MLX server) |
91
+ | `FOLDCRUMBS_LLM_MODEL` | `gemma-4-26b-a4b` | model name |
92
+ | `FOLDCRUMBS_LLM_API_KEY` | – | optional bearer token |
93
+ | `FOLDCRUMBS_CONTEXT_BUDGET` | `200000` | context window size (tokens) for the monitor |
94
+ | `FOLDCRUMBS_CONTEXT_PCT` | `0.45` | fraction at which to checkpoint + nudge |
95
+ | `FOLDCRUMBS_MIN_CONFIDENCE` | `0.7` | write gate floor |
96
+ | `FOLDCRUMBS_DIR` | derived from cwd | override the memory directory |
97
+
98
+ Swap the LLM for a remote gateway or OpenRouter by changing `FOLDCRUMBS_LLM_ENDPOINT` — recall is
99
+ unaffected.
100
+
101
+ ## CLI
102
+
103
+ ```bash
104
+ python3 -m foldcrumbs status
105
+ python3 -m foldcrumbs remember "Recall is grep, no vector DB" --type decision --tag arch
106
+ python3 -m foldcrumbs recall "vector db"
107
+ python3 -m foldcrumbs index
108
+ python3 -m foldcrumbs distill transcript.txt # distil durable memories (LLM)
109
+ python3 -m foldcrumbs checkpoint transcript.txt # write a resume handoff (LLM)
110
+ python3 -m foldcrumbs handoff # print the current handoff
111
+ python3 -m foldcrumbs answer "how does recall work?"
112
+ ```
113
+
114
+ ## Surviving `/clear` and `/compact`
115
+
116
+ Two layers cross the context switch:
117
+
118
+ - **Durable memories** (decisions, rules, preferences, facts) — always re-injected via
119
+ the `MEMORY.md` index at SessionStart / PostCompact.
120
+ - **Working-state handoff** — a single overwritten snapshot of the *current* task, files
121
+ in flight and next steps, written at each checkpoint and re-injected so you resume the
122
+ exact task after a hard `/clear`.
123
+
124
+ At ~45% context foldcrumbs nudges you; pick `/compact` (keep working) or `/clear` (fresh start) —
125
+ either way the next turn is re-primed. Force a snapshot anytime with `foldcrumbs checkpoint`.
126
+
127
+ ## Local LLM
128
+
129
+ Distillation needs any OpenAI-compatible chat endpoint — point `FOLDCRUMBS_LLM_ENDPOINT`
130
+ at whatever you run. It's used only for async distillation, so a cold model load is
131
+ invisible to the editor, and **recall needs no model at all**.
132
+
133
+ Common local servers (all expose `/v1/chat/completions`):
134
+
135
+ ```bash
136
+ # MLX — Apple Silicon only, fastest on Mac
137
+ mlx_lm.server --model <gemma-mlx-repo> --port 8081 # or mlx_vlm.server for VLMs
138
+
139
+ # Ollama — cross-platform (macOS / Linux / Windows)
140
+ ollama serve # endpoint :11434/v1
141
+
142
+ # llama.cpp / LM Studio / vLLM — also OpenAI-compatible
143
+ ```
144
+
145
+ Then e.g. `export FOLDCRUMBS_LLM_ENDPOINT=http://localhost:11434 FOLDCRUMBS_LLM_MODEL=qwen2.5`.
146
+ A remote gateway or OpenRouter works the same way — only the env var changes.
147
+
148
+ ## Tests
149
+
150
+ ```bash
151
+ python3 -m unittest discover -s tests -v
152
+ ```
153
+
154
+ ## MCP server
155
+
156
+ foldcrumbs ships a minimal MCP server (stdio, stdlib only — no `mcp` SDK dependency) exposing
157
+ `remember`, `recall` and `answer` to any MCP client:
158
+
159
+ ```bash
160
+ foldcrumbs-mcp # or: python3 -m foldcrumbs.mcp_server
161
+ ```
162
+ Codex and OpenCode are wired to it by `foldcrumbs install --agent …`. Use it directly from any
163
+ MCP-speaking tool by registering the command above.
164
+
165
+ ## How each agent is wired
166
+
167
+ | Agent | Inject at start | Capture | Notes |
168
+ |-------|-----------------|---------|-------|
169
+ | Claude Code | SessionStart hook | PostToolUse monitor + SessionEnd | full lifecycle hooks |
170
+ | Codex | SessionStart hook (`additionalContext`) | Stop + PostToolUse hooks | same scripts; + MCP for in-session tool calls |
171
+ | OpenCode | AGENTS.md → agent calls `recall` (MCP) | plugin `session.idle`/`session.compacted` | no inject-capable hook, so prompt-driven recall |
172
+
173
+ ## Roadmap
174
+
175
+ - **Phase 1 ✓** — Claude Code: file store, grep recall, distillation, anti-rot.
176
+ - **Phase 2 ✓** — Codex + OpenCode on the same store via a stdlib MCP server + installers.
177
+ - **Phase 3** — embeddings + open vector DB only if scale outgrows grep; document ingest via OCR.
178
+
179
+ ## Credits
180
+
181
+ foldcrumbs adapts a few utilities from [memanto](https://github.com/moorcheh-ai/memanto)
182
+ (MIT, © Moorcheh / Edge AI Innovations): the typed-memory categories and confidence/decay
183
+ model, the session-distillation approach, the transcript-reading helper, and the context-block
184
+ rendering idea. These are reimplemented here against a file store; the Moorcheh retrieval engine
185
+ is not used. Full notice in [LICENSE](LICENSE). Thanks to the memanto authors for releasing it
186
+ under MIT.
187
+
188
+ ## License
189
+
190
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,9 @@
1
+ """foldcrumbs — persistent cross-session memory for coding agents.
2
+
3
+ File-based memory store + lifecycle hooks for Claude Code. No external
4
+ service, no vector DB: retrieval is done by the agent's own grep over the
5
+ memory folder, and a local OpenAI-compatible LLM is used only for async
6
+ distillation. Pure stdlib, so hook scripts never fail on a missing import.
7
+ """
8
+
9
+ __version__ = "0.3.0"
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,117 @@
1
+ """Store integrity audit + pruning.
2
+
3
+ Link integrity: the index links to real files on disk (``store.rebuild_index``
4
+ is path-based), so a dead link — or an active memory the index doesn't link —
5
+ just means the index is stale; ``heal_index`` rebuilds it. Pollution: a memory
6
+ whose title/content is a structural tooling artifact (markdown table, code
7
+ fence, status glyphs, the local-command caveat — distill's strict detector) is
8
+ never durable knowledge and can be pruned. The strict detector deliberately
9
+ excludes prose that merely mentions MEMORY.md so legitimate foldcrumbs design notes
10
+ are never deleted. Superseded/deleted records keep their
11
+ files but drop out of the index/recall; ``prune`` clears those too.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+
18
+ from . import config, store
19
+ from .distill import _is_hard_artifact
20
+
21
+ _LINK_RE = re.compile(r"\]\(([^)]+\.md)\)")
22
+ # compute_confidence below this is low-trust (stale/contradicted); prune only on
23
+ # explicit request, never automatically.
24
+ STALE_CONF = 0.3
25
+
26
+
27
+ def _name(m) -> str:
28
+ return m.source_path or m.filename()
29
+
30
+
31
+ def _index_links(cwd=None) -> set[str]:
32
+ p = config.index_path(cwd)
33
+ if not p.exists():
34
+ return set()
35
+ try:
36
+ return set(_LINK_RE.findall(p.read_text(encoding="utf-8")))
37
+ except OSError:
38
+ return set()
39
+
40
+
41
+ def audit(cwd=None) -> dict:
42
+ """Read-only report: dead index links, orphaned active memories (on disk but
43
+ unlinked), artifact pollution, and low-trust/stale memories."""
44
+ linked = _index_links(cwd)
45
+ mems = list(store.iter_memories(cwd))
46
+ active = [m for m in mems if m.status == "active"]
47
+ on_disk = {_name(m) for m in mems}
48
+ active_names = {_name(m) for m in active}
49
+ return {
50
+ "dead_links": sorted(t for t in linked if t not in on_disk),
51
+ "orphans": sorted(n for n in active_names if n not in linked),
52
+ "pollution": sorted(_name(m) for m in active
53
+ if _is_hard_artifact(m.title) or _is_hard_artifact(m.content)),
54
+ "stale": sorted(_name(m) for m in active
55
+ if m.compute_confidence() < STALE_CONF),
56
+ "active": len(active),
57
+ "total": len(mems),
58
+ }
59
+
60
+
61
+ def heal_index(cwd=None) -> bool:
62
+ """Rebuild the index if it is stale (dead links or unlinked active memories).
63
+
64
+ Cheap and idempotent; returns True iff it rebuilt. Callers that share a store
65
+ across machines should gate this on ``config.distill_enabled()`` so only a
66
+ writing machine repairs (avoids sync churn)."""
67
+ a = audit(cwd)
68
+ if a["dead_links"] or a["orphans"]:
69
+ store.rebuild_index(cwd)
70
+ return True
71
+ return False
72
+
73
+
74
+ def _delete(name: str, cwd=None) -> bool:
75
+ try:
76
+ (config.memory_dir(cwd) / name).unlink()
77
+ return True
78
+ except OSError:
79
+ return False
80
+
81
+
82
+ def prune_artifacts(cwd=None) -> list[str]:
83
+ """Delete active memories whose text is a clear tooling artifact, then rebuild
84
+ the index. Conservative — only unambiguous artifacts. Returns deleted names."""
85
+ removed = [
86
+ _name(m)
87
+ for m in list(store.iter_memories(cwd))
88
+ if m.status == "active" and (_is_hard_artifact(m.title) or _is_hard_artifact(m.content))
89
+ ]
90
+ removed = [n for n in removed if _delete(n, cwd)]
91
+ if removed:
92
+ store.rebuild_index(cwd)
93
+ return removed
94
+
95
+
96
+ def prune(cwd=None, apply: bool = False, include_stale: bool = False) -> dict:
97
+ """Find (and with ``apply``, delete) prune candidates.
98
+
99
+ Candidates: superseded/deleted records (files left behind), active artifact
100
+ pollution, and — only with ``include_stale`` — low-trust active memories.
101
+ Dry-run by default; rebuilds the index when it deletes anything."""
102
+ candidates: dict[str, str] = {}
103
+ for m in store.iter_memories(cwd):
104
+ name = _name(m)
105
+ if m.status in ("deleted", "superseded"):
106
+ candidates[name] = "superseded/deleted"
107
+ elif m.status == "active" and (_is_hard_artifact(m.title) or _is_hard_artifact(m.content)):
108
+ candidates[name] = "artifact"
109
+ elif (include_stale and m.status == "active"
110
+ and m.compute_confidence() < STALE_CONF):
111
+ candidates[name] = "stale"
112
+ removed: list[str] = []
113
+ if apply and candidates:
114
+ removed = [n for n in candidates if _delete(n, cwd)]
115
+ if removed:
116
+ store.rebuild_index(cwd)
117
+ return {"candidates": candidates, "removed": removed, "applied": apply}