diff --git a/.claude/commands/orquestador.md b/.claude/commands/orquestador.md index c8b5124f..66975ac0 100644 --- a/.claude/commands/orquestador.md +++ b/.claude/commands/orquestador.md @@ -168,6 +168,13 @@ políticas por clasificación, verificador, auto-kill, nudge, splitter, cadencia no el número de agentes vivos — el hook te empuja un bloque `FLEET-STATE` cada turno; tú drenas con `./fn run drain_fleet_events` y actúas por clasificación. +**Vía preferida — tools MCP `fleet_*`:** si la sesión tiene el MCP `orchestrator` conectado (lo +normal: está en `.mcp.json`), usa sus 6 tools — `mcp__orchestrator__fleet_list` / `fleet_drain` / +`fleet_classify` / `fleet_set_dod` / `fleet_kill` / `fleet_spawn` — en lugar de los `./fn run` +equivalentes: permisos pre-aprobados y salida estructurada, y `fleet_list` expone `role`/`dod_*` +directamente. El `./fn run` (y el binario `fleetview` para el listado) es el fallback CLI. Mapa +completo op→tool en `.claude/rules/orchestration.md`. + ### 6. Parar un ejecutor — NUNCA `pkill`/`killall claude` (canónica) Un `pkill claude` o `killall claude` **te mata a ti mismo** (el orquestador) junto con la flota. diff --git a/.claude/rules/orchestration.md b/.claude/rules/orchestration.md index 5dc53ffd..c17078fc 100644 --- a/.claude/rules/orchestration.md +++ b/.claude/rules/orchestration.md @@ -34,8 +34,11 @@ apps/fleetview/fleetview list # tabla legible (incluye columna AGE) Nota: **NO** uses `./fn run list_claude_fleet` — `list_claude_fleet_go_infra` es una función Go con tests, así que `fn run` la despacha como `go test` (corre la suite, no imprime la flota). La vía ejecutable es el binario `apps/fleetview/fleetview` (el atajo `/fleet` del humano envuelve este mismo -CLI). Gotcha: el JSON de `fleetview list` **no** incluye todavía `role`/`dod_contract`/`dod_status`; -para esos campos lee el sidecar `~/.claude/goals/.json` (ver abajo). +CLI). El JSON de `fleetview list` **ya incluye** `role`/`dod_contract`/`dod_status` (además de +`tmux_window`): el binario los serializa directamente (`""` cuando el `goal.json` no los declara, +ver `apps/fleetview/cli.go`). El tool MCP `fleet_list` (ver abajo) además rellena los que el binario +deje vacíos leyéndolos del sidecar `~/.claude/goals/.json`, así que con el MCP nunca te +faltan. Ya no hace falta leer el sidecar a mano salvo que uses el binario crudo y el campo venga vacío. **Tiempo — usa el de ACTIVIDAD, no el del proceso.** Para "cuánto lleva cada agente" usa la columna `AGE` de `fleetview list` (o `age`/`idle_seconds` en `--json`): es el tiempo desde su última @@ -43,6 +46,29 @@ actividad (proxy de cuánto lleva sin avanzar / en su estado), lo útil para det `etime` de `list_claude_agents` es la **vida del proceso** (cuánto lleva la terminal abierta, p.ej. 8h) — NO es el tiempo de la tarea; nunca lo reportes como progreso. +### Vía preferida: tools MCP `fleet_*` (`orchestrator_mcp`) + +El MCP `orchestrator` (registrado en `.mcp.json` como `orchestrator`, binario +`apps/orchestrator_mcp/orchestrator_mcp`) expone la maquinaria de la flota como **6 tools** que +envuelven las mismas funciones del registry. **En una sesión con `orchestrator_mcp` conectado, +prefiere los tools `mcp__orchestrator__fleet_*` sobre `./fn run`**: tienen permisos pre-aprobados, +devuelven salida estructurada y se registran en la telemetría como cualquier MCP (regla +`registry_calls.md`). El `./fn run` (o el binario `fleetview` para el listado) sigue siendo el +**fallback CLI** cuando el MCP no está conectado. Mapa de cada operación de la flota a su tool: + +| Operación de la flota | Tool MCP (preferido) | Fallback `./fn run` / binario | +|---|---|---| +| Listar la flota tipada (session_id, goal, phase, status, **role, dod_contract, dod_status**, tmux_window, age, idle_seconds) | `mcp__orchestrator__fleet_list` | `apps/fleetview/fleetview list --json` (NO `./fn run list_claude_fleet`) | +| Drenar la cola de transiciones del watcher (agrupada por clasificación + urgentes) | `mcp__orchestrator__fleet_drain` (`advance` true consume, false hace peek) | `./fn run drain_fleet_events` | +| Clasificar el estado de terminación de UN agente (RECLAMA/MAL_LANZADO/DICE_TERMINADO/ESTANCADO/TRABAJANDO) | `mcp__orchestrator__fleet_classify` | (Go con tests; lo consume el watcher, no se invoca a mano) | +| Escribir el DoD-contrato fijo (`dod_contract`/`dod_status`) en el `goal.json` de un agente | `mcp__orchestrator__fleet_set_dod` | `./fn run set_dod_contract` | +| Cerrar dirigido UN ejecutor (auto-kill: SIGTERM + kill-window, con guards) | `mcp__orchestrator__fleet_kill` (`dry_run` para ver el plan) | `./fn run kill_fleet_agent` | +| Lanzar un ejecutor como window de la flota tmux (con `parent` para el push) | `mcp__orchestrator__fleet_spawn` | `./fn run spawn_fleet_agent` | + +Ventaja extra de `fleet_list`: expone `role`/`dod_contract`/`dod_status` directamente (y rellena los +vacíos desde el sidecar `goal.json`), así que la regla "No te vigiles a ti mismo" se resuelve sin leer +el sidecar a mano — filtra por el `role` que ya trae cada fila. + Mantén una **tabla de seguimiento**, una fila por secundario, y actualízala en cada turno: | slug | título kitty | PID | cwd / dir aislado | rama | log | report | estado | @@ -134,10 +160,14 @@ produce `classify_fleet_termination` (pura) desde su estado (status + phase + do dod_status + segundos ociosos). **No te vigiles a ti mismo.** Al procesar la cola, **ignora** los eventos de tu propia sesión y de -cualquier agente con `role=orchestrator`. Como `fleetview list --json` no expone `role`, resuélvelo -leyendo el sidecar del goal de cada `session_id`: +cualquier agente con `role=orchestrator`. El `role` ya viene en cada fila de `fleet_list` (y de +`fleetview list --json`), así que filtras directamente por ese campo. Solo si usas el binario crudo y +la fila trae `role` vacío, cae al sidecar del goal de cada `session_id`: ```bash +# Preferido: filtrar por el role que ya trae fleet_list / fleetview list --json. +apps/fleetview/fleetview list --json | jq -r '.[] | select((.role // "executor") != "orchestrator") | .session_id' +# Fallback solo si el binario dejó role vacío en alguna fila: jq -r '.role // "executor"' ~/.claude/goals/.json # "orchestrator" => ignóralo ``` @@ -271,11 +301,12 @@ en lote. | `drain_fleet_events_py_infra` | Consumir la cola de transiciones del watcher (`~/.claude/fleet/events.jsonl`), agrupada por clasificación + urgentes | | `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` | +| `list_claude_fleet_go_infra` | Fleet tipado con goal/phase/`role` + `dod_contract`/`dod_status` + `tmux_window` (alimenta `/fleet`, el watcher y el tool `fleet_list`). **Invócala por el tool `mcp__orchestrator__fleet_list` (preferido) o el binario `apps/fleetview/fleetview list --json`**, NUNCA por `./fn run` (la despacha como `go test`). El JSON del CLI **ya expone** `role`/`dod_contract`/`dod_status` (`""` si el `goal.json` no los declara); el tool MCP además rellena los vacíos desde `~/.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. `--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) | +| `notify_desktop_go_infra` | Notificación de escritorio del fleet (`notify-send --app-name=fleetview`, degradación silenciosa si no hay `notify-send`). La usa el orquestador/watcher para avisar a la persona de un `RECLAMA` u otro evento urgente cuando no está mirando la terminal | **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`, diff --git a/bash/functions/infra/reboot_all_claudes.md b/bash/functions/infra/reboot_all_claudes.md index 84cdb9ab..b17af4d3 100644 --- a/bash/functions/infra/reboot_all_claudes.md +++ b/bash/functions/infra/reboot_all_claudes.md @@ -7,7 +7,7 @@ version: "1.0.0" purity: impure signature: "reboot_all_claudes([--go|--yes] [--resume-mode resume|continue|none] [--exclude-current] [--only-idle] [-h|--help])" description: "Cierra todas las terminales kitty con una sesion de Claude Code corriendo y las relanza retomando la misma sesion (claude --resume ). Mapea cada PID vivo a su ~/.claude/sessions/.json para sacar sessionId, cwd y la ventana kitty. DRY-RUN por defecto; --go ejecuta de verdad de forma desacoplada." -tags: [claude, session, terminal, kitty, reboot, infra, terminal-capture] +tags: [claude, session, terminal, kitty, reboot, infra, terminal-capture, orchestration] uses_functions: [] uses_types: [] returns: [] diff --git a/functions/infra/classify_fleet_termination.md b/functions/infra/classify_fleet_termination.md index e0ca8ec9..ff7e41bb 100644 --- a/functions/infra/classify_fleet_termination.md +++ b/functions/infra/classify_fleet_termination.md @@ -7,7 +7,7 @@ version: "1.0.0" purity: pure signature: "func ClassifyFleetTermination(status, phase, dodContract, dodStatus string, idleSeconds, stallThresholdSeconds int) string" description: "Clasifica MECANICAMENTE el estado de terminacion de un agente Claude de la flota para que un watcher barato sin LLM decida que hacer. Pura y determinista. Devuelve una de RECLAMA, MAL_LANZADO, DICE_TERMINADO, ESTANCADO o TRABAJANDO segun precedencia fija: RECLAMA (pide input humano) manda sobre todo, luego MAL_LANZADO (sin DoD-contrato), luego DICE_TERMINADO, ESTANCADO y TRABAJANDO." -tags: [fleet, claude-fleet, classification, watcher, termination, orchestrator, pure, infra] +tags: [fleet, claude-fleet, classification, watcher, termination, orchestrator, pure, infra, orchestration] uses_functions: [] uses_types: [] returns: [] diff --git a/functions/infra/list_claude_fleet.md b/functions/infra/list_claude_fleet.md index bf4e775f..30b1d02c 100644 --- a/functions/infra/list_claude_fleet.md +++ b/functions/infra/list_claude_fleet.md @@ -7,7 +7,7 @@ version: "1.0.0" purity: impure signature: "func ListClaudeFleetFrom(claudeDir string) ([]ClaudeFleet, error) | func ListClaudeFleet() ([]ClaudeFleet, error)" description: "Lista la flota de procesos Claude Code de la maquina local (Linux). Escanea ~/.claude/sessions/*.json, cruza cada PID vivo contra /proc para validar liveness (anti-PID-reciclado via procStart == campo 22 de /proc//stat), une el goal/phase de ~/.claude/goals/.json, extrae KITTY_PID del environ y deriva los campos de display (Target, Rename). Devuelve todas las sesiones ordenadas por status (idle, waiting, busy, otro) y por updatedAt desc; el caller filtra por Alive. Pieza de datos de la app TUI fleetview." -tags: [claude-fleet, infra, claude, session, proc, fleet, tui] +tags: [claude-fleet, infra, claude, session, proc, fleet, tui, orchestration] uses_functions: [] uses_types: [claude_fleet_go_infra] returns: [claude_fleet_go_infra] diff --git a/python/functions/infra/drain_fleet_events.md b/python/functions/infra/drain_fleet_events.md index ce488dba..693119db 100644 --- a/python/functions/infra/drain_fleet_events.md +++ b/python/functions/infra/drain_fleet_events.md @@ -7,7 +7,7 @@ version: "1.1.0" purity: impure signature: "def drain_fleet_events(events_path: str | None = None, cursor_path: str | None = None, advance: bool = True) -> dict" description: "Drena la cola JSONL de eventos de la flota desde un cursor persistente. Lee los eventos nuevos (desde la linea del cursor hasta el final), los parsea saltando lineas en blanco o JSON invalido, los agrupa por su campo `to` (RECLAMA, MAL_LANZADO, DICE_TERMINADO, ESTANCADO, TRABAJANDO, GONE), aisla los urgentes y avanza el cursor para no reprocesar. Pensado para que el orquestador-Claude de flota consuma eventos nuevos cada vez que despierta." -tags: [fleet, claude-fleet, jsonl, cursor, queue, drain, watcher, orchestrator] +tags: [fleet, claude-fleet, jsonl, cursor, queue, drain, watcher, orchestrator, orchestration] uses_functions: [] uses_types: [] returns: [] diff --git a/python/functions/infra/mark_claude_role.md b/python/functions/infra/mark_claude_role.md index 8904e920..033e812e 100644 --- a/python/functions/infra/mark_claude_role.md +++ b/python/functions/infra/mark_claude_role.md @@ -7,7 +7,7 @@ version: "1.0.0" purity: impure signature: "def mark_claude_role(pid: int, role: str, wait_s: float = 10.0, sessions_dir: str | None = None, goals_dir: str | None = None) -> dict" description: "Marca el role (orchestrator | executor) de una sesion de Claude Code resolviendo PID -> sessionId. Sondea ~/.claude/sessions/.json (escrito por Claude Code unos segundos despues de arrancar) hasta wait_s segundos con deadline time.monotonic, extrae el sessionId y escribe SOLO la clave `role` en ~/.claude/goals/.json preservando el resto del goal (goal, phase, dod, dod_contract...). Escritura atomica tmp + os.replace. Si el sessions JSON no aparece a tiempo devuelve ok=False timeout sin lanzar. Pensado para el launcher del meta-orquestador de flota (fleetview) que necesita clasificar el orquestador frente a los executors." -tags: [fleet, claude-fleet, role, session, goal, orchestrator, launcher, pid] +tags: [fleet, claude-fleet, role, session, goal, orchestrator, launcher, pid, orchestration] uses_functions: [] uses_types: [] returns: [] diff --git a/python/functions/infra/set_dod_contract.md b/python/functions/infra/set_dod_contract.md index 6f7ae681..ea528fff 100644 --- a/python/functions/infra/set_dod_contract.md +++ b/python/functions/infra/set_dod_contract.md @@ -7,7 +7,7 @@ version: "1.0.0" purity: impure signature: "def set_dod_contract(session_id: str, contract: str, status: str = 'pending', goals_dir: str | None = None) -> dict" description: "Escribe un DoD-contrato fijo y su estado en el sidecar goal.json de una sesion Claude (~/.claude/goals/.json). Preserva TODOS los campos existentes (goal, phase, dod, history, prompts) y solo actualiza dod_contract y dod_status. Usado por el meta-orquestador de flota para fijar el criterio de aceptacion estable contra el que se evalua la terminacion de un agente. Escritura atomica via tmp + os.replace." -tags: [fleet, claude-fleet, dod, goals, orchestrator, sidecar, json] +tags: [fleet, claude-fleet, dod, goals, orchestrator, sidecar, json, orchestration] uses_functions: [] uses_types: [] returns: []