Hauptschleife des Agenten
Verantwortung
run_conversation ist das Herz von Hermes: Es nimmt eine Nutzernachricht, ruft wiederholt Modell + Werkzeuge (tools) auf, bis das Iterationsbudget (iteration budget) aufgebraucht oder die Aufgabe erledigt ist. Es koordiniert zugleich Kontextaufbau (context build), Werkzeug-Dispatch (tool dispatch), Stopp-Bedingungs-Prüfung sowie Fallback (fallback) bei Misserfolg.
Die Datei hat insgesamt etwa 5800 Zeilen und hohe logische Dichte – diese Seite betrachtet nur die Hauptlinie, Details werden auf Unterseiten behandelt.
Schlüsseldateien
run_conversation-Einstieg:588-720— Funktionssignatur, Initialisierung und Vorbereitung vor der Schleifewhile-Hauptschleife:721-760— Bedingungapi_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0, inkl. Grace-CallAPI-Aufruf-Wiederholungs-Teilschleife:1227-1280—while retry_count < max_retriesbuild_turn_context— Kontextaufbau pro Turnprompt_builder— System-Prompt-Zusammenbau (system prompt assembly)moa_loop— Mixture-of-Agents-Aggregationspfadcontext_compressor— Kompression der Historie beim Erreichen der SchwelleIterationBudget— Kern der Budgetzählung
Datenfluss
run_agent.py AIAgentist ein dünner Forwarder; die echte Dependency-Injection passiert ininit_agent.- Der Aufrufer übergibt
user_message;run_conversationläuft zuerst durchbuild_turn_context, um den Turn-Kontext (turn context) (api_messages) aufzubauen. - Eintritt in die
while-Hauptschleife: Abbruchprüfung → Budget (budget) dekrementieren (iteration_budget.consume()) →api_messagesaufbauen/desinfizieren → Vorkompression →/steer-Texte leeren → MoA-Aggregation → Eintritt in die API-Aufruf-Wiederholungs-Teilschleife. - Nach der Modell-Rückgabe erfolgen Response-Validierung und
finish_reason-Behandlung; bei Misserfolg greiftagent._try_activate_fallback(). - Wird die Kompressionsschwelle (compression threshold) getroffen, läuft
context_compressorund räumt Platz zurück. - Nach Schleifenende (Budget erschöpft / Aufgabe erledigt / abgebrochen) wird die finale Nachricht zurückgegeben und vom Gateway (gateway) zugestellt (siehe Gateway-Schicht).
Das echte Gesicht der Hauptschleife steht in 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"
breakDer angehängte or agent._budget_grace_call ist der Hebel «nach Budget-Null noch eine Chance»: Zu Beginn dieses Turn wird das Flag sofort gelöscht, deshalb bleibt es bei «einer Chance».
Die API-Wiederholung ist nicht rekursiv, sondern eine innere while retry_count < max_retries. Hier der Anfang aus 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 loopNach dem Provider-Wechsel setzt retry_count = 0 den Zähler aus einem sauberen Zustand zurück. Das äußerste except Exception: pass ist Absicht: Der Guard ist eine Optimierung und darf niemals die Hauptschleife selbst brechen.
Designmotiv
Warum ist die Wiederholung als Teilschleife statt als Rekursion gebaut? Rekursion frisst in Python die Aufruf-Stack-Tiefe, und der Zustand eines Fehlers verstreut über mehrere Stack-Frames, was schwer einheitlich aufzuräumen ist. Mit while retry_count < max_retries liegen alle Wiederholungszustände (retry_count, compression_attempts) als lokale Variablen im selben Frame; beim Fallback-Wechsel genügt ein retry_count = 0 zum kompletten Reset.
Warum ein Grace-Call? Das Budget ist eine harte Obergrenze, aber das Modell ist manchmal nur ein oder zwei Werkzeugaufrufe vom Abschluss entfernt — direktes Abwürgen würde bereits geleistete Arbeit wegwerfen. _budget_grace_call wird von innen gesetzt, wenn «fast geschafft», der äußere or lässt diesen Turn durchlaufen, aber das Flag wird beim Eintritt sofort gelöscht — «eine Chance» bleibt eine.
Warum ein iteration_budget-Objekt statt ein simpler Zähler? Das Budget wird threadübergreifend angesprochen (Subagent, Kompressions-Refund); IterationBudget kapselt consume/refund mit einer Sperre und garantiert Thread-Sicherheit, lässt aber dem Kompressor nach dem Entfernen alter Nachrichten den Weg, über refund() Iterations-Guthaben zurückzugeben. Eine einfache Ganzzahl kann beides nicht.
Grenzen und Fehler
- Budget mid-turn erschöpft: Vor dem Austritt schreibt
_persist_session(messages, conversation_history)die aktuelle Sitzung (session) auf Platte;_turn_exit_reasonwird"budget_exhausted"gesetzt, damit die Oberschicht zwischen normalem Ende und Abbruch unterscheiden kann. - Werkzeugaufruf-Ausnahme: Fehler auf Werkzeugseite werden von der inneren Wiederholung gefangen; nach
max_retriesläuftagent._try_activate_fallback(); ist die Fallback-Kette leer, kommt einfailed: True-Ergebnisdict zurück statt einer nach außen sichtbaren Exception — das Gateway oben sieht nur strukturiertes Scheitern. - Guard des Rate-Limits selbst fehlerhaft: Schlägt der Import oder Aufruf von
nous_rate_guardfehl, fängtexcept Exceptiones ab; der Guard degradiert zu «nicht wirksam», die Hauptschleife läuft weiter auf dem regulären Pfad; der Kommentar sagt es direkt: «Never let rate guard break the agent loop». - OAuth-Pool unendlich Refresh:
_auth_pool_refresh_countswird zu Turn-Beginn zurückgesetzt und verhindert, dass ein einzelner OAuth-Pool unter dauerndem 401 vontry_refresh_current()unendlich «erfolgreich» refreshed wird (Auffangwehr aus#26080).
Zusammenfassung
Die Hauptschleife orchestriert nur: Turn-Antrieb, Budget-Steuerung, Wiederholung und Fallback. Die echte Fähigkeit kommt von Tools und Skills der Fähigkeitsschicht, die Multi-Plattform-Zustellung obliegt der Gateway-Schicht, die Selbst-Evolution treibt die Lernschleife.
Für den Abgleich mit der offiziellen chinesischen Einführung siehe README.zh-CN und website/i18n/zh-Hans/.