Back to home@MauricioPerera

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

  1. Dependencias locales (el plugin corre desde su propia carpeta, necesita su propio node_modules):

    cd kdd-gates
    npm install
    

    (@deepseek-ai/dsh-tools y @deepseek-ai/dsh-sandbox, mismas versiones que el dsh instalado — ver package.json.)

  2. 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'
    
  3. Reiniciar el proceso de dsh para 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ámetroTipoRequeridoDescripción
repoRootstringRuta absoluta del repo KDD.
contractsDirstringnoDefault knowledge/contracts.
okfDirstringnoDefault 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ámetroTipoRequerido
repoRootstring
testsPathstringsí (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ámetroTipoRequerido
repoRootstring
contractstringsí (ej. knowledge/contracts/mi-tarea.md)
changedFilesarray<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ámetroTipoRequeridoDescripción
repoRootstring
sandboxPermissionsstring enum ["danger-full-access"]noEscala el sandbox para el gate validate_test_commands (ver abajo). Pide aprobación real al usuario.
justificationstringnoObligatorio junto con sandboxPermissions.
run_in_backgroundbooleannoCorre 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, nunca oneOf ni type:"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_preflight usa ctx.shell, no ctx.subprocess.spawn directo. preflight.py lanza sus propios subprocesos anidados (uno por gate); el spawn directo se cuelga indefinidamente en Windows para ese patrón — probado con argv plano y con cmd.exe de por medio, ninguno funcionó. ctx.shell (el mismo servicio detrás de la tool pwsh del agente) sí. Reportado como bug aparte: discussion #4796. El wrapper vive en safe-shell.js (runNested(shell, opts)), separado de host.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_commands se corre aparte, llamando directo a validate_test_commands.run_all(..., timeout=300) (su API pública) en vez de por main() (que fija 120s sin exponerlo). Escalar el árbol anidado completo de preflight.py de 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_commands vive 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-tmp adentro, anidándose sin fin.
  • La escalada de sandbox pasa por el servicio real de aprobación (ctx.approval, el mismo que usa dsh-tool-pwsh) — nunca hay bypass silencioso: sin sandboxPermissions + justification explí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_perimeter positivo y negativo) probado de punta a punta contra la plantilla y contra un proyecto instanciado con init_project.py --apply — mismo resultado en ambos.
  • kdd_preflight: 19/19 con escalada, reproducible.