Files
ClawdDotNet/docs/Agentenkommunikation-Konzept.md
T
RichardandClaude Opus 5 2853541629 Fruehjahrsputz: WinForms-Altlast entfernt, Dokumentation nachgezogen
Die Avalonia-Portierung ist abgeschlossen, damit ist die in ClawdDotNet.slnx
angekuendigte Aufgabe "WinForms-Oberflaeche entfernen" faellig. Der Stand
davor liegt unter dem Tag vor-fruehjahrsputz-2026-08.

Entfernt (56 Dateien, seit dem Herausloesen der Anwendungsschicht nicht mehr
Teil des Builds): ClawdDotNet.csproj, Program.cs, sieben frm_*-Formulare,
UI/, Models/, EmbeddedUI/, Properties/, Resources/, Services/, das alte
Anwendungssymbol und Deploy-Build.ps1 (ersetzt durch deploy/publish.py).
Dazu configs/*.json - Beispielkonfigurationen aus der Zeit vor dem
Instanzverzeichnis, auf die nur noch die alten Prompts verwiesen.

Die vier Entwicklungs-Prompts der Anfangszeit ziehen nach docs/archiv/ um,
mit README, das ihren Stand einordnet. Eine Regel darin gilt weiter - die
Pflichtfelder fetchedAt/dataAsOf/source der Internet-Tools -, deshalb
Archiv statt Loeschen; der WebSearch-Plan verweist auf den neuen Pfad.

Toter Code
- PlaceholderPageViewModel samt Ansicht: Es gibt keinen Platzhalter-Bereich
  mehr, seit alle neun Seiten portiert sind.
- Snappier als direkter Paketverweis: MongoDB.Driver loest es ohnehin auf
  dieselbe Fassung auf, der Verweis hob nichts an.

Zwei Fehler, die dabei sichtbar wurden
- Die taegliche Sicherung lief ins Leere. Die Oberflaeche bot sie an und
  schrieb Uhrzeit, Zielordner und Anzahl in die Einstellungen, aber der
  BackupScheduler wurde nirgends erzeugt. Jetzt am AppHost verdrahtet und
  in den geordneten Abbau aufgenommen.
- SettingsPageViewModel hielt die vier Sicherungs-Einstellungen doppelt.
  Aus der Ansicht waren sie laengst verschwunden, gelesen und beim
  Speichern zurueckgeschrieben wurden sie weiter: Wer die Uhrzeit auf der
  Sicherungs-Seite aenderte und danach die Einstellungen speicherte, bekam
  den alten Wert zurueck.

Pakete: keine bekannten Sicherheitsluecken mehr
- SQLitePCLRaw.bundle_e_sqlite3 auf 2.1.13 angehoben. Microsoft.Data.Sqlite
  bringt 2.1.11 mit, darin steckt GHSA-2m69-gcr7-jv3q (NU1903, hoch).
- SharpCompress bleibt als direkter Verweis stehen. Beim Aufraeumen erst
  als ungenutzt entfernt - dabei kam die von MongoDB.Driver gezogene
  Fassung 0.30.1 mit GHSA-6c8g-7p36-r338 zurueck. Der Verweis ist eine
  Anhebung, kein Ballast; das steht jetzt als Kommentar dabei.

Dokumentation
- Roadmap mit Statusblock: A1 und A3 erledigt, A2 nur zur Haelfte - Gate,
  Policy und Dienst greifen, aber keine Ansicht ruft ApproveAsync auf, ein
  gestagter Aufruf liegt unbeantwortet. Das ist jetzt Punkt 1 der Reihung.
  Rocket.Chat steht und kollidiert mit A5 (Matrix) - Entscheidung faellig.
- Avalonia-Portierungsleitfaden -> Oberflaechen-Leitfaden: kein Auftrag mehr,
  sondern Beschreibung des Stands.
- Bestandsaufnahme und Linux-Analyse als datierte Befunde gekennzeichnet;
  der teure Teil der Linux-Analyse (8.900 Zeilen WinForms) ist hinfaellig.
- Verweise auf frm_*, WebView2 und ClawdDotNet.csproj in den lebenden
  Dokumenten richtiggestellt.

Build fehlerfrei, 585 Tests gruen (6 uebersprungen).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:26:14 +02:00

16 KiB

Agentenkommunikation — Erfassung, Ansicht, Auswertung

Ziel: Die gesamte Kommunikation zwischen Agenten wird erfasst, ist im WhatsApp-Stil paarweise nachlesbar und lässt sich von einem Agenten automatisiert auswerten — um zu finden, wo die Zusammenarbeit klemmt.

Abgegrenzt davon: Rocket.Chat (siehe RocketChat-Nextcloud-Konzept) trägt ausschließlich das, was ein Mensch wissen soll. Interne Absprachen der Agenten gehen dort nie hin.

Aufbauend auf Audit-Konzept (A3) und Taskboard-Konzept (A1).


1 — Beschlüsse (August 2026)

# Beschluss
1 Korrelation: ParentRunId + RootRunId in Audit-Log und Receipts. Eine Delegationskette wird damit zu einer Abfrage.
2 Nachrichtenspeicher: eigene Tabelle AgentMessages, die alle Wege gleich behandelt und beide Richtungen festhält.
3 Rocket.Chat bleibt außen vor — kein Spiegeln der Agentenkommunikation dorthin. Ein Gruppenchat mit allem drin wäre unlesbar.
4 Ansicht: Paar auswählen (Agent A / Agent B), Verlauf im Chat-Stil scrollen.
5 Auswertung: ein Analyse-Tool, das ein dafür vorgesehener Agent bekommt.

2 — AgentComm behalten oder durch das Taskboard ersetzen?

Das war die offene Frage. Der Befund zuerst, die Empfehlung danach.

2.1 Was heute passiert

AgentComm.send_message ist ein synchroner Aufruf: A ruft, SendMessageAsync startet ChatAsync(B), wartet auf den vollständigen Lauf von B und gibt dessen Schlussnachricht als Tool-Ergebnis an A zurück. Drei Eigenschaften folgen daraus:

  • Es gibt keine Tiefenbegrenzung (B8). A→B→A→B… läuft, bis ein Timeout greift.
  • Es kann echt verklemmen. Seit B2 serialisiert ein Gate je Agent alle Läufe. A hält sein Gate, während es auf B wartet. Ruft B nun send_message(A), wartet B auf As Gate — das A hält, während es auf B wartet. Das löst nur der Timeout auf. Der Selbstaufruf A→A wurde damals abgefangen, der Zweierzyklus nicht.
  • Der Fehler ist teuer: Jeder Hop ist ein vollständiger, bezahlter Lauf.

2.2 Was ein Task nicht kann

Trotzdem ist „einfach alles über Tasks" nicht ohne Verlust. Zwei Dinge kann der asynchrone Weg strukturell nicht:

  • Antwort im selben Lauf. Bei send_message kommt die Antwort als Tool-Ergebnis zurück, und A arbeitet damit sofort weiter. Über einen Task endet As Lauf; die Antwort kommt später als neuer Weckvorgang, und A muss seinen Gedankengang neu aufnehmen. Für eine Rückfrage sind das zwei Läufe statt einem — der asynchrone Weg ist hier also nicht nur langsamer, sondern teurer.
  • Antwortzeit. Der Scanner tickt im Minutentakt. Eine Rückfrage „hast du die Datei schon abgelegt?" braucht damit im Mittel eine halbe Minute plus den Lauf des anderen.

2.3 Empfehlung: nach Zweck trennen, nicht beides parallel führen

Der Fehler in der jetzigen Lage ist nicht, dass es zwei Mechanismen gibt — es ist, dass beide dasselbe können. Ein Agent kann Arbeit sowohl per Task delegieren als auch per send_message „mal eben" abschieben, und der zweite Weg ist der gefährliche.

Vorschlag:

Delegation gehört ausschließlich ins Taskboard. AgentComm verliert diese Rolle vollständig — kein „mach du mal", kein Auftrag, kein Arbeitspaket.

Die kurze Rückfrage bleibt, aber als eigenes, eng gefasstes Werkzeug: ask_agent.

ask_agent mit harten Grenzen:

Grenze Begründung
Tiefe 1 — wer gerade eine Rückfrage beantwortet, darf selbst keine stellen Beendet die Rekursion an der Wurzel. Prüfbar, sobald ParentRunId steht (Punkt 1) — die Synergie ist der Grund, warum das jetzt fast umsonst zu haben ist
Zyklusprüfung — Ziel darf nicht im aktuellen Aufrufpfad liegen Schließt den Deadlock aus 2.1 aus, statt auf den Timeout zu hoffen
Kurzer Timeout + kleines Schrittbudget (z. B. 60 s, 5 Schritte) Eine Rückfrage, die fünf Schritte braucht, war keine Rückfrage, sondern ein Auftrag
Eigene Beschreibung im Prompt: „für kurze Fragen an einen Kollegen, nicht um Arbeit abzugeben" Der häufigste Missbrauch ist der falsche Griff, nicht die böse Absicht

Damit gibt es weiterhin zwei Wege, aber sie überschneiden sich nicht mehr: Der eine ist ein Auftrag (dauerhaft, nachvollziehbar, mit Abnahme), der andere eine Frage (flüchtig, sofort, begrenzt).

Die Gegenposition, fairerweise: Man kann AgentComm auch ersatzlos streichen und die Rückfrage über einen Task mit hoher Priorität abbilden. Das wäre die konsequentere Umsetzung des „alles ist ein Task"-Prinzips und spart ein Tool. Der Preis sind die zwei Läufe je Rückfrage und die Minute Wartezeit. Meine Empfehlung ist die Trennung, weil Rückfragen im Mehr-Agenten-Betrieb häufig sind und der Aufpreis sich dann summiert — aber das ist eine Abwägung, keine technische Notwendigkeit.

AgentSpawn geht in beiden Varianten im Taskboard auf (assignee: @new:<agent> ist genau das) und wird zurückgebaut.


3 — Korrelation: ParentRunId und RootRunId

Heute erzeugt jeder Lauf eine frische runId; der Lauf des Empfängers weiß nichts vom Lauf des Absenders. Eine Kette A→B→C ist deshalb nur über Zeitstempel zu erraten.

Zwei Felder auf AuditEntry und RunReceipt:

  • ParentRunId — der Lauf, aus dem dieser hervorging. null bei einem Lauf, den ein Mensch oder der Scanner auslöst.
  • RootRunId — die Wurzel der Kette. Das ist faktisch die Vorgangs-Id: Alles, was aus einer Anweisung entstand, trägt denselben Wert.

Regeln:

  • Die Engine stempelt. Wie beim Audit gilt: Herkunft wird nie vom Agenten behauptet.
  • Ein Lauf ohne Vorgänger ist seine eigene Wurzel (RootRunId = RunId).
  • Weitergereicht wird über die Aufrufstellen, an denen ein Lauf einen anderen auslöst: ask_agent, Task-Dispatch, Staging-Folgetask.
  • Migration: Bestandszeilen bekommen RootRunId = RunId und ParentRunId = NULL. Das ist nicht rückwirkend korrekt, aber ehrlich — alte Ketten bleiben unbekannt, statt falsch zusammengesetzt zu werden.

Der Nutzen reicht über die Analyse hinaus: Was eine Delegationskette insgesamt gekostet hat, ist danach ein SUM über RunReceipts gruppiert nach RootRunId. Damit fällt ein Teil von C7 nebenbei ab.


4 — Der Nachrichtenspeicher

4.1 Tabelle

Neue Tabelle in der Instanz-DB, neben AuditLog und RunReceipts:

CREATE TABLE IF NOT EXISTS AgentMessages (
    Id          INTEGER PRIMARY KEY AUTOINCREMENT,
    PairKey     TEXT NOT NULL,   -- sortiertes Paar: "agentA|agentB"
    FromAgentId TEXT NOT NULL,
    ToAgentId   TEXT NOT NULL,
    Channel     TEXT NOT NULL,   -- ask | task | task_comment | task_result
    Direction   TEXT NOT NULL,   -- request | response
    Content     TEXT NOT NULL,   -- vollständig, nach Scrubbing
    RunId       TEXT NOT NULL,
    ParentRunId TEXT,
    RootRunId   TEXT NOT NULL,
    TaskId      TEXT,
    Status      TEXT NOT NULL,   -- delivered | failed | timeout | denied
    OccurredAt  TEXT NOT NULL
);
CREATE INDEX IX_AgentMessages_Pair ON AgentMessages(PairKey, OccurredAt);
CREATE INDEX IX_AgentMessages_Root ON AgentMessages(RootRunId, OccurredAt);

Dazu ein FTS5-Index auf Content — dieselbe Technik wie beim geplanten Historien-Umzug (K6), damit die Suche in der Ansicht und im Analyse-Tool nicht über LIKE läuft.

4.2 Die Entscheidungen dahinter

PairKey als sortiertes Paar. Die geforderte Ansicht („A und B auswählen, scrollen") wird damit zu WHERE PairKey = ? ORDER BY OccurredAt — eine Abfrage auf einem Index, unabhängig davon, wer gerade wen anspricht.

Beide Richtungen als eigene Zeilen. Eine Rückfrage erzeugt zwei Zeilen (request A→B, response B→A) mit derselben RootRunId. Nur so entsteht ein Verlauf, der sich wie ein Chat liest. Das Audit-Log kann das nicht leisten: Es speichert nur Arguments, die Antwort landet dort nirgends.

Inhalt ungekappt. Das Audit kappt bei 4.000 Zeichen — richtig, denn es dient der Nachvollziehbarkeit. Für die Auswertung braucht es den vollen Text. Gekappt wird erst dort, wo Text in einen LLM-Kontext zurückfließt (Abschnitt 6).

Alle Wege in einer Tabelle. ask_agent, Task-Delegation, task_comment und das Ergebnis eines Tasks landen im selben Format. Sonst müsste die Auswertung drei Quellen zusammensuchen — und genau daran scheitert sie heute.

Fan-out statt Sammelzeile. Eine Nachricht an mehrere Empfänger wird zu mehreren Zeilen. Etwas redundant, dafür bleibt jede Zeile paarweise auswertbar.

4.3 Wer schreibt

Ein IAgentMessageLog im Core, aufgerufen an genau den Stellen, an denen eine Nachricht eine Agentengrenze überschreitet:

Aufrufstelle Zeilen
AgentEngineask_agent request beim Absenden, response beim Rückgabewert (auch bei Fehler/Timeout, mit passendem Status)
Taskboard — task_create mit fremdem Assignee request
Taskboard — task_comment request bzw. response, je nach Richtung
Taskboard — Task abgeschlossen/geblockt response mit Ergebnis oder Blocker-Grund

Drei bis vier Stellen, alle im Core. Kein Tool schreibt selbst — sonst könnte ein Agent seine eigene Kommunikationsakte färben.

Fehlschläge werden mitgeschrieben. Eine nicht zugestellte Nachricht ist für die Analyse wertvoller als eine erfolgreiche.

4.4 Was hier nicht hineingehört

  • Mensch↔Agent-Chat. Das ist die Chat-Historie, ein anderer Gegenstand mit anderem Umzugsplan (K6). Ausnahme mit gutem Preis-Leistungs-Verhältnis: Tasks mit assignee: @human durchlaufen dieselben Aufrufstellen — man kann sie als Paar (Agent, @human) mitschreiben und bekommt die Ansicht dafür geschenkt. Vorschlag: ja, aber als Nachzügler, nicht als Teil der ersten Fassung.
  • Tool-Aufrufe. Die stehen im Audit-Log und gehören nicht in einen Gesprächsverlauf.
  • Rocket.Chat-Nachrichten. Anderer Gegenstand, andere Vertrauensgrenze.

4.5 Zwei Pflichten

  • Output-Scrubbing vor dem Schreiben. Nachrichteninhalte enthalten Tool-Ergebnisse. Ohne Maskierung bekannter Geheimnisse wird dieser Speicher zur zweiten Fundstelle für Zugangsdaten — und über den MySQL-Spiegel (A6) verlässt er sogar die Maschine. Der Roadmap-Punkt „Output-Scrubbing" ist damit Voraussetzung, nicht Beiwerk.
  • Aufbewahrung. Die Tabelle wächst unbegrenzt. Ein Instanz-Wert (agentMessageRetentionDays, 0 = unbegrenzt) plus ein Aufräum-Task gehören von Anfang an dazu, nicht erst, wenn die DB groß ist.

5 — Die Ansicht

Neuer Reiter im Hauptfenster: Agentenkommunikation.

Bedienung: zwei Auswahlfelder (Agent A, Agent B) — dazu „alle" für einen Agenten, um zu sehen, mit wem er überhaupt spricht. Zeitraum, Kanalfilter, Freitextsuche.

Darstellung: Chat-Stil, A rechts, B links, Zeitstempel, Tagestrenner. Jede Blase trägt eine kleine Kennzeichnung des Kanals (Rückfrage / Auftrag / Kommentar / Ergebnis) und, wo vorhanden, die anklickbare Task-Id.

Technisch: damals über den WebView2-Unterbau von frm_chat gedacht. Beides gibt es seit der Avalonia-Portierung nicht mehr — der Abschnitt ist als Entwurfsstand von damals zu lesen; die Ansicht wäre heute eine Avalonia-Seite wie AgentChatsPageView (inkl. Virtual-Host-Mapping auf einen lokalen Ordner). Der Verlauf wird als HTML gerendert. Handgezeichnete Sprechblasen wären dort ein Vielfaches an Aufwand für ein schlechteres Ergebnis.

Paging: die jüngsten ~200 Nachrichten, „ältere laden" nach oben. Ein Paar mit 50.000 Zeilen darf die Oberfläche nicht am Start blockieren.

Der eigentliche Mehrwert liegt über dem flachen Verlauf: Ein Klick auf eine Nachricht zeigt die ganze Kette zu ihrer RootRunId — also den Vorgang von der auslösenden Anweisung bis zum letzten Beitrag, über alle beteiligten Agenten hinweg, mit den Kosten aus den Receipts. Das ist die Ansicht, die die Frage „warum hat das drei Stunden und vier Dollar gekostet" tatsächlich beantwortet.


6 — Automatisierte Auswertung

Ein Tool AgentCommAnalysis, das bewusst nur einem dafür vorgesehenen Agenten zugewiesen wird (die Zuweisung je Agent gibt es ohnehin).

Aktion Zweck
list_pairs Wer spricht mit wem, wie oft, seit wann — der Einstieg
stats Kennzahlen je Paar/Kanal/Zeitraum (siehe unten)
read_conversation Verlauf eines Paares, gekappt und seitenweise
chain Ein kompletter Vorgang über RootRunId, inkl. Kosten
search Volltext über FTS5

Fragen, die das beantworten soll — sie sind der Grund für den Schnitt des Schemas:

  • Wie viele Delegationen führen zu einem Ergebnis, und wie viele versanden?
  • Wie tief werden Ketten, und ab welcher Tiefe steigt die Fehlerquote?
  • Welche Paare stellen sich wiederholt dieselbe Rückfrage? (Ein Hinweis auf unklare Zuständigkeit oder einen fehlenden Skill — genau die Art Problem, die man sucht.)
  • Was kostet ein Vorgang von der Anweisung bis zum Ergebnis?
  • Wo häufen sich failed/timeout?

Drei Sicherungen, weil ein Agent hier fremde Kommunikation liest:

  1. Ergebnisse werden als <untrusted_content> gerahmt. Der Inhalt stammt aus anderen Läufen und kann Anweisungen enthalten — auch ohne böse Absicht.
  2. Standardmäßig liefert das Tool Kennzahlen, Rohtext nur auf ausdrückliche Anfrage und mit harter Obergrenze. Ein unbedachtes „lies mir alles vor" ist sonst ein Kontext-Überlauf mit Rechnung.
  3. Das Tool ist lesend. Es gibt keine Schreibaktion.

7 — Verhältnis zu Rocket.Chat

Klargestellt, weil es die vorherige Überlegung ablöst:

  • Agentenkommunikation wird nicht nach Rocket.Chat gespiegelt. Ein Raum, in dem jede interne Absprache mitläuft, ist nach einer Woche unlesbar und verdeckt genau das, was man sehen soll.
  • Nach Rocket.Chat geht nur, was ein Mensch wissen soll oder muss — über den ChannelRouter bzw. eine bewusste Handlung des Agenten.
  • Wer den internen Verlauf sehen will, nimmt die Ansicht aus Abschnitt 5. Die ist dafür gebaut; ein Gruppenchat ist es nicht.

8 — Schnitt

Phase Inhalt Abhängigkeit
1 ParentRunId + RootRunId in AuditLog/RunReceipts, Migration, Stempelung in der Engine
2 AgentMessages + FTS5 + IAgentMessageLog, Schreiben an den Aufrufstellen 1
3 ask_agent (Tiefe 1, Zyklusprüfung), Rückbau von AgentComm/AgentSpawn 1 — die Tiefe kommt aus der Kette
4 Ansicht (WebView2-Reiter) 2
5 AgentCommAnalysis-Tool 2
Output-Scrubbing vor 2

Phase 1 zuerst, weil Phase 3 die Kette braucht und Phase 2 die Felder mitschreibt. Das Scrubbing muss vor Phase 2 stehen — sonst legen wir einen Speicher an, der erst nachträglich bereinigt werden müsste.

Zur Einstufung: Phasen 1, 2, 4 und 5 sind gut spezifizierbar. Phase 3 fasst das Agent-Gate an, an dem schon einmal ein Deadlock lauerte (B2/B8) — dafür gehören die Tests aus der Teststrategie mit dazu, insbesondere der dort vorgesehene, bis heute fehlende Fall A13 (A→B→A wird begrenzt statt zu verklemmen).


9 — Offene Punkte

  1. ask_agent behalten oder ersatzlos streichen? Meine Empfehlung steht in 2.3 (behalten, eng gefasst) — die Gegenposition ist dort ebenfalls notiert.
  2. @human-Tasks mitschreiben? Gibt die Paar-Ansicht auch für Mensch↔Agent, fast ohne Zusatzaufwand. Vorschlag: ja, aber nach Phase 4.
  3. Aufbewahrungsdauer — Vorgabewert? (Vorschlag: unbegrenzt, bis der Spiegel aus A6 steht; dann 180 Tage lokal.)
  4. Wer bekommt AgentCommAnalysis? Ein eigener Analyse-Agent oder ein bestehender?