Orkas Orkas
Inicio Blog Arquitectura
Arquitectura

La capa que convierte un modelo en un producto: la ingeniería de Agent Harness de Orkas

Cómo Orkas convierte las llamadas a modelos en un entorno de ejecución fiable para agentes de escritorio: bucles de ejecución con transmisión en tiempo real, enrutamiento de herramientas, compactación de contexto, abstracción de proveedores, memoria y sesiones resistentes a fallos.

Cualquiera que haya lanzado un producto basado en agentes conoce la sensación: poner en marcha una demostración es rápido, pero convertirla en algo en lo que un usuario confíe a diario —algo que funcione todo el día en su propia máquina sin fallar— es difícil. Y lo difícil no es conectar el modelo. Es toda la capa que lo rodea.

Esa capa recibe varios nombres; aquí la llamaré infraestructura de ejecución del agente. Se sitúa entre el "gran modelo" y las "funciones del producto", y es el verdadero entorno de ejecución: convierte una sola solicitud del usuario en sucesivas rondas de conversación con el modelo, intercalando llamadas a herramientas, devolviendo resultados, compactando el contexto antes de que se desborde, reintentando ante problemas de red y recuperando la conversación incluso después de que el proceso falle. El modelo se encarga de pensar; la infraestructura de ejecución convierte ese pensamiento en una secuencia fiable de acciones.

Orkas es una aplicación de escritorio con agentes que se ejecuta en la propia máquina del usuario, y su infraestructura de ejecución reside íntegramente en el cliente. Este artículo explica cómo se construye esa infraestructura: cómo se divide en capas, cómo es el bucle de ejecución, cómo se abstraen las herramientas y los modelos, y cómo se gestionan la memoria y las sesiones. Los detalles del código se han depurado y generalizado, pero la estructura de ingeniería es real.

En pocas palabras La infraestructura de ejecución es lo que realmente instalas Todo lo descrito aquí se incluye en la aplicación de escritorio: la misma capa que ejecuta tus agentes de forma local, con el código fuente en GitHub.
Descargar Orkas — gratis

Las capas

Si descomponemos un producto basado en agentes, obtenemos aproximadamente estas capas, ordenadas de abajo hacia arriba:

┌─────────────────────────────────────────────────────────────────────────────┐
│  Funciones del producto (chat / habilidades / conectores / sincronización)  │
├─────────────────────────────────────────────────────────────────────────────┤
│  Infraestructura de ejecución del agente (bucle / herramientas / sesión)    │
├─────────────────────────────────────────────────────────────────────────────┤
│  Abstracción de proveedores (unifica varios proveedores de LLM)             │
├─────────────────────────────────────────────────────────────────────────────┤
│  Infraestructura (tipos / errores / registros / configuración)              │
└─────────────────────────────────────────────────────────────────────────────┘

Aquí hay una decisión de gran alcance incorporada desde el principio: toda la inferencia del modelo se realiza en el cliente. La aplicación de escritorio no es un cliente ligero: contiene la propia infraestructura de ejecución y llama al modelo directamente. El servidor solo gestiona las cuentas, la sincronización entre dispositivos y la facturación; no ejecuta el agente en absoluto. Esta decisión condicionó casi todo lo demás: las sesiones se guardan en el disco local, las herramientas operan directamente sobre el directorio de trabajo del usuario y los datos sensibles nunca salen de la máquina.

La propia infraestructura de ejecución se divide en varias piezas: el bucle de ejecución (ejecutor), la sesión, las herramientas, la capa Provider y la memoria. Veámoslas una por una.

El bucle de ejecución: un generador de flujo continuo

El núcleo de la infraestructura de ejecución es el ejecutor. En una frase, su función es: hablar con el modelo una y otra vez hasta que este diga "he terminado".

Está implementado como un generador asíncrono, y esa elección importa. Una sola ejecución del agente es mucho más que "enviar una solicitud y esperar un resultado". Entre ambas cosas sucede mucho: el modelo emite tokens, quiere llamar a una herramienta, la herramienta termina, el contexto crece lo suficiente como para activar la compactación, la red falla y hay que reintentar. Con funciones de retorno o simples promesas, es difícil exponer esos estados intermedios de forma clara a quien realiza la llamada. Con un generador, todos se convierten en un flujo de eventos emitidos mediante yield:

type AgentRunEvent =
  | { type: "text_delta"; text: string }              // model emitting tokens
  | { type: "tool_start"; name: string; input: unknown } // a tool starts executing
  | { type: "tool_end"; name: string; result: string }   // a tool finished
  | { type: "compaction"; tokensBefore: number; tokensAfter: number } // context compacted
  | { type: "retry"; attempt: number; reason: string }   // error, retrying
  | { type: "done"; result: AgentRunResult }             // terminal

La interfaz de usuario se suscribe a este flujo de eventos y muestra la salida del modelo y la ejecución de las herramientas en tiempo real. Internamente, el punto de entrada sin transmisión continua se limita a "consumir el flujo hasta el final y tomar el done final": ambos puntos de entrada comparten una única implementación, así que no hay una segunda ruta de código que pueda quedar desincronizada.

Qué ocurre dentro de un turno

Desglosado, un turno tiene aproximadamente este aspecto:

  1. Añadir el mensaje del usuario (posiblemente con imágenes) al historial de la sesión.
  2. Preparar las instrucciones del sistema, incorporando las herramientas disponibles en ese momento, el índice de habilidades y demás.
  3. Analizar la cadena que identifica el modelo y resolverla a un Provider y un ID de modelo concretos.
  4. Convertir todas las herramientas en definiciones que el modelo entienda y enviarlas junto con el historial.
  5. Consumir el flujo de respuesta del modelo, emitiendo texto token a token mediante yield mientras se recopilan las llamadas a herramientas que realice el modelo.
  6. Cuando termine el flujo, examinar el motivo de finalización del modelo:
  • Si es tool_use, el modelo quiere llamar a una herramienta: ejecutar las herramientas, volver al paso 5 y consultar de nuevo al modelo.
  • En caso contrario, el turno ha terminado: preparar el resultado, emitir yield done y retornar.

Aquí hay una condición invariable que debes mantener: cada llamada a una herramienta que haga el modelo debe ir seguida inmediatamente en el historial por el resultado correspondiente. La API del modelo exige estrictamente esta correspondencia: si se rompe, la siguiente solicitud dará un error o simplemente quedará bloqueada. Volveremos a esto al hablar de la autorreparación de las sesiones.

Cómo se devuelve el resultado de una llamada a herramienta

El modelo no ejecuta las herramientas por sí mismo; solo dice "quiero llamar a read_file con estos argumentos". Una vez que el ejecutor detecta esa intención:

for (const call of toolUseBlocks) {
  yield { type: "tool_start", name: call.name, input: call.input };

  const tool = this.tools.get(call.name);
  const ctx = { workingDir, signal, state: { sandboxEnv } };
  const result = await tool.execute(call.input, ctx);

  // append the result to the session as a tool-result message
  session.addToolResult(call.id, result);

  yield { type: "tool_end", name: call.name, result: result.content };
}

Las herramientas se ejecutan de forma secuencial, los resultados se escriben en el historial en el orden en que el modelo las declaró y, después, se vuelve a consultar al modelo con esos resultados. Al verlos, el modelo puede llamar a otra herramienta o dar su respuesta final. Este bucle de "consultar → llamar → responder → volver a consultar" es precisamente lo que permite a un agente completar tareas de varios pasos.

Hay un detalle que merece una mención aparte: algunas herramientas devuelven imágenes, como capturas de pantalla o imágenes generadas. Pero muchos modelos no aceptan imágenes en el canal de resultados de herramientas. Orkas lo resuelve colocando la imagen en un mensaje de usuario separado, situado después del resultado de la herramienta: el modelo primero lee "la herramienta devolvió este texto" y, en el turno inmediatamente siguiente, ve la imagen correspondiente. Es una pequeña solución de compromiso para sortear las diferencias de capacidades entre proveedores.

Qué hacer cuando el contexto está a punto de desbordarse

El obstáculo con el que más suelen encontrarse las tareas largas es la ventana de contexto. Orkas no espera a que se llene: establece un umbral del 60%. Después de cada ronda de herramientas, estima qué proporción de la ventana ocupan los tokens actuales y, en cuanto supera el 60%, activa la compactación de forma preventiva.

La compactación pide al modelo que resuma la conversación anterior y luego sustituye los mensajes antiguos por ese resumen, conservando solo el tramo más reciente. Parece sencillo, pero hay una trampa: después de la sustitución, el tramo conservado no debe comenzar con un "resultado de herramienta huérfano"; no puede haber un "resultado sin su llamada correspondiente", porque eso volvería a romper la condición de correspondencia. Por eso, la lógica de compactación se asegura de que el corte se produzca en un límite limpio.

Aquí hay una decisión más interesante que merece explicarse: ¿por qué usar el enfoque general de "resumir todo el bloque al llegar al 60%", en lugar de algo más detallado, como puntuar cada mensaje y recortar según su importancia, extraer información estructurada de los resultados de las herramientas o mantener un árbol de memoria por capas? Esos enfoques se ven muy bien en los artículos de investigación, pero decidimos deliberadamente no seguir ese camino por tres razones.

Primero, la caché. La caché de instrucciones del modelo funciona por prefijos: mientras el prefijo del historial no cambie, ese tramo se recupera de la caché, lo que ahorra dinero y reduce la latencia. La compactación detallada reescribe constantemente partes intermedias del historial, lo que rompe una y otra vez el prefijo almacenado en caché: cada modificación obliga a repetir una gran parte del procesamiento inicial. La estrategia de "dejarlo como está y compactar una sola vez al alcanzar el umbral" mantiene estable el prefijo durante la gran mayoría de los turnos, y solo esa compactación lo invalida. Es mucho más favorable para la caché.

Segundo, la complejidad. Esa condición de que "cada llamada a una herramienta debe tener su resultado correspondiente", en la que tanto insistimos: cuanto más detallado sea el recorte del historial, más probable es romperla en algún caso particular. Un resumen general solo tiene que proteger un punto de corte limpio; hay un orden de magnitud menos de lugares donde equivocarse. Una categoría menos de casos límite es una categoría menos de incidentes en producción.

Tercero, aprovechar las mejoras de los modelos. Las ventanas de contexto han crecido de forma constante en los últimos dos años, y los modelos manejan cada vez mejor los contextos largos. Dedicar esfuerzo hoy a un algoritmo de compactación elaborado equivale, en esencia, a combatir un problema que se está reduciendo: es probable que termines de ajustarlo justo cuando la siguiente generación duplique su ventana, y que esa complejidad pase a ser una mera carga. En cambio, delegar el resumen al propio modelo mejora automáticamente a medida que el modelo mejora: cuanto mejor identifique lo importante, mayor será la calidad del resumen, sin que cambiemos una sola línea. La complejidad que el modelo puede asumir por ti es complejidad que no deberías asumir tú.

La estimación de tokens esconde un problema fácil de pasar por alto: el chino. Si estimas el chino con criterios propios del inglés (aproximadamente un token por cada pocos caracteres), subestimarás mucho el recuento. Orkas pondera por separado los caracteres CJK en su estimación; de lo contrario, el umbral de una conversación íntegramente en chino se calcula mal y la compactación no se activa cuando debería.

Errores y reintentos

Al ejecutarse en la máquina del usuario y depender de una API de modelos externa, los errores son la norma, no la excepción. El ejecutor los clasifica en varias categorías y trata cada una de forma distinta:

  • Con posibilidad de reintento: límites de solicitudes, tiempos de espera agotados, conexiones interrumpidas, 5xx. Espera exponencial entre reintentos con una variación aleatoria y un máximo de 30 segundos; si se trata de un límite de solicitudes y el servidor envió retry-after, se respeta.
  • Sin posibilidad de reintento: casos como fallos de autenticación; ningún número de reintentos servirá, así que se devuelve un error de inmediato.
  • Especiales: desbordamiento del contexto. Primero se intenta compactar, después se reintenta una vez y solo se devuelve un error si eso también falla.

Hay una categoría más: "la propia herramienta falló". Esto no hace fracasar todo el turno: el fallo de una herramienta es, en sí mismo, información para el modelo, que, al ver que "ese comando dio un error", puede probar perfectamente otro enfoque. La infraestructura de ejecución distingue estos errores transitorios de herramientas de los fallos reales: no interrumpe el flujo ni los pierde; aparecen en las estadísticas posteriores. (Esos datos alimentan después el mecanismo de evolución autónoma, que será el tema del siguiente artículo).

La señal externa de cancelación (AbortSignal) se comprueba en todos los puntos clave. El usuario pulsa "detener" y el turno actual se detiene inmediatamente, sin iniciar nuevos reintentos.

Abstracción de herramientas: lo bastante sencilla para ampliarla

La interfaz de las herramientas es deliberadamente mínima:

interface AgentTool {
  readonly name: string;
  readonly description: string;          // shown to the model
  readonly inputSchema: Record<string, unknown>;  // JSON Schema to constrain inputs
  execute(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult>;
}

Una herramienta es simplemente "un nombre + una descripción para el modelo + un esquema de entrada + una función de ejecución". Las herramientas integradas —leer archivos, escribir archivos, ejecutar comandos de shell, buscar en la web y obtener contenido web— implementan esta interfaz. La capa de escritorio añade encima un conjunto de herramientas orientadas al entorno local (búsqueda en la base de conocimientos, generación de imágenes, llamadas a conectores externos), pero la interfaz es la misma.

La ventaja de una interfaz mínima es que al ejecutor le resulta irrelevante de dónde venga una herramienta: integrada, definida por el usuario o cargada desde una habilidad, todas son el mismo tipo de elemento, se registran en un único Map<string, AgentTool> y se convierten en definiciones que el modelo puede leer en cada turno.

Las herramientas con efectos secundarios, como los comandos de shell, pasan por un ejecutor aislado: tiempos de espera, límites de longitud de salida, una lista de comandos bloqueados y variables de entorno que se pasan por separado en lugar de modificar el entorno global del proceso. Esto último se propagaría a multitud de procesos secundarios y, en una arquitectura multiproceso como Electron, podría impedir fácilmente el inicio.

La capa Provider: unificar muchos modelos en una sola interfaz

Las preferencias de los usuarios en cuanto a modelos son muy variadas, y un producto no puede quedar atado a un único proveedor. Debajo de la infraestructura de ejecución, Orkas establece una abstracción Provider que unifica los modelos de distintos proveedores tras una sola interfaz:

interface LLMProvider {
  readonly id: string;
  complete(params: CompletionParams): Promise<CompletionResult>;
  stream(params: CompletionParams): AsyncIterable<StreamEvent>;
  validateAuth(): Promise<boolean>;
}

El ejecutor de la capa superior solo se comunica con esta interfaz; no sabe qué proveedor hay detrás. Un registro se encarga del enrutamiento según la cadena que identifica el modelo: el formato explícito provider/model se divide directamente; un nombre de modelo sin proveedor se asigna por su prefijo. Aquí también se gestiona la autenticación (una clave de API o un token OAuth), y los tokens OAuth vencidos se renuevan automáticamente.

Al unificar muchos modelos, el verdadero quebradero de cabeza no es completar texto: son los casos en que la semántica de los proveedores difiere. Dos ejemplos nos dieron problemas.

Uno es conservar los bloques de razonamiento entre proveedores. Los modelos de razonamiento emiten un tramo de contenido de "pensamiento"; algunos proveedores lo cifran y exigen que se les devuelva sin cambios, mientras que otros lo representan con un conjunto distinto de campos. Si un usuario cambia del proveedor A al proveedor B en mitad de una conversación, la firma de ese tramo de razonamiento en el historial deja de coincidir. La solución consiste en marcar cada mensaje del historial con "qué modelo lo produjo", para que la capa de transformación pueda decidir si lo conserva tal cual: si es el mismo modelo, se conserva; si es otro, se convierte a una representación más básica según las reglas.

El otro es la caché de instrucciones. Entre los turnos de una misma sesión, el prefijo se repite mucho, y almacenarlo en caché permite ahorrar una cantidad significativa de costo y latencia. La implementación pasa el ID de la sesión como clave de caché a los proveedores que lo admiten, respetando los límites de longitud de clave de cada proveedor (por ejemplo, truncándola o calculando un hash si es demasiado larga).

Todo esto es trabajo rutinario, pero es precisamente esta capa de trabajo rutinario la que permite al ejecutor de arriba actuar como si "solo existiera un tipo de modelo".

Memoria: dos mecanismos, cada uno con su función

La "memoria" en Orkas consiste en realidad en dos mecanismos paralelos que resuelven dos problemas completamente distintos. Uno es una base de conocimientos basada en recuperación, para el material voluminoso que "se consulta cuando hace falta". El otro es la memoria entre sesiones, para el pequeño conjunto de hechos clave que "siempre conviene tener presentes". Muchos productos mezclan ambos; mantenerlos separados aclara mucho las cosas.

Base de conocimientos: recuperación híbrida

El primer mecanismo se dirige a contenido voluminoso que solo resulta relevante de vez en cuando: documentos del usuario, notas anteriores y conocimientos del ámbito de trabajo. Se trata de una base de conocimientos local con recuperación vectorial y dos implementaciones de almacenamiento: una versión ligera que funciona solo en memoria (para pruebas y uso temporal) y una versión que persiste en una base de datos local (para producción, con indexación de texto completo y vectores).

Los datos siguen esta ruta:

documentos → dividir por límites de línea (con solapamiento) → doble indexación
                                                             ├─ índice de texto completo (palabras clave, sin costo de embeddings)
                                                             └─ índice vectorial (si se configura un modelo de embeddings)

Los fragmentos se cortan en los límites de línea, con un pequeño solapamiento entre ellos, para evitar dividir por la mitad una unidad completa de significado. La recuperación es híbrida: una búsqueda vectorial (proximidad semántica) y una búsqueda por palabras clave (coincidencias literales), cuyos resultados se combinan mediante RRF (fusión de rangos recíprocos):

score = Σ  1 / (k + rank_i)

Cuanto más alto aparece un resultado en una búsqueda, mayor es su contribución; al sumar ambas búsquedas, se respeta la relevancia semántica sin perder las coincidencias literales exactas. Los pesos de vectores y palabras clave son ajustables y, de forma predeterminada, favorecen la semántica. Tras la combinación, se eliminan los duplicados por "(documento, línea inicial)", conservando solo el mejor resultado de cada ubicación; después se descarta todo lo que queda por debajo de un umbral y se devuelven los K mejores resultados.

¿Por qué no depender solo de los vectores? Porque la recuperación vectorial suele fallar con nombres propios, símbolos de código y cadenas literales exactas: consultas que no tienen nada especial desde el punto de vista semántico, pero en las que la literalidad importa mucho. Por su parte, una búsqueda solo por palabras clave no puede detectar "el mismo significado con distintas palabras". Usar ambas es un equilibrio muy práctico entre calidad de recuperación y costo.

Memoria entre sesiones: tener presente al usuario

La base de conocimientos resuelve el problema de "demasiado material para retenerlo todo". Pero hay otra categoría de información, mínima en volumen, que debe tenerse presente en todo momento: quién es este usuario, qué prefiere y qué se acordó la última vez. Estos datos no deberían depender de la recuperación para "tener suerte y recordarlos": deberían estar presentes en cada turno.

Para ello, Orkas crea una capa independiente de memoria entre sesiones, dividida en dos partes según el contenido:

  • Perfil del usuario: hechos estables sobre la persona, como su función, preferencias, estilo de comunicación y tecnologías que utiliza.
  • Notas de hechos: hechos duraderos sobre el trabajo, como decisiones, hitos y convenciones del proyecto.

Ambas son pequeñas, cada una con un límite estricto de unos pocos miles de caracteres, lo que las obliga a conservar solo lo que es realmente útil a largo plazo. No pasan por un proceso de recuperación; se incorporan directamente como contenido fijo a las instrucciones del sistema al inicio de cada turno. Esto significa que el agente simplemente "sabe" esas cosas, sin tener que acordarse de consultarlas. Es exactamente el enfoque opuesto al de la base de conocimientos: la base de conocimientos es "consultar solo cuando haga falta y retirar después"; la memoria entre sesiones es "siempre presente, siempre visible".

Las escrituras pasan por una herramienta de memoria específica que el modelo llama cuando considera, en mitad de la conversación, que "vale la pena recordar esto a largo plazo". Permite añadir, sustituir subcadenas y eliminar. La descripción de la herramienta detalla claramente qué guardar y qué no: las correcciones y preferencias del usuario tienen la máxima prioridad; las decisiones y convenciones duraderas se guardan; en cambio, el estado transitorio de la tarea actual, la información puntual de depuración y cualquier dato fácil de volver a obtener no se guardan. La memoria sirve para "hechos duraderos sobre el usuario y el proyecto", no para "hasta dónde llegué esta vez".

Hay un detalle fácil de pasar por alto, pero bastante importante: se ejecuta un análisis de seguridad antes de cada escritura. Este contenido entra tal cual en las instrucciones del sistema y persiste entre sesiones durante mucho tiempo, por lo que constituye, en la práctica, una superficie de inyección duradera. Por eso, antes de escribir cualquier recuerdo en el disco, se buscan patrones sospechosos: expresiones clásicas de inyección de instrucciones ("ignora todas las instrucciones anteriores" y similares), comandos que intentan extraer claves y caracteres Unicode invisibles ocultos en el texto. Si se detecta una coincidencia, la escritura se rechaza por completo. Con la eliminación de duplicados y el recorte del contenido que supera el límite, esta capa de memoria sigue siendo útil sin convertirse en un riesgo.

Juntos, los dos mecanismos cubren ambos extremos: "voluminoso pero ocasional" y "pequeño pero constante". La base de conocimientos se ocupa del primero; la memoria entre sesiones, del segundo. Si a eso añadimos lo que el agente sabe sobre sí mismo (el tema del siguiente artículo), un agente de Orkas empieza con tres tipos de memoria a la vez: sobre el material, sobre el usuario y sobre sí mismo.

Sesiones: preparadas para fallar y repararse

Una sesión gestiona el historial de mensajes. La versión básica es simplemente un arreglo de mensajes en memoria con recorte y compactación del historial. Pero cualquier cosa que se ejecute en la máquina de un usuario debe asumir que puede terminar de forma abrupta en cualquier momento: el usuario cierra la aplicación, el sistema se reinicia o un tiempo de espera del supervisor termina el proceso. Por eso, en producción se utiliza una sesión persistente, escrita en un archivo JSONL local, con un mensaje por línea.

Hay dos estrategias de escritura: añadir un mensaje nuevo utiliza una operación de anexado atómica; cualquier operación que reescriba el archivo completo (compactación, vaciado) utiliza "escribir un archivo temporal + renombrado atómico". Así, aunque se corte la electricidad durante la escritura, nunca queda medio registro corrupto.

La pieza más interesante es la reparación de llamadas a herramientas huérfanas. Volvamos a la condición de correspondencia: el modelo hace una llamada a una herramienta, la infraestructura de ejecución la ejecuta y se escribe el resultado. Si se interrumpe cualquiera de esos tres pasos, queda en el disco un elemento huérfano: "una llamada sin resultado". Si la próxima vez se carga esa sesión y se envía al modelo tal cual, la API la rechazará o quedará bloqueada.

La lógica de reparación se ejecuta cada vez que se carga una sesión desde el disco y es idempotente:

  1. Examinar todos los mensajes del asistente y recopilar los ID de las llamadas a herramientas que contienen.
  2. Buscar más adelante los resultados de herramientas correspondientes.
  3. Para cada llamada que carezca de un resultado correspondiente, generar uno marcado como "interrumpido".
  4. Durante el proceso, alinear el orden de los resultados con el orden de declaración de las llamadas y descartar los resultados huérfanos que no tengan una llamada correspondiente.

Después de esta revisión, se garantiza que la sesión cumple el requisito de correspondencia de la API y puede enviarse con seguridad. El mecanismo parece poco llamativo, pero es la red de seguridad que evita que "la conversación de un usuario quede bloqueada permanentemente por un solo fallo".

Algunas decisiones que demostraron su importancia con el tiempo

Al reunir todas estas piezas, algunas decisiones resultan especialmente valiosas en retrospectiva.

Generadores como interfaz principal. Los modos con y sin transmisión continua comparten una única implementación, el estado intermedio se expone de forma natural y la interfaz de usuario puede mostrar tanto detalle como desee. Esto evitó toda una categoría de errores de incoherencia que habría creado el enfoque de "implementar primero sin transmisión continua y añadirla después".

Compactar al 60%, no cuando esté lleno. Deja margen para la propia compactación (que también requiere una llamada al modelo) y evita las prisas de última hora.

La condición de correspondencia se aplica en todas partes. Desde el punto de corte de la compactación hasta la escritura en disco y la reparación al cargar, cada lugar que modifica la sesión respeta la misma regla. Con una sola regla, ninguna parte necesita inventar su propia lógica de corrección.

Trabajo rutinario concentrado en la capa Provider. Todas las complicaciones entre proveedores —bloques de razonamiento, claves de caché, diferencias de capacidades— se resuelven en esta única capa, a cambio de mantener limpio el ejecutor que está encima. Si algún día se añade un nuevo proveedor de modelos, los cambios apenas se extienden fuera de ella.

Para terminar

La infraestructura de ejecución de Orkas no tiene ningún algoritmo deslumbrante. Su valor consiste en tomar el objetivo de "hacer que un agente funcione de forma fiable en un entorno real" y dividirlo en un conjunto de módulos con límites claros, cada uno responsable de una parte: el ejecutor se ocupa del bucle y los reintentos; las herramientas, de las capacidades; la capa Provider, de unificar muchos modelos; la memoria, de la recuperación; y la sesión, de la persistencia y la reparación. Ninguno es complejo por sí solo; solo juntos sostienen algo que la gente usa a diario.

Si hay algo que conviene llevarse de aquí, es esto: convierte el bucle de ejecución en un generador de flujo continuo y el estado intermedio será mucho más fácil de gestionar; una vez establecida una condición invariable central (como "las llamadas a herramientas deben tener su resultado correspondiente"), mantenla de forma coherente en la compactación, las escrituras en disco y la carga, sin excepciones; concentra el trabajo rutinario entre proveedores en una sola capa y mantenlo fuera de la lógica de negocio; y, lo más sencillo de todo, asume que tu proceso terminará de forma abrupta en el peor momento posible y escribe de antemano la reparación necesaria para ese momento.

El siguiente artículo profundiza en una parte más interesante de Orkas: cómo este agente aprende de su propio uso, transforma la experiencia en habilidades reutilizables y, poco a poco, se vuelve más útil.