Pinky Brain — Piano di architettura (locale)
Sistema di gestione della conoscenza per agenti + esseri umani, scalabile a più progetti per 2+ anni. Evoluzione di quack-brain. Decisione presa: modalità LOCALE. SQLite incorporato, senza server, senza rete. Senza astrazione di storage (un solo backend reale → non si astrae, YAGNI).
1. Visione e obiettivi
- Fonte di verità leggibile e versionata: la conoscenza vive in markdown + git. La modifica l'agente con i suoi tool nativi e la revisiona un essere umano con
git diff. - Retrieval reale: ricerca ibrida (full-text + semantica), non
grepmanuale. - Multi-progetto: conoscenza di progetto + conoscenza globale tra progetti.
- Durevole 2+ anni: l'indice è 100% rigenerabile dal markdown; nessun dato critico vive solo nel DB.
- Zero ops: un binario + un file
brain.db. Senza Docker, senza daemon, offline.
2. Principi di design
- Markdown = fonte di verità. SQLite = indice derivato, usa e getta. Se il DB si corrompe →
pinky reindexlo ricostruisce dai.md. - SOLID senza sovra-ingegneria. Un modulo
storeconcreto su SQLite, **senza trait di astrazione**: non c'è un secondo backend, quindi non si astrae. Se un giorno apparisse (improbabile), si estrae il trait allora — non prima. - Pull semantico invece di push cieco. L'agente cerca quando ne ha bisogno (tool MCP), invece di iniettare tutto il contesto in ogni hook.
- Offline-first. Funziona senza rete. Embeddings locali. La rete entra solo nel
git pull/pushdel brain globale, ed è opzionale. - Append-only dove si può (diary) affinché git faccia merge senza conflitti.
3. Architettura
┌─ Fuente de verdad (git) ─────────────────────────────┐
│ <proyecto>/documentation/*.md │
│ ~/.pinky/brain/*.md (global cross-proyecto)│
└───────────────────────┬──────────────────────────────┘
│ notify (file watcher)
▼
┌─ pinky-core (lib Rust) ──────────────────────────────┐
│ parse(frontmatter) → chunk → embed → upsert │
│ search híbrido (BM25 + vector, fusión RRF) → rerank │
│ módulo `store`: SQLite (sqlite-vec + FTS5) │
└───────────────────────┬──────────────────────────────┘
┌───────────────┼────────────────┐
▼ ▼ ▼
pinky (CLI) pinky-mcp (server) pinky-hooks
reindex/search brain_search tool SessionStart/Stop
pinky-core: libreria con tutta la logica (parse, chunk, embed, search, store).store: modulo concreto surusqlite+sqlite-vec+ FTS5. Senza trait.pinky-mcp: esponebrain_search,brain_save,brain_statscome MCP server. L'agente fa pull semantico invece di grep.pinkyCLI:reindex,search,doctor,stats,gc.pinky-hooks: i 4 hook di quack-brain (SessionStart/PreRead/PreWrite/Stop), sottili: il retrieval pesante si delega al core/MCP invece di iniettare tutto.
4. Modello dei dati (SQLite)
Il frontmatter YAML si mappa a metadata indicizzati per filtrare:
entry(
id TEXT PK, -- hash estable del path
path TEXT, -- ruta del .md (relativa al root)
scope TEXT, -- 'project:<name>' | 'global'
type TEXT, -- gotcha | pattern | bug | decision | diary | guide
project TEXT,
tags TEXT, -- JSON array
created TEXT, -- ISO date
last_verified TEXT, -- para staleness/decay (§9)
title TEXT,
body TEXT,
content_hash TEXT -- re-index incremental (skip si no cambió)
)
chunk_fts FTS5 virtual table (text) -- BM25
chunk_vec sqlite-vec virtual table (embedding float[384])
chunk(
id, entry_id, ord, text -- une fts + vec con metadata
)
backlink( -- grafo código ↔ conocimiento desde `// Brain: {slug}`
entry_id, file_path, line, repo
)
usage( -- telemetría: qué entradas se recuperan/usan de verdad
entry_id, retrieved_at, query, was_useful
)
5. Stack tecnologico
| Necessità | Scelta | Perché |
|---|---|---|
| Linguaggio | Rust | Latenza <50ms negli hook, un binario, senza runtime |
| Indice | rusqlite + sqlite-vec + FTS5 | Un file, ibrido in una query, in-process |
| Embeddings | fastembed (ONNX, multilingual-e5-small) | Locale, senza API, senza costo per hook, ES+IT |
| Rerank (opzionale) | cross-encoder ONNX (bge-reranker-base) | Precisione dopo il retrieval ibrido |
| Frontmatter | gray_matter | YAML + body in un passo |
| File watching | notify | Re-index incrementale |
| MCP | rmcp (SDK ufficiale Rust) | brain_search come tool |
| Async runtime | tokio | Server MCP + watcher |
Embeddings multilingue: multilingual-e5-small (384 dims) copre spagnolo e italiano (corsi SGSVP) senza costo per query. Se più avanti serve più recall, bge-m3. Il modello si versiona nel metadata del chunk → reindex globale al cambiarlo.
6. Retrieval ibrido
Non solo vector. BM25 (FTS5) + vector (sqlite-vec), fusi con Reciprocal Rank Fusion, poi rerank opzionale:
- BM25: termini esatti (slugs, nomi di funzione, error strings).
- Vector: similarità semantica ("qualcosa di simile a questo problema").
- RRF: fonde entrambi i ranking senza calibrare pesi.
- Rerank (cross-encoder) sul top-N per la precisione finale.
- Filtri di metadata: per
scope,project,type,tags, freschezza.
Lo stack ibrido (FTS5 + sqlite-vec + embeddings) parte dalla Fase 0, senza tappe intermedie di solo-testo.
7. Sync e multi-progetto
- Conoscenza di progetto: vive nel repo del progetto (
documentation/), versionata con il codice. - Conoscenza globale: repo git dedicato clonato in
~/.pinky/brain. Sync tra le tue macchine =git pull/push(questo NON è "modalità condivisa": resta locale, git è solo il trasporto). Append-only nel diary → merge senza conflitto. - L'indice
brain.dbnon si committa mai; si ricostruisce su ogni macchina.
8. Miglioramenti che vorrei fare (rispetto a quack-brain)
- Staleness decay: penalizzare nel ranking le entry con
last_verifiedvecchio; promemoria di ri-verifica. La conoscenza che invecchia si degrada da sola. - Grafo código ↔ conocimiento: indicizzare i breadcrumb
// Brain: {slug}come backlink. "Quale codice dipende da questo gotcha?" e viceversa. - Dedup semantico: al salvare, rilevare entry quasi-duplicate (cosine > soglia) e proporre merge. Evita che il brain si riempia di gotcha ripetuti.
- Auto-tagging / classificazione via LLM al salvare (type + tags coerenti).
- Query rewriting / HyDE: espandere la query prima di cercare per migliorare il recall.
- Telemetria di utilizzo: registrare quali entry si recuperano e se sono state utili → potare la conoscenza morta (quelle che non si usano mai in 6 mesi).
- Rollup di diary: riassunti settimanali/mensili autogenerati dai diari giornalieri. Il changelog di alto livello si mantiene da solo.
- Eval harness: set di "golden queries" per misurare la qualità del retrieval nel tempo (regressioni di rilevanza al cambiare modello/chunking).
- Citazioni/provenance: ogni risposta dell'agente referenzia lo slug + path della entry che ha usato. Tracciabilità.
- CLAUDE.md evergreen automatizzato: validatore che rifiuta dati volatili (LOC, numeri di riga) in CLAUDE.md, come già richiede la regola 7 di quack-brain.
9. Roadmap per fasi
- Fase 0 — Scaffold + retrieval ibrido completo (workspace Cargo con 4 crate:
pinky-core,pinky,pinky-mcp,pinky-hooks). Modello dei dati +storeSQLite con FTS5 e sqlite-vec +fastembed(multilingual-e5-small) + parse di frontmatter + reindex incrementale + ricerca ibrida (BM25 + vector, RRF). Embeddings dal giorno 1, senza tappa di solo-testo. - Fase 1 — MCP + hooks:
pinky-mcpconbrain_search/brain_save/brain_stats. I 4 hook sottili (SessionStart/PreRead/PreWrite/Stop) appoggiati sul core/MCP. - Fase 2 — Diary + rollup: diary automatico nell'hook Stop, rollup settimanali/ mensili, breadcrumb
// Brain:→ backlink. - Fase 3 — Miglioramenti di qualità: dedup semantico, staleness decay, telemetria di utilizzo, rerank cross-encoder, eval harness di golden queries.
10. Scalabilità e performance (orizzonte 2 anni)
- Volume reale: più progetti × diary giornaliero × gotcha ≈ decine di migliaia di entry in 2 anni. È poco dato: SQLite gestisce milioni di righe e la ricerca vettoriale su decine di migliaia di vettori è banale (<10ms). Il collo di bottiglia non è il volume, è l'organizzazione e il retrieval.
- Re-index incrementale per
content_hash: si ri-embedda solo ciò che è cambiato. - Cache di embeddings su disco per non ricalcolare.
- Partizionamento per scope/progetto per delimitare le ricerche.
- WAL mode in SQLite per letture concorrenti (CLI + MCP + hooks) senza bloccare.
11. Rischi e mitigazioni
- Drift markdown ↔ indice → l'indice è usa e getta + reindex idempotente + hash.
- Qualità degli embeddings multilingue → l'eval harness misura le regressioni (miglioramento 8).
- Rumore di conoscenza (entra spazzatura) → le 4 condizioni di salvataggio + dedup + telemetria di potatura.
- Lock-in del modello di embeddings → versionare il modello nel metadata del chunk; reindex globale al cambiarlo.