Files
fn_registry/python/functions/infra/mark_claude_parent.md
T
egutierrez 753e16b84c feat(infra): mark_claude_parent — escribe parent_orchestrator en goal.json (PID->sessionId)
Helper py analogo a mark_claude_role: resuelve el sessionId de un Claude recien
arrancado por su PID (sondeando sessions/<pid>.json) y escribe SOLO la clave
parent_orchestrator en su goal.json, preservando el resto. Lo consume
spawn_fleet_agent --parent para que el watcher de fleetview rutee los avisos del
ejecutor a su orquestador padre. Tests: escribe+preserva, goal inexistente,
parent vacio (ValueError), timeout sin crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:27:47 +02:00

5.5 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports params output tested tests test_file_path file_path
mark_claude_parent function py infra 1.0.0 impure def mark_claude_parent(pid: int, parent_orchestrator: str, wait_s: float = 10.0, sessions_dir: str | None = None, goals_dir: str | None = None) -> dict Marca el orquestador padre de una sesion de Claude Code resolviendo PID -> sessionId. Sondea ~/.claude/sessions/<pid>.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 `parent_orchestrator` en ~/.claude/goals/<sessionId>.json preservando el resto del goal (goal, phase, dod, dod_contract, role...). Escritura atomica tmp + os.replace. Si el sessions JSON no aparece a tiempo devuelve ok=False timeout sin lanzar. Equivalente de mark_claude_role para la clave parent_orchestrator: lo invoca spawn_fleet_agent (--parent) para que el watcher de fleetview rutee los avisos del ejecutor al orquestador que lo lanzo cuando hay mas de uno activo.
fleet
claude-fleet
orchestration
parent
session
goal
orchestrator
launcher
pid
false error_go_core
os
json
time
name desc
pid PID del proceso Claude Code recien arrancado (el ejecutor). Se usa para localizar ~/.claude/sessions/<pid>.json
name desc
parent_orchestrator sessionId del orquestador que lanzo el ejecutor. No puede ser vacio (lanza ValueError). Cualquier string no vacio es valido: es un sessionId arbitrario, no un enum
name desc
wait_s segundos maximos a esperar (sondeo cada ~0.25s) a que aparezca sessions/<pid>.json con un sessionId no vacio. Default 10.0
name desc
sessions_dir directorio de los sessions JSON. Default ~/.claude/sessions
name desc
goals_dir directorio de los goal JSON. Default ~/.claude/goals (se crea si falta)
dict. En exito: ok=True, pid (int), session_id (str), parent_orchestrator (str), path (ruta del goal escrito). En timeout del poll: ok=False, error (str con 'timeout esperando sessions/<pid>.json'), pid (int). NO lanza en timeout; el launcher decide true
sessions presente resuelve y escribe parent_orchestrator preservando otros campos (incl role)
parent vacio lanza ValueError sin escribir
sessions ausente devuelve timeout sin crash
goal inexistente se crea con solo parent_orchestrator
python/functions/infra/mark_claude_parent_test.py python/functions/infra/mark_claude_parent.py

Ejemplo

from infra.mark_claude_parent import mark_claude_parent

# El orquestador acaba de lanzar un ejecutor (Claude Code) y conoce su PID, pero
# todavia NO su sessionId. Deja constancia de quien lo lanzo para que el watcher
# de fleetview rutee al orquestador correcto el aviso de cierre del ejecutor.
res = mark_claude_parent(executor_pid, my_session_id, wait_s=15.0)

if res["ok"]:
    print(f"padre marcado: {res['session_id']} -> {res['parent_orchestrator']}")
else:
    # Timeout: el sessions/<pid>.json no aparecio a tiempo. El launcher decide.
    print("no se pudo marcar el padre:", res["error"])

Invocacion canonica via fn run (asi lo llama spawn_fleet_agent --parent):

./fn run mark_claude_parent <pid_del_ejecutor> <sessionId_del_orquestador>

Cuando usarla

Usala desde el launcher del orquestador de flota (spawn_fleet_agent --parent) justo despues de arrancar un ejecutor, cuando conoces su PID pero todavia NO su sessionId. Resuelve PID -> sessionId esperando el archivo que Claude Code escribe unos segundos despues, y deja parent_orchestrator en el goal.json del ejecutor. El watcher de fleetview consume esa clave para rutear el aviso de DICE_TERMINADO / estancamiento de cada ejecutor al pane tmux del orquestador que lo lanzo, en vez de a toda la flota — necesario cuando hay mas de un orquestador activo. Es el par de mark_claude_role (misma mecanica de poll, distinta clave escrita).

Gotchas

  • Impura: lee ~/.claude/sessions/<pid>.json y escribe ~/.claude/goals/<sessionId>.json (crea goals_dir si falta).
  • Timing del sessions JSON: Claude Code NO escribe sessions/<pid>.json al instante de arrancar — tarda unos segundos. Por eso la funcion sondea hasta wait_s (deadline con time.monotonic, no un unico sleep largo) y reacciona en cuanto el archivo aparece con un sessionId no vacio.
  • Timeout NO lanza: si el archivo no aparece a tiempo devuelve {"ok": False, "error": "timeout esperando sessions/<pid>.json", "pid": pid}. Solo parent_orchestrator vacio lanza (ValueError), y lo hace antes de tocar disco.
  • NO pisa otros campos del goal: abre el goal existente (dict vacio si no existe o es JSON invalido / no-dict), setea UNICAMENTE la clave parent_orchestrator y conserva todo lo demas (goal, phase, dod, dod_contract, role, ...). Si el goal.json no existia, lo crea con solo {"parent_orchestrator": ...}.
  • Escritura atomica: escribe a un archivo temporal y hace os.replace, asi un lector concurrente (la TUI / watcher fleetview) nunca ve un goal.json a medio escribir.
  • Orden con mark_claude_role: cuando spawn_fleet_agent aplica --role y --parent a la vez, los encadena 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.