AI Agent Memory: Schema-Guided State und Provenance

Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.

Long-running Agents rufen häufig veraltete Fakten ab, weil gewöhnliches Semantic Memory keine Regel dafür besitzt, welcher Wert aktuell ist.

Wenn ein Benutzer eine Passfrist für den Reisepass vom 15. Juli auf den 30. Juni ändert, kann Vector Search beide Aussagen abrufen. Eine zustandsbehaftete Memory-Schicht muss festhalten, dass der Wert 30. Juni den früheren Wert ersetzt.

Für Engineers, die Long-running oder Multi-Tenant Agents entwickeln, zeigt dieses Problem mit veralteten Fakten, warum Memory separate Runs überdauern und dabei den aktuellen Zustand sowie Tenant-Grenzen durchsetzen muss. Das folgende Design zeigt, wie sich typisierte Records schreiben und aktuelle sowie Point-in-Time-Reads testen lassen, ohne Vector Recall als Source of Truth zu behandeln.


Die Context-Window-Falle

Das Context Window bezeichnet die Eingabe, die für einen einzelnen Model Call verfügbar ist. Eine Anwendung kann Messages in spätere Calls übernehmen, aber die Application Policy muss entscheiden, welche alten Fakten weiterhin gelten und wer sie sehen darf.

Long-running Agents müssen sich Preferences, Task-Status, Customer Facts, Tool Decisions, Compliance Notes und frühere Fehler merken. Die einfache Variante besteht darin, Summaries anzuhängen oder alte Notes in einen Vector Store zu kippen. Das funktioniert, bis sich einer der gespeicherten Fakten ändert.

Nun verfügt der Agent über zwei Passfristen, zwei bevorzugte Formate oder zwei Projektentscheidungen. Semantic Search kann beide abrufen. Eine Summary kann eine davon überschreiben. Ein langer Context kann den veralteten Wert neben dem aktiven enthalten. Diese Designs rufen Text ab, erzwingen aber nicht den aktuellen Wert.

Der Memory Contract muss konkrete Fragen beantworten:

  • Was gilt jetzt?
  • Was galt am 2. Juni?
  • Wer hat es gesagt?
  • Zu welchem Tenant gehört es?
  • Welchen älteren Fakt hat dieser Fakt ersetzt?
  • Kann ich ihn löschen oder auslaufen lassen?

Schema-Guided Agent Memory (SGAM) speichert diese Antworten als Fields und Relations, statt sie implizit in Prose zu belassen.


Was SGAM bedeutet

Drei ähnlich benannte Konzepte legen den Scope von SGAM fest.

Schema-Guided Dialogue (SGD) ist das 2019 Google task-oriented dialogue dataset. Sein Schema beschreibt Service-APIs, Intents und Slots, damit ein Dialogue Model den Zustand von Services verfolgen kann, die es noch nicht gesehen hat. Es ist ein nützlicher Präzedenzfall für Schema-basiertes Tracking, sein Scope ist jedoch auf Dialogue Services begrenzt.

Schema-Guided Memory (SGM) ist der Research-Begriff, den Mei et al. in According to Me: Long-Term Personalized Referential Memory QA verwenden. Das Paper vergleicht Free-Text-Descriptive Memory (DM) mit Memory Items in einem festen Key-Value-Schema. Beide Repräsentationen enthalten dieselben Quellinformationen, jedoch in unterschiedlichen Strukturen.

In diesem Artikel verwende ich Schema-Guided Agent Memory (SGAM) für ein Engineering Pattern, bei dem Schemas Writes, Updates, Retrieval und Deletion steuern. Das Schema definiert den Application State und seinen Lifecycle.

ATM-Bench zeigt, warum die Repräsentation relevant ist. Der Benchmark verwendet ungefähr vier Jahre persönliche Daten aus Emails, Bildern und Videos. Die Fragen erfordern persönliche Referenzen, den Ort, mehrere Evidenzstücke und Updates im Zeitverlauf. Für seinen Hard Split berichtet das Paper, dass SGM im getesteten Setup DM bei Retrieval und Question Answering übertrifft. SGM stellt Fields wie Time, Source, Location, Entities und Tags in einer festen Repräsentation bereit. Ich behandle diese Repräsentation und die berichteten Ergebnisse als unterstützte Schlussfolgerung; das Paper etabliert keinen direkten Field-Addressing-Mechanismus.

SGM versus DM beantwortet eine Storage-Frage: Soll Memory als Free Text erhalten bleiben oder benannte Fields verwenden? Ein produktiver Agent hat bereits vor dem Storage ein weiteres Problem. Er muss eine unstrukturierte Conversation in ein vorgeschlagenes Memory Update umwandeln. Schema-Guided Reasoning (SGR) macht diesen beabsichtigten Decision Path überprüfbar: Die Response kann Evidence, Subject und Attribute, einen Vergleich mit dem aktuellen State sowie einen Candidate Write enthalten. Die Model Response erzwingt keine Dependencies oder Reihenfolge zwischen diesen Fields. Separate Calls, Validators und Application Policy setzen diese Regeln durch; SGAM wendet die Storage- und Lifecycle-Regeln nach dem Model Call an.


Model Extraction und Memory Ownership trennen

Der Memory Write durchläuft drei Layers. Structured Output (SO) erzwingt die Form des Candidate Objects. Schema-Guided Reasoning (SGR) macht die beabsichtigten Fields und die Decision Intent des Models in einer strukturierten Response überprüfbar. Schema-Guided Agent Memory (SGAM) verwaltet den Candidate nach dem Model Call als dauerhaften State. Separate Calls, Validators und Application Policy setzen Dependencies, Reihenfolge und Lifecycle-Regeln durch.

Ein Verdict, eine Route oder ein Plan läuft normalerweise zusammen mit dem aktuellen Request ab. Ein anderer Run kann einen Memory Candidate Tage später lesen oder ihn zur Auswahl eines Tool Calls verwenden. Diese längere Lebensdauer erfordert Storage Rules, die SGR nicht bereitstellt.

SGR strukturiert einen Model Call um eine beabsichtigte Reasoning Topology. Für einen Memory Write könnte die Response Source Evidence, ein normalisiertes Subject und Attribute, einen Vergleich mit dem aktuellen State sowie ein vorgeschlagenes Update enthalten. Pydantic oder JSON Schema beschreibt diese Fields. Provider-natives Structured Output oder ein Guided-Decoding-Runtime wie XGrammar hält die Response in dieser Form.

Diese Form macht die deklarierten Fields und die Intent des Models überprüfbar. Sie garantiert weder eine korrekte Schlussfolgerung noch beweist sie, dass das Model diese Fields in der richtigen Reihenfolge verwendet hat. Separate Calls, Validators und Application Policy bilden die Enforcement Boundary für Dependencies, Reihenfolge und Lifecycle.

SGAM entscheidet, was geschieht, nachdem dieses Object existiert. Soll es gespeichert werden? Ersetzt es einen älteren Fakt? Welcher Tenant darf es sehen? Ist es aktuell oder historisch? Welche Source Episode stützt es?

Die Tabelle zeigt Ownership und Failure Mode jeder Layer:

DimensionSOSGRSGAM
ZweckEin Object zurückgeben, das einem Schema entsprichtBeabsichtigte Fields und Decision Intent überprüfbar machenDauerhaftes Memory nach dem Model Call verwalten
ScopeEine generierte ResponseEine Model Response; Cross-Step Policy kann mehrere Calls umfassenRecords, die zwischen Calls, Sessions und Runs verwendet werden
Schema-RolleDefiniert Output-Fields, Types und zulässige WerteBeschreibt Evidence, Intermediate Fields und Candidate DecisionDefiniert gespeicherte Records, Relations und Lifecycle
EnforcementConstrained Decoding blockiert schema-invalid OutputNur Shape; separate Calls, Validators und Policy erzwingen Dependencies und ReihenfolgeApplication Validation, Database Constraints und Conflict Rules erzwingen den Lifecycle
LebensdauerAktueller Call, sofern die Anwendung das Object speichertReasoning Trace wird nach der Decision meist verworfenBleibt bestehen, bis es aktualisiert, abgelaufen oder gelöscht wird
Failure ModeGültige Form mit falscher BedeutungFields sind vorhanden, aber Reasoning oder Dependency Handling kann weiterhin falsch seinVeralteter, verunreinigter, nicht gescopter oder nicht auditierbarer State

Auf dem Write Path sieht die Sequenz folgendermaßen aus:

SGR reasoning schema + Structured Output -> candidate write -> SGAM policy and persistence

Der folgende illustrative Ausschnitt aus memory_models.py definiert das Object, das von der Extraction an den SGAM Write Service übergeben wird. Dieser Block benötigt Pydantic und ist deshalb im Beispiel mit no-run markiert, damit Environments ohne diese optionale Dependency die Checks überspringen können:

from datetime import datetime
from pydantic import BaseModel, Field

class MemoryDelta(BaseModel):
    tenant_id: str = Field(description="Isolation boundary, e.g. acme")
    subject: str = Field(description="Normalized entity ID, e.g. mira")
    attribute: str = Field(description="Property being updated")
    value: str = Field(description="New value")
    valid_from: datetime
    source_episode_id: str

MemoryDelta hält fest, was das Model extrahiert hat. Der SGAM Write Service entscheidet weiterhin, ob er den Candidate ablehnt, zusammenführt oder speichert.


Write Path und Read Path haben unterschiedliche Aufgaben

Nur der Write Path mutiert den gespeicherten State. Der Read Path wählt Records für den aktuellen Request aus.

Der Ingestion Flow ist der Write Path:

  1. Erfasse eine Raw Episode aus Messages, Tool Results oder Business Events.
  2. Extrahiere typisierte Candidates über Structured Output.
  3. Validiere das Schema und lehne fehlerhafte Writes ab.
  4. Reconcile Conflicts, schließe veraltete Fakten und bewahre Provenance.
  5. Committe den Record in den SGAM Store.

Der Request Flow ist der Read Path:

  1. Beginne mit der User Question.
  2. Entscheide, ob die Frage den aktuellen State oder einen Point-in-Time State benötigt.
  3. Filtere nach Tenant, Memory Type, Subject, Attribute und Validity Window.
  4. Füge Vector- oder Graph-Expansion nur hinzu, wenn der exakte State Lookup nicht ausreicht.
  5. Stelle den kleinsten zitierten Context für das Model zusammen.

Schema-Guided Agent Memory architectureSchema-Guided Agent Memory architecture

Lies das Diagramm von links nach rechts in zwei Lanes. Die obere Lane schreibt Memory, die untere liest es. Beide verwenden denselben Store.


Was in ein Memory Schema gehört

Ein minimales SGAM Record benötigt mehr als text.

tenant_id
memory_id
subject
attribute
value
memory_type
schema_version
valid_from
valid_to
supersedes_memory_id
source_episode_id
confidence
retention_policy

Mit diesen Fields kann eine neue Passfrist die vorherige schließen, ohne die History zu löschen. Dieselbe Tabelle kann Current- und Point-in-Time-Queries beantworten und das Ergebnis anschließend bis zu seiner Source Episode zurückverfolgen. schema_version unterstützt Migrations, während retention_policy Löschjobs mitteilt, was sie zusätzlich entfernen müssen.

Verwende RAG zum Abruf von Dokumenten und SGAM zur Verwaltung veränderlicher States. Vector Search gehört weiterhin für Fuzzy Recall, Clustering und Expansion ins System. Der aktuelle Wert von mira.passport_deadline sollte aus einem gescopten Memory Record stammen und nicht aus dem Chunk, der zufällig am höchsten gerankt wurde.


Ein Beispiel für einen veralteten Fakt

Betrachte einen synthetischen Trace mit zwei Episodes, dargestellt als DM-Baseline und SGAM Ledger:

e1: Mira prefers concise answers. Her passport deadline is 2026-07-15.
e2: Mira corrected the deadline. It is now 2026-06-30.

Eine DM-Baseline bewahrt beide Episodes als Free Text auf. Daher kann Text Search e1 zurückgeben, weil dort die richtigen Wörter vorkommen. SGAM extrahiert aus jeder Episode einen typisierten Fakt, der nach Tenant, Subject und Attribute verschlüsselt ist. Es sollte e2 als aktuellen State zurückgeben und e1 für eine historische Query behalten.

Der folgende eigenständige SQLite Write Path verwendet ISO-8601-UTC-Strings für Timestamps, konfiguriert sqlite3.Row vor dem Lesen nach Column Name und stellt Fakten als Half-Open Intervals dar: [valid_from, valid_to). Die Tabelle lehnt ungültige Intervalle ab und besitzt einen Partial Unique Index für genau einen offenen Fakt pro Tenant, Subject und Attribute. replace_fact verwaltet eine Transaction; SQLite serialisiert Concurrent Writers, daher sollten Caller nach einem Lock- oder Uniqueness-Fehler die gesamte Transaction erneut versuchen.

import sqlite3
from dataclasses import dataclass

@dataclass(frozen=True)
class MemoryFact:
    tenant_id: str
    subject: str
    attribute: str
    value: str
    valid_from: str
    source_episode_id: str

def open_db() -> sqlite3.Connection:
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    db.executescript(
        """
        create table memory_facts (
            fact_id integer primary key,
            tenant_id text not null,
            subject text not null,
            attribute text not null,
            value text not null,
            valid_from text not null,
            valid_to text,
            source_episode_id text not null,
            check (valid_to is null or valid_from < valid_to)
        );
        create unique index one_open_fact
        on memory_facts (tenant_id, subject, attribute)
        where valid_to is null;
        """
    )
    return db

def replace_fact(db: sqlite3.Connection, fact: MemoryFact) -> None:
    with db:
        exact = db.execute(
            """
            select fact_id
            from memory_facts
            where tenant_id = ? and subject = ? and attribute = ? and valid_from = ?
            """,
            (fact.tenant_id, fact.subject, fact.attribute, fact.valid_from),
        ).fetchone()
        if exact:
            raise ValueError("equal valid_from requires an application conflict policy")

        containing = db.execute(
            """
            select fact_id, valid_to
            from memory_facts
            where tenant_id = ?
              and subject = ?
              and attribute = ?
              and valid_from < ?
              and (valid_to is null or valid_to > ?)
            limit 1
            """,
            (
                fact.tenant_id,
                fact.subject,
                fact.attribute,
                fact.valid_from,
                fact.valid_from,
            ),
        ).fetchone()
        if containing:
            db.execute(
                "update memory_facts set valid_to = ? where fact_id = ?",
                (fact.valid_from, containing["fact_id"]),
            )
            successor_boundary = containing["valid_to"]
        else:
            successor = db.execute(
                """
                select valid_from
                from memory_facts
                where tenant_id = ? and subject = ? and attribute = ? and valid_from > ?
                order by valid_from
                limit 1
                """,
                (fact.tenant_id, fact.subject, fact.attribute, fact.valid_from),
            ).fetchone()
            successor_boundary = successor["valid_from"] if successor else None

        db.execute(
            """
            insert into memory_facts
                (tenant_id, subject, attribute, value, valid_from, valid_to, source_episode_id)
            values (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                fact.tenant_id,
                fact.subject,
                fact.attribute,
                fact.value,
                fact.valid_from,
                successor_boundary,
                fact.source_episode_id,
            ),
        )

def fact_at(db: sqlite3.Connection, timestamp: str) -> sqlite3.Row:
    return db.execute(
        """
        select value, source_episode_id
        from memory_facts
        where tenant_id = ?
          and subject = ?
          and attribute = ?
          and valid_from <= ?
          and (valid_to is null or valid_to > ?)
        limit 1
        """,
        ("acme", "mira", "passport_deadline", timestamp, timestamp),
    ).fetchone()

db = open_db()
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-07-15", "2026-06-01T09:00:00Z", "e1"),
)
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-06-30", "2026-06-03T10:00:00Z", "e2"),
)
current = fact_at(db, "2026-06-04T00:00:00Z")
assert (current["value"], current["source_episode_id"]) == ("2026-06-30", "e2")

historical = fact_at(db, "2026-06-02T00:00:00Z")
assert (historical["value"], historical["source_episode_id"]) == ("2026-07-15", "e1")

# A delayed extraction predates e1. It ends at e1's existing boundary,
# rather than closing e2, which remains current.
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-08-01", "2026-05-30T08:00:00Z", "e0"),
)
delayed = fact_at(db, "2026-05-31T00:00:00Z")
assert (delayed["value"], delayed["source_episode_id"]) == ("2026-08-01", "e0")
current = fact_at(db, "2026-06-04T00:00:00Z")
assert (current["value"], current["source_episode_id"]) == ("2026-06-30", "e2")
intervals = db.execute("select valid_from, valid_to from memory_facts").fetchall()
assert all(row["valid_to"] is None or row["valid_from"] < row["valid_to"] for row in intervals)

episodes = (
    "e1: Mira prefers concise answers. Her passport deadline is 2026-07-15.",
    "e2: Mira corrected the deadline. It is now 2026-06-30.",
)
naive_match = next(episode for episode in episodes if "passport deadline" in episode)
assert naive_match.startswith("e1:")

replace_fact lehnt zunächst ein identisches valid_from ab: Dieser Conflict benötigt eine Application Policy, etwa Source Precedence oder eine vom User bestätigte Revision, statt eines stillen Overwrites. Andernfalls findet der Code das Interval, das den eingehenden Timestamp enthält. Wird eines gefunden, schließt er diesen Vorgänger an der eingehenden Grenze und gibt der eingefügten Row das frühere Ende des Vorgängers. Liegt der Timestamp vor allen gespeicherten Intervals, verwendet er das valid_from des nächsten Intervals als Ende der neuen Row. So wird ein verspätet eingehender Fakt vor e1 zu [2026-05-30, 2026-06-01) und lässt e1 sowie e2 unverändert. Die Row Factory macht row["fact_id"] und benannte Result Fields auf der hier erzeugten Default Connection gültig. Der Partial Unique Index ist die Database Guard für genau eine offene Row; ein Multi-Writer Deployment sollte eine zu seinem Workload passende Database- und Retry-Policy wählen. DM besitzt keinen entsprechenden Update-Schritt, daher kann der alte Text die Korrektur weiterhin überranken.

Die Assertions prüfen die aktuelle e2-Row, die historische e1-Row, das verzögerte Einfügen vor e1, die Interval-Invariante und die erste passende naive Text Episode:

Naive text memory:
  returned episode: e1 -> passport deadline is 2026-07-15

SGAM current state:
  mira.passport_deadline = 2026-06-30
  valid_from=2026-06-03T10:00:00Z, source=e2

SGAM point-in-time state:
  on 2026-06-02, mira.passport_deadline = 2026-07-15

Kombiniere diese Transaction in Production mit Structured Extraction auf dem Write Path. Die Database Transaction aktualisiert die zeitliche Gültigkeit. Das Model extrahiert einen Candidate Fact, entscheidet aber nicht, welche gespeicherte Row aktuell bleibt.


Storage Choices folgen dem Retrieval Pattern

Projekte verwenden verschiedene Bezeichnungen für Teile dieses Patterns: Memory Stores, Context Graphs, Profiles, Long-Term Stores, Graph RAG und Stateful Agents.

Tool oder FrameworkMain Storage LayerMechanismus für Temporal StateSchema-MechanismusPraktischer Einsatzbereich
GraphitiNeo4j, FalkorDB und Amazon Neptune; Kuzu ist deprecatedFact Validity Intervals plus Source-Episode-ProvenancePydantic Entity- und Edge-Types, Temporal Edges, ProvenanceSelf-hosted Temporal Graph Memory
ZepProprietary Context Graph EngineVerwaltete Temporal Context Graphs, die sich ändernde Fakten behaltenDefault- und Custom Entity- und Edge-TypesManaged Agent Memory und Governance
LangGraph / LangMemLangGraph Stores, Postgres-backed StoresApplication-owned Timestamps und Fields in Store RecordsJSON Stores plus Pydantic Profile- oder Collection-ExtractionAgent Apps, die bereits auf LangGraph basieren
Mem0Managed Stack, Valkey / Redis / Vector Backends in OSS SetupsMemory Updates; Temporal Policy bleibt Application-ownedMemory Types, Custom Categories, Extraction PromptsUser-, Agent- und Session-Memory als Service
Letta / MemGPTDatenbankbasierter Agent State und Memory BlocksEditierbare Blocks ohne Field-Level Validity IntervalsEditierbare gelabelte Memory BlocksStateful Agents mit OS-artigem Context Management
CogneeGraph plus Vector- und Relational-BackendsHistory hängt von Ontology und ausgewähltem Backend abOntology-orientierte Extraction und ValidationEnterprise Knowledge-Graph Memory
LlamaIndex Property GraphProperty-Graph Stores plus Vector StoresTime Fields hängen von Graph Schema und Store abSchemaLLMPathExtractor mit zulässigen Entities und RelationsGraph Extraction über Documents und Traces

Graphiti ist eine konkrete Open-Source-Implementierung von relationalem, temporalem Memory. Es verfolgt, wie sich Fakten ändern, hält Pointer auf Source Episodes vor und unterstützt Hybrid Retrieval. LangGraph trennt Thread Checkpoints von Cross-Thread Stores. Mem0 verpackt Memory Operations als Managed Service. Letta verwendet editierbare Context Blocks statt Field-Level-SGAM, behandelt Agent State aber ebenfalls als persistente Daten.

Beginne mit dem Data Model. Wenn der exakte Fact Lookup die wichtigste Operation ist, reicht eine Relational Table mit JSON Payloads, Validity Columns, Tenant Indexes und einem Vector Sidecar normalerweise aus. Füge einen Graph hinzu, wenn Relationship Traversal Teil des Produkts ist – nicht, weil die Graph-Demo beeindruckend aussieht.


Baue den Write Path vor dem Graph

Entscheide zuerst, woran sich das Produkt erinnern darf. Die Entscheidung zwischen Graph und Vector kommt später.

Ein Support Agent könnte sich Account Tier, Open Cases und dauerhafte Contact Preferences merken. Er sollte nicht jede frustrierte Nebenbemerkung in den Profile State übernehmen. Ein Coding Agent könnte sich Repo Conventions und ungelöste Tasks merken. Er sollte eine private Note nicht für immer behalten, nur weil sie einmal abgerufen wurde.

Beginne mit dem Write Path und behandle Memory als kleine State Mutation:

  1. Benenne den Memory Type, das Subject, den Tenant Scope und die Retention Class.
  2. Extrahiere Candidate Records mit Structured Output.
  3. Validiere das Payload mit Pydantic oder der Schema Layer, die dein Stack bereits verwendet.
  4. Löse Conflicts vor dem Insert, einschließlich der Frage, ob der neue Record einen alten ersetzt.
  5. Bewahre einen Source Pointer auf die Raw Episode, den Tool Result, die File, das Ticket oder die User Confirmation auf, durch die der Record entstanden ist.
  6. Schreibe die Schema Version mit jedem Record, statt sie nur im Application Code zu hinterlegen.

Der erste SGAM Store kann eine Relational Table mit einer JSON Column und einigen Indexes sein. Ein Graph wird nützlich, wenn das Produkt Relations wie Customer-to-Account, Account-to-Policy, Task-to-Artifact oder Project-to-Decision traversieren muss.

Hot Path und Background Writes

Immediate Extraction lohnt sich, wenn der nächste Turn vom neuen Memory abhängt. Wenn der User sagt „Merke dir, dass ich kurze Antworten bevorzuge“, sollte das System keinen Nightly Job benötigen, bevor es sich anders verhält.

Die meisten Turns benötigen keinen Immediate Write. Speichere die Raw Episode mit Tenant-, Session- und Tool-Metadata und lasse anschließend einen Background Worker Candidates extrahieren. Bei Recurrence-basierter Consolidation puffert der Worker schwache Signals und befördert einen Fakt erst, nachdem sich ähnliche Evidence wiederholt oder der User ihn bestätigt hat. Das erzeugt Freshness Lag. Für „Der User fragt häufig nach CSV-Exports“ ist das akzeptabel, für „Der Kunde hat die Lieferadresse geändert“ riskant.

Halte den Read Path deterministisch. Erzwinge Tenant Scope und Validity zuerst und verwende Fuzzy Retrieval anschließend nur dann, wenn es nützlichen Context ergänzen kann.

  1. Filtere nach Tenant, Memory Type und Validity Window.
  2. Rufe den exakten Structured State vor Semantic Neighbors ab.
  3. Verwende Vector- oder Graph-Expansion für unterstützende Evidence, verwandte Entities und Beispiele – nicht als Authority für aktuelle Fakten.
  4. Stelle den kleinsten zitierten Context zusammen, der die Frage beantworten kann.

Behandle eine Schema Migration als Product Change, weil sie verändert, woran sich der Agent erinnern, was er zitieren oder löschen kann. Sie kann auch ändern, welche historischen Fakten als aktuell gelten. Plane Migration Scripts, Backfills, Dual-Read Windows und Deletion Behavior im selben Release.


Wann sich SGAM lohnt

Verwende SGAM, wenn sich Fakten im Lauf der Zeit ändern können:

  • User Preferences, die aktualisiert oder widerrufen werden können
  • Customer- oder Account Facts mit Audit-Anforderungen
  • Task State für Long-running Assistants
  • Coding-Agent Project Memory
  • von mehreren Agents geteilter State
  • Compliance Notes, bei denen Provenance relevant ist
  • Temporal Questions wie „Was haben wir vor der Migration geglaubt?“

SGAM ist Overkill, wenn Memory kurzlebig, explorativ oder günstig neu berechenbar ist. Wenn der Agent nur einige Turns Continuity benötigt, reichen ein Checkpoint und eine gekürzte Message History aus. Für statische Document QA genügt möglicherweise RAG. Und wenn die Domain so unbeständig ist, dass sich das Schema täglich ändert, wird Typed Memory das Team ausbremsen.


Evaluation Checklist

Evaluiere den Memory Lifecycle ebenso wie die finale Answer. Ein System kann eine plausible Response erzeugen, obwohl es den falschen Fakt geschrieben, einen veralteten abgerufen oder eine Tenant-Grenze überschritten hat.

Ich verwende dieselbe Stage-by-Stage-Aufteilung wie in meinem RAG-Evaluation-Artikel. Messe die Stage, in der ein Fehler auftreten kann, statt die Evaluation auf den generierten Text zu beschränken. Die Trace-Disziplin aus dem Agent-Evaluation-Artikel gilt ebenfalls, weil ein Memory Bug häufig bereits in der Run History sichtbar wird, bevor er die Answer erreicht.

Ich würde SGAM mit Replay testen. Führe eine feste Sequenz von Episodes in den Memory Writer ein und untersuche das Ledger nach jedem relevanten Turn. Stelle anschließend Current-State- und Point-in-Time-Fragen an den resultierenden Store.

LayerGesuchter FehlerMeasures
Write ExtractionDer Agent hat einen Fakt übersehen, erfunden oder eine ungültige Form erzeugtSchema-valid Write Rate, Extraction Precision/Recall, Source-Episode-Coverage
Conflict HandlingEin veralteter Fakt blieb aktuell oder ein gültiger alter Fakt wurde überschriebenSupersession Correctness, Duplicate Rate, Stale-Fact-Invalidation-Correctness
Isolation und PolicyMemory ist zwischen Usern durchgesickert oder überlebte sein Policy Window hinausTenant-Isolation-Fehler, Deletion Correctness, Retention Compliance
Read RetrievalDer richtige Record existiert, wurde aber nicht abgerufenCurrent-State Accuracy, Point-in-Time Accuracy, Recall@k über Memory Records
Answer GroundingDie Answer nutzte Memory ohne Support oder zitierte die falsche SourceClaim Support gegen Source Episodes, Citation Accuracy, Conflict-Resolution-Correctness
OperationsDer Memory Path ist zu langsam, zu veraltet oder zu teuerp95 Write Latency, Freshness Lag, Read Latency, Cost per Query

Benchmarks wie LoCoMo, LongMemEval und ATM-Bench stellen öffentliche Test Cases bereit. Sie ersetzen keine Domain Test Suite. Ein Coding Assistant, ein Customer-Support-Bot und ein Compliance Copilot benötigen unterschiedliche Schemas, Filters, Retention Rules und Failure Tests.


Caveats

SGAM ist meine Bezeichnung für ein Pattern, kein Standard. Bestehende Projekte teilen das Problem unterschiedlich auf. LangGraph Memory und LangMem beschreiben Short-Term- und Long-Term Stores, Profiles, Collections, Hot-Path Writes und Background Memory Managers. Zep Graphiti verwendet den Begriff Temporal Context Graph. Letta persistiert editierbare Memory Blocks, während Mem0 eine Managed Memory Layer anbietet. Microsoft GraphRAG, LlamaIndex Property Graphs und Cognee fassen verwandte Teile des Problems als Knowledge Graphs auf.

Ein User Profile, ein Episode Log, ein Document Graph und ein vom Agent editierbarer Memory Block lösen unterschiedliche Retrieval- und Update-Probleme. Ich verwende SGAM ausschließlich für dauerhaftes Memory, das den aktuellen Application State repräsentiert und deshalb Schema, Validity, Provenance, Conflict Handling, Retention und Migration benötigt.

Typed Memory kann weiterhin falsch sein. Ein Schema macht fehlerhafte Writes leichter überprüfbar; vertrauenswürdig werden sie dadurch nicht. Du benötigst weiterhin Source Trust, User Confirmation für sensitive Fakten, Conflict Policy, Deletion und Monitoring.

Schema Migration ist Arbeit. Sobald Memory zu State wird, gehören Versioning, Backfills, alte Records und Deletion Behavior zu deiner Verantwortung. Wenn du diese Arbeit überspringst, werden alte Records die Semantics oder Retention Policy überleben, unter denen sie entstanden sind.


References