Multi-Agenten-Systeme sollten nicht tratschen. Sie sollten eine Fallakte teilen.
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.
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.
# 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)
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.
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.
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.
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.
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.
| Pattern | Kommunikation | Symptom |
|---|---|---|
| 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.
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.
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
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:
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)
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.
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:
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.
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
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.
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.
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.
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
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.
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.
Ü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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 ConversationState zwischen HumanGraph und CharacterGraph geteilt wird und dieselbe Session weiterführt.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Einwand | Antwort |
|---|---|
| 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. |