diff --git a/dev/flows/0012-fleet-orchestrator-dod.md b/dev/flows/0012-fleet-orchestrator-dod.md new file mode 100644 index 00000000..e222d02c --- /dev/null +++ b/dev/flows/0012-fleet-orchestrator-dod.md @@ -0,0 +1,169 @@ +--- +name: fleet-orchestrator-dod +id: 0012 +status: pending +created: 2026-06-20 +updated: 2026-06-20 +priority: high +risk: medium +related_issues: [] +apps: + - fleetview + - fleet_watcher + - dag_engine +trigger: manual +schedule: "" +expected_runtime_s: 0 +tags: [orchestration, fleet, dod, multi-agent, watcher] + +# Contrato de evidencia DoD del sistema completo (las superficies observables que +# prueban que el meta-orquestador funciona, no solo que compila). +dod_evidence_schema: + - id: watcher_events + kind: cmd + expected: "sqlite3 apps/fleet_watcher/operations.db 'SELECT count(*) FROM fleet_events WHERE created_at > date(now,-1 day)' > 0; cada fila es una TRANSICION de estado, no un nivel repetido" + required: true + - id: dod_contract_on_spawn + kind: cmd + expected: "todo agente lanzado por el orquestador tiene dod_contract no vacio en su goal.json; spawn sin dod_contract se rechaza" + required: true + - id: verifier_verdict + kind: log + expected: "al cerrar un agente, existe un veredicto del verificador (met|failed) con evidencia citada; el verificador NO es el mismo agente que ejecuto la tarea" + required: true + - id: human_load_reduction + kind: url + expected: "con N>=10 agentes vivos, el orquestador presenta UN resumen agrupado por prioridad (no N mensajes sueltos); el humano responde solo lo que requiere decision" + required: true + - id: push_on_reclama + kind: screenshot + expected: "un agente que pasa a waiting/preguntando/bloqueado dispara PushNotification al movil en < 1 min" + required: true + - id: stall_nudge + kind: log + expected: "un agente idle con dod_contract incompleto y sin actividad N min recibe un nudge automatico (send-keys) registrado en fleet_events; jamas se nudgea a un agente en waiting" + required: false +--- + +## Goal + +Un meta-orquestador que permita a una persona manejar una flota de 20-30 agentes Claude hablando solo con uno. El orquestador no entra nunca en los detalles de cada agente: vigila estados, persigue que cada agente **termine lo que empieza** (cumpla un DoD-contrato fijo), y solo escala a la persona lo que requiere su decisión. La métrica de salud es el **throughput de DoD cumplidos**, no el número de agentes vivos. + +## Problema que resuelve + +Hoy lanzar muchos Claudes produce saturación: N ventanas dispersas, N agentes que quedan idle sin cerrar nada, y la persona como cuello de botella revisando todo. El criterio de "terminado" tampoco existe de forma estable: el campo `dod` del `goal.json` lo reescribe el hook GOAL-TRACKER con cada prompt (es un resumen móvil), así que no hay un blanco fijo contra el que evaluar la terminación. Resultado: 30 agentes vivos que no resuelven nada. + +## Arquitectura: 4 roles + +El orquestador delega, nunca ejecuta (regla `orquestador-delega-no-ejecuta`). Reparto: + +``` +orquestador (la persona habla SOLO con el; solo vigila, agrupa y escala) + ├── splitter agente EFIMERO. Tarea grande -> la parte en sub-tareas + │ atomicas (paralelas o secuenciales), cada una con su + │ dod_contract pequeno y verificable. Tope de fan-out. + ├── ejecutores Claudes INTERACTIVOS en la flota tmux (los 20-30 que la + │ persona ve). Cada uno con UNA tarea y UN dod_contract. + └── verificador agente EFIMERO e INDEPENDIENTE del ejecutor. Al cierre: + compara lo hecho contra el dod_contract -> met | failed + con evidencia citada. Cero auto-aprobacion. +``` + +Distinción dura: **splitter y verificador son agentes efímeros** (subagentes vía Agent tool / SDK: corren, devuelven un resultado estructurado y mueren). NO ocupan slot en la flota visible. La flota que la persona maneja = solo **ejecutores con tarea**. La maquinaria de verificación y descomposición es invisible para ella. + +El reparto de coste: **el watcher vigila (barato, sin LLM, siempre activo); el orquestador y los agentes efímeros piensan (caro, solo cuando hay algo que decidir).** + +## Modelo de datos: DoD-contrato fijo + +En el `goal.json` de cada agente conviven dos campos distintos: + +- `dod` (ya existe) — resumen móvil que el hook GOAL-TRACKER reescribe con cada prompt. Se queda como está. +- `dod_contract` (NUEVO, FIJO) — criterio de aceptación con evidencia ejecutable, escrito UNA vez al lanzar el agente y nunca reescrito por hooks. Es el blanco estable contra el que se evalúa "terminado". +- `dod_status` (NUEVO) — `pending | met | failed`, lo actualiza el verificador. + +El hook GOAL-TRACKER debe respetar `dod_contract`/`dod_status` (solo reescribe `dod`). Spawn sin `dod_contract` se rechaza: ningún agente arranca sin saber cuándo habrá terminado. + +## Máquina de terminación (lo que el watcher clasifica, mecánico, sin LLM) + +| Estado del agente | Clasificación | Acción | +|---|---|---| +| `waiting` / phase `preguntando`/`bloqueado` | RECLAMA | escalar a la persona (push inmediato) | +| `idle` + phase `hecho` | DICE-TERMINADO | orquestador lanza verificador contra `dod_contract` | +| `idle` + phase≠hecho + sin actividad N min | ESTANCADO | nudge automático: "cierra tu DoD" | +| `busy` + phase `haciendo`/`testeando` | TRABAJANDO | no molestar | +| sin `dod_contract` | MAL LANZADO | bloquear / re-lanzar con DoD | + +"Dar por terminado al hablar con ellos": cuando la persona se enruta a un ejecutor, lo primero es cerrar su `dod_contract` — si el verificador dice met, se cierra/reasigna; si quedó a medias, se empuja a terminar antes de abrir nada nuevo. + +## Fases de construcción + +### Fase 1 — fleet_watcher (cerebro barato, sin LLM) + +Service liviano (Go o bash) que corre como step de dag_engine o systemd-user. Cada N segundos: +1. Snapshot del fleet (`fleetview list --json`, extendido con `dod_contract`/`dod_status`/`phase`). +2. Diff contra el snapshot anterior -> transiciones (edge-triggered, no nivel). +3. Clasifica cada transición según la máquina de terminación. +4. Escribe un evento por transición en `apps/fleet_watcher/operations.db` (tabla `fleet_events`). +5. Push inmediato (PushNotification) para clasificación RECLAMA. + +DoD Fase 1: +- Golden: un agente pasa busy->idle -> aparece 1 evento `DICE-TERMINADO` o `ESTANCADO` en `fleet_events`. +- Edge 1: el mismo agente sigue idle 10 ticks -> NO se duplica el evento (edge, no nivel). +- Edge 2: un agente pasa a waiting -> evento RECLAMA + push en < 1 min. +- Error 1: goal.json corrupto/ausente -> el agente se clasifica MAL LANZADO sin crash del watcher. +- Vida: 7 días corriendo, 0 crashes (`journalctl`/log), cola sin huecos. + +### Fase 2 — orquestador-Claude reactivo + verificador + splitter + +Extiende el skill `/orquestador`. NO hace polling. Despierta por: la persona | heartbeat largo (ScheduleWakeup 20-30 min) | push del watcher. Al despertar: +1. Vacía `fleet_events`, agrupa por prioridad (RECLAMA > DICE-TERMINADO > ESTANCADO) y por ámbito. +2. Para DICE-TERMINADO: lanza un **verificador** (Agent efímero) que compara el output del ejecutor con su `dod_contract` -> met/failed+evidencia. met -> autocierra y reporta; failed -> nudge al ejecutor con el gap o escala. +3. Para ESTANCADO: nudge (send-keys) bajo política (solo idle con DoD pendiente; jamás waiting). +4. Para RECLAMA: presenta a la persona UN resumen corto con la decisión concreta que se necesita. Usa `/fleet focus` para saltarla al agente elegido. + +DoD Fase 2: +- Golden: un agente DICE-TERMINADO con DoD realmente cumplido -> verificador met -> autocierre + reporte, sin intervención humana. +- Edge 1: agente DICE-TERMINADO con DoD a medias -> verificador failed -> nudge con el gap, no se cierra. +- Edge 2: 10 agentes con eventos a la vez -> un solo resumen agrupado, no 10 mensajes. +- Error 1: verificador no puede leer el output -> reporta "no evaluable", escala, no autocierra en falso. +- Vida: 7 días gestionando flota real; la persona responde solo decisiones, no enrutamiento. + +### Fase 3 — spawn dentro de la flota + splitter + +Extiende `/orquestador` para lanzar ejecutores con `TmuxNewClaudeWindow` (socket fleet) en vez de kitties sueltas, escribiendo `dod_contract` en el `goal.json` del nuevo agente y un prompt con el DoD claro. Antes de spawnar, si la tarea se estima grande, pasa por el **splitter** (Agent efímero) que devuelve un plan de sub-tareas con dependencias; el orquestador spawna un ejecutor por sub-tarea (paralelas a la vez, secuenciales encadenadas). + +DoD Fase 3: +- Golden: una tarea atómica -> 1 ejecutor en la flota con `dod_contract` escrito; `/fleet` lo lista. +- Edge 1: una tarea grande -> splitter devuelve >=2 sub-tareas, cada una con su `dod_contract`; se spawnan respetando deps. +- Edge 2: tope de fan-out -> el splitter nunca genera más de K sub-agentes de golpe (sin explosión). +- Error 1: spawn sin `dod_contract` -> rechazado con mensaje claro. +- Vida: 7 días lanzando trabajo real por esta vía. + +## Pre-requisitos + +- Sesión tmux fleet activa (perfil `launch_fleetclaude`); `/fleet` operativo (flow previo). +- PushNotification configurado (Remote Control activo en el móvil). +- dag_engine activo para schedule del watcher (regla `dag-engine-over-cron`). + +## Acceptance + +- [ ] `dod_contract` fijo escrito al spawn y respetado por el hook GOAL-TRACKER. +- [ ] watcher edge-triggered con eventos en `fleet_events` + push en RECLAMA. +- [ ] verificador independiente del ejecutor, con veredicto+evidencia. +- [ ] splitter con tope de fan-out para tareas grandes. +- [ ] orquestador presenta resumen agrupado, no N mensajes; usa `/fleet focus`. +- [ ] la persona maneja >=10 agentes respondiendo solo decisiones. + +## Definition of Done + +- [ ] **Mecánica**: watcher + funciones del registry compilan, `fn doctor` verde, sin drift `uses_functions`. +- [ ] **Cobertura**: cada fase con su golden + >=2 edge + >=1 error path, evidencia ejecutable (ver DoD por fase). +- [ ] **Vida útil**: >=7 días de uso real gestionando flota, 0 crashes del watcher, 0 "done" falsos detectados (verificador funciona). +- [ ] **Carga humana**: medible reducción — la persona responde decisiones, no enrutamiento ni vigilancia. +- [ ] **Secrets**: cero credenciales fuera de pass/vaults; el watcher no loguea contenido de sesiones, solo estados/transiciones. + +## Notas (onboarding) + +Para usarlo: lanzas trabajo por el orquestador (no abres Claudes a mano). Cada tarea recibe un `dod_contract`. El watcher vigila en background y empuja al móvil cuando un agente te reclama. Cuando vuelves, el orquestador te da un resumen agrupado y te lleva (`/fleet focus`) solo a lo que necesita tu decisión; lo demás (verificar cierres, empujar estancados, dividir tareas grandes) lo hace solo con agentes efímeros. La flota que ves = ejecutores con tarea; la maquinaria de verificación/división es invisible. + +Relación con otras reglas: `dod_quality` (las 3 capas + verificador independiente), `orquestador-delega-no-ejecuta` (el orquestador no ejecuta), `dag-engine-over-cron` (schedule del watcher), `autonomous_loop` (fn-orquestador autónomo es el primo no-interactivo de este flujo), y el flow previo de `/fleet`/`fleetview` (la base de datos de estado). diff --git a/dev/flows/INDEX.md b/dev/flows/INDEX.md index a129a5e6..b2cfc2fd 100644 --- a/dev/flows/INDEX.md +++ b/dev/flows/INDEX.md @@ -13,6 +13,7 @@ Tabla de casos de uso multi-app. Mantenida por `/flow create` y `/flow done`. | [0007](0007-matrix-telemetry-bot.md) | matrix-telemetry-bot | event-driven | data_factory, dag_engine, call_monitor, agents_and_robots | pending | low | 0% | 2026-05-16 | | [0008](0008-kanban-cpp-and-agent-workflows.md) | kanban-cpp-and-agent-workflows | realtime-loop | kanban_cpp, kanban, skill_tree, agent_runner_api | pending | medium | 0% | 2026-05-18 | | [0009](0009-agentes-dispositivos-mesh.md) | agentes-dispositivos-mesh | event-driven | agents_dashboard, agents_and_robots, wg_hub, device_agent | pending | high | 0% | 2026-05-23 | +| [0012](0012-fleet-orchestrator-dod.md) | fleet-orchestrator-dod | event-driven | fleetview, fleet_watcher, dag_engine | pending | medium | 0% | 2026-06-20 | ## Leyenda