feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user