319 lines
15 KiB
Markdown
319 lines
15 KiB
Markdown
# 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:<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`:
|
|
|
|
```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 `<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](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?
|