Files
ClawdDotNet/docs/Bestandsaufnahme-2026-07.md
T
RichardandClaude Opus 4.8 eae13771cf B1, B14 und B3 beheben: Compaction-Paarung und Token-Semantik
B1 — Die Compaction behielt blind die letzten 6 Nachrichten. Fiel diese Grenze
mitten in eine Tool-Sequenz, entstand eine tool-Antwort ohne zugehoerigen
assistant-tool_call; die API lehnt das mit HTTP 400 ab. FindSafeTailStart
verschiebt die Grenze jetzt rueckwaerts auf eine Blockgrenze.

B14 — Bei Konversationen mit hoechstens 6 Nachrichten enthielt der Tail auch die
system-Nachricht, die anschliessend ein zweites Mal angehaengt wurde. Ergebnis
war ein doppelter System-Prompt und eine duplizierte Konversation — die
Compaction vergroesserte den Kontext, statt ihn zu verkleinern. Der Tail beginnt
nun grundsaetzlich hinter dem System-Prompt; liegt davor nichts Nennenswertes,
wird die Kompaktierung uebersprungen. Gefunden durch den Property-Test.

Nebeneffekt: Zusammengefasst wird nur noch der Teil, der tatsaechlich wegfaellt.
Der Tail bleibt woertlich erhalten und musste bisher doppelt bezahlt werden.

B3 — maxTokens zaehlte kumulativ ueber alle Schritte, wurde aber wie eine
Kontextgrenze konfiguriert. Da jeder Schritt den vollen Kontext erneut sendet,
brach ein Chat mit 20k Kontext nach vier Schritten ab. Aufgeteilt in
maxCumulativeTokens (Kostenbudget, Default 500k) und maxContextTokens
(Kontextgroesse). Alte Konfigurationen werden beim Laden migriert, die
Fehlermeldungen unterscheiden jetzt Schritt- und Kostenlimit.

Alle 31 Tests gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 10:15:50 +02:00

27 KiB
Raw Blame History

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

DatabaseTool.cs:218

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.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

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

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

FileRWTool.cs:211

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 ⚠️

ContextCompactor.cs:140

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_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.

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

frm_main.cs:292

_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) 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) 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

AgentEngine.cs:510

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

AgentEngine.cs:593

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 ⚠️

ContextCompactor.cs:140

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: 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).
  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 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 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_toolsetsload_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

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
  4. S2 yt-dlp-Injection
  5. S3 API-Key-Leak

Kurzfristig — größter Nutzen pro Aufwand 6. T1 Prompt-Caching 7. T2 Tool-Ergebnisse kappen (= B5) 8. T3 Günstiges Compaction-Modell 9. B4 Kostenerfassung korrigieren 10. B12 Retry/Backoff

Mittelfristig — Fundament 11. S1 DatabaseTool absichern 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), S5S7, weitere Tools, Streaming