kdd-gates
KDD methodology gates as DeepSeek Harness (dsh) plugin tools
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 27, 2026
- Updated
- Aug 27, 2026
Introduction
kdd-gates
Plugin de composición para DeepSeek Harness (dsh) que expone los gates deterministas de la metodología KDD (Knowledge-Driven Development) como Tools nativas del modelo: kdd_validate, kdd_seal, kdd_perimeter, kdd_preflight.
No reimplementa la lógica de KDD — es un envoltorio delgado que llama a los scripts Python de la propia plantilla KDD (scripts/*.py) sobre el repoRoot que le pases.
Requisito
El repo destino (repoRoot) tiene que tener la plantilla KDD instalada: carpetas scripts/, knowledge/, .agents/. Sin eso, las tools fallan (no hay nada que ejecutar).
Si el proyecto no la tiene todavía, instanciala desde una copia de la plantilla:
python scripts/init_project.py --repo-dir <tu-proyecto> --apply --name "Nombre del proyecto"
Esto borra los artefactos de ejemplo y deja el tooling de KDD listo — a partir de ahí el proyecto es independiente de la plantilla original.
Instalación
-
Dependencias locales (el plugin corre desde su propia carpeta, necesita su propio
node_modules):cd kdd-gates npm install(
@deepseek-ai/dsh-toolsy@deepseek-ai/dsh-sandbox, mismas versiones que eldshinstalado — verpackage.json.) -
Montarlo en el perfil de
dsh(~/.dsh/profiles/<perfil>/cordis.patch.yml):- insert: - id: kdd-gates name: 'file:///C:/ruta/a/kdd-gates/host.js' -
Reiniciar el proceso de
dshpara que cargue el plugin.
Tools
kdd_validate
Corre los 2 gates core de Nivel 1: validate_contracts + validate_okf. Rápido (segundos), sin subprocesos anidados.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
repoRoot | string | sí | Ruta absoluta del repo KDD. |
contractsDir | string | no | Default knowledge/contracts. |
okfDir | string | no | Default knowledge. |
kdd_seal
Sella el oráculo: devuelve el SHA256 (normalizado LF) de un archivo de tests, para pegar en tests_sha256 del contrato.
| Parámetro | Tipo | Requerido |
|---|---|---|
repoRoot | string | sí |
testsPath | string | sí (ruta del archivo de tests, relativa al repo) |
kdd_perimeter
Gate de perímetro: compara archivos cambiados contra el touch_only de un contrato. Detecta OUT_OF_PERIMETER y TESTS_TOUCHED.
| Parámetro | Tipo | Requerido |
|---|---|---|
repoRoot | string | sí |
contract | string | sí (ej. knowledge/contracts/mi-tarea.md) |
changedFiles | array<string> | sí (ej. salida de git diff --name-only) |
kdd_preflight
Dry-run de los 19 gates completos (18 Nivel 1 + validate_attestation) en una sola pasada.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
repoRoot | string | sí | |
sandboxPermissions | string enum ["danger-full-access"] | no | Escala el sandbox para el gate validate_test_commands (ver abajo). Pide aprobación real al usuario. |
justification | string | no | Obligatorio junto con sandboxPermissions. |
run_in_background | boolean | no | Corre como job (job_output/job_kill genéricos de dsh) en vez de bloquear la llamada. |
Por qué validate_test_commands suele fallar sin escalar: ese gate corre la suite de tests propia del repo, que necesita escribir en %TEMP% y capturar salida de subprocesos — ambas cosas denegadas por el sandbox workspace-write. No es un defecto del repo ni de este plugin: es el límite de seguridad esperado. Con sandboxPermissions: "danger-full-access" + justification, ese gate puntual se reintenta con acceso completo (el resto de los 18 gates siempre corre sin escalar — ver "Decisiones de diseño").
Guía de flujo para el modelo
El plugin inyecta una sección de system prompt (visible para el modelo, no para vos) que enseña el orden correcto de la metodología — oráculo primero, sellar, contrato, validar, implementar, verificar, perímetro — así el agente no necesita que se lo expliquen en cada tarea. Se activa automáticamente al montar el plugin, sin importar qué agent preset esté en uso.
Forzar la separación implementador/validador con un subagente restringido
La regla central de KDD ("el agente que implementa nunca es el mismo que decide si está bien") normalmente depende solo de que el modelo la respete — es guía de system prompt, no una restricción real. Se puede hacer cumplir mecánicamente montando una segunda instancia de @deepseek-ai/dsh-tool-subagent con un toolFilter que le niegue las 5 tools de este plugin al hijo delegado. No es un plugin nuevo — es config sobre un paquete que ya viene con dsh — así que vive directo en cordis.patch.yml, no en este repo:
- insert:
- id: tool-subagent-kdd-implementer
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn # hijo fresco, sin historial del padre — el contrato
# escrito es la interfaz, no la memoria conversacional
toolName: kdd_delegate_implementer
backgroundMode: one-shot
toolFilter:
deny:
- kdd_validate
- kdd_seal
- kdd_perimeter
- kdd_preflight
- kdd_scaffold # si tenés kdd-scaffold montado también
persona: >-
You are a KDD implementer subagent. You write code to satisfy a task
contract's frozen oracle — you never decide whether it's correct. The
kdd_* gate tools are not available to you by design. Implement the
target, run the contract's test_command yourself as a sanity check,
and report back — the delegating agent runs the real gates.
Por qué deny y no allow: un allow-list exige enumerar cada tool genérica que el implementador va a necesitar (edición de archivos, shell, búsqueda...) — fácil de romper por omisión. deny solo bloquea las tools de decisión, deja todo lo demás intacto.
Trampa real encontrada al verificarlo: toolFilter.deny con el nombre de una tool que no está montada en ese momento (ej. kdd_scaffold sin el plugin kdd-scaffold cargado) hace que tools.restrict() explote al arrancar la delegación, con Error: tools.restrict() names unknown global tool "kdd_scaffold". El deny-list tiene que coincidir exactamente con qué plugins están montados en el mismo perfil.
Verificado: delegando una tarea al subagente y pidiéndole que reporte textualmente su lista de tools disponibles, confirmó que ninguna de las 5 tools KDD aparece — no pudo ni intentar llamarlas.
Decisiones de diseño (por qué está armado así)
- Schemas 100% tipados (
string/array/boolean, nuncaoneOfnitype:"json"sin tipo de nivel superior) — este deployment tiene un bug conocido del materializador de argumentos que pierde parámetros sin tipo explícito con ciertos modelos (discussion). Evitarlo en el diseño es más simple que depender de que esté parcheado. kdd_preflightusactx.shell, noctx.subprocess.spawndirecto.preflight.pylanza sus propios subprocesos anidados (uno por gate); el spawn directo se cuelga indefinidamente en Windows para ese patrón — probado conargvplano y concmd.exede por medio, ninguno funcionó.ctx.shell(el mismo servicio detrás de la toolpwshdel agente) sí. Reportado como bug aparte: discussion #4796. El wrapper vive ensafe-shell.js(runNested(shell, opts)), separado dehost.js, para que una tool nueva que necesite el mismo patrón lo reuse en vez de reimplementarlo inline.- El preflight completo siempre corre sin escalar, y si se pidió escalada,
validate_test_commandsse corre aparte, llamando directo avalidate_test_commands.run_all(..., timeout=300)(su API pública) en vez de pormain()(que fija 120s sin exponerlo). Escalar el árbol anidado completo depreflight.pyde una sola vez se cuelga por una causa que no se aisló; separar las dos corridas evita el problema sin tocar ningún archivo de KDD. - El temp para
validate_test_commandsvive fuera del repo (%TEMP%/kdd-preflight-tmp, borrado y recreado antes de cada corrida). Ponerlo dentro del repo (<repoRoot>/.kdd-tmp) causó una recursión real: un test que copia el repo entero (shutil.copytree) terminaba copiando corridas anteriores sin limpiar, cada una con su propio.kdd-tmpadentro, anidándose sin fin. - La escalada de sandbox pasa por el servicio real de aprobación (
ctx.approval, el mismo que usadsh-tool-pwsh) — nunca hay bypass silencioso: sinsandboxPermissions+justificationexplícitos en la llamada, el preflight corre con la política estándar.
Estado verificado
- Ciclo KDD completo (oráculo → sello → contrato →
kdd_validate→ implementación →kdd_perimeterpositivo y negativo) probado de punta a punta contra la plantilla y contra un proyecto instanciado coninit_project.py --apply— mismo resultado en ambos. kdd_preflight: 19/19 con escalada, reproducible.