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