PickySteve

Roteador de habilidades e seletor de contexto para agentes de codificação — recuperação híbrida + reclassificação escolhe a habilidade certa; um gate de injeção de prompt ONNX verifica tanto a solicitação quanto cada documento recuperado.

Documentação

PickySteve — picks the right skill for your coding agent

Exigente quanto ao que carrega no contexto, incluindo o que se recusa a carregar.

▶ Assista ao trailer

https://github.com/user-attachments/assets/8750946b-36be-4c48-bf73-79513451d1f5

license: MIT python 3.11+ CI

O PickySteve é uma camada de orquestração leve. Um modelo barato descobre qual habilidade uma solicitação realmente precisa, recupera essa única habilidade e entrega um pacote de contexto pequeno, focado e com limites de dados não confiáveis a um modelo capaz. Ele não despeja todas as ferramentas e documentos que você possui no contexto a cada solicitação.

Este repositório é a Fase 1 (MVP), construído a partir de uma especificação de arquitetura. O trabalho da Fase 2 (plataforma de rastreamento, harness de avaliação contínua, cofre de credenciais, sandbox) ainda não foi construído. Cada peça é adicionada apenas quando uma falha real da Fase 1 a justifica.

Início rápido em 30 segundos

# from the repo root (uv 0.10+; on Windows the venv python is .venv/Scripts/python.exe — substitute it throughout)
uv venv --python 3.11 .venv
uv pip install --python .venv/bin/python -r requirements.txt

# choose your model — local Ollama, OpenAI, Claude, OpenRouter, or any OpenAI-compatible endpoint
.venv/bin/python -m pickysteve.setup

# calibrate the reranker floor on the labeled set
.venv/bin/python eval/calibrate.py

# run one request
.venv/bin/python -m pickysteve "review my Rust endpoint for security and REST design"

Traga seu próprio modelo. python -m pickysteve.setup pergunta qual modelo usar e o salva. Funciona com qualquer coisa que fale a API compatível com OpenAI: Ollama local (offline, sem chave), OpenAI, Claude, Gemini, Llama, etc. via OpenRouter / LiteLLM / seus endpoints nativos compatíveis. Os benchmarks publicados foram medidos no qwen3:8b local; um modelo diferente apenas precisa de uma reexecução do eval/calibrate.py.

Nota: atualmente, esta é uma instalação uv / git clone. Ainda não há pacote PyPI, então uvx pickysteve e pipx install pickysteve não existem. Se isso mudar, esta seção ganha uma linha única. Por enquanto, o caminho mais rápido para um agente de codificação real é o instalador de conector abaixo.

Conecte-o ao seu agente (um comando)

python -m pickysteve.connectors.install --list   # see which of 18 agents are detected
python -m pickysteve.connectors.install --all    # wire every detected agent (backs up configs first)

Suporta Claude Code, Codex, Cursor, Windsurf, Cline, Roo Code, Gemini CLI, Qwen Code, Goose, OpenHands, GitHub Copilot, Kimi Code, OpenCode, ZeroClaw via MCP stdio, e Aider, Hermes, OpenClaw, NanoClaw via um proxy compatível com OpenAI em :8077/v1. Snippets de configuração completos por agente e a matriz de conectividade estão em INTEGRATIONS.md.

Como funciona

flowchart TD
    A[Request] --> B[Security Gate\nscan raw request]
    B -->|clean| C[Router\ncheap model → search query]
    B -->|injection| X1[Abort]
    C --> D[Retrieval\nBM25 + embeddings, RRF fused]
    D --> E[Security Gate\nscan every retrieved doc]
    E -->|clean| F[Rerank\ncross-encoder vs original request]
    E -->|poisoned| X2[Abort / drop candidate]
    F --> G[Floor + Dedupe\nbelow floor → clarify, don't guess]
    G --> H[Knowledge Graph\nconfused_with edges + distinguishers]
    H --> I[Judge\nLLM reads full skill bodies + KG notes]
    I --> J[Compat Check\nflag conflicts, don't merge]
    J --> K[Assembly\nnonce-wrapped untrusted-data boundary]
    K --> L[Execution\ncapable model does the work]
    L --> M[Log\nfull trace to logs/runs.jsonl]

Dez estágios: portão, roteamento, recuperação, novo portão no conteúdo recuperado, reclassificação, piso/deduplicação, contexto de grafo de conhecimento, juiz, verificação de compatibilidade, montagem, execução, registro. A segunda passagem do portão verifica cada candidato recuperado, não apenas a solicitação do usuário. A maioria dos projetos similares pula essa passagem, e ela é a superfície de maior risco: um documento de habilidade envenenado é conteúdo controlado por atacante, posicionado ao lado do seu modelo de execução.

A stack (e porquê)

PapelEscolhaNota
RuntimePython 3.11 via uvO Python padrão aqui é 3.14, que ainda tem wheels torch instáveis. uv fixa um venv isolado 3.11 onde a stack de ML é estável.
Portão de segurançastackone-defender[onnx]O verdadeiro defensor StackOne (port Python, v0.7.2), não um placeholder de regex. Classificador ONNX de ~22MB incluído, sem download.
Roteador / compat / esclarecimento / execuçãoOllama local qwen3:8b via /api/chat nativo (think:false)Funciona sem chave de nuvem. O endpoint compatível com OpenAI não honra o controle de pensamento para qwen3 (ele despeja a saída em um canal reasoning e deixa content vazio, cerca de 20x mais lento), então o cliente usa o endpoint nativo por padrão. Defina PS_OLLAMA_NATIVE=0 / PS_LLM_BASE_URL para qualquer host compatível com OpenAI.
Recuperaçãorank_bm25 + embeddings sentence-transformers, fundidos com RRFHíbrido de palavra-chave + denso.
ReclassificadorCross-encoder BAAI/bge-reranker-baseExatamente o modelo que a especificação nomeia. Sua saída é um logit, não uma probabilidade, então o piso é calibrado em vez de adivinhado.
RegistroJSONL planoRevisão manual é o processo de avaliação da Fase 1.

Dependências totais da Fase 1: stackone-defender, rank-bm25, sentence-transformers, openai, numpy. Esse é o conjunto mínimo que a especificação prescreve.

Duas decisões que a especificação deixou em aberto (decididas e documentadas)

  • Unidade de recuperação (§2.3): cada arquivo markdown é uma unidade de recuperação. Uma pasta de habilidade com vários arquivos (veja registry/rag-architecture/) gera múltiplas unidades compartilhando um skill_id. Após a reclassificação, unidades da mesma habilidade colapsam para a melhor na montagem, então o modelo de execução nunca recebe três pedaços de uma habilidade.
  • Política de portão em recuperação envenenada (§2.1): padrão RETRIEVED_INJECTION_POLICY=abort. Se um candidato recuperado dispara o portão (alto risco), a solicitação inteira é abortada. A alternativa documentada é drop, que descarta apenas esse candidato e continua. Para conteúdo permitido, mas sanitizado, o pipeline usa o texto sanitizado de Nível 1 a jusante (defesa em profundidade) e registra que a sanitização ocorreu.

Refinamentos após uma revisão adversarial de 21 agentes

A primeira validação revelou três falhas. Corrigi-las, e revisar adversariamente as correções, adicionou estes mecanismos. Veja FINDINGS.md para o antes/depois completo.

  • Escalonamento de Nível 3 (portão, apenas caminho de solicitação): uma pergunta legítima sobre injeção de prompt estava sendo bloqueada. O portão de solicitação agora habilita o hook LLM de Nível 3 do defensor sobre a banda cinza [0.64, 0.85), logo acima do limite de bloqueio calibrado de 0.64 do modelo. Um adjudicador barato pode resgatar um bloqueio potencial, mas nunca reverter uma permissão potencial, enquanto ataques quase certos (≥0.85) ainda bloqueiam duramente sem consultá-lo. Conteúdo de terceiros recuperado nunca escala (portão estrito).
  • Roteador de múltiplas intenções com resgate seguro §2.4: o roteador emite subconsultas e uniões de recuperação entre elas para recall. A reclassificação permanece governada pela solicitação original (§2.4). Apenas uma solicitação genuinamente composta (duas ou mais subintenções distintas) também maximiza sobre suas subconsultas, para revelar uma intenção secundária que a pontuação da solicitação completa enterraria.
  • Portão de dominância relativa: uma habilidade secundária é mantida apenas se pontuar pelo menos DOMINANCE_RATIO (0.08) vezes a habilidade principal. Isso mantém o PickySteve exigente em vez de despejar acompanhantes marginais.
  • Correção honesta #13: uma habilidade correta que o reclassificador subpontuou foi corrigida enriquecendo o documento de habilidade com vocabulário real de sintomas, não abaixando o piso sobre dados vazados. O piso é recalibrado em um conjunto rotulado sem vazamento com negativos difíceis.

Benchmarks

Todos os números abaixo vêm dos próprios documentos de avaliação e registros deste repositório.

100% × 10 consecutive runs — Base 26/26, Harder 42/42, Held-out 47/47

Reranker alone 71% vs full PickySteve pipeline 96% on 24 confusable skill pairs Routing accuracy across suites: Base, Harder, Held-out 100%; Adversarial 96%

Security gate: 100% attack detection, 0 bypasses, 0% false positives, 180-payload red-team Two-tier conformal gate: 96% recall at 38% of frontier cost

Precisão de roteamento, a tríade (DEEP_CONTEXT.md):

SuíteTarefasResultado
Base26100% × 10 execuções consecutivas (juiz qwen3)
Mais difícil (base + 16 adversariais brutais)42100% × 10 (juiz qwen3)
Retido (não visto, mecanismos de confusão novos)47100% × 10 (juiz cego Claude)
Heldout2 (mais difícil, conjunto adversarial deliberadamente não saturado)2423/24 (96%). Uma falha genuína em uma tarefa composta de canário/feature-flag onde a armadilha ficou acima do ouro (logs/heldout2_final_run.log)

O conjunto heldout2 é mantido deliberadamente difícil e não saturado. Novas tarefas de pares confusos são adicionadas mais rápido do que a stack de roteador/reclassificação é reajustada, então ele atua como um canário contínuo para regressões em vez de uma suíte que se espera atingir 100%.

Em um conjunto de precisão retido de 40 solicitações sem sobreposição de calibração (TEST_REPORT.md): 90% correto no geral, 100% de precisão top-1 (30/30 respondíveis), 96.7% de recall completo, MRR 1.000, 100% de rejeição fora do domínio (solicitações de haiku/receita corretamente recebem no_confident_match).

Portão de dois níveis (recall-all + abstenção conforme). O juiz local barato roteia previsões de singleton diretamente; casos ambíguos escalam para um juiz de fronteira (logs/two_tier.out):

MétricaResultado
Cobertura conforme44/47 = 94%
Roteado barato (singleton)29/47 = 62%, correto 27/29
Escalado para fronteira18/47 = 38%, correto 18/18
Top-1 combinado45/47 = 96%

Segurança, detecção de red-team (SECURITY_AUDIT.md, TEST_REPORT.md):

  • Corpus de 180 payloads (129 ataque / 51 benigno, 14 famílias de evasão): 100% de detecção de ataques, zero bypasses após endurecimento. A linha de base era 86%.
  • Corpus separado de 115 ataques: 97.6% de detecção no caminho de solicitação, 96.5% no conteúdo recuperado, acima de 87.1%. A taxa de falso positivo benigno permaneceu em 0.0% durante todo o processo.
  • No corpus adversarial de 180 payloads, a taxa de permissão benigna é 61% (39% de falso positivo em prompts deliberadamente complicados com sabor de segurança). No registro real de habilidades, falsos positivos são 0/43, verificado por uma passagem de aquecimento na inicialização que o servidor se recusa a servir sem.

Teste de classificação de registro de armadilhas (SIM_REPORT.md): 24 habilidades construídas para confundir um correspondente ingênuo, 14 tarefas. A habilidade de ouro superou todas as armadilhas 13/13 (100%), top-1 correto em 12/13 tarefas respondíveis, tratamento correto de sem correspondência 1/1.

Por que não apenas RAG ou LangGraph?

  • Ele não recupera tudo e deixa o modelo resolver. O piso, a deduplicação e o portão de razão de dominância existem para que o modelo de execução nunca veja documentos acompanhantes marginais. O objetivo é escolher uma coisa, não cinco coisas plausíveis.
  • Não é um framework de orquestração maior. Não há máquina de estados e nenhum runtime de grafo estilo LangGraph. A Fase 1 são cinco módulos Python (retrieval.py, rerank.py, router.py, security_gate.py, pipeline.py). Veja "Não-objetivos da Fase 1" abaixo para o que é deixado de fora (sem grafo de conhecimento como padrão, sem harness de avaliação contínua, sem sandbox) até que uma falha real justifique adicioná-lo.
  • O portão de segurança não é um complemento. A maioria das configurações RAG trata documentos recuperados como confiáveis uma vez que passam em um limite de similaridade. O PickySteve verifica o conteúdo recuperado através do mesmo portão de injeção que a solicitação do usuário, com falha fechada, antes de chegar à montagem.

[!IMPORTANT] Duas varreduras, falha fechada por design. Cada solicitação é verificada duas vezes: uma vez bruta antes do roteamento, e uma vez por candidato recuperado antes da montagem. Qualquer varredura pode abortar a solicitação ou descartar um único candidato envenenado. Em timeout, erro ou adjudicação LLM ambígua, o portão falha fechado. Nada ambíguo chega ao modelo de execução silenciosamente.

[!IMPORTANT] Conteúdo não confiável nunca se torna instruções. Documentos de habilidade recuperados são envolvidos em um limite de nonce aleatório por chamada (<<UNTRUSTED-{nonce}>>...<<END-{nonce}>>) antes de serem entregues ao modelo de execução, então um documento envenenado não pode forjar uma diretiva [SYSTEM]: ou fechar o limite prematuramente. Isso foi endurecido após uma descoberta real: delimitadores estáticos eram forjáveis por um corpo de habilidade elaborado (veja SECURITY_AUDIT.md, linha "assembly.py").

Visualizador ao vivo: abra assets/pickysteve_live.html em um navegador para ver Steve passar por portão, roteador, recuperação, reclassificação, juiz e montagem em uma solicitação de exemplo. Para a versão nativa de terminal, eval/run_examples.py transmite o mesmo rastreamento estágio por estágio para logs/runs.jsonl enquanto dirige as 18 solicitações de exemplo de ponta a ponta.

Princípio central

Pontuações de confiança e relevância medem similaridade tópica, não correção. Nada aqui afirma que uma recuperação estava certa, apenas que era plausível. Todo conteúdo recuperado é tratado como dados de baixa confiança, nunca como instruções.

Limitações conhecidas

[!WARNING] O PickySteve pode cometer erros. Não confie cegamente nele em tarefas críticas. Ele roteia para uma habilidade plausível, não uma garantidamente correta. Revise o que ele escolhe antes de agir.

  • Limiares calibrados com qwen3. O piso do reranker e a faixa cinza de escalonamento do Tier-3 são calibrados contra qwen3:8b como roteador/avaliador. Trocar o modelo local exige reexecutar eval/calibrate.py. Os limiares não são portáveis entre avaliadores por premissa.
  • Resíduo de injeção de prompt em latim sem inglês. Injeção em espanhol ainda pode contornar o classificador somente-inglês incluído em alguns casos. Isso é uma lacuna no modelo ONNX incluído, não um bug de lógica na fiação do portão.
  • Item residual do Heldout2. Uma falha genuína (tarefa #12, um caso composto de canário/feature-flag-vs- blue-green) onde a armadilha superou o ouro. Veja a tabela de benchmarks acima e logs/heldout2_final_run.log para o rastreamento completo.
  • O reranker (bge-reranker-base) leva cerca de 2s por 8 candidatos na CPU e domina a latência de ponta a ponta.
  • O roteador ocasionalmente pode decompor demais uma única intenção em múltiplas facetas, exibindo uma habilidade secundária marginal.
  • Cinco lacunas lógicas abertas no nível de especificação permanecem por design (veja abaixo) e TEST_REPORT.md §6.

Lacunas lógicas abertas (carregadas da especificação)

  1. Confiança não é correção. A pontuação do reranker é similaridade tópica, não qualidade de resultado. Não há loop de feedback de resultado; isso precisa de resultados reais rotulados ao longo do tempo.
  2. O roteador pode errar. A decomposição de intenção para solicitações vagas ou compostas é um problema de raciocínio difícil.
  3. A resolução de conflito de habilidades não está resolvida. A verificação de compatibilidade sinaliza conflitos em vez de resolvê-los.
  4. "Habilidades compatíveis podem ser combinadas" não tem definição concreta. Não há mesclagem automática de habilidades.
  5. Sem ponderação de recência ou confiança na recuperação. Uma habilidade desatualizada é classificada igual a uma nova com relevância equivalente. A desatualização é sinalizada, não rebaixada.

Não-objetivos da Fase 1 (intencionalmente ausentes)

Sem grafo de conhecimento ou LightRAG como caminho padrão, sem LangGraph ou framework de máquina de estados, sem rastreamento externo (Laminar/Langfuse), sem cofre de credenciais, sem harness de avaliação automatizada (DeepEval/Ragas), sem runtime de sandbox. Cada um é adicionado na Fase 2 somente quando uma falha real da Fase 1 justificar.

FAQ

Como o avaliador não é enganado? Dois avaliadores independentes rodam nas suítes de avaliação: um qwen3:8b local, e um modo de avaliador-cego-Claude onde o Claude escolhe a habilidade de causa raiz sem ver a resposta rotulada. Quando eles discordam, isso é informativo. No subconjunto adversarial mais difícil, o Claude pontuou menor (86%) que o avaliador local (91%) porque contestou alguns rótulos discutíveis. Essa divergência é tratada como um sinal de que o rótulo é ambíguo, não prova de que o avaliador está errado (DEEP_CONTEXT.md). Toda chamada de modelo no pipeline de avaliação é armazenada em cache, então uma determinada taxa de aprovação é determinística e reproduzível.

Isso não é apenas RAG? A recuperação é uma etapa entre dez. As etapas que não são RAG (os dois portões de segurança, o piso do reranker/portão de razão de dominância, a verificação de compatibilidade e o limite de dados não confiáveis com nonce) são onde a maior parte da engenharia e a maioria dos bugs corrigidos foram. RAG puro não se recusa a responder quando nada supera um piso calibrado, e não reexamina seus próprios documentos recuperados para injeção antes do uso.

O que acontece quando não há correspondência confiante? PickySteve retorna no_confident_match e faz uma pergunta de esclarecimento em vez de adivinhar. O piso é calibrado em um conjunto rotulado bom/ruim em vez de ajustado manualmente, e a filosofia documentada é que a confiança mede similaridade tópica, não correção. Quando nada supera a barra, uma pergunta é melhor que uma escolha errada. A rejeição fora do domínio foi testada em 100% em todas as suítes retidas.

Funciona com ferramentas que não suportam MCP? Sim. Um proxy compatível com OpenAI (pickysteve.connectors.http_server, porta 8077) fica na frente de qualquer ferramenta que aceite uma URL base OpenAI personalizada (Aider, Hermes, ZeroClaw, OpenCode, OpenClaw, NanoClaw). Há também um endpoint REST /pick e uma importação direta em Python para qualquer outra coisa. Veja INTEGRATIONS.md para a matriz completa de conectividade entre 18 agentes.

Quais são as limitações conhecidas? Veja a seção "Limitações conhecidas" acima e TEST_REPORT.md §6.

Configuração

# from this directory (uv 0.10+, Ollama with qwen3:8b running locally)
uv venv --python 3.11 .venv
uv pip install --python .venv/bin/python -r requirements.txt

No Windows, o python do venv está em .venv/Scripts/python.exe; substitua-o em todos os comandos abaixo.

Uso

# 1) Calibrate the reranker floor on the labeled set (writes eval/calibrated_floor.json)
.venv/bin/python eval/calibrate.py

# 2) Run a single request
.venv/bin/python -m pickysteve "review my Rust endpoint for security and REST design"

# 3) Run the 18 example requests end to end (traces -> logs/runs.jsonl)
.venv/bin/python eval/run_examples.py          # add --no-exec to skip the execution model

# 4) The mandatory security-gate test
.venv/bin/python tests/test_security_gate.py

A configuração é toda por variáveis de ambiente (PS_*). Veja pickysteve/config.py.

Conecte-o ao seu agente de codificação

Usuários do Claude Code podem instalar o PickySteve como um plugin (após a configuração do venv acima):

/plugin marketplace add KernelLord/pickysteve
/plugin install pickysteve@pickysteve

Depois, crie o venv uma vez dentro do diretório do plugin instalado (~/.claude/plugins/cache/pickysteve/…), os mesmos dois comandos uv do quickstart. Todo o resto usa os conectores diretamente:

# MCP (Claude Code, Codex, Cursor, Windsurf, Cline, Roo, Gemini CLI, Qwen Code, Goose, ...):
.venv/bin/python -m pickysteve.connectors.mcp_server      # exposes pick_context + list_skills

# OpenAI-compatible proxy (Aider, Hermes, ZeroClaw, ...): point the tool's base URL at :8077/v1
.venv/bin/python -m pickysteve.connectors.http_server     # /pick + /v1/chat/completions

Trechos de configuração completos por agente, o instalador de um comando e a exportação do segundo cérebro do Obsidian (python -m pickysteve.connectors.obsidian --vault <path>) estão documentados em INTEGRATIONS.md.

Contribuindo

Veja CONTRIBUTING.md para configuração de desenvolvimento, o layout da suíte de avaliação/teste (o que é uma verificação rápida de pré-commit vs. o que precisa de um Ollama ativo), a regra de que qualquer mudança que afete o roteamento deve reexecutar a tríade base/mais difícil/retido antes do merge, e convenções de estilo de código.

Créditos

Música do trailer: "Powerful Emotional Trailer" por MaxKoMusic, via Chosic, licenciado sob CC BY-SA 3.0.

Licença

MIT. Veja o arquivo LICENSE para o texto completo.