29 KiB
ClawdDotNet — Bestandsaufnahme (Juli 2026)
Vollständiges Review von Core-Engine, Security-Layer, Tools, Scheduling und UI.
Stand: Commit 92e50d3.
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
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:
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) 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, auch Z.484 und Z.490
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, auch Z.99, 154, 177, 263
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.jsonauf 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
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
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)
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
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).
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 ⚠️
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 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_messageauf (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:
ExecuteToolCallAsyncfingOperationCanceledExceptionmit ab und gab sie als Tool-Fehlerergebnis zurück. Der Abbruch griff dadurch erst einen Schritt später.send_messagean 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, Defaults in
AgentConfig.cs:105
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
_statusService?.RecordUsage(model, result.TokensUsed / 2, result.TokensUsed / 2);
- 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.
- Modell fehlt in der Preistabelle:
AgentConfig.Modelhat den Defaultanthropic/claude-sonnet-4-5— dieser Eintrag existiert inModelPricing(OpenRouterStatusService.cs:15) nicht.CalculateCostgibt dann stillschweigend0zurü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) erzeugt bis zu
512 KB Text ≈ 130.000 Tokens in einer einzigen Tool-Antwort. FileRW.read
(FileRWTool.cs:245) 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) — 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 und
ToolJobScheduler.cs:126
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 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
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 ruft
index_Count 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
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 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 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 ⚠️
Gefunden durch den Property-Test, nicht beim Lesen des Codes.
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). 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).
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:
ChatMessage.Contentmuss das Array-Format unterstützen ([{ "type": "text", "text": "...", "cache_control": { "type": "ephemeral" } }]). Aktuell ist es ein reinerstring(ChatMessage.cs:12).- Cache-Breakpoint ans Ende des System-Prompts und ans Ende der Tool-Definitionen setzen.
- Optional einen dritten Breakpoint nach der letzten stabilen Konversationsgrenze (rollierend).
Usageumprompt_tokens_details.cached_tokenserweitern, 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 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) 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). 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_addausFileRWherauslö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
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).
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 (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
B1 Compaction-Paarung (bricht produktiv ab)✅ behobenB3✅ behoben 2b.maxTokens-Semantik (bricht produktiv ab)B14 System-Prompt-Duplikat✅ behobenB2 Race Condition im Chat-Kontext✅ behobenS2 yt-dlp-Injection✅ behobenS3 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 |