Chair y workers

Un Chair por proyecto, elegido por llegada. Un segundo Chair interactivo se rechaza, y los workers no pueden lanzar workers.

Qué es#

Dos roles, y solo dos.

Rol Cómo se define ¿Puede lanzar workers?
chair El primer asiento interactivo orch <agent> de un proyecto, abierto sin tarea Sí — dispatch_worker, task_add, task_run
worker Creado por task_run o dispatch_worker, o cualquier proceso con ORCH_ROLE=worker No — el intento da error
        tú, escribiendo en la interfaz del agente
                   |
                 Chair                (uno por proyecto git)
             /     |     \
        worker  worker  worker        (un worktree orch/<session> cada uno)
             \     |     /
           msg_send / ask / verify_report
                   |
                 Chair

La elección es por llegada, no por configuración. Abres orch claude en un proyecto y ese asiento es el Chair. Abres un segundo asiento interactivo en el mismo proyecto y Orchemax lo rechaza:

project already has Chair session <id> (<name>) — one Chair per project; spawn workers with orch task run, or pass --force-chair

--force-chair existe para el caso que nombra el mensaje: de verdad quieres dos Chairs vivos. Los asientos fantasma que deja una terminal muerta se limpian solos, así que rara vez debería hacer falta.

Cuántos Chairs y workers permite tu plan#

Un Chair por proyecto es la ley del taller de arriba. Aparte, el plan limita cuántos Chairs de agente abierto y cuántos workers pueden correr a la vez en una máquina vinculada:

Plan Chairs abiertos (orch.agents.max) Workers concurrentes (orch.agents.concurrent)
Free 1 0 (solo Chair; workers desde Starter)
Starter 2 2
Professional 5 (concurrente del plan)
Team 10 (concurrente del plan)

Los Chairs que solo sirven MCP (sin TUI abierta) no consumen el medidor de workers. Los workers solo cuentan contra orch.agents.concurrent.

Roster de workers baratos#

Opcional en orch.yaml: agents.workers lista pares preferidos {agent, model} (orden = preferencia). El Chair debería despachar desde esa lista cuando la tarea encaje; lista vacía / placeholders = apagado y el Chair usa su propio agente. El spawn en vivo sigue respetando el tope concurrente. Ver Configurar agentes.

Workers con otros modelos#

  • Un worker puede correr otro CLI y otro modelo que el Chair. Le escribe a sus pares por nombre sobre el mismo bus, en modo contexto.
  • Los workers con modelos externos pasan por Pi, que orch instala y fija bajo machine-home la primera vez que despachas uno. Ver Configura agentes.
  • Cada worker corre en su propio worktree, así que lo que lee y escribe no entra en la ventana del Chair; solo se le factura a su propio modelo esa subtarea.
  • Tokens, bytes ahorrados y saltos por asiento: /app/board.

El fan-out desde un worker se deniega en ambos transportes, con la misma cadena:

dispatch_worker denied: ORCH_ROLE=worker — only the Chair may spawn (use orch msg to the Chair)

Por qué existe#

Un worker que puede lanzar workers es una bomba de bifurcación con tarjeta de crédito. Más silencioso todavía: también es un hueco de responsabilidad, porque nadie queda sosteniendo el hilo que decide cuándo la tarea está lista. Un Chair por proyecto deja un solo lugar donde se reparte el trabajo, una sola bandeja para las preguntas y un solo veredicto al final.

Reportar no es fan-out, y la regla es explícita: un worker sí puede llamar a verify_report, porque decir lo que hiciste no es lo mismo que repartir más trabajo.

Los workers sobreviven al proceso del Chair#

Un worker lanzado no corre dentro del proceso orch mcp del Chair. dispatch_worker y task_run preparan el asiento, el worktree y el log, y luego entregan la corrida a un supervisor aparte, orch worker-run, que lanza el agente, sella la verificación y escribe el handoff. Matar al Chair — fin de turno, Ctrl-C, un crash — ya no deja workers sin sellar. La respuesta trae el supervisor_pid y la vía de separación que permitió el sistema operativo (breakaway, intermediary o setsid). En Windows, breakaway necesita que el job object del propio Chair lo permita (JOB_OBJECT_LIMIT_BREAKAWAY_OK); sin esa bandera ni breakaway ni intermediary sacan al supervisor, porque intermediary queda dentro del mismo job y muere con él.

El Chair sigue teniendo que recoger. task_wait bloquea hasta que los workers sellan y devuelve todos los veredictos juntos:

dispatch_worker {agent, prompt, wait: false}    uno por worker, en el mismo turno
task_wait {all: true, timeout_sec: 300}         -> status, exit, verification, handoff

Nunca termines un turno con workers en running.

Novedad de 0.5.0#

  • Una tarjeta de inicio veraz. La tarjeta de inicio del Chair se construye con los registros propios de Orchemax: las sesiones que murieron se muestran como muertas, las ramas ya integradas no se listan y las líneas pendientes del ledger se muestran, no se cuentan.
  • El ciclo de mejora. Cada error, de un worker o del Chair, se vuelve una fila que avanza detectar → corregir → mejorar (una compuerta) → memorizar (una lección) → aprender (se muestra en la acción) → repetir (se verifica que no reincide). Un turno o una sesión no puede cerrar con un error sin memorizar, y una reincidencia lo reabre.
  • El Chair sujeto a las mismas reglas. Se rechazan los briefs sin invariantes con pruebas o sin sección fuera de alcance, las anulaciones repetidas de una compuerta deben nombrar el ítem que la arregla, y el aterrizaje nunca se bloquea a sí mismo y espera a que la máquina esté tranquila.

Ver Versión 0.5.0.

Modo plan#

El trabajo riesgoso se puede lanzar primero en modo solo lectura. dispatch_worker {agent, prompt, plan: true} (o orch dispatch --plan) corre el worker en solo lectura. Claude Code rechaza Write/Edit/MultiEdit/NotebookEdit en su hook PreToolUse con plan mode: read-only — return the plan in your final answer; cursor-agent no tiene hook previo a la escritura, así que para él (y cualquier otro CLI) la verificación del árbol de abajo es la única aplicación. El sellado falla con plan: tree changed si el worker escribió algo de todos modos (un redirect de shell, un CLI sin hook). El prompt recibe una plantilla corta — objetivo, archivos a tocar, pasos, riesgos, pruebas — y la respuesta final del worker queda capturada como plan_text, devuelta en la respuesta y guardada en la fila de la sesión.

Una vez que el plan se ve bien, el trabajo real se lanza a partir de él: dispatch_worker {agent, prompt, plan_from: "<session-planeada>"} (o orch dispatch --plan-from <session>) inyecta ese plan capturado en el prompt del worker que ejecuta, bajo "Plan approved by the Chair", en vez de pedirle que escriba uno nuevo.

dispatch_worker {agent, prompt, plan: true, wait: true}       -> plan_text, verification
dispatch_worker {agent, prompt, plan_from: "<session>"}       -> ejecuta con el plan aprobado

Cómo se relaciona con el resto#

  • Un worker que necesita una decisión levanta un ask bloqueante en vez de adivinar o de lanzar a alguien que decida por él.
  • Cada worker lanzado recibe por defecto un worktree git orch/<session>; orch worktree status, orch worktree promote <session> y orch worktree prune son su ciclo de vida. promote cierra en falso ante conflictos y nunca hace push.
  • Una tarea puede llevar context de fondo y una lista de acceptance_criteria; cada criterio se renderiza en el prompt del worker como una casilla sin marcar y tiene que volver por verify_report marcado como cumplido o no. Un worker que nunca reporta los deja a todos como incumplidos en el board, a propósito.
  • Cuando clarify_before_spawn está activo, lanzar queda supeditado a que orch ledger check pase, salvo que la llamada use force.
  • Un spawn se deniega cuando el ledger entrega un archivo que está en el árbol de trabajo pero no en HEAD: el worktree del worker es un checkout de HEAD, así que no lo encontraría. Una ruta que no existe en ninguna parte es un archivo que el trabajo va a crear y no dice nada, y una ruta que git ignora tampoco viaja. Esta corre pase lo que diga clarify_before_spawn, porque lo que cuesta es el alcance del worker; force es la salida.

Aquí no hay nada que ejecutar#

Eliges un Chair abriendo un asiento, y lanzas un worker pidiéndoselo al Chair en el chat. orch dispatch --prompt "…" existe para scripts y CI, no para el uso diario.