ReferenciaCLI

vx sentinel

Vigilar a los soldiers despachados y registrar los cambios de status

Vigilar a los soldiers despachados y registrar los cambios de status.

Uso:
  vx sentinel         Hace polling para siempre (en primer plano; correlo en
                      segundo plano)
  vx sentinel drain   Imprime y confirma los wakes pendientes ahora mismo
  vx sentinel await   Bloquea hasta que llegue un wake, o hasta el timeout
                      (para el hook Stop asíncrono que configura 'vx init')

El sentinel consulta cada tarea "running" rastreada en el status en vivo de
herdr, en todos los proyectos que haya visto alguna vez (un proceso sentinel
por máquina). Cuando una termina (done o blocked), persiste el cambio y
registra un wake durable en la cola del commander que despachó la tarea. Es el
proyecto propio de la tarea cuando el commander trabaja ahí, y el proyecto del
commander cuando despachó desde otro (la tarea registra a su commander: proyecto
y pane de herdr, que fija 'vx dispatch'). "drain" y "await" resuelven en qué proyecto
actuar a partir del directorio actual (el toplevel de git, con el mismo
namespace con el que "vx dispatch" arma los camps de un proyecto): si no es un
repo, o si el toplevel es en sí un camp administrado por vexillum, no hay nada
que drenar acá, así que ambos son no-ops silenciosos en lugar de un error.

"drain" revisa una sola vez, al instante: {"decision":"block",...} si ya hay
algo pendiente para ese proyecto, {} en caso contrario. "await" es lo que el
hook Stop asíncrono llama en realidad: bloquea (revisando de nuevo cada pocos
segundos) hasta que aparece un wake o se acaba el tiempo, así que el commander
se despierta incluso si su turno ya había terminado antes de que un soldier
terminara, y no solo cuando un wake ya está pendiente justo en el momento en
que termina un turno. "await" sale de inmediato, sin esperar, fuera de un pane
administrado por herdr (sin HERDR_WORKSPACE_ID): el hook se commitea en el
proyecto y por eso alcanza también a los turnos de Claude Code de cualquier
otra herramienta, que no tienen ninguna tarea para que el sentinel rastree.

Un wake también puede ser feedback de forum: el listener de forum guarda el prompt de un usuario en el inbox del
proyecto y registra un wake de forum, que tanto "drain" como "await" informan
como "forum session <file>: N new messages (ended: false). Run vx forum
inbox." Es el mismo hook y la misma entrega de una sola vez; un wake de forum cuyos mensajes ya fueron
confirmados se descarta. Ver "vx forum" para el listener.

Un wake se consume solo una vez entregado. "drain" y "await" lo reclaman, lo
imprimen y recién entonces lo borran: cuando el hook que lanzó un "await" ya no
está (su proceso padre salió, recibió una señal, o no se puede escribir en
stderr), el wake se vuelve a poner para el siguiente en lugar de imprimirse al
vacío, y un reclamo cuyo dueño murió se encuentra de nuevo a los dos minutos.
Los wakes que esperan en un proyecto sin "await" vivo ni commander registrado
vivo los informa 'vx doctor'. Así también se ve un commander de Claude Code cuyo "await" agotó
sus 55 minutos (dentro del timeout de una hora del hook): no oye nada hasta su
próximo turno, y vx status muestra lo que pasó mientras tanto. Un commander Pi
arranca solo un "await" nuevo cuando uno vence, y uno OpenCode no tiene ninguno.

Un proyecto tiene una sola cola de wakes y un wake se entrega una sola vez, al
primer commander que lo pide. Dos commanders vivos en el mismo proyecto (por
ejemplo Claude Code y Pi) por lo tanto no lo oyen los dos: mantené un commander
por proyecto (el otro puede ponerse al día con vx status y vx forum inbox, y vx
doctor advierte sobre esa configuración).

Un commander OpenCode 2.x no tiene hook Stop, así que el propio sentinel entrega
sus wakes: cuando un proyecto tiene un commander opencode registrado y vivo
(vx doctor lo informa) y ningún await vivo, el sentinel postea el mismo texto en la sesión
del commander a través de la API HTTP de OpenCode (opencode api POST
/api/session/<id>/prompt, encolado detrás del turno que corre, sin tocar el
editor), una sola vez, con las mismas reglas para descartar lo que ya no es
noticia. Un wake que no puede entregar (todavía no hay sesión porque no se envió
nada en el pane, el servicio es inalcanzable, un sentinel con otro HOME o
directorios XDG que el OpenCode del commander) se vuelve a poner y se reintenta
en cada pasada, nunca se descarta. Después de 30 segundos fallando, y solo
mientras herdr informa al commander como idle, lo escribe en el pane con "herdr
agent prompt", que puede pegar el texto a un borrador que la persona escribió
pero no envió.

Corre un sentinel por máquina, garantizado por un lock que el kernel suelta
cuando el proceso muere, así que un crash nunca deja un reclamo viejo. El
sentinel registra el build desde el que arrancó (versión y archivo ejecutable)
en ~/.vexillum/sentinel.json y lo revisa después de cada pasada: cuando ese
archivo fue reemplazado en disco (un rebuild, un upgrade) termina la pasada,
suelta su lock y sale, para que un sentinel viejo nunca siga reescribiendo
estado con lógica vieja. "vx dispatch", "vx prompt" y cada "await" arrancan
uno nuevo desde el binario nuevo; "vx doctor" advierte sobre un sentinel que
está desactualizado o duplicado.

Cada "await" se registra bajo ~/.vexillum/sentinel-awaiters/ y nunca sobrevive
al hook que lo lanzó: sale apenas su proceso padre desaparece, y un "await"
más nuevo de la misma sesión y proyecto reemplaza al del turno anterior. Al
arrancar "vx sentinel" o cualquier "await" también se detienen los huérfanos
que dejó una sesión muerta. A un proceso solo se le envía una señal después de
verificar que es un "vx sentinel await", nunca por nombre.