Files
ClawdDotNet/ClawdDotNet_StartPrompt.md
T
RichardandClaude Opus 4.8 2fed388c99 Initial commit: ClawdDotNet
Import des bestehenden Projektstands in Git.
- .NET 10 WinForms Anwendung (Multi-Agent / Tool-System)
- .gitignore fuer Build-Artefakte, Secrets und Runtime-Daten ergaenzt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 18:21:46 +02:00

471 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
1. **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.
2. **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
3. **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.
4. **Tools sind niemals voneinander abhängig.**
Tool A darf Tool B weder referenzieren noch aufrufen. Jedes Tool ist ein eigenständiges Projekt/Assembly.
5. **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`**
```csharp
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`**
```csharp
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`**
```csharp
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`**
```csharp
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`**
```csharp
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`**
```csharp
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`**
```csharp
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`**
```csharp
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).
```csharp
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 `await` ohne CancellationToken
- Rückgabe: `AgentRunResult` mit Status, FinalMessage, StepCount, TokensUsed, Duration
### 1.8 Scheduler
**Datei: `Core/Scheduling/AgentScheduler.cs`**
- Nutze `System.Threading.PeriodicTimer` oder Cron-Parsing (einfaches eigenes Parsing oder NCrontab NuGet)
- Pro AgentConfig mit `Scheduler != null` wird ein eigener Timer gestartet
- Scheduled Runs werden als `Task` gestartet (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:
```json
"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 auf `allowedTables`, SQL-Injection-Schutz via Parameterized Queries
- `insert` INSERT, nur auf `allowedTables`
- `upsert` 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:
```json
"FileRW": {
"rootPath": "./data/analyst/",
"allowWrite": true,
"allowedExtensions": [".txt", ".json", ".html", ".md"]
}
```
Operationen: `read`, `write`, `append`, `list`, `delete`
**Sicherheit (Pflicht):**
- Alle Pfade werden mit `Path.GetFullPath` aufgelöst
- Prüfe: `resolvedPath.StartsWith(Path.GetFullPath(rootPath))` — sonst `PathTraversalException`
- Nur Dateien mit erlaubter Extension (aus `allowedExtensions`) dürfen gelesen/geschrieben werden
### Tool: Mail (`ClawdDotNet.Tools.Mail`)
AgentConfig-Beispiel:
```json
"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`)
```csharp
// 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`
```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`, optional `NCrontab`
- **Keine statischen Zustände** in Tools oder Engine-Komponenten
- **Jeder await-Aufruf** bekommt einen CancellationToken
- **Exceptions** in Agent-Runs niemals schlucken — loggen und als `AgentRunResult` mit 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
1. Solution-Struktur und .csproj-Dateien anlegen (Projekt-Verweise korrekt setzen)
2. Core vollständig implementieren (Interfaces, Config, Registry, Gate, Guard, Client, Engine, Scheduler)
3. Tool: FileRW (einfachstes Tool, gut testbar)
4. Tool: Database
5. Tool: Mail
6. Host: Program.cs Startup, MainForm UI
7. xUnit Tests für Core + FileRW
8. Beispiel-Configs erstellen
**Beginne mit Schritt 1.**