./run foldcrumbs-memoria-persistente-agenti-grep
foldcrumbs: memoria per agenti con grep, non con un vector DB
Diario tecnico di foldcrumbs: memoria persistente per agenti di coding basata su grep invece che su un vector DB. Zero dipendenze, un MCP server stdlib, e un indice deterministico che cavalca la prompt cache.
- status
- draft
- project
- engram
- updated
- 2026-07-07
- tags
Ogni /clear cancella il cervello dell’agente. Ogni sessione riparte da zero, ti fa le stesse domande, dimentica le decisioni prese ieri. La soluzione ovvia è un database vettoriale con embedding. foldcrumbs (nato come engram) fa l’opposto, e la tesi è tutta qui: l’agente è già un motore di retrieval di prim’ordine — non costruirne un secondo.
La tesi: grep, non vettori
Un agente di coding (Claude Code, Codex, OpenCode) ha già Grep e Read nativi e sa usarli. foldcrumbs scrive la memoria come una cartella di file markdown e lascia che l’agente li grepp-i come fa col codice sorgente. Niente embedding, niente indice da ricostruire a ogni scrittura, niente similarity search, niente servizio da tenere su. L’argomento tecnico, per esteso:
- Il recall è istantaneo e non dipende da un modello acceso. L’LLM serve solo per la distillazione asincrona (trasformare una sessione finita in memorie). Il recupero non tocca né modello né rete.
- Zero parti in movimento contro uno stack vettoriale (Docker + motore di retrieval + LLM + API REST).
- Grep è leggibile e debuggabile. La memoria è markdown che puoi
cat, diffare, sincronizzare con Syncthing. Nessun indice opaco che va silenziosamente fuori sync con la verità. - La scala è un problema di Fase 3, non di Fase 1. Gli embedding + un vector DB open arrivano solo se la scala supera grep. La scommessa: la memoria per-progetto è piccola (decine di fatti a una riga), e lì grep + un indice curato batte la ricerca vettoriale su latenza e correttezza.
Il recall è a due livelli: un indice sempre iniettato (deterministico, cavalca la prompt cache) più grep on-demand nella cartella quando serve dettaglio.
Il formato
Un file markdown per memoria, frontmatter + corpo, scritto atomicamente (tempfile + os.replace). Tredici tipi tipati (fact, decision, preference, instruction, error, goal, learning…), una provenienza con peso di fiducia (explicit_statement=1.0 … inferred=0.7), e un modello di decadimento: la confidenza cala con l’età solo per preferenze e osservazioni, una contraddizione la abbatte al 30%, una validazione la promuove.
---
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.
L’indice che cavalca la prompt cache
Il file MEMORY.md è una riga per memoria, raggruppate per tipo. Il dettaglio di design load-bearing è la determinismo: dentro ogni tipo le voci sono ordinate per created_at immutabile, non per titolo mutabile. Così un bump di fiducia o una ri-distillazione non rimescolano le righe esistenti — solo aggiungere o togliere una memoria cambia il file. Il payoff: il prefisso iniettato a inizio sessione è byte-identico tra sessioni, quindi cavalca la prompt cache dell’agente invece di invalidarla, e il file resta diff-pulito per Syncthing.
Il recall: l’agente è il motore
Due cose attraversano il confine di contesto. La prima è l’iniezione dell’indice: un hook SessionStart legge MEMORY.md e lo emette come additionalContext, con l’istruzione “onoralo; non richiedere ciò che è già registrato; per il dettaglio, leggi il file linkato o greppa la cartella”. La seconda è il grep on-demand dell’agente stesso. Nessun percorso di codice fa retrieval semantico al momento del recall. Per i tool che hanno bisogno di un equivalente programmatico (la CLI, il tool MCP recall), c’è un ranker stdlib: match esatto = 1.0, altrimenti overlap di parole + difflib.SequenceMatcher.
Integrazione: hook + un MCP server stdlib
Gli hook sono agnostici rispetto all’agente — leggono cwd/transcript_path dal payload — così Claude Code e Codex riusano gli stessi script sotto nomi di evento diversi. L’installer è merge-safe e idempotente: appende i propri gruppi di hook con un marcatore, lascia intatti quelli esistenti (GSD, graphify…), fa backup prima.
L’MCP server è scritto da zero in stdlib: JSON-RPC 2.0 su stdin/stdout, nessuna dipendenza dal SDK mcp. Espone tre tool sullo store condiviso — remember, recall, answer.
Anti-context-rot
Un monitor su PostToolUse stima i token leggendo l’intero transcript (caratteri / 3.5). Quando supera il 45% di una finestra da 200k, fa fuoco una volta: spawna un worker di distillazione completamente staccato (stdio su /dev/null, start_new_session=True) così la chiamata LLM non blocca mai l’editor, e inietta un promemoria: “checkpoint salvato in background, buon momento per /compact o /clear, non si perde nulla”. Un piccolo stato per-sessione evita di rifare fuoco a ogni tool call.
Perché stdlib puro, zero dipendenze
pyproject.toml: dependencies = []. È il vincolo centrale, e a cascata spiega tutto:
Gli hook girano sul percorso caldo dello sviluppatore, a ogni tool call, lanciati dall’editor. Un import mancante o incompatibile (pydantic, il SDK MCP, una libreria di embedding) romperebbe Claude Code stesso. Zero dipendenze = lo script importa sempre.
Ogni hook gira sotto un contratto “non rompere mai Claude”: qualsiasi eccezione esce con codice 0 in silenzio. Le sostituzioni concrete: dataclasses invece di pydantic, difflib per il fuzzy match, urllib per la chiamata LLM, un JSON-RPC MCP scritto a mano invece del SDK. La distillazione locale può girare interamente su MLX o Ollama, così uno store condiviso può avere una sola macchina come indicizzatore mentre le altre restano consumatori read-only.
Come va
Spedito, v0.3.0 su PyPI, MIT, CI verde. File store + indice, recall grep, hook per tre agenti, monitor anti-rot, quattro backend di distillazione, l’MCP server stdlib, redazione dei segreti. Fase 3 (embedding, solo se grep non basta più) è futura — e il fatto che sia ancora ipotetica è, per me, la conferma che la scommessa regge.
Codice: github.com/vcnngr/foldcrumbs · pip install foldcrumbs.