diff --git a/.claude/commands/orquestador.md b/.claude/commands/orquestador.md index 9da8607c..907a4385 100644 --- a/.claude/commands/orquestador.md +++ b/.claude/commands/orquestador.md @@ -116,7 +116,8 @@ se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`: ```bash ./fn run spawn_fleet_agent --socket "$FLEET_SOCKET" --session "$FLEET_SESSION" \ - --cwd --prompt-file /tmp/orq_.md --title "" + --cwd --prompt-file /tmp/orq_.md --title "" \ + --parent "$MI_SESSION_ID" # devuelve el window_id; despues escribe el DoD-contrato del ejecutor: ./fn run set_dod_contract "" pending ``` @@ -125,6 +126,12 @@ se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`: (o `--skill ` para arrancar en un modo), y con `--role executor|orchestrator` marca su `goal.json` (via `mark_claude_role`). El aislamiento git (sub-repo / worktree / scope) sigue imponiéndose en el prompt igual que con kitty. +- **`--parent ` (recomendado):** escribe `parent_orchestrator` en el `goal.json` del + ejecutor (via `mark_claude_parent`) **atribuyéndotelo a ti**. Es lo que habilita el **push + automático** del watcher: cuando ese ejecutor se autodeclara terminado, el watcher inyecta el + aviso en TU pane tmux (no en toda la flota), porque te identifica como su orquestador padre. Sin + `--parent` el aviso no se rutea a un orquestador concreto. Pásate tu propio `sessionId` (el de la + sesión orquestadora). Es opcional y retro-compatible: omitirlo deja el spawn igual que antes. - Usa kitty (arriba) solo para secundarios que deban vivir **fuera** de un perfil fleet. ### 3. Aislamiento git obligatorio por secundario (regla de oro) @@ -266,7 +273,7 @@ Seguir la flota (paso 5) no es solo "¿quién vive?". Es **vigilar la salud por ### DoD-contrato fijo al lanzar (regla dura) -Ningún secundario arranca sin **DoD-contrato**: el criterio de aceptación FIJO contra el que se evalúa su terminación. Es distinto del campo `dod` (resumen móvil que el hook GOAL-TRACKER reescribe con cada prompt). Tras lanzar y conocer el `sessionId`: +Ningún secundario arranca sin **DoD-contrato**: el criterio de aceptación FIJO contra el que se evalúa su terminación. Es distinto del campo `dod` del statusline (texto corto identificativo de la terminal). **Desde 2026-06-21 ese `dod` ya NO se regenera con un LLM en cada turno**: el hook `goal_refine.sh` que lo reescribía con haiku por prompt quedó desactivado (amplificaba el rate-limit compartido). El objetivo+DoD inicial los fija `goal_autogen.sh` **una sola vez** por terminal; a partir de ahí son fijos y el usuario los ajusta a mano con `objetivo: ...` / `dod: ...`. El criterio que clasifica la flota es `dod_contract` + `dod_status` (lo escribe `set_dod_contract`, sin LLM), no ese `dod` móvil. Tras lanzar y conocer el `sessionId`: ```bash ./fn run set_dod_contract "Golden: . Edge: <2 bordes>. Error: <1 fallo manejado>." pending @@ -290,9 +297,30 @@ existe, degrada limpio sin romper el turno. El bloque es solo un **aviso** (hace cursor): para consumir las transiciones y aplicar la política por clasificación sigues drenando (abajo). El resumen lo produce `summarize_fleet_transitions_py_infra` sobre el feed del watcher. -Gotcha conocido: hoy el bloque lista transiciones de TODA la flota, incluidas las de otros -orquestadores y sus ejecutores. Si hay más de un orquestador activo, filtra por tu propia familia de -agentes (los que tú lanzaste) — igual que en "No te vigiles a ti mismo" más abajo. +Gotcha conocido: el bloque `FLEET-STATE` (peek pasivo) lista transiciones de TODA la flota, +incluidas las de otros orquestadores y sus ejecutores. Si hay más de un orquestador activo, filtra +por tu propia familia de agentes (los que tú lanzaste) — igual que en "No te vigiles a ti mismo" más +abajo. El **push activo** (siguiente apartado) sí está ya ruteado por familia. + +### Push activo del watcher — send-keys dirigido (routing por `parent_orchestrator`) + +Además del aviso pasivo en cada turno, el **watcher de fleetview** empuja activamente: cuando un +ejecutor transita a `DICE_TERMINADO`, hace `tmux send-keys` **directamente al pane del orquestador +que lo lanzó**, para que el cierre no espere a tu siguiente turno. El ruteo se resuelve por la clave +`parent_orchestrator` del `goal.json` del ejecutor — la que escribe `spawn_fleet_agent --parent +`. Por eso **lanza siempre tus ejecutores con `--parent`**: sin esa clave el watcher no +sabe a qué pane mandar el aviso y el cierre queda solo en el peek pasivo (toda la flota). Con +`--parent`, cada familia de agentes avisa a su propio orquestador y desaparece el ruido cruzado entre +orquestadores. + +### Indicador "idle nuevo sin ver" en la TUI fleetview + +La TUI `fleetview` marca de forma distinguible los ejecutores que **acaban de quedar idle y que aún +no has atendido** (idle nuevo sin ver), para que el humano y el orquestador localicen de un vistazo +qué agentes reclaman acción frente a los que ya están en seguimiento. Es la señal visual hermana del +push del watcher: el push te lo trae a la terminal, el indicador lo resalta en la lista. Úsalo como +disparador para drenar la cola y aplicar la política por clasificación (verificar `DICE_TERMINADO`, +nudge a `ESTANCADO`). ### Drenar la cola @@ -316,7 +344,7 @@ El orquestador no tiene `dod_contract` y aparecería como `MAL_LANZADO` — es r | Transición a… | Qué hace el orquestador | |---|---| | `RECLAMA` (urgent) | **Escalar a la persona**: resumen corto de QUÉ decisión se necesita + `/fleet focus ` para llevarla al agente. Si no está presente, `PushNotification`. NUNCA decidir tú por ella en un RECLAMA. | -| `DICE_TERMINADO` | Lanzar **verificador independiente** (abajo). No confiar en el autodeclarado. | +| `DICE_TERMINADO` | Lanzar **verificador independiente** (abajo). No confiar en el autodeclarado. Si `met` → cerrar con `kill_fleet_agent` (auto-kill, libera el slot idle). | | `ESTANCADO` | **Nudge** al agente (abajo). Solo idle; jamás waiting. | | `MAL_LANZADO` | Escribir `dod_contract` retroactivo (`set_dod_contract`) o re-lanzar con DoD. | | `TRABAJANDO` | No molestar. | @@ -336,9 +364,34 @@ Agent(subagent_type="general-purpose", prompt: Por defecto failed si la evidencia no respalda una cláusula.") ``` -- `met` → el orquestador **cierra/reasigna** el agente y lo informa a la persona. Marca `set_dod_contract "" met`. +- `met` → el orquestador marca `set_dod_contract "" met`, informa a la persona y + **cierra el ejecutor para liberar el slot idle** con `kill_fleet_agent` (regla de auto-kill, abajo). - `failed` → **nudge** al ejecutor con el gap concreto (no cerrar). `set_dod_contract "" failed` (vuelve a pending tras el nudge si reabre trabajo). +### Auto-kill — cerrar el ejecutor tras verificar `met` (libera el slot idle) + +Un ejecutor verificado `met` **no se deja vivo en reposo**: se cierra de inmediato para que no se +acumule en la flota ocupando un slot idle. En cuanto el verificador devuelve `met` y has marcado +`set_dod_contract "" met`, ciérralo: + +```bash +./fn run kill_fleet_agent --socket "$FLEET_SOCKET" +``` + +`kill_fleet_agent_bash_infra` manda **SIGTERM** al proceso `claude` del ejecutor (cierre limpio, +recuperable luego con `claude --resume `) y cierra su window tmux (`kill-window`). Trae +**guards** que lo hacen seguro de invocar programáticamente: + +- **No mata a un `role=orchestrator`** (lo lee del `goal.json`): nunca decapitas la flota por error. +- **No se mata a sí mismo**: rechaza el target si es la sesión que invoca (equivalente dirigido de + la regla "nunca `pkill claude`"). +- Acepta el target por `sessionId` (exacto o prefijo) o por PID. Usa `--dry-run` para ver el plan + sin tocar nada. + +Esto cierra el ciclo del modo: lanzas con `--parent` → el watcher te avisa del `DICE_TERMINADO` → +verificas → `kill_fleet_agent` libera el slot. No uses `pkill`/`killall` ni `kill` a pelo para esto: +`kill_fleet_agent` resuelve la window y aplica los guards. + ### Nudge — `ESTANCADO` Agente idle con `dod_contract` sin cumplir y sin actividad > umbral (10 min). Empújalo a cerrar SU DoD inyectando en su pane tmux: @@ -424,12 +477,15 @@ El orquestador no hace polling caro: drena la cola **cuando actúa** (cuando la | `summarize_fleet_transitions_py_infra` | Resumir las transiciones del feed en una línea (`terminados/reclaman/estancados`); alimenta el bloque `FLEET-STATE` que el hook `UserPromptSubmit` inyecta cada turno | | `classify_fleet_termination_go_infra` | Clasificar el estado de terminación de un agente (RECLAMA/MAL_LANZADO/DICE_TERMINADO/ESTANCADO/TRABAJANDO) — lo usa el watcher | | `list_claude_fleet_go_infra` | Fleet tipado con goal/phase/`role` + `tmux_window` (alimenta `/fleet` y el watcher). **Invócala por el binario `apps/fleetview/fleetview list --json`**, NUNCA por `./fn run` (la despacha como `go test`). El JSON del CLI aún no expone `role`/`dod_contract`/`dod_status`; léelos de `~/.claude/goals/.json` | -| `spawn_fleet_agent_bash_infra` | Lanzar un ejecutor (o el orquestador) como window de la flota tmux — preferido sobre kitty cuando hay perfil fleet | +| `spawn_fleet_agent_bash_infra` | Lanzar un ejecutor (o el orquestador) como window de la flota tmux — preferido sobre kitty cuando hay perfil fleet. `--parent ` atribuye el ejecutor a ti y habilita el push activo del watcher | | `mark_claude_role_py_infra` | Marcar `role` (orchestrator/executor) en el goal.json de un Claude resolviendo PID→sessionId | +| `mark_claude_parent_py_infra` | Marcar `parent_orchestrator` (sessionId del orquestador que lo lanzó) en el goal.json de un ejecutor resolviendo PID→sessionId. Lo invoca `spawn_fleet_agent --parent`; habilita el routing del watcher al pane del orquestador padre | +| `kill_fleet_agent_bash_infra` | Cierre dirigido de UN ejecutor: SIGTERM al claude + kill-window de su window tmux. Guards anti-orquestador y anti-self. Lo usa el orquestador para liberar el slot idle tras verificar `met` (auto-kill) | **Cómo invocarlas.** Las Bash y Python del grupo se lanzan con `./fn run [args]` (verificado: `list_claude_agents`, `drain_fleet_events`, `reboot_all_claudes`, `set_dod_contract`, -`mark_claude_role`, `launch_claude_agent_kitty`, `spawn_fleet_agent`). Las **Go con tests** NO: +`mark_claude_role`, `mark_claude_parent`, `kill_fleet_agent`, `launch_claude_agent_kitty`, +`spawn_fleet_agent`). Las **Go con tests** NO: `./fn run` las despacha como `go test`. Por eso `list_claude_fleet_go_infra` se usa por el binario `apps/fleetview/fleetview list --json`, y `classify_fleet_termination_go_infra` la consume el watcher embebido en fleetview (no se invoca a mano).