Delegar una tarea a otro agente CLI
Ejecuta una tarea en un CLI de agente externo en segundo plano y devuelve un resultado normalizado.
Controlador: delegate.mjs, en el propio directorio de esta habilidad (Node ≥ 18, sin dependencias).
El comportamiento se define mediante tres contratos en contracts/ — léelos antes de modificar el controlador.
start devuelve un run_id inmediatamente; collect recupera el resultado. Nada te bloquea.
Resolver primero el controlador y el directorio de ejecuciones
Dónde está instalada esta habilidad decide dónde vive delegate.mjs, y los artefactos de ejecución se
sitúan por defecto junto al controlador. Configura ambos una vez por sesión, luego usa $DELEGATE en cada
comando a continuación:
# Instalación como plugin de Claude Code (agent-tools@sanduq)
DELEGATE="$CLAUDE_PLUGIN_ROOT/skills/delegate-task/delegate.mjs"
# Instalación con npx skills, o una copia confirmada en el repositorio
DELEGATE=".claude/skills/delegate-task/delegate.mjs"
# Mantén los artefactos de ejecución en el proyecto en el que trabajas, no junto al controlador
export DELEGATE_RUNS_DIR="$PWD/.delegate/runs"
DELEGATE_RUNS_DIR es obligatorio para una instalación como plugin: sin él, los artefactos de cada proyecto
se acumulan dentro del directorio compartido del plugin, y contienen el texto de tu tarea y la salida
bruta del CLI. Añade .delegate/ al .gitignore del proyecto. Cada comando que toca una ejecución
— start, status, collect, list, prune — debe ver el mismo valor.
Comprobar primero qué está disponible
node "$DELEGATE" doctor
doctor sondea la versión de cada CLI, por lo que distingue no instalado de instalado pero
roto — e imprime qué solicita realmente --sandbox a cada uno.
| CLI | id | nivel | reanudar | --sandbox solicita |
|---|---|---|---|---|
| Claude Code | claude | verificado | verificado | --permission-mode plan |
| OpenAI Codex | codex | verificado | verificado | -s read-only (impuesto por el CLI) |
| OpenCode | opencode | verificado | verificado | --agent plan |
| GitHub Copilot | copilot | experimental | experimental | --mode plan |
| Pi | pi | experimental | experimental | --no-approve --tools read,grep,find,ls |
Prefiere claude, codex, o opencode.
Ejecutar una delegación
node "$DELEGATE" start \
--harness codex \
--cwd /ruta/al/espacio-de-trabajo \
--timeout 600 \
--task "Añade validación de entrada a parse_config() y una prueba que cubra el camino de entrada incorrecta."
Imprime {"run_id": "codex-…", "state": "running", ...} y termina. Luego:
node "$DELEGATE" collect codex-… --wait 300
--wait N sondea durante un máximo de N segundos. Sin él, collect sale con código 3 si la ejecución
sigue en curso.
Otros comandos:
node "$DELEGATE" status <run_id> # iniciando|ejecutando|estancado|a_la_deriva|perdido|hecho
node "$DELEGATE" collect <run_id> --json # el resultado legible por máquina — analiza esto
node "$DELEGATE" list # todas las ejecuciones, la más antigua primero
node "$DELEGATE" prune --keep 20 --yes # elimina artefactos antiguos de ejecución
node "$DELEGATE" contract # banderas, campos, códigos de salida legibles por máquina
Leer el resultado
Estado EXITOSO
Causa telemetría_del_cli
Cambios
medido greeting.txt
afirmado greeting.txt
Permiso solicitado=bypass aplicado=codex: --dangerously-bypass-approvals-and-sandbox
Tres campos contienen la mayor parte del significado:
status—successful/failed/abandoned.status_provenance.primary— por qué ese estado, en una palabra:timeout,spawn_error,signal,exit_code,parse_error,telemetry_incomplete,harness_telemetry,self_report,head_moved,index_moved,relay_aborted,supervisor_lost,legacy. Nunca informes de un estado sin saber cuál de estos lo produjo.evidencelista todas las observaciones que eran ciertas, no solo la que ganó.dirty_paths_changed— lo que git midió, en contraste confiles_claimed, que es lo que el delegado dijo.files_mismatchseñala desacuerdo.
Permisos
El valor por defecto es bypasseo completo. El delegado ejecuta comandos arbitrarios sin supervisión.
--sandbox solicita el modo de solo lectura real de cada CLI (tabla anterior) y activa el cable trampa:
read_only_violation es true / false / null. null significa desconocido — git no
estaba disponible o la cobertura de la huella era incompleta. Nunca se colapsa a false.
false no significa "no ocurrieron escrituras". Significa que no se detectó ningún delta de estado
final cubierto. No puede ver una escritura-y-reversión, nada ignorado por git, ni ninguna escritura fuera
del repositorio. true no significa que lo hiciera el delegado — una edición concurrente en el mismo
árbol de trabajo tiene el mismo aspecto.
El sobre le dice al delegado que no confirme, pero eso es política, no imposición. Lo que lo
impone es la medición: si HEAD se mueve durante la ventana de ejecución, el resultado se fuerza a
revisión-requerida (head_moved) a menos que hayas pasado --allow-commit.
Continuar una ejecución
node "$DELEGATE" start --harness codex --cwd <mismo directorio> --resume <run_id> \
--task "La corrección es correcta, pero usa el fixture real en lugar de un mock."
Envía solo el delta. Resume obtiene un nuevo run_id con parent_run_id establecido, y se
niega si el CLI, el directorio de trabajo o el id de sesión del padre no coinciden.
Un envío reanudado repite cada regla de política en lugar de asumir que la sesión padre sigue respetándolas.
Banderas
| bandera | uso |
|---|---|
--cwd DIR | directorio de trabajo — establécelo siempre explícitamente |
--model M | anula el modelo |
--timeout SEG | valor por defecto 1800; validado de antemano; al expirar el árbol se elimina → abandoned |
--deliverable TEXTO | qué devolver ("un diff unificado, no confirmes") |
--constraint TEXTO | regla extra repetible |
--sandbox | solicita el modo de solo lectura del CLI |
--allow-commit | permite confirmar, y deja de tratar un HEAD movido como revisión-requerida |
--resume <run_id> | continúa la sesión de una ejecución finalizada con una tarea delta |
--clean-env / --keep-env N | entrega al CLI solo lo básico del tiempo de ejecución; repite --keep-env por variable necesaria para autenticación |
--raw | envía la tarea tal cual (pierde estado/resumen; incompatible con --resume) |
Advertencias
- El código de salida 0 no significa que la tarea estuviera hecha — y hasta esta refactorización, un código de salida distinto de cero no significaba que fallara. Los adaptadores recibían el código de salida y ninguno lo leía, por lo que un evento con forma de éxito más código de salida 1 se publicaba como
successful. Corregido; fijado por el casoREPAIR-D1detest/run.mjs. Lee siemprestatus_provenance, nunca soloexit_code. - Los campos de tokens significan cosas diferentes según el CLI.
input_tokens/cache_creation/cache_readde Claude son disjuntos — súmalos (una sonda reportóinput_tokens: 2para una solicitud que realmente enviaba 58,065).input_tokensde Codex es inclusivo decached_input_tokens— resta para obtener el recuento fresco. Usainput_total/input_fresh;usage_rawconserva los números del CLI para auditoría pero nunca se muestra como total. - Los totales no son comparables entre CLIs. OpenCode emite un
step_finishpor paso y el controlador los suma, por lo que el contexto reenviado en cada paso se cuenta cada vez. Misma tarea trivial: 66,341 (codex), 117,014 (claude), 426,523 (opencode). No es una clasificación de eficiencia. - Delegar a
claudetiene un mínimo de ~58k tokens.claude -pcarga CLAUDE.md y cada habilidad descubierta antes de leer la tarea. - Codex hereda tus servidores MCP. Si codex se siente lento, lee
runs/<run_id>/stderr.log— el retraso suele ser de MCP, no del modelo. - Codex emite eventos
item.type: "error"en ejecuciones exitosas. Soloturn.failedo unturn.completedfaltante cuentan. - El bypasseo completo realmente no tiene sandbox. Un delegado de OpenCode escribió una vez un archivo fuera de su
--cwd. Usa--sandbox, o un directorio desechable. - No todos los CLIs reportan todos los campos. Copilot no emite ningún recuento de tokens (
fidelity: "unavailable"— los ceros significan desconocido, no gratis). Codex no emite ni un costo ni un modelo en ninguna parte de su JSONL, por lo quecost_usdymodelson nulos por naturaleza, no por un fallo de análisis —cost_reported/model_reporteddicen cuál, y el resultado impreso dice "no reportado por este CLI". Claude reporta ambos; OpenCode reporta costo. - El texto de la tarea nunca llega a un shell. Cada CLI se resuelve a un ejecutable real y se lanza con
shell:false. pipersiste una sesión en su propio almacén.--no-sessionno se pasa deliberadamente — sin sesión no hay nada que reanudar. Las sesiones caen en~/.pi/agent/sessions/<slug-del-cwd>/, indexadas por directorio de trabajo;pruneno las toca. La autenticación de Pi está basada en archivos (~/.pi/agent/auth.json), por lo que--clean-envno la rompe.- Una ejecución cuyo supervisor es eliminado está
lost, no atascada.collectsintetiza un resultado terminal a partir del diario y los registros. Su delta de git esnull: no sobrevivió ninguna línea base de inicio de ejecución, y reportarfalsesería una garantía de que no se midió nada. - Los ids de ejecución son validados. Deben coincidir con
<harness>-<14 dígitos>-<6 hex>, y deben resolverse directamente bajo el directorio de ejecuciones. Un id proporcionado por el que llama no puede salir de él. - Un supervisor muerto cuyo CLI sigue vivo está
adrift, nolost.collectse niega a publicar, porque un resultado escrito entonces capturaría una ejecución aún en movimiento. Espera al hijo, o mata el pid que nombrastatus.prunetampoco eliminará una ejecución a la deriva. DELEGATE_BIN_<HARNESS>anula dónde se encuentra un binario — p. ej.DELEGATE_BIN_CODEX=/opt/codex/bin/codex. También es cómo el conjunto de pruebas impulsa un CLI falso de extremo a extremo sin gastar un token.
Solución de problemas
| síntoma | causa / solución |
|---|---|
<bin> no encontrado en PATH | ese CLI no está instalado; ejecuta doctor |
encontrado <ruta> pero no se pudo resolver qué lanza | el CLI está instalado detrás de un shim .cmd no npm que este controlador no puede decodificar |
doctor muestra ROTO: sonda agotó el tiempo de espera | suele ser un arranque en frío; vuelve a ejecutar doctor antes de concluir nada |
el supervisor no pudo iniciar (código de salida 5) | el supervisor separado nunca confirmó; comprueba runs/<id>/journal.jsonl. La ejecución se finaliza como failed con containment_evidence: "the harness never launched"; si el diario solo contiene created y terminal y el pid del supervisor ha desaparecido, ningún agente se ejecutó |
ejecución <id> aún ejecutándose (código de salida 3) | añade --wait <segundos> |
Rechazando publicar … todavía en ejecución (código de salida 6) | la ejecución está adrift: el supervisor murió pero el CLI vive. Espera, o mata el pid que nombra |
otro proceso está finalizando esta ejecución (código de salida 7) | un escritor vivo tiene el reclamo; reintenta en un momento |
escrito por un controlador diferente (código de salida 8) | el resultado lleva un esquema desconocido; esta versión no adivinará |
estado abandoned, procedencia supervisor_lost | el supervisor fue eliminado; inspecciona el árbol de trabajo antes de reenviar |
estado failed, procedencia head_moved / index_moved | algo confirmó o preparó durante la ejecución; el árbol se conserva, revísalo |
Resumen dice (no se produjo resumen) | el delegado nunca emitió el bloque de resultado; lee runs/<run_id>/stdout.log |
Archivos
Los artefactos de ejecución viven en $DELEGATE_RUNS_DIR/<run_id>/ (prompt.txt, meta.json, journal.jsonl,
stdout.log, stderr.log, result.json). Si no está establecido, DELEGATE_RUNS_DIR por defecto es runs/ junto
al controlador — que es el directorio compartido del plugin en una instalación como plugin, así que establécelo. Limita el tamaño del registro con
DELEGATE_MAX_LOG_BYTES (por defecto 32 MB por flujo) y la ventana de retención posterior al límite con
DELEGATE_LOG_TAIL_BYTES (por defecto 512 KB).
Más allá del límite, el controlador mantiene registros analizados completos en lugar de un sufijo de bytes,
por lo que la telemetría aún se analiza. Los registros que la ventana no puede contener se cuentan en
records_dropped — un valor distinto de cero significa que el estado sigue siendo sólido pero la respuesta
final del propio delegado puede estar incompleta.
⚠️ Los artefactos contienen el texto de tu tarea, la solicitud completa y la salida bruta del CLI — trátalos como
sensibles. La finalización enmascara formas de tokens y pares NAME=valor con aspecto de credencial en
stdout.log y stderr.log (no en result.json). En POSIX, el directorio de ejecuciones tiene permisos chmod 0700;
en Windows los permisos se heredan y este controlador no puede restringirlos. Un collect exitoso
elimina las ejecuciones finalizadas más antiguas que DELEGATE_PRUNE_DAYS días (por defecto 30; 0 desactiva);
limpia antes con prune.
Profundizando
-
Para un lote de comprobaciones CLI locales conocidas, lee el contrato nativo de ejecutor de comprobaciones. Cubre planes JSON argv, intentos inmutables, recolección limitada e invocación de PowerShell. Usa la misma referencia para
collect --summary, diagnósticos de solo latido y plazos de inicio opcionales. -
contracts/status-precedence.md— cómo se decide un estado, y el producto cruzado a probar -
contracts/git-fields.md— qué mide el cable trampa y qué no puede ver -
contracts/result-schema-v2.md— la forma del resultado, lectura hacia atrás v1, diario, escritor único -
references/writing-the-brief.md— cómo escribir una tarea que el delegado pueda ejecutar a ciegas -
references/reviewing-the-result.md— cómo revisar lo que vuelve, incluyendo el barrido -
references/task-queues.md— ejecutar varias tareas dependientes sin perder el hilo
Pruebas: node test/run.mjs, desde el directorio de esta habilidad.
Regresiones de ejecución: node --test test/execution.test.mjs (fixtures de CLI Node/Python).