Bucle principal del agente
Responsabilidad
run_conversation es el corazón de Hermes: recibe un mensaje del usuario, llama al modelo y a las herramientas de forma repetida hasta que se agota el presupuesto (budget) de iteración (iteration) o la tarea se completa. Coordina también la construcción del contexto (context), el despacho (dispatch) de herramientas (tool), la decisión de parada y el fallback ante fallos.
El archivo ocupa unas 5800 líneas con alta densidad lógica; esta página solo cubre la línea principal; los detalles quedan en subpáginas.
Archivos clave
entrada run_conversation:588-720— firma, inicialización y preparación previa al buclebucle while principal:721-760— condiciónapi_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0, incluye grace callsubbucle de reintentos de API:1227-1280—while retry_count < max_retriesbuild_turn_context— ensamblaje del contexto de cada turno (turn)prompt_builder— composición del system promptmoa_loop— ruta de agregación Mixture-of-Agentscontext_compressor— compresión (compression) del historial al alcanzar el umbralIterationBudget— núcleo del conteo de presupuesto
Flujo de datos
run_agent.py AIAgentes un reenviador fino; la inyección de dependencias real ocurre eninit_agent.- El llamador pasa
user_message;run_conversationprimero pasa porbuild_turn_contextpara ensamblar el contexto del turno (api_messages). - Entra al bucle
whileprincipal: comprobar interrupciones → decrementar presupuesto (iteration_budget.consume()) → construir/sanitizarapi_messages→ precompresión → vaciar texto/steer→ agregación MoA → entrar al subbucle de reintentos de API. - Tras la respuesta del modelo, valida y procesa
finish_reason; en caso de fallo, ejecutaagent._try_activate_fallback(). - Si se alcanza el umbral de compresión, llama a
context_compressorpara liberar espacio. - Al salir del bucle (presupuesto agotado / tarea completada / interrupción) devuelve el mensaje final, que la capa de gateway entrega (ver capa de gateway).
El aspecto real del bucle principal está en agent/conversation_loop.py:721-740:
while (api_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0) or agent._budget_grace_call:
agent._checkpoint_mgr.new_turn()
if agent._interrupt_requested:
interrupted = True
_turn_exit_reason = "interrupted_by_user"
break
api_call_count += 1
if agent._budget_grace_call:
agent._budget_grace_call = False
elif not agent.iteration_budget.consume():
_turn_exit_reason = "budget_exhausted"
breakEl or agent._budget_grace_call añadido a la condición es el punto de apoyo para «dar una oportunidad más cuando el presupuesto ya está a cero»: al entrar en el turno el flag se limpia de inmediato, así que «una oportunidad» es literalmente una.
Los reintentos de API no son recursivos, sino un while retry_count < max_retries interno. Aquí está el inicio de agent/conversation_loop.py:1227-1245:
while retry_count < max_retries:
if agent.provider == "nous":
try:
from agent.nous_rate_guard import nous_rate_limit_remaining
if nous_rate_limit_remaining() is not None and nous_rate_limit_remaining() > 0:
if agent._try_activate_fallback():
active_system_prompt = _sync_failover_system_message(
agent, api_messages, active_system_prompt)
retry_count = 0
continue
return {"final_response": "⏳ rate-limited, no fallback.",
"messages": messages, "completed": False, "failed": True}
except Exception:
pass # Never let rate guard break the agent loopTras cambiar de provider, retry_count = 0 reinicia desde un estado limpio. El except Exception: pass más externo es deliberado: el rate guard es una optimización; no debe, bajo ninguna circunstancia, romper el bucle principal.
Motivo de diseño
¿Por qué los reintentos son un subbucle y no recursión? La recursión en Python consume call stack (profundidad limitada), y el estado de cada fallo se reparte entre varios stack frames difíciles de limpiar a la vez. Con while retry_count < max_retries, todo el estado del reintento (retry_count, compression_attempts) son variables locales en el mismo frame; al cambiar de fallback, un único retry_count = 0 lo resetea entero.
¿Por qué existe el grace call? El presupuesto es un tope duro, pero a veces al modelo le faltan una o dos llamadas a herramienta para terminar; cortar de plano pierde el trabajo ya hecho. _budget_grace_call lo activa el bucle interno cuando detecta que «está a punto de terminar»; el or externo permite que ese turno se complete, pero el flag se limpia en cuanto entra — «una oportunidad» es realmente una.
¿Por qué un objeto iteration_budget en lugar de un simple contador? El presupuesto es accedido desde varios hilos (subagentes, refund de compresión); IterationBudget envuelve consume/refund con un lock, garantizando thread safety y dejando que el compresor, tras eliminar mensajes viejos, llame refund() para devolver iteraciones. Un entero simple no cubre ninguna de las dos cosas.
Límites y fallos
- Presupuesto agotado a mitad: antes de salir,
_persist_session(messages, conversation_history)vuelca la conversación a disco;_turn_exit_reasonse marca como"budget_exhausted", para que la capa de arriba distinga fin normal de corte. - Excepción de una herramienta: el error que lanza la herramienta lo atrapa el bucle interno de reintentos; al agotar
max_retries, se llamaagent._try_activate_fallback(). Si la cadena de fallback está vacía, se devuelve un dict confailed: Trueen lugar de propagar la excepción — el gateway de arriba solo ve un fallo estructurado. - El rate guard falla por sí mismo: si el import de
nous_rate_guardfalla o su llamada lanza, elexcept Exceptionlo cubre y el guard se degrada a «no aplica»; el bucle principal sigue por su camino original. El comentario lo dice claro: "Never let rate guard break the agent loop". - Pool OAuth que se refresca infinitamente:
_auth_pool_refresh_countsse resetea al inicio de cada turn, para que una entrada del pool OAuth no quede «refrescándose con éxito» indefinidamente bajo 401 continuos (parche para#26080).
Resumen
El bucle principal solo orquesta: impulso de turnos, control de presupuesto, reintentos y fallback. La capacidad real viene de las herramientas de la capa de capacidades y las habilidades; la entrega multiplataforma la gestiona la capa de gateway; la autoevolución la impulsa el bucle de aprendizaje.
Para la presentación oficial en chino, ver README.zh-CN y website/i18n/zh-Hans/.