Ferramentas · 6 diagramas · 3 gráficos

Não comece a sessão do zero

Quatro perguntas sobre um código que o agente não conhece. Pelo grafo elas custam 8 kB. Lendo arquivo, 680 kB — e uma delas não tem resposta.

Um agente de IA não tem véspera. Cada sessão nova abre com a mesma ignorância da primeira: não sabe onde mora a autenticação, não sabe que aquele módulo com nome de utilitário é o coração do domínio, não sabe que o arquivo de 6 mil linhas só delega. Para trocar três linhas ele precisa antes reconstruir o mapa — e reconstruir o mapa é grep, ler arquivo inteiro, seguir import, ler outro arquivo inteiro. É o que faz um desenvolvedor no primeiro dia, com a diferença de que o desenvolvedor faz isso uma vez.

O Codebase Memory MCP indexa o repositório num grafo de conhecimento em SQLite e o expõe por MCP, para que a sessão de amanhã pergunte ao grafo o que a de hoje descobriu lendo arquivo. Em vez de descrever a promessa, medi.1

Quatro perguntas, duas rotas

Peguei um repositório público que qualquer leitor pode baixar — o fastapi/fastapi no commit 50113da, 1.138 arquivos .py e 112.998 linhas — e fiz quatro perguntas que aparecem de verdade quando se chega num código desconhecido. Cada uma respondida duas vezes: pelo grafo, e pela rota que um agente sem grafo é obrigado a seguir.

Pergunta Pelo grafo Lendo arquivo Razão
Onde eu começo? 3.930 B
get_architecture
80.459 B
README + árvore de .py
20×
Quem chama isto, e o que ela chama? 3.528 B
search_graph + trace_path
303.462 B
grep + 3 arquivos
86×
O que meu diff afeta? 544 B
detect_changes
295.629 B
git diff + 2 arquivos
543×
Que função não tem chamador? 43 B
query_graph
não responde
10 B 100 B 1 kB 10 kB 100 kB 1 MB onde eu começo? 3,9 kB 80 kB quem chama isto? 3,5 kB 303 kB o que meu diff afeta? 544 B 296 kB o que não tem chamador? 43 B não responde
  • pelo grafo
  • lendo arquivo
O eixo é logarítmico: cada linha da grade vale dez vezes a anterior. Numa escala linear as três barras de acento seriam invisíveis — o que também seria uma leitura honesta do resultado.

Somando as quatro, a rota do grafo devolveu 8.045 bytes e a rota manual 679.550. Traduzido para a moeda que importa numa sessão de agente — contexto — são cerca de 2 mil tokens contra 170 mil.2

pelo grafo — 2.011 tokens, 1,0% da janela lendo arquivo — 169.888 tokens, 84,9% da janela 0 200.000 tokens e a quarta pergunta não tem resposta na rota de baixo
A mesma sessão, o mesmo trabalho. Embaixo, três perguntas já consumiram 85% da janela e não sobrou espaço para fazer a mudança — que era o motivo de ter aberto a sessão.

E a diferença não é só de volume. O grep devolve posições de texto; o grafo devolve relações. São perguntas de tipo diferente, e a quarta linha da tabela é a prova: "que função não tem nenhum chamador" não é consulta textual, é consulta sobre arestas. Não existe expressão regular que responda.

flowchart TD
    Q["uma pergunta sobre relação entre símbolos"]
    Q --> G["consulta ao grafo"]
    Q --> R["grep no repositório"]
    G --> GA["devolve as arestas: quem chama, o que chama, a quantos saltos"]
    R --> RA["devolve as posições onde o nome aparece como texto"]
    RA --> RB["abrir cada arquivo e reconstruir a relação lendo"]
    RB --> RC["a relação sai da cabeça do modelo, não do repositório"]
O nó de baixo é o problema real: relação reconstruída por leitura é relação inferida, e inferência sobre código que o modelo acabou de conhecer é exatamente onde ele inventa.

O que cada resposta contém

Contagem de bytes só interessa se a resposta menor for também a melhor. Vale abrir as quatro.

Onde eu começo?

Uma chamada, 3.930 bytes, e sai o censo do repositório: quantos nós de cada tipo, quantas arestas de cada tipo, as linguagens com a contagem real de arquivos, os pacotes e os pontos de entrada.

bash · get_architecture, recortado
total_nodes: 31130
total_edges: 73526

node_labels:                 edge_types:
  Section    15029             DEFINES     36705
  Function    4307             CALLS        7462
  File        2890             USAGE        6457
  Module      2707             SIMILAR_TO   4539
  Route       2574             HANDLES      3888
  Variable    2228             TESTS        3062
  Class        663             IMPORTS      1881
  Method       323             INHERITS      102
  Decorator     15             HTTP_CALLS     47

languages: Python 1138, YAML 42, HTML 5, Bash 5, JavaScript 4, CSS 3, TOML 1
entry_points: 20
Repare em USAGE com 6.457 contra 7.462 de CALLS: 46% das referências não têm alvo único provado, e o grafo declara isso em vez de esconder. Ferramenta que junta os dois num tipo só de aresta está vendendo confiança que não tem.

Quem chama isto, e o que ela chama?

A pergunta era sobre solve_dependencies, o miolo da injeção de dependência do FastAPI. A primeira chamada acha o símbolo e já devolve a faixa de linhas exata, mais o grau de entrada e de saída:

bash · 382 bytes de saída
$ codebase-memory-mcp cli search_graph \
    --project fastapi --name-pattern 'solve_dependencies'

total: 2
fastapi.fastapi.dependencies.utils (fastapi/dependencies/utils.py):
  solve_dependencies Function 586-731 2 20
fastapi.fastapi.routing._FrontendRouteGroup (fastapi/routing.py):
  _solve_dependencies Method 2216-2252 1 7
Antes de ler uma linha eu já sei que a função de 146 linhas em utils.py chama vinte coisas e é chamada por duas, e que existe um método homônimo em routing.py que não é ela.

A segunda chamada percorre o grafo em largura, nas duas direções, e devolve em 3.146 bytes os 72 símbolos alcançados até três saltos — agrupados por módulo, com a distância de cada um — mais os 3 chamadores.

flowchart TD
    E2["handle"] -->|"salto 2"| E1["_solve_dependencies em routing.py"]
    E1 -->|"salto 1"| S["solve_dependencies"]
    S -->|"salto 1"| A["get_dependant, request_body_to_args, _solve_generator e outros 16"]
    A -->|"salto 2"| B["analyze_param, get_typed_signature, get_cached_model_fields e outros 30"]
    B -->|"salto 3"| C["field_annotation_is_scalar, copy_field_info, validate e outros 20"]
É busca em largura com profundidade limitada, não varredura: os 22 módulos alcançados saem da travessia de arestas, e o custo não depende do tamanho do repositório, mas do raio pedido.

O que meu diff afeta?

Aqui a diferença fica mais brutal, e é a pergunta que mais aparece na prática: mudei uma linha, o que quebra? Acrescentei um comentário dentro de solve_dependencies e chamei detect_changes. Ele leu o diff não comitado, mapeou para o símbolo afetado e devolveu o raio:

bash · detect_changes, 544 bytes na íntegra
changed_files: 1
  fastapi/dependencies/utils.py
seed_symbols: 1
impacted_total: 3
impacted:
  fastapi.routing._FrontendRouteGroup:
    _solve_dependencies Method 1
    handle              Method 2
  fastapi.routing.py:
    __file__            File 1
impacted_modules:
  fastapi/routing.py 3
Cinco linhas úteis. O git diff equivalente tem 390 bytes e diz o que mudou — não diz o que depende do que mudou, que é a única coisa que eu queria saber.
flowchart TD
    D["diff: 1 linha em fastapi/dependencies/utils.py"]
    D --> S["símbolo semente: solve_dependencies"]
    S -->|"salto 1"| A["_solve_dependencies"]
    S -->|"salto 1"| F["o arquivo routing.py"]
    A -->|"salto 2"| B["handle"]
    B --> R(["raio: 3 símbolos, um módulo só"])
Para chegar nas mesmas três caixas pela outra rota é preciso ler utils.py e routing.py inteiros — 295 kB para descobrir que o estrago cabe num arquivo.

Que função não tem chamador?

Esta é a que muda de categoria. Restrita ao diretório da biblioteca, a resposta inteira tem 43 bytes:

bash · query_graph, openCypher de leitura
MATCH (f:Function)
WHERE f.file_path STARTS WITH 'fastapi/'
  AND NOT EXISTS { (f)<-[:CALLS]-() }
RETURN count(f)

rows: 1  (cols: COUNT(f))
  "10"
10 das 150 funções de fastapi/ não têm nenhuma aresta CALLS entrando. O que sai do subconjunto suportado — escrita, MERGE, list comprehension — falha com erro explícito de unsupported, e não devolvendo vazio.

Por que é barato perguntar

Vale desfazer uma confusão comum: o Codebase Memory MCP não tem LLM dentro. Ele é um motor de análise estrutural. Quem traduz "quem chama isso?" para trace_path(direction="inbound") é o agente que você já está usando — e é por isso que não há chave de API nem modelo a configurar.

flowchart TD
    V["você pergunta em português"] --> A["o agente escolhe a tool e monta os argumentos"]
    A --> C["o servidor executa a consulta no grafo"]
    C --> S["SQLite devolve as linhas"]
    S --> A2["o agente lê as linhas e escreve a resposta"]
    A2 --> V2["você recebe a cadeia de chamadas em português"]
Nenhuma caixa deste desenho é um modelo, exceto as duas do agente. O servidor é determinístico: a mesma consulta no mesmo grafo dá a mesma resposta — o que é a razão de dar para conferir o número.

A consulta é barata porque o trabalho difícil já foi feito na indexação, em duas passadas. A primeira é tree-sitter, sintática, com as gramáticas compiladas dentro do binário. A segunda — que o projeto chama de Hybrid LSP — é uma implementação enxuta em C de algoritmos de resolução de tipo, e é ela que sabe que user.profile.display_name() resolve para Profile.display_name, declarado três módulos adiante. Existe para Python, TypeScript, PHP, C#, Go, C, C++, Java, Kotlin, Rust e Perl; as outras linguagens caem na resolução textual.

flowchart TD
    F["arquivos do repositório"] --> D["descoberta: .gitignore, .cbmignore, sem symlink"]
    D --> TS["passada tree-sitter: definições, chamadas e imports"]
    TS --> LSP["passada Hybrid LSP: resolve o alvo por inferência de tipo"]
    LSP --> S["SQLite: nós e arestas, no cache local"]
    S --> Q(["consulta estrutural"])
    S --> A[".codebase-memory/graph.db.zst"]
A segunda passada é o que separa "há uma chamada com esse nome" de "esta chamada resolve para aquela definição" — a diferença entre USAGE e CALLS no censo acima.

Indexei quatro repositórios públicos de tamanhos bem diferentes, na mesma máquina, para ver como esse custo escala:

Repositório Linhas .py Indexação Nós Arestas Banco
psf/requests12.0324,95 s1.4285.6298,6 MB
pallets/flask18.3454,35 s2.0348.40810,2 MB
fastapi/fastapi112.9988,80 s31.13073.52673,9 MB
django/django526.18630,08 s55.583345.183269,8 MB
5 mil 10 mil 15 mil 2.431 4.217 12.841 17.492 requests flask fastapi django 12 mil linhas 18 mil 113 mil 526 mil
Vazão em linhas por segundo. Ela cresce com o tamanho porque os dois repositórios pequenos gastam a maior parte dos ~4 s subindo o processo: abaixo de umas 20 mil linhas você não está medindo o indexador, está medindo a partida.

Meio milhão de linhas de Python em 30 segundos, uma vez, e depois a pergunta sai em milissegundos. O detalhe honesto está numa coluna que não coube na tabela: o django teve 51 arquivos com trecho que o parser não conseguiu ler por inteiro (parse_partial), contra 3 no flask, 1 no requests e nenhum no fastapi. O indexador reporta esses arquivos em vez de fingir cobertura — e é por isso que afirmação de ausência ainda pede grep.

As três memórias

Chamar tudo isso de "memória" esconde que são três coisas com durabilidades diferentes. A segunda é a que de fato ensina um agente ao seguinte, e é a que quase ninguém usa.

MemóriaO que guardaSobrevive a
o grafoestrutura derivada do código: símbolos, chamadas, rotassessão e reinício da máquina
o ADR (manage_adr)o que o código não diz: decisão, trade-off, armadilhasessão — e é o único que não se regenera
o artefato .zsto grafo comprimido, versionado ao lado do fontea máquina — atravessa o time

O grafo é derivado: apagar o cache não perde nada além de tempo de CPU. O ADR não. Ele é um documento em markdown por projeto que um agente escreve e o próximo lê — o recado que quem está saindo deixa para quem está chegando:

markdown · o documento que fica gravado
## ARCHITECTURE

A resolução de dependências tem um único ponto de entrada:
solve_dependencies (fastapi/dependencies/utils.py:586-731). O único
chamador real é _FrontendRouteGroup._solve_dependencies
(fastapi/routing.py:2216-2252).

## PATTERNS

Não procure a resolução em routing.py: ela delega.
Isso entra pelo campo content de manage_adr(mode='update') e volta inteiro no mode='get'. As duas consultas da segunda pergunta viram 400 bytes que a próxima sessão lê antes de abrir arquivo.

A terceira memória atravessa pessoas. Indexando com persistence ligado, o grafo é exportado para .codebase-memory/graph.db.zst, ao lado do fonte: quem clona o repositório importa o artefato e roda indexação incremental só do próprio diff. No FastAPI o artefato ficou em 6,2 MB contra 73,9 MB de banco local, e o artifact.json ao lado registra o commit exato de que ele saiu — o que impede alguém de confiar num grafo de três meses atrás.

sequenceDiagram
    autonumber
    participant A as sessão 1
    participant G as grafo
    participant D as ADR
    participant B as sessão 2

    A->>G: index_repository
    G-->>A: 31.130 nós
    A->>G: trace_path
    A->>D: escreve a conclusão
    Note over D: o que o código não diz
    B->>D: lê antes de tudo
    D-->>B: a conclusão da sessão 1
    B->>G: pergunta só o que falta
O passo 5 é o ponto do desenho. Sem ele a sessão 2 refaz os passos 1 a 3 — e o grafo tinha economizado tokens, não conhecimento.

Instalar e usar

É um executável nativo único, sem runtime de linguagem, sem serviço e sem chave de API. O instalador detecta os agentes já presentes na máquina e escreve a configuração de MCP de cada um — na v0.10.8 ele declara 43 superfícies de cliente:

bash · macOS / Linux
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash

# só o binário, sem mexer na configuração dos agentes:
#   ... | bash -s -- --skip-config
O instalador escreve nos arquivos de configuração dos seus agentes. É o que ele deve fazer — e é motivo suficiente para ler o script antes de canalizar para o shell.

Preferindo configurar à mão, é uma entrada de MCP como qualquer outra. No Claude Code, em ~/.claude.json ou no .mcp.json do projeto:

json · .mcp.json
{
  "mcpServers": {
    "codebase-memory-mcp": {
      "command": "/home/voce/.local/bin/codebase-memory-mcp",
      "args": []
    }
  }
}
Caminho absoluto. Com caminho relativo o servidor não sobe e o /mcp simplesmente não mostra a entrada.

Depois é reiniciar o agente e dizer "indexe este projeto". Todo tool do MCP também é comando de linha, o que serve para depurar sem um agente no meio e para pendurar indexação num job noturno:

bash · pelo CLI, fora do agente
codebase-memory-mcp cli index_repository --repo-path /caminho/do/repo

codebase-memory-mcp cli search_graph --project meu-projeto \
    --name-pattern '.*Handler.*' --label Function
codebase-memory-mcp cli trace_path --project meu-projeto \
    --function-name solve_dependencies --direction both
codebase-memory-mcp cli detect_changes --project meu-projeto

# o artefato de time NÃO nasce sozinho: persistence é false por default
#   index_repository(repo_path="...", persistence=true)

codebase-memory-mcp config set auto_index true   # indexa ao abrir a sessão
codebase-memory-mcp --ui=true --port=9749        # grafo em 3D no navegador
A ordem que economiza mais é a das quatro perguntas deste texto: get_architecture para se orientar, search_graph para achar o nome exato, trace_path para as relações e get_code_snippet só no fim — quando já se sabe quais são as 146 linhas que importam.

O que isso custa

Disco. É o custo mais visível: 31.130 nós ocupam 73,9 MB, e o django chega a 269,8 MB. Nesta máquina há 101 projetos indexados e 5,1 GB de cache. Tudo descartável, nada desprezível.

Frescor. Um grafo de ontem responde com confiança sobre código que mudou hoje, e essa é a falha mais perigosa da ideia toda — porque não parece uma falha. O projeto trata com um observador em segundo plano, com detect_changes para o que ainda não foi comitado e com check_index_coverage, que lista os arquivos não cobertos por inteiro. Mas cobertura limpa significa "nenhuma lacuna registrada", não prova de completude: afirmação de ausência continua exigindo grep.

Histórico do Git. Se você comitar o artefato, comite com cadência. Ele é reescrito a cada indexação, o Git guarda cada reescrita como um blob novo inteiro, e o próprio projeto documenta um time que chegou a ~6 GB em ~350 commits desse único caminho. Um marco, uma release, um job noturno — não cada salvamento.

Feita a conta, ainda fecha com folga. O maior grafo que eu mantenho indexado tem 167.902 nós e 765.371 arestas; nenhuma janela de contexto comporta aquele repositório, e nunca vai comportar. A pergunta nunca foi se o agente cabe no código — é se ele chega na sessão sabendo onde procurar. É o mesmo que a gente pede de um desenvolvedor novo: não que tenha lido tudo, mas que não comece do zero toda segunda-feira.


notas

  1. Versão medida: codebase-memory-mcp 0.10.8, MIT, em Ubuntu 24.04.5 LTS com Intel Core i7-1355U (12 threads) e 31 GiB de RAM. Repositórios clonados com --depth 1 nos commits 50113da (fastapi), 2b30f62 (django), d73fa1c (flask) e dae7ef6 (requests). Tempo de indexação por /usr/bin/time -f %e; tamanho de banco e de artefato em MB decimais, por stat -c%s. Os números de outras máquinas citados no texto — o kernel do Linux em 3 minutos, os ~6 GB de histórico — são do README do projeto e da preprint arXiv:2603.27277; não são meus.
  2. As quatro medições, para reprodução. Onde eu começo: cli get_architecture (3.930 B) contra wc -c README.md (22.780 B) mais a listagem de find . -name '*.py' (57.679 B). Quem chama isto: cli search_graph --name-pattern 'solve_dependencies' (382 B) e cli trace_path --direction both --depth 3 (3.146 B) contra grep -rn "solve_dependencies" --include='*.py' . (558 B, 8 ocorrências) mais wc -c de fastapi/dependencies/utils.py, fastapi/routing.py e fastapi/dependencies/models.py (302.904 B). O que meu diff afeta: cli detect_changes (544 B) contra git diff (390 B) mais os dois arquivos do raio (295.239 B). Sem chamador: o query_graph do texto (43 B). Comparei bytes de saída, não tokens contados com um tokenizer: a conversão de 4 bytes por token é a mesma razão que a ferramenta usa como teto ao dimensionar resposta, e serve para ordem de grandeza, não para dois dígitos de precisão. A janela de 200 mil é a referência do gráfico, não um limite do servidor. O nome do projeto nos blocos está encurtado para fastapi: o derivado do caminho aqui era mais longo, e só o nome muda.

Anderson Woss

Engenheiro de software. Escrevo aqui sobre sistemas distribuídos que precisam continuar de pé e modelos que precisam parar de inventar.