Geplante Agenten begannen bei jedem Cron-Lauf bei null. Ein Agent, der alle 30
Minuten lief, wusste nichts von seinem letzten Durchgang — er rief dieselben
Quellen ab, zog dieselben Schluesse und konnte keine Entwicklung ueber Zeit
verfolgen. Das war zugleich die groesste Faehigkeitsluecke und eine dauerhafte
Token-Verschwendung.
Speicher-Fundament
SqliteStorage buendelt den Zugang zur Instanz-Datenbank und aktiviert WAL,
busy_timeout und Connection-Pooling. Vorher oeffnete jeder Aufruf eine Verbindung
ohne diese Einstellungen; bei mehreren gleichzeitig schreibenden Agenten gab das
"database is locked". Das sah nach einer Grenze von SQLite aus, war aber nur
fehlende Konfiguration. Zwei Tests decken das gezielt ab.
Gedaechtnis
Typisierte Tabelle statt JSON in einer Wert-Spalte — nur so laesst sich filtern,
sortieren und spaeter auswerten. Das Schema ist schlicht gehalten, damit eine
MySQL-Variante spaeter dieselbe Struktur mit wenigen Dialektunterschieden
bekommen kann.
Der wichtigste Teil ist der optionale Schluessel: Erneutes Merken darunter
aktualisiert den Eintrag, statt einen zweiten anzulegen. Ohne das wuechse das
Gedaechtnis eines halbstuendlich laufenden Agenten um 48 Eintraege pro Tag zur
selben Sache. Beobachtungen ohne Schluessel sammeln sich weiterhin an, wenn ein
Verlauf entstehen soll.
Der Abruf sortiert nach Wichtigkeit, dann Aktualitaet — wesentlich, weil das
Ergebnis begrenzt wird und bei einer Kappung das Wichtigste ueberleben muss.
Zusaetzlich greift eine Zeichenobergrenze, damit ein Abruf den Kontext nicht
sprengt.
Die Trennung privat/geteilt ist absichtlich dieselbe wie beim FileRW-Tool, damit
das Konzept fuer Agenten wiedererkennbar bleibt.
Beim Testen fiel auf, dass das Maskieren der LIKE-Platzhalter falsch war: Die
Zeichen wurden entfernt statt maskiert, wodurch eine Suche nach einem
Prozentzeichen zu einem leeren Muster und damit zu einem Treffer auf alles wurde.
Jetzt mit ESCAPE-Klausel.
338 Tests gruen (190 Core, 148 Tools).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Konversationskontext _chatContexts[agentId] ist eine geteilte List<ChatMessage>.
Nur der Lookup lief unter Lock, alle Add-Aufrufe im Schleifenkoerper waren
ungeschuetzt. Da WebView, ToolJob-Wakeups und AgentComm denselben Agenten
gleichzeitig ansprechen koennen, verschraenkten sich ihre Nachrichten zu einer
ungueltigen Tool-Sequenz, die die API mit HTTP 400 ablehnt.
ChatAsync laeuft jetzt hinter einem SemaphoreSlim(1,1) pro Agent; verschiedene
Agenten bleiben unabhaengig. Die Timeout-Uhr startet erst nach dem Eintritt,
damit Wartezeit in der Warteschlange den Lauf nicht aufzehrt.
Zwei Folgeprobleme mit demselben Ursprung:
- _runningChats hielt nur EINE CancellationTokenSource je Agent; der zweite Lauf
ueberschrieb den ersten. AbortChat brach dadurch nur einen ab, der andere lief
bis ins Run-Timeout. Jetzt eine Liste, die auch wartende Laeufe erfasst.
- ExecuteToolCallAsync fing OperationCanceledException mit ab und gab sie als
Tool-Ergebnis zurueck, wodurch der Abbruch erst einen Schritt spaeter griff.
Cancellation wird nun durchgereicht.
Ausserdem: send_message an den eigenen Agenten wird abgelehnt — es waere mit dem
neuen Gate in einen Deadlock gelaufen.
Neu: GetChatContext(agentId) als Momentaufnahme des Kontexts, fuer Diagnose und
Kontextgroessen-Anzeige.
Build-Fix: Das WinForms-Projekt globbt **/*.cs und kompilierte dadurch die
Test-Quellen mit. tests\** wird jetzt wie src\** ausgeschlossen.
Alle 49 Tests gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>