Novaberg
Novaberg Papers · II of III
KategorieThought Leadership
Lesezeit≈ 22 Minuten
ZielgruppeArchitekten & Praktiker
ImplementierungLangGraph · TypedDict
LizenzApache 2.0

Das Clipboard Pattern:
wie man KI-Agenten richtig komponiert.

Multi-Agenten-Systeme sollten nicht tratschen. Sie sollten eine Fallakte teilen.

Von Claus Schlehhuber · Unabhängige Forschung · April 2026 · master@novaberg.de

Abstract

Multi-Agenten-LLM-Systeme koppeln spezialisierte Agenten üblicherweise über natürlichsprachliche Nachrichten. Ein Agent erzeugt ein Textresultat. Ein anderer Agent liest dieses Resultat, kodiert es in seinem eigenen Vokabular neu und produziert seinen eigenen textuellen Output. Ich argumentiere, dass das ein architektonisches Anti-Pattern ist, kein beabsichtigtes Feature.

Jeder natürlichsprachliche Umkodierungsschritt ist eine verlustbehaftete Kompression: er löscht Typen, Struktur und Herkunft. Er führt semantische Drift ein, die sich mit jedem Hop akkumuliert. Er macht einzelne Agenten untestbar, weil ihre Ein- und Ausgaben Strings sind. Und er multipliziert das Token-Budget für alles Nicht-Triviale.

Ich schlage das Clipboard Pattern vor: ein geteiltes, typisiertes State-Objekt, erzwungen durch ein TypedDict-Schema, das durch eine Sequenz spezialisierter Nodes innerhalb einer kognitiven Einheit (einem LangGraph) fließt. Jeder Spezialist liest die Felder, die er braucht, schreibt seine Ergebnisse in strukturierte Felder und gibt das Clipboard an den nächsten Spezialisten weiter. Kein Message Passing. Kein Umkodieren.

Dieser Artikel zeigt, wie das Pattern in Novabergs Graphen implementiert ist, wie eine dreistufige Taxonomie — Rollen, Fachabteilungen, Graphen — Komposition auch bei wachsender Komplexität sauber hält, und warum ein einzelnes typisiertes State-Objekt der technische Ausdruck des Prinzips ist: Daten vollständig transportieren, Formatierung dem Konsumenten überlassen.

§ 1
Einleitende Metapher

Eine Anwaltskanzlei schickt keine E-Mails zwischen Anwalt und Buchhalter.

Eine Fallakte wandert von Schreibtisch zu Schreibtisch. Der Prozessanwalt liest die Fakten, entwirft eine Rechtsposition, schiebt die Akte zum Paralegal. Der Paralegal recherchiert die Präzedenzfälle, heftet sie in die Akte, schiebt sie zurück. Niemand schreibt E-Mails aneinander. Niemand fasst die Akte in eigenen Worten zusammen. Die Akte ist die Wahrheit. Jeder trägt einen Abschnitt bei. Am Ende ist die Akte das Ergebnis.

Jetzt schaut man sich die meisten Multi-Agenten-LLM-Systeme in der freien Wildbahn an. Ein Agent erzeugt einen Absatz. Ein anderer Agent liest diesen Absatz, schreibt seinen eigenen Absatz zurück. Ein dritter Agent schreibt über beide Absätze. Wenn der Workflow endet, kann einem niemand sagen, was passiert ist, und die Tokens, die man bezahlt hat, wurden dafür ausgegeben, dass drei Agenten jeweils die Zusammenfassung des Vorgängers in ihrer eigenen Stimme umgeschrieben haben.

Wenn man die beiden Bilder nebeneinander stellt: Eines ist offensichtlich, wie ernsthafte Arbeit funktioniert. Das andere ist ein Kinderspiel, verkleidet als Orchestrierung. Der Rest dieses Artikels ist eine Verteidigung des ersten Bildes und eine Kritik des zweiten — fundiert in Code, in einem funktionierenden System und in dem pragmatischen Eingeständnis, dass dieses Pattern nicht neu ist. Es hat nur noch keinen Namen.

Listing 1 Zwei Wege, Agenten zu komponieren python
# Pattern A — Agent-zu-Agent-Nachrichten
result = agent_b.invoke(agent_a.invoke(user_message))

# Pattern B — Clipboard
state = agent_a(state)
state = agent_b(state)
Beides sieht ähnlich aus. Nur bei einem weiß man, was Agent B empfangen hat.
§ 2
Das Standard-Pattern und seine Probleme

Vier Schäden akkumulieren sich bei jedem Hop.

Das Standard-Pattern in populären Multi-Agenten-Frameworks — CrewAI, AutoGen und den meisten „Agent-Supervisor"-Templates im LangChain-Ökosystem — ist dieses: Spezialisierte Agenten kommunizieren, indem sie einander natürlichsprachliche Strings senden. Der Output eines Agenten wird zum Prompt des nächsten. Das Framework kümmert sich um Scheduling und Übergaben. Der Anwendungsentwickler schreibt Rollen und Instruktionen; die Agenten erledigen den Rest.

Das ist das Pattern, das Marina Wyss in ihrem Überblick von 2025 sorgfältig dokumentiert, „AI Agents: Complete Course" — eine 150-seitige Synthese einer Senior-Praktikerin bei Amazon. Sie widmet ein Kapitel den „Communication Pitfalls", und sie hat nicht Unrecht damit, was diese sind. Worin ich widerspreche, ist das vorgeschlagene Heilmittel. Das Heilmittel des Konsens ist bessere Prompts und klarere Rollen. Ich denke, das Heilmittel muss architektonisch sein. Die Fallstricke sind keine Verhaltensweisen, die das Paradigma zufällig erzeugt; sie sind das, was das Paradigma ist.

Schaden eins — semantische Drift

Jedes Mal, wenn ein Agent den Text eines anderen Agenten liest und seinen eigenen schreibt, verändert sich etwas Subtiles. Der empfangende Agent hat seinen eigenen Vokabularkanon, seinen eigenen Sinn für das Wesentliche, seine eigenen trainingsdateninduzierte Priors. Er formuliert die Aussage in seiner eigenen Stimme um. Diese Stimme ist nicht identisch mit der Quelle. Nach zwei Hops ist der Fehler noch klein. Nach fünf ist die ursprüngliche Intention ein Gerücht.

In einem System, das ich auditiert habe, sollte ein Legal-Review-Agent einen Vertrag prüfen. Was er bekam, war nicht der Vertrag. Es war die Zusammenfassung des Compliance-Officers über den Vertrag. Die Zusammenfassung hatte die Kündigungsklausel weggelassen. Der Legal-Review-Agent fand pflichtbewusst nichts Auffälliges. Niemand war schuld. Die Architektur war schuld.

Schaden zwei — Token-Kosten

Jeder natürlichsprachliche Hop kostet Tokens. Der empfangende Agent muss den Text des Senders lesen und seinen eigenen generieren. Wenn vier Agenten in der Schleife sind, bezahlt man vier Umkodierungen im Wesentlichen derselben Information. Ein typisiertes Feld, das confidence: 0.83 sagt, kostet ein Token im Prompt und null Tokens zum Lesen. Ein Absatz, der sagt „Ich bin ziemlich zuversichtlich, vielleicht so achtzig Prozent oder so" kostet fünfzehn Tokens zum Schreiben und fünfzehn zum Lesen und sagt dem nächsten Agenten weniger.

Schaden drei — Untestbarkeit

Ein Agent, dessen Input ein Freitext-String ist, kann in keiner sinnvollen Weise mit Unit-Tests getestet werden. Man kann behaupten, dass der Output „die Kündigung erwähnt" — und diese Assertion besteht oder scheitert nach Laune der Generierung. Es gibt keinen Vertrag zum Erzwingen und kein Fixture zum Pinnen. Produktive LLM-Arbeit ist Arbeit; sie verdient Tests. Strings-rein, Strings-raus ist das Gegenteil von Tests.

Ein typisiertes Feld hingegen ist etwas, worüber man Assertions schreiben kann. state["agent_results"][0].status == "abgeschlossen" gilt oder gilt nicht. Der Node, der es erzeugen soll, kann mit Fixture-Input angetrieben und mit Fixture-Output verifiziert werden. Die CI-Götter lächeln.

Schaden vier — Audit-Trail

Eine Spur natürlichsprachlicher Austausche ist, technisch gesehen, ein Audit-Trail. Praktisch ist es eine Ausgrabungsstätte. Wer hat entschieden, den Präzedenzfall aufzunehmen? Warum wurde die Kündigungsklausel gestrichen? Man kann den Austausch lesen und raten. Ein typisierter State, geschrieben von benannten Nodes mit deklarierten Write-Sets, lässt einen diese Fragen in O(1) beantworten: das Feld wurde von diesem Node in diesem Graph-Schritt gesetzt.

Tabelle I · Vier Patterns, vier Trade-offs
PatternKommunikationSymptom
Agent-zu-Agent-Nachrichten
CrewAI · AutoGen
Text-Strings Semantische Drift, Token-Kosten, String-I/O
Supervisor + Tool-Handoff
LangChain · ReAct
Text + implizite Tool-Args Besser, aber das Arg-Schema ist implizit
Shared-Memory-Blöcke
Letta / MemGPT
LLM-editierter Speicher Flexibel, nicht-deterministisch
Clipboard
Novaberg · LangGraph
Typisierter State + Dispatch Deterministisch, testbar, keine Drift

Nichts davon ist die Behauptung, dass Text-Messaging nie die richtige Wahl ist. Es ist die Behauptung, dass Text-Messaging als Standard-Kopplung zwischen Spezialisten teuer, fragil und undurchsichtig ist. Der Standard muss sich ändern.

§ 3
Das Pattern, in Code

Ein Clipboard ist ein typisiertes Dictionary, in das eine Funktion schreibt und die nächste liest.

Das Clipboard Pattern ist kein Framework. Es ist eine Disziplin. In Python wird die Disziplin durch ein TypedDict und durch die Laufzeitgarantien eines State-Graphen wie LangGraph erzwungen. Die Regeln sind drei.

Erstens gibt es genau ein State-Objekt pro kognitiver Einheit — ein Clipboard pro Fall, wenn man so will. Zweitens deklariert jeder Node per Konvention, welche Felder er liest und welche er schreibt; ein Reviewer, der den Code liest, kann den Abhängigkeitsgraphen der Pipeline erstellen, ohne sie auszuführen. Drittens gibt es keine Nachrichten. Nodes reden nicht miteinander. Sie reden mit dem State.

Listing 2 Der State, typisiert python · TypedDict
class ConversationState(TypedDict):
    user_input:         str
    intent:             str
    current_emotion:    str
    needs_memory:       bool
    memory_context:     str
    agent_name:         str                  # one agent per turn
    agent_results:      list[AgentResult]     # audit trail
    response:           str
Ein minimaler Auszug. Der echte ConversationState in Novaberg trägt über sechzig Felder — jedes mit einem einzelnen deklarierten Writer und einem oder mehreren Readern.

Die entscheidende Disziplin liegt nicht im TypedDict selbst — Python lässt einen bereitwillig über die Klippe rennen — sondern in der Code-Review-Kultur drumherum. Eine Node-Funktion hat die Form State → State. Sie empfängt das Clipboard, modifiziert ihre eigenen Felder und gibt es zurück. Das ist der gesamte Vertrag. Betrachten wir den Planner, der Arbeit innerhalb des Charakter-Graphen routet:

Listing 3 Planner und Dispatch python · Node-Form
def planner_node(state: ConversationState) -> ConversationState:
    # reads:  intent, management_target, agent_results
    # writes: agent_name  (empty when nothing left to do)
    state["agent_name"] = next_agent_to_run(state)
    return state


def agent_dispatch_node(state: ConversationState) -> ConversationState:
    # reads:  agent_name, state (as a whole — the clipboard)
    # writes: agent_results (+1), agent_name = ""
    dispatch = find_dispatch(state["agent_name"])
    state["agent_results"].append(dispatch(state))
    state["agent_name"] = ""
    return state


# LangGraph conditional edges:
#   planner -> agent_dispatch   if agent_name != ""
#   planner -> responder        if agent_name == ""
#   agent_dispatch -> planner   (loop: planner decides anew)
Keine agent_queue. Kein Batch. Der Planner trifft genau eine Entscheidung pro Durchlauf — „welcher Agent, falls einer" — und der Dispatch führt ihn aus. Multi-Agenten-Turns entstehen durch Iteration, nie durch eine vorbefüllte Liste; spätere Agenten können auf den Ergebnissen früherer aufbauen, und der Planner beendet die Schleife, sobald die Antwort reif ist.

Zwei Dinge verdienen es, laut gesagt zu werden. Der Unterschied zum Text-Messaging-Pattern ist nicht stilistisch; er ist Typsicherheit. Der Unterschied zu einem queue-basierten Scheduler ist auch nicht stilistisch; er ist Observability. Jede Iteration der Schleife ist ein eigener Graph-Schritt mit einem vollständigen State-Snapshot — man kann pausieren, inspizieren, replaying. Eine Queue würde dieselben Entscheidungen im lokalen Scope einer Funktion verstecken.

Der Dispatch — eine Tür zwischen zwei Welten

Das Wort „Dispatch" in agent_dispatch_node verbirgt ein kleines, aber folgenreiches Pattern. Ein Spezialistenagent lebt in seiner eigenen Welt — seinem eigenen Vokabular, seinem eigenen Subgraphen, seinem eigenen State-Schema. Der Planner kann davon nichts wissen. Der Planner kennt nur den äußeren ConversationState. Irgendetwas muss zwischen dem äußeren Clipboard und dem inneren des Agenten übersetzen. Dieses Etwas ist der Dispatch.

Eines der Prinzipien, auf die ich in Design-Notizen immer wieder zurückgekommen bin, ist dieses:

“Dispatch und Node sind eine Einheit, wie du und ich. Ein Dispatcher für alles wäre zu unflexibel und zu ungenau.”

Jede Fachabteilung liefert ihren eigenen Dispatch. Der Notizen-Agent hat eine dispatch.py, die die Teile des ConversationState auspackt, die er braucht — den aktuellen Intent, die Emotion des Users, die relevanten Gedächtnisfragmente — in einen abteilungslokalen AgentState, führt den Agenten aus und faltet das AgentResult zurück in das äußere Clipboard. Kein zentraler Router fasst es an. Plugin-förmig von Konstruktion.

Listing 4 Der Notizen-Agent auf der Festplatte Verzeichnisstruktur
agents/notizen/
├── agent.py           # Subgraph-Verdrahtung (LangGraph-Nodes)
├── klassifikation.py  # Classify    — Aktion erkennen
├── suche.py           # Resolve     — Ziel-Eintrag finden
├── crud.py            # CRUD        — Erstellen/Lesen/Ändern/Löschen
├── resume.py          # Resume      — nach Rückfrage fortsetzen
├── bestaetigung.py    # Confirm     — Ergebnis für den Responder formulieren
├── dispatch.py        # ConversationState ↔ AgentState
├── init.sql           # Schema (bi-temporal, Soft-Delete-indiziert)
├── __init__.py        # Package-Marker für Auto-Discovery
└── AGENT.md           # Fähigkeiten, Trigger, Tests
Die Dateien sind auf Deutsch benannt, weil Novabergs Fachsprache der Sprache des Users folgt. Ein englischsprachiges Deployment würde sie classify / resolve / crud / resume / confirm nennen. Die Dateien sind der Punkt, nicht die Namen.

Einen neuen Agenten hinzufügen bedeutet, ein neues Verzeichnis mit eigener dispatch.py und eigenem init.sql anzulegen. Kein zentraler Router-Code wird angefasst. Auto-Discovery findet den Dispatch genauso wie den Agenten. Das Schema lebt beim Agenten, nicht in einer monolithischen Migrations-Datei — denn die Fachabteilung, die das Verhalten besitzt, besitzt auch die Speicherung.

Die Normalisierungsschicht — wo Sprache zu Struktur wird

Der Dispatch löst die Grenze zwischen dem äußeren Clipboard und dem inneren. Aber es gibt eine tiefere Grenze innerhalb des Agenten selbst: die Grenze zwischen dem, was der User gesagt hat, und dem, was der Spezialist hören muss. Sie sauber zu überschreiten ist das, was einen Domain-Agenten von einem cleveren Prompt in ein verlässliches Stück Software verwandelt.

Ein Patient kommt in eine Klinik. Er sagt zur Empfangsdame: „Irgendwas stimmt nicht, meine Brust wird eng wenn ich Treppen steige, und mir war die ganze Woche schwindelig." Die Empfangsdame diagnostiziert nicht. Sie routet — Kardiologie, nicht Dermatologie. Das ist der Router. In der Kardiologie-Aufnahme nimmt eine Schwester die Worte des Patienten und füllt ein Aufnahmeformular aus: Belastungsdyspnoe, Vertigo, sieben Tage, keine kardiale Vorgeschichte. Das ist der Classify-Node. Er übersetzt natürliche Sprache in das Fachvokabular der Abteilung.

Der Kardiologe liest das Aufnahmeformular. Er bittet den Patienten nicht, die Geschichte nochmal zu erzählen. Er liest strukturierte Beobachtungen in seiner eigenen Sprache und entscheidet, was als nächstes kommt. Das ist die CRUD. Die Originalworte des Patienten — die Sorge, die Formulierung, der Ton — sind nicht verloren. Sie stehen in ihren eigenen Feldern auf dem Clipboard, gelesen vom Responder, wenn es Zeit ist, dem Patienten in der Sprache des Patienten zu antworten. Aber der Spezialist sieht sie nie. Der Spezialist arbeitet auf validierten, domänenspezifischen Daten. Zwei Schichten innerhalb desselben State-Objekts, konsumiert von verschiedenen Nodes.

Fachsprachen-Normalisierung

Novaberg nennt das Fachsprachen-Normalisierung. Jede Fachabteilung deklariert ihr eigenes Fachvokabular — die Aktionen, die sie erkennt, die Entitäten, auf denen sie arbeitet, die Form, die sie erwartet. Der Classify-Node übersetzt den natürlichen Satz des Users in dieses Vokabular, inline, als Teil seines existierenden LLM-Calls. Kein Extra-Roundtrip. Keine separate NLU-Pipeline. Der Output ist ein einzelnes strukturiertes Feld, normalised, auf das sich die nachgelagerten Rollen verlassen können.

Listing 5 Von Sprache zu Struktur ein Turn · zwei Schichten
User sagt:      "Hau die Bananen von der Liste runter."

Classify:       action      = remove_content
                target      = "Einkaufsliste"
                normalised  = "remove_content: Bananen
                               von Notiz 'Einkaufsliste' entfernen"

CRUD liest:     action, target, normalised
Responder:      liest user_input — bewahrt den Ton der Antwort
Die CRUD parst den Satz nie. Sie empfängt eine benannte Aktion und ein lokalisiertes Ziel. Der Responder liest die Originalworte — verschiedene Konsumenten, verschiedene Felder, selbes Clipboard.

Dasselbe Prinzip von der anderen Seite. Ein Architekt sagt zum Kunden: „Wir nehmen Eichenbalken, lasiert in einem warmen Grau." Der Kunde nickt. Aber der Statiker, der die Akte aufnimmt, liest nicht „warmes Grau." Er liest: Tragfähigkeit 14 kN/m, Spannweite 4,2 m, Querschnitt 120 × 240 mm. Architekt und Statiker teilen eine Akte. Sie teilen kein Vokabular. Die Akte trägt beide Schichten — die kundenseitige Beschreibung und die ingenieurseitige Spezifikation — und jeder Leser nimmt, was ihm gehört.

Fünf Rollen, weil Separation es verlangt

Das ist der Grund, warum eine Fachabteilung fünf spezialisierte Rollen hat — nicht weil die Domäne kompliziert ist, sondern weil jede Rolle von der Arbeit entlastet werden muss, die nicht zu ihr gehört. Ein einzelner Agent, der alle fünf in einem Prompt erledigen soll, bricht unter der Kontextlast zusammen. Fünf Rollen halten fünf kleine Kontexte, jeder einzeln testbar.

Rolle eins — Classify

Übersetzt. Er überbrückt die Kluft zwischen natürlicher Sprache und einer benannten Aktion. Er füllt das Aufnahmeformular der Fachabteilung in der Fachsprache der Abteilung aus. Alles Nachgelagerte arbeitet auf dem, was Classify produziert hat — nicht auf dem, was der User tatsächlich gesagt hat.

Rolle zwei — Resolve

Findet. Er lokalisiert die richtige Entität — die richtige Liste, den richtigen Termin, die richtige Notiz — mittels Fuzzy Search mit Score-Gap-Disambiguierung und einem Embedding-Fallback. Er interpretiert nie den Intent. Wenn der Match mehrdeutig ist, signalisiert er eine Rückfrage statt zu raten.

Rolle drei — CRUD

Führt aus. Genau die Operation, die Classify benannt hat, auf der Entität, die Resolve lokalisiert hat. CRUD liest nie die Originalworte des Users. Ihr Input besteht vollständig aus den strukturierten Feldern, die ihre Upstream-Peers geschrieben haben. Das ist es, was das vierphasige Hardening — erkennen, validieren, ausführen, verifizieren — überhaupt erst möglich macht.

Rolle vier — Resume

Setzt fort. Wenn im letzten Turn eine Rückfrage nötig war — „Welche Liste, Haushalt oder Büro?" — nimmt Resume beim nächsten User-Input den wartenden State auf und startet den Subgraphen vom Pausenpunkt neu. Die Pause ist kein Fehlerpfad; sie ist ein erstklassiger State, den das Clipboard halten kann.

Rolle fünf — Confirm

Formuliert das Ergebnis in domänengerechter Sprache, damit der Responder es in die Antwort einweben kann. Confirm ist die einzige Rolle, die innerhalb des Agenten natürliche Sprache produziert, und sie produziert einen Satz, für einen Konsumenten. Der Responder besitzt weiterhin die nutzerseitige Stimme; Confirm reicht ihm nur ein sauberes Faktum.

Warum das skaliert

Die Fachsprache ist es, die eine neue Abteilung zu einer kleinen statt einer großen Änderung macht. Eine Abteilung hinzufügen bedeutet, ein neues Vokabular zu deklarieren — die Aktionen, die Entitäten, eine Handvoll Übersetzungsbeispiele — und ein neues Verzeichnis neben die anderen zu legen. Kein zentraler Code wird angefasst. Kein Koordinator muss die neue Domäne lernen. Die Abteilung kommt mit eigenem Aufnahmeformular, eigenen Spezialisten, eigenem Dispatch. Das Clipboard trägt, was sie schreiben.

Das Clipboard ist also nicht nur ein Datentransport-Mechanismus. Es ist die Übersetzungsgrenze zwischen menschlicher Sprache und maschinenlesbarer Aktion — und es hält beide Repräsentationen lebendig, Seite an Seite, für die verschiedenen Leser, die sie brauchen.

§ 4
Ein echtes Beispiel, von Anfang bis Ende

„Pack Butter auf die Einkaufsliste." Was dann passiert.

Metaphern und Schemata reichen nur so weit. Hier ist, was tatsächlich in Novaberg läuft, wenn ein User einen Satz tippt. Novabergs Event-Modell teilt einen Gesprächs-Turn auf zwei Graphen auf: Pfad 1 — der HumanGraph — nimmt den Turn des Users wahr und persistiert ihn; Pfad 2 — der CharacterGraph — generiert die Antwort. Sie sind durch eine Redis-Event-Queue entkoppelt. Der User bekommt ein sofortiges 202 Accepted nach Pfad 1, und die Antwort kommt später über einen WebSocket. Die Latenz ist unverändert; die Graphen sind frei, spezialisiert zu sein.

Abbildung 1 Zwei Graphen, eine Fallakte
Path 1 · HumanGraph · synchron 1. Perception intent, emotion 2. Enricher KZG · LZG · hash 3. EI-Calc user stream 4. Salience pending writes 5. Dispatcher turn + KZG Redis event queue Path 2 · CharacterGraph · asynchron Enricher KZG · LZG · hash EI-Calc character stream Router update · notizen Planner agent_name="notizen" Dispatch · NotizenAgent classify → resolve → crud → confirm schreibt in agent_results Planner agent_name="" Responder formatiert Antwort User Antwort Iteration bis agent_name == ""
Abb. 1 — Das Clipboard (ConversationState) bewegt sich von links nach rechts durch jeden Node. Kein Pfeil trägt einen String zwischen Peers; jeder Pfeil ist eine State-Transition. Die gestrichelte Verbindung zwischen den Pfaden ist ein Redis-Event; die beiden Pfade teilen eine Session, aber keinen Graphen. Qualitätskontroll-Nodes (Thinker, Tribunal, Corrector) und die Selbstwahrnehmung des Charakters sind der Übersichtlichkeit halber weggelassen.

Pfad 1 — HumanGraph

Perception extrahiert intent="task", topic="shopping", emotion="neutral". Der Enricher lädt die Session, das Kurzzeitgedächtnis, das Langzeitgedächtnis und den Character-Hash. EI-Calc läuft auf dem User-Stream: die Emotions-Trajektorie des Users, der aktuelle Affektvektor, ein Plausibilitätscheck auf den Kommunikationsmodus. Salienz entscheidet, dass „Butter / Einkaufsliste" es wert ist, sich zu merken, und fügt einen Pending-Write für den Kurzzeitspeicher hinzu. Der Dispatcher persistiert den Turn und emittiert ein Event auf die Redis-Queue. Kein Agenten-Call hier. Pfad 1 nimmt wahr und speichert. Fertig.

Pfad 2 — CharacterGraph

Der Graph des Charakters nimmt das Event auf. Sein eigener Enricher lädt, was er braucht. Der Router sieht management_action="update" und flaggt die Notizen-Abteilung. Der Planner setzt agent_name="notizen". Agent Dispatch übersetzt das äußere Clipboard in den AgentState des Notizen-Agenten, führt den Subgraphen aus — Classify erkennt eine Append-Aktion, Resolve findet die Einkaufsliste per Name mittels Fuzzy Matching mit Embedding-Fallback, CRUD hängt „Butter" an, Confirm formuliert das Ergebnis — und faltet das AgentResult zurück in agent_results.

Der Planner sieht jetzt ein frisches Ergebnis und entscheidet, dass nichts mehr zu tun ist: agent_name="". Der Responder liest agent_results[0].ergebnis und schreibt: „Erledigt — Butter steht auf der Einkaufsliste." An jedem Punkt zwischen Pfad 1 und dem Moment, in dem der User diesen Satz sieht, war der State typisiert, lesbar und pausierbar.

Der Punkt

Wenn man den State nach jedem Node ausgibt, bekommt man eine vollständige kausale Spur des Turns — kein Transkript davon, was ein Modell über das gesagt hat, was ein anderes Modell über das gesagt hat, was der User gesagt hat. Das Clipboard ist die Konversation. Der nutzerseitige Satz ist ein Seiteneffekt.

§ 5
Drei Ebenen: Rollen, Fachabteilungen, Graphen

Eine Taxonomie, zu der Novaberg konvergiert — kein Interface, das es erzwingt.

Skalierung ist, wo Designentscheidungen sich entweder potenzieren oder verfallen. Wenn jede neue Fähigkeit erfordert, eine zentrale Datei anzufassen, wird ein System pro hinzugefügter Fähigkeit weniger wartbar. Das Clipboard Pattern skaliert — in Novaberg zumindest — weil die Fachabteilungen sich um drei wiederkehrende Ebenen organisieren, nicht weil irgendein Framework es verlangt. Ich möchte hier vorsichtig sein. Die drei Ebenen sind ein Pattern, in das sich der Code eingependelt hat, kein Vertrag, den die Laufzeit verifiziert.

Ebene 1 — Rollen

Rollen sind wiederkehrende funktionale Muster, die die meisten Workflow-Agenten teilen. Es gibt fünf davon, und sie haben sich ungezwungen herauskristallisiert. Classify entscheidet, welche Art von Aktion angefragt wird. Resolve findet die Ziel-Entität — die richtige Einkaufsliste, den richtigen Termin, die richtige Notiz. CRUD führt die Datenoperation aus. Resume behandelt den Fall, dass der Agent nach einem Klärungs-Turn wieder betreten wird („Welche Liste? Die vom Haushalt oder die vom Büro?"). Confirm formuliert das Ergebnis in domänengerechter Sprache, damit der Responder es in die Antwort einweben kann.

Jede Rolle lebt in ihrer eigenen Datei. Die Form ist eine Konvention, keine Subklasse von irgendwas. Workflow-Agenten teilen die Konvention; Agenten, deren Arbeit anders geformt ist, weichen ab, absichtlich. Die Charakterisierungs-Agenten (die Novas Selbstmodell aktualisieren) haben keinen Resolve-Schritt; sie haben ein anderes Skelett. Das ist in Ordnung. Die Taxonomie ist deskriptiv, nicht präskriptiv.

Ebene 2 — Fachabteilungen

Eine Fachabteilung ist eine Komposition von Rollen, spezialisiert auf eine Domäne. Die Notizen-Abteilung (notizen), die Timeline-Abteilung, die Charakter-Identitäts-Abteilung — alle teilen das Fünf-Rollen-Skelett, jede instanziiert es mit ihrem eigenen Vokabular, ihrer eigenen Speicherung, ihrer eigenen Domänensemantik. In Novabergs aktuellem Codebase gibt es elf Agenten-Verzeichnisse, jedes mit eigenem Dispatch.

Ebene 3 — Graphen

Ein Graph ist eine kognitive Einheit. Er orchestriert Fachabteilungen zu sinnvollen Workflows. Novaberg betreibt drei kompilierte Graphen: den HumanGraph (nimmt den Turn des Users wahr und speichert ihn), den CharacterGraph (entscheidet und antwortet) und einen kleineren AgentGraph für den Hintergrund-Worker. Jeder Graph hat sein eigenes State-Schema — wobei Conversation­State zwischen HumanGraph und CharacterGraph geteilt wird und dieselbe Session weiterführt.

Warum die Taxonomie deskriptiv ist

Ich möchte der Versuchung widerstehen, das in ein formales Framework zu verwandeln. Der Wert liegt nicht in einer Role-Basisklasse oder einer Department-Registry. Der Wert liegt darin, dass die Wiederholung sichtbar wird. Wenn man weiß, dass ein neuer Agent wahrscheinlich Classify/Resolve/CRUD/Resume/Confirm brauchen wird, beginnt man mit einem Template und überschreibt, wo die Domäne es verlangt. Wenn man weiß, dass ein Graph Arbeit mittels Planner → Dispatch → Planner → Responder routet, erkennt man die Form sofort in einer neuen Codebasis. Die Taxonomie ist zuerst eine Lesehilfe und zweitens ein Faktorisierungshinweis. Sie ist kein Typsystem.

Ein Leser, der nichts anderes aus diesem Artikel mitnimmt, sollte dies mitnehmen: Das Clipboard Pattern ist das Primitiv. Die drei Ebenen sind, wie der Code aussieht, nachdem man das Primitiv lange genug anwendet.

§ 6
Was man gewinnt

Determinismus, Testbarkeit, Token-Disziplin.

Das Clipboard Pattern zu übernehmen verändert, was man über das Verhalten seines Systems sagen kann. Nicht in Abstraktionen — in konkreten Aussagen, die entweder gelten oder nicht.

Determinismus, wo man ihn will

Eine Node-Funktion ist State → State. Wenn sie nicht absichtlich stochastisch ist — die meisten Routing-, Planning-, Dispatch-Nodes sind es nicht — dann ist sie deterministisch. Gleiches Clipboard zweimal eingeben, gleiches Clipboard zweimal bekommen. Das kann man über eine Agent-zu-Agent-Konversation nicht sagen; allein die Model-Temperature sorgt dafür, dass derselbe Prompt beim zweiten Mal einen anderen Absatz erzeugt.

Unit-Tests, die tatsächlich Verhalten pinnen

Der Test eines Nodes sieht so aus: Fixture-State bauen, Node aufrufen, auf benannte Felder des Outputs asserten. Planner bekommt agent_results[0].status == "abgeschlossen" und sollte beenden — tut er's? Dispatch empfängt agent_name="notizen" und sollte den Notizen-Dispatch aufrufen — tut er's? Das sind echte, schnelle, robuste Tests. Eine Agent-zu-Agent-„Assertion", dass der Output „die Kündigung erwähnt", ist es nicht.

Token-Disziplin, gratis

Felder sind Felder. Ein confidence: 0.83 ist eine Zahl, kein Absatz. Natürliche Sprache kommt in der Pipeline genau zweimal vor: einmal am Eingang (der Turn des Users) und einmal am Ausgang (die Antwort des Responders). Jede Stufe dazwischen arbeitet auf strukturierten Daten. Token-Verbrauch folgt direkt: ein Perception-Call, ein Response-Call, plus was auch immer die interne Arbeit des Agenten erfordert. Vergleiche das mit einem Vier-Agenten-Messaging-System, wo jeder Hop ein kompletter Roundtrip ist.

Audit-Trails, by Construction

LangGraph persistiert State-Snapshots zwischen Nodes. Ein Produktions-Incident wird zum Replay: den Snapshot für Graph-Schritt 7 laden, den Node im Debugger ausführen, die Entscheidung beobachten. Der Trail ist kein Transkript; er ist das Clipboard an jedem Schreibtisch.

§ 7
Was man aufgibt (und warum das in Ordnung ist)

Emergentes Agentenverhalten ist nicht das, wofür man es hält.

Der häufigste Einwand, den ich gegen das Clipboard Pattern höre, ist, dass es emergentes Verhalten opfert — die Sache, bei der zwei Agenten einen durch ihren Austausch überraschen, indem sie eine bessere Antwort produzieren, als jeder allein könnte. Darin steckt ein Körnchen Wahrheit. Für die Art von Workflow, die ich beschrieben habe — ein User-Turn, der eine fundierte, korrekte, persistente Antwort will — ist emergentes Verhalten eine Belastung, kein Vorteil. Man will nicht, dass der Compliance-Beauftrage und der Jurist improvisieren. Man will, dass sie die Akte lesen.

Wo freie Agentenkonversation wirklich hilft — Brainstorming, Debatte, Verhandlung, Rollenspiel — ist das Clipboard Pattern das falsche Werkzeug. Baut das mit einem Messaging-Substrat und akzeptiert die Token-Kosten. Aber die meisten Systeme, die als „Multi-Agent" beworben werden, sind Pipelines. Eine Pipeline eine Konversation zu nennen macht sie nicht zu einer. Es macht sie nur teuer.

§ 8
Eine Einladung

Lest den Code.
Widersprecht in den Issues.

Das Clipboard Pattern gehört nicht mir. Es lebt in jeder LangGraph-Codebasis, die State ernst nimmt, in jedem Rails- oder Django-Controller, der Logik aus HTTP-Bodies heraushält, und — wahrscheinlich — in jeder Anwaltskanzlei, die man je besucht hat.

Was ich getan habe, ist ihm einen Namen zu geben, die Disziplin zu kodifizieren und zu zeigen, was passiert, wenn man sich ganz und gar darauf einlässt. Novaberg implementiert das Pattern durchgängig. Der Quellcode liegt auf codeberg.org/ClausVomBerg/Novaberg unter Apache 2.0. Wenn dieses Argument Kraft hat, sollte man im Code eine Stelle finden können, an der es bricht. Wenn ja, würde ich sehr gerne davon in den Issues erfahren.

Als nächstes in dieser Serie: warum ein KI-Assistent eigene Emotionen braucht, und was schiefgeht, wenn er nur die euren spiegelt.

Verwandte Arbeiten

Das Clipboard Pattern liegt an der Schnittstelle mehrerer Traditionen. LangGraph selbst ist die Laufzeit, die es in Python praktikabel macht. Elixir/OTPs GenServer-Messaging inspirierte die Structured-Payload-Disziplin (über Prozess-Isolation statt Shared State, aber das Prinzip ist verwandt). Reacts unidirektionaler Datenfluss ist die engste Analogie außerhalb der LLM-Welt.

Der Amazon-Praktiker-Überblick, den ich referenziert habe — Marina Wyss, „AI Agents: Complete Course" (Medium / Data Science Collective, Dezember 2025) — ist die sorgfältigste Verteidigung des Messaging-Paradigmas, die ich kenne, und lesenswert, selbst wenn man am Ende widerspricht. Ihre Complexity/Precision-Matrix für Task-Auswahl ist orthogonal zu diesem Argument und kompatibel mit beiden Paradigmen. Ihre Memory-Taxonomie — dynamisch und statisch — ist eine nützliche Einführung, aber dünner als das fünfschichtige Gedächtnissystem in Novaberg; das ist Material für ein späteres Paper.

Graphiti / Zep (arXiv 2501.13956) argumentiert angrenzend: strukturierter Speicher schlägt textbasierte Gesprächshistorie. langgraph-supervisor-py und die Patterns in JoshuaC215/agent-service-toolkit und cgoncalves94/multi_agent_system bewegen sich in dieselbe Richtung wie dieses Paper, ohne das Pattern als solches zu benennen. Wenn der Name haften bleibt, hoffe ich, dass er diesen Bemühungen hilft, zusammenzufinden.

Erwartete Einwände

EinwandAntwort
Textnachrichten sind flexibler. Ja — und flexibler ist in der Produktion weniger zuverlässig. Ein typisierter State kann immer ein freies notes-Feld tragen, wenn Flexibilität wirklich gebraucht wird.
Amazon-Praktiker nutzen das Messaging-Pattern. Tun sie, und sie beschreiben die „Communication Pitfalls" als eigenes Kapitel. Das Problem ist erkannt; das Heilmittel bleibt innerhalb des Paradigmas. Dieses Paper schlägt einen Paradigmenwechsel vor, keinen Patch.
Das State-Dict wird riesig. Disziplin erforderlich. Novabergs ConversationState hat über sechzig Felder, code-reviewed, versioniert, lesbar. Es ist in über sechzig Entwicklungssessions nicht explodiert.
Das ist doch nur Shared Memory. Nein. Typisiert. Versioniert durch LangGraphs immutable Reducers. Orchestriert durch einen Graphen. Kein konkurrierender Thread-Zugriff — Nodes laufen in deterministischer Sequenz.
Was ist mit emergentem Verhalten? Behandelt in § 7. Das richtige Werkzeug wählen. Die meisten „Multi-Agent"-Systeme sind Pipelines, die das Wort Konversation tragen.
Was, wenn ein Node fehlschlägt? Jedes AgentResult trägt ein Status-Feld — abgeschlossen, fehler, rueckfrage. Der Responder sieht den Fehler und spricht ihn an. Nodes, die Exceptions werfen, werden gefangen, geloggt und auf einen Degraded-Path geleitet.
Was ist mit Streaming? LangGraph + FastAPIs WebSocket-Support erlaubt es jedem Node, Events zu emittieren. Der State wird durch das Streaming der Antwort nicht beeinträchtigt; Streaming ist ein Delivery-Detail.
Palette