./run foldcrumbs-memoria-persistente-agenti-grep
foldcrumbs: agent memory with grep, not a vector DB
Technical diary of foldcrumbs: persistent memory for coding agents built on grep instead of a vector DB. Zero dependencies, one stdlib MCP server, and a…
- status
- shipped
- project
- engram
- updated
- 2026-07-08
- tags
Every /clear wipes the agent’s brain. Every session starts from scratch, asks you the same questions, forgets yesterday’s decisions. The obvious solution is a vector database with embeddings. foldcrumbs (born as engram) does the opposite, and the whole thesis is right here: the agent is already a first-rate retrieval engine — don’t build a second one.
The thesis: grep, not vectors
A coding agent (Claude Code, Codex, OpenCode) already has native Grep and Read and knows how to use them. foldcrumbs writes memory as a folder of markdown files and lets the agent grep them the way it greps source code. No embeddings, no index to rebuild on every write, no similarity search, no service to keep running. The technical argument, in full:
- Recall is instantaneous and doesn’t depend on a model being up. The LLM is used only for asynchronous distillation (turning a finished session into memories). Retrieval touches neither model nor network.
- Zero moving parts versus a vector stack (Docker + a retrieval engine + LLM + REST API).
- Grep is readable and debuggable. Memory is markdown you can
cat, diff, sync with Syncthing. No opaque index that silently drifts out of sync with the truth. - Scale is a Phase 3 problem, not a Phase 1 one. Embeddings + an open vector DB arrive only if scale outgrows grep. The bet: per-project memory is small (dozens of one-line facts), and there grep + a curated index beats vector search on latency and correctness.
Recall is two-tiered: an index that’s always injected (deterministic, rides the prompt cache) plus on-demand grep into the folder when detail is needed.
The format
One markdown file per memory, frontmatter + body, written atomically (tempfile + os.replace). Thirteen typed kinds (fact, decision, preference, instruction, error, goal, learning…), a provenance with a confidence weight (explicit_statement=1.0 … inferred=0.7), and a decay model: confidence falls with age only for preferences and observations, a contradiction knocks it down to 30%, a validation promotes it.
---
name: Recall is grep, not a vector DB
description: The agent's own Grep/Read is the retrieval engine; no embeddings.
type: decision
confidence: 0.9
provenance: explicit_statement
tags: architecture, retrieval
---
Memory retrieval is delegated to the agent's native grep over the memory folder.
Embeddings/vector search are deferred to Phase 3 only if scale outgrows grep.
The index that rides the prompt cache
The MEMORY.md file is one line per memory, grouped by type. The load-bearing design detail is determinism: within each type the entries are ordered by immutable created_at, not by mutable title. This way a confidence bump or a re-distillation doesn’t reshuffle the existing lines — only adding or removing a memory changes the file. The payoff: the prefix injected at the start of a session is byte-identical across sessions, so it rides the agent’s prompt cache instead of invalidating it, and the file stays diff-clean for Syncthing.
Recall: the agent is the engine
Two things cross the context boundary. The first is index injection: a SessionStart hook reads MEMORY.md and emits it as additionalContext, with the instruction “honor it; don’t re-ask what’s already recorded; for detail, read the linked file or grep the folder”. The second is the agent’s own on-demand grep. No code path does semantic retrieval at recall time. For the tools that need a programmatic equivalent (the CLI, the recall MCP tool), there’s a stdlib ranker: exact match = 1.0, otherwise word overlap + difflib.SequenceMatcher.
Integration: hooks + one stdlib MCP server
The hooks are agent-agnostic — they read cwd/transcript_path from the payload — so Claude Code and Codex reuse the same scripts under different event names. The installer is merge-safe and idempotent: it appends its own hook groups with a marker, leaves existing ones intact (GSD, graphify…), and backs up first.
The MCP server is written from scratch in stdlib: JSON-RPC 2.0 over stdin/stdout, no dependency on the mcp SDK. It exposes three tools over the shared store — remember, recall, answer.
Anti-context-rot
A monitor on PostToolUse estimates tokens by reading the whole transcript (chars / 3.5). When it crosses 45% of a 200k window, it fires once: it spawns a fully detached distillation worker (stdio to /dev/null, start_new_session=True) so the LLM call never blocks the editor, and injects a reminder: “checkpoint saved in the background, good moment for /compact or /clear, nothing is lost”. A small per-session state avoids re-firing on every tool call.
Why pure stdlib, zero dependencies
pyproject.toml: dependencies = []. It’s the central constraint, and it cascades into explaining everything:
The hooks run on the developer’s hot path, on every tool call, launched by the editor. A missing or incompatible import (pydantic, the MCP SDK, an embedding library) would break Claude Code itself. Zero dependencies = the script always imports.
Every hook runs under a “never break Claude” contract: any exception exits with code 0, silently. The concrete substitutions: dataclasses instead of pydantic, difflib for fuzzy matching, urllib for the LLM call, a hand-written JSON-RPC MCP instead of the SDK. Local distillation can run entirely on MLX or Ollama, so a shared store can have a single machine as the indexer while the others stay read-only consumers.
How it’s going
Shipped, v0.3.0 on PyPI, MIT, CI green. File store + index, grep recall, hooks for three agents, anti-rot monitor, four distillation backends, the stdlib MCP server, secret redaction. Phase 3 (embeddings, only if grep is no longer enough) is future — and the fact that it’s still hypothetical is, to me, the confirmation that the bet holds.
Code: github.com/vcnngr/foldcrumbs · pip install foldcrumbs.


