# 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](RocketChat-Nextcloud-Konzept.md)) trägt **ausschließlich** das, was ein Mensch wissen soll. Interne Absprachen der Agenten gehen dort nie hin. Aufbauend auf [Audit-Konzept](Audit-Konzept.md) (A3) und [Taskboard-Konzept](Taskboard-Konzept.md) (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:` 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`: ```sql 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 | |---|---| | `AgentEngine` — `ask_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**: über den vorhandenen **WebView2**-Unterbau, wie ihn `frm_chat` schon nutzt (inkl. Virtual-Host-Mapping auf einen lokalen Ordner). Der Verlauf wird als HTML gerendert. Handgezeichnete Sprechblasen in WinForms wären 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 `` 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](Teststrategie.md) 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?