sqlite-graphrag 1.2.8

Persistent GraphRAG memory for Claude Code, Codex, Cursor, and 27 AI agents — one self-contained ~19 MiB Rust binary, zero daemon. Never re-explain your codebase again. Hybrid retrieval (FTS5 BM25 + cosine similarity + multi-hop graph traversal) surfaces the right memory in milliseconds. Embedding and entity enrichment run as parallel REST calls against your cloud LLM — no fragile headless subprocesses, no ONNX runtime, no model downloads. Soft-delete with full version history, transactional atomic writes, BLAKE3-tracked mutations. OAuth-only: raw API keys ABORT the spawn.
Documentation
# sqlite-graphrag (v1.2.8 LLM-Only One-Shot)

> Memória persistente para 27 agentes de IA em um único binário Rust de ~19 MiB (v1.2.2 superfície agent-native; CAPA v1.2.1; schema v17 pela migration V017; DEFAULT_EMBEDDING_DIM=1024; XDG config; harness e2e_offline_v120)

27 agentes de IA. Um binário de ~19 MiB. Zero download de modelo. sqlite-graphrag v1.2.8 (atual — crate `version = "1.2.8"`; a família morta `pending` removida, o catálogo de topo em 50 verbos e o de schemas em 76 contratos, o campo de contrato `fts_bm25` nunca preenchido removido, o reaper de órfãos reconstruído sobre `sysinfo` para o macOS parar de reportar zero sem medir, e um gate de idioma do código; herda da v1.2.8 — o gate de links do rustdoc (GAP-SG-228) e o conserto da superfície NDJSON (GAP-SG-229); contrato de entrada do remember (GAP-SG-216) — o nome pode ser posicional ou `--name`, todo `entity_type` não canônico é reportado em `warnings` e recusável com `--strict-entity-types` em `remember` e `remember-batch`, `--dry-run` reporta os tamanhos do grafo lido e cada rótulo não canônico, e a forma de fio de `--graph-stdin` / `--graph-file` mais o envelope de dry-run passam a ser os schemas `graph-input` e `remember-dry-run`; `--count-only` recusado com exit 2 sobre página cortada por teto de consulta, sobre stream por linha, e suprimido após escrita sem array de resultado; `count_scope` reporta `page`; `export` / `embedding list` declaram o teto; contrato de stream NDJSON (GAP-SG-215) — linhas de registro não carregam `agent_surface`, a linha de sumário carrega o único registro do stream e nunca é moldada, e todo knob de conjunto recusa antes do primeiro byte; superfície de saída agent-native mais `--no-input`; herda selo CAPA da fila enrich: isolamento de claim por namespace; `--until-empty` conta só op+ns; `--force-redescribe` reabre skipped/done; re-embed `LENGTH(embedding)=dim*4` + reconciliação de zumbis; strip de `entity:` no enqueue; validação de chunk no ns; CAPA-D marcadores compostos de configuration file; DEFAULT_EMBEDDING_DIM=1024; vocabulário aberto de `entity_type` (GAP-SG-277, GAP-SG-278) — qualquer rótulo é gravado como escrito, os treze tipos canônicos são recomendados, `--strict-entity-types` restringe a escrita a eles — pela migration **V017**, então o schema vai de **v16 para v17**; XDG `config set|get|list|unset` + `config list --effective`; OpenRouter via `network.openrouter.*`; orçamento único de embedding via XDG `embedding.timeout_secs` (padrão 30s, `--openrouter-timeout` vence); EntityType `module`→Concept; remember-batch exige description; `pending-embeddings status` + `cache stats`; `purge --now`; `related_to`→`related`; `enrich --list-skipped` / `--requeue-skipped`; GAP-SG-139 `--db` no-op em folhas host; help scrub sem product env; harness offline `scripts/e2e_offline_v120.sh` **20/20**; residual honesto: GAP-SG-89 LOC≤800 majoritariamente fechado, monólitos residuais opcionais; live LQ=operador; a v1.1.06 fechou GAP-ENTITY-CONNECT-SCAN-CARTESIAN (scan O(k), pair keys, timeout no 1º scan exit 1, scan_start; v1106, ADR-0066); a v1.1.05 fechou os cinco bugs do incidente deep-research de sujeito único: Bug 1 token único → sub-queries aspect; Bug 2 `--output` atomwrite + `--quiet` + contrato stdout-JSON/stderr-logs; Bug 3 `graph traverse --fuzzy` + sugestões; Bug 4 `merge-entities` self-ref pré-DB; Bug 5 `link --from-id`/`--to-id` + rejeita nomes só dígitos; a v1.1.04 fecha os dois gaps estruturais rastreados no gaps.md: o GAP-001 do panic de nested Tokio runtime no `deep-research` está corrigido (o entry point síncrono computa os embeddings por sub-query ANTES de construir seu runtime dedicado via `compute_sub_embeddings`, e os três caminhos de embedding OpenRouter em embedder.rs adotam o padrão de reentrada `Handle::try_current` + `block_in_place`; `ingest_opencode` também guardado), o GAP-002 do `entity-connect` agora converge (a nova tabela `entity_connect_seen` registra o veredito do LLM por par, o scanner exclui pares avaliados, o `count_operation_backlog` reporta um backlog real O(n), o `--until-empty` atinge `eligible_remaining == 0`); a v1.1.03 fecha os seis bugs que bloqueavam operadores catalogados no gaps.md mais o portão V8 de corpo excessivamente grande; o manifesto do crate carrega `version = "1.1.3"` porque o parser SemVer rejeita zero à esquerda no componente patch; sem migração, schema v15; a v1.1.02 fecha os dois gaps residuais deixados após v1.1.01: o argumento depreciado --gliner-variant (no-op) é totalmente removido (clap o rejeita com exit 2, plumbing morto do GLiNER deletado), o teto de tokens de embedding vira o erro tipado exit-6 `AppError::TooManyTokens { tokens, limit }` enforced na borda de escrita, um teste de regressão guarda o dispatch de re-embed de entidades, e `enrich --prune-dead-entity-orphans` remove linhas dead-letter entity-keyed da fila sidecar; a v1.1.01 fechou o roteiro de 12 prioridades do gaps.md da auditoria do banco de produção: P1 vetores de entidade não são mais pulados silenciosamente na escrita — com `--embedding-backend openrouter` a cadeia de embedding de entidade resolve para `[OpenRouter]` mesmo sob `--llm-backend none` (REST, sem subprocesso) e uma guarda de vetor vazio em `upsert_entity_vec`/`upsert_chunk_vec`/`memories::upsert_vec` nunca persiste BLOB vazio; P2 backfill retroativo via `enrich --operation re-embed --target memories|entities|chunks|all` (padrão `memories`) com `scan_backlog` por alvo no `--status`; P3 `graph recompute-degree` reconcilia o cache `entities.degree` com as contagens reais de arestas em uma transação única, com `--dry-run` e envelope `{total, updated, zeroed, unchanged}`; P4 `reclassify-relation --literal-from` casa a relação armazenada VERBATIM (sem normalização do clap) para migrar arestas legadas com hífen como `applies-to`; P5 `merge-entities --ids/--into-id` e `rename-entity --id` para desambiguação por ID com escopo de namespace; P6 `health --json` ganha `vec_*_missing`/`vec_*_coverage_pct` e `embedding status --json` ganha contadores `*_missing` por tabela; P7 `EntityType` com `Deserialize` manual, mensagem rica com os 13 valores válidos e validação precoce no `--graph-stdin`; P10 predicados do re-embed também selecionam `dim` divergente e blob vazio nas três tabelas de vetor; P11 variantes tipadas `AppError::BodyTooLarge`/`AppError::TooManyChunks` com bytes/chunks + limite no envelope (exit 6 preservado); P12 `ingest --name-prefix` com validação de teto; User-Agent `sqlite-graphrag/1.1.2` via CARGO_PKG_VERSION; v1.1.0 GAP-SG-70..78 corrigiu o backlog dead-letter do enrich na raiz: completions truncadas (`finish_reason=length`) retentam com `max_tokens` maior, a classificação de retry é tipada por variante de `AppError` (sem match por substring), `--list-dead` ganha diagnósticos `finish_reason`/`input_tokens`/`output_tokens`, `--status` ganha `scan_backlog` por operação, e o loop de dequeue é limitado sob contenção `SQLITE_BUSY` (exit 15); v1.0.99 GAP-SG-67 removeu a poda destrutiva do degree-cap + a flag --max-entity-degree de remember/link (QUEBRANTE, clap exit 2; escritas puramente aditivas, nunca deletam arestas); GAP-SG-68 alinhou o doc de `graph entities --sort-by degree` ao comportamento ascendente (`--order desc` para o mais-conectado-primeiro); GAP-SG-69 `enrich body-enrich --until-empty` converge (o scan pula corpos vetados pela preservação); sem migração, schema v15; v1.0.97 recuperação dead-letter no enrich + ergonomia de escrita: `--requeue-dead`/`--list-dead`/`--ignore-backoff`/`--prune-dead-orphans` (deleta entradas dead de memory da fila cujo item_key não existe mais no banco principal, GAP-SG-66, ADR-0058), `--status`/`--list-dead`/`--requeue-dead`/`--prune-dead-orphans` rodam sem `--operation`/`--mode`, nova operação `augment-bindings` (exige `--names`), `body-extract --body-extract-graph-only`, default de `--max-attempts` 8, default de `--openrouter-timeout` 600s; `remember --graph-file`/`--strict-name`/`--replace-graph`; `ingest --force-merge` com dedup por `body_hash` e auto-split nativo de corpos grandes; `read --format raw`; `unlink --memory/--entity`; objeto `coverage` em `embedding status`; `total_memories` no topo de `stats`; `--db` vem depois do subcomando e é o canal canônico de alvo, com a chave XDG `db.path` como padrão (GAP-20, SG-32; a variável de produto `SQLITE_GRAPHRAG_DB_PATH` é **histórica** e **não é lida** em runtime); v1.0.96 dead-letter no enrich + fan-out REST bounded (ADR-0055): `enrich --until-empty` roda um loop interno scan->drain até a fila elegível esvaziar ou `--max-runtime` expirar, uma fila dead-letter adiciona as colunas `error_class`/`next_retry_at` mais o status terminal `dead` para o backlog convergir estritamente (GAP-ENRICH-BACKLOG-CONVERGE), novas flags `--max-attempts`/`--status`/`--rest-concurrency`, e o embedding REST OpenRouter faz fan-out por lote de 32 chunks via um `tokio::task::JoinSet` bounded (GAP-OPENROUTER-REST-CONCURRENCY); v1.0.95 enrich chat OpenRouter (ADR-0054, GAP-OR-ENRICH): `enrich --mode openrouter` roteia o judge do enrich para o endpoint REST `/chat/completions` do OpenRouter sem CLI local; v1.0.94 remediação de quatro gaps: dim de embedding padrão 384, enrich --mode obrigatório, timeout de embedding 300s, embedding de entidades honra o backend selecionado; v1.0.93 adicionou backend de embedding via API REST OpenRouter com `--embedding-backend openrouter` com latência de ~100-500ms vs 20-60s do subprocesso LLM, flag `--enrich-after` para ingest, 5 correções BUG-OR; v1.0.91 corrigiu isolamento de CWD em spawn, BUG-17 correção de degree; v1.0.90 adicionou backend OpenCode; v1.0.89 schema drift via schemars; v1.0.87 camada de validação pre-flight; v1.0.79 fundação LLM-only) entrega a qualquer assistente de programação IA uma camada de memória local, rápida e privada, baseada em um único arquivo SQLite. Geração de embedding usa SOMENTE a API REST OpenRouter (`--embedding-backend openrouter`); os backends de subprocesso headless claude/codex/opencode foram removidos, e `--llm-backend` aceita apenas `open-router` (grafado COM hífen) ou `none`; a grafia `openrouter` é recusada pelo clap com exit 2. Sem subprocesso, sem daemon, sem runtime ONNX, sem modelo local. Recuperação nativa em grafo. Saída JSON determinística pronta para orquestração em pipelines.

## v1.2.7 Proveniência do Alvo + Leituras com Faixa Validada (sem migração de schema)
- Crate **1.2.7**; schema permanece em **v16**; fixe `=1.2.7`
- Todo verbo com efeito colateral publica a proveniência do alvo no envelope: `db_path_source` e `db_path_resolved`
- Treze argumentos numéricos de leitura passam a ter faixa em tempo de parse; valor fora de faixa é recusado pelo clap com **exit 2** antes de qualquer trabalho
- Top-k (`-k`, `--k`, `--limit` de leitura, `--max-results`): **1..=4096**
- `export --limit`, `pending-embeddings --limit`, `embedding --limit`: **1..=1000000** (**histórico**: `pending --limit` estava nesta lista até a v1.2.8 remover a família `pending` inteira; o binário responde `unrecognized subcommand` com exit 2)
- `--max-hops` / `--hops` / `--depth`: **1..=64**
- `--max-sub-queries`: **1..=64**
- Os lints de rustdoc são negados pela tabela `[lints.rustdoc]` do manifesto

## v1.2.6 Correções da Superfície de Saída Agent-Native (sem migração de schema)
- Crate **1.2.6**; schema permanece em **v16**; fixe `=1.2.6`
- O `--filter` passa a avaliar o conjunto inteiro em vez da página emitida
- Uma chave inexistente em `--select` / `--filter` é recusada com **exit 2** em vez de devolver payload vazio com exit 0
- O array de resultado deixa de ser fabricado quando o envelope não traz nenhum
- Um knob declarado sem efeito deixa de devolver exit 0

## Superfície de Saída Agent-Native v1.2.2 (sem migração de schema)
- Crate **1.2.2**; schema permanece em **v16**; pin `=1.2.2`
- Oito flags globais remodelam o envelope de todo subcomando em um único ponto: `--select`/`--fields`, `--filter`, `--max-items`, `--sort`, `--dedupe-by`, `--count-only`, `--truncate-content`, `--max-output-bytes`
- Ordem fixa: filter → sort → dedupe → max-items → select → count-only → truncate-content → max-output-bytes
- Gramática do `--filter`: `chave=valor`, `chave!=valor`, `chave~substring`; `==` sinônimo de `=`; repita para conjugar com AND; expressão malformada sai com exit **2**
- `--max-items` limita só a emissão, depois do filtro — DISTINTA do `--limit` por subcomando e do `-k`, que limitam a consulta
- Envelopes de falha (`error: true` / `ok: false`) **nunca** são filtrados; documentos `$schema` passam intactos; streams NDJSON contornam a superfície
- Truncagem nunca é silenciosa: registrada em `agent_surface`, levanta `truncated` de topo; `--max-output-bytes` descarta elementos do fim, nunca fatia o texto JSON
- `--no-input` recusa stdin de forma declarativa — todo leitor de stdin falha de antemão com **exit 1** (`AppError::Validation`; envelope medido com `code: 1`); precedência flag > XDG `cli.no_input` > `false`
- Subcomando `schema` — `sqlite-graphrag schema` lista TODOS os **76** contratos como NDJSON `{"id","invoke"}`, um por linha; `sqlite-graphrag schema --name <ID>` emite aquele documento JSON Schema; `<ID>` desconhecido sai com **exit 4**; documentos `$schema` são isentos da superfície de saída, então qualquer flag global encadeia com segurança
- **Histórico** (o valor `claude-code` saiu com os backends de subprocesso; `ingest --mode` aceita só `none` e o clap recusa qualquer outro com exit 2) — NDJSON do `--mode claude-code` unificado com o pipeline padrão (GAP-SG-148 item 5): `status` por arquivo é **`indexed`** e não `done`; o sumário reporta **`files_total`/`files_succeeded`/`files_failed`/`files_skipped`** e não `completed`
- Precedência dos tetos: flag > XDG `agent_surface.max_items` / `agent_surface.truncate_content` / `agent_surface.max_output_bytes` > `0` (desligado)
- Sem nenhuma flag definida, o envelope é idêntico byte a byte à saída da v1.2.1

## Selo CAPA v1.2.1 (fila enrich, sem migração de schema)
- Crate **1.2.1**; schema permanece em **v16**; pin `=1.2.1`
- Isolamento de claim por namespace: claim / contagem / resume filtram por `operation` **e** `namespace`
- `--until-empty` conta pendentes **só desta op+namespace**
- `--force-redescribe` reabre `skipped`/`done` uma vez por processo (`reopen_force_redescribe_candidates`; nunca `dead`)
- Re-embed: elegibilidade via `LENGTH(embedding) = dim*4`; `reconcile_satisfied_reembed_pending` limpa zumbis
- Enqueue: strip de `entity:` na lookup; valida chaves de chunk no namespace alvo; CAPA-D só marcadores compostos de "configuration file"
- Regressões: `enqueue_candidate_accepts_entity_prefixed_reembed_key`, `dequeue_next_pending_isolates_by_namespace`; suíte da fila 38 OK

## Arquitetura v1.0.85 (apenas LLM, substitui v1.0.79)
### Build one-shot sem modelo local
- Todo build é LLM-only e one-shot: não há daemon (removido na v1.0.79), não há runtime ONNX, não há download de modelo
- Features `embedding-legacy`, `ner-legacy` e `full` REMOVIDAS na v1.0.79 com as dependências opcionais fastembed/ort/ndarray/tokenizers/hf-hub
- Pipeline de embedding (G42/v1.2.0): dimensionalidade padrão **1024** (`--embedding-dim` / XDG `embedding.dim`, faixa [8, 4096]; precedência flag > XDG > `schema_meta.dim` > 1024); chamadas em lote (bases de calibração de 8 chunks / 25 nomes de entidade em dim 64, adaptadas por clamp(base×64/dim, 1, base) — G44); paralelismo limitado via `--llm-parallelism` em `remember` (padrão 4), `ingest` (padrão 2) e `edit`
- Re-embed canônico: `enrich --operation re-embed --limit N --resume` (v1.1.01 adiciona `--target memories|entities|chunks|all`, padrão `memories`); memória individual via `edit --force-reembed`
- Nota **histórica** (v1.0.79 a v1.1.x): todo subprocesso LLM usava `kill_on_drop` mais `SQLITE_GRAPHRAG_EMBED_TIMEOUT_SECS`; os subprocessos foram **removidos** na v1.2.0 e o orçamento por requisição hoje é a flag global `--openrouter-timeout` ou a chave XDG `embedding.timeout_secs`
- Busca vetorial: cosseno em Rust puro sobre embeddings BLOB (`memory_embeddings`, `entity_embeddings`, `chunk_embeddings`); a extensão `sqlite-vec` não existe mais

- Leia este documento em [inglês (EN)](llms.txt).


## Arquitetura v1.0.86 → v1.0.91 (Pre-flight, Schema Drift, Flag Parity, Backend OpenCode, Isolamento de Spawn)
- v1.0.86 adicionou 10 subcomandos do pipeline LLM: `pending list|show|cleanup`, `embedding status|list|abandon`, `pending-embeddings list|process`, `slots status|release`. Novas flags globais: `--max-concurrency`, `--wait-lock`, `--llm-parallelism`, `--ingest-parallelism`, --graceful-shutdown-secs (**histórico**: o parser da v1.2.8 não a define), `--skip-embedding-on-failure`. Semáforo de slots host-wide via `fs4` com `fcntl(F_SETLK)` em Unix e `LockFileEx` em Windows (ADR-0036, 0037, 0038, 0039, 0040)
- v1.0.87 adicionou a camada de validação pre-flight: `src/spawn/preflight.rs` (≥200 linhas, 7 guards, 15 testes unitários) porta todo spawn de subprocesso LLM ANTES do fork. Nova variante `AppError::PreFlightFailed(PreFlightError)` com `exit_code() == 16` (`EX_CONFIG`, `is_permanent() == true`) — **apenas histórico**: `PreFlightFailed` tem zero ocorrências em `src/` hoje, o módulo `src/spawn/preflight.rs` não existe mais, e não há exit 16; o `exit_code()` de `src/errors.rs` mapeia para 1, 3, 4, 5, 6, 10, 11, 12, 14 e 20, além do 2 do clap e do 75 de slot. Os 7 guards em ordem: `check_argv_size`, `check_binary_exists`, `check_mcp_config_inline`, `check_mcp_config_path`, `check_walkup_mcp_json`, `check_output_buffer`, `check_claude_config_dir`. Bypass via `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1` (ADR-0045, GAP-META-005). **Histórica**: a camada pre-flight e essa variável foram **removidas** na v1.2.0 junto com os backends de subprocesso, e nenhuma flag ou chave XDG as substitui
- v1.0.88 hotfixes: BUG-11 (CRÍTICO) falha de pre-flight em `extract/llm_embedding.rs:563-565` agora propaga para `remember` via `embed_via_backend_strict`; BUG-12 (MÉDIO) enforço OAuth-only emite 1 linha stderr (era 2); BUG-13 (MÉDIO) `link --create-missing` respeita validação de nome de entidade. 11 novos regression tests (ADR-0046, ADR-0047)
- v1.0.89 schema drift + flag parity: `health.schema.json` regenerado via `schemars` derive macro (política Must-Ignore por RFC 7493 I-JSON, `additionalProperties: true`); 17 novos campos adicionados. 5 subcomandos agora aceitam `--db <PATH>`: `embedding status|list|abandon`, `pending list|show`. `migrate --dry-run --json` reporta migrações pendentes sem aplicar. `codex-models --json` aceito como no-op (**histórico**: o subcomando não existe na v1.2.8 — o binário responde `unrecognized subcommand` com exit 2). `ingest --auto-describe` (padrão true) extrai descrição da primeira linha significativa do corpo. `health --namespace <NS> --json` filtra contagens. Tamanho do binário 14.6 MiB documentado em `Cargo.toml:6` (ADR-0048, ADR-0049)
- v1.0.90 integração do backend OpenCode (ADR-0051): terceiro backend LLM `opencode run` ao lado de `codex exec` e `claude -p`. Novo `--llm-backend opencode`, `--mode opencode` para ingest/enrich, flags `--opencode-binary/model/timeout` e variáveis `SQLITE_GRAPHRAG_OPENCODE_*` (**histórico**: backend e variáveis **removidos** na v1.2.0). Cadeia de fallback estendida para `codex → claude → opencode → none`. Remediação de 24 bugs incluindo ativação de `--skip-embedding-on-failure`, correção de flag morto `--llm-fallback`, dessincronia FTS em batch/enrich, compilação cruzada Windows, timeout hardcoded de embedding, paginação de `list`, enforço de `--yes` em comandos destrutivos. 875+ testes passando
- v1.0.91 isolamento de CWD em spawn (**histórico**: o diretório `src/spawn/` inteiro saiu na v1.2.0 com os backends de subprocesso e não existe mais na árvore): `spawn_isolation_dir()` e `apply_cwd_isolation()` em `src/spawn/mod.rs` definiam `current_dir` para diretório temporário efêmero e `CLAUDE_CONFIG_DIR` para o mesmo diretório em TODOS os 10 spawn sites de produção. Subprocessos LLM não herdam mais `.mcp.json` do projeto do chamador — embedding funciona zero-config em qualquer diretório. `cleanup_spawn_dir()` remove o diretório temporário ao final do processo. BUG-17 corrigiu inflação de `entities.degree`: `increment_degree()` substituído por `recalculate_degree()` após inserção de todas as relações. BUG-15/16 corrigiram 8 JSON schemas com valores de enum e campos ausentes


## Arquitetura v1.0.93 (Backend de Embedding OpenRouter)
- v1.0.93 adiciona backend de embedding via API REST OpenRouter com a flag global `--embedding-backend` (ADR-0052, GAP-OR-INGEST). Ela nasceu como `auto|openrouter|llm`; o valor `llm` é **histórico** e saiu com os backends de subprocesso na v1.2.0, então o binário aceita só `auto` e `openrouter`
- Enum `EmbeddingBackendChoice` (`Auto`, `Openrouter`, `Llm`) é separado de `LlmBackendChoice` — seleção de embedding e extração LLM são independentes
- `--embedding-model MODEL` seleciona o modelo OpenRouter; SEM padrão — usuário DEVE especificar ao usar `--embedding-backend openrouter`
- Chave: flag `--openrouter-api-key` ou XDG `config add-key openrouter` (G-T-XDG-04: `OPENROUTER_API_KEY` não é lida em runtime; tratada via `secrecy::SecretString` com zeroize-on-drop, JAMAIS logada)
- API REST direta via `reqwest+rustls-tls` para `POST https://openrouter.ai/api/v1/embeddings` — ~100-500ms vs 20-60s do subprocesso LLM
- Truncamento MRL (Matryoshka Representation Learning) para `--embedding-dim` configurado (padrão 1024)
- 10 modelos verificados: `qwen/qwen3-embedding-4b`, `qwen/qwen3-embedding-8b`, `nvidia/llama-nemotron-embed-vl-1b-v2:free`, `openai/text-embedding-3-small`, `openai/text-embedding-3-large`, `perplexity/pplx-embed-v1-0.6b`, `mistralai/mistral-embed-2312`, `baai/bge-m3`, `google/gemini-embedding-001`, `google/gemini-embedding-2`
- `EmbeddingBackendChoice` propagado para TODOS os 8 comandos de embedding: `remember`, `remember-batch`, `ingest`, `recall`, `edit`, `restore`, `hybrid-search`, `deep-research`
- Flag `--enrich-after` no `ingest` dispara `enrich --operation memory-bindings` sequencialmente após todos os arquivos serem ingeridos
- BUG-OR-1 a BUG-OR-5: input_type por modelo, detecção MRL, validação de modelo, retry em HTTP 200 malformado, override de dimensão


## Documentação Principal
### Fontes canônicas em inglês para ingestão por LLMs
- [README](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/README.md): instalação completa, referência de comandos, tabela de integrações e FAQ
- [HOW_TO_USE](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/docs/HOW_TO_USE.md): passo a passo da instalação até o primeiro hybrid search em 60 segundos
- [COOKBOOK](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/docs/COOKBOOK.md): 34+ receitas cobrindo ingestão, recuperação, travessia de grafo, backup, auditoria, XDG config (v1.2.0) e o scan O(k) do entity-connect (v1.1.06)
- [AGENTS](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/docs/AGENTS.md): guia persuasivo para autores de agentes IA: economia, contrato JSON, roteamento por exit codes
- [INTEGRATIONS](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/INTEGRATIONS.md): configuração específica por fornecedor para todos os 27 agentes e IDEs suportados
- [CHANGELOG](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/CHANGELOG.md): histórico completo de versões com notas de migração
- [CONTRIBUTING](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/CONTRIBUTING.md): fluxo de pull request e padrões de código
- [SECURITY](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/SECURITY.md): política de divulgação responsável e canal de contato
- [CODE_OF_CONDUCT](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/CODE_OF_CONDUCT.md): padrões da comunidade


## Comandos Principais
### Subcomandos agrupados por ciclo de vida
- `init` inicializa o banco SQLite e escreve o schema; não baixa modelo, não spawna subprocesso e **não** valida CLI nenhuma no `PATH` (a validação de CLI é **histórica**, saiu na v1.2.0)
- `remember` salva uma memória com nome, tipo, descrição, corpo e grafo de entidades opcional; use `--max-rss-mb` para limitar o RSS do processo durante embedding (padrão 8192 MiB)
- `ingest` ingere em lote arquivos sob um diretório como memórias separadas (`--mode none`; progresso NDJSON)
- `recall` realiza busca por similaridade vetorial KNN sobre as memórias armazenadas
- `hybrid-search` funde FTS5 full-text e KNN vetorial via Reciprocal Rank Fusion
- `deep-research` decompõe uma query em até 7 sub-queries, computa embedding separado por sub-query, executa busca híbrida vetorial+FTS paralela fundida via RRF mais travessia de grafo de 3 hops por sub-query, deduplica e monta cadeias de evidência direcionadas; flags: `--k` (padrão 20), `--max-sub-queries` (padrão 7), `--max-hops` (padrão 3), `--min-weight`, `--max-concurrency`, `--timeout`, `--with-bodies`, `--max-results` (padrão 50), `--rrf-k` (padrão 60), `--graph-decay` (padrão 0.7), `--graph-min-score` (padrão 0.05), `--max-neighbors-per-hop`
- `read` `list` `forget` `rename` `edit` `history` `restore` gerenciam o ciclo de vida da memória; `read --id <id>` busca pelo ID numérico da memória como alternativa ao `--name`; `edit --type` muda o tipo da memória
- `remember-batch` ingere múltiplas memórias a partir de um stream NDJSON em uma única invocação; v1.2.0 exige `description` não vazia na criação
- `export` despeja memórias como NDJSON (uma linha JSON por memória, mais linha de resumo); filtre com `--namespace` / `--type`
- `completions` gera scripts de autocompletar para bash, zsh, fish, elvish e powershell
- `schema` lista todo JSON Schema embarcado como NDJSON, um objeto `{id, invoke}` por linha (**76** contratos, com `invoke` sendo o comando pronto para copiar); `schema --name <ID>` emite aquele documento e um `<ID>` desconhecido sai com **exit 4**. Este é o caminho canônico de autodescoberta para um agente
- `link` `unlink` `related` gerenciam relacionamentos tipados entre entidades para travessia multi-hop
- `health` `stats` `migrate` `vacuum` `optimize` `sync-safe-copy` gerenciam o banco de dados; `health` reporta `vec_memories_missing` e `vec_memories_orphaned` para diagnósticos do índice vetorial; `health` também reporta `relation_concentration_warning` e detecção de super-hub quando qualquer entidade ou tipo de relação domina o grafo
- `backup` cria backup consistente do banco usando a API SQLite Online Backup, seguro com WAL
- `fts rebuild` `fts check` `fts stats` reparam, verificam e inspecionam o índice FTS5 full-text
- `vec orphan-list` `vec purge-orphan` `vec stats` mantêm tabelas de vetor BLOB (detecção/purga de órfãos)
- `memory-entities` lista todas as entidades vinculadas a uma memória pelo nome
- `delete-entity` remove uma entidade e opcionalmente propaga a exclusão para seus relacionamentos
- `rename-entity` renomeia uma entidade do grafo preservando todos os relacionamentos baseados em FK e re-gera embedding para busca vetorial; `--id <id>` (v1.1.01) seleciona por ID para desambiguação com escopo de namespace
- `reclassify` muda o `entity_type` de uma entidade ou de uma categoria inteira via `--batch`
- `merge-entities` mescla nós de entidade duplicados em um único nó canônico; `--ids <csv>`/`--into-id <id>` (v1.1.01) selecionam por ID quando nomes duplicados entre namespaces bloqueiam o merge por nome; `--cross-namespace` (v1.1.03, opt-in, padrão mesmo-namespace) permite que `--ids`/`--into-id` resolvam através de todos os namespaces; v1.1.05 rejeita self-ref pré-DB
- `prune-ner` remove vínculos gerados por NER de uma entidade (`--entity`) ou de todas (`--all --yes`)
- `remember --dry-run` faz preview do parsing e extração do grafo sem gravar no banco
- `cleanup-orphans` `prune-relations` removem entidades órfãs e relações fracas ou não utilizadas do grafo
- `purge` (`--now` = `--retention-days 0`) e `namespace-detect` cuidam de manutenção e resolução de namespace; `pending-embeddings status` e `cache stats` (v1.2.0) para observabilidade
- `embedding` `pending-embeddings` inspecionam a fila de embeddings pendentes (`pending-embeddings status` alias de `embedding status`); **histórico**: a família `pending` inspecionava a fila de checkpoint do remember até a v1.2.8 removê-la, e o binário responde `unrecognized subcommand` com exit 2
- `slots` inspeciona/libera o semáforo de slots LLM host-wide; `cache list|stats|clear-models` gerencia cache de modelos XDG (`stats` alias de `list`)
- `config set|get|list|unset|list --effective|doctor|path|add-key` gerencia config XDG (GAP-SG-139: `--db` no-op em `config`/`slots`/`cache`/`completions`)
- `reclassify-relation` renomeia tipos de relacionamento no grafo; modo individual: `--source A --target B --from-relation antigo --to-relation novo`; modo batch: `--from-relation antigo --to-relation novo --batch`; filtros opcionais: `--filter-source-type`, `--filter-target-type`; `--dry-run` faz preview; trata colisões UNIQUE via `UPDATE OR IGNORE` + `DELETE`; `--literal-from` (v1.1.01) casa a relação armazenada verbatim (sem normalização) para migrar arestas legadas com hífen; `--literal-to <RELATION>` (v1.1.03) escreve o alvo verbatim, então `--literal-from applies_to --literal-to applies_to --batch` migra arestas legadas com underscore para a forma canônica com hífen
- `graph recompute-degree` (v1.1.01) reconcilia o cache `entities.degree` com as contagens reais de arestas em uma transação única, por namespace ou todos; `--dry-run` faz preview, envelope `{total, updated, zeroed, unchanged}`
- `graph entity-types` (v1.2.8) reporta o vocabulário de tipos que o banco realmente contém — `{types[{type, count, canonical}], total_types, total_entities, namespace, elapsed_ms}`, do mais frequente ao menos; `--namespace` delimita o escopo, `--format text` imprime um resumo compacto. Como o vocabulário é aberto, os rótulos gravados não são mais conhecíveis pelo código-fonte, então este é o único caminho para descobrir um antes de filtrar com `graph entities --entity-type`
- `split-body` (v1.1.03) divide memórias cujo corpo excede 25 000 caracteres em memórias filhas em fronteiras de chunk, marca a original como superseded, e cria relações canônicas `replaces` de cada filha para a original; as filhas NÃO são embedadas inline — rode `enrich --operation re-embed --target memories` depois
- `normalize-entities` normaliza todos os nomes de entidade no namespace para kebab-case ASCII minúsculo; mescla colisões automaticamente (ex.: `Claude Code` e `claude-code` viram um nó com relacionamentos combinados); `--dry-run` faz preview, `--yes` aplica
- `enrich` pipeline de qualidade do grafo aumentada por LLM via `--mode openrouter` (único valor aceito; exige `--openrouter-model`); padrão scan-judge-persist com fila sidecar para resume/retry; ops FULLY-IMPLEMENTED: `memory-bindings`, `entity-descriptions`, `body-enrich`, `re-embed` (`--target memories|entities|chunks|all`), `augment-bindings` (exige `--names`), `body-extract` (+ `--body-extract-graph-only`), `entity-connect` (**v1.1.06**: scan O(k) coocorrência+hub×ilha, chaves `pair:{id1}:{id2}` / `item_type=entity_pair`, drain por PK, primeiro scan `InterruptHandle` → Timeout exit **1** ≠ 75, NDJSON `scan_start`/`scan_meta`, GAP-002 `entity_connect_seen` preservado), `cross-domain-bridges` (mesmo path O(k)); inspetores `--list-skipped` / `--requeue-skipped` (v1.2.0), `--list-dead` / `--requeue-dead`; **v1.2.1 CAPA:** isolamento de claim por namespace, `--until-empty` conta só op+ns, `--force-redescribe` reabre skipped/done, re-embed BLOB `LENGTH(embedding)=dim*4` + reconciliação de zumbis, strip de `entity:`, validação de chunk no ns, CAPA-D configuration-file; `--dry-run` preview sem spawnar LLM; `--max-cost-usd` limita orçamento; `--llm-parallelism <N>` (padrão 1); saída NDJSON (fases, itens, resumo); suite `tests/v1106_entity_connect_scan_regression.rs`; ADR-0066


## Configuração (XDG — v1.2.0)
### Superfície de configuração em tempo de execução
- Precedência: **flag CLI > XDG `config set` > default** (sem env de produto no hot path; G-T-XDG-04)
- `config path` / `config set` / `config get` / `config list` / `config list --effective` / `config unset` / `config doctor`
- Chaves: `network.openrouter.chat_url` / `network.openrouter.embeddings_url` (aliases `network.chat_url` / `network.embed_url`), `embedding.dim` (padrão 1024), `log.level`, `display.tz`, `enrich.*`
- DB via `--db` ou XDG `db.path` (alias legado `db.default_path`→`db.path`) — não use `SQLITE_GRAPHRAG_*` como contrato instalado
- Chaves de API: flag `--openrouter-api-key` > XDG `config add-key` > env depreciada
- Env de SO legítima: locale / PATH / HOME / XDG / NO_COLOR apenas
- `pending-embeddings status`, `cache stats`, `purge --now` para observabilidade e manutenção


## Entrada do Grafo
### Contrato mínimo de payload para `remember`
- `--entities-file` espera um array JSON de objetos de entidade
- Cada entidade deve incluir `name` mais `entity_type` ou alias `type`
- Valores recomendados de `entity_type`: `project`, `tool`, `person`, `file`, `concept`, `incident`, `decision`, `memory`, `dashboard`, `issue_tracker`, `organization`, `location`, `date`
- Desde a v1.2.8 o vocabulário é ABERTO: qualquer outro termo é aceito e gravado como escrito, reportado no `warnings` da resposta, e recusado com exit 1 apenas sob `--strict-entity-types`
- `--relationships-file` espera um array JSON de objetos de relacionamento
- Cada relacionamento deve incluir `source`/`from`, `target`/`to`, `relation` e `strength`
- `strength` deve ser float em `[0.0, 1.0]` e é mapeado para `weight` nas saídas do grafo
- Payloads de arquivo usam relações com underscore como `applies_to`, `depends_on` e `tracked_in`; aliases com hífen são normalizados antes da gravação
- As flags CLI de `link` e `unlink` usam relações com hífen como `applies-to`, `depends-on` e `tracked-in`


## Extração Automática
### Extração de URL por regex (GLiNER removido na v1.0.79)
- Passe `--enable-ner` para ativar no `remember` e no `ingest` (product env não é lido no runtime v1.2.0)
- Desde a v1.0.79 isso executa APENAS extração de URL por regex — o pipeline GLiNER zero-shot foi removido com a feature `ner-legacy`
- **Histórico**: a flag --gliner-variant (escrita aqui sem crase porque o parser não a define mais) era aceita como no-op até a v1.1.02 **removê-la**; hoje o clap a REJEITA com exit 2. `SQLITE_GRAPHRAG_GLINER_MODEL` e `SQLITE_GRAPHRAG_GLINER_THRESHOLD` também são históricas e **não** são lidas em runtime
- Campo de resposta `extraction_method`: `url-regex`, `regex-only` ou `none:extraction-failed`
- Para extração curada por LLM rode um passo SEPARADO de `enrich --mode openrouter`; para controle exato use `--graph-stdin`
- `--skip-extraction` está depreciado desde v1.0.45 e não tem efeito
- `--max-rss-mb <MiB>` em `remember` e `ingest` aborta embedding quando o RSS do processo excede o limite (padrão 8192 MiB)

## Modo de Ingestão
### O único modo de extração para ingestão em massa
- `--mode none` é o padrão E o único valor aceito; o clap rejeita qualquer outro com exit 2
- Extração de entidades e relações é um passo SEPARADO: rode `enrich` como processo próprio depois que o `ingest` sair 0, nunca encadeado com `&&`
- Controle de orçamento via `--max-cost-usd <N>` para limitar gasto acumulado
- Deduplicação via `--force-merge`, que casa por `body_hash`
- Descrição inferida por `--auto-describe` (padrão) ou `--no-auto-describe`
- Escopo de nome via `--name-prefix <PREFIXO>`; seleção de arquivos via `--pattern`, `--recursive`, `--max-files`
- Paralelismo via `--ingest-parallelism <N>`; fan-out de embedding via `--llm-parallelism <N>` depois do verbo
- Saída é NDJSON: eventos por arquivo seguidos de um sumário

Autenticação: a chave OpenRouter vem da config XDG (`config add-key --provider openrouter --from-stdin`) ou da flag `--openrouter-api-key`. Não há subprocesso para autenticar, e nenhuma variável de ambiente de chave é lida no caminho de produto.


## Exit Codes
### Status determinístico para roteamento em pipelines
- `0` sucesso: continue o loop do agente
- `1` falha de validação ou runtime: registre e informe o operador
- `2` argumento CLI inválido: corrija o uso e tente novamente
- `9` memória duplicata detectada: ignore ou use `--force-merge`
- `3` conflito de atualização otimista: releia `updated_at` e tente novamente
- `4` memória ou entidade não encontrada: trate o recurso ausente com elegância
- `5` namespace não pôde ser resolvido: passe `--namespace` explicitamente
- `6` payload excedeu os limites configurados: divida o corpo em partes menores
- `10` erro no banco de dados SQLite: execute `health` para inspecionar integridade
- `11` geração de embedding falhou: verifique disponibilidade da CLI LLM e tente novamente
- `13` falha parcial em batch: respeite o backoff e tente novamente
- `14` erro de I/O no sistema de arquivos: diretório de cache não gravável, diretório de ingestão inexistente
- `15` banco de dados ocupado após tentativas: aguarde e tente novamente
- `19` SHUTDOWN: shutdown graceful interrompeu trabalho, estado parcial descartado, RETRY OBRIGATÓRIO
- `20` erro interno ou de serialização JSON
- `75` EX_TEMPFAIL: todos os slots de concorrência ocupados OU singleton de job travado, tente com backoff
- `77` RAM disponível abaixo do mínimo necessário para subprocesso LLM


## O Que Mudou na v1.0.79
### G42: pipeline de embedding LLM rápido, paralelo e robusto
- Dimensionalidade de embedding configurável, padrão **1024** (`--embedding-dim`; precedência flag > XDG `embedding.dim` > `schema_meta.dim` > 1024; bancos 384-dim existentes continuam via dim gravada; ZERO alteração de schema)
- Chamadas LLM em lote: bases de calibração de 8 chunks / 25 nomes de entidade em dim 64, adaptadas por clamp(base×64/dim, 1, base) por chamada (schema `{items:[{i,v}]}`, G44) — 39 spawns viram 4-5
- Paralelismo limitado (`Semaphore` + `JoinSet`): nova flag `--llm-parallelism` em `remember` (padrão 4), `ingest` (padrão 2) e `edit`; permits com clamp [1, 32]
- Nota **histórica** (**removidos** na v1.2.0 com os backends de subprocesso): overrides `SQLITE_GRAPHRAG_CLAUDE_EMBED_MODEL` e `SQLITE_GRAPHRAG_EMBED_TIMEOUT_SECS` mais `kill_on_drop` em todo subprocesso LLM; hoje o orçamento por requisição é `--openrouter-timeout` ou a chave XDG `embedding.timeout_secs`
- `CLAUDE_CONFIG_DIR` vazio por padrão no caminho de embedding (~40-50s → ~10-15s por chamada)
- Handler de sinais sem panic: segundo sinal sai com 130 e zero I/O; SIGPIPE sai com 141
- Re-embed canônico: `enrich --operation re-embed --limit N --resume` mais `edit --force-reembed`; a v1.1.01 adiciona `--target memories|entities|chunks|all` para backfill de entidades e chunks
- `validate_dim` falha em vetores divergentes em vez de truncar/preencher silenciosamente
- REMOVIDOS: infraestrutura do daemon; features `embedding-legacy`/`ner-legacy`/`full` e dependências opcionais fastembed/ort/ndarray/tokenizers/hf-hub

## O Que Mudou na v1.0.68
### Correções críticas (G28 + G29)
- v1.0.68 é o primeiro release desde v1.0.65 que compila no Windows via `cargo install`.  v1.0.66 e v1.0.67 quebravam com `error[E0308]` em `src/terminal.rs:29` porque `HANDLE` em `windows-sys >= 0.59` é `*mut c_void` (era `isize` em 0.48/0.52).  Correção: `!handle.is_null() && handle != INVALID_HANDLE_VALUE` mais `windows-sys` fixado em `=0.59.0` exato, mais job de CI `windows-build-check`.
- (**histórico**, v1.0.68 — `ingest --mode` aceita só `none` desde a v1.2.0, e o clap recusa `claude-code` / `codex` com exit 2) `enrich`, `ingest --mode claude-code` e `ingest --mode codex` adquirem um singleton por namespace via `lock::acquire_job_singleton(job_type, namespace, wait_seconds)`.  Uma segunda invocação concorrente no mesmo banco retorna `AppError::JobSingletonLocked { job_type, namespace }` (exit 75, retryable) em vez de empilhar 4 × N workers × 10 servidores MCP.
- `claude_runner::build_claude_command` agora respeita `SQLITE_GRAPHRAG_CLAUDE_EMPTY_CONFIG_DIR` (opt-in).  Quando definida para um diretório vazio, o subprocesso é iniciado com `CLAUDE_CONFIG_DIR=<esse dir>`, suprimindo servidores MCP do escopo user.  Este é o único mecanismo que o Claude Code realmente honra — `--strict-mcp-config` e `--mcp-config '{}'` são silenciosamente ignorados conforme [anthropics/claude-code#10787].
- `enrich` emite `tracing::warn!` quando `--llm-parallelism > 4`, recomendando a combinação com o override `CLAUDE_CONFIG_DIR`.
- Helper `retry::CircuitBreaker` adicionado com `AttemptOutcome::{Success, Transient, HardFailure}`.  Erros rate-limited e timeout são explicitamente excluídos da contagem de falhas.
- 3 falhas de teste pré-existentes em `src/commands/{history,list,read}.rs` corrigidas (asserções timezone-agnostic).

## Referências Opcionais
### Materiais complementares para contexto mais profundo
- [Contrato de agentes (docs/AGENTS)](https://github.com/danilo-aguiar-br/sqlite-graphrag/blob/main/docs/AGENTS.md): orientação híbrida + contrato imperativo da CLI (empacotado no crate)
- [Definições de SKILL](https://github.com/danilo-aguiar-br/sqlite-graphrag/tree/main/skill/sqlite-graphrag-en): skills de slash-command pré-construídas para o harness Claude Code
- [Pacote crates.io](https://crates.io/crates/sqlite-graphrag): binário publicado com semver e metadados de MSRV
- [Referência API docs.rs](https://docs.rs/sqlite-graphrag): rustdoc para consumidores da biblioteca


## Fatos Estáveis
### Identidade e metadados de versão
- Nome do pacote `sqlite-graphrag` publicado no crates.io sob MIT OR Apache-2.0
- Versão atual **1.2.8** (crate `1.2.8`; schema **v17** desde a migration **V017**; MSRV Rust **1.88**; binário ~19 MiB). O detalhe por release está em `CHANGELOG.pt-BR.md`; este arquivo não guarda uma segunda cópia dele.
- Repositório `https://github.com/danilo-aguiar-br/sqlite-graphrag` sem GitHub Actions CI (releases publicadas manualmente)
- Modelo de embeddings: somente API REST OpenRouter (`qwen/qwen3-embedding-8b`, ~100-500ms por chamada). Os backends de subprocesso headless `claude code` / `codex` / `opencode` foram REMOVIDOS na v1.2.0; nada local guarda modelo.
- Camada de armazenamento `rusqlite` com SQLite bundled e módulo FTS5. Extensão `sqlite-vec` REMOVIDA na v1.0.76; similaridade vetorial é cosseno em Rust puro sobre embeddings BLOB
- Até **16** instâncias simultâneas por padrão via semáforo de contagem com locks advisory `fs4` (`MAX_CONCURRENT_CLI_INSTANCES = 16` em `src/constants/runtime.rs:42`; o teto rígido de 4 slots saiu na v1.0.75). Sobrescreva por invocação com `--max-concurrency N`, limitado a 2×nCPUs
- Catálogo oficial: **27** agentes de IA e IDEs suportados de imediato (README); o `description` do crate usa a mesma contagem
### Schema e armazenamento
- Schema atual do banco principal: **17** (`CURRENT_SCHEMA_VERSION` em `src/constants/storage.rs:64`), elevado pela migration **V017** na v1.2.8, que abriu o vocabulário de `entity_type`; a v1.1.06 **não** adiciona migração
- Schema v16 veio na v1.1.04 via V016: tabela `entity_connect_seen(source_id, target_id, namespace, verdict, relation, evaluated_at)` com PK composta, FK dupla `ON DELETE CASCADE` para `entities(id)`, CHECK de verdict, índice de namespace
- Embeddings em BLOB em `memory_embeddings` / `entity_embeddings` / `chunk_embeddings`; cosseno pure Rust; dim padrão **1024** (truncagem MRL; legados mantêm `schema_meta.dim`)
- Fila do enrich no sidecar `.enrich-queue.sqlite` (dead-letter `dead`, `error_class`, `next_retry_at`, `claimed_at`)
### Códigos de saída (críticos para operadores)
- `0` sucesso; `1` validação **ou** Timeout de wall-clock (inclui InterruptHandle do 1º scan de entity-connect); `2` args CLI; `3` lock otimista; `4` não encontrado (pode incluir sugestões); `6` payload grande; `11` falha de embedding; `15` SQLITE_BUSY; `19` SHUTDOWN (retry obrigatório); `75` singleton/slot (**não** é timeout de scan). **Não existe exit 78**: chave ou modelo OpenRouter ausente resolve para `1`. O mapa completo do `exit_code()` em `src/errors.rs` é 1, 3, 4, 5, 6, 10, 11, 12, 14 e 20, além do 2 do clap e do 75 de slot
- NUNCA trate Timeout do primeiro scan de entity-connect como exit `75`
### Enrich entity-connect (contrato v1.1.06)
- Chaves `pair:{id1}:{id2}` com `item_type=entity_pair`; drain por PK sem re-scan
- Candidatos: coocorrência em `memory_entities` + hub × ilha grau-0 (O(k); nunca cartesiano O(n²))
- NDJSON: `scan_start` **antes** do SQL com `operation` real (`entity-connect` ou `cross-domain-bridges`), `entities_in_namespace`, `backlog_degree0_proxy`; `scan_meta` com `pairs_enqueued_this_scan` / `scan_elapsed_ms` — NÃO equacione os dois campos de backlog
- Primeiro scan coberto por `--max-runtime` e teto soft 120s via `InterruptHandle` → Timeout exit **1**
- `cross-domain-bridges` compartilha o mesmo path O(k) + `entity_connect_seen`; GAP-002 preservado
- Suite: `tests/v1106_entity_connect_scan_regression.rs`; ADR-0066; pin `=1.2.8`
### Concorrência e credenciais
- Singleton por namespace em `enrich` / ingest LLM → exit `75` sob contenção
- OAuth-only para subprocessos `claude` / `codex`: `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` brutas ABORTAM o spawn (exit 1)
- OpenRouter usa `config add-key` / `--openrouter-api-key` (REST embedding e chat enrich); o produto nunca lê `OPENROUTER_API_KEY`; nunca logar a chave
### Defaults de retrieval
- Hybrid: FTS5 BM25 + KNN cosseno BLOB fundidos por RRF; multi-hop via `graph traverse` / `related` / `deep-research`
- `deep-research` de token único faz fan-out de aspectos (`source: "aspect"`) desde v1.1.05; envelopes grandes usam `--output` atomwrite + `--quiet`