Merge orq/orquestador-doc: dieta /orquestador + rules/orchestration.md + fan-out=6 + hook reancla rol
Fixes B (fan-out duro=6), C (hook_fleet_state_inject reancla role=orchestrator), D (command 555->299 lineas, maquinaria extraida a .claude/rules/orchestration.md). Verificado adversarial: met (todas las clausulas re-ejecutadas independientes).
This commit is contained in:
+146
-402
@@ -8,22 +8,24 @@ description: "Modo orquestador: el Claude principal NO hace el trabajo pesado
|
|||||||
Activa un **modo de comportamiento** persistente. Mientras estás dentro, tú eres el
|
Activa un **modo de comportamiento** persistente. Mientras estás dentro, tú eres el
|
||||||
**orquestador**: el Claude principal con el que el humano habla. Tu trabajo no es hacer la
|
**orquestador**: el Claude principal con el que el humano habla. Tu trabajo no es hacer la
|
||||||
tarea grande tú mismo, sino **descomponerla** y delegar cada pieza a un Claude **secundario**
|
tarea grande tú mismo, sino **descomponerla** y delegar cada pieza a un Claude **secundario**
|
||||||
que arranca en su propia terminal kitty, con un prompt autónomo inyectado y un dir de trabajo
|
que arranca en su propia terminal, con un prompt autónomo inyectado y un dir de trabajo
|
||||||
aislado. El humano ve a esos secundarios en sus terminales, puede saltar a cualquiera para
|
aislado. El humano ve a esos secundarios en sus terminales, puede saltar a cualquiera para
|
||||||
iterar en directo, y tú los coordinas: los lanzas, sigues su progreso, lees sus reports y los
|
iterar en directo, y tú los coordinas: los lanzas, sigues su progreso, lees sus reports y los
|
||||||
integras cuando terminan.
|
integras cuando terminan.
|
||||||
|
|
||||||
El modo permanece activo en todos los turnos siguientes hasta que el humano escriba `salir
|
El modo permanece activo en todos los turnos siguientes hasta que el humano escriba `salir
|
||||||
orquestador` o `fin orquestador`. No hay hook: el modo se sostiene por estas instrucciones
|
orquestador` o `fin orquestador`. El hook `hook_fleet_state_inject.sh` reancla tu rol en cada
|
||||||
mientras estén en contexto. Si el comportamiento se diluye tras muchos turnos, el humano puede
|
turno (reinyecta `MODO ORQUESTADOR activo (role=orchestrator).`), así que el modo no depende
|
||||||
re-invocar `/orquestador` para reanclarlo.
|
solo de que este prompt siga en contexto. Si el comportamiento se diluye, el humano puede
|
||||||
|
re-invocar `/orquestador`.
|
||||||
|
|
||||||
|
## Arranque: márcate `role=orchestrator`
|
||||||
|
|
||||||
**Al entrar, ANTES de confirmar, márcate `role=orchestrator`** (paso obligatorio). Sin esto
|
**Al entrar, ANTES de confirmar, márcate `role=orchestrator`** (paso obligatorio). Sin esto
|
||||||
fleetview te clasifica como un ejecutor más y te mezcla con la flota en lugar de pinnearte
|
fleetview te clasifica como un ejecutor más y te mezcla con la flota en lugar de pinnearte
|
||||||
arriba separado por su propio bloque. El pin lo produce el campo `.role` del `goal.json` de tu
|
arriba separado por su propio bloque (★). El pin lo produce el campo `.role` del `goal.json` de
|
||||||
sesión (`apps/fleetview/cli.go::sortMembers`), y nadie lo escribe por ti salvo que el launcher
|
tu sesión (`apps/fleetview/cli.go::sortMembers`); nadie lo escribe por ti salvo que el launcher
|
||||||
de flota te haya arrancado con `--role orchestrator`. Cuando entras a `/orquestador` en una
|
de flota te haya arrancado con `--role orchestrator`:
|
||||||
sesión normal, eres tú quien debe marcarlo:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Resuelve tu PID por tu sessionId (el del goal de esta sesión) y marca el role.
|
# Resuelve tu PID por tu sessionId (el del goal de esta sesión) y marca el role.
|
||||||
@@ -33,10 +35,8 @@ PID=$(grep -l "$SID" ~/.claude/sessions/*.json | head -1 | xargs -n1 basename |
|
|||||||
```
|
```
|
||||||
|
|
||||||
`mark_claude_role_py_infra` escribe SOLO la clave `role` en tu `goal.json` preservando el resto
|
`mark_claude_role_py_infra` escribe SOLO la clave `role` en tu `goal.json` preservando el resto
|
||||||
(goal, phase, dod, dod_contract). Es idempotente: re-marcarlo no rompe nada. Si ya fuiste
|
(goal, phase, dod, dod_contract). Es idempotente. Tras marcarte, responde con una sola línea de
|
||||||
lanzado por el launcher de flota con `--role orchestrator`, este paso es un no-op seguro.
|
confirmación y queda a la espera de la tarea grande:
|
||||||
|
|
||||||
Tras marcarte, responde con una sola línea de confirmación y queda a la espera de la tarea grande:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
MODO ORQUESTADOR activo (role=orchestrator, pinneado arriba). Dame la tarea grande; la descompongo y lanzo secundarios. 'fin orquestador' para terminar.
|
MODO ORQUESTADOR activo (role=orchestrator, pinneado arriba). Dame la tarea grande; la descompongo y lanzo secundarios. 'fin orquestador' para terminar.
|
||||||
@@ -48,18 +48,17 @@ Hay dos cosas con nombre parecido. No las confundas:
|
|||||||
|
|
||||||
| | **Modo orquestador** (este comando) | **`fn-orquestador`** (subagent / `/autopilot`) |
|
| | **Modo orquestador** (este comando) | **`fn-orquestador`** (subagent / `/autopilot`) |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Mecanismo | Lanza Claudes **interactivos** en terminales **kitty** | Lanza un sub-agente via el **Agent tool** (no interactivo) |
|
| Mecanismo | Lanza Claudes **interactivos** en terminales (flota tmux / kitty) | Lanza un sub-agente via el **Agent tool** (no interactivo) |
|
||||||
| Visibilidad | El humano **ve y habla** con cada secundario en su kitty | El sub-agente corre headless; el humano no lo ve |
|
| Visibilidad | El humano **ve y habla** con cada secundario | El sub-agente corre headless; el humano no lo ve |
|
||||||
| Persistencia | El secundario **vive en su terminal**, se puede retomar (`claude --resume`) | El sub-agente termina y devuelve su texto final |
|
| Persistencia | El secundario **vive en su terminal**, se puede retomar (`claude --resume`) | El sub-agente termina y devuelve su texto final |
|
||||||
| Aislamiento | worktree / sub-repo / scope de archivos, impuesto en el prompt | worktree `auto/<issue>` gestionado por el propio `fn-orquestador` |
|
| Aislamiento | worktree / sub-repo / scope de archivos, impuesto en el prompt | worktree `auto/<issue>` gestionado por el propio `fn-orquestador` |
|
||||||
| Gobierno | El humano coordina via el orquestador; iteración en vivo | Bucle autónomo CONSTRUIR→EJECUTAR→...→MEJORAR hasta converger, PR draft |
|
| Gobierno | El humano coordina via el orquestador; iteración en vivo | Bucle autónomo CONSTRUIR→…→MEJORAR hasta converger, PR draft |
|
||||||
| Regla de referencia | esta página | `.claude/rules/autonomous_loop.md` |
|
| Regla de referencia | esta página + `.claude/rules/orchestration.md` | `.claude/rules/autonomous_loop.md` |
|
||||||
|
|
||||||
Resumen: **`fn-orquestador` (issue 0069) es para autonomía no supervisada con PR al final**; el
|
Resumen: **`fn-orquestador` (issue 0069) es para autonomía no supervisada con PR al final**; el
|
||||||
**modo orquestador es para trabajo largo que el humano quiere ver y poder retomar**, con varios
|
**modo orquestador es para trabajo largo que el humano quiere ver y poder retomar**, con varios
|
||||||
Claudes humanos-en-el-loop a la vez. Si el humano quiere fan-out autónomo y barato sin mirar,
|
Claudes humanos-en-el-loop a la vez. Fan-out autónomo y barato sin mirar → Agent tool o
|
||||||
usa el Agent tool o `/autopilot`; si quiere una flota de Claudes interactivos que él supervisa,
|
`/autopilot`; flota de Claudes interactivos que el humano supervisa → este modo.
|
||||||
usa este modo.
|
|
||||||
|
|
||||||
## El ciclo del orquestador (8 pasos)
|
## El ciclo del orquestador (8 pasos)
|
||||||
|
|
||||||
@@ -68,51 +67,26 @@ usa este modo.
|
|||||||
Parte la tarea grande en **sub-tareas independientes** que puedan correr en paralelo **sin
|
Parte la tarea grande en **sub-tareas independientes** que puedan correr en paralelo **sin
|
||||||
pisarse**. El criterio de independencia es sobre todo de **git**: dos sub-tareas que escriben
|
pisarse**. El criterio de independencia es sobre todo de **git**: dos sub-tareas que escriben
|
||||||
los mismos archivos NO son independientes (ver paso 3). Buenas líneas de corte: una app/sub-repo
|
los mismos archivos NO son independientes (ver paso 3). Buenas líneas de corte: una app/sub-repo
|
||||||
distinto por secundario; un dominio de funciones distinto; un módulo o paquete disjunto; el
|
distinto por secundario; un dominio de funciones distinto; un módulo o paquete disjunto; frontend
|
||||||
frontend vs el backend; documentación vs código. Si dos piezas comparten archivos, o las fusionas
|
vs backend; documentación vs código. Si dos piezas comparten archivos, o las fusionas en un
|
||||||
en un secundario, o las serializas (una después de otra), o las das scopes de archivos disjuntos.
|
secundario, o las serializas, o las das scopes de archivos disjuntos. Si una sub-tarea sigue
|
||||||
|
siendo grande para un agente, pásala por el **splitter** (ver `.claude/rules/orchestration.md`).
|
||||||
|
|
||||||
### 2. Lanzar cada secundario
|
### 2. Lanzar cada secundario
|
||||||
|
|
||||||
**Regla dura: cada secundario se lanza SIEMPRE como terminal visible — window de la flota tmux
|
**Regla dura: cada secundario se lanza SIEMPRE como terminal visible — window de la flota tmux si
|
||||||
si hay perfil fleet (`$FLEET_SOCKET`, lo normal), o kitty fuera de él. NUNCA como sub-agente del
|
hay perfil fleet (`$FLEET_SOCKET`, lo normal), o kitty fuera de él. NUNCA como sub-agente del Agent
|
||||||
Agent tool (ver paso 8).** Empieza por el bloque de flota tmux de abajo cuando estás en un perfil
|
tool (ver paso 8).** Empieza por el bloque de flota tmux cuando estás en un perfil fleet; kitty es
|
||||||
fleet; el bloque kitty es el fallback para secundarios que deban vivir fuera de la flota. Lanzar
|
el fallback para secundarios que deban vivir fuera de la flota.
|
||||||
un agente de trabajo con el Agent tool lo deja invisible: el humano no lo ve en `fleetview`, no
|
|
||||||
puede saltar a él con `/fleet focus` ni retomarlo — exactamente lo contrario de lo que este modo
|
|
||||||
busca.
|
|
||||||
|
|
||||||
Comando canónico de lanzamiento (memoria `lanzar-agentes-skip-permissions`), **siempre** con
|
Siempre con `--dangerously-skip-permissions` (memoria `lanzar-agentes-skip-permissions`): los
|
||||||
`--dangerously-skip-permissions` porque los secundarios trabajan autónomos y desatendidos y los
|
secundarios trabajan autónomos y desatendidos; los prompts de permiso en cada Bash los atascarían.
|
||||||
prompts de permiso en cada Bash los atascarían:
|
|
||||||
|
|
||||||
```bash
|
#### En la flota tmux (PREFERIDO en perfil fleet)
|
||||||
setsid nohup kitty --title "<PROYECTO> · <subtarea>" --directory <dir-aislado> \
|
|
||||||
zsh -ic 'claude --dangerously-skip-permissions "$(cat /tmp/orq_<slug>.md)"; exec zsh' \
|
|
||||||
>/tmp/orq_<slug>_kitty.log 2>&1 & disown
|
|
||||||
```
|
|
||||||
|
|
||||||
`setsid nohup ... & disown` hace que la kitty sobreviva al cierre de la terminal padre. El
|
Si estás dentro de un perfil FleetView (`$FLEET_SOCKET` seteada), **NO lances kitties sueltas**:
|
||||||
`zsh -ic '...; exec zsh'` deja una shell interactiva viva cuando el claude termina, para que el
|
lanza cada ejecutor como una **window de la flota tmux** con `spawn_fleet_agent`, para que viva en
|
||||||
humano siga en esa terminal. El log de `/tmp/orq_<slug>_kitty.log` es donde se ve el arranque.
|
la flota, se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`:
|
||||||
|
|
||||||
**Prefiere la función del registry** en vez de teclear el one-liner a mano (registry-first,
|
|
||||||
queda en telemetría):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./fn run launch_claude_agent_kitty "<PROYECTO> · <subtarea>" <dir-aislado> /tmp/orq_<slug>.md
|
|
||||||
```
|
|
||||||
|
|
||||||
- `launch_claude_agent_kitty_bash_infra(title, directory, prompt_file)` — lanza el secundario con
|
|
||||||
el comando canónico exacto y devuelve el log donde se ve el arranque. Valida que el dir y el
|
|
||||||
prompt_file existan y que kitty esté instalado.
|
|
||||||
|
|
||||||
#### En la flota tmux (PREFERIDO cuando operas en un perfil fleet)
|
|
||||||
|
|
||||||
Si estás dentro de un perfil FleetView (variable `$FLEET_SOCKET` seteada — eres el orquestador de
|
|
||||||
una flota tmux montada con `launch_fleetclaude`), **NO lances kitties sueltas**: lanza cada
|
|
||||||
ejecutor como una **window de la flota tmux** con `spawn_fleet_agent`, para que viva en la flota,
|
|
||||||
se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./fn run spawn_fleet_agent --socket "$FLEET_SOCKET" --session "$FLEET_SESSION" \
|
./fn run spawn_fleet_agent --socket "$FLEET_SOCKET" --session "$FLEET_SESSION" \
|
||||||
@@ -123,432 +97,202 @@ se vea en la TUI `fleetview` y sea conmutable con `/fleet focus`:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- `spawn_fleet_agent_bash_infra` crea la window tmux + arranca claude con el prompt autocontenido
|
- `spawn_fleet_agent_bash_infra` crea la window tmux + arranca claude con el prompt autocontenido
|
||||||
(o `--skill <name>` para arrancar en un modo), y con `--role executor|orchestrator` marca su
|
(o `--skill <name>`), y con `--role executor|orchestrator` marca su `goal.json`. El aislamiento
|
||||||
`goal.json` (via `mark_claude_role`). El aislamiento git (sub-repo / worktree / scope) sigue
|
git (sub-repo / worktree / scope) sigue imponiéndose en el prompt.
|
||||||
imponiéndose en el prompt igual que con kitty.
|
|
||||||
- **`--parent <mi-sessionId>` (recomendado):** escribe `parent_orchestrator` en el `goal.json` del
|
- **`--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
|
ejecutor atribuyéndotelo a ti. Es lo que habilita el **push activo** del watcher (te avisa en TU
|
||||||
automático** del watcher: cuando ese ejecutor se autodeclara terminado, el watcher inyecta el
|
pane cuando ese ejecutor termina). Sin `--parent` el aviso no se rutea. Opcional y
|
||||||
aviso en TU pane tmux (no en toda la flota), porque te identifica como su orquestador padre. Sin
|
retro-compatible. Ver `.claude/rules/orchestration.md`.
|
||||||
`--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.
|
#### Fuera de la flota (kitty fallback)
|
||||||
- Usa kitty (arriba) solo para secundarios que deban vivir **fuera** de un perfil fleet.
|
|
||||||
|
```bash
|
||||||
|
./fn run launch_claude_agent_kitty "<PROYECTO> · <subtarea>" <dir-aislado> /tmp/orq_<slug>.md
|
||||||
|
```
|
||||||
|
|
||||||
|
- `launch_claude_agent_kitty_bash_infra(title, directory, prompt_file)` lanza el secundario con el
|
||||||
|
comando canónico (`setsid nohup kitty … zsh -ic 'claude --dangerously-skip-permissions … ; exec
|
||||||
|
zsh'`) que sobrevive al cierre de la terminal padre y deja una shell viva al terminar el claude;
|
||||||
|
devuelve el log de arranque (`/tmp/orq_<slug>_kitty.log`). Usa kitty solo fuera de un perfil fleet.
|
||||||
|
|
||||||
### 3. Aislamiento git obligatorio por secundario (regla de oro)
|
### 3. Aislamiento git obligatorio por secundario (regla de oro)
|
||||||
|
|
||||||
**Dos Claudes en el MISMO working tree comparten `HEAD` y el índice; sus `git checkout` se
|
**Dos Claudes en el MISMO working tree comparten `HEAD` y el índice; sus `git checkout` se
|
||||||
interleavean y los commits caen en la rama equivocada** (memoria `multi-agent-git-race-same-repo`,
|
interleavean y los commits caen en la rama equivocada** (memoria `multi-agent-git-race-same-repo`,
|
||||||
caso real del 06/06/2026: los commits de un agente acabaron en la rama del otro y su propia rama
|
caso real 06/06/2026). Por eso **cada secundario trabaja en un espacio aislado**, y el orquestador
|
||||||
quedó vacía). Por eso **cada secundario trabaja en un espacio aislado**, y el orquestador elige
|
elige cuál y se lo **impone** en el prompt:
|
||||||
cuál y se lo **impone** en el prompt del secundario:
|
|
||||||
|
|
||||||
| Opción | Cómo | Cuándo |
|
| Opción | Cómo | Cuándo |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **(a) Sub-repo Gitea propio** | El secundario trabaja dentro de `apps/<x>/`, `analysis/<x>/`, `projects/<p>/...` — cada uno tiene su `.git` independiente (regla `apps_subrepo.md`) | Cuando las sub-tareas caen en apps/analyses/projects distintos. Es el aislamiento natural del monorepo. |
|
| **(a) Sub-repo Gitea propio** | El secundario trabaja dentro de `apps/<x>/`, `analysis/<x>/`, `projects/<p>/...` — cada uno con su `.git` independiente (regla `apps_subrepo.md`) | Sub-tareas en apps/analyses/projects distintos. Aislamiento natural del monorepo. |
|
||||||
| **(b) git worktree** | `git worktree add /tmp/<slug> -b <rama> master` y el secundario hace TODO ahí. Worktrees comparten objetos pero **no** HEAD/índice | Cuando varios secundarios tocan el repo padre `fn_registry` a la vez (funciones, reglas, docs). |
|
| **(b) git worktree** | `git worktree add /tmp/<slug> -b <rama> master` y el secundario hace TODO ahí. Worktrees comparten objetos pero **no** HEAD/índice | Varios secundarios tocan el repo padre `fn_registry` a la vez (funciones, reglas, docs). |
|
||||||
| **(c) Scope de archivos disjunto** | Mismo working tree pero cada secundario commitea **solo sus paths**: `git add <paths-específicos>`, **nunca** `git add -A` | Último recurso, solo si los scopes están garantizados disjuntos y no hay `git checkout` de rama de por medio. Frágil; prefiere (a) o (b). |
|
| **(c) Scope de archivos disjunto** | Mismo working tree pero cada secundario commitea **solo sus paths** (`git add <paths>`, **nunca** `git add -A`) | Último recurso, scopes garantizados disjuntos y sin `git checkout` de por medio. Frágil; prefiere (a) o (b). |
|
||||||
|
|
||||||
Para (b), crea el worktree **tú** (el orquestador) antes de lanzar, desde el working tree
|
Para (b), crea el worktree **tú** (el orquestador) antes de lanzar, desde el working tree principal,
|
||||||
principal, y pásale al secundario el path del worktree como `<dir-aislado>`.
|
y pásale al secundario el path del worktree como `<dir-aislado>`.
|
||||||
|
|
||||||
### 4. El prompt de cada secundario
|
### 4. El prompt de cada secundario
|
||||||
|
|
||||||
Lo escribes tú en `/tmp/orq_<slug>.md` antes de lanzar. El secundario **no ve este historial**;
|
Lo escribes tú en `/tmp/orq_<slug>.md` antes de lanzar. El secundario **no ve este historial**; el
|
||||||
el prompt debe ser **autocontenido**. Incluye SIEMPRE:
|
prompt debe ser **autocontenido**. Incluye SIEMPRE:
|
||||||
|
|
||||||
1. **Objetivo claro** — qué construir/arreglar, acotado y verificable.
|
1. **Objetivo claro** — qué construir/arreglar, acotado y verificable.
|
||||||
2. **Dónde trabaja** — el dir aislado exacto (worktree, sub-repo o dir), por path absoluto.
|
2. **Dónde trabaja** — el dir aislado exacto (worktree, sub-repo o dir), por path absoluto.
|
||||||
3. **Reglas de aislamiento git** — qué NO tocar (otros repos/worktrees, el working tree
|
3. **Reglas de aislamiento git** — qué NO tocar (otros repos/worktrees, el working tree principal
|
||||||
principal `~/fn_registry`), en qué rama commitear, y **cómo**: commits atómicos con `git add`
|
`~/fn_registry`), en qué rama commitear, y **cómo**: commits atómicos con `git add` de paths
|
||||||
de paths específicos, nunca `git add -A`; si es worktree, push de la rama al terminar, sin
|
específicos, nunca `git add -A`; si es worktree, push de la rama al terminar, sin merge a master
|
||||||
merge a master (lo integra el orquestador).
|
(lo integra el orquestador).
|
||||||
4. **Qué entrega y dónde** — un **report** en `reports/` (o `projects/<p>/reports/`) con
|
4. **Qué entrega y dónde** — un **report** en `reports/` (o `projects/<p>/reports/`) con evidencia
|
||||||
evidencia ejecutable (comandos + salida cruda), siguiendo `.claude/rules/reports.md` y
|
ejecutable (comandos + salida cruda), siguiendo `.claude/rules/reports.md` y `dod_quality.md`.
|
||||||
`.claude/rules/dod_quality.md`. Reports son artefacto local gitignored: se escriben, no se
|
Reports son artefacto local gitignored: se escriben, no se commitean.
|
||||||
commitean.
|
|
||||||
5. **Que puede delegar** — recuérdale que es full-capaz: puede spawnar `fn-constructor`,
|
5. **Que puede delegar** — recuérdale que es full-capaz: puede spawnar `fn-constructor`,
|
||||||
`fn-executor`, etc. via el Agent tool, y debe seguir registry-first (`registry_calls.md`,
|
`fn-executor`, etc. via el Agent tool, y debe seguir registry-first (`registry_calls.md`,
|
||||||
`delegation.md`).
|
`delegation.md`).
|
||||||
6. **La coletilla**: *"reporta tu progreso en esta terminal"* — para que el humano que mire la
|
6. **La coletilla**: *"reporta tu progreso en esta terminal"* — para que el humano que mire la
|
||||||
kitty vea el estado sin abrir el report.
|
terminal vea el estado sin abrir el report.
|
||||||
7. **DoD-contrato** — el criterio de aceptación **fijo y verificable** del secundario (golden +
|
7. **DoD-contrato** — el criterio de aceptación **fijo y verificable** (golden + edge + error path
|
||||||
edge + error path con evidencia ejecutable, `dod_quality.md`), redactado por ti. Va en el
|
con evidencia ejecutable, `dod_quality.md`), redactado por ti. Va en el prompt Y se escribe en el
|
||||||
prompt Y se escribe en el `goal.json` del secundario con `set_dod_contract` en cuanto conozcas
|
`goal.json` del secundario con `set_dod_contract` en cuanto conozcas su `sessionId`. Es el blanco
|
||||||
su `sessionId` (paso 5). Es el blanco estable contra el que el verificador juzgará el cierre.
|
estable contra el que el verificador juzgará el cierre. Sin `dod_contract`, el agente es
|
||||||
Sin `dod_contract`, el agente se clasifica `MAL_LANZADO`. Ver "Consumo de la cola de la flota".
|
`MAL_LANZADO`. Ver `.claude/rules/orchestration.md`.
|
||||||
|
|
||||||
Mira `/tmp/unibus_agent_*.md` como ejemplos reales de prompts de secundario que imponen
|
Mira `/tmp/unibus_agent_*.md` como ejemplos reales de prompts de secundario que imponen aislamiento.
|
||||||
aislamiento (cada uno fija sub-repo, rama, flags de build, DoD y dónde reportar).
|
|
||||||
|
|
||||||
### 5. Seguir la flota
|
### 5. Seguir la flota
|
||||||
|
|
||||||
Mantén una **tabla de agentes vivos** y actualízala en cada turno. La fuente de verdad del
|
Mantén una **tabla de agentes vivos** y actualízala en cada turno. La maquinaria de seguimiento
|
||||||
mapeo PID→sessionId→cwd son los archivos `~/.claude/sessions/<PID>.json` (memoria
|
(listar la flota tipada con `apps/fleetview/fleetview list`, el tiempo de **actividad** vs vida del
|
||||||
`claude-session-pid-mapping`). Usa la función del registry para listarla:
|
proceso, drenar la cola del watcher) y la **vigilancia reactiva** (clasificación de cada agente,
|
||||||
|
políticas por clasificación, verificador, auto-kill, nudge, splitter, cadencia) viven íntegras en
|
||||||
|
**`.claude/rules/orchestration.md`**. En resumen: la métrica es el **throughput de DoD cumplidos**,
|
||||||
|
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.
|
||||||
|
|
||||||
```bash
|
### 6. Parar un ejecutor — NUNCA `pkill`/`killall claude` (canónica)
|
||||||
./fn run list_claude_agents # tabla: PID, STATUS, ETIME, KITTY, SELF, SESSION_ID, CWD
|
|
||||||
./fn run list_claude_agents --json # para parsear y decidir
|
|
||||||
```
|
|
||||||
|
|
||||||
- `list_claude_agents_bash_infra([--json] [--exclude-current])` — cruza `pgrep -x claude` con los
|
|
||||||
`sessions/<PID>.json` (con validación anti-PID-reciclado), marca tu propia sesión como `SELF`,
|
|
||||||
y reporta cwd + sessionId de cada secundario (para retomar con `claude --resume <sessionId>`).
|
|
||||||
|
|
||||||
**Flota tipada (goal/phase/window/age) — usa el binario `fleetview`, NO `fn run`.** La flota con
|
|
||||||
`goal`, `phase`, `status`, `tmux_window` y `age`/`idle_seconds` la da el CLI de la app fleetview:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apps/fleetview/fleetview list --json # flota tipada: session_id, goal, phase, status, tmux_window, age, idle_seconds
|
|
||||||
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/<session_id>.json` (ver abajo).
|
|
||||||
|
|
||||||
**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
|
|
||||||
actividad (proxy de cuánto lleva sin avanzar / en su estado), lo útil para detectar estancados. El
|
|
||||||
`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.
|
|
||||||
|
|
||||||
Tu tabla de seguimiento, una fila por secundario:
|
|
||||||
|
|
||||||
| slug | título kitty | PID | cwd / dir aislado | rama | log | report | estado |
|
|
||||||
|---|---|---|---|---|---|---|---|
|
|
||||||
| docs | fn_registry · docs | 3637133 | /tmp/orq_docs_wt | orq/docs | /tmp/orq_docs_kitty.log | reports/00NN-…-docs.md | en curso |
|
|
||||||
|
|
||||||
Cuando un secundario parezca terminado, confirma: ¿pusheó la rama? ¿escribió el report? Lee el
|
|
||||||
report (`reports/`), revisa los commits de su rama (`git -C <dir> log --oneline`).
|
|
||||||
|
|
||||||
### 6. NUNCA `pkill`/`killall` sobre claude
|
|
||||||
|
|
||||||
Un `pkill claude` o `killall claude` **te mata a ti mismo** (el orquestador) junto con la flota.
|
Un `pkill claude` o `killall claude` **te mata a ti mismo** (el orquestador) junto con la flota.
|
||||||
Para parar un secundario:
|
Para parar un ejecutor:
|
||||||
|
|
||||||
- **Kill por PID exacto** del secundario (lo tienes en la tabla / `list_claude_agents`):
|
- **`kill_fleet_agent` (preferido)** tras verificar `met`: SIGTERM al claude + cierra su window tmux,
|
||||||
`kill <PID>` (o `kill <KITTY_PID>` para cerrar su ventana). Verifica que NO es tu `SELF`.
|
con guards anti-orquestador y anti-self. Es el auto-kill que libera el slot idle (ver
|
||||||
- **`reboot_all_claudes_bash_infra`** para reiniciar la flota retomando sesiones; tiene
|
`.claude/rules/orchestration.md`).
|
||||||
`--exclude-current` para no tocarte a ti. Es dry-run por defecto; `--go` para ejecutar.
|
- **Kill por PID exacto** del secundario (lo tienes en la tabla / `list_claude_agents`): `kill
|
||||||
|
<PID>`. Verifica que NO es tu `SELF`.
|
||||||
|
- **`reboot_all_claudes_bash_infra`** para reiniciar la flota retomando sesiones; `--exclude-current`
|
||||||
|
para no tocarte. Dry-run por defecto; `--go` para ejecutar.
|
||||||
|
|
||||||
### 7. Integrar
|
### 7. Integrar
|
||||||
|
|
||||||
Cuando un secundario termina (rama pusheada + report verde):
|
Cuando un secundario termina (rama pusheada + report verde):
|
||||||
|
|
||||||
1. **Revisa** su diff y su report. Si el report no trae evidencia ejecutable o falla la DoD,
|
1. **Revisa** su diff y su report. Si el report no trae evidencia ejecutable o falla la DoD,
|
||||||
devuélvele trabajo (el humano puede saltar a su kitty, o tú le mandas otro prompt).
|
devuélvele trabajo (el humano puede saltar a su terminal, o tú le mandas otro prompt / nudge).
|
||||||
2. **Mergea si procede** desde el **working tree principal** (ahí suele estar `master`
|
2. **Mergea si procede** desde el **working tree principal** (ahí suele estar `master`):
|
||||||
checked-out): `git -C ~/fn_registry merge --no-ff <rama>` para apps con TBD, o el flujo que
|
`git -C ~/fn_registry merge --no-ff <rama>` para apps con TBD, o el flujo del sub-repo. Para
|
||||||
corresponda al sub-repo. Para funciones nuevas del registry padre, sus archivos viajan en la
|
funciones nuevas del registry padre, sus archivos viajan en la rama y el merge los lleva a master.
|
||||||
rama y el merge los lleva a master.
|
|
||||||
3. **Informa al humano** y **resume el estado de la flota** en cada turno: quién terminó, quién
|
3. **Informa al humano** y **resume el estado de la flota** en cada turno: quién terminó, quién
|
||||||
sigue, qué se integró, qué falta.
|
sigue, qué se integró, qué falta.
|
||||||
|
|
||||||
### 8. Cómo lanzar un agente: SIEMPRE terminal del fleet (regla dura)
|
### 8. Cómo lanzar un agente: SIEMPRE terminal del fleet, NUNCA Agent tool (canónica)
|
||||||
|
|
||||||
**Todo agente que lances para que EJECUTE trabajo va como terminal visible, NUNCA como sub-agente
|
**Todo agente de trabajo va como terminal visible del fleet, NUNCA como sub-agente headless del Agent tool.** Un sub-agente headless corre invisible: no sale en `fleetview`, no es conmutable con `/fleet focus` ni se puede retomar. Jerarquía al lanzar un agente:
|
||||||
headless del Agent tool.** El humano quiere ver y poder saltar a cada agente en la flota; un
|
|
||||||
sub-agente del Agent tool corre invisible, no aparece en `fleetview`, no es conmutable con
|
|
||||||
`/fleet focus` y no se puede retomar. Jerarquía obligatoria al lanzar un agente:
|
|
||||||
|
|
||||||
1. **Estás en un perfil fleet** (`$FLEET_SOCKET` seteada, lo normal) → `spawn_fleet_agent` (window
|
1. **En perfil fleet** (`$FLEET_SOCKET`, lo normal) → `spawn_fleet_agent` (window de la flota tmux).
|
||||||
de la flota tmux). Es el default duro: el agente vive en la flota, se ve y se conmuta. Ver paso 2.
|
2. **Fuera de un perfil fleet** → kitty con `launch_claude_agent_kitty`.
|
||||||
2. **Fuera de un perfil fleet** → kitty con `launch_claude_agent_kitty` (paso 2).
|
3. **Agent tool (sub-agente headless)** → **PROHIBIDO para lanzar un agente de trabajo.** SOLO para
|
||||||
3. **Agent tool (sub-agente headless)** → **PROHIBIDO para lanzar un agente de trabajo.** Se
|
utilidades internas read-only tuyas que devuelven un resultado y mueren: el **verificador**
|
||||||
permite SOLO para las **utilidades internas read-only del propio orquestador** que devuelven un
|
adversarial de un cierre, el **splitter** (`Plan`), o una búsqueda puntual (`Explore`).
|
||||||
resultado y mueren sin que el humano las gestione como agentes de la flota: el **verificador**
|
|
||||||
adversarial de un cierre (`DICE_TERMINADO`), el **splitter** (`Plan`) de una tarea grande, o una
|
|
||||||
búsqueda puntual en el codebase (`Explore`). Nunca para ejecutar una sub-tarea.
|
|
||||||
|
|
||||||
Regla práctica: si el humano podría querer hablar con ello, mirarlo trabajar o retomarlo → terminal
|
Regla práctica: si el humano podría querer hablar con ello, mirarlo o retomarlo → terminal del fleet
|
||||||
del fleet (1 ó 2), SIEMPRE. Si es una consulta efímera que TÚ haces para decidir y nadie más ve →
|
(1 ó 2). Si es consulta efímera que TÚ haces para decidir y nadie más ve → Agent tool (3). Ante la
|
||||||
Agent tool (3). Ante la duda, terminal del fleet.
|
duda, terminal del fleet.
|
||||||
|
|
||||||
## Consumo de la cola de la flota — el cerebro reactivo (flow 0012)
|
|
||||||
|
|
||||||
Seguir la flota (paso 5) no es solo "¿quién vive?". Es **vigilar la salud por el DoD**: cada agente termina lo que empieza, o sabes por qué no. La métrica es el **throughput de DoD cumplidos**, no el número de agentes vivos — 30 agentes que no cierran nada no sirven. La fuente es la cola del **watcher embebido en fleetview** (`~/.claude/fleet/events.jsonl`): una línea por **transición** de estado de un agente (edge-triggered, sin ruido de nivel). El orquestador la drena cada vez que actúa y aplica una política por clasificación.
|
|
||||||
|
|
||||||
### 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` 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
|
|
||||||
```
|
|
||||||
|
|
||||||
El contrato sigue `dod_quality.md` (golden + edge + error con evidencia ejecutable), no un checkbox vago. Sin él, el agente es `MAL_LANZADO`.
|
|
||||||
|
|
||||||
### Push automático: el bloque `FLEET-STATE`
|
|
||||||
|
|
||||||
No hace falta acordarse de drenar para enterarse de un cambio. El hook `UserPromptSubmit`
|
|
||||||
`hook_fleet_state_inject.sh` (registrado en `.claude/settings.local.json`) inyecta en CADA turno del
|
|
||||||
orquestador —solo cuando la sesión es `role=orchestrator`— un bloque resumen de las transiciones
|
|
||||||
pendientes del watcher:
|
|
||||||
|
|
||||||
```
|
|
||||||
FLEET-STATE: terminados=[<sid>:<goal>…] reclaman=[…] estancados=[…] (drain con ./fn run drain_fleet_events para consumir)
|
|
||||||
```
|
|
||||||
|
|
||||||
Si no hay cambios emite `FLEET-STATE: sin cambios`; si el watcher está caído o el `events.jsonl` no
|
|
||||||
existe, degrada limpio sin romper el turno. El bloque es solo un **aviso** (hace peek, no avanza el
|
|
||||||
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: 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
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./fn run drain_fleet_events # consume nuevos (avanza cursor), agrupa por clasificación, marca urgentes
|
|
||||||
./fn run drain_fleet_events --advance false # peek sin consumir (inspección)
|
|
||||||
```
|
|
||||||
|
|
||||||
Devuelve `{total_new, events, by_classification, urgent, cursor}`. La clasificación de cada agente la produce `classify_fleet_termination` (pura) desde su estado (status + phase + dod_contract + 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`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
jq -r '.role // "executor"' ~/.claude/goals/<session_id>.json # "orchestrator" => ignóralo
|
|
||||||
```
|
|
||||||
|
|
||||||
El orquestador no tiene `dod_contract` y aparecería como `MAL_LANZADO` — es ruido, no un ejecutor que vigilar. Solo actúas sobre los **ejecutores** (`role=executor` o sin role).
|
|
||||||
|
|
||||||
### Políticas por clasificación
|
|
||||||
|
|
||||||
| 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. 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. |
|
|
||||||
| `GONE` | Limpiar de la tabla de seguimiento (terminó o murió; si tenía DoD sin cumplir, anótalo). |
|
|
||||||
|
|
||||||
### Verificador — cierre de `DICE_TERMINADO` (cero auto-aprobación)
|
|
||||||
|
|
||||||
Cuando un agente se autodeclara terminado, **no se confía**: lanzas un **verificador independiente** del ejecutor (Agent efímero), que compara el **report** del ejecutor (en `reports/`, con evidencia ejecutable) contra su `dod_contract`:
|
|
||||||
|
|
||||||
```
|
|
||||||
Agent(subagent_type="general-purpose", prompt:
|
|
||||||
"Verifica de forma ADVERSARIAL si el trabajo cumple su DoD-contrato. NO ejecutaste tú la tarea.
|
|
||||||
DoD-contrato: <contract>
|
|
||||||
Report del ejecutor: <ruta del reports/NNNN-*.md>
|
|
||||||
Comprueba CADA cláusula (golden + edge + error) contra la evidencia citada en el report; re-ejecuta
|
|
||||||
los comandos de verificación si puedes. Devuelve {verdict: met|failed, gaps: [...], evidence: [...]}.
|
|
||||||
Por defecto failed si la evidencia no respalda una cláusula.")
|
|
||||||
```
|
|
||||||
|
|
||||||
- `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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tmux -L "${FLEET_SOCKET:-fleet}" send-keys -t <window_id> \
|
|
||||||
"Sigues idle con tu DoD-contrato sin cerrar. Falta: <gap>. Cierra el golden+edge+error con evidencia, o reporta el bloqueo concreto." Enter
|
|
||||||
```
|
|
||||||
|
|
||||||
El `window_id` es el campo `tmux_window` (p.ej. `@20`) de `apps/fleetview/fleetview list --json`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apps/fleetview/fleetview list --json | jq -r '.[] | select(.session_id|startswith("<sid>")) | .tmux_window'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solo a idle/ESTANCADO. JAMÁS a un agente en `waiting`/`preguntando`** — esos te reclaman a TI, no un empujón del bot.
|
|
||||||
|
|
||||||
### Splitter — tarea demasiado grande
|
|
||||||
|
|
||||||
Si una sub-tarea sigue siendo grande para un solo agente, antes de lanzarla pásala por un **splitter** (Agent efímero) que devuelve un plan de sub-tareas atómicas, cada una con su `dod_contract` y sus dependencias:
|
|
||||||
|
|
||||||
```
|
|
||||||
Agent(subagent_type="Plan", prompt:
|
|
||||||
"Descompón esta tarea en sub-tareas ATÓMICAS, cada una cerrable por UN agente en una sesión, con
|
|
||||||
su propio DoD-contrato (golden+edge+error) y dependencias (cuáles son paralelas y cuáles
|
|
||||||
secuenciales). Máximo 6 sub-tareas. Tarea: <...>. Devuelve [{tarea, dod_contract, deps:[...]}].")
|
|
||||||
```
|
|
||||||
|
|
||||||
El orquestador lanza un ejecutor por sub-tarea respetando las dependencias (paralelas a la vez, secuenciales encadenadas). **Tope de fan-out** para no explotar la flota.
|
|
||||||
|
|
||||||
### Cadencia
|
|
||||||
|
|
||||||
El orquestador no hace polling caro: drena la cola **cuando actúa** (cuando la persona le habla) y, para vigilancia desatendida, con un heartbeat largo (`ScheduleWakeup` 20-30 min) o cuando el watcher empuja un urgente. Lo urgente (`RECLAMA`) sube al instante; el resto (cierres, estancados) se procesa en lote.
|
|
||||||
|
|
||||||
## Reglas duras del modo
|
## Reglas duras del modo
|
||||||
|
|
||||||
- **Responde CONCISO — velocidad de iteración sobre detalle.** Una o dos líneas por turno:
|
- **Responde CONCISO — velocidad de iteración sobre detalle.** Una o dos líneas por turno: estado de
|
||||||
estado de la flota + la decisión que pides o tomas. Nada de análisis largos, ni reformular el
|
la flota + la decisión que pides o tomas. Nada de análisis largos ni reformular el contexto — eso te
|
||||||
contexto, ni explicaciones extensas — eso te frena cuando gestionas muchos proyectos a la vez.
|
frena cuando gestionas muchos proyectos a la vez. Si te encuentras escribiendo un párrafo largo,
|
||||||
El detalle y el trabajo viven en los **ejecutores**; tú despachas, vigilas y escalas. Si te
|
párate: probablemente eso debería ir a un ejecutor.
|
||||||
encuentras escribiendo un párrafo largo, párate: probablemente eso debería ir a un ejecutor.
|
- **El orquestador no hace el trabajo pesado.** Descompone, lanza, sigue, integra. Si te encuentras
|
||||||
- **El orquestador va pinneado arriba en el sidebar.** Gracias a `role=orchestrator`, fleetview
|
escribiendo tú la feature, párate: ¿no debería ser un secundario? (Va pinneado arriba en el sidebar
|
||||||
lo fija arriba de la lista (★), separado de los ejecutores, para que el humano lo localice de un
|
por `role=orchestrator` ★, separado de los ejecutores.)
|
||||||
vistazo y no baile entre la flota que rota por estado.
|
- **Todo agente de trabajo va como terminal del fleet, NUNCA como sub-agente del Agent tool** — ver
|
||||||
- **El orquestador no hace el trabajo pesado.** Descompone, lanza, sigue, integra. Si te
|
paso 8 (canónica). El Agent tool queda solo para utilidades internas read-only tuyas.
|
||||||
encuentras escribiendo tú la feature, párate: ¿no debería ser un secundario?
|
- **Cada secundario, su aislamiento.** Nunca lances dos secundarios sobre el mismo working tree sin
|
||||||
- **Todo agente que lances va como terminal del fleet, NUNCA como sub-agente headless.** Si hay
|
worktrees/sub-repos/scopes disjuntos — causa nº1 de commits perdidos. Su prompt lleva SIEMPRE las
|
||||||
`$FLEET_SOCKET`, `spawn_fleet_agent` (window de la flota); si no, kitty. El Agent tool queda
|
reglas de aislamiento (dir, qué NO tocar, rama, cómo commitear). Nunca `git add -A` salvo dir
|
||||||
solo para utilidades internas read-only tuyas (verificador, splitter, búsqueda con `Explore`),
|
exclusivamente suyo (worktree/sub-repo).
|
||||||
jamás para lanzar un agente de trabajo. Ver paso 8. Cuando el humano dice "lanza un agente",
|
- **Tope de fan-out: máximo 6 ejecutores `role=executor` activos a la vez** por orquestador. Al
|
||||||
significa terminal visible en la flota.
|
alcanzarlo, encola el resto hasta que un slot se libere (ejecutor `met` + `kill_fleet_agent`).
|
||||||
- **Cada secundario, su aislamiento.** Nunca lances dos secundarios sobre el mismo working tree
|
Detalle y justificación en `.claude/rules/orchestration.md`.
|
||||||
sin worktrees/sub-repos/scopes disjuntos. Es la causa nº1 de commits perdidos.
|
- **Nunca `pkill`/`killall claude`** — ver paso 6 (canónica). Kill dirigido (`kill_fleet_agent`), por
|
||||||
- **El prompt del secundario lleva SIEMPRE las reglas de aislamiento.** Un prompt sin "trabaja
|
PID exacto, o `reboot_all_claudes --exclude-current`.
|
||||||
aquí, no toques aquello, commitea así" es un secundario que contaminará otro repo.
|
|
||||||
- **Nunca `git add -A` en un secundario** salvo que su dir aislado sea exclusivamente suyo
|
|
||||||
(worktree/sub-repo). En scope compartido, paths específicos.
|
|
||||||
- **Nunca `pkill`/`killall claude`.** Kill por PID exacto o `reboot_all_claudes --exclude-current`.
|
|
||||||
- **El humano habla contigo.** Tú resumes la flota; no le hagas perseguir 5 terminales.
|
- **El humano habla contigo.** Tú resumes la flota; no le hagas perseguir 5 terminales.
|
||||||
|
|
||||||
## Anti-patrones
|
## Anti-patrones
|
||||||
|
|
||||||
| Anti-patrón | Por qué es malo | En su lugar |
|
| Anti-patrón | Por qué es malo | En su lugar |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `pkill claude` para parar la flota | Te mata a ti (el orquestador) también | Kill por PID exacto / `reboot_all_claudes --exclude-current` |
|
| `pkill claude` para parar la flota | Te mata a ti (el orquestador) también | Kill dirigido / por PID exacto / `reboot_all_claudes --exclude-current` (paso 6) |
|
||||||
| Dos secundarios en el mismo working tree | Comparten HEAD/índice → commits dispersos, ramas vacías | worktree / sub-repo / scope disjunto por secundario |
|
| Dos secundarios en el mismo working tree | Comparten HEAD/índice → commits dispersos, ramas vacías | worktree / sub-repo / scope disjunto por secundario |
|
||||||
| Prompt de secundario sin reglas de aislamiento | El secundario contamina el repo padre u otro worktree | El prompt fija dir, qué NO tocar, rama y cómo commitear |
|
| Prompt de secundario sin reglas de aislamiento | El secundario contamina el repo padre u otro worktree | El prompt fija dir, qué NO tocar, rama y cómo commitear |
|
||||||
| `git add -A` en scope compartido | Arrastra cambios de otra sub-tarea al commit | `git add <paths-específicos>` |
|
| `git add -A` en scope compartido | Arrastra cambios de otra sub-tarea al commit | `git add <paths-específicos>` |
|
||||||
| Lanzar un agente de trabajo con el Agent tool (sub-agente headless) | Corre invisible: no sale en `fleetview`, el humano no puede verlo, saltarle con `/fleet focus` ni retomarlo | `spawn_fleet_agent` (window de la flota) o kitty fuera de fleet; Agent tool SOLO para utilidades internas read-only (verificador, splitter, `Explore`) |
|
| Lanzar un agente de trabajo con el Agent tool | Corre invisible (paso 8) | `spawn_fleet_agent` o kitty; Agent tool SOLO para utilidades read-only |
|
||||||
| Hacer tú la feature "porque es rápido" | Pierdes el sentido del modo; el humano no lo ve evolucionar | Descompón y lanza un secundario |
|
| Hacer tú la feature "porque es rápido" | Pierdes el sentido del modo; el humano no lo ve evolucionar | Descompón y lanza un secundario |
|
||||||
| Lanzar sin `--dangerously-skip-permissions` | El secundario se atasca pidiendo permiso en cada Bash | Siempre `--dangerously-skip-permissions` (riesgo asumido) |
|
| Lanzar sin `--dangerously-skip-permissions` | El secundario se atasca pidiendo permiso en cada Bash | Siempre `--dangerously-skip-permissions` (riesgo asumido) |
|
||||||
| Mergear desde el dir del secundario | Master suele estar en el working tree principal; colisión de HEAD | Mergear desde `~/fn_registry` |
|
| Mergear desde el dir del secundario | Master suele estar en el working tree principal; colisión de HEAD | Mergear desde `~/fn_registry` |
|
||||||
|
|
||||||
## Funciones del registry que usa este modo (grupo `orchestration`)
|
|
||||||
|
|
||||||
| Función | Para qué |
|
|
||||||
|---|---|
|
|
||||||
| `launch_claude_agent_kitty_bash_infra` | Lanzar un secundario en kitty con prompt autónomo + `--dangerously-skip-permissions` |
|
|
||||||
| `list_claude_agents_bash_infra` | Listar la flota de Claudes vivos (PID, sessionId, cwd, status, kitty) para seguirla |
|
|
||||||
| `reboot_all_claudes_bash_infra` | Reiniciar/parar la flota retomando sesiones; `--exclude-current` para no tocarte |
|
|
||||||
| `set_dod_contract_py_infra` | Escribir el DoD-contrato fijo (`dod_contract`/`dod_status`) en el `goal.json` de un secundario al lanzarlo |
|
|
||||||
| `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/<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. `--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`, `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).
|
|
||||||
|
|
||||||
## Ejemplo end-to-end
|
## Ejemplo end-to-end
|
||||||
|
|
||||||
Tarea grande: *"añade un endpoint `/api/health` al backend de la app `kanban` y, en paralelo,
|
Tarea grande: *"añade un endpoint `/api/health` al backend de la app `kanban` y, en paralelo,
|
||||||
documenta el grupo de capacidad `deploy` en `docs/capabilities/deploy.md`"*. Dos piezas
|
documenta el grupo de capacidad `deploy` en `docs/capabilities/deploy.md`"*. Dos piezas
|
||||||
independientes: una toca el sub-repo `apps/kanban` (su propio `.git`), la otra toca el repo
|
independientes: una toca el sub-repo `apps/kanban` (su propio `.git`), la otra toca el repo padre
|
||||||
padre `fn_registry` (docs). Aislamiento natural distinto para cada una.
|
`fn_registry` (docs). Aislamiento natural distinto para cada una.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Descomponer → 2 secundarios independientes:
|
# 1. Descomponer → 2 secundarios independientes:
|
||||||
# A) health endpoint → sub-repo apps/kanban (aislamiento (a))
|
# A) health endpoint → sub-repo apps/kanban (aislamiento (a))
|
||||||
# B) doc capability → worktree del padre (aislamiento (b))
|
# B) doc capability → worktree del padre (aislamiento (b))
|
||||||
|
|
||||||
# 2. Preparar aislamiento de B (worktree del padre; A ya está aislado por su sub-repo):
|
# 2. Preparar aislamiento de B (A ya está aislado por su sub-repo):
|
||||||
git -C ~/fn_registry worktree add /tmp/orq_capdoc -b orq/cap-deploy master
|
git -C ~/fn_registry worktree add /tmp/orq_capdoc -b orq/cap-deploy master
|
||||||
|
|
||||||
# 3. Escribir los prompts autónomos (autocontenidos, con reglas de aislamiento):
|
# 3. Escribir los prompts autónomos (autocontenidos, con reglas de aislamiento + DoD-contrato):
|
||||||
# /tmp/orq_health.md → "trabaja en apps/kanban (sub-repo propio), rama issue/health,
|
# /tmp/orq_health.md → trabaja en apps/kanban (sub-repo propio), rama issue/health, push, report.
|
||||||
# commits atómicos de tus paths, push al terminar, report en reports/. No toques el
|
# /tmp/orq_capdoc.md → trabaja SOLO en /tmp/orq_capdoc (worktree), rama orq/cap-deploy, push, report.
|
||||||
# repo padre. Reporta tu progreso en esta terminal."
|
|
||||||
# /tmp/orq_capdoc.md → "trabaja SOLO en /tmp/orq_capdoc (worktree), rama orq/cap-deploy,
|
|
||||||
# toca solo docs/capabilities/deploy.md, git add de ese path, push al terminar, report
|
|
||||||
# en reports/. No toques ~/fn_registry. Reporta tu progreso en esta terminal."
|
|
||||||
|
|
||||||
# 4. Lanzar ambos secundarios (cada uno su kitty, su dir aislado):
|
# 4. Lanzar ambos (window de la flota si hay $FLEET_SOCKET; aquí kitty fallback). Tras conocer su
|
||||||
./fn run launch_claude_agent_kitty "kanban · health endpoint" \
|
# sessionId, escribe su DoD-contrato con set_dod_contract.
|
||||||
~/fn_registry/apps/kanban /tmp/orq_health.md
|
./fn run launch_claude_agent_kitty "kanban · health endpoint" ~/fn_registry/apps/kanban /tmp/orq_health.md
|
||||||
./fn run launch_claude_agent_kitty "fn_registry · doc deploy" \
|
./fn run launch_claude_agent_kitty "fn_registry · doc deploy" /tmp/orq_capdoc /tmp/orq_capdoc.md
|
||||||
/tmp/orq_capdoc /tmp/orq_capdoc.md
|
|
||||||
|
|
||||||
# 5. Seguir la flota (cada turno):
|
# 5. Seguir cada turno: drena FLEET-STATE, verifica DICE_TERMINADO, nudge a ESTANCADO, lee reports/ (maquinaria en orchestration.md).
|
||||||
./fn run list_claude_agents
|
|
||||||
# → tabla con los 2 secundarios vivos (PID, cwd, sessionId, status) + tu SELF.
|
|
||||||
# Lee /tmp/orq_*_kitty.log para el arranque; cuando terminen, lee sus reports/.
|
|
||||||
|
|
||||||
# 7. Integrar (desde el working tree principal):
|
# 7. Integrar (desde el working tree principal):
|
||||||
git -C ~/fn_registry/apps/kanban merge --no-ff issue/health # sub-repo de la app
|
git -C ~/fn_registry/apps/kanban merge --no-ff issue/health # sub-repo de la app
|
||||||
git -C ~/fn_registry merge --no-ff orq/cap-deploy # repo padre (la doc)
|
git -C ~/fn_registry merge --no-ff orq/cap-deploy # repo padre (la doc)
|
||||||
git -C ~/fn_registry worktree remove /tmp/orq_capdoc # limpiar worktree
|
git -C ~/fn_registry worktree remove /tmp/orq_capdoc # limpiar worktree
|
||||||
|
# Resumen al humano: A integrado (endpoint + test verde), B integrado (doc), flota vacía.
|
||||||
# Resumen al humano: A integrado (endpoint + test verde), B integrado (doc),
|
|
||||||
# flota vacía. Tarea grande hecha.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Salida del modo
|
## Salida del modo
|
||||||
|
|
||||||
Cuando el humano escriba `salir orquestador` o `fin orquestador`, cierra con un resumen de la
|
Cuando el humano escriba `salir orquestador` o `fin orquestador`, cierra con un resumen de la flota:
|
||||||
flota: secundarios lanzados, cuáles terminaron e integraste, cuáles siguen vivos (con su kitty
|
secundarios lanzados, cuáles terminaron e integraste, cuáles siguen vivos (con su terminal para que el
|
||||||
para que el humano decida), y los reports generados. Si quedan secundarios vivos, recuérdale que
|
humano decida), y los reports generados. Si quedan secundarios vivos, recuérdale que
|
||||||
`list_claude_agents` los lista y que para pararlos es kill por PID exacto, nunca `pkill`.
|
`list_claude_agents` los lista y que para pararlos es kill dirigido / por PID exacto, nunca `pkill`
|
||||||
|
(paso 6).
|
||||||
|
|
||||||
## Relación con otras reglas
|
## Relación con otras reglas
|
||||||
|
|
||||||
- `.claude/rules/autonomous_loop.md` — `fn-orquestador` (Agent tool, sandbox no-interactivo). Es
|
- `.claude/rules/orchestration.md` — la maquinaria del modo: seguir la flota, watcher + cola,
|
||||||
lo que este modo **no** es; tenlas claras separadas.
|
clasificación, políticas, verificador, auto-kill, nudge, splitter, cadencia, y el catálogo de
|
||||||
- `.claude/rules/apps_subrepo.md` — apps/analyses/projects son sub-repos Gitea (`apps/*`
|
funciones del grupo `orchestration`.
|
||||||
gitignored): el aislamiento natural (opción (a)) y el gotcha de `git init` antes de limpiar un
|
- `.claude/rules/autonomous_loop.md` — `fn-orquestador` (Agent tool, sandbox no-interactivo). Es lo
|
||||||
worktree con una app nueva dentro.
|
que este modo **no** es; tenlas claras separadas.
|
||||||
- `.claude/rules/reports.md` + `.claude/rules/dod_quality.md` — qué entrega cada secundario:
|
- `.claude/rules/apps_subrepo.md` — apps/analyses/projects son sub-repos Gitea (`apps/*` gitignored):
|
||||||
report con evidencia ejecutable + gaps.
|
el aislamiento natural (opción (a)) y el gotcha de `git init` antes de limpiar un worktree.
|
||||||
|
- `.claude/rules/reports.md` + `.claude/rules/dod_quality.md` — qué entrega cada secundario.
|
||||||
- `.claude/rules/delegation.md` + `.claude/rules/registry_calls.md` — los secundarios siguen
|
- `.claude/rules/delegation.md` + `.claude/rules/registry_calls.md` — los secundarios siguen
|
||||||
registry-first y delegan a `fn-constructor` igual que tú.
|
registry-first y delegan a `fn-constructor` igual que tú.
|
||||||
- Memorias: `lanzar-agentes-skip-permissions`, `multi-agent-git-race-same-repo`,
|
- Memorias: `lanzar-agentes-skip-permissions`, `multi-agent-git-race-same-repo`,
|
||||||
|
|||||||
@@ -42,3 +42,4 @@ Reglas operativas del proyecto. Cada archivo es una regla independiente.
|
|||||||
| 35 | [llm_invocation.md](llm_invocation.md) | Invocacion de LLM: SIEMPRE `ask_llm` (grupo `claude-direct`, API directa, arranque 0), NUNCA `claude -p` (lento, cold start). One-shot/streaming/tool-loop + legacy `claude_stream_go_core` deprecado. |
|
| 35 | [llm_invocation.md](llm_invocation.md) | Invocacion de LLM: SIEMPRE `ask_llm` (grupo `claude-direct`, API directa, arranque 0), NUNCA `claude -p` (lento, cold start). One-shot/streaming/tool-loop + legacy `claude_stream_go_core` deprecado. |
|
||||||
| 36 | [reports.md](reports.md) | Reports: reportes de trabajo como artefacto local (entregable de tarea con evidencia). Gitignored salvo `.gitkeep`, NO suben a Gitea ni se indexan (como vaults+playgrounds). Viven en `reports/` o `projects/<p>/reports/`. Convencion + plantilla. ADR 0006. |
|
| 36 | [reports.md](reports.md) | Reports: reportes de trabajo como artefacto local (entregable de tarea con evidencia). Gitignored salvo `.gitkeep`, NO suben a Gitea ni se indexan (como vaults+playgrounds). Viven en `reports/` o `projects/<p>/reports/`. Convencion + plantilla. ADR 0006. |
|
||||||
| 37 | [flow_replay.md](flow_replay.md) | Flow replay: guardar un flujo web (login, reiniciar server, formulario) como funcion del registry. Patron grabar→destilar→reproducir con jerarquia HTTP puro > headless chromium > visible humanizado. Empieza por Nivel 1. Seguridad: HAR sensible, secrets a pass, acciones con efecto exigen confirmacion. Grupo `flow-replay`. Issue 0087. |
|
| 37 | [flow_replay.md](flow_replay.md) | Flow replay: guardar un flujo web (login, reiniciar server, formulario) como funcion del registry. Patron grabar→destilar→reproducir con jerarquia HTTP puro > headless chromium > visible humanizado. Empieza por Nivel 1. Seguridad: HAR sensible, secrets a pass, acciones con efecto exigen confirmacion. Grupo `flow-replay`. Issue 0087. |
|
||||||
|
| 38 | [orchestration.md](orchestration.md) | Maquinaria del modo `/orquestador`: seguir la flota (fleetview, tiempo de actividad), cola del watcher (events.jsonl, push activo, FLEET-STATE), clasificacion (`classify_fleet_termination`), politicas por clasificacion, verificador adversarial de cierres, auto-kill (`kill_fleet_agent`), nudge, splitter, cadencia + catalogo de funciones del grupo `orchestration`. Tope de fan-out=6. Flow 0012. |
|
||||||
|
|||||||
@@ -0,0 +1,301 @@
|
|||||||
|
## Maquinaria del modo orquestador: vigilancia reactiva de la flota
|
||||||
|
|
||||||
|
Esta regla recoge la **maquinaria estable** del modo `/orquestador` (`.claude/commands/orquestador.md`):
|
||||||
|
cómo se sigue la flota, cómo se consume la cola del watcher, cómo se clasifica cada agente y qué
|
||||||
|
política se aplica a cada clasificación, el verificador adversarial de cierres, el auto-kill, el
|
||||||
|
nudge, el splitter, la cadencia, y el catálogo de funciones del registry del grupo `orchestration`.
|
||||||
|
|
||||||
|
El comando `/orquestador` se queda con la doctrina y el flujo de cada turno; el detalle operativo
|
||||||
|
vive aquí para que el prompt del comando sea corto y la maquinaria no se diluya. El cerebro reactivo
|
||||||
|
de esta regla corresponde al flow 0012.
|
||||||
|
|
||||||
|
### Seguir la flota — listado y tiempo
|
||||||
|
|
||||||
|
La fuente de verdad del mapeo PID→sessionId→cwd son los archivos `~/.claude/sessions/<PID>.json`
|
||||||
|
(memoria `claude-session-pid-mapping`). Para listar la flota de Claudes vivos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./fn run list_claude_agents # tabla: PID, STATUS, ETIME, KITTY, SELF, SESSION_ID, CWD
|
||||||
|
./fn run list_claude_agents --json # para parsear y decidir
|
||||||
|
```
|
||||||
|
|
||||||
|
- `list_claude_agents_bash_infra([--json] [--exclude-current])` — cruza `pgrep -x claude` con los
|
||||||
|
`sessions/<PID>.json` (con validación anti-PID-reciclado), marca tu propia sesión como `SELF`,
|
||||||
|
y reporta cwd + sessionId de cada secundario (para retomar con `claude --resume <sessionId>`).
|
||||||
|
|
||||||
|
**Flota tipada (goal/phase/window/age) — usa el binario `fleetview`, NO `fn run`.** La flota con
|
||||||
|
`goal`, `phase`, `status`, `tmux_window` y `age`/`idle_seconds` la da el CLI de la app fleetview:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apps/fleetview/fleetview list --json # flota tipada: session_id, goal, phase, status, tmux_window, age, idle_seconds
|
||||||
|
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/<session_id>.json` (ver abajo).
|
||||||
|
|
||||||
|
**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
|
||||||
|
actividad (proxy de cuánto lleva sin avanzar / en su estado), lo útil para detectar estancados. El
|
||||||
|
`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.
|
||||||
|
|
||||||
|
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 |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| docs | fn_registry · docs | 3637133 | /tmp/orq_docs_wt | orq/docs | /tmp/orq_docs_kitty.log | reports/00NN-…-docs.md | en curso |
|
||||||
|
|
||||||
|
Cuando un secundario parezca terminado, confirma: ¿pusheó la rama? ¿escribió el report? Lee el
|
||||||
|
report (`reports/`), revisa los commits de su rama (`git -C <dir> log --oneline`).
|
||||||
|
|
||||||
|
### El cerebro reactivo: vigilar la salud por el DoD
|
||||||
|
|
||||||
|
Seguir la flota no es solo "¿quién vive?". Es **vigilar la salud por el DoD**: cada agente termina lo
|
||||||
|
que empieza, o sabes por qué no. La métrica es el **throughput de DoD cumplidos**, no el número de
|
||||||
|
agentes vivos — 30 agentes que no cierran nada no sirven. La fuente es la cola del **watcher embebido
|
||||||
|
en fleetview** (`~/.claude/fleet/events.jsonl`): una línea por **transición** de estado de un agente
|
||||||
|
(edge-triggered, sin ruido de nivel). El orquestador la drena cada vez que actúa y aplica una política
|
||||||
|
por clasificación.
|
||||||
|
|
||||||
|
#### 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` 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
|
||||||
|
```
|
||||||
|
|
||||||
|
El contrato sigue `dod_quality.md` (golden + edge + error con evidencia ejecutable), no un checkbox
|
||||||
|
vago. Sin él, el agente es `MAL_LANZADO`.
|
||||||
|
|
||||||
|
#### Push automático: el bloque `FLEET-STATE`
|
||||||
|
|
||||||
|
No hace falta acordarse de drenar para enterarse de un cambio. El hook `UserPromptSubmit`
|
||||||
|
`hook_fleet_state_inject.sh` (registrado en `.claude/settings.local.json`) inyecta en CADA turno del
|
||||||
|
orquestador —solo cuando la sesión es `role=orchestrator`— una línea recordatorio del rol
|
||||||
|
(`MODO ORQUESTADOR activo (role=orchestrator).`, que reancla el modo aunque su prompt se haya
|
||||||
|
diluido del contexto) seguida de un bloque resumen de las transiciones pendientes del watcher:
|
||||||
|
|
||||||
|
```
|
||||||
|
FLEET-STATE: terminados=[<sid>:<goal>…] reclaman=[…] estancados=[…] (drain con ./fn run drain_fleet_events para consumir)
|
||||||
|
```
|
||||||
|
|
||||||
|
Si no hay cambios emite `FLEET-STATE: sin cambios`; si el watcher está caído o el `events.jsonl` no
|
||||||
|
existe, degrada limpio sin romper el turno (la línea de rol se sigue emitiendo). El bloque es solo un
|
||||||
|
**aviso** (hace peek, no avanza el 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: 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./fn run drain_fleet_events # consume nuevos (avanza cursor), agrupa por clasificación, marca urgentes
|
||||||
|
./fn run drain_fleet_events --advance false # peek sin consumir (inspección)
|
||||||
|
```
|
||||||
|
|
||||||
|
Devuelve `{total_new, events, by_classification, urgent, cursor}`. La clasificación de cada agente la
|
||||||
|
produce `classify_fleet_termination` (pura) desde su estado (status + phase + dod_contract +
|
||||||
|
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`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq -r '.role // "executor"' ~/.claude/goals/<session_id>.json # "orchestrator" => ignóralo
|
||||||
|
```
|
||||||
|
|
||||||
|
El orquestador no tiene `dod_contract` y aparecería como `MAL_LANZADO` — es ruido, no un ejecutor que
|
||||||
|
vigilar. Solo actúas sobre los **ejecutores** (`role=executor` o sin role).
|
||||||
|
|
||||||
|
### Políticas por clasificación
|
||||||
|
|
||||||
|
| 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. 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. |
|
||||||
|
| `GONE` | Limpiar de la tabla de seguimiento (terminó o murió; si tenía DoD sin cumplir, anótalo). |
|
||||||
|
|
||||||
|
### Verificador — cierre de `DICE_TERMINADO` (cero auto-aprobación)
|
||||||
|
|
||||||
|
Cuando un agente se autodeclara terminado, **no se confía**: lanzas un **verificador independiente**
|
||||||
|
del ejecutor (Agent efímero), que compara el **report** del ejecutor (en `reports/`, con evidencia
|
||||||
|
ejecutable) contra su `dod_contract`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent(subagent_type="general-purpose", prompt:
|
||||||
|
"Verifica de forma ADVERSARIAL si el trabajo cumple su DoD-contrato. NO ejecutaste tú la tarea.
|
||||||
|
DoD-contrato: <contract>
|
||||||
|
Report del ejecutor: <ruta del reports/NNNN-*.md>
|
||||||
|
Comprueba CADA cláusula (golden + edge + error) contra la evidencia citada en el report; re-ejecuta
|
||||||
|
los comandos de verificación si puedes. Devuelve {verdict: met|failed, gaps: [...], evidence: [...]}.
|
||||||
|
Por defecto failed si la evidencia no respalda una cláusula.")
|
||||||
|
```
|
||||||
|
|
||||||
|
El verificador (y el splitter y las búsquedas con `Explore`) son la **única** excepción autorizada al
|
||||||
|
Agent tool dentro del modo: utilidades internas read-only del propio orquestador, que devuelven un
|
||||||
|
resultado y mueren sin que el humano las gestione como agentes de la flota. Jamás se usa el Agent tool
|
||||||
|
para ejecutar una sub-tarea (ver paso 8 del comando).
|
||||||
|
|
||||||
|
- `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`", paso 6 del comando).
|
||||||
|
- 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tmux -L "${FLEET_SOCKET:-fleet}" send-keys -t <window_id> \
|
||||||
|
"Sigues idle con tu DoD-contrato sin cerrar. Falta: <gap>. Cierra el golden+edge+error con evidencia, o reporta el bloqueo concreto." Enter
|
||||||
|
```
|
||||||
|
|
||||||
|
El `window_id` es el campo `tmux_window` (p.ej. `@20`) de `apps/fleetview/fleetview list --json`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apps/fleetview/fleetview list --json | jq -r '.[] | select(.session_id|startswith("<sid>")) | .tmux_window'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solo a idle/ESTANCADO. JAMÁS a un agente en `waiting`/`preguntando`** — esos te reclaman a TI, no un
|
||||||
|
empujón del bot.
|
||||||
|
|
||||||
|
### Splitter — tarea demasiado grande
|
||||||
|
|
||||||
|
Si una sub-tarea sigue siendo grande para un solo agente, antes de lanzarla pásala por un **splitter**
|
||||||
|
(Agent efímero) que devuelve un plan de sub-tareas atómicas, cada una con su `dod_contract` y sus
|
||||||
|
dependencias:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent(subagent_type="Plan", prompt:
|
||||||
|
"Descompón esta tarea en sub-tareas ATÓMICAS, cada una cerrable por UN agente en una sesión, con
|
||||||
|
su propio DoD-contrato (golden+edge+error) y dependencias (cuáles son paralelas y cuáles
|
||||||
|
secuenciales). Máximo 6 sub-tareas. Tarea: <...>. Devuelve [{tarea, dod_contract, deps:[...]}].")
|
||||||
|
```
|
||||||
|
|
||||||
|
El orquestador lanza un ejecutor por sub-tarea respetando las dependencias (paralelas a la vez,
|
||||||
|
secuenciales encadenadas), **siempre dentro del tope de fan-out** (ver "Tope de fan-out" abajo).
|
||||||
|
|
||||||
|
### Tope de fan-out (regla dura)
|
||||||
|
|
||||||
|
**Máximo 6 ejecutores `role=executor` activos simultáneos por orquestador.** Si se alcanza el tope,
|
||||||
|
el orquestador NO lanza más: **encola** las sub-tareas restantes y las despacha a medida que un slot
|
||||||
|
se libera — un slot se libera cuando un ejecutor se verifica `met` y se cierra con `kill_fleet_agent`
|
||||||
|
(auto-kill). El conteo es de la **familia propia** (ejecutores con tu `parent_orchestrator`), no de
|
||||||
|
toda la flota; resuélvelo con el routing por `parent_orchestrator`, igual que el push activo.
|
||||||
|
|
||||||
|
Por qué un número duro y no "los que hagan falta": ya hubo el caso de **30 agentes que no cerraban
|
||||||
|
nada** y, al competir todos por el mismo rate-limit compartido, hubo que desactivar `goal_refine`
|
||||||
|
(el hook que reescribía el `dod` con un LLM por prompt). Más ejecutores no es más throughput: el
|
||||||
|
cuello de botella es el rate-limit compartido y los DoD que nadie cierra, no el número de procesos.
|
||||||
|
|
||||||
|
### Cadencia
|
||||||
|
|
||||||
|
El orquestador no hace polling caro: drena la cola **cuando actúa** (cuando la persona le habla) y,
|
||||||
|
para vigilancia desatendida, con un heartbeat largo (`ScheduleWakeup` 20-30 min) o cuando el watcher
|
||||||
|
empuja un urgente. Lo urgente (`RECLAMA`) sube al instante; el resto (cierres, estancados) se procesa
|
||||||
|
en lote.
|
||||||
|
|
||||||
|
## Funciones del registry del grupo `orchestration`
|
||||||
|
|
||||||
|
| Función | Para qué |
|
||||||
|
|---|---|
|
||||||
|
| `launch_claude_agent_kitty_bash_infra` | Lanzar un secundario en kitty con prompt autónomo + `--dangerously-skip-permissions` |
|
||||||
|
| `list_claude_agents_bash_infra` | Listar la flota de Claudes vivos (PID, sessionId, cwd, status, kitty) para seguirla |
|
||||||
|
| `reboot_all_claudes_bash_infra` | Reiniciar/parar la flota retomando sesiones; `--exclude-current` para no tocarte |
|
||||||
|
| `set_dod_contract_py_infra` | Escribir el DoD-contrato fijo (`dod_contract`/`dod_status`) en el `goal.json` de un secundario al lanzarlo |
|
||||||
|
| `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/<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. `--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`, `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).
|
||||||
|
|
||||||
|
## Relación con otras reglas
|
||||||
|
|
||||||
|
- `.claude/commands/orquestador.md` — la doctrina y el flujo de cada turno del modo; esta regla es su
|
||||||
|
maquinaria operativa.
|
||||||
|
- `.claude/rules/autonomous_loop.md` — `fn-orquestador` (Agent tool, sandbox no-interactivo). Es lo
|
||||||
|
que el modo orquestador **no** es.
|
||||||
|
- `.claude/rules/apps_subrepo.md` — apps/analyses/projects son sub-repos Gitea (`apps/*` gitignored):
|
||||||
|
el aislamiento natural y el gotcha de `git init` antes de limpiar un worktree con una app nueva.
|
||||||
|
- `.claude/rules/reports.md` + `.claude/rules/dod_quality.md` — qué entrega cada secundario: report
|
||||||
|
con evidencia ejecutable + gaps.
|
||||||
|
- `.claude/rules/delegation.md` + `.claude/rules/registry_calls.md` — los secundarios siguen
|
||||||
|
registry-first y delegan a `fn-constructor`.
|
||||||
|
- Memorias: `lanzar-agentes-skip-permissions`, `multi-agent-git-race-same-repo`,
|
||||||
|
`claude-session-pid-mapping`, `prefiere-kitty-terminal`.
|
||||||
@@ -37,6 +37,14 @@ ROLE=""
|
|||||||
# Solo el orquestador recibe el feed de la flota. Resto: silencio total.
|
# Solo el orquestador recibe el feed de la flota. Resto: silencio total.
|
||||||
[ "$ROLE" != "orchestrator" ] && exit 0
|
[ "$ROLE" != "orchestrator" ] && exit 0
|
||||||
|
|
||||||
|
# Reanclar el rol en cada turno: el modo /orquestador no debe depender solo de
|
||||||
|
# que su prompt (.claude/commands/orquestador.md) siga en contexto. Este
|
||||||
|
# recordatorio se reinyecta aunque el watcher este caido o falte el venv (la
|
||||||
|
# guarda de abajo saldria con exit 0 sin emitir FLEET-STATE). Se emite SOLO para
|
||||||
|
# role=orchestrator: las sesiones sin goal.json o sin ese rol ya salieron arriba
|
||||||
|
# con exit 0 y stdout vacio, asi que el path limpio queda intacto.
|
||||||
|
printf '%s\n' "MODO ORQUESTADOR activo (role=orchestrator)."
|
||||||
|
|
||||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/fn_registry}"
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/fn_registry}"
|
||||||
PY="$PROJECT_DIR/python/.venv/bin/python3"
|
PY="$PROJECT_DIR/python/.venv/bin/python3"
|
||||||
{ [ -x "$PY" ] && [ -d "$PROJECT_DIR/python/functions" ]; } || exit 0
|
{ [ -x "$PY" ] && [ -d "$PROJECT_DIR/python/functions" ]; } || exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user