Einstellungen

Sprache

Warum Ihr AI Agent ständig sein Gedächtnis verliert

T
TokenLab
·5. März 2026·1331 Aufrufe
Warum Ihr AI Agent ständig sein Gedächtnis verliert

Ihr AI Agent hat gerade ein 30-minütiges Gespräch mit einem Benutzer geführt. Sie haben Projektanforderungen besprochen, Präferenzen ausgetauscht und Entscheidungen getroffen. Dann gibt der Benutzer /new ein, um eine neue Session zu starten.

Der Agent versucht, dieses Gespräch im Langzeitgedächtnis zu konsolidieren. Der LLM-Aufruf schlägt fehl. Rate Limit. Timeout. Oder das Modell gibt Text zurück, anstatt das erforderliche Tool aufzurufen.

Das Gedächtnis ist weg. Dreißig Minuten Kontext, einfach verpufft.

Das passiert häufiger, als man denkt. In der Hermes Agent Runtime hatte die Gedächtniskonsolidierung bei jedem einzelnen Modell eine Fehlerrate von ca. 15 %, bevor wir einen dedizierten Fallback hinzugefügt haben. Für ein Feature, das eigentlich eine unsichtbare Infrastruktur sein sollte, ist das inakzeptabel.

Wenn Sie die umgebende Produktoberfläche bauen und nicht nur das Memory-Subsystem, kombinieren Sie diese Seite mit dem One-Key Chatbot Guide und dem AI API Rate Limiting Guide. Die Beständigkeit des Gedächtnisses spielt nur dann eine Rolle, wenn der Agent tatsächlich in einer nutzbaren Anwendung lebt.

Wie andere Frameworks damit umgehen (Gar nicht)

Die meisten AI Agent Frameworks behandeln die Gedächtniskonsolidierung als einfachen LLM-Aufruf. Wenn es funktioniert, super. Wenn nicht, ist das Gedächtnis verloren.

Die Hermes Agent Baseline verwendete dasselbe Modell für die Konsolidierung wie für die Konversation. Ein Claude Sonnet Aufruf, der 0,003 $ kostet und mehr als 8 Sekunden dauert, nur um einen Chat zusammenzufassen, den der Benutzer nie sehen wird. Wenn dieser Aufruf fehlschlägt (Rate Limit, Timeout, Modellfehler), loggt das Framework eine Warnung und macht weiter. Der Kontext des Benutzers ist weg.

nanobot, ein weiteres beliebtes Framework, hat die gleiche Architektur. Ein Modell, ein Versuch, kein Fallback. Die Konsolidierungsfunktion hat nicht einmal einen Timeout. Ein langsamer Upstream (Cloudflare 524 Fehler sind häufig) blockiert die gesamte Session, bis die Verbindung abbricht.

Keines der Frameworks trennt die Konsolidierung vom Hauptmodell. Keines verfügt über eine Fallback-Logik für Memory-Operationen. Keines unterscheidet zwischen „der API-Aufruf ist fehlgeschlagen“ und „der API-Aufruf war erfolgreich, aber das Modell hat nicht getan, was wir verlangt haben“.

Dies sind keine Randfälle. Bei einer Fehlerrate von 15 % bei jedem einzelnen Modell verliert ein Framework, das 100 Konsolidierungen pro Tag durchführt, bei 15 davon das Gedächtnis. Über eine Woche hinweg sind das 105 Gespräche, bei denen der Agent alles vergisst.

Das Problem liegt tiefer als die Retry-Logik

Die offensichtliche Lösung ist ein Retry mit exponentiellem Backoff. Das hatten wir. Es fängt vorübergehende HTTP-Fehler gut ab:

# Retry loop: 1s → 2s → 4s backoff
for attempt in range(3):
    try:
        response = await acompletion(**kwargs)
        return await self._collect_stream(response)
    except (RateLimitError, APIConnectionError) as e:
        await asyncio.sleep(RETRY_DELAYS[attempt])

Das fängt 429er und Netzwerk-Glitches ab. Aber zwei Fehlermodi schlüpfen durch:

Fehlermodus 1: Das Modell beherrscht kein Tool Calling. Einige Modelle, insbesondere kleinere, die auf schnellen Inference Engines laufen, scheitern gelegentlich daran, bei komplexen Prompts valide Funktionsaufrufe zu generieren. Die API gibt einen 200er mit einem ServiceUnavailableError zurück, der in einem MidStreamFallbackError verpackt ist. Ihre Retry-Logik sieht eine Exception, versucht es mit demselben Modell erneut und erhält denselben Fehler.

Fehlermodus 2: Das Modell „ist erfolgreich“, ruft aber das Tool nicht auf. Das LLM liefert eine perfekt valide Antwort. HTTP 200. Keine Fehler. Aber anstatt save_memory mit strukturierten Daten aufzurufen, schreibt es eine Zusammenfassung in reinem Text. Ihre Retry-Engine betrachtet dies als Erfolg. Die Konsolidierungsfunktion prüft auf Tool-Aufrufe, findet keine und gibt auf.

Der zweite Fehlermodus ist der tückische. Der Transport-Layer denkt, alles hätte funktioniert. Der Business-Layer weiß, dass es nicht so war. Kein Maß an Retries auf HTTP-Ebene wird ein Modell reparieren, das Ihr Tool-Schema nicht versteht.

Dual-Layer Fallback-Architektur

Wir haben dies mit zwei unabhängigen Fallback-Loops gelöst, die auf verschiedenen Ebenen operieren:

User sends /new
    │
    ▼
consolidate() ─── Business Layer Fallback
    │               "Did the model call save_memory?"
    │               No → try next model in chain
    │
    ▼
_chat_with_retry() ─── Transport Layer Fallback
    │                    HTTP errors → exponential backoff
    │                    All retries exhausted → walk fallback chain
    │
    ▼
MODEL_MAP fallback chain:
    llama-3.3-70b  ─$0.59/M─→  qwen3-32b  ─$0.29/M─→  llama-4-scout  ─$0.11/M─→  gpt-4.1-mini  ─→  claude-haiku
    (394 TPS)                   (662 TPS)                (594 TPS)                  (reliable)        (last resort)

Layer 1 kümmert sich um Transportfehler. Layer 2 kümmert sich um Fehler in der Business-Logik. Die Fallback-Chain wird von beiden Layern gemeinsam genutzt und einmal in einem zentralen Katalog definiert.

Dies ist ein grundlegend anderer Ansatz als das bloße Wiederholen desselben Modells. Wenn ein Modell ein Tool nicht aufrufen kann, hilft ein erneuter Versuch mit demselben Prompt selten. Der Wechsel zu einem anderen Modell mit anderen Gewichten und einem anderen Tool Calling Verhalten hingegen schon.

Der Modell-Katalog: Eine Single Source of Truth

Jedes Modell in unserem Katalog hat ein optionales fallback-Feld, das auf das nächste zu testende Modell verweist:

@dataclass(frozen=True)
class ModelEntry:
    id: str
    label: str
    tier: str
    description: str
    fallback: str | None = None
    hidden: bool = False  # Hidden from user-facing /model list

MODEL_CATALOG = [
    # User-visible models (16 models users can switch between)
    ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
               "Recommended", fallback="claude-sonnet-4-5"),
    ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
               "Stable tool calling", fallback="claude-haiku-4-5"),

    # Hidden consolidation models (internal use only)
    ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
               "394 TPS", fallback="qwen3-32b", hidden=True),
    ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
               "662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
    # ...
]

Das hidden=True Flag hält interne Modelle aus dem benutzerseitigen /model Befehl heraus, während sie dennoch an Fallback-Chains teilnehmen. Benutzer sehen 16 Modelle, zwischen denen sie wechseln können. Das System verwendet 19. Die drei versteckten Modelle existieren ausschließlich für Hintergrundaufgaben wie die Gedächtniskonsolidierung, bei denen Geschwindigkeit und Kosten wichtiger sind als die Konversationsqualität.

Dieser Katalog ist die Single Source of Truth für das gesamte Modell-Routing. Das Hinzufügen eines neuen Modells zur Fallback-Chain bedeutet das Hinzufügen einer einzigen Zeile. Keine Konfigurationsdateien zum Synchronisieren, keine Umgebungsvariablen zum Aktualisieren, keine Deployment-Skripte zum Ändern.

Transport-Layer: Geketteter Fallback mit Zyklus-Prävention

Die Retry-Engine durchläuft die Fallback-Chain unter Verwendung eines Visited-Sets, um Endlosschleifen zu verhindern:

async def _chat_with_retry(self, kwargs, original_model):
    # Phase 1: Exponential backoff on the primary model
    for attempt in range(3):
        try:
            response = await acompletion(**kwargs)
            return await self._collect_stream(response)
        except (RateLimitError, APIConnectionError, APIError) as e:
            await asyncio.sleep(RETRY_DELAYS[attempt])
        except AuthenticationError:
            return LLMResponse(content="API key invalid.", finish_reason="error")

    # Phase 2: Walk the fallback chain
    visited = {original_model}
    current = original_model
    while True:
        entry = MODEL_MAP.get(current)
        if not entry or not entry.fallback or entry.fallback in visited:
            break
        current = entry.fallback
        visited.add(current)

        # Resolve correct gateway for this model
        gw = self._resolve_gateway_for_model(current)
        resolved = self._resolve_model(current, gateway=gw)
        fb_kwargs = {**kwargs, "model": resolved}

        # Fix api_base for the target model's protocol
        if gw and gw.default_api_base:
            fb_kwargs["api_base"] = gw.default_api_base

        try:
            response = await acompletion(**fb_kwargs)
            return await self._collect_stream(response)
        except Exception:
            continue  # Try next in chain

    return LLMResponse(content="Service unavailable.", finish_reason="error")

Das visited-Set ist entscheidend. Ohne es würde eine Kette wie A→B→A ewig loopen. Damit probiert die Engine jedes Modell genau einmal aus.

Auch die Gateway-Auflösung ist wichtig. Verschiedene Modelle benötigen unterschiedliche API-Formate. Claude-Modelle werden über ein Gateway im Anthropic-Format geroutet (kein /v1 Suffix). GPT-Modelle laufen über ein OpenAI-kompatibles Gateway (mit /v1). Groq-Modelle nutzen wiederum einen anderen Endpoint. Die Fallback-Engine löst das korrekte Gateway für jedes Modell in der Kette auf und verhindert so Protokoll-Fehler, wie das Senden von Anthropic-Requests an einen OpenAI-Endpoint.

Dies ist ein Detail, das die meisten Frameworks komplett ignorieren. Sie gehen davon aus, dass alle Modelle dasselbe Protokoll sprechen. In der Produktion, mit 19 Modellen über 4 verschiedene API-Formate hinweg, bricht diese Annahme sofort zusammen.

Business-Layer: Tool-Call-Verifizierung

Die Konsolidierungsfunktion fügt obenauf einen eigenen Fallback-Loop hinzu:

async def consolidate(self, session, provider, model, **kwargs):
    visited = set()
    current_model = model

    while current_model and current_model not in visited and len(visited) <= 3:
        visited.add(current_model)

        response = await asyncio.wait_for(
            provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
            timeout=30,
        )

        if response.has_tool_calls:
            # Success: extract and save memory
            args = response.tool_calls[0].arguments
            self.write_long_term(args["memory_update"])
            self.append_history(args["history_entry"])
            return True

        # Model didn't call the tool — try next in chain
        entry = MODEL_MAP.get(current_model)
        next_model = entry.fallback if entry else None
        if next_model and next_model not in visited:
            current_model = next_model
            continue

        return False  # No more fallbacks

    return False

Dies fängt den Fall ab, in dem _chat_with_retry eine erfolgreiche Antwort zurückgibt (HTTP 200, valider Inhalt), das Modell aber das Tool nicht verwendet hat. Die Konsolidierungsfunktion prüft auf has_tool_calls und geht, falls diese fehlen, zum nächsten Modell in der Kette über.

Der Timeout-Wrapper (asyncio.wait_for) löst ebenfalls einen Fallback aus. Wenn ein Modell länger als 30 Sekunden benötigt (häufig bei Cloudflare 524 Fehlern auf langsamen Upstreams), fängt die Funktion den TimeoutError ab und versucht es stattdessen mit dem nächsten Modell, anstatt die Session des Benutzers auf unbestimmte Zeit zu blockieren.

Warum Groq für die Konsolidierung?

Gedächtniskonsolidierung ist eine Hintergrundaufgabe. Der Benutzer sieht den Output nicht. Er muss einfach nur funktionieren. Das macht sie zu einem perfekten Kandidaten für schnelle, günstige Modelle.

Die meisten Frameworks verwenden für alles dasselbe teure Modell. Wenn Sie Claude Sonnet für die Konversation nutzen, nutzen Sie Claude Sonnet auch für die Gedächtniskonsolidierung. Das sind 3 $/M Input-Token und mehr als 8 Sekunden pro Konsolidierung für eine Aufgabe, die einen Output erzeugt, den kein Mensch jemals liest.

Wir haben die Konsolidierung komplett vom Konversationsmodell entkoppelt. Die Konversation nutzt das vom Benutzer gewählte Modell. Die Konsolidierung nutzt eine dedizierte Kette von auf Groq gehosteten Modellen:

Modell Geschwindigkeit Input-Kosten Output-Kosten
llama-3.3-70b-versatile 394 TPS $0.59/M $0.79/M
qwen3-32b 662 TPS $0.29/M $0.59/M
llama-4-scout-17b-16e 594 TPS $0.11/M $0.34/M
gpt-4.1-mini (vorherig) ~150 TPS $0.40/M $1.60/M

Das primäre Modell (llama-3.3-70b) konsolidiert eine Session mit 60 Nachrichten in ca. 5 Sekunden. Der vorherige Standard (gpt-4.1-mini) dauerte mehr als 8 Sekunden. Die Kosten pro Konsolidierung sanken von ca. 0,003 $ auf ca. 0,001 $.

Der Kompromiss: Groq-Modelle haben ein weniger zuverlässiges Tool Calling bei komplexen Prompts. Genau deshalb existiert der Dual-Layer Fallback. Wenn llama-3.3-70b das Tool nicht aufrufen kann, übernimmt qwen3-32b. Wenn auch das fehlschlägt, versucht es llama-4-scout. Wenn alle drei Groq-Modelle scheitern, übernimmt gpt-4.1-mini mit einer Tool-Calling-Zuverlässigkeit von nahezu 100 %.

In der Produktion sehen wir, dass das primäre Modell in ca. 85 % der Fälle erfolgreich ist. Die Kette erreicht gpt-4.1-mini in weniger als 2 % der Konsolidierungen. Die Gesamtausfallrate ist praktisch null.

Produktionsergebnisse

Wir haben dies auf zwei Hermes Agent Runtime-Instanzen deployed und mit echten Telegram-Gesprächen getestet.

Erstes Deployment (nur Single-Layer Fallback):

Memory consolidation (archive_all): 56 messages
llama-3.3-70b-versatile → "Failed to call a function"
Falling back → qwen3-32b
qwen3-32b: LLM did not call save_memory, skipping
→ "Memory archival failed, session not cleared."

Der Transport-Layer fing den ersten Fehler ab und wechselte zum Fallback. Aber qwen3-32b gab Text zurück, ohne das Tool aufzurufen. Ein Single-Layer Fallback konnte dies nicht handhaben. Dies ist genau das Szenario, bei dem jedes andere Framework stillschweigend das Gedächtnis verlieren würde.

Zweites Deployment (Dual-Layer Fallback):

Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining

Dasselbe Modell, dasselbe Nachrichtenvolumen. Diesmal hat es beim ersten Versuch geklappt. Die intermittierende Natur des Tool-Calling-Fehlers ist genau der Grund, warum man eine Fallback-Chain anstelle eines einzelnen Backup-Modells benötigt.

Wenn das primäre Modell tatsächlich scheitert, fängt die Kette dies ab:

llama-3.3-70b → tool call failed
→ consolidate() fallback → qwen3-32b
→ qwen3-32b didn't call tool
→ consolidate() fallback → llama-4-scout
→ llama-4-scout didn't call tool
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini called save_memory ✓
Memory consolidation done

Vier Modelle versucht, Gedächtnis gespeichert. Der Benutzer sieht „Neue Session gestartet.“ und hat keine Ahnung, dass all dies passiert ist.

Die Architektur-Lücke

Das Memory-System im Vergleich zu den Alternativen, Feature für Feature:

Funktion Typisches AI Agent Framework Dual-Layer Fallback-Design
Konsolidierungsmodell Identisch mit Konversation (teuer, langsam) Unabhängige Modellkette, Groq-beschleunigt
Fehlerbehandlung Warnung loggen, Gedächtnis verlieren Dual-Layer Fallback, 5 Modelle tief
Transport-Fallback Dasselbe Modell 3x erneut versuchen Geketteter Fallback über verschiedene Modelle
Business-Logik-Fallback Keine Tool-Call-Verifizierung + Modellwechsel
Timeout-Schutz Keine (Cloudflare 524 blockiert Session) asyncio.wait_for(timeout=30) + Fallback
Session-Kürzung Keine (Kontext wächst ewig) Alte Nachrichten nach Konsolidierung kürzen
Historien-Suche Keine HISTORY.md Rolling Window, per grep durchsuchbar
Interne Modelle Nicht unterstützt hidden=True für rein systeminterne Modelle
Zyklus-Prävention Nicht benötigt (keine Ketten) visited-Set verhindert A→B→A Schleifen
Gateway-Auflösung Einzelnes API-Format vorausgesetzt Pro-Modell-Gateway mit Protokoll-Erkennung

Jede Zeile in dieser Tabelle repräsentiert einen Produktionsfehler, den wir entweder selbst erlebt oder in den Issue-Trackern anderer Frameworks beobachtet haben. Der Dual-Layer Fallback, der versteckte Modellkatalog, die Gateway-Auflösung pro Modell, der durch Timeout ausgelöste Fallback: Nichts davon existiert in nanobot oder einem anderen Open-Source Agent Framework, das wir untersucht haben.

Was wir gelernt haben

„Request erfolgreich“ ist nicht gleichbedeutend mit „Aufgabe erfolgreich“. Generische Retry-Engines arbeiten auf HTTP-Ebene. Sie können nicht wissen, dass eine 200er-Antwort mit validem JSON eigentlich ein Fehler ist, weil das Modell das angeforderte Tool nicht verwendet hat. Business-kritische Operationen benötigen eigene Erfolgskriterien und eine eigene Fallback-Logik.

Kleine Modelle scheitern anders als große Modelle. Große Modelle (GPT-4.1, Claude Sonnet) rufen fast immer Tools auf, wenn sie dazu aufgefordert werden. Kleine Modelle auf schnellen Inference Engines generieren manchmal valide aussehende Antworten, die das Tool-Schema komplett ignorieren. Das ist kein Bug, den man mit Prompt Engineering beheben kann. Es ist eine Fähigkeitslücke, die architektonische Gegenmaßnahmen erfordert.

Testen Sie mit Produktionsdaten, nicht mit synthetischen Daten. Unser initialer Test mit 6 synthetischen Nachrichten war bei jedem Modell erfolgreich. Die echte 60-Nachrichten-Session mit Tool-Call-Historie, Zeitstempeln und gemischten Sprachen schlug bei zwei von drei Groq-Modellen fehl. Die Komplexität realer Daten deckt Fehlermodi auf, die saubere Testdaten niemals zeigen würden.

Das ist auch der Grund, warum der AI API Rate Limiting Guide hier wichtig ist. Das Memory-System braucht nicht nur ein „besseres Modell“. Es braucht eine Transport-Policy, einen Business-Logik-Erfolgscheck und eine Fallback-Leiter, die nicht bei gewöhnlichen Provider-Ausfällen zusammenbricht.


Dieser Artikel beschreibt ein Zuverlässigkeitsmuster aus dem Hermes Agent. Nutzen Sie das hier vorgestellte Architekturmuster als Referenz für Memory-Durability, Fallback-Design und Gateway-bewusstes Modell-Routing.

Benötigen Sie Zugriff auf über 300 AI-Modelle über einen einzigen API Key? tokenlab.sh bietet vereinheitlichten Zugriff auf OpenAI, Anthropic, Google, DeepSeek, Groq und mehr.

Teilen: