feat(infra): spawn_fleet_agent --parent <sid> atribuye el ejecutor a su orquestador

Nuevo flag opcional --parent: persiste parent_orchestrator en el goal.json del
Claude recien lanzado (via mark_claude_parent, en background). Habilita el routing
del watcher de fleetview, que asi sabe a que pane de orquestador empujar el aviso
de cierre del ejecutor. El bloque background ahora cubre --role y/o --parent
encadenados secuencialmente (primero role, luego parent) para evitar la carrera de
lectura-modificacion-escritura sobre el goal.json. Retro-compatible: sin --parent,
el spawn se comporta igual que antes. Bump 1.0.0 -> 1.1.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 13:28:07 +02:00
parent 753e16b84c
commit b6ad1a3a15
2 changed files with 33 additions and 11 deletions
+21 -8
View File
@@ -15,7 +15,7 @@ set -euo pipefail
IFS=$' \t\n'
spawn_fleet_agent() {
local socket="" session="" cwd="" prompt_file="" skill="" role="" title="claude"
local socket="" session="" cwd="" prompt_file="" skill="" role="" parent="" title="claude"
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -25,6 +25,7 @@ spawn_fleet_agent() {
--prompt-file) shift; prompt_file="${1:-}" ;;
--skill) shift; skill="${1:-}" ;;
--role) shift; role="${1:-}" ;;
--parent) shift; parent="${1:-}" ;;
--title) shift; title="${1:-claude}" ;;
-h|--help)
cat <<'USAGE'
@@ -42,15 +43,20 @@ Opciones:
--role <r> Marca el goal.json del nuevo Claude: orchestrator|executor
(via mark_claude_role, en background). Sin esto, executor
implicito.
--parent <sid> sessionId del orquestador que lanza este ejecutor. Si se
pasa, escribe parent_orchestrator en el goal.json del nuevo
Claude (via mark_claude_parent, en background) para que el
watcher de fleetview rutee sus avisos al orquestador padre.
--title <t> Nombre de la window tmux. Default: claude.
Ejemplos:
# Orquestador en la flota actual:
spawn_fleet_agent --socket fleet2 --session fleet2 --cwd ~/fn_registry \
--skill orquestador --role orchestrator --title orquestador
# Ejecutor con tarea autocontenida:
# Ejecutor con tarea autocontenida, atribuido a su orquestador padre:
spawn_fleet_agent --socket fleet2 --session fleet2 --cwd ~/fn_registry \
--prompt-file /tmp/orq_health.md --title "kanban-health"
--prompt-file /tmp/orq_health.md --title "kanban-health" \
--parent 32945650-a4e1-472b-90c9-5b38ef60a463
USAGE
return 0 ;;
*)
@@ -98,17 +104,24 @@ USAGE
"exec claude --dangerously-skip-permissions" C-m
fi
# Marcar role en background (no-fatal). El sleep da tiempo a que el `exec
# claude` reemplace al shell antes de leer el pane_pid; mark_claude_role luego
# espera a que aparezca sessions/<PID>.json para resolver el sessionId.
if [[ -n "$role" ]]; then
# Marcar role y/o parent_orchestrator en background (no-fatal). El sleep da
# tiempo a que el `exec claude` reemplace al shell antes de leer el pane_pid;
# mark_claude_role / mark_claude_parent luego esperan a que aparezca
# sessions/<PID>.json para resolver el sessionId. Se encadenan SECUENCIALMENTE
# en el mismo subshell (primero role, luego parent) para que el segundo lea el
# goal ya con la primera clave escrita y no haya carrera de
# lectura-modificacion-escritura entre ambos.
if [[ -n "$role" || -n "$parent" ]]; then
local repo_root fn_bin
repo_root="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel 2>/dev/null || echo "${FN_REGISTRY_ROOT:-$HOME/fn_registry}")"
fn_bin="$repo_root/fn"
if [[ -x "$fn_bin" ]]; then
( sleep 1
pid=$(tmux -L "$socket" display-message -p -t "$win_pane" '#{pane_pid}' 2>/dev/null || true)
[[ -n "$pid" ]] && "$fn_bin" run mark_claude_role "$pid" "$role" >/dev/null 2>&1
if [[ -n "$pid" ]]; then
[[ -n "$role" ]] && "$fn_bin" run mark_claude_role "$pid" "$role" >/dev/null 2>&1
[[ -n "$parent" ]] && "$fn_bin" run mark_claude_parent "$pid" "$parent" >/dev/null 2>&1
fi
) >/dev/null 2>&1 &
disown 2>/dev/null || true
fi