Die Avalonia-Portierung ist abgeschlossen, damit ist die in ClawdDotNet.slnx angekuendigte Aufgabe "WinForms-Oberflaeche entfernen" faellig. Der Stand davor liegt unter dem Tag vor-fruehjahrsputz-2026-08. Entfernt (56 Dateien, seit dem Herausloesen der Anwendungsschicht nicht mehr Teil des Builds): ClawdDotNet.csproj, Program.cs, sieben frm_*-Formulare, UI/, Models/, EmbeddedUI/, Properties/, Resources/, Services/, das alte Anwendungssymbol und Deploy-Build.ps1 (ersetzt durch deploy/publish.py). Dazu configs/*.json - Beispielkonfigurationen aus der Zeit vor dem Instanzverzeichnis, auf die nur noch die alten Prompts verwiesen. Die vier Entwicklungs-Prompts der Anfangszeit ziehen nach docs/archiv/ um, mit README, das ihren Stand einordnet. Eine Regel darin gilt weiter - die Pflichtfelder fetchedAt/dataAsOf/source der Internet-Tools -, deshalb Archiv statt Loeschen; der WebSearch-Plan verweist auf den neuen Pfad. Toter Code - PlaceholderPageViewModel samt Ansicht: Es gibt keinen Platzhalter-Bereich mehr, seit alle neun Seiten portiert sind. - Snappier als direkter Paketverweis: MongoDB.Driver loest es ohnehin auf dieselbe Fassung auf, der Verweis hob nichts an. Zwei Fehler, die dabei sichtbar wurden - Die taegliche Sicherung lief ins Leere. Die Oberflaeche bot sie an und schrieb Uhrzeit, Zielordner und Anzahl in die Einstellungen, aber der BackupScheduler wurde nirgends erzeugt. Jetzt am AppHost verdrahtet und in den geordneten Abbau aufgenommen. - SettingsPageViewModel hielt die vier Sicherungs-Einstellungen doppelt. Aus der Ansicht waren sie laengst verschwunden, gelesen und beim Speichern zurueckgeschrieben wurden sie weiter: Wer die Uhrzeit auf der Sicherungs-Seite aenderte und danach die Einstellungen speicherte, bekam den alten Wert zurueck. Pakete: keine bekannten Sicherheitsluecken mehr - SQLitePCLRaw.bundle_e_sqlite3 auf 2.1.13 angehoben. Microsoft.Data.Sqlite bringt 2.1.11 mit, darin steckt GHSA-2m69-gcr7-jv3q (NU1903, hoch). - SharpCompress bleibt als direkter Verweis stehen. Beim Aufraeumen erst als ungenutzt entfernt - dabei kam die von MongoDB.Driver gezogene Fassung 0.30.1 mit GHSA-6c8g-7p36-r338 zurueck. Der Verweis ist eine Anhebung, kein Ballast; das steht jetzt als Kommentar dabei. Dokumentation - Roadmap mit Statusblock: A1 und A3 erledigt, A2 nur zur Haelfte - Gate, Policy und Dienst greifen, aber keine Ansicht ruft ApproveAsync auf, ein gestagter Aufruf liegt unbeantwortet. Das ist jetzt Punkt 1 der Reihung. Rocket.Chat steht und kollidiert mit A5 (Matrix) - Entscheidung faellig. - Avalonia-Portierungsleitfaden -> Oberflaechen-Leitfaden: kein Auftrag mehr, sondern Beschreibung des Stands. - Bestandsaufnahme und Linux-Analyse als datierte Befunde gekennzeichnet; der teure Teil der Linux-Analyse (8.900 Zeilen WinForms) ist hinfaellig. - Verweise auf frm_*, WebView2 und ClawdDotNet.csproj in den lebenden Dokumenten richtiggestellt. Build fehlerfrei, 585 Tests gruen (6 uebersprungen). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15 KiB
ClawdDotNet – Claude Code Entwicklungs-Prompt
Projektkontext
Wir entwickeln ClawdDotNet – einen modularen "Coworking Space" für AI-Agenten in C# .NET 10 / WinForms. Das Projekt ist bereits angelegt und hat erste UI-Steuerelemente.
Das System ermöglicht es, mehrere spezialisierte AI-Agenten parallel laufen zu lassen, die gemeinsam strukturierte Aufgaben erledigen (z.B. Finanzmarktanalyse, Trading-Empfehlungen, SEO, Programmierung). Als LLM-Backend wird OpenRouter verwendet, damit jeder Agent flexibel ein anderes Modell nutzen kann.
Kernprinzipien – diese gelten für JEDE Zeile Code
-
Agenten blockieren sich niemals gegenseitig. Alle Agent-Runs laufen vollständig async/await mit eigenem CancellationToken. Kein shared mutable state ohne explizites Locking. Kein Agent wartet synchron auf einen anderen.
-
Mehrinstanzfähigkeit von Anfang an. Die Anwendung kann mehrfach gleichzeitig gestartet werden (z.B. ein Prozess für Aktien-Team, ein Prozess für Krypto-Team). Jede Instanz ist vollständig isoliert:
- Eigene Konfigurationsdatei (per Instanz wählbar beim Start, z.B.
--config stock-team.json) - Eigene Datenbankverbindungen (keine Shared-DB-Locks ohne explizites Design dafür)
- Eigener Arbeitsordner und wwwroot-Ordner
- Eigener Netzwerk-Port für den integrierten Webserver (konfigurierbar)
- Keine globalen Singletons, keine statischen Felder mit Zustand
- Eigene Konfigurationsdatei (per Instanz wählbar beim Start, z.B.
-
Core ist niemals von einem Tool abhängig. Der Core kompiliert und läuft vollständig ohne jedes Tool. Fehlt ein Tool, bleibt der Core funktionsfähig. Tools werden zur Laufzeit registriert.
-
Tools sind niemals voneinander abhängig. Tool A darf Tool B weder referenzieren noch aufrufen. Jedes Tool ist ein eigenständiges Projekt/Assembly.
-
Jedes Tool ist pro Agent konfiguriert. Agent A kann auf eine andere Datenbank zugreifen als Agent B. Agent A darf in
./wwwroot/schreiben, Agent B nicht. Die Konfiguration liegt in der AgentConfig, nicht im Tool-Code.
Architektur-Übersicht
ClawdDotNet.sln
├── src/
│ ├── ClawdDotNet.Core/ ← .NET 10 Klassenbibliothek, KEIN Tool-Verweis
│ ├── ClawdDotNet.Tools.Database/ ← Tool-Plugin, nur Core-Verweis
│ ├── ClawdDotNet.Tools.FileRW/ ← Tool-Plugin, nur Core-Verweis
│ ├── ClawdDotNet.Tools.Mail/ ← Tool-Plugin, nur Core-Verweis
│ └── ClawdDotNet.Host/ ← WinForms .NET 10, verweist auf Core + alle Tools
└── configs/
├── stock-team.json
└── crypto-team.json
Phase 1: Core implementieren
1.1 – Kern-Interfaces und Datentypen
Datei: Core/Tools/IAgentTool.cs
namespace ClawdDotNet.Core.Tools;
public interface IAgentTool
{
/// Eindeutiger Name, den das LLM in tool_calls verwendet
string Name { get; }
/// Natürlichsprachige Beschreibung für das LLM (geht in den System-Prompt)
string Description { get; }
/// JSON Schema des Input-Objekts (OpenAI Function Calling Format)
System.Text.Json.JsonElement InputSchema { get; }
/// Ausführung – bekommt NUR seinen eigenen Kontext, nie andere Tools
Task<ToolResult> ExecuteAsync(
System.Text.Json.JsonElement input,
AgentToolContext context,
CancellationToken ct);
}
Datei: Core/Tools/AgentToolContext.cs
namespace ClawdDotNet.Core.Tools;
/// Wird vom Core befüllt und an das Tool übergeben.
/// Das Tool liest seine Konfiguration NUR aus ToolConfig[tool.Name].
public sealed record AgentToolContext(
string AgentId,
string InstanceId, // Mehrinstanz-Isolation
IReadOnlyDictionary<string, object?> ToolConfig, // tool-spezifische Config aus AgentConfig
Microsoft.Extensions.Logging.ILogger Logger,
CancellationToken CancellationToken
);
Datei: Core/Tools/ToolResult.cs
namespace ClawdDotNet.Core.Tools;
public sealed record ToolResult(
bool Success,
string Content, // JSON oder Plaintext, geht zurück ans LLM
string? ErrorMessage = null
);
1.2 – AgentConfig (Konfigurationsmodell)
Datei: Core/Config/AgentConfig.cs
namespace ClawdDotNet.Core.Config;
public sealed class AgentConfig
{
public string AgentId { get; set; } = "";
public string DisplayName { get; set; } = "";
public string Model { get; set; } = "anthropic/claude-sonnet-4-5";
public string SystemPrompt { get; set; } = "";
/// Welche Tools darf dieser Agent nutzen?
/// Key = Tool.Name, Value = tool-spezifische Konfiguration (frei definierbar je Tool)
public Dictionary<string, Dictionary<string, object?>> Tools { get; set; } = new();
public SchedulerConfig? Scheduler { get; set; }
public LoopGuardConfig LoopGuard { get; set; } = new();
}
public sealed class SchedulerConfig
{
public string Cron { get; set; } = ""; // z.B. "0 7 * * 1-5"
public bool RunOnStart { get; set; } = false;
}
public sealed class LoopGuardConfig
{
public int MaxSteps { get; set; } = 20;
public int MaxTokens { get; set; } = 80_000;
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(10);
}
Datei: Core/Config/InstanceConfig.cs
namespace ClawdDotNet.Core.Config;
/// Instanz-weite Konfiguration (eine pro laufendem Prozess)
public sealed class InstanceConfig
{
public string InstanceId { get; set; } = Guid.NewGuid().ToString("N")[..8];
public string InstanceName { get; set; } = "Default";
public string OpenRouterApiKey { get; set; } = "";
public string WorkingDirectory { get; set; } = "./data/";
public int WebServerPort { get; set; } = 8080;
public List<AgentConfig> Agents { get; set; } = new();
}
1.3 – Tool Registry
Datei: Core/Tools/ToolRegistry.cs
namespace ClawdDotNet.Core.Tools;
/// Thread-safe Registry. Wird beim Start im Host befüllt.
/// Der Core kennt keine konkreten Tool-Typen.
public sealed class ToolRegistry
{
private readonly Dictionary<string, IAgentTool> _tools = new();
private readonly Lock _lock = new();
public void Register(IAgentTool tool)
{
lock (_lock)
_tools[tool.Name] = tool;
}
public IAgentTool? Get(string name)
{
lock (_lock)
return _tools.GetValueOrDefault(name);
}
/// Gibt nur die Tools zurück, für die der Agent eine Config hat
public IReadOnlyList<IAgentTool> GetForAgent(AgentConfig agent)
{
lock (_lock)
return _tools.Values
.Where(t => agent.Tools.ContainsKey(t.Name))
.ToList();
}
}
1.4 – Permission Gate
Datei: Core/Security/PermissionGate.cs
namespace ClawdDotNet.Core.Security;
public sealed class PermissionGate
{
public bool IsAllowed(string agentId, string toolName,
Config.AgentConfig agentConfig)
=> agentConfig.Tools.ContainsKey(toolName);
public void Enforce(string agentId, string toolName,
Config.AgentConfig agentConfig)
{
if (!IsAllowed(agentId, toolName, agentConfig))
throw new ToolAccessDeniedException(agentId, toolName);
}
}
public sealed class ToolAccessDeniedException(string agentId, string toolName)
: Exception($"Agent '{agentId}' has no access to tool '{toolName}'.");
1.5 – Loop Guard
Datei: Core/Engine/LoopGuard.cs
namespace ClawdDotNet.Core.Engine;
/// Pro Agent-Run instanziieren, nicht wiederverwenden.
public sealed class LoopGuard
{
private readonly Config.LoopGuardConfig _cfg;
private int _steps;
private int _tokens;
public LoopGuard(Config.LoopGuardConfig cfg) => _cfg = cfg;
public void RecordStep()
{
if (Interlocked.Increment(ref _steps) > _cfg.MaxSteps)
throw new LoopLimitExceededException($"Max steps ({_cfg.MaxSteps}) exceeded.");
}
public void RecordTokens(int count)
{
if (Interlocked.Add(ref _tokens, count) > _cfg.MaxTokens)
throw new LoopLimitExceededException($"Max tokens ({_cfg.MaxTokens}) exceeded.");
}
}
public sealed class LoopLimitExceededException(string message) : Exception(message);
1.6 – OpenRouter Client
Datei: Core/Api/OpenRouterClient.cs
Implementiere einen schlanken HTTP-Client gegen https://openrouter.ai/api/v1/chat/completions.
Format ist OpenAI-kompatibel (JSON).
namespace ClawdDotNet.Core.Api;
public sealed class OpenRouterClient : IDisposable
{
// Basis-URL: https://openrouter.ai/api/v1/
// Header: Authorization: Bearer {ApiKey}
// Header: HTTP-Referer: ClawdDotNet
// Format: OpenAI Chat Completions JSON
// Methoden:
// Task<ChatResponse> CompleteAsync(ChatRequest request, CancellationToken ct)
// IAsyncEnumerable<ChatChunk> StreamAsync(ChatRequest request, CancellationToken ct) [optional]
// ChatRequest enthält: model, messages[], tools[] (optional), tool_choice
// ChatResponse enthält: choices[0].message (content + tool_calls), usage (prompt_tokens, completion_tokens)
}
Nutze System.Net.Http.HttpClient mit IHttpClientFactory-Muster.
Keine externen HTTP-Bibliotheken. Serialisierung mit System.Text.Json.
1.7 – Agent Engine
Datei: Core/Engine/AgentEngine.cs
Der Kern des Agentenablaufs. Pro Agent-Run wird eine neue Instanz erzeugt.
Ablauf eines Agent-Runs:
1. AgentConfig laden → erlaubte Tools aus Registry holen → Tool-Beschreibungen für LLM bauen
2. System-Prompt + User-Message an OpenRouter senden (mit Tool-Definitionen)
3. Antwort prüfen:
a. Enthält tool_calls → PermissionGate.Enforce → Tool.ExecuteAsync → Ergebnis zurück ans LLM
b. Kein tool_call → Antwort ist final → Run beendet
4. Nach jedem Schritt: LoopGuard.RecordStep() + LoopGuard.RecordTokens(usage)
5. Bei Exception: sauber abbrechen, Status = Failed, Exception loggen
Wichtig:
- Jeder Run bekommt seinen eigenen
CancellationToken(kombiniert aus Timeout + externer Abbruch) - Kein
awaitohne CancellationToken - Rückgabe:
AgentRunResultmit Status, FinalMessage, StepCount, TokensUsed, Duration
1.8 – Scheduler
Datei: Core/Scheduling/AgentScheduler.cs
- Nutze
System.Threading.PeriodicTimeroder Cron-Parsing (einfaches eigenes Parsing oder NCrontab NuGet) - Pro AgentConfig mit
Scheduler != nullwird ein eigener Timer gestartet - Scheduled Runs werden als
Taskgestartet (fire-and-forget mit Exception-Handling) RunOnStart = true→ erster Run sofort beim Registrieren- Scheduler ist instanzweit (ein Scheduler pro Prozess, verwaltet alle Agenten)
Phase 2: Tools implementieren
Tool: Database (ClawdDotNet.Tools.Database)
AgentConfig-Beispiel:
"Database": {
"connectionString": "Server=localhost;Database=markets;User=agent_a;Password=...;",
"allowedTables": ["quotes", "indicators", "news"]
}
Implementiere IAgentTool mit diesen Operationen (via action-Feld im Input):
query– SELECT, nur aufallowedTables, SQL-Injection-Schutz via Parameterized Queriesinsert– INSERT, nur aufallowedTablesupsert– INSERT ... ON DUPLICATE KEY UPDATE
Verbindungsstring kommt IMMER aus context.ToolConfig, nie aus statischen Feldern.
NuGet: MySqlConnector (für MySQL) und/oder MongoDB.Driver (für MongoDB), je nach Config-Eintrag "type": "mysql" oder "type": "mongodb".
Tool: FileRW (ClawdDotNet.Tools.FileRW)
AgentConfig-Beispiel:
"FileRW": {
"rootPath": "./data/analyst/",
"allowWrite": true,
"allowedExtensions": [".txt", ".json", ".html", ".md"]
}
Operationen: read, write, append, list, delete
Sicherheit (Pflicht):
- Alle Pfade werden mit
Path.GetFullPathaufgelöst - Prüfe:
resolvedPath.StartsWith(Path.GetFullPath(rootPath))— sonstPathTraversalException - Nur Dateien mit erlaubter Extension (aus
allowedExtensions) dürfen gelesen/geschrieben werden
Tool: Mail (ClawdDotNet.Tools.Mail)
AgentConfig-Beispiel:
"Mail": {
"imapHost": "imap.example.com",
"imapPort": 993,
"smtpHost": "smtp.example.com",
"smtpPort": 587,
"username": "agent@example.com",
"password": "...",
"allowedRecipients": ["owner@example.com"]
}
Operationen: send, read_inbox, read_message, mark_read
NuGet: MailKit
Empfänger müssen in allowedRecipients stehen, sonst Exception.
Phase 3: Host (WinForms)
Datei: Host/Program.cs
Startparameter: --config <pfad> (optional, default: ./config.json)
// Startup-Ablauf:
// 1. InstanceConfig aus JSON laden (Pfad aus --config Argument)
// 2. ToolRegistry befüllen (Database, FileRW, Mail registrieren)
// 3. PermissionGate, AgentScheduler, OpenRouterClient instanziieren
// 4. AgentScheduler starten (alle Agenten aus InstanceConfig)
// 5. WinForms Application.Run(new MainForm(...))
MainForm: Zeigt pro Agent eine Statuszeile (AgentId, letzter Run, Status, Token-Verbrauch). Manueller "Run now"-Button pro Agent. Log-Output in einer ListBox oder RichTextBox.
Konfigurationsbeispiel: stock-team.json
{
"instanceId": "stock-01",
"instanceName": "Aktien-Team",
"openRouterApiKey": "sk-or-...",
"workingDirectory": "./data/stock/",
"webServerPort": 8081,
"agents": [
{
"agentId": "market-analyst",
"displayName": "Marktanalyse",
"model": "anthropic/claude-sonnet-4-5",
"systemPrompt": "Du bist ein erfahrener Marktanalyst...",
"tools": {
"Database": {
"connectionString": "Server=db1;Database=stocks;...",
"allowedTables": ["quotes", "indicators"]
},
"FileRW": {
"rootPath": "./data/stock/analyst/",
"allowWrite": true,
"allowedExtensions": [".json", ".txt"]
}
},
"scheduler": { "cron": "0 7 * * 1-5", "runOnStart": false },
"loopGuard": { "maxSteps": 25, "maxTokens": 100000 }
},
{
"agentId": "webdev",
"displayName": "Web-Entwickler",
"model": "google/gemini-flash-1.5",
"systemPrompt": "Du erstellst HTML-Dashboards...",
"tools": {
"FileRW": {
"rootPath": "./data/stock/wwwroot/",
"allowWrite": true,
"allowedExtensions": [".html", ".css", ".js", ".json"]
}
},
"loopGuard": { "maxSteps": 10, "maxTokens": 40000 }
}
]
}
Entwicklungsregeln (für Claude Code)
- Keine externen Frameworks außer:
MailKit,MySqlConnector,MongoDB.Driver, optionalNCrontab - Keine statischen Zustände in Tools oder Engine-Komponenten
- Jeder await-Aufruf bekommt einen CancellationToken
- Exceptions in Agent-Runs niemals schlucken — loggen und als
AgentRunResultmit Status=Failed zurückgeben - Alle Pfadoperationen in FileRW mit Path-Traversal-Check
- Connection Strings kommen immer aus
AgentToolContext.ToolConfig, nie hardcoded - Tests: Für Core-Komponenten (PermissionGate, LoopGuard, FileRW-Pfadprüfung) xUnit-Unit-Tests anlegen
- Logging:
Microsoft.Extensions.Logging.ILoggerüberall, kein Console.WriteLine in Produktionscode
Startreihenfolge für Claude Code
- Solution-Struktur und .csproj-Dateien anlegen (Projekt-Verweise korrekt setzen)
- Core vollständig implementieren (Interfaces, Config, Registry, Gate, Guard, Client, Engine, Scheduler)
- Tool: FileRW (einfachstes Tool, gut testbar)
- Tool: Database
- Tool: Mail
- Host: Program.cs Startup, MainForm UI
- xUnit Tests für Core + FileRW
- Beispiel-Configs erstellen
Beginne mit Schritt 1.