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>
29 KiB
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 — 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
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 |