# 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: 60–90 % 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 4–6k 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 B6–B8 sind in den Roadmap-Vorhaben
> A1–A4 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 (B6–B10, 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 |
