PickySteve

Enrutador de habilidades y selector de contexto para agentes de codificación: recuperación híbrida + reordenamiento selecciona la habilidad correcta; una puerta de inyección de indicaciones ONNX escanea tanto la solicitud como cada documento recuperado.

Documentación

PickySteve — picks the right skill for your coding agent

Exigente con lo que carga en el contexto, incluido lo que se niega a cargar.

▶ Ver el tráiler

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

license: MIT python 3.11+ CI

PickySteve es una capa de orquestación ligera. Un modelo barato determina qué habilidad necesita realmente una solicitud, recupera esa única habilidad y entrega un paquete de contexto pequeño, enfocado y con límites de datos no confiables a un modelo capaz. No vuelca todas las herramientas y documentos que posees en el contexto en cada solicitud.

Este repositorio es la Fase 1 (MVP), construido según una especificación de arquitectura. El trabajo de la Fase 2 (plataforma de trazabilidad, arnés de evaluación permanente, bóveda de credenciales, sandbox) aún no está construido. Cada pieza se agrega solo cuando un fallo real de la Fase 1 lo justifica.

Inicio rápido en 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"

Trae tu propio modelo. python -m pickysteve.setup pregunta qué modelo usar y lo guarda. Se ejecuta en cualquier cosa que hable la API compatible con OpenAI: Ollama local (sin conexión, sin clave), OpenAI, Claude, Gemini, Llama, etc. a través de OpenRouter / LiteLLM / sus endpoints nativos compatibles. Los benchmarks publicados se midieron en qwen3:8b local; un modelo diferente solo necesita una re-ejecución de eval/calibrate.py.

Nota: actualmente es una instalación de uv / git clone. Aún no hay paquete PyPI, por lo que uvx pickysteve y pipx install pickysteve no existen. Si eso cambia, esta sección recibe una línea. Por ahora, el camino más rápido hacia un agente de codificación real es el instalador de conectores a continuación.

Conéctalo a tu agente (un 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)

Soporta Claude Code, Codex, Cursor, Windsurf, Cline, Roo Code, Gemini CLI, Qwen Code, Goose, OpenHands, GitHub Copilot, Kimi Code, OpenCode, ZeroClaw a través de MCP stdio, y Aider, Hermes, OpenClaw, NanoClaw a través de un proxy compatible con OpenAI en :8077/v1. Los fragmentos de configuración completos por agente y la matriz de conectividad están en INTEGRATIONS.md.

Cómo 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]

Diez etapas: puerta, enrutamiento, recuperación, puerta nuevamente sobre el contenido recuperado, reordenamiento, piso/deduplicación, contexto de grafo de conocimiento, juez, verificación de compatibilidad, ensamblaje, ejecución, registro. La segunda pasada de la puerta escanea cada candidato recuperado, no solo la solicitud del usuario. La mayoría de los proyectos similares omiten esa pasada, y es la superficie de mayor riesgo: un documento de habilidad envenenado es contenido controlado por el atacante que se encuentra justo al lado de tu modelo de ejecución.

La pila (y por qué)

RolElecciónNota
Tiempo de ejecuciónPython 3.11 via uvEl Python predeterminado aquí es 3.14, que aún tiene wheels inestables de torch. uv fija un venv aislado de 3.11 donde la pila de ML es estable.
Puerta de seguridadstackone-defender[onnx]El defensor real de StackOne (puerto de Python, v0.7.2), no un marcador de posición de regex. Clasificador ONNX incluido de ~22MB, sin descarga.
Enrutador / compat / aclaración / ejecuciónOllama local qwen3:8b a través del /api/chat nativo (think:false)Se ejecuta sin clave en la nube. El endpoint compatible con OpenAI no respeta el control de pensamiento para qwen3 (vuelca la salida en un canal reasoning y deja content vacío, aproximadamente 20 veces más lento), por lo que el cliente usa el endpoint nativo por defecto. Establece PS_OLLAMA_NATIVE=0 / PS_LLM_BASE_URL para cualquier host compatible con OpenAI.
Recuperaciónrank_bm25 + sentence-transformers embeddings, fusionados con RRFHíbrido de palabras clave + denso.
ReordenadorBAAI/bge-reranker-base cross-encoderExactamente el modelo que nombra la especificación. Su salida es un logit, no una probabilidad, por lo que el piso se calibra en lugar de adivinarse.
RegistroJSONL planoLa revisión manual es el proceso de evaluación de la Fase 1.

Dependencias totales de la Fase 1: stackone-defender, rank-bm25, sentence-transformers, openai, numpy. Ese es el conjunto mínimo que prescribe la especificación.

Dos decisiones que la especificación dejó abiertas (decididas y documentadas)

  • Unidad de recuperación (§2.3): cada archivo markdown es una unidad de recuperación. Una carpeta de habilidades con varios archivos (ver registry/rag-architecture/) produce múltiples unidades que comparten un skill_id. Después del reordenamiento, las unidades de la misma habilidad se colapsan a la mejor en el ensamblaje, por lo que el modelo de ejecución nunca recibe tres fragmentos de una misma habilidad.
  • Política de puerta sobre una recuperación envenenada (§2.1): por defecto RETRIEVED_INJECTION_POLICY=abort. Si un candidato recuperado activa la puerta (alto riesgo), toda la solicitud se aborta. La alternativa documentada es drop, que descarta solo ese candidato y continúa. Para contenido permitido pero sanitizado, el pipeline usa el texto sanitizado de Nivel 1 aguas abajo (defensa en profundidad) y registra que ocurrió la sanitización.

Refinamientos después de una revisión adversarial de 21 agentes

La primera validación reveló tres fallos. Arreglarlos, y revisar adversarialmente los arreglos, agregó estos mecanismos. Ver FINDINGS.md para el antes/después completo.

  • Escalada de Nivel 3 (puerta, solo ruta de solicitud): una pregunta legítima sobre inyección de prompts estaba siendo bloqueada. La puerta de solicitud ahora habilita el gancho LLM de Nivel 3 del defensor sobre la banda gris [0.64, 0.85), justo por encima del umbral de bloqueo calibrado de 0.64 del modelo. Un adjudicador barato puede rescatar un bloqueo potencial pero nunca cambiar un permiso potencial, mientras que los ataques casi seguros (≥0.85) aún se bloquean duramente sin consultarlo. El contenido de terceros recuperado nunca escala (puerta estricta).
  • Enrutador de múltiples intenciones con rescate seguro según §2.4: el enrutador emite subconsultas y uniones de recuperación entre ellas para el recall. El reordenamiento sigue gobernado por la solicitud original (§2.4). Solo una solicitud genuinamente compuesta (dos o más subintenciones distintas) también maximiza sobre sus subconsultas, para sacar a la superficie una intención secundaria que la puntuación de la solicitud completa enterraría.
  • Puerta de dominancia relativa: una habilidad secundaria se mantiene solo si puntúa al menos DOMINANCE_RATIO (0.08) veces la habilidad principal. Esto mantiene a PickySteve exigente en lugar de volcar acompañantes marginales.
  • Arreglo honesto #13: una habilidad correcta que el reordenador subestimó se arregló enriqueciendo el documento de habilidad con vocabulario real de síntomas, no bajando el piso hacia datos filtrados. El piso se recalibra en un conjunto etiquetado sin fugas con negativos duros.

Benchmarks

Todos los números a continuación provienen de los documentos y registros de evaluación de este repositorio.

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

Precisión de enrutamiento, la trifecta (DEEP_CONTEXT.md):

ConjuntoTareasResultado
Base26100% × 10 ejecuciones consecutivas (juez qwen3)
Más difícil (base + 16 adversarial brutal)42100% × 10 (juez qwen3)
Reservado (mecanismos de confusión nuevos no vistos)47100% × 10 (juez ciego de Claude)
Heldout2 (conjunto adversarial más difícil, deliberadamente no saturado)2423/24 (96%). Un fallo genuino en una tarea compuesta de canario/feature-flag donde la trampa se clasificó por encima del oro (logs/heldout2_final_run.log)

El conjunto heldout2 se mantiene deliberadamente difícil y no saturado. Se agregan nuevas tareas de pares confusables más rápido de lo que se reajusta la pila de enrutador/reordenamiento, por lo que actúa como un canario continuo para regresiones en lugar de un conjunto que se espera que alcance el 100%.

En un conjunto de precisión reservado de 40 solicitudes sin superposición de calibración (TEST_REPORT.md): 90% correcto en general, 100% de precisión top-1 (30/30 respondibles), 96.7% de recall completo, MRR 1.000, 100% de rechazo fuera de dominio (las solicitudes de haiku/receta correctamente obtienen no_confident_match).

Puerta de dos niveles (recall-all + abstención conforme). El juez local barato enruta predicciones singleton directamente; los casos ambiguos escalan a un juez de frontera (logs/two_tier.out):

MétricaResultado
Cobertura conforme44/47 = 94%
Enrutado barato (singleton)29/47 = 62%, correcto 27/29
Escalado a frontera18/47 = 38%, correcto 18/18
Top-1 combinado45/47 = 96%

Seguridad, detección de red team (SECURITY_AUDIT.md, TEST_REPORT.md):

  • Corpus de 180 cargas (129 ataques / 51 benignos, 14 familias de evasión): 100% de detección de ataques, cero bypass después del endurecimiento. La línea base era 86%.
  • Corpus separado de 115 ataques: 97.6% de detección en la ruta de solicitud, 96.5% en contenido recuperado, frente al 87.1%. La tasa de falsos positivos benignos se mantuvo en 0.0% en todo momento.
  • En el corpus adversarial de 180 cargas, la tasa de permitir benignos es 61% (39% de falsos positivos en prompts deliberadamente engañosos con sabor a seguridad). En el registro de habilidades real, los falsos positivos son 0/43, verificado por una pasada de calentamiento al inicio que el servidor se niega a servir sin ella.

Prueba de clasificación de registro de trampas (SIM_REPORT.md): 24 habilidades construidas para confundir a un matcher ingenuo, 14 tareas. La habilidad dorada superó a cada trampa 13/13 (100%), top-1 correcto en 12/13 tareas respondibles, manejo correcto de no coincidencia 1/1.

¿Por qué no solo RAG o LangGraph?

  • No recupera todo y deja que el modelo lo resuelva. El piso, la deduplicación y la puerta de ratio de dominancia existen para que el modelo de ejecución nunca vea documentos marginales de acompañamiento. El objetivo es elegir una cosa, no cinco cosas plausibles.
  • No es un marco de orquestación más grande. No hay máquina de estados ni runtime de grafo estilo LangGraph. La Fase 1 son cinco módulos de Python (retrieval.py, rerank.py, router.py, security_gate.py, pipeline.py). Ver "No objetivos de la Fase 1" a continuación para lo que se omite (sin grafo de conocimiento por defecto, sin arnés de evaluación permanente, sin sandbox) hasta que un fallo real justifique agregarlo.
  • La puerta de seguridad no es un complemento. La mayoría de las configuraciones RAG tratan los documentos recuperados como confiables una vez que superan un umbral de similitud. PickySteve escanea el contenido recuperado a través de la misma puerta de inyección que la solicitud del usuario, con fallo cerrado, antes de que llegue al ensamblaje.

[!IMPORTANT] Dos escaneos, fallo cerrado por diseño. Cada solicitud se escanea dos veces: una vez en bruto antes del enrutamiento, y una vez por cada candidato recuperado antes del ensamblaje. Cualquier escaneo puede abortar la solicitud o descartar un único candidato envenenado. En caso de tiempo de espera, error o una adjudicación ambigua del LLM, la puerta falla cerrada. Nada ambiguo llega al modelo de ejecución en silencio.

[!IMPORTANT] El contenido no confiable nunca se convierte en instrucciones. Los documentos de habilidad recuperados se envuelven en un límite de nonce aleatorio por llamada (<<UNTRUSTED-{nonce}>>...<<END-{nonce}>>) antes de entregarse al modelo de ejecución, por lo que un documento envenenado no puede forjar una directiva [SYSTEM]: ni cerrar el límite temprano. Esto se endureció después de un hallazgo real: los delimitadores estáticos eran falsificables mediante un cuerpo de habilidad manipulado (ver SECURITY_AUDIT.md, fila "assembly.py").

Visualizador en vivo: abre assets/pickysteve_live.html en un navegador para ver a Steve pasar por puerta, enrutador, recuperación, reordenamiento, juez y ensamblaje en una solicitud de muestra. Para la versión nativa de terminal, eval/run_examples.py transmite el mismo rastreo etapa por etapa a logs/runs.jsonl mientras impulsa las 18 solicitudes de ejemplo de principio a fin.

Principio central

Las puntuaciones de confianza y relevancia miden similitud temática, no corrección. Nada aquí afirma que una recuperación fue correcta, solo que fue plausible. Todo el contenido recuperado se trata como datos de baja confianza, nunca como instrucciones.

Limitaciones conocidas

[!WARNING] PickySteve puede cometer errores. No confíes ciegamente en él para tareas críticas. Enruta a una habilidad plausible, no a una garantizada correcta. Revisa lo que elige antes de actuar sobre ello.

  • Umbrales calibrados con qwen3. El piso del reranker y la banda gris de escalada de Nivel 3 están calibrados contra qwen3:8b como enrutador/juez. Cambiar el modelo local requiere volver a ejecutar eval/calibrate.py. Los umbrales no son portables entre jueces por suposición.
  • Residual de inyección de prompts en escritura latina no inglesa. La inyección en español aún puede eludir el clasificador integrado solo en inglés en algunos casos. Esto es una brecha en el modelo ONNX integrado, no un error de lógica en el cableado de la puerta.
  • Elemento residual de Heldout2. Un fallo genuino (tarea #12, un caso compuesto de canario/feature-flag vs. blue-green) donde la trampa superó al oro. Consulte la tabla de benchmarks anterior y logs/heldout2_final_run.log para el rastreo completo.
  • El reranker (bge-reranker-base) tarda aproximadamente 2s por 8 candidatos en CPU y domina la latencia de extremo a extremo.
  • El enrutador ocasionalmente puede sobre-descomponer una única intención en múltiples facetas, mostrando una habilidad secundaria marginal.
  • Quedan cinco brechas lógicas abiertas a nivel de especificación por diseño (ver más abajo) y TEST_REPORT.md §6.

Brechas lógicas abiertas (arrastradas desde la especificación)

  1. La confianza no es corrección. La puntuación de rerank es similitud temática, no calidad de resultado. No hay bucle de retroalimentación de resultados; eso necesita resultados reales etiquetados con el tiempo.
  2. El enrutador puede equivocarse. La descomposición de intenciones para solicitudes vagas o compuestas es un problema de razonamiento difícil.
  3. La resolución de conflictos de habilidades no está resuelta. La verificación de compatibilidad señala conflictos en lugar de resolverlos.
  4. "Las habilidades compatibles se pueden combinar" no tiene una definición concreta. No hay fusión automática de habilidades.
  5. No hay ponderación de recencia o confianza en la recuperación. Una habilidad obsoleta se clasifica igual que una nueva con relevancia equivalente. La obsolescencia se señala, no se pondera a la baja.

No objetivos de la Fase 1 (intencionalmente ausentes)

Sin grafo de conocimiento ni LightRAG como ruta predeterminada, sin LangGraph ni framework de máquina de estados, sin trazado externo (Laminar/Langfuse), sin bóveda de credenciales, sin arnés de evaluación automatizado (DeepEval/Ragas), sin runtime de sandbox. Cada uno se agrega en la Fase 2 solo cuando un fallo real de la Fase 1 lo justifica.

Preguntas frecuentes

¿Cómo no se manipula al juez? Dos jueces independientes se ejecutan en los conjuntos de evaluación: un qwen3:8b local, y un modo de juez ciego a Claude donde Claude elige la habilidad de causa raíz sin ver la respuesta etiquetada. Cuando no están de acuerdo, es informativo. En el subconjunto adversarial más difícil, Claude obtuvo una puntuación más baja (86%) que el juez local (91%) porque disputó un par de etiquetas discutibles. Esa divergencia se trata como una señal de que la etiqueta es ambigua, no como prueba de que el juez esté equivocado (DEEP_CONTEXT.md). Cada llamada de modelo en el pipeline de evaluación se almacena en caché, por lo que una tasa de aprobación dada es determinista y reproducible.

¿No es esto solo RAG? La recuperación es una etapa de diez. Las etapas que no son RAG (las dos puertas de seguridad, el piso del reranker/compuerta de relación de dominancia, la verificación de compatibilidad y el límite de datos no confiables envuelto en nonce) son donde se fue la mayor parte de la ingeniería y la mayoría de los errores corregidos. El RAG simple no se niega a responder cuando nada supera un piso calibrado, y no vuelve a escanear sus propios documentos recuperados en busca de inyección antes de usarlos.

¿Qué sucede cuando no hay una coincidencia confiable? PickySteve devuelve no_confident_match y hace una pregunta aclaratoria en lugar de adivinar. El piso se calibra en un conjunto etiquetado de bueno/malo en lugar de ajustarse manualmente, y la filosofía documentada es que la confianza mide similitud temática, no corrección. Cuando nada supera el listón, una pregunta es mejor que una elección incorrecta. El rechazo fuera de dominio se ha probado al 100% en todos los conjuntos de exclusión.

¿Funciona con herramientas que no soportan MCP? Sí. Un proxy compatible con OpenAI (pickysteve.connectors.http_server, puerto 8077) se sitúa frente a cualquier herramienta que acepte una URL base personalizada de OpenAI (Aider, Hermes, ZeroClaw, OpenCode, OpenClaw, NanoClaw). También hay un endpoint REST /pick y una importación directa de Python para cualquier otra cosa. Consulte INTEGRATIONS.md para la matriz de conectividad completa en 18 agentes.

¿Cuáles son las limitaciones conocidas? Consulte la sección "Limitaciones conocidas" anterior y TEST_REPORT.md §6.

Configuración

# 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

En Windows, el python del venv se encuentra en .venv/Scripts/python.exe; sustitúyalo en cada comando a continuación.

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

La configuración es todas variables de entorno (PS_*). Consulte pickysteve/config.py.

Conéctalo a tu agente de codificación

Los usuarios de Claude Code pueden instalar PickySteve como un plugin (después de la configuración del venv anterior):

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

Luego construya el venv una vez dentro del directorio del plugin instalado (~/.claude/plugins/cache/pickysteve/…), los mismos dos comandos uv que en el inicio rápido. Todo lo demás usa los conectores directamente:

# 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

Los fragmentos de configuración completos por agente, el instalador de un comando y la exportación de segundo cerebro de Obsidian (python -m pickysteve.connectors.obsidian --vault <path>) están documentados en INTEGRATIONS.md.

Contribuciones

Consulte CONTRIBUTING.md para la configuración de desarrollo, la disposición del conjunto de evaluación/pruebas (qué es una verificación rápida de pre-commit vs. qué necesita un Ollama en vivo), la regla de que cualquier cambio que afecte el enrutamiento debe volver a ejecutar la trifecta base/más difícil/exclusión antes de fusionar, y las convenciones de estilo de código.

Créditos

Música de tráiler: "Powerful Emotional Trailer" de MaxKoMusic, vía Chosic, licenciada bajo CC BY-SA 3.0.

Licencia

MIT. Consulte el archivo LICENSE para el texto completo.