Pinky Brain — Plano de arquitetura (local)

Sistema de gestão de conhecimento para agentes + humanos, escalável a vários projetos durante 2+ anos. Evolução do quack-brain. Decisão tomada: modo LOCAL. SQLite embarcado, sem servidor, sem rede. Sem abstração de storage (um único backend real → não se abstrai, YAGNI).

1. Visão e objetivos

  • Fonte da verdade legível e versionada: o conhecimento vive em markdown + git. É editado pelo agente com suas tools nativas e revisado por um humano com git diff.
  • Retrieval real: busca híbrida (full-text + semântica), não grep manual.
  • Multiprojeto: conhecimento de projeto + conhecimento global entre projetos.
  • Durável 2+ anos: o índice é 100% regenerável a partir do markdown; nenhum dado crítico vive apenas na DB.
  • Zero ops: um binário + um arquivo brain.db. Sem Docker, sem daemon, offline.

2. Princípios de design

  1. Markdown = fonte da verdade. SQLite = índice derivado, descartável. Se a DB se corromper → pinky reindex a reconstrói a partir dos .md.
  2. SOLID sem sobre-engenharia. Um módulo store concreto sobre SQLite, **sem trait de abstração**: não há um segundo backend, então não se abstrai. Se algum dia aparecer (improvável), extrai-se o trait então — não antes.
  3. Pull semântico sobre push cego. O agente busca quando precisa (tool MCP), em vez de injetar todo o contexto em cada hook.
  4. Offline-first. Funciona sem rede. Embeddings locais. A rede só entra no git pull/push do brain global, e é opcional.
  5. Append-only onde for possível (diary) para que o git faça merge sem conflitos.

3. Arquitetura

┌─ 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: biblioteca com toda a lógica (parse, chunk, embed, search, store).
  • store: módulo concreto sobre rusqlite + sqlite-vec + FTS5. Sem trait.
  • pinky-mcp: expõe brain_search, brain_save, brain_stats como MCP server. O agente faz pull semântico em vez de grep.
  • CLI pinky: reindex, search, doctor, stats, gc.
  • pinky-hooks: os 4 hooks do quack-brain (SessionStart/PreRead/PreWrite/Stop), finos: o retrieval pesado é delegado ao core/MCP em vez de injetar tudo.

4. Modelo de dados (SQLite)

O frontmatter YAML é mapeado para 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

NecessidadeEscolhaPor quê
LinguagemRustLatência <50ms nos hooks, um binário, sem runtime
Índicerusqlite + sqlite-vec + FTS5Um arquivo, híbrido em uma query, in-process
Embeddingsfastembed (ONNX, multilingual-e5-small)Local, sem API, sem custo por hook, ES+IT
Rerank (opcional)cross-encoder ONNX (bge-reranker-base)Precisão após o retrieval híbrido
Frontmattergray_matterYAML + body em um passo
File watchingnotifyRe-index incremental
MCPrmcp (SDK oficial Rust)brain_search como tool
Async runtimetokioServer MCP + watcher

Embeddings multilíngues: multilingual-e5-small (384 dims) cobre espanhol e italiano (cursos SGSVP) sem custo por query. Se mais adiante for preciso mais recall, bge-m3. O modelo é versionado na metadata do chunk → reindex global ao trocá-lo.

6. Retrieval híbrido

Não só vetor. BM25 (FTS5) + vetor (sqlite-vec), fundidos com Reciprocal Rank Fusion, depois rerank opcional:

  1. BM25: termos exatos (slugs, nomes de função, error strings).
  2. Vetor: similaridade semântica ("algo parecido com este problema").
  3. RRF: funde ambos os rankings sem calibrar pesos.
  4. Rerank (cross-encoder) sobre o top-N para precisão final.
  5. Filtros de metadata: por scope, project, type, tags, frescor.

O stack híbrido (FTS5 + sqlite-vec + embeddings) vai desde a Fase 0, sem etapas intermediárias de só-texto.

7. Sync e multiprojeto

  • Conhecimento de projeto: vive no repo do projeto (documentation/), versionado com o código.
  • Conhecimento global: repo git dedicado clonado em ~/.pinky/brain. Sync entre suas máquinas = git pull/push (isto NÃO é "modo compartilhado": continua sendo local, o git é apenas o transporte). Append-only no diary → merges sem conflito.
  • O índice brain.db nunca é commitado; é reconstruído em cada máquina.

8. Melhorias que eu gostaria de fazer (sobre o quack-brain)

  1. Staleness decay: penalizar no ranking entradas com last_verified antigo; lembretes de reverificação. O conhecimento que envelhece se degrada sozinho.
  2. Grafo código ↔ conhecimento: indexar os breadcrumbs // Brain: {slug} como backlinks. "Que código depende deste gotcha?" e vice-versa.
  3. Dedup semântico: ao salvar, detectar entradas quase-duplicadas (cosine > limiar) e propor merge. Evita que o brain se encha de gotchas repetidos.
  4. Auto-tagging / classificação via LLM ao salvar (type + tags consistentes).
  5. Query rewriting / HyDE: expandir a query antes de buscar para melhorar o recall.
  6. Telemetria de uso: registrar que entradas são recuperadas e se foram úteis → podar conhecimento morto (as que nunca são usadas em 6 meses).
  7. Rollups de diary: resumos semanais/mensais autogerados a partir dos diários diários. O changelog de alto nível se mantém sozinho.
  8. Eval harness: conjunto de "golden queries" para medir a qualidade do retrieval ao longo do tempo (regressões de relevância ao trocar modelo/chunking).
  9. Citações/provenance: toda resposta do agente referencia o slug + path da entrada que usou. Rastreabilidade.
  10. CLAUDE.md evergreen automatizado: validador que rejeita dados voláteis (LOC, números de linha) no CLAUDE.md, como já pede a regra 7 do quack-brain.

9. Roadmap por fases

  • Fase 0 — Scaffold + retrieval híbrido completo (workspace Cargo com 4 crates: pinky-core, pinky, pinky-mcp, pinky-hooks). Modelo de dados + store SQLite com FTS5 e sqlite-vec + fastembed (multilingual-e5-small) + parse de frontmatter + reindex incremental + busca híbrida (BM25 + vector, RRF). Embeddings desde o dia 1, sem etapa de só-texto.
  • Fase 1 — MCP + hooks: pinky-mcp com brain_search/brain_save/brain_stats. Os 4 hooks finos (SessionStart/PreRead/PreWrite/Stop) apoiados no core/MCP.
  • Fase 2 — Diary + rollups: diary automático no hook Stop, rollups semanais/ mensais, breadcrumbs // Brain: → backlinks.
  • Fase 3 — Melhorias de qualidade: dedup semântico, staleness decay, telemetria de uso, rerank cross-encoder, eval harness de golden queries.

10. Escalabilidade e performance (horizonte 2 anos)

  • Volume real: vários projetos × diary diário × gotchas ≈ dezenas de milhares de entradas em 2 anos. É pouco dado: o SQLite lida com milhões de linhas e a busca vetorial sobre dezenas de milhares de vetores é trivial (<10ms). O gargalo não é o volume, é a organização e o retrieval.
  • Re-index incremental por content_hash: só se re-embeda o que mudou.
  • Cache de embeddings em disco para não recalcular.
  • Particionamento por scope/projeto para restringir buscas.
  • WAL mode no SQLite para leituras concorrentes (CLI + MCP + hooks) sem bloquear.

11. Riscos e mitigações

  • Drift markdown ↔ índice → o índice é descartável + reindex idempotente + hash.
  • Qualidade de embeddings multilíngue → eval harness mede regressões (melhoria 8).
  • Ruído de conhecimento (entra lixo) → as 4 condições de salvamento + dedup + telemetria de poda.
  • Lock-in do modelo de embeddings → versionar o modelo na metadata do chunk; reindex global ao trocá-lo.