ken
Busca híbrida rápida de código para agentes - é Go puro, binário estático único, 5 embeddings lexicais + semânticos Model2Vec + fusão RRF + um reranker ciente de código, com o algoritmo de recuperação portado literalmente do semble
Documentação
ken
Busca híbrida rápida de código para agentes. Escrito em Go puro, binário estático único, compatível com MCP como substituto direto do MinishLab/semble — mesmos schemas de ferramentas, mesmo formato de saída, passos de instalação trocados por um binário Go.
ken é um port em Go do semble: BM25 lexical + embeddings semânticos Model2Vec + fusão RRF + um reranker ciente de código, com o algoritmo de recuperação portado literalmente do search.py + ranking/*.py do semble.
Por que ken
- ~97% de recall@10 no modo padrão (híbrido) — 0.967 NL / 0.995 símbolo no benchmark de 1.251 consultas do semble, vs ~99.9% do grep — enquanto custa ao agente ~46× menos tokens que o
grep + Read(4.120 vs 189.773 tokens medianos em consultas NL — medidos no mesmo modo híbrido padrão). Para "encontre o trecho que responde a isto", isso é uma vitória de 1–2 ordens de magnitude em tokens com recall quase equivalente. (Reproduza:docs/BENCH.md.) - Binário estático único. Go puro, sem cgo, sem interpretador Python no cold start, sem GIL na indexação. Compila cruzado para Linux / macOS / Windows (amd64/arm64) gratuitamente.
- Substituto direto do semble. Mesmos schemas de ferramentas MCP
search/find_relatede mesmo formato de string markdown — troque o caminho docommand:e agentes existentes funcionam sem alterações. - Local, apenas CPU. Inferência de embeddings, BM25 e fusão rodam todos na CPU. Sem chaves de API, sem GPU, sem banco de vetores, compatível com ambientes isolados.
Um único controle define o recall. O número 82–91% nas tabelas de orçamento de tokens é o fallback apenas-BM25 que o ken usa quando nenhum modelo de embeddings está instalado. O ken-mcp baixa o modelo automaticamente na primeira execução (~60 MB, Go puro, sem Python — serve bm25 até o modelo chegar, então atualiza para o caminho híbrido de ~97%;
KEN_MCP_AUTO_FETCH=0para desativar). Para a CLI, executeken download-modeluma vez. Enumeração exaustiva (refatorações, auditorias pré-rename) ainda pertence ao grep; ken é para "encontre o trecho que responde a isto."
Por onde começar
- ARCHITECTURE.md — mapa do estado atual: layout de módulos, modelo de runtime/concorrência, fluxo de dados, invariantes. Comece aqui para o código.
- docs/USERS.md — usuários agentes. Instale o ken-mcp, aponte seu agente para ele, use as nove ferramentas. On-ramp de 5 minutos.
- docs/DEVELOPERS.md — autores de SDK e ajustadores. A biblioteca de corpus embutido
mcp.Run, índices pré-construídos, indexaçãofs.FS, chunkers personalizados, ajuste de rerank, expectativas de performance. - docs/DESIGN.md + docs/internal/DECISIONS.md — especificação do algoritmo + cada decisão arquitetural (ADRs).
- docs/BENCH.md — reprodução de benchmarks (NDCG, recall por orçamento de tokens, a decomposição híbrido-vs-BM25).
Início rápido
Instale via gerenciador de pacotes:
# macOS / Linux (Homebrew) — installs both `ken` and `ken-mcp`:
brew install --cask townsendmerino/tap/ken
# Windows (Scoop):
scoop bucket add townsendmerino https://github.com/townsendmerino/scoop-bucket
scoop install ken
Ou com Go:
# Install both binaries (Go 1.26+).
go install github.com/townsendmerino/ken/cmd/ken@latest
go install github.com/townsendmerino/ken/cmd/ken-mcp@latest
# Download the default Model2Vec model (~60 MB, one-time). Pure Go, no Python.
# (ken-mcp auto-fetches this on first run; the CLI needs it explicitly.)
# This is the single biggest retrieval-quality lever — it puts you on the ~97% path.
ken download-model
# Search any local repo from the CLI.
ken search /path/to/myrepo "save model to disk" --model ~/.ken/model
Ou pule o modelo e use o modo apenas-lexical (modo apenas-BM25 custa ~14 pp de recall@10 vs o padrão híbrido — veja docs/BENCH.md):
ken search /path/to/myrepo "validateToken" --mode bm25
Não tem certeza se sua configuração está correta? ken doctor verifica disponibilidade do modelo, aquecimento do cache de rerank, enriquecimento, rastreamento de economia de tokens e configuração do ken-mcp, e imprime recomendações priorizadas (ex.: "sem modelo — execute ken download-model").
Binários pré-compilados para macOS, Linux e Windows (amd64/arm64) estão anexados a cada release — .tar.gz para macOS/Linux, .zip para Windows.
A partir da v0.3, ken index <path> usa por padrão o modo de observação — ele permanece ativo e reindexa em mudanças (debounce de 2 s); --no-watch restaura o comportamento de compilar-uma-vez-e-sair. ken-mcp sempre observa, então um agente editando o repositório no meio da sessão vê suas próprias mudanças sem reiniciar. ken também respeita arquivos .gitignore aninhados (por diretório, correspondendo ao git).
Instalar como servidor MCP
ken-mcp fala JSON-RPC sobre stdio e serve as mesmas duas ferramentas principais (search, find_related) que o semble, com os mesmos formatos de argumentos e saída markdown.
# Claude Code
claude mcp add ken -s user -- /absolute/path/to/ken-mcp
// ~/.cursor/mcp.json (or .cursor/mcp.json) — also .vscode/mcp.json with "servers"
{ "mcpServers": { "ken": { "command": "/absolute/path/to/ken-mcp" } } }
# ~/.codex/config.toml
[mcp_servers.ken]
command = "/absolute/path/to/ken-mcp"
// ~/.opencode/config.json
{ "mcp": { "ken": { "type": "local", "command": ["/absolute/path/to/ken-mcp"] } } }
Transporte remoto (Streamable HTTP)
O padrão acima executa o ken-mcp como um subprocesso local — a escolha certa para a maioria das configurações (o limite do usuário do SO é o limite de autenticação; nada é exposto à rede). Para um dev box centralizado, servidor de staging ou instância compartilhada de equipe — um ken-mcp alimentando muitos agentes, ou um IDE remoto não co-residente com o código — o ken-mcp também fala MCP sobre Streamable HTTP (KEN_MCP_TRANSPORT=http, ADR-041). Mesmas ferramentas, mesmo formato de wire; agentes já treinados no servidor stdio funcionam sem alterações.
Isso expõe o ken-mcp à rede, então autenticação é obrigatória e vários guards falham alto na inicialização:
# On the server (front with a TLS-terminating reverse proxy — ken-mcp does NOT do TLS):
export KEN_MCP_TRANSPORT=http
export KEN_MCP_ADDR=:8080 # default
export KEN_MCP_AUTH_TOKEN_FILE=/etc/ken/token # preferred (keeps the secret out of the environment)
export KEN_MCP_DEFAULT_REPO=/srv/code
export KEN_MCP_RATE_LIMIT=100 # req/min per client IP (0 disables); default 100
ken-mcp # exits non-zero if no token is set
Aponte um agente para ele com um bearer token (Claude Code mostrado; qualquer cliente MCP Streamable-HTTP funciona):
claude mcp add --transport http ken https://ken.example.com \
--header "Authorization: Bearer $TOKEN"
// Cursor / VS Code mcp.json — remote form (check your editor's remote-MCP docs)
{ "mcpServers": { "ken": { "url": "https://ken.example.com",
"headers": { "Authorization": "Bearer <token>" } } } }
Guards: o modo HTTP recusa iniciar sem token (sem padrão inseguro, sem exceção de localhost); KEN_DB_SAMPLE_ROWS>0 é rejeitado de forma rígida no modo HTTP (valores de banco amostrados seriam pesquisáveis na rede); requisições são limitadas por IP do cliente. TLS está fora do escopo por design — termine-o em um reverse proxy na frente do ken-mcp. stdio continua o padrão e é completamente afetado.
Variáveis de ambiente principais
| Variável | Padrão | Propósito |
|---|---|---|
KEN_MCP_DEFAULT_REPO | (não definido) | Fonte pré-indexada; permite que as ferramentas omitam o argumento repo. |
KEN_MCP_MODE | hybrid | bm25 / semantic / hybrid. Serve bm25 enquanto o modelo está ausente — baixado na primeira execução por padrão (veja KEN_MCP_AUTO_FETCH). |
KEN_MCP_MODEL_DIR | ~/.ken/model | Caminho para um snapshot do Model2Vec contendo model.safetensors. Recai para ~/.ken/model (onde ken download-model grava) quando não definido. |
KEN_MCP_AUTO_FETCH | 1 | Na primeira execução com um modo que exige modelo e sem modelo presente, baixa potion-code-16M (~60 MB) em segundo plano, servindo bm25 até que chegue e então atualizando para híbrido. 0 desativa (serve bm25, avisa). |
KEN_MCP_CHUNKER | regex | regex / treesitter / line / markdown. Veja Escolhendo um chunker. |
KEN_MCP_CACHE_SIZE | 16 | Limite LRU no cache repo→Index. |
KEN_MCP_LOG_LEVEL | warn | debug / info / warn / error. Todos os logs vão para stderr; stdout é o canal JSON-RPC (detalhes). |
KEN_MCP_TRANSPORT | stdio | stdio (subprocesso local, padrão) ou http (Streamable HTTP de rede — veja Transporte remoto). |
KEN_MCP_ADDR | :8080 | Endereço de bind HTTP (somente modo http). Sem TLS em processo — use um proxy reverso na frente. |
KEN_MCP_AUTH_TOKEN / …_TOKEN_FILE | (não definido) | Token Bearer para modo http (_FILE preferido). Obrigatório — o modo http não inicia sem ele. |
KEN_MCP_RATE_LIMIT | 100 | Requisições/min por IP do cliente (modo http); 0 desativa. KEN_DB_SAMPLE_ROWS>0 é rejeitado no modo http. |
KEN_MEMLIMIT | (não definido) | Limite de memória suave para o servidor de longa duração (1GiB, 512MiB ou contagem de bytes), aplicado via debug.SetMemoryLimit. Substitui GOMEMLIMIT quando ambos estão definidos. ken-mcp também define GOGC=50 por padrão (RSS de estado estacionário menor), a menos que você defina GOGC você mesmo. |
KEN_MCP_SHUTDOWN_GRACE | 5s | Após um SIGINT/SIGTERM, quanto tempo deixar chamadas de ferramentas em voo drenarem antes de forçar a saída (qualquer duração Go). Uma drenagem limpa sai com 0; uma saída forçada é status 1, e um segundo sinal força a saída imediata com 128+sinal (130 / 143). |
KEN_MCP_SNAPSHOT | 1 | Persiste o índice construído em <repo>/.ken/ e, na reinicialização, carrega-o + varredura de deriva (mtime+tamanho) em vez de reconstruir quando o repo está inalterado — o caminho rápido do dia a dia a frio. .ken/ é um cache (seguro para excluir; adicione-o a .gitignore). Apenas repos de caminho local. 0 desativa leitura+escrita. |
KEN_MCP_LAZY_ENRICH | 0 | Adia o enriquecimento estrutural para fora do caminho de construção a frio: serve um índice bruto para uma primeira consulta rápida em uma construção verdadeiramente fria (~2–3× mais rápido para o primeiro serviço em PHP), depois enriquece + republica em segundo plano. Os resultados são bem formados antes do enriquecimento, apenas com classificação mais baixa até que a passagem em segundo plano chegue. Afeta apenas uma construção do zero; um carregamento de snapshot já está enriquecido. Opt-in enquanto a calibração padrão está pendente. |
KEN_MCP_EMBED_CACHE | 0 | Cache persistente de sha256(chunk)→vector em <repo>/.ken/embed.db, para que uma reconstrução completa re-embuta apenas texto de chunk nunca visto (semântico/híbrido, repos locais). Segunda linha de defesa atrás do snapshot — ajuda reconstruções recorrentes (deriva pesada, mudança de modo). Escopo por modelo (uma mudança de modelo o limpa). Opt-in; a primeira construção o aquece (aproximadamente no mesmo nível de sem cache). KEN_MCP_EMBED_CACHE_MAX limita entradas (padrão 1.000.000). |
KEN_MCP_STAGED | 0 | Prontidão em etapas: em uma construção híbrida a frio, serve BM25 (somente lexical) instantaneamente (~4× mais rápido para a primeira consulta em PHP), depois enriquece + embute em segundo plano e atualiza para híbrido. As respostas das ferramentas carregam "semantic":"warming" até que a atualização chegue. Tem precedência sobre KEN_MCP_LAZY_ENRICH. Opt-in; afeta apenas uma construção do zero (um carregamento de snapshot já está completo). |
KEN_MAX_FILE_BYTES | 2MiB | Pula arquivos maiores que isso da indexação (512KiB / contagem de bytes). Aplica-se a ken index e ken-mcp. Reduza em repos com muitos artefatos para encolher o índice + memória. |
KEN_MAX_AVG_LINE_BYTES | 1000 | Pula arquivos minificados cuja cabeça amostrada tem média de mais que isso em bytes por linha (bundles JS/CSS construídos, JSON de linha única). 0 desativa a heurística. |
KEN_SKIP_GENERATED | 0 | Desativado por padrão: defina 1 para também pular código gerado cujo cabeçalho carrega uma tag @generated ou um banner Code generated … DO NOT EDIT (stubs protobuf, listers client-go, stringer/mockgen). Captura arquivos gerados que a heurística de minificação perde. Opt-in porque alguns corpora querem sua superfície gerada pesquisável. Aplica-se a ken index e ken-mcp. |
KEN_MAX_FILES | 1000000 | Limite de admissão na contagem de arquivos indexáveis — um repo acima disso é rejeitado com um erro em vez de arriscar OOM. Generoso por padrão (o kernel Linux tem ~80k arquivos); reduza para endurecer um servidor contra repos hostis, 0 = ilimitado. |
KEN_ENRICH_FILE_BUDGET_MS | 500 (ken-mcp); 2000 (ken index/search/bench/perf); 0 (ken build-index, biblioteca) | Limite de relógio de parede por arquivo no enriquecimento Arm B / parse estrutural tree-sitter. Um arquivo cujo parse excede isso é pulado (indexado sem seu rótulo estrutural — resultados não são afetados, apenas não enriquecidos), registrado e contado. Protege contra arquivos individuais patologicamente semelhantes a modelos que o limite de tamanho perde (exemplo real: grandes arrays de inicializadores designados em C estilo driver, gotreesitter#1100). 0 desativa. Desativado por padrão apenas para ken build-index e chamadores diretos da biblioteca (BuildAndSerializeIndex), para manter essa construção específica byte-determinística. |
KEN_ALLOW_PRIVATE_CLONE_TARGETS | 0 | Desativado por padrão: para URLs http(s) repo, ken rejeita endereços loopback / link-local / RFC1918 (proteção SSRF). Defina 1 para permitir hosts git internos. |
KEN_MCP_ALLOWED_REPO_ROOTS | (não definido) | Confina argumentos repo de caminho local fornecidos pelo agente a essas raízes (separados por lista de caminhos do SO, como PATH). Não definido = sem confinamento (qualquer caminho local) — e ken-mcp registra um aviso de segurança de inicialização alto nesse caso, já que um servidor de longa duração cujo agente também lida com conteúdo não confiável poderia ser direcionado a ler arquivos arbitrários. O análogo de caminho local da proteção SSRF de clone: defini-lo impede o agente de apontar ken para /etc, ~/.ssh, etc. (e silencia o aviso). KEN_MCP_DEFAULT_REPO é isento (o operador o garantiu). Symlinks são resolvidos antes da verificação. |
A referência completa de env — incluindo as variáveis de banco de dados KEN_DB_* — está em docs/USERS.md e docs/db-indexing.md. Para agentes que devem rotear entre ken e grep deliberadamente (em vez da instrução padrão "prefira ken" de ken), veja o trecho de roteamento em docs/USERS.md.
Ferramentas
Ambas as ferramentas principais retornam uma string markdown formatada idêntica à saída _format_results de semble. (ken-mcp também expõe sete ferramentas estruturais — definition, references, callers, outline, symbols, recently_changed, status — além de reindex_db quando um banco de dados está configurado; veja docs/USERS.md.)
search
| Arg | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
query | string | ✓* | — | Consulta em linguagem natural ou código. *Forneça query ou queries. |
queries | string[] | — | Lote de consultas em uma chamada (economiza idas e voltas); resultados agrupados por consulta. Mesmo modo/top_k/filtros aplicam-se a cada uma. Limitado a 20. | |
repo | string | — | URL https:// / http:// ou diretório local. Obrigatório se não houver KEN_MCP_DEFAULT_REPO. | |
mode | hybrid|semantic|bm25 | hybrid | Modo de busca. | |
top_k | int | 5 | Número de resultados (limita a contagem). | |
max_tokens | int | — | Orçamento opcional de tamanho de resposta. top_k limita a contagem, mas um chunk pode ser minúsculo ou enorme; com max_tokens definido, ken preenche a lista classificada de cima para baixo e descarta a cauda quando o custo estimado de tokens excederia (o hit principal sempre mantido). Aproximado — ken não inclui tokenizador BPE, então é uma heurística, não uma contagem exata. | |
explain | bool | false | Anota cada resultado com por que correspondeu: quais termos da consulta aparecem no chunk (kind=lexical) ou que surgiu por similaridade semântica sem sobreposição exata de termos (kind=semantic). Uma explicação de sobreposição lexical para depurar "por que isso está aqui?", não um detalhamento completo de classificação. |
find_related
| Arg | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
file_path | string | ✓ | — | Caminho como aparece em um resultado search. |
line | int (1-indexado) | ✓ | — | Uma linha dentro do chunk para semear a busca de similaridade. |
repo | string | — | Igual ao de search. | |
top_k | int | 5 | Número de chunks semelhantes. | |
max_tokens | int | — | Orçamento opcional de tamanho de resposta; mesmas semânticas de search. |
O que ken indexa
A recuperação híbrida de ken é calibrada para código-fonte (Python / Go / TypeScript / Java / Rust têm chunking ciente de linguagem; outros recaem no chunker de linha) e documentação (markdown dividido em limites de cabeçalho, blocos de código/tabelas mantidos atômicos, frontmatter tratado). Corpora mistos de código e docs roteiam por arquivo por extensão.
Também indexa esquemas de banco de dados junto com código — arquivos .sql estáticos (com dobramento de histórico de migração) e introspecção ao vivo de Postgres / SQLite / MySQL / MariaDB — para que um agente respondendo "como os usuários são autenticados" obtenha a função Go, o SQL que ela executa, a definição da tabela users e os relacionamentos FK em uma lista classificada. Referência completa (Tier-1/Tier-2, amostragem de linhas, LISTEN/NOTIFY, a ferramenta reindex_db, postura PII, todas as variáveis KEN_DB_*): docs/db-indexing.md.
Para prosa simples sem código ou docs estruturados, o modo BM25 (--mode=bm25) carrega o peso; o modelo semântico é treinado em código e não validado em texto literário.
Excluindo arquivos: .kenignore
ken respeita seus arquivos .gitignore (aninhados, por diretório). Mas muitos repos commitam arquivos que você não quer pesquisados — migrações geradas, bundles JS/CSS construídos, código vendido, fixtures. Coloque um .kenignore na raiz do repo (ou em qualquer subdiretório) para excluí-los da indexação. Usa a mesma sintaxe de .gitignore, é aplicado no momento da indexação e é honrado tanto por ken index quanto pelo watch ao vivo de ken-mcp:
# .kenignore — keep built + generated files out of the search index
web/assets/ # compiled front-end bundles
vendor/ # Composer / third-party PHP
runtime/ # Yii runtime cache + logs
**/migrations/*.php # generated DB migrations
*.min.js
*.min.css
Semânticas: união com .gitignore (um caminho é excluído se qualquer um o ignorar), avaliado independentemente para que um !negation em um arquivo não possa re-incluir o que o outro excluiu. .kenignore é ligado por padrão — um repo sem um se comporta exatamente como antes. Para uma migração drop-in de semble, ken também honra .sembleignore como fallback quando nenhum .kenignore está presente (.kenignore vence se ambos existirem). Em monorepos grandes com artefatos commitados, esta é a maior alavanca única no tamanho do índice, tempo de inicialização a frio e memória. Veja ADR-038.
Mesmo sem um .kenignore, o ken automaticamente ignora arquivos acima de KEN_MAX_FILE_BYTES (2 MiB) e arquivos minificados (comprimento médio de linha muito longo — bundles compilados, JSON de linha única) via KEN_MAX_AVG_LINE_BYTES. O .kenignore é para os caminhos específicos do repositório que essas heurísticas não capturam.
Ignorar código gerado (opt-in). Defina KEN_SKIP_GENERATED=1 para também ignorar arquivos cujo cabeçalho contenha um marcador de geração por máquina — @generated, ou um banner de Code generated … DO NOT EDIT (stubs de protobuf, listadores de client-go, saída de stringer/mockgen). Isso captura código-fonte gerado que parece normal para a heurística de minificação. Desativado por padrão, porque alguns corpora legitimamente querem sua superfície gerada pesquisável (um agente perguntando "onde está o cliente tipado para X" quer esse arquivo gerado). Honrado tanto no índice inicial quanto no watch ao vivo.
Como funciona
gitignore + .kenignore respecting walk
→ regex chunker (Python / Go / TS / Java / Rust) with line-chunker fallback
→ BM25 (Lucene variant, k1=1.5, b=0.75) + Model2Vec semantic (cosine over a dense matrix)
→ α-weighted RRF fusion (α auto-detected: 0.3 for symbol queries, 0.5 for NL)
→ file-coherence boost + query-type boosts (definition / embedded-symbol / stem-match)
→ path penalties (test files, compat / legacy, `.d.ts`) + file-saturation decay
→ top-k
O algoritmo de recuperação é um port verbatim do search.py + ranking/*.py do semble; veja docs/DESIGN.md §7 para cada constante e sutileza de ordem de pipeline, e §4 para o contrato de inferência do Model2Vec (safetensors de três tensores, a indireção de mapping[], a precisão float64 que é essencial para a paridade de cosseno).
Comparação com semble
| Propriedade | semble | ken |
|---|---|---|
| Linguagem / distribuição | Python · uvx / pip | Go · binário estático único |
| Cold start | ~500 ms (interpretador + numpy + modelo) | ~10–20 ms ken search sobre um índice minúsculo |
| Algoritmo de recuperação | implementação de referência | port verbatim (constantes + ordem de pipeline de search.py + ranking/*.py) |
| NDCG@10 no benchmark do semble | 0.854 | 0.842 híbrido (gap 0.012, 63 repositórios completos × 1.251 consultas) |
| Recall@10 em consultas de agente | (não medido) | ~0.97 híbrido (0.967 NL / 0.995 símbolo); fallback somente BM25 ~0.84 |
| Tokens para recall@10 | (não medido) | ~46× menos que grep+Read em consultas NL (4.120 vs 189.773 mediana, híbrido) |
| Servidor MCP | sim | sim — drop-in (mesmos schemas + formato de wire) |
| Tamanho do binário | n/a | release (slim) ken ~22 MB · ken-mcp ~38 MB |
Requer huggingface-cli | sim | não — ken download-model busca direto do HF |
Metodologia completa, o detalhamento por ablação (semântico-raw corresponde ao semble dentro de 0.003, validando o port de embedding + tokenizer + ANN), a âncora externa CoIR-CSN-Python e cada nota de rodapé estão em docs/BENCH.md.
Comparado a outras ferramentas de busca de código para agentes
A parte concorrida desta categoria se divide em um eixo: o que você precisa executar. A aposta do ken é que o modelo de embedding pertence dentro do binário — inferência Model2Vec puramente em Go, sem cgo — então não há mais nada para levantar: sem daemon de embedding, sem banco de dados vetorial, sem chave de API, air-gapped. Os dois pontos de comparação mais próximos:
- grepai — o análogo arquitetural mais próximo: um único binário Go com um watcher de arquivos e um servidor MCP, 100% local. Ele descarrega embeddings para um servidor Ollama separado (você instala + executa Ollama e baixa um modelo).
- claude-context (Zilliz) — o mais visível: busca híbrida BM25 + densa, mas apoiada em um banco de dados vetorial (Milvus auto-hospedado via Docker, ou Zilliz Cloud gerenciado) e um provedor de embeddings (API OpenAI / VoyageAI / Gemini, ou Ollama local).
| ken | grepai | claude-context | |
|---|---|---|---|
| Runtime | binário Go estático único (sem cgo) | binário Go único | Node/TS (npm) |
| Embeddings | em processo, Go puro (Model2Vec) | daemon Ollama externo | provedor externo (OpenAI / Voyage / Gemini, ou Ollama) |
| Serviços externos necessários | nenhum — busca automaticamente um modelo de ~60 MB, depois roda offline | Ollama (daemon + modelo) | banco de dados vetorial (Milvus/Docker ou Zilliz Cloud) + uma API/daemon de embedding |
| Recuperação | BM25 + denso + RRF + rerank ciente de código | denso + grafos de chamada | híbrido (BM25 + denso) |
| Recall / NDCG | 0.967 recall@10 · 0.842 NDCG@10, com harness de reprodução | não publicado | não publicado |
| Economia de tokens | ~46× vs grep+Read, medido + reproduzível | não publicado | reivindicação do fornecedor −39% vs uma linha de base |
| Velocidade | índice ~1.6 s / 13 k chunks; busca híbrida p50 ~1.5 ms (medido) | fornecedor: "10 k arquivos em segundos, consultas em ms" | depende do banco de dados vetorial + rede |
| Linguagens (estrutural) | 13 (tree-sitter) | 10 | nível de chunk, agnóstico de linguagem |
| Licença | MIT | MIT | MIT |
Duas ressalvas honestas. Primeiro, os números do ken vêm com comandos de reprodução (docs/BENCH.md); as células marcadas como "não publicado" significam que não encontramos nenhuma figura de benchmark padrão para citar e não avaliamos independentemente a velocidade dos outros — arquitetura, dependências e licença são os eixos verificáveis (em junho de 2026). Segundo, as ferramentas otimizam para coisas diferentes — o grepai adiciona rastreamento de grafo de chamadas; o claude-context depende de um banco de dados vetorial gerenciado para escalar. A reivindicação específica do ken é recall próximo ao grep com ~1–2 ordens de magnitude a menos de tokens, de um único binário sem serviços externos, cada número reproduzível.
Escolhendo um chunker
O chunker padrão regex lida bem com a maioria dos casos. O chunker opt-in treesitter (--chunker=treesitter / KEN_MCP_CHUNKER=treesitter, gotreesitter puramente em Go) vence de forma mensurável para Kotlin, Zig, TypeScript, Java, PHP e perde em Python, C, Rust, Lua, Scala — Δ líquido de −0.004 NDCG no geral (dentro do ruído), então permanece opt-in. A tabela completa de recomendações por linguagem está em docs/BENCH.md; a justificativa para o padrão permanecer regex é ADR-011.
Para autores de SDK: envie documentação como um único binário
A biblioteca mcp.Run permite que você incorpore um corpus //go:embed + o modelo Model2Vec em um único binário de servidor MCP estático — sem backend, sem banco de dados vetorial, sem egresso de rede por consulta, com versão fixada pelo artefato de build. ~20 linhas de main.go, go build, envie para um release do GitHub; usuários brew install e adicionam uma linha à configuração do agente. O walker e o indexador aceitam qualquer fs.FS (embed.FS, fstest.MapFS, com suporte a tarball), o que também fornece sandboxing do agente por construção.
Guia completo — o padrão canônico, índices pré-construídos para cold start rápido, o contrato de tamanho do binário e o pacote opt-in mcp/db — está em docs/DEVELOPERS.md.
Demos ao vivo (binários mcp.Run baixáveis sobre codebases reais, com transcrições de auditoria): release demos/v0.1.0 — Kubernetes v1.31.0 (59.795 chunks) e PostgreSQL 17.0 (64.506 chunks). Artigo: I shipped two downloadable code search binaries. The audit caught two bugs..
Roadmap
O registro de riscos com gatilhos explícitos está em docs/DESIGN.md §10; o tracker vivo de prontidão para 1.0 é docs/internal/road-to-1.0.md. A recuperação é tratada como fechada para 1.0 (a curva de relevância é plana); o trabalho restante é polimento + onboarding (levar novas instalações ao caminho híbrido) + distribuição.
Como isso foi construído
ken é um port. O algoritmo de recuperação é verbatim de MinishLab/semble (Python); a implementação em Go foi escrita por Claude sob restrições fixas: Go puro / sem cgo, constantes do algoritmo portadas verbatim e nunca ajustadas, a fonte original vence sempre que a reconstrução de Claude diverge do código vivo do semble. Essa última regra capturou cinco erros materiais durante o port do pipeline de rerank — cada um uma alucinação de som confiante que estava errada quando verificada contra a fonte Python. A disciplina de sempre verificar, a regra do port verbatim e o harness de paridade do tokenizer de 11k entradas (que revelou três bugs que um spot-check de 18 casos perdeu) são fornecidos por humanos. Cada decisão arquitetural está registrada em docs/internal/DECISIONS.md.
Agradecimentos
ken se apoia nos ombros do MinishLab — o algoritmo de recuperação, o modelo, toda a abordagem da tabela de embeddings são deles.
- semble — a implementação original em Python. © Thomas van Dongen, MIT.
- model2vec — a biblioteca de embeddings estáticos cujo formato de três tensores o ken implementa. © Thomas van Dongen, MIT.
- potion-code-16M — pesos do modelo, destilados de
nomic-ai/CodeRankEmbed(MIT), que por sua vez vem deSnowflake/snowflake-arctic-embed-m-long(Apache-2.0). © Minish Lab. Redistribuído conformeNOTICE.
Licença
ken é licenciado sob MIT. Ele inclui atribuição para os pesos de modelo redistribuídos em NOTICE e uma lista gerada de licenças de dependências em THIRD_PARTY_LICENSES.md; cada link na cadeia de proveniência é permissivo (MIT, Apache-2.0, MPL-2.0). Veja docs/DESIGN.md §6.
Para contribuidores: CLAUDE.md tem as convenções de build/teste/formatação e os invariantes do projeto (contrato de precisão, contrato de stdout/stderr).