Orkas Orkas
Inicio Blog Arquitectura
Arquitectura

Orquestación de múltiples agentes en la práctica: cómo Orkas ejecuta un agente principal y sus subagentes

Así funciona la orquestación de múltiples agentes de Orkas: un agente principal convierte una solicitud en un plan, asigna trabajo a subagentes según las dependencias, transmite contexto entre los pasos y se recupera de los fallos.

El artículo anterior trataba sobre cómo hacer que un agente sea confiable: el bucle de ejecución, el enrutamiento de herramientas, la compactación del contexto y las sesiones resistentes a fallos. Esa capa responde a la pregunta "¿cómo completa un solo agente una tarea sin fallar?". Este artículo trata sobre la capa superior: qué ocurre cuando un agente no basta y hay que repartir un trabajo entre un equipo.

A esto suele referirse la gente con orquestación de múltiples agentes: un agente principal que lleva la conversación divide una solicitud en partes, asigna cada parte a un subagente especializado, espera a que terminen los que deben hacerlo antes de iniciar la siguiente, transmite los resultados y mantiene todo bajo control cuando falla un paso. Orkas ejecuta todo esto en la propia máquina del usuario. A continuación explicamos cómo está construida esa capa de orquestación: el código se ha depurado y generalizado, pero la estructura es real.

La versión breve Un agente principal y sus subagentes, en un mismo espacio de trabajo Así distribuye Orkas el trabajo hoy: Commander reúne especialistas, los ejecuta en paralelo o en serie, y tú puedes ver cómo sucede.
Descargar Orkas — gratis

Agente principal y subagentes

El modelo conceptual es un equipo pequeño con una cadena de mando clara. Un agente principal (lo llamamos Commander) se encarga de la conversación y del contexto general. No hace todo el trabajo por sí mismo; su función es decidir qué hay que hacer, en qué orden y quién debe hacerlo. Los subagentes son especialistas: cada uno está configurado con sus propias instrucciones de sistema, sus propias herramientas permitidas y su propio conjunto de habilidades. Un subagente domina una parte del trabajo y se invoca cuando esa parte entra en juego.

Dos aspectos hacen que esto sea algo más que un término de moda. Primero, los subagentes y las habilidades son unidades de primer nivel, no trucos con instrucciones: un subagente es un agente real, configurado por separado, y asignarle trabajo supone una transferencia real con su propio contexto. Segundo, la coordinación no se deja a las buenas intenciones del modelo: la rige un artefacto explícito cuya fidelidad a la realidad garantiza el sistema, no el modelo. Ese artefacto es el plan.

Un plan es un grafo, no un guion

Cuando el agente principal decide que una solicitud necesita más de un paso, escribe un plan. El plan no es un texto libre ni una lista de verificación lineal: es un pequeño grafo de dependencias (un DAG). Cada nodo es un paso, y cada paso contiene todo lo que el orquestador necesita para asignarlo:

interface PlanStep {
  index: number;            // 1-based, stable, never renumbered
  title: string;            // human-readable, shown in the UI
  assignee: string;         // who runs it: "user" | "commander" | a sub-agent
  input?: string;           // the dispatch payload — a template (see below)
  wait_for?: number[];      // upstream step indexes; defaults to [index - 1]
  on_failure?: "abort_plan" | "continue" | "ask_commander";

  // --- runtime state, owned by the orchestrator, NOT written by the model ---
  status: "pending" | "in_progress" | "done" | "failed" | "skipped" | "blocked";
  output_summary?: string;  // short summary of what the step produced
  output_files?: string[];  // files the step produced
  failure_reason?: string;
}

El campo wait_for es lo que convierte una lista en un grafo. De forma predeterminada, un paso espera al anterior (una cadena simple), pero puede declarar que depende de varios pasos previos: "resumir" podría esperar tanto a "investigar el mercado" como a "estudiar a la competencia". Eso es un rombo, no una línea, y el orquestador lo trata como tal.

Quién determina la verdad: el orquestador, no el modelo

Observa de nuevo la división en esa estructura. El modelo completa la intención —títulos, responsables, entradas y dependencias— cuando escribe el plan por primera vez. Pero todo lo relacionado con el estado de ejecuciónstatus, output_summary, failure_reason— pertenece exclusivamente al orquestador. El modelo propone el plan una vez; nunca puede marcar sus propios pasos como "completados".

Esta separación es deliberada y es la decisión más importante de todo el diseño. Un modelo de lenguaje es perfectamente capaz de anunciar alegremente "paso 3 completado" cuando el paso 3 dio un error, o de perder la cuenta de qué pasos siguen pendientes a mitad de una conversación larga. Si el estado viviera en la cabeza del modelo, el plan se alejaría de la realidad. Al convertir el estado en un artefacto estructurado que solo escribe el ejecutor —y que solo escribe cuando un paso realmente termina—, el plan sigue siendo un reflejo preciso de lo que ha ocurrido. El modelo decide la estructura del trabajo; el entorno de ejecución decide qué es cierto sobre su progreso.

Asignar los pasos que están listos

Una vez que existe un plan, un pequeño motor —el ejecutor— lo hace avanzar. La operación central es "encontrar los pasos que están listos y asignarlos". Un paso está listo cuando sigue en pending y todos los pasos que espera han llegado a un estado final con un grado de éxito suficiente:

function findReadySteps(plan): PlanStep[] {
  return plan.steps.filter((s) => {
    if (s.status !== "pending") return false;
    const deps = s.wait_for ?? (s.index > 1 ? [s.index - 1] : []);
    return deps.every((d) => isTerminal(plan.step(d).status)); // done or skipped
  });
}

Esto no se ejecuta con un temporizador, sino en respuesta a eventos. Cada vez que termina un paso —un subagente devuelve un resultado o Commander concluye un turno de síntesis—, el ejecutor reconcilia el estado: registra el resultado del paso que acaba de terminar, vuelve a buscar qué pasos han quedado listos como consecuencia y los asigna. La orquestación consiste en este bucle de reconciliación, que se repite hasta que no queda ningún paso.

La asignación pasa por el mismo chat, no por un canal secundario

Esta es una decisión que mantiene la transparencia del sistema: asignar un paso no utiliza un canal RPC oculto. Publica un mensaje en la misma conversación de grupo que está viendo el usuario, enviado por el agente principal, con una mención @ al subagente. Para el subagente, recibir una asignación es indistinguible de que alguien se dirija a él en el chat: simplemente ejecuta su turno habitual. No hay una segunda ruta de ejecución que deba mantenerse sincronizada con la primera.

El responsable puede ser de tres tipos, y cada uno recibe la asignación de forma un poco distinta:

  • Un subagente: el caso habitual. El ejecutor resuelve el nombre del agente a su identificador y publica @<agent> <rendered input> en nombre de Commander. El subagente lo recibe y ejecuta un turno completo de agente.
  • El usuario: cuando un paso realmente necesita información humana, se convierte en un formulario y pausa el plan (más detalles a continuación). Ningún paso posterior avanza hasta que el usuario responde.
  • El propio Commander: para pasos de síntesis o decisión ("lee todo lo anterior y escribe el resumen"). Se trata de una activación privada que no publica un mensaje redundante visible para el usuario; el agente principal simplemente ejecuta un turno con el contexto reunido a su disposición.

Pasar el contexto de un paso al siguiente

Un equipo solo es útil si el trabajo fluye entre sus integrantes. El mecanismo es la plantilla input. Cuando el agente principal escribe un paso, la entrada no es una cadena fija: puede hacer referencia a resultados anteriores, y el ejecutor sustituye esas referencias al asignar el paso:

// step 3.input, as written by the lead agent
"Using the findings below, draft the launch note.\n\n{{step_1.output_summary}}"

// what the sub-agent actually receives at dispatch time
"Using the findings below, draft the launch note.\n\n- Market is growing ~20% YoY; two incumbents…"

Fíjate en lo que se transmite: output_summary, un resumen breve de cada paso terminado, no su transcripción completa. Es una decisión sobre el uso de recursos, y sigue el mismo criterio que la compactación del contexto de la infraestructura de ejecución. Si cada paso posterior heredara el historial completo, token por token, de todo lo anterior, el contexto crecería desmesuradamente y el costo se dispararía tras solo unos pocos traspasos. Los resúmenes mantienen bajo el costo de cada traspaso y centran a cada subagente en lo que realmente necesita de los pasos anteriores, en lugar de obligarlo a revisar cómo llegaron a sus resultados. El mensaje inicial del usuario y los archivos adjuntos también se transmiten, de modo que un paso situado tres traspasos más adelante en la cadena sigue conociendo la solicitud original.

En serie de forma predeterminada, y por qué

Podrías esperar que, cuando varios pasos quedan listos a la vez —por ejemplo, dos ramas de un rombo—, el orquestador los lance todos en paralelo. Podría hacerlo; sin embargo, hoy asigna un paso listo a la vez, el primero según su índice, y deja que el resto espere a la siguiente reconciliación. Ejecutar a todo un equipo estrictamente de uno en uno es una decisión deliberada y conservadora, y conviene explicar con franqueza por qué.

La razón es garantizar la corrección en condiciones de concurrencia. Imagina que dos subagentes terminan casi al mismo tiempo. Ambas finalizaciones desencadenan una reconciliación; ambas reconciliaciones leen el plan; ambas ven el mismo paso posterior todavía en pending, y ambas lo asignan. Ahora el mismo paso se ejecuta dos veces. Para que eso sea imposible, cada ciclo de lectura, modificación y asignación de una conversación determinada se serializa mediante un bloqueo por conversación:

// all state-changing paths for one conversation run under one mutex
planLock(uid, cid).runExclusive(async () => {
  const plan = await readPlan(uid, cid);
  applyOutcomeOfFinishedStep(plan);   // mark done / failed / skipped
  await dispatchReady(plan);          // dispatch the next ready step
});

El bloqueo garantiza que "registrar lo que terminó" y "decidir qué sigue" ocurran como una única unidad indivisible, por lo que un paso posterior nunca puede asignarse dos veces. Con ese bloqueo, asignar los pasos de uno en uno es la solución más sencilla cuya corrección resulta evidente. La ejecución verdaderamente paralela de varias ramas es una ampliación viable sobre esta base, pero la base es un ejecutor serializado y libre de condiciones de carrera, y ese orden de prioridades —primero correcto, después rápido— es lo esencial.

Cuando un paso sale mal

Al ejecutarse en una máquina real contra API de modelos externos, los fallos son habituales, y el orquestador los clasifica en unos pocos casos en lugar de tratar todos los errores por igual.

Primero determina si el fallo fue meramente transitorio: una conexión interrumpida, un límite de solicitudes o un incidente momentáneo. Si es así, y el paso aún no ha agotado un pequeño cupo de reintentos, se devuelve discretamente a pending para que la siguiente reconciliación vuelva a asignarlo. (Esto se sitúa por encima de los reintentos internos de la infraestructura de ejecución; el plan solo vuelve a asignar el paso cuando se agotan los intentos del propio agente, con un límite estricto para que un paso realmente defectuoso no entre en un bucle infinito.)

Si el fallo es real, la política on_failure declarada en el paso decide qué hace el equipo a continuación:

  • abort_plan: este paso era tan importante que ningún paso posterior tiene sentido sin él. Se marca como fallido y se propaga el efecto: todos los pasos aún pendientes se marcan como skipped. El plan se detiene de forma ordenada en lugar de continuar sin una base necesaria.
  • continue: este paso era opcional. Se marca como skipped y se permite que los pasos posteriores avancen como si simplemente no hubiera producido nada.
  • ask_commander (la opción predeterminada): ni abortar ni continuar a ciegas. Se marca como fallido y se activa al agente principal para que examine lo ocurrido y decida: reintentar de otra forma, buscar una alternativa o detenerse y preguntar al usuario.

Hay un sexto estado que merece destacarse: blocked. Un subagente puede darse cuenta, a mitad de su paso, de que necesita algo que solo el usuario puede aportar, y mostrar un formulario o una pregunta. El paso no falla: pasa a blocked, y todo el plan se pausa. En cuanto el usuario responde, el ejecutor reconcilia el estado y el equipo retoma el trabajo exactamente donde lo dejó. Un plan bloqueado es un plan en pausa, no un plan averiado.

Cada paso es una ejecución completa de un agente

Conviene volver al artículo anterior para completar la explicación. Cuando el orquestador asigna un paso a un subagente, ese subagente no ejecuta una rutina simplificada: ejecuta el bucle completo de la infraestructura de ejecución: su propio bucle de ejecución con transmisión continua, sus propias llamadas a herramientas, su propia ventana de contexto con compactación y su propia sesión resistente a fallos. La orquestación se sitúa claramente por encima del entorno de ejecución de un solo agente; nunca interviene en su interior. El agente principal decide la estructura del trabajo y el orden de los traspasos; cada subagente, una vez que recibe su parte, es un agente completo por derecho propio.

Esa organización en capas es lo que hace que los dos artículos se complementen. La infraestructura de ejecución hace que un agente sea confiable para una tarea. El orquestador combina varios agentes confiables en un equipo capaz de abordar una tarea demasiado grande o demasiado variada para cualquiera de ellos por separado.

Algunas decisiones importantes

El orquestador controla el estado de ejecución; el modelo, la intención. El modelo propone el plan; solo el entorno de ejecución marca los pasos como completados, fallidos u omitidos, y solo en respuesta a algo que realmente ocurre. Este único límite es lo que mantiene el plan como un reflejo fiel de la realidad, en lugar de una conjetura optimista del modelo.

Asignar a través de la misma conversación, no de un canal secundario. Un paso asignado es simplemente un mensaje del agente principal a un subagente. Una sola ruta de ejecución, nada oculto que pueda desincronizarse, y el usuario puede ver al equipo trabajar en el mismo hilo que ya está leyendo.

Resúmenes, no transcripciones, entre pasos. Cada traspaso lleva un breve resumen de los resultados anteriores. Esto mantiene razonable el consumo de contexto en cadenas largas y centra a cada subagente en lo que necesita, no en cómo llegó el anterior a su resultado.

Corrección antes que paralelismo. Un bloqueo por conversación serializa cada ciclo de lectura, modificación y asignación, y los pasos se asignan de uno en uno. La versión estrictamente en serie está claramente libre de condiciones de carrera que causen asignaciones duplicadas; la ejecución paralela de varias ramas es una optimización que se añade sobre una base que ya es correcta.

Para terminar

La capa de orquestación de Orkas no tiene ningún algoritmo exótico en su núcleo. Su valor reside en unos pocos límites que se mantienen firmes: un plan que es un grafo de dependencias en lugar de un guion; un estado de ejecución controlado por el entorno de ejecución en lugar del modelo; una asignación que fluye por el mismo chat que observa el usuario; un contexto que pasa entre los pasos en forma de resúmenes; y un ejecutor que antepone la corrección a la concurrencia. Cada uno es sencillo por separado. Juntos convierten un solo agente confiable en un equipo que divide el trabajo, transmite resultados y se recupera cuando una parte sale mal.

Si te interesa la capa que está debajo de esta, lee cómo se diseña un agente para que se ejecute de forma confiable. Si te interesa la capa que permite que cada agente mejore con el uso, lee cómo aprenden los agentes de Orkas de su propio trabajo. Y si prefieres dirigir esta capa en vez de construirla, Orkas la ofrece como orquestación de agentes de IA de código abierto que se ejecuta en tu propia máquina.