Agentes Harness, Loop y Graph: Tres capas detrás de los agentes de IA fiables
Por qué los agentes poco fiables suelen carecer de un elemento clave: el arnés alrededor del modelo, un bucle basado en evidencias y un grafo explícito, junto con patrones de código y una lista de verificación para la depuración.
Fuentes: Harness, Loop, and Graph: What I Learned Building the Same AI Agent Three Different Ways
Nombres que parecen familiares pero no se entienden
Las frases “agent harness engineering” y “loop engineering” aparecen con tanta frecuencia en publicaciones sociales y artículos de blogs que dan la impresión de ser comprensibles. La familiaridad es engañosa, y un pequeño fallo suele revelarlo.
Imagínese un agente de investigación cuya tarea es buscar artículos de arXiv, resumirlos y elaborar un resumen semanal. Tres pruebas funcionan bien. Luego, una llamada a una API con límite de velocidad falla sin dejar rastro; el código no se da cuenta de nada, y el agente comienza a inventar resúmenes de artículos que nunca abrió. El modelo no tiene la culpa, al igual que la instrucción dada. Lo que falta es alguna forma en que el código circundante pueda distinguir entre un artículo que realmente se leyó y una fabricación verosímil. Esa brecha existe, tiene un nombre, y los ajustes en las instrucciones no la solucionan.
Crear el mismo agente tres veces de forma separada hace que las diferencias sean concretas: un proceso ajustado para el entorno, otro para los bucles de retroalimentación y un tercero para el control explícito del flujo de trabajo. Este campo tiene solo unos dieciocho meses de existencia en su forma actual, por lo que la denominación aún es objeto de debate. Sin embargo, las distinciones subyacentes son reales y se corresponden con decisiones de ingeniería reales. Posteriormente, un agente defectuoso debería indicar en qué de las tres capas se debe reparar primero.
Por sí solo, un modelo de lenguaje simplemente traduce texto a texto. La memoria entre llamadas solo existe si se proporciona. El acceso a archivos, los comandos de shell y las llamadas a API requieren infraestructura que alguien debe construir, y el modelo no puede verificar sus propias respuestas sin una verificación externa. Quienes desarrollan agentes en 2024 se dieron cuenta de esto cuando una demostración sin problemas fallaba ante la primera acción inesperada del usuario.
La industria ha respondido dejando de tratar “el agente” como un único elemento. Se trata de tres problemas de ingeniería separables que se superponen sobre el modelo.
LAYER QUESTION IT ANSWERS FAILS AS
----------------------------------------------------------------------
Harness What can the agent see and touch? Missing tools,
(environment, tools, memory, lost state, blown
permissions, execution limits) budgets, leaks
----------------------------------------------------------------------
Loop How does one attempt turn into a Confident wrong
correct one? (retry, verify, stop) answers, infinite
spinning, silent
failure
----------------------------------------------------------------------
Graph What is the actual shape of the work? Wrong step order,
(sequence, branches, parallel paths, no recovery path,
human checkpoints) no visibility
----------------------------------------------------------------------
Es más fácil comprender cada capa al crear su versión más pequeña, romperla y luego leer los documentos de investigación y del framework para entender por qué falló.
El arnés: lo que el agente puede ver y tocar
La definición más clara aparece en una nota técnica de LangChain que reduce la idea a una ecuación: agente igual a modelo más arnés. El modelo proporciona el razonamiento. El arnés suministra todo lo que ese razonamiento necesita para interactuar con el mundo real, es decir, herramientas, memoria, contexto, permisos y las restricciones que impiden que haga algo costoso o peligroso.
Un modelo mental común y inicial de “proporcionar herramientas a un agente” consiste en escribir unas pocas funciones en Python y describirlas en el prompt del sistema. Eso está bien para una demostración, pero no funciona en tareas que superen una ventana de contexto, ni en llamadas a herramientas que fallen a las 2 a.m. sin que nadie las supervise. En la práctica, este enfoque acaba teniendo seis responsabilidades distintas.
- La inyección de contexto determina qué ve el modelo antes de cada paso de razonamiento: el mensaje del usuario, pero también documentos recuperados, conversaciones anteriores, el estado del proyecto y las políticas organizacionales. Es fácil sobrecorregir; incluir todo un árbol de archivos y el historial de git en cada llamada empeora la precisión, ya que el modelo se ve abrumado por tokens irrelevantes, y un contexto más reducido y seleccionado suele ser mejor que uno más grande e indiscriminado.
- Las acciones convierten una decisión en un efecto en el mundo, ya sea una solicitud API, un comando de shell, una escritura en una base de datos o un clic en el navegador. Aquí interviene el Protocolo de Contexto del Modelo, el cual ha experimentado muchos cambios este año. Comenzó a finales de 2024 como una forma estándar para que los agentes encontraran e invocaran herramientas, evitando a los equipos la necesidad de crear integraciones personalizadas para cada API. Para la especificación de julio de 2026 ya contaba con un núcleo sin estado que funcionaba detrás de balanceadores de carga normales sin sesiones persistentes, una extensión Tasks de AWS para tareas que superan el alcance de una sola solicitud, y una autorización más estricta basada en OAuth y OpenID Connect. La fuente indica más de diez mil servidores en producción públicos y descargas de SDK superiores a 97 millones al mes. Hoy en día, construir una solución sobre una capa de herramientas que no sea MCP o similar requiere una justificación sólida.
- La persistencia se refiere a todo aquello que debe sobrevivir a un reinicio, una caída o el cierre de una ventana de contexto completa, como puntos de control, estado de sesión, memoria vectorial e historial de Git. El primer agente de investigación no conservaba nada de esto, por lo que la interrupción de la conexión eliminaba cuarenta minutos de progreso. Escribir un pequeño punto de control en JSON después de cada subtarea completada contribuyó más a la fiabilidad que cualquier modificación en las instrucciones.
- El control de ejecución agrupa reglas de intentos repetidos, tiempos de espera, presupuestos de tokens y gastos, enrutamiento de modelos, límites para los subagentes generados y los pasos en los que una persona debe dar su aprobación. Sin él, un agente atrapado en un ciclo de intentos fallidos durante toda la noche puede hacer que un trabajo se alargue indefinidamente.
- La seguridad y la gobernanza abarcan el acceso con mínimo privilegio a las herramientas, el manejo de secretos, el aislamiento en entornos sandbox, los registros de auditoría y las listas de permisos. El código escrito por el modelo siempre debe ejecutarse en un entorno sandbox. Se capturó a un agente de programación a punto de ejecutar
rm -rfen una carpeta que erróneamente consideró como un artefacto de compilación. No hubo mala intención, solo un error combinado con permisos para causar daños. - La observabilidad se refiere a los rastros, los registros de llamadas a herramientas, el uso de tokens, la latencia y los resultados de evaluación. Parece algo aburrido hasta que un agente comienza a comportarse de manera extraña en producción y no hay registro de lo que realmente hizo en comparación con lo que se suponía.
A continuación se muestra un framework mínimo pero funcional en Python. Es impulsado por un modelo local de Ollama, por lo que no se requiere clave API, y cambiar a un modelo alojado solo implica una línea de código.
import json
import subprocess
import requests
from datetime import datetime
OLLAMA_URL = "http://localhost:11434/api/chat"
MODEL = "llama3.1"
# --- Action surface: the only two things this agent is allowed to do ---
def read_file(path: str) -> str:
with open(path, "r") as f:
return f.read()[:4000] # cap it, don't blow the context window
def run_tests() -> str:
result = subprocess.run(
["pytest", "-q"], capture_output=True, text=True, timeout=60
)
return result.stdout[-2000:] + result.stderr[-2000:]
TOOLS = {"read_file": read_file, "run_tests": run_tests}
TOOL_SCHEMA = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a file from the project",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
},
{
"type": "function",
"function": {
"name": "run_tests",
"description": "Run the project's pytest suite",
"parameters": {"type": "object", "properties": {}},
},
},
]
# --- Persistence: cheap but real ---
def checkpoint(state: dict, path: str = "checkpoint.json"):
with open(path, "w") as f:
json.dump({**state, "saved_at": datetime.utcnow().isoformat()}, f)
# --- Execution control: hard ceilings, not suggestions ---
MAX_TOOL_CALLS = 8
def harness_run(user_task: str):
messages = [{"role": "user", "content": user_task}]
calls_made = 0
while calls_made < MAX_TOOL_CALLS:
response = requests.post(
OLLAMA_URL,
json={"model": MODEL, "messages": messages, "tools": TOOL_SCHEMA, "stream": False},
).json()
msg = response["message"]
messages.append(msg)
if not msg.get("tool_calls"):
return msg["content"] # model is done, no more tools needed
for call in msg["tool_calls"]:
name = call["function"]["name"]
args = call["function"].get("arguments", {})
if name not in TOOLS:
result = f"error: tool {name} is not permitted"
else:
result = TOOLS[name](**args)
messages.append({"role": "tool", "content": str(result)})
calls_made += 1
checkpoint({"messages": messages, "calls_made": calls_made})
return "stopped: hit the tool call ceiling before finishing"
Nada aquí es sofisticado, y esa es la lección: el harness funciona como un registro cuidadoso alrededor de un modelo que no lleva registros. Para un modelo alojado, reemplace el bloque requests.post por una llamada al SDK de OpenAI o Anthropic. El esquema de la herramienta y el sistema de guardado de puntos de control permanecen sin cambios, ya que no dependen de quién responde.
Las quejas de que el modelo se ha vuelto más tonto casi siempre se deben al harness. Puede que la herramienta haya devuelto datos obsoletos, el contexto esté saturado de historial irrelevante, o las permisos sean demasiado amplios o demasiado restrictivos, lo que hace que el agente intente hacer algo para lo que nunca tuvo autorización. Revise el harness antes de editar los prompts o cambiar de modelo, porque allí suele estar el problema.
El ciclo: cómo un intento se convierte en uno bueno
Si el arnés decide qué puede tocar el agente, el bucle decide cómo pasar de un primer intento a un buen resultado. Es la capa que más a menudo se subestima, ya que “simplemente déjelo intentarlo de nuevo” suena trivial hasta que el agente realmente lo hace.
El fallo clásico es un bucle autocorregible que, después de que el modelo responde, le pregunta si está seguro de que la respuesta es correcta. La respuesta es siempre sí. Un modelo que califica su propio trabajo sin ninguna señal externa casi siempre se considera exitoso, porque no hay nada contra lo que pueda fallar. Este es un problema bien documentado y no un error aislado: un bucle sin evidencia contra la cual fallar siempre creerá que ha tenido éxito.
La solución consiste en dejar de pedirle al modelo que se evalúe por sí mismo y asignarle a el bucle un árbitro externo: un conjunto de pruebas con resultados de aprobado o rechazado, un validador de esquemas, un compilador, una regla de linting, o una segunda búsqueda independiente que respalde o contradiga la primera respuesta. Los bucles basados en evidencias observan el estado del mundo, mientras que los basados en confianza se centran en cómo se siente el modelo.
La técnica de Ralph Wiggum y por qué funciona un bucle simple
Un patrón llamado “Ralph Wiggum” ganó popularidad después de que el ingeniero Geoffrey Huntley lo describiera en 2025. En esencia, es sorprendentemente sencillo: un bucle de bash envía repetidamente el mismo prompt a un agente de programación hasta que la tarea se completa, y el progreso se registra en archivos y commits de git en lugar de en el recuerdo del modelo del chat.
#!/bin/bash
# ralph.sh - crude but surprisingly effective
while true; do
claude -p "$(cat PROMPT.md)" --dangerously-skip-permissions
if git diff --quiet HEAD~1 HEAD -- STATUS.md && grep -q "DONE" STATUS.md; then
echo "Task complete."
break
fi
sleep 5
done
Parece que no debería funcionar, y tras una pequeña refactorización resulta que su crudeza es precisamente su ventaja. Al descartar el contexto conversacional en cada paso e imponer el progreso en los archivos de historial y estado de Git, se evita la degradación del contexto, y se impide que el agente se convenza de que ya ha terminado algo que en realidad no ha hecho. No es elegante. Es una solución basada en evidencias por casualidad, ya que Git y el sistema de archivos no mienten para halagar al modelo.
Aquí hay un pequeño bucle basado en evidencias del mismo tipo, nuevamente ejecutable localmente contra Ollama, que considera que una prueba fallida es el único significado aceptable de “todavía no está hecho”.
def evidence_loop(task_description: str, max_attempts: int = 5):
attempt = 0
history = []
while attempt < max_attempts:
attempt += 1
code = generate_patch(task_description, history) # calls the model
apply_patch(code)
test_output = run_tests() # external evidence
passed = "failed" not in test_output.lower()
history.append({
"attempt": attempt,
"patch": code,
"test_output": test_output,
"passed": passed,
})
if passed:
return {"status": "success", "attempts": attempt}
# feed the *actual failure*, not a vague "try again"
task_description = (
f"{task_description}\n\nPrevious attempt failed with:\n{test_output}\n"
"Fix the specific failure above."
)
return {"status": "gave_up", "attempts": attempt, "history": history}
Mire la última línea antes del retorno, porque es la que contiene la idea clave. El bucle no dice “intente con más fuerza”. En su lugar, proporciona al modelo el fallo real del test, ya que son las pruebas externas concretas las que hacen que el segundo intento sea mejor que el primero, y no un nuevo intento aleatorio. En la mayoría de los agentes, el mayor aumento en la fiabilidad se logra al reemplazar la pregunta “¿Esto parece correcto?” por una verificación externa real, sea del tipo que sea y que se adapte a la tarea.
También es esencial saber cuándo detenerse. Un bucle sin límite estricto en cuanto a intentos, costos o tiempo real no constituye un agente sólido; es más bien un error listo para causar problemas.
El grafo: cuando la tarea tiene ramas reales
Un arnés proporciona el soporte para el cuerpo del agente y un bucle le permite mejorar intento tras intento, pero ninguno de los dos describe una tarea con forma de árbol: hacer esto, luego elegir una de tres acciones según el resultado, esperar la aprobación de un humano y, finalmente, ejecutar dos verificaciones en paralelo y combinarlas.
Eso es ingeniería de grafos. Suele ser el aspecto al que la gente se resiste, ya que un bucle parece suficiente durante mucho tiempo, pero deja de serlo cuando el trabajo no consiste en una tarea continua hacia una sola prueba de aprobación. Los bucles son buenos solo para una cosa: intentar, verificar y revisar hasta que se cumpla una condición. Son inadecuados cuando el siguiente paso depende de cuál de varios resultados ocurrió, cuando dos subtareas pueden realizarse simultáneamente, o cuando alguien debe aprobarlo antes de continuar. Incluir todo eso en una sola instrucción y esperar que el modelo lo gestione funcionó para algunos equipos, pero es frágil. Una vez que aparecen las ramificaciones, el flujo de control debe estar en una estructura explícita, no en lo que el modelo improvise en cada turno.
La ingeniería de grafos hace que el flujo de trabajo sea explícito. Los nodos representan pasos como una llamada a un agente, una llamada a una herramienta, un validador o un punto de control humano, mientras que las aristas son las condiciones que permiten pasar la ejecución de un nodo a otro. Al observar el grafo se puede ver con exactitud qué ruta siguió una ejecución, lo cual es de gran importancia cuando ocurre un incidente en producción a medianoche y es necesario saber qué realmente sucedió en lugar de suponerlo.
LangGraph es la opción habitual en este caso. Representa un flujo de trabajo como un grafo dirigido, transfiere estados tipados de nodo en nodo, utiliza aristas condicionales para crear ramificaciones y cuenta con funcionalidades de puntos de control integradas, lo que permite que una ejecución se detenga, reinicie o vuelva a un estado anterior.
from langgraph.graph import StateGraph, END
from typing import TypedDict
class AgentState(TypedDict):
task: str
draft: str
review_passed: bool
attempts: int
def draft_node(state: AgentState) -> AgentState:
state["draft"] = generate_draft(state["task"])
return state
def review_node(state: AgentState) -> AgentState:
state["review_passed"] = run_validator(state["draft"])
state["attempts"] += 1
return state
def needs_human(state: AgentState) -> str:
if state["review_passed"]:
return "approved"
if state["attempts"] >= 3:
return "escalate" # branch: give up automating, ask a human
return "retry"
graph = StateGraph(AgentState)
graph.add_node("draft", draft_node)
graph.add_node("review", review_node)
graph.set_entry_point("draft")
graph.add_edge("draft", "review")
graph.add_conditional_edges(
"review",
needs_human,
{"approved": END, "retry": "draft", "escalate": "human_approval"},
)
Un bucle simple tiene dificultades para explicar con claridad qué hace esto: una verificación que genera tres resultados (finalizado, repetir, detenerse y consultar a una persona), cada uno llevando a un destino diferente. El camino de escalada es importante; marca la diferencia entre un agente que se rinde en silencio tras el tercer fallo y uno que proporciona al humano todo el historial de los intentos realizados.
AutoGen de Microsoft añadió algo similar este año, llamado GraphFlow, por la misma razón. Varios agentes o flujos de trabajo ramificados hacen que un bucle implícito sea difícil de seguir, mientras que un grafo puede inspeccionarse directamente.
Para determinar dónde brilla realmente cada framework, en lugar de confiar en las páginas de marketing, se probaron cuatro en un único proyecto pequeño: un pipeline de revisión de documentos que incluía una rama de validación y una puerta de aprobación humana.
FRAMEWORK ORCHESTRATION MODEL STRONGEST AT STATE / PERSISTENCE
--------------------------------------------------------------------------------------------
LangGraph Directed graph, typed Complex branching, Built-in checkpointing,
state, conditional edges long-running workflows, time-travel debugging
human-in-the-loop
--------------------------------------------------------------------------------------------
CrewAI Role-based "crew" of Fast prototyping, Sequential task output
agents, sequential or multi-agent brainstorm passing, lighter state
hierarchical process style collaboration model
--------------------------------------------------------------------------------------------
OpenAI Agents SDK Explicit agent handoffs Minimal-friction start Ephemeral context
inside the OpenAI variables by default
ecosystem
--------------------------------------------------------------------------------------------
AutoGen / GraphFlow Multi-agent conversation Agent-to-agent Conversation history
plus optional graph layer negotiation, graph- plus graph state
for explicit control based orchestration
--------------------------------------------------------------------------------------------
La conclusión es práctica. Si el flujo de trabajo cabe en una página como una lista plana de pasos, no se necesita un marco de grafos, y un bucle bien estructurado dentro de un mecanismo sólido cumple la función con menos código que mantener. Una vez que se dibuja el flujo de trabajo y este tiene ramificaciones, un marco de grafos comienza a valerse por sí mismo de inmediato, principalmente al ahorrar tiempo en la depuración, y no por algo que haga en tiempo de ejecución.
Cómo se superponen las tres capas
Estos no son enfoques rivales. Se superponen verticalmente, y así es como se ve la estructura una vez que se ha utilizado las tres.
+--------------------------------------------------------------+
| GRAPH - decides which node runs next, where branches |
| are, where a human has to approve |
+--------------------------------------------------------------+
| LOOP - inside a given node, drives attempt -> verify -> |
| revise until the evidence says stop |
+--------------------------------------------------------------+
| HARNESS - underneath both, provides the tools, memory, |
| permissions, and limits that make any of it |
| possible to execute safely |
+--------------------------------------------------------------+
| MODEL - the reasoning engine none of the above exists |
| without |
+--------------------------------------------------------------+
Dentro de un flujo de trabajo de LangGraph, un nodo de borrador podría ejecutar un bucle basado en evidencias varias veces antes de pasar el control de vuelta al grafo. Ese bucle interno solo funciona porque la estructura subyacente permite que el modelo realice pruebas, lea archivos y recuerde sus intentos anteriores. Si se elimina cualquier capa, el resto deja de funcionar, por lo que hacer que un “agente” monolítico sea más complejo siempre llegaría a un punto de estancamiento.
Pensar en tres capas hace que la depuración sea notablemente más rápida. Esta es la lista de verificación que se debe seguir, en orden, antes de modificar un prompt.
SYMPTOM LIKELY LAYER TO CHECK FIRST
----------------------------------------------------------------------
Agent doesn't know something it should Harness -> context injection
Agent "can't" do something it should be Harness -> action surface /
able to do permissions
Agent forgets earlier progress after a Harness -> persistence
restart or long run
Costs or run time spiral unexpectedly Harness -> execution control
Agent confidently reports success on Loop -> swap confidence checks
work that's actually wrong for external evidence
Agent retries the same mistake Loop -> is it seeing the actual
repeatedly without improving failure, or just "try again"?
Agent takes the wrong path when a task Graph -> is the branching logic
has more than one possible outcome explicit, or implicit in a
single prompt?
No human ever gets a chance to catch a Graph -> is there an approval
bad decision before it ships node, or is it all automatic?
Una tabla como esta habría evitado pasar todo un fin de semana culpando al prompt por algo que en realidad era la falta de un punto de control.
Preguntas abiertas
Algunas cuestiones siguen sin resolverse, y sería incorrecto concluir con más certeza de la que permiten las pruebas.
En primer lugar, el vocabulario sigue evolucionando. La “ingeniería de harness” apenas existía a gran escala antes de este año, y dentro de otro año podría integrarse en la categoría más amplia de “ingeniería de agentes”, donde “harness”, “loop” y “graph” pasarían a ser subcategorías y no disciplinas en las que las personas se especialicen.
En segundo lugar, el enfoque de Ralph Wiggum parece ligeramente defectuoso incluso cuando funciona. Darle a un modelo una sola instrucción en un bucle repetitivo y depender del historial de Git como su única memoria se asemeja a un parche aplicado sobre un arnés que carece de verdadera persistencia. Funciona bien para refactores pequeños y de alcance limitado, pero no se debe confiar en él para tareas que involucren ramas reales o consecuencias significativas.
En tercer lugar, los frameworks de grafos podrían seguir cambiando. La función de guardado de estados de LangGraph es excelente, pero los equipos adoptan un framework de grafos completo para un flujo de tres pasos que nunca se bifurca, simplemente porque está de moda. Un flujo de trabajo que es una línea recta puede seguir siéndolo.
Hay una afirmación que resiste todo esto: un agente poco fiable casi nunca se soluciona con una instrucción mejor. La falla se encuentra casi siempre en uno de los tres niveles, y las preguntas anteriores ayudan a identificar cuál es.
Comenzar a desarrollar el agente de investigación hoy mismo significaría construir primero el sistema de soporte y tratarlo como una infraestructura básica y resistente, en lugar de algo secundario. Desde el primer día se implementaría un ciclo basado en evidencias en lugar de confiar en la autoevaluación del modelo, ya que ese cambio por sí solo corrige más errores que cualquier otra cosa. Un marco gráfico esperaría hasta que se pudiera representar el flujo de trabajo en papel con una división visible.
El modelo ha captado toda la atención durante tres años, pero la fiabilidad en producción proviene de la ingeniería que lo rodea. Ese trabajo es mucho menos llamativo y mucho más manejable que la afirmación de que el modelo no es lo suficientemente inteligente. La mayoría de las veces sí lo es. Simplemente no se le ha proporcionado el entorno, los comentarios o la información necesaria para demostrarlo.
Leer más
- Conceptos de LangGraph en lenguaje sencillo: estado, memoria y orquestación — Antes de escribir código: qué significan la orquestación, el tiempo de ejecución, los agentes con estado, la memoria a corto y largo plazo, los sistemas multiagente y la intervención humana en LangGraph.
- Construyendo un harness para DeepAgents: permisos, subagentes y aprobación humana — Una guía práctica para crear un planificador de diseño de sistemas con DeepAgents: servidores de archivos, reglas de permisos, subagentes, puntos de aprobación, habilidades y MCP, además de los casos en los que el juicio del modelo sigue fallando.