Pinky Brain — Plan de arquitectura (local)
Sistema de gestión de conocimiento para agentes + humanos, escalable a varios proyectos durante 2+ años. Evolución de quack-brain. Decisión tomada: modo LOCAL. SQLite embebido, sin servidor, sin red. Sin abstracción de storage (un solo backend real → no se abstrae, YAGNI).
1. Visión y objetivos
- Fuente de verdad legible y versionada: el conocimiento vive en markdown + git. Lo edita el agente con sus tools nativas y lo revisa un humano con
git diff. - Retrieval real: búsqueda híbrida (full-text + semántica), no
grepmanual. - Multi-proyecto: conocimiento de proyecto + conocimiento global entre proyectos.
- Durable 2+ años: el índice es 100% regenerable desde el markdown; ningún dato crítico vive solo en la DB.
- Cero ops: un binario + un archivo
brain.db. Sin Docker, sin daemon, offline.
2. Principios de diseño
- Markdown = fuente de verdad. SQLite = índice derivado, desechable. Si la DB se corrompe →
pinky reindexla reconstruye desde los.md. - SOLID sin sobre-ingeniería. Un módulo
storeconcreto sobre SQLite, **sin trait de abstracción**: no hay un segundo backend, así que no se abstrae. Si algún día apareciera (improbable), se extrae el trait entonces — no antes. - Pull semántico sobre push ciego. El agente busca cuando necesita (tool MCP), en vez de inyectar todo el contexto en cada hook.
- Offline-first. Funciona sin red. Embeddings locales. La red solo entra en el
git pull/pushdel brain global, y es opcional. - Append-only donde se pueda (diary) para que git haga merge sin conflictos.
3. Arquitectura
┌─ 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: librería con toda la lógica (parse, chunk, embed, search, store).store: módulo concreto sobrerusqlite+sqlite-vec+ FTS5. Sin trait.pinky-mcp: exponebrain_search,brain_save,brain_statscomo MCP server. El agente hace pull semántico en vez de grep.pinkyCLI:reindex,search,doctor,stats,gc.pinky-hooks: los 4 hooks de quack-brain (SessionStart/PreRead/PreWrite/Stop), delgados: el retrieval pesado se delega al core/MCP en vez de inyectar todo.
4. Modelo de datos (SQLite)
El frontmatter YAML se mapea a metadata indexada para filtrar:
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 tecnológico
| Necesidad | Elección | Por qué |
|---|---|---|
| Lenguaje | Rust | Latencia <50ms en hooks, un binario, sin runtime |
| Índice | rusqlite + sqlite-vec + FTS5 | Un archivo, híbrido en una query, in-process |
| Embeddings | fastembed (ONNX, multilingual-e5-small) | Local, sin API, sin costo por hook, ES+IT |
| Rerank (opcional) | cross-encoder ONNX (bge-reranker-base) | Precisión tras el retrieval híbrido |
| Frontmatter | gray_matter | YAML + body en un paso |
| File watching | notify | Re-index incremental |
| MCP | rmcp (SDK oficial Rust) | brain_search como tool |
| Async runtime | tokio | Server MCP + watcher |
Embeddings multilingües: multilingual-e5-small (384 dims) cubre español e italiano (cursos SGSVP) sin costo por query. Si más adelante hace falta más recall, bge-m3. El modelo se versiona en la metadata del chunk → reindex global al cambiarlo.
6. Retrieval híbrido
No vector solo. BM25 (FTS5) + vector (sqlite-vec), fusionados con Reciprocal Rank Fusion, luego rerank opcional:
- BM25: términos exactos (slugs, nombres de función, error strings).
- Vector: similitud semántica ("algo parecido a este problema").
- RRF: fusiona ambos rankings sin calibrar pesos.
- Rerank (cross-encoder) sobre el top-N para precisión final.
- Filtros de metadata: por
scope,project,type,tags, frescura.
El stack híbrido (FTS5 + sqlite-vec + embeddings) va desde la Fase 0, sin etapas intermedias de solo-texto.
7. Sync y multi-proyecto
- Conocimiento de proyecto: vive en el repo del proyecto (
documentation/), versionado con el código. - Conocimiento global: repo git dedicado clonado en
~/.pinky/brain. Sync entre tus máquinas =git pull/push(esto NO es "modo compartido": sigue siendo local, git es solo el transporte). Append-only en diary → merges sin conflicto. - El índice
brain.dbnunca se commitea; se reconstruye en cada máquina.
8. Mejoras que me gustaría hacer (sobre quack-brain)
- Staleness decay: penalizar en el ranking entradas con
last_verifiedviejo; recordatorios de re-verificación. El conocimiento que envejece se degrada solo. - Grafo código ↔ conocimiento: indexar los breadcrumbs
// Brain: {slug}como backlinks. "¿Qué código depende de este gotcha?" y viceversa. - Dedup semántico: al guardar, detectar entradas casi-duplicadas (cosine > umbral) y proponer merge. Evita que el brain se llene de gotchas repetidos.
- Auto-tagging / clasificación vía LLM al guardar (type + tags consistentes).
- Query rewriting / HyDE: expandir la query antes de buscar para mejorar recall.
- Telemetría de uso: registrar qué entradas se recuperan y si fueron útiles → podar conocimiento muerto (las que nunca se usan en 6 meses).
- Rollups de diary: resúmenes semanales/mensuales autogenerados desde los diarios diarios. El changelog de alto nivel se mantiene solo.
- Eval harness: set de "golden queries" para medir calidad de retrieval a lo largo del tiempo (regresiones de relevancia al cambiar modelo/chunking).
- Citas/provenance: toda respuesta del agente referencia el slug + path de la entrada que usó. Trazabilidad.
- CLAUDE.md evergreen automatizado: validador que rechaza datos volátiles (LOC, números de línea) en CLAUDE.md, como ya pide la regla 7 de quack-brain.
9. Roadmap por fases
- Fase 0 — Scaffold + retrieval híbrido completo (workspace Cargo con 4 crates:
pinky-core,pinky,pinky-mcp,pinky-hooks). Modelo de datos +storeSQLite con FTS5 y sqlite-vec +fastembed(multilingual-e5-small) + parse de frontmatter + reindex incremental + búsqueda híbrida (BM25 + vector, RRF). Embeddings desde el día 1, sin etapa de solo-texto. - Fase 1 — MCP + hooks:
pinky-mcpconbrain_search/brain_save/brain_stats. Los 4 hooks delgados (SessionStart/PreRead/PreWrite/Stop) apoyados en el core/MCP. - Fase 2 — Diary + rollups: diary automático en el hook Stop, rollups semanales/ mensuales, breadcrumbs
// Brain:→ backlinks. - Fase 3 — Mejoras de calidad: dedup semántico, staleness decay, telemetría de uso, rerank cross-encoder, eval harness de golden queries.
10. Escalabilidad y performance (horizonte 2 años)
- Volumen real: varios proyectos × diary diario × gotchas ≈ decenas de miles de entradas en 2 años. Es poco dato: SQLite maneja millones de filas y la búsqueda vectorial sobre decenas de miles de vectores es trivial (<10ms). El cuello de botella no es el volumen, es la organización y el retrieval.
- Re-index incremental por
content_hash: solo se re-embebe lo que cambió. - Caché de embeddings en disco para no recalcular.
- Particionado por scope/proyecto para acotar búsquedas.
- WAL mode en SQLite para lecturas concurrentes (CLI + MCP + hooks) sin bloquear.
11. Riesgos y mitigaciones
- Drift markdown ↔ índice → el índice es desechable + reindex idempotente + hash.
- Calidad de embeddings multilingüe → eval harness mide regresiones (mejora 8).
- Ruido de conocimiento (entra basura) → las 4 condiciones de guardado + dedup + telemetría de poda.
- Lock-in de modelo de embeddings → versionar el modelo en la metadata del chunk; reindex global al cambiarlo.