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

15 KiB
Raw Permalink Blame History

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

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

"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:

"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:

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