Files
ClawdDotNet/docs/archiv/Bestandsaufnahme-2026-07.md
T
RichardandClaude Opus 5 740649789e Eine Roadmap statt neun verteilter Listen
Der Stand lag ueber eine Bestandsaufnahme, drei Konzeptpapiere, vier
Umsetzungsplaene und zwei Deploymentcenter-Dokumente verteilt - jedes mit
eigener Reihenfolge, teils widersprueglich. Alle offenen Punkte daraus sind
in docs/Roadmap.md zusammengefuehrt.

Aufbau der neuen Roadmap
- Statusvokabular: erledigt / beschlossen-offen / Entscheidung noetig /
  zurueckgestellt / Idee. Damit steht das Geparkte sichtbar drin, statt in
  einem Konzeptpapier zu verschwinden.
- Herkunft-Spalte traegt das alte Kuerzel (S4, K2, B9, T4, F-A1, C1, DC1),
  damit die archivierten Papiere auffindbar bleiben, ohne sie zu lesen.
- Abschnitt 1 Reihenfolge, 2 offene Entscheidungen, 3 die Vorhaben nach
  Bereich, 4 Ideenspeicher, 5 Chronik des Erledigten, 6 Modell-Einstufung,
  7 Herkunftskarte.

Was dabei sichtbar wurde
- Neun Entscheidungen blockieren Arbeit, ohne dass sie Aufwand kosten -
  allen voran Matrix oder Rocket.Chat. Sie stehen jetzt gesammelt in
  Abschnitt 2 statt verstreut in den Diskussionsteilen der Konzepte.
- B8 (keine Tiefenbegrenzung bei AgentComm) ist unveraendert offen. Das ist
  keine Theorie: A haelt sein Gate, waehrend es auf B wartet - ruft B nun A,
  warten beide bis zum Timeout. Der Testfall A13 dafuer fehlt bis heute.
- Die vier Umsetzungsplaene vom 2026-08-05 sind alle unumgesetzt und waren
  in keiner Roadmap verzeichnet.

Archiv
Sechs Dokumente ziehen nach docs/archiv/: Bestandsaufnahme, Konzepte
Backup/Finanz/Analyse, Linux-Portierung-Analyse, Lizenz-HardwareId-v2
(gegenstandslos - LicenseLabrador ist ersetzt), Deploymentcenter-Review und
der 2.4-Integrationsplan. Sie bleiben als Begruendung lesbar, werden aber
nicht mehr fortgeschrieben; das README ordnet jedes einzeln ein und warnt,
dass ihre Quelltext-Verweise ins Leere gehen koennen.

Bauplan bleibt Bauplan
Taskboard, Audit, Staging, Memory, Agentenkommunikation, RocketChat und
Deploymentcenter-Integration bleiben in docs/ - sie sind die Detailvorgabe
fuer die Umsetzung, nicht Vorhabenlisten. Jedes bekommt oben eine Zeile,
die seine Rolle und den Umsetzungsstand nennt und auf die Roadmap zeigt.
Dasselbe fuer die vier Umsetzungsplaene: die Reihenfolge gilt in der
Roadmap, nicht im Plan.

Nebenbei repariert: Taskboard-Konzept und Teststrategie verwiesen auf
AgentScheduler und ToolJobScheduler, die seit der Scanner-Konsolidierung
geloescht sind. Alle Dokument-Verweise in docs/ sind geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:31:06 +02:00

586 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ClawdDotNet — Bestandsaufnahme (Juli 2026)
Vollständiges Review von Core-Engine, Security-Layer, Tools, Scheduling und UI.
Stand: Commit `92e50d3`.
> **Dies ist ein datierter Befund, keine Todoliste.** Er wird nicht fortgeschrieben.
> Was davon offen ist, steht in der [Roadmap](../Roadmap.md) — dort auch, was seither
> erledigt wurde. Die Abschnitte zur Oberfläche beziehen sich auf die Windows-Forms-
> Fassung, die es seit dem 2026-08-23 nicht mehr gibt.
**Kurzfassung:** Die Architektur ist gut — die Trennung Core/Tools/UI, das `IAgentTool`-Interface,
der Tool-Job-Mechanismus und das Instanz-Konzept tragen. Die Probleme liegen fast alle in
drei Bereichen: (1) Sicherheitsprüfungen, die als String-Vergleiche implementiert sind und
deshalb umgehbar sind, (2) fehlende Nebenläufigkeits-Absicherung im geteilten Chat-Kontext,
(3) kein Prompt-Caching — der mit Abstand größte Kostenhebel.
---
## 1. Kritische Sicherheitslücken (P0)
### S1 — DatabaseTool: Tabellen-Whitelist ist vollständig umgehbar
[`DatabaseTool.cs:218`](../../src/ClawdDotNet.Tools.Database/DatabaseTool.cs#L218)
```csharp
return allowed.Any(t => t != null && inputLower.Contains(t));
```
Die Prüfung sucht den erlaubten Tabellennamen *irgendwo* im SQL-String — auch in einem
Kommentar oder String-Literal. Bei `allowedTables: ["prices"]` und `accessLevel: ReadWrite`:
```sql
DELETE FROM users -- prices
```
`IsWriteAttempt` → true (ReadWrite erlaubt), `IsTableAllowed` → true (enthält "prices").
Die `users`-Tabelle wird gelöscht. Gleiches gilt für jede beliebige andere Tabelle.
Zusätzlich:
- `IsWriteAttempt`/`IsAdminAttempt` ([Z.189/195](../../src/ClawdDotNet.Tools.Database/DatabaseTool.cs#L189))
sind ebenfalls Substring-Prüfungen → False Positives (`SELECT * FROM prices WHERE note='update'`
wird als Schreibzugriff blockiert).
- Mehrere Statements pro Aufruf sind nicht unterbunden (`;`-Verkettung).
- Query-Ergebnisse haben kein `LIMIT` → eine Tabelle mit 100k Zeilen landet komplett im Kontext.
**Fix:** Echtes SQL-Parsing (z.B. via `Microsoft.SqlServer.TransactSql.ScriptDom` für MSSQL bzw.
einem generischen Tokenizer), oder — deutlich robuster — den freien SQL-Zugriff durch
vordefinierte, parametrisierte Named Queries ersetzen. Freies SQL vom LLM ist grundsätzlich
schwer abzusichern.
### S2 — SocialMediaManager: Kommando-Injection über yt-dlp
[`SocialMediaManagerTool.cs:442`](../../src/ClawdDotNet.Tools.SocialMediaManager/SocialMediaManagerTool.cs#L442), auch Z.484 und Z.490
```csharp
var psi = new ProcessStartInfo(ytDlpPath, $"--print \"%(id)s\" --playlist-end 1 {channelUrl}")
```
`channelUrl` stammt aus dem Agenten-Input und wird ungequotet in die Argumentliste interpoliert.
`UseShellExecute = false` verhindert Shell-Metazeichen, aber **nicht** Options-Injection:
yt-dlp kennt `--exec`, das beliebige Befehle nach dem Download ausführt.
Ein Wert wie `--exec "cmd /c ..." https://youtube.com/...` führt zu Codeausführung.
Kritisch wird das, weil der Agent untrusted Content verarbeitet (YouTube-Titel, X-Posts,
Mails) — eine Prompt-Injection in abgerufenen Inhalten kann den Agenten dazu bringen,
genau so einen "URL"-Parameter zu setzen.
**Fix:** `ProcessStartInfo.ArgumentList` verwenden (quotet korrekt), zusätzlich die URL gegen
eine Regex validieren (`^https://(www\.)?(youtube\.com|youtu\.be)/…`) und `--`-Separator
vor dem URL-Argument setzen.
### S3 — DirectAPI: API-Keys landen im Modell-Kontext
[`DirectAPITool.cs:83`](../../src/ClawdDotNet.Tools.DirectAPI/DirectAPITool.cs#L83), auch Z.99, 154, 177, 263
```csharp
var url = $"https://api.twelvedata.com/quote?symbol={symbol}&apikey={apiKey}";
return CreateSuccessResult(fetchedAt, dataAsOf, url, json.Value); // url → "source"
```
Die vollständige URL inklusive `apikey=` wird als `source` an das Modell zurückgegeben. Damit
landet der Key:
- im Chat-Kontext → wird bei **jedem** Folge-Request erneut an OpenRouter gesendet,
- in `ChatContext.json` auf der Platte,
- in den Logs.
**Fix:** Key als Header senden statt als Query-Parameter (wo möglich), und in `source`
grundsätzlich eine bereinigte URL zurückgeben (Query-String entfernen oder `apikey` maskieren).
### S4 — PermissionGate ist faktisch wirkungslos
[`PermissionGate.cs:7`](../../src/ClawdDotNet.Core/Security/PermissionGate.cs#L7)
```csharp
public bool IsAllowed(string agentId, string toolName, AgentConfig agentConfig)
=> agentConfig.Tools.ContainsKey(toolName);
```
Der einzige Check ist "Tool ist dem Agenten zugewiesen" — was `ToolRegistry.GetForAgent` ohnehin
schon filtert. Der `agentId`-Parameter wird nicht verwendet. Es gibt keine Aktions-Granularität
(lesen vs. schreiben vs. senden), kein Rate-Limiting, kein Audit. Die gesamte Sicherheit liegt
ad-hoc in den einzelnen Tools — jedes mit eigener, inkonsistenter Logik.
**Fix:** Das Gate zum zentralen Policy-Punkt ausbauen: Aktions-Ebene (`Tool.Action`),
Rate-Limits pro Agent/Tool/Zeitfenster, Audit-Log jedes Aufrufs mit Argumenten, und ein
Approval-Hook für irreversible Aktionen (siehe F-A1).
### S5 — WebFetch: kein SSRF-Schutz bei Redirects
[`WebFetchTool.cs:58`](../../src/ClawdDotNet.Tools.WebFetch/WebFetchTool.cs#L58)
Die Domain-Whitelist wird nur auf die *ursprüngliche* URL angewendet. `HttpClient` folgt
Redirects standardmäßig — eine erlaubte Domain kann auf `http://169.254.169.254/`,
`http://localhost:8418/` (dein Gitea!) oder beliebige interne Hosts weiterleiten.
Nebenbei: `uri.Host.Replace("www.", "")` ([Z.59](../../src/ClawdDotNet.Tools.WebFetch/WebFetchTool.cs#L59))
ersetzt das Fragment überall im Hostnamen, nicht nur am Anfang.
**Fix:** `HttpClientHandler { AllowAutoRedirect = false }` und Redirects manuell auflösen,
dabei jede Zwischen-URL erneut gegen die Whitelist prüfen. Zusätzlich private IP-Bereiche
(RFC1918, Loopback, Link-Local) hart blockieren.
### S6 — FileRW: Path-Traversal-Prüfung per Präfix
[`FileRWTool.cs:211`](../../src/ClawdDotNet.Tools.FileRW/FileRWTool.cs#L211)
```csharp
if (!fullPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
```
Ohne abschließenden Verzeichnis-Separator: Root `C:\Instances\Dev\Workspace` erlaubt Zugriff auf
`C:\Instances\Dev\Workspace-Backup\…`. In der aktuellen Verzeichnisstruktur nicht direkt
ausnutzbar, aber eine Zeitbombe.
**Fix:** `rootPath` mit `Path.DirectorySeparatorChar` terminieren, oder besser
`Path.GetRelativePath` + Prüfung auf `..`-Präfix.
### S7 — Secrets im Klartext
`openRouterApiKey`, DB-`connectionString` (mit Passwort), Mail-`password`, Telegram-`password2FA`
liegen unverschlüsselt in `AgentSettings.json` / `InstanceConfig.json`
([`InstanceConfig.cs:46`](../../src/ClawdDotNet.Core/Config/InstanceConfig.cs#L46)).
**Fix:** DPAPI (`ProtectedData.Protect` mit `CurrentUser`-Scope) für alle Secret-Felder,
oder Windows Credential Manager. Zumindest sollten die Felder beim Speichern verschlüsselt
und erst zur Laufzeit entschlüsselt werden.
---
## 2. Bugs (P1)
### B1 — ContextCompactor zerstört die tool_call-Paarung ⚠️
[`ContextCompactor.cs:140`](../../src/ClawdDotNet.Core/Engine/ContextCompactor.cs#L140)
```csharp
var tail = messages.Skip(Math.Max(0, messages.Count - ProtectedTailMessages)).ToList();
messages.Clear();
messages.AddRange(tail);
```
Die letzten 6 Nachrichten werden blind übernommen. Wenn die Grenze mitten in einer
Tool-Sequenz liegt, beginnt der Tail mit einer `tool`-Nachricht, deren zugehörige
`assistant`-Nachricht mit `tool_calls` gerade gelöscht wurde. Die OpenRouter/Anthropic-API
lehnt das mit HTTP 400 ab ("tool_result without preceding tool_use").
Das passiert **genau dann**, wenn der Kontext lang wird — also im wichtigsten Moment.
Bei einer typischen Sequenz `assistant(2 tool_calls) → tool → tool` ist die Wahrscheinlichkeit
hoch.
**Fix:** Die Tail-Grenze auf einen sicheren Schnittpunkt verschieben — rückwärts suchen bis zu
einer Nachricht mit Rolle `user` oder `assistant` ohne `tool_calls`. Analog muss ein
`assistant` mit `tool_calls` am Ende immer seine vollständigen `tool`-Antworten behalten.
### B2 — Race Condition auf dem geteilten Chat-Kontext ⚠️
[`AgentEngine.cs:250`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L250) und Z.268/316/322
`_chatContexts[agentId]` ist eine geteilte `List<ChatMessage>`. Nur der *Lookup* läuft unter
`_lock` — alle `messages.Add(...)` im Loop passieren ungeschützt.
Für denselben Agenten kann `ChatAsync` mehrfach parallel laufen:
- Telegram-/Mail-ToolJob weckt den Agenten (`ToolJobScheduler.cs:187`),
- der Benutzer schickt gleichzeitig eine WebView-Nachricht,
- ein anderer Agent ruft `send_message` auf (`AgentEngine.cs:528`).
Folgen: `List<T>`-Korruption (nicht threadsicher), verschränkte Tool-Call-Sequenzen →
API-400-Fehler, und `_runningChats[agentId] = linkedCts` überschreibt den vorherigen Eintrag,
sodass `AbortChat` nur den zuletzt gestarteten Lauf abbricht.
**Fix:** Pro Agent ein `SemaphoreSlim(1,1)`, das den gesamten `ChatAsync`-Durchlauf serialisiert.
Wartende Nachrichten in eine Queue statt parallel starten.
Beim Umsetzen kamen zwei Folgeprobleme dazu, die denselben Ursprung haben:
- `ExecuteToolCallAsync` fing `OperationCanceledException` mit ab und gab sie als
Tool-Fehlerergebnis zurück. Der Abbruch griff dadurch erst einen Schritt später.
- `send_message` an den eigenen Agenten wäre mit dem neuen Gate in einen Deadlock gelaufen
(der laufende Chat hält es bereits) — wird jetzt abgelehnt.
### B3 — `maxTokens` vermischt Abrechnungs-Budget und Kontextgröße ⚠️
[`LoopGuard.cs:22`](../../src/ClawdDotNet.Core/Engine/LoopGuard.cs#L22), Defaults in
[`AgentConfig.cs:105`](../../src/ClawdDotNet.Core/Config/AgentConfig.cs#L105)
`RecordTokens` summiert `response.Usage.TotalTokens` über alle Schritte. Da jeder Schritt den
**kompletten** Kontext erneut sendet, wächst diese Summe quadratisch:
| Schritt | Kontext | kumuliert |
|---|---|---|
| 1 | 20.000 | 20.000 |
| 2 | 21.000 | 41.000 |
| 3 | 22.000 | 63.000 |
| 4 | 23.000 | **86.000** → Abbruch |
Mit `maxTokens: 80_000` bricht ein völlig normaler Chat nach 4 Schritten mit
"Max tokens exceeded" ab, obwohl der Kontext nie über 23k lag. Das ist vermutlich die
häufigste Frustquelle im laufenden Betrieb.
**Fix:** Zwei getrennte Begriffe:
- `maxCumulativeTokens` — Kostenbudget pro Run (bewusst hoch, z.B. 500k)
- `maxContextTokens` — Kontextgröße, steuert die Compaction (existiert bereits)
und beide getrennt im UI ausweisen.
### B4 — Kostenanzeige ist doppelt falsch
[`frm_main.cs:292`](../../frm_main.cs#L292)
```csharp
_statusService?.RecordUsage(model, result.TokensUsed / 2, result.TokensUsed / 2);
```
1. **50/50-Aufteilung**: Reales Verhältnis in Agenten-Loops ist eher 95:5 (Prompt:Completion).
Da Output ~5x teurer ist, überschätzt die Anzeige die Kosten um Faktor ~2,5.
2. **Modell fehlt in der Preistabelle**: `AgentConfig.Model` hat den Default
`anthropic/claude-sonnet-4-5` — dieser Eintrag existiert in `ModelPricing`
([`OpenRouterStatusService.cs:15`](../../Services/OpenRouterStatusService.cs#L15)) nicht.
`CalculateCost` gibt dann stillschweigend `0` zurück.
Die Tabelle ist zudem veraltet (`claude-sonnet-4`, `claude-opus-4`, `claude-haiku-4.5`).
**Fix:** `Usage` um `PromptTokens`/`CompletionTokens` erweitern und bis in `AgentRunResult`
durchreichen. Preise live vom `/models`-Endpoint holen (liefert `pricing.prompt`/`pricing.completion`)
statt hartzucodieren. Bei unbekanntem Modell sichtbar warnen statt 0 anzuzeigen.
### B5 — Tool-Ergebnisse landen ungekappt im Kontext
Ein einziger `WebFetch` mit dem Default `maxResponseKb: 512`
([`WebFetchTool.cs:90`](../../src/ClawdDotNet.Tools.WebFetch/WebFetchTool.cs#L90)) erzeugt bis zu
512 KB Text ≈ **130.000 Tokens** in einer einzigen Tool-Antwort. `FileRW.read`
([`FileRWTool.cs:245`](../../src/ClawdDotNet.Tools.FileRW/FileRWTool.cs#L245)) hat gar kein Limit,
`Database.query` und `Mail.read_inbox` ebenfalls nicht.
Gekürzt wird erst nachträglich in der Compaction — und dort nur außerhalb der letzten
6 Nachrichten. Der teure Request ist zu dem Zeitpunkt längst bezahlt.
**Fix:** Kappung beim Einfügen in `ExecuteToolCallAsync`
([`AgentEngine.cs:668`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L668)) — zentral, für alle
Tools, mit konfigurierbarem `maxToolResultTokens` und einem Hinweis an das Modell, dass gekürzt
wurde (inkl. Angebot, gezielt nachzulesen).
### B6 — `Task.Delay` wirft bei langen Cron-Intervallen
[`AgentScheduler.cs:89`](../../src/ClawdDotNet.Core/Scheduling/AgentScheduler.cs#L89) und
[`ToolJobScheduler.cs:126`](../../src/ClawdDotNet.Core/Scheduling/ToolJobScheduler.cs#L126)
`Task.Delay` wirft `ArgumentOutOfRangeException` bei Werten über ~24,8 Tagen. Ein jährlicher
Cron (`0 3 1 1 *`) erzeugt eine Wartezeit von bis zu 365 Tagen. Der `catch` fängt nur
`OperationCanceledException` — die Exception beendet den Scheduler-Task still. Da die Tasks in
`_schedulerTasks` nie beobachtet werden (`await` erst beim Dispose), fällt das nicht auf: der
Job läuft ab dann einfach nie wieder.
**Fix:** Delay auf max. 1 Tag deckeln und in einer Schleife erneut prüfen. Zusätzlich alle
Scheduler-Tasks mit einem `ContinueWith`-Fehler-Logger versehen.
### B7 — Cron rechnet in Lokalzeit
[`CronExpression.cs:51`](../../src/ClawdDotNet.Core/Scheduling/CronExpression.cs#L51) mit
`DateTime.Now`. Bei Zeitumstellung: im Oktober läuft ein `0 2 * * *`-Job doppelt, im März gar nicht.
**Fix:** Intern in UTC rechnen und nur für die Anzeige konvertieren, oder `TimeZoneInfo`
explizit berücksichtigen.
### B8 — Keine Rekursionsbremse bei AgentComm / AgentSpawn
[`AgentEngine.cs:510`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L510)
Agent A ruft `send_message(B)``ChatAsync(B)` → B ruft `send_message(A)` → … Es gibt keine
Tiefenbegrenzung und keinen Zyklus-Check. Bei `SpawnAgentAsync` schützt immerhin der
`IsRunning`-Check, bei `send_message` nicht. Begrenzt wird das nur durch die Timeouts —
in der Zwischenzeit brennt jeder Hop einen vollständigen LLM-Run.
**Fix:** Aufruf-Tiefe im `AgentToolContext` mitführen (`maxDelegationDepth`, Default 3) und
den Aufrufpfad zur Zyklenerkennung mitgeben.
### B9 — `index_Count` liest die Datei außerhalb des Locks erneut
[`FileRWTool.cs:569`](../../src/ClawdDotNet.Tools.FileRW/FileRWTool.cs#L569) ruft
[`index_Count`](../../src/ClawdDotNet.Tools.FileRW/FileRWTool.cs#L572) auf, nachdem das Lock
freigegeben wurde — Race mit parallelen `stock_add`-Aufrufen, plus ein überflüssiges
vollständiges Parsen der Index-Datei. Der Zähler ist innerhalb des Locks ohnehin bekannt
(`index.Count`).
### B10 — `PersistChatState` schreibt bei jedem Eintrag alles neu
[`AgentEngine.cs:593`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L593)
Vollständige History **und** vollständiger Kontext werden als `WriteIndented`-JSON bei jedem
einzelnen Chat-Eintrag rausgeschrieben → O(n²) Schreiblast über eine Sitzung. Bei einem Agenten
mit 500 Nachrichten sind das mehrere MB pro Nachricht.
**Fix:** History als Append-only JSONL, Kontext nur periodisch/beim Beenden persistieren,
`WriteIndented = false`.
### B11 — Kein `max_tokens` im Request
[`AgentEngine.cs:105`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L105) und Z.279 setzen
`ChatRequest.MaxTokens` nie. Ein Modell kann in einem Schritt sein volles Output-Limit
ausschöpfen.
### B12 — Kein Retry/Backoff
[`OpenRouterClient.cs:42`](../../src/ClawdDotNet.Core/Api/OpenRouterClient.cs#L42) wirft bei jedem
Nicht-2xx sofort. Ein einzelnes HTTP 429 killt einen kompletten geplanten Run. Bei OpenRouter
sind 429/502/503 im Normalbetrieb zu erwarten.
**Fix:** Polly o.ä. mit exponentiellem Backoff + Jitter für 429/5xx, `Retry-After` respektieren.
### B14 — Compaction dupliziert den System-Prompt bei kurzen Konversationen ⚠️
[`ContextCompactor.cs:140`](../../src/ClawdDotNet.Core/Engine/ContextCompactor.cs#L140)
*Gefunden durch den Property-Test, nicht beim Lesen des Codes.*
```csharp
var tail = messages.Skip(Math.Max(0, messages.Count - ProtectedTailMessages)).ToList();
messages.Clear();
if (systemMsg is not null) messages.Add(systemMsg); // ← System-Prompt
messages.AddRange(tail); // ← enthält ihn nochmal
```
Bei `messages.Count <= 6` ist `Skip(0)` — der Tail enthält also **alle** Nachrichten
einschließlich der System-Nachricht. Diese wird danach ein zweites Mal angehängt.
Ergebnis: zwei `system`-Nachrichten, die zweite mitten in der Konversation. Zusätzlich wird
die komplette Konversation dupliziert, obwohl sie gerade zusammengefasst wurde — die
Compaction *vergrößert* den Kontext in diesem Fall, statt ihn zu verkleinern.
Erreichbar, sobald wenige Nachrichten die Token-Schwelle reißen — z.B. ein einzelner
`WebFetch` mit 500 KB Ergebnis (siehe B5). Minimales Gegenbeispiel aus FsCheck:
System-Prompt + eine User-Nachricht.
**Fix:** Den System-Prompt aus dem Tail herausfiltern, bevor er wieder angehängt wird —
und den Tail generell erst ab der Position hinter der System-Nachricht bilden.
### B13 — Inkonsistente `instanceId`
`RunAsync`/`ChatAsync` bekommen `instanceId` als Parameter, `SendMessageAsync`/`SpawnAgentAsync`
verwenden dagegen das Feld `_instanceId`, das nur gesetzt wird, wenn
`SetAgentConfigProvider` aufgerufen wurde — sonst `""`.
---
## 3. Konzeptfehler (P2)
### K1 — Geplante Agenten haben kein Gedächtnis
`RunAsync` baut bei jedem Cron-Tick eine frische Nachrichtenliste
([`AgentEngine.cs:89`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L89)). Ein Agent, der alle
30 Minuten läuft, weiß nichts von seinem letzten Lauf: er ruft dieselben Quellen erneut ab,
zieht dieselben Schlüsse und kann keine Entwicklung über Zeit verfolgen.
Das ist gleichzeitig die größte Fähigkeitslücke **und** eine dauerhafte Token-Verschwendung.
Der `IStateStore` (SQLite) existiert bereits — es fehlt das Konzept darüber.
### K2 — Prompt-Injection ist unbehandelt
Agenten lesen untrusted Content (Web, Mail, Telegram, X, YouTube-Transkripte) und besitzen
gleichzeitig ausgehende Fähigkeiten (Mail senden, X posten, Dateien schreiben, DB schreiben).
Tool-Ergebnisse werden ununterscheidbar von echten Anweisungen in den Kontext gelegt.
Das ist die klassische gefährliche Kombination aus Datenzugriff, untrusted Input und
Exfiltrations-Kanal. Ein präparierter Tweet oder eine Mail kann den Agenten steuern.
**Ansatz:** Tool-Ergebnisse konsequent als Daten rahmen (`<untrusted_content source="...">`),
im System-Prompt verankern, dass daraus keine Anweisungen befolgt werden, und irreversible
Aktionen an eine Freigabe koppeln (siehe F-A1).
### K3 — Keine Tests
Kein Testprojekt in `ClawdDotNet.slnx`. Bei einer Engine mit Zustandsmaschine, Compaction,
Scheduling und Nebenläufigkeit ist das riskant — die Bugs B1 und B2 wären mit
Unit-Tests sofort aufgefallen.
**Ansatz:** `ClawdDotNet.Core.Tests` mit einem Fake-`OpenRouterClient` (Interface extrahieren).
Erste Kandidaten: `CronExpression`, `ContextCompactor` (Paarungs-Invarianten!), `LoopGuard`,
`FileRWTool`-Pfadprüfungen.
### K4 — Kein Streaming
`request.Stream = false` fest verdrahtet ([`OpenRouterClient.cs:34`](../../src/ClawdDotNet.Core/Api/OpenRouterClient.cs#L34)).
Bei langen Antworten wirkt die UI eingefroren; es gibt nur den Typing-Indicator.
### K5 — Kein Kostenlimit
Es gibt kein hartes Budget pro Agent/Instanz/Tag. Ein Agent in einer Schleife (siehe B8) kann
unbeaufsichtigt Guthaben verbrennen. Die Credits-Anzeige ist rein informativ.
### K6 — Chat-Historie wächst unbegrenzt
`_chatHistories` und die zugehörigen JSON-Dateien werden nie rotiert oder archiviert.
---
## 4. Token-Optimierung
Das ist der Bereich mit dem größten unmittelbaren Hebel. Nach Wirkung sortiert:
### T1 — Prompt-Caching einführen (Einsparung: 6090 % der Input-Tokens) ★★★
Der wichtigste Punkt. Aktuell wird bei **jedem** Schritt der gesamte Prompt neu gesendet und
voll berechnet — inklusive System-Prompt (Identity + Soul + SystemPrompt) und aller
Tool-Definitionen. Das ist ein stabiler Präfix, der sich innerhalb eines Runs nie ändert.
Anthropic-Modelle über OpenRouter unterstützen `cache_control`-Breakpoints: gecachte
Input-Tokens kosten nur ~10 % des Normalpreises (Schreiben in den Cache kostet einmalig ~125 %).
**Rechenbeispiel** — 10-Schritt-Run, Präfix 15k Tokens (System + 10 Tool-Definitionen):
| | Input-Tokens (Präfix) | relative Kosten |
|---|---|---|
| heute | 10 × 15.000 = 150.000 | 100 % |
| mit Caching | 15.000 (×1,25) + 9 × 15.000 (×0,1) ≈ 32.250 effektiv | **~21 %** |
Umsetzung:
1. `ChatMessage.Content` muss das Array-Format unterstützen
(`[{ "type": "text", "text": "...", "cache_control": { "type": "ephemeral" } }]`).
Aktuell ist es ein reiner `string` ([`ChatMessage.cs:12`](../../src/ClawdDotNet.Core/Api/Models/ChatMessage.cs#L12)).
2. Cache-Breakpoint ans Ende des System-Prompts und ans Ende der Tool-Definitionen setzen.
3. Optional einen dritten Breakpoint nach der letzten stabilen Konversationsgrenze (rollierend).
4. `Usage` um `prompt_tokens_details.cached_tokens` erweitern, damit der Effekt messbar wird.
Wichtig: Die Reihenfolge der Nachrichten vor dem Breakpoint muss **byte-identisch** bleiben —
d.h. keine Zeitstempel oder wechselnden Hinweise im System-Prompt.
### T2 — Tool-Ergebnisse beim Einfügen kappen (Einsparung: stark schwankend, bis 90 %) ★★★
Siehe B5. Ein einziger ungekappter `WebFetch` kann mehr Tokens erzeugen als der gesamte
restliche Run. Zentral in `ExecuteToolCallAsync` lösen, nicht pro Tool.
Sinnvolle Defaults: 4.000 Tokens pro Tool-Ergebnis, mit Kürzungshinweis und der Möglichkeit,
gezielt weiterzulesen (Offset-Parameter bei `FileRW.read`, `LIMIT`/`OFFSET` bei `Database.query`).
### T3 — Günstiges Modell für die Compaction (Einsparung: ~95 % der Compaction-Kosten) ★★
[`ContextCompactor.cs:114`](../../src/ClawdDotNet.Core/Engine/ContextCompactor.cs#L114) nutzt
`model` — also das teure Modell des Agenten — um bis zu 30k Zeichen zusammenzufassen.
Bei Opus kostet eine einzige Compaction so mehr als der halbe Run.
**Fix:** `summaryModel` konfigurierbar machen, Default z.B. `google/gemini-2.5-flash` oder
`anthropic/claude-haiku-4.5`. Zusammenfassen ist eine anspruchslose Aufgabe.
### T4 — Proaktiv statt reaktiv kompaktieren ★★
`CompactIfNeededAsync` läuft **nach** dem API-Call
([`AgentEngine.cs:123`](../../src/ClawdDotNet.Core/Engine/AgentEngine.cs#L123)) und nutzt die
`promptTokens` der gerade bezahlten Anfrage. Der überfüllte Prompt wurde also bereits berechnet.
**Fix:** Vor dem Senden prüfen (mit `EstimateTokens`, das es schon gibt) und erst dann den
Request bauen. Die gemessenen `promptTokens` dienen zur Kalibrierung der Schätzung.
### T5 — `maxTokens`-Semantik korrigieren ★★
Siehe B3. Aktuell führt der Fehler dazu, dass Läufe abbrechen und **neu gestartet** werden —
was die Tokenkosten verdoppelt statt sie zu begrenzen.
### T6 — Tool-Definitionen verschlanken ★
Die Beschreibungen sind großzügig: `FileRW` allein hat ~500 Zeichen Description plus ein
Schema mit 11 Properties ≈ 900 Tokens
([`FileRWTool.cs:26`](../../src/ClawdDotNet.Tools.FileRW/FileRWTool.cs#L26)). Bei 8 zugewiesenen
Tools sind das schnell 46k Tokens — bei **jedem** Schritt (ohne Caching).
Maßnahmen:
- Beschreibungen auf das Nötige kürzen, Beispiele in den System-Prompt statt ins Schema.
- `stock_add` aus `FileRW` herauslösen — es ist ein fachfremdes Feature, das das Schema aufbläht,
auch für Agenten, die es nie nutzen.
- Für Agenten mit vielen Tools: zweistufiges Laden (`list_toolsets``load_toolset`).
### T7 — `RunAsync` statt `ChatAsync` für Job-Wakeups prüfen ★
[`ToolJobScheduler.cs:186`](../../src/ClawdDotNet.Core/Scheduling/ToolJobScheduler.cs#L186)
entscheidet über `UseChatContext`. Mit `true` wird die komplette (potenziell riesige)
Chat-Historie in einen "Prüfe neue Mails"-Tick gezogen. Für zustandslose Ticks ist `RunAsync`
um Größenordnungen günstiger — der Default sollte bewusst gesetzt und im UI erklärt sein.
### T8 — `max_tokens` setzen ★
Siehe B11. Verhindert Ausreißer bei der Ausgabe (die teuerste Token-Art).
### T9 — Caching-Wirkung messbar machen ★
Ohne `cached_tokens` im `Usage`-Modell lässt sich der Erfolg von T1 nicht belegen. Sollte
zusammen mit T1 kommen und im Token-Dashboard auftauchen.
### Erwartete Gesamtwirkung
T1 + T2 + T3 + T4 zusammen sollten die Kosten eines typischen Multi-Step-Runs auf
**etwa ein Viertel bis ein Fünftel** senken — ohne Qualitätsverlust, da nur Redundanz und
Ballast entfernt werden.
---
## 5. Vorschläge für Erweiterungen
### Neue Tools
| Tool | Nutzen |
|---|---|
| **Memory** | Strukturiertes Langzeitgedächtnis (Fakten, Entscheidungen, Beobachtungen mit Zeitstempel) auf dem vorhandenen SQLite-Store. Löst K1 und spart massiv Tokens, weil Wissen nicht neu hergeleitet wird. **Höchste Priorität.** |
| **WebSearch** | Aktuell können Agenten nur bekannte Domains abrufen (`WebFetch`), aber nichts finden. Brave/Tavily/SearXNG-Anbindung. |
| **Http** | Generisches REST-Tool mit Allowlist pro Agent. `DirectAPI` ist fest auf Finanz-Provider verdrahtet — jede neue API erfordert heute Code. |
| **Shell** | Sandboxed, mit Kommando-Allowlist. Ersetzt die hartcodierten yt-dlp/ffmpeg-Aufrufe (und behebt S2 gleich mit). |
| **Git** | Für den "Senior Developer"-Agenten — jetzt, wo das Projekt in Git liegt. |
| **Vision** | Charts/Screenshots analysieren — für das Trading-Team naheliegend. |
| **Notify** | Einheitliche Benachrichtigung (Telegram existiert; ntfy/Discord/Push ergänzen). |
### Neue Konzepte
**F-A1 — Freigabe-Workflow (Human-in-the-Loop)**
Irreversible Aktionen (Mail senden, X posten, DB-Schreibzugriff, Datei löschen) landen in einer
Freigabe-Queue im Hauptfenster, statt sofort ausgeführt zu werden. Pro Tool/Aktion
konfigurierbar: `auto` | `approve` | `deny`. Das entschärft K2 und S1/S2 erheblich.
**F-A2 — Audit-Log**
Jeder Tool-Aufruf mit Agent, Zeitstempel, Argumenten, Ergebnis-Status in eine eigene
SQLite-Tabelle. Aktuell existiert nur `LogDebug` — nach einem Zwischenfall ist nicht
rekonstruierbar, was ein Agent getan hat.
**F-A3 — Budget-Guard**
Hartes USD-Limit pro Agent und pro Instanz und Tag. Bei Überschreitung wird der Agent pausiert
und im UI markiert. Setzt B4 (korrekte Kostenerfassung) voraus.
**F-A4 — Run-Historie**
`AgentRunResult` wird aktuell nur im Speicher als "letztes Ergebnis" gehalten
([`AgentScheduler.cs:14`](../../src/ClawdDotNet.Core/Scheduling/AgentScheduler.cs#L14)).
Persistierte Runs (mit Schritten, Tokens, Kosten, Fehlern) wären die Grundlage für Diagnose
und Kostenanalyse.
**F-A5 — Task-Queue statt rekursivem `send_message`**
Agent-zu-Agent-Kommunikation läuft heute als synchrone Rekursion (B8). Eine echte
Aufgaben-Queue mit Zustand (`pending`/`running`/`done`) wäre robuster, nachvollziehbar und
würde Zyklen strukturell verhindern.
**F-A6 — Secret-Store**
DPAPI-verschlüsselte Secrets (S7), mit UI zum Setzen/Rotieren.
**F-A7 — Testprojekt**
Siehe K3.
---
## 6. Vorgeschlagene Reihenfolge
> **Abgelöst durch die [Roadmap](../Roadmap.md)** (Juli 2026). Die offenen Punkte
> werden dort weitergeführt; dieser Abschnitt bleibt als Stand der Bestandsaufnahme
> eingefroren. F-A1/S4, F-A2, F-A5, T6, T7 sowie B6B8 sind in den Roadmap-Vorhaben
> A1A4 aufgegangen.
**Sofort — es blockiert oder gefährdet den Betrieb**
1. ~~B1 Compaction-Paarung (bricht produktiv ab)~~ ✅ behoben
2. ~~B3 `maxTokens`-Semantik (bricht produktiv ab)~~ ✅ behoben
2b. ~~B14 System-Prompt-Duplikat~~ ✅ behoben
3. ~~B2 Race Condition im Chat-Kontext~~ ✅ behoben
4. ~~S2 yt-dlp-Injection~~ ✅ behoben
5. ~~S3 API-Key-Leak~~ ✅ behoben
**Kurzfristig — größter Nutzen pro Aufwand**
6. ~~T1 Prompt-Caching~~ ✅ umgesetzt (inkl. T9 `cached_tokens`)
7. ~~T2 Tool-Ergebnisse kappen (= B5)~~ ✅ umgesetzt
8. ~~T3 Günstiges Compaction-Modell~~ ✅ umgesetzt
9. ~~B4 Kostenerfassung korrigieren~~ ✅ vollständig: Prompt/Completion getrennt
erfasst, Preise kommen live vom `/models`-Endpunkt (`ModelPricingCatalog`),
Modelle ohne Preisdaten werden sichtbar gemeldet statt still mit 0 gerechnet.
10. ~~B12 Retry/Backoff~~ ✅ umgesetzt
11. T4 Proaktiv statt reaktiv kompaktieren
**Mittelfristig — Fundament**
11. ~~S1 DatabaseTool absichern~~ ✅ behoben (`SqlGuard`)
12. Memory-Tool (K1)
13. F-A1 Freigabe-Workflow + F-A2 Audit-Log
14. K3 Testprojekt
15. S4 PermissionGate ausbauen
**Danach**
16. Restliche Bugs (B6B10, B13), S4 PermissionGate, weitere Tools, Streaming
(~~S5~~ ✅ SSRF, ~~S6~~ ✅ Pfadprüfung, ~~S7~~ ✅ Secrets sind behoben)
---
## 7. Umgesetzt
| Punkt | Was |
|---|---|
| B1, B14 | Compaction: sicherer Schnittpunkt, kein doppelter System-Prompt |
| B2 | Chat-Läufe pro Agent serialisiert, `AbortChat` erreicht alle |
| B3 | `maxCumulativeTokens` von `maxContextTokens` getrennt |
| B4 | Prompt/Completion getrennt erfasst, Preise live vom Anbieter |
| B12 | Retry mit Backoff für 429/5xx |
| S1 | `SqlGuard` statt Teilzeichenketten-Prüfung |
| S2 | `YouTubeUrl` + `ArgumentList` gegen Options-Injection |
| S3 | `UrlSanitizer` gegen API-Key-Leak ins Modell |
| S5 | `UrlGuard`, Redirects einzeln geprüft |
| S6 | `WorkspacePath` auf Verzeichnisgrenzen |
| S7 | `SecretProtector` (DPAPI) für Zugangsdaten |
| T1, T9 | Prompt-Caching mit Breakpoints, `cached_tokens` gemessen |
| T2 | Tool-Ergebnisse zentral gekappt |
| T3 | Günstiges Modell für die Zusammenfassung |
| K3 | Testfundament: 309 Tests, davon ~90 gezielte Angriffsfälle |