docs(orquestador): paternidad spawn --parent, auto-kill, push activo, statusline sin LLM

Integra las 4 mejoras de la tanda: (1) --parent en los ejemplos de spawn y la
explicacion del routing del watcher por parent_orchestrator; (2) regla de
auto-kill — cerrar cada ejecutor con kill_fleet_agent tras verificar met, con sus
guards; (3) push activo del watcher (tmux send-keys al pane del orquestador padre)
+ indicador 'idle nuevo sin ver' de la TUI fleetview (los implementa otro agente,
aqui solo se describen); (4) el dod movil del statusline ya no se regenera con LLM
por turno (objetivo/DoD fijo, ajustable con dod:). Anade mark_claude_parent y
kill_fleet_agent a la tabla del grupo orchestration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 13:28:07 +02:00
parent f415dd56f5
commit 687c72805d
+65 -9
View File
@@ -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 <dir-aislado> --prompt-file /tmp/orq_<slug>.md --title "<subtarea>"
--cwd <dir-aislado> --prompt-file /tmp/orq_<slug>.md --title "<subtarea>" \
--parent "$MI_SESSION_ID"
# devuelve el window_id; despues escribe el DoD-contrato del ejecutor:
./fn run set_dod_contract <sessionId-del-ejecutor> "<DoD golden+edge+error>" pending
```
@@ -125,6 +126,12 @@ se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`:
(o `--skill <name>` 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 <mi-sessionId>` (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 <sessionId> "Golden: <caso feliz+evidencia>. 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
<tu-sessionId>`. 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 <sid>` 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 <sid> "<contract>" met`.
- `met` → el orquestador marca `set_dod_contract <sid> "<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 <sid> "<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 <sid> "<contract>" met`, ciérralo:
```bash
./fn run kill_fleet_agent <sessionId> --socket "$FLEET_SOCKET"
```
`kill_fleet_agent_bash_infra` manda **SIGTERM** al proceso `claude` del ejecutor (cierre limpio,
recuperable luego con `claude --resume <sessionId>`) 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/<session_id>.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 <tu-sessionId>` 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 <id> [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).