4.3 KiB
Audit-Log & Receipts — Nachvollziehbarkeit
Setzt A3 aus der Roadmap um (F-A2). Zwei zusammengehörige Dinge:
- Audit-Log — ein Eintrag je Tool-Aufruf: wer, wann, welches Tool, mit welchem Ausgang.
- Receipts — ein Abschluss-Beleg je Lauf: Ergebnis, Schritte, Tokens, Kosten, verknüpft mit dem Task.
A3 ist das Fundament für A2 (jede Staging-Entscheidung wird als Datensatz verankert) und für C7 („Kosten pro Ergebnis", fällt aus den Receipts ab). Die Tool-Fehlerquote aus der Leistungsanalyse liest sich direkt aus dem Log.
Warum eigene Tabellen, nicht der State-Store
Dieselbe Überlegung wie bei Gedächtnis und Taskboard: IStateStore ist Schlüssel-Wert.
Ein Log, das man nach Lauf, Task oder Tool filtern und dessen Fehlerquote man auswerten
will, braucht typisierte Spalten. Zwei Tabellen auf dem vorhandenen
SqliteStorage: AuditLog und
RunReceipts.
Provenienz — von der Engine gestempelt, nie vom Agenten behauptet
Die entscheidende Regel (aus dem OpenAlice-Provenance-Konzept):
- Herkunft stempelt die Engine.
AgentId,ModelundSourcekommen aus dem Wissen der Engine über den Lauf, nicht aus dem Tool-Ergebnis. Ein Tool kann seine Herkunft nicht fälschen, weil es sie gar nicht schreibt. - Einträge sind unveränderlich. Das Repository hat kein Update und kein Delete — eine Korrektur ist ein neuer Eintrag. Das ist die eigentliche Zusage, keine fehlende Funktion.
- Unbekanntes wird als unbekannt markiert, nicht geraten. Fehlt die Quelle, steht
unknown, nicht ein plausibel geratener Kanal. - Worker-Typ und Session sind getrennt.
Model(das ausführende Modell) undSource(die verantwortliche Session:webview,telegram,task,agentcomm,job,direct) sind verschiedene Begriffe und stehen in eigenen Spalten.
Audit-Log
Gestempelt an genau einer Stelle: AgentEngine.ExecuteToolCallAsync — dort, wo jeder
Tool-Aufruf durchläuft. Je Aufruf ein Eintrag mit Ausgang:
| Status | Wann |
|---|---|
Ok |
Tool lief und lieferte ein Ergebnis |
Error |
Tool meldete einen Fehler oder warf |
Denied |
das PermissionGate hat abgelehnt |
NotFound |
Tool dem Agenten nicht zugewiesen/unbekannt |
Ein Abbruch (Cancellation) wird nicht protokolliert — der Aufruf kam nicht zum Abschluss. Die Argumente werden roh, aber gekappt abgelegt (4 000 Zeichen); die Ausgangsnotiz kurz (500).
Best effort: Ein Fehler beim Schreiben des Audits darf den Lauf nie scheitern lassen — dieselbe Linie wie bei der Verbrauchserfassung. Der Eintrag wird geschrieben, nachdem die eigentliche Arbeit getan ist.
Receipts
Je Lauf ein Beleg, geschrieben beim Abschluss von RunAsync/ChatAsync (neben der
vorhandenen RunUsage-Erfassung). Er trägt Status, Schritte, Prompt-/Completion-/
Cached-Tokens, geschätzte Kosten (aus dem ModelPricingCatalog, mit
CostIsKnown-Flag) und einen kurzen Ergebnis-Verweis.
Verknüpfung RunUsage ↔ Task: Der Receipt trägt die TaskId, wenn der Lauf aus dem
Taskboard kam — der EngineTaskDispatcher reicht sie (samt source: task) durch. Damit
ist „Kosten pro Ergebnis" (C7) ein Abfallprodukt: ListReceiptsForTaskAsync liefert
alle Belege zu einem Task.
RunId — die Klammer
Jeder Lauf bekommt zu Beginn eine RunId (GUID). Alle Audit-Einträge und der
Receipt eines Laufs tragen sie. So lässt sich ein Lauf lückenlos rekonstruieren:
ListForRunAsync(runId) gibt die Aufrufe in Reihenfolge, GetReceiptForRunAsync(runId)
den Abschluss.
Verdrahtung
IAuditRepository ist optional (wie Gedächtnis und Taskboard): ohne Repo läuft die
Engine unverändert. In Program.cs wird ein SqliteAuditRepository auf der Instanz-DB
erzeugt und der Engine übergeben.
Offen
- Output-Scrubbing — die
Argumentskönnen Secrets enthalten. Das zentrale Maskieren bekannter Secret-Werte (eigener beschlossener Roadmap-Punkt) greift, sobald es steht; der Andockpunkt (ExecuteToolCallAsync) ist derselbe. - Review-Oberfläche — die Anzeige/Durchsicht des Logs und der Receipts gehört zu A2 (Staging-Review im Hauptfenster); die Abfragemethoden dafür stehen bereit.
- Export — ein JSONL-Export des Logs wäre für externe Auswertung nützlich (später, passt zum A6-Spiegel).