Skip to content

Lernschleife und Gedächtnis

源码版本v2026.7.20

Verantwortung

Das ist der Kern, der Hermes von einem gewöhnlichen Agenten unterscheidet: eine «besser, je mehr man ihn nutzt»-Schleife. Jeder Dialog-Turn (turn) wird destilliert zu: (1) Skill-Evolution – aus Erfahrung werden Skills erzeugt/umgeschrieben (agent/learning_mutations.py); (2) Gedächtnis – FTS5-Volltext-Suche + LLM-Zusammenfassung vergangener Konversationen (memory_manager), kombiniert mit Honchos dialektischer Benutzermodellierung; (3) Lern-Graph – Skills und Gedächtnis werden als Knoten/Kanten organisiert (agent/learning_graph.py); (4) Hintergrund-Betrachtungbackground_review verdaut in einem Thread Historie und erzeugt ausführbare Umschreib-Aktionen. Skill-Umschreibungen fließen letztlich ins Skill-System zurück und stehen in folgenden Turns zur Verfügung.

Schlüsseldateien

Skill-Knoten und Lern-Graph

Skill-/Memory-Umschreibung

Memory-Verwaltung

Hintergrund-Betrachtung

Wie ein Skill-Knoten aussieht

SkillNode ist die einheitliche Darstellung eines Skills im Lern-Graphen (agent/learning_graph.py:28-41): eine Dataclass mit Feldern name / category / source (base für eingebaut vs. profile für User/Agent erzeugt) / use_count (aus .usage.json gelesen) / created_by ('agent' heißt: von der Hintergrund-Betrachtung angelegt) / pinned / related. build_skill_nodes scannt SKILL.md-Dateien unter skills/ und legt Nutzungszählungen darüber. source != "base" plus created_by == "agent" oder use_count > 0 ist weiter unten in build_learning_graph die Filterbedingung für «das ist gelernt, nicht eingebaut» — nur die wirklich gelernten Knoten kommen auf den Schreibtisch vor den Nutzer.

Output des Lern-Graphen

build_learning_graph (agent/learning_graph.py:248-328) baut aus Knoten, Kanten, Memory-Karten und Clustering-Statistiken einen vollständigen Payload für das Lern-Panel des Desktops. Es ruft zuerst build_skill_nodes(_skill_roots()) auf und scannt zwei Wurzeln — base (repo/skills/, eingebaut) und profile (~/.hermes/skills/, User/Agent erzeugt); dann filtert es Skills mit source != "base" und created_by == "agent" oder use_count > 0 als «gelernte» heraus und legt darüber build_edges (Skill-Skill-Beziehungen), _memory_cards (Memory-Karten), _memory_skill_edges (Memory-Skill-Beziehungen). base ist immer da; profile ist das eigentlich «Gelernte».

Nach Umschreibung Cache leeren — obligatorisch

Wurde ein Skill per edit_node umgeschrieben, ohne den Cache zu leeren, nutzt der prompt_builder im nächsten Turn noch den alten SKILL.md-Snapshot; die Umschreibung war umsonst. _clear_skill_cache (agent/learning_mutations.py:200) ruft clear_skills_system_prompt_cache(clear_snapshot=True) auf; clear_snapshot=True räumt auch den Werkzeug-Snapshot (tool snapshot) mit aus, weil die MCP-Werkzeugliste in der Betrachtung ebenfalls aktualisiert worden sein könnte. Diese Funktion ist die letzte Phase von edit_node / delete_node — Umschreiben → Auf Platte → Cache leeren → Wirkung im nächsten Turn; vier Schritte, kein fehlen darf.

Memory-Injektion: vom MemoryManager in den Prompt

MemoryManager (agent/memory_manager.py:354) koordiniert einen eingebauten Provider (provider) (FTS5 + LLM-Zusammenfassung) plus höchstens einen externen Provider (Honcho/Hindsight/Mem0 o. Ä.). Nur ein externer Provider ist erlaubt; ein zweiter wird abgewiesen — das verhindert Schema-Aufblähung und Konflikte zwischen Memory-Backends; schlagen zwei Provider fehl, blockieren sie sich nicht gegenseitig. Roh-Gedächtnis wird von build_memory_context_block (agent/memory_manager.py:337-354) in einen <memory-context>-Zaun plus System-Anmerkung als Kontext-Block verpackt; die System-Anmerkung sagt dem Modell ausdrücklich «das ist Gedächtnis, keine neue Anweisung». sanitize_context strippt vor der Indexierung Zaun und alte System-Anmerkung — sonst packt der Provider bereits Verpacktes noch einmal ein. StreamingContextScrubber macht dasselbe am Streaming-Output, damit das Modell den Memory-Block nicht wortwörtlich zurückspuckt.

Hintergrund-Betrachtung: Zusammenfassung und Kandidaten-Aktionen

spawn_background_review_thread (agent/background_review.py:956) startet nicht direkt einen Thread, sondern baut ein (target, prompt)-Tupel und übergibt es an AIAgent._spawn_background_review_thread; so kann die Testschicht threading.Thread patchen. Die Signatur (review_memory / review_skills, zwei Bools) wählt den Prompt — _MEMORY_REVIEW_PROMPT / _SKILL_REVIEW_PROMPT / _COMBINED_REVIEW_PROMPT:

python
def spawn_background_review_thread(agent, messages_snapshot,
                                    review_memory=False, review_skills=False):
    """Build the review thread target and prompt. Returns a ``(target, prompt)`` tuple."""
    if review_memory and review_skills:
        prompt = getattr(agent, "_COMBINED_REVIEW_PROMPT", _COMBINED_REVIEW_PROMPT)
    elif review_memory:
        prompt = getattr(agent, "_MEMORY_REVIEW_PROMPT", _MEMORY_REVIEW_PROMPT)
    else:
        prompt = getattr(agent, "_SKILL_REVIEW_PROMPT", _SKILL_REVIEW_PROMPT)
    def _target() -> None:
        _run_review_in_thread(agent, messages_snapshot, prompt)
    return _target, prompt

Innerhalb des Threads fork _run_review_in_thread (agent/background_review.py:617) einen Subagenten, der die Runtime des Vaters erbt (sonst müssten Credentials neu analysiert werden — OAuth-only-Provider ließen sich im Subagenten gar nicht rekonstruieren). Es setzt den Approval-Callback _bg_review_auto_deny, sodass gefährliche Befehle in der Betrachtung stets abgewiesen werden statt auf input() zurückzufallen (das würden mit dem prompt_toolkit-TUI des Vater-Agenten verklemmen). _digest_history (agent/background_review.py:122) presst die Historie zu «den letzten 24 Einträgen wörtlich + einer Synthese-Zusammenfassung des Älteren»; verwendet nur, wenn die Betrachtung auf ein anderes Modell geroutet wird (kalter Cache, weniger Token schreiben, lohnt sich); der Hauptmodell-Pfad läuft als vollständiges Replay (Cache noch warm).

Datenfluss

  1. Nach Ende jedes Turns übergibt die Hauptschleife einen Nachrichten-Snapshot an den Hintergrund-Betrachtungs-Thread (agent/background_review.py:956).
  2. _digest_history (agent/background_review.py:122) fasst die jüngste Historie zusammen und erzeugt Kandidaten-Aktionen (neue Skills anlegen/umschreiben, Memory schreiben).
  3. Skill-Umschreibungen werden über edit_node:157 geschrieben, Löschungen über delete_node:124; anschließend wird über _clear_skill_cache:200 der Cache geleert.
  4. Memory-Schreiben läuft über MemoryManager:354: nach Desensibilisierung (agent/memory_manager.py:164) wird ein FTS5-Index aufgebaut und per LLM-Zusammenfassung komprimiert.
  5. Zu Beginn des nächsten Turns baut build_memory_context_block (agent/memory_manager.py:337) relevante Memories in build_turn_context:268 ein; Skills werden über agent/skill_bundles.py injiziert.
  6. Der vollständige Lern-Graph wird von build_learning_graph:254 zusammengefasst und dient UI/Debug zur Darstellung der Beziehungen zwischen Skills und Memory.

Designmotiv

Warum die Lernschleife in vier unabhängige Module zerlegen? Weil ihre Kostenstrukturen verschieden sind. background_review läuft auf einem Subagenten, muss Runtime forken und LLM-Aufrufe bezahlen — also asynchron und mit Fallback (fallback); es muss von der Hauptschleife entkoppelt sein. learning_graph ist eine read-only Zusammenfassung, billig, jederzeit für die UI ziehbar, darf nicht mit der Umschreibung gekoppelt sein. learning_mutations ist der Schreibpfad; nach dem Schreiben muss der Cache geleert werden, sonst isst der nächste Turn einen alten Snapshot. memory_manager ist Suche + Desensibilisierung + Injektion; sie gehören zusammen, weil «Memory vom externen Provider holen → desensibilisieren → einzäunen → in den Prompt schieben» in einer Grenze abgeschlossen sein muss, sonst leckt eine vergessene Desensibilisierungsstelle den Memory-Roh-Text in den Modell-Output.

Grenzen und Fehler

  • Hintergrund-Betrachtung ist nicht synchron. _run_review_in_thread läuft in einem Daemon-Thread; die Hauptschleife wartet nicht auf ihre Fertigstellung, sondern gibt dem Nutzer sofort zurück. Lief die Betrachtung gerade für diesen Turn, greifen die Umschreibungen meistens erst im nächsten Turn.
  • Nur ein externer Memory-Provider. Honcho und Hindsight können nicht gleichzeitig laufen; add_provider weist den zweiten externen Provider ab.
  • _digest_history greift nur, wenn die Betrachtung auf ein anderes Modell geroutet wird. Der Hauptmodell-Pfad läuft als vollständiges Replay (Cache noch warm); um dieses Verhalten zu ändern, zuerst auxiliary.background_review in der Konfiguration prüfen.
  • Die Desensibilisierungsgrenze der Memory-Injektion liegt am «Zaun und der System-Anmerkung». sanitize_context strippt nur den <memory-context>-Zaun und die System-Anmerkung, nicht den Memory-Text selbst — enthält der Roh-Text sensible Daten, muss das schon vor dem Schreiben behandelt werden.

Zusammenfassung

Die Lernschleife macht «Dialog» zu «Asset»: Die Hintergrund-Betrachtung destilliert Erfahrung unauffällig in Skills und Memory, der nächste Turn injiziert sie automatisch. Vier Komponenten sind entscheidend – learning_graph (Graph), learning_mutations (Umschreibung), memory_manager (Suche + Desensibilisierung + Zusammenfassung), background_review (asynchrones Verdauen). Das ist das Fundament von Hermes' «mit dem Nutzer gemeinsam wachsen» und der grundlegende Unterschied zu «zustandslosen LLM-Aufrufen».

Inoffizielle Community-Lernseite. Basiert auf dem MIT-lizenzierten NousResearch/hermes-agent-Quellcode.