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>
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
# ClawdDotNet – Instanz-Setup-Guide
|
||||
|
||||
Anleitung für die vollständige Planung, Erstellung und Konfiguration einer neuen ClawdDotNet-Instanz. Richtet sich an menschliche Nutzer und AI-Agenten gleichermaßen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Planung: Was soll die Instanz leisten?
|
||||
|
||||
Bevor eine Instanz erstellt wird, muss klar definiert werden:
|
||||
|
||||
| Frage | Beispiel |
|
||||
|-------|---------|
|
||||
| Welche Aufgabe(n) soll die Instanz als Ganzes erledigen? | "Social-Media-Monitoring mit automatischer Zusammenfassung" |
|
||||
| Wie viele Agenten werden benötigt? | 1–5 (mehr = höhere Koordinationskosten) |
|
||||
| Welche externen Systeme sind beteiligt? | Telegram, E-Mail, Datenbank, APIs |
|
||||
| Wie hoch ist das Token-Budget pro Tag? | z.B. $1/Tag, $10/Tag |
|
||||
| Soll die Instanz autonom arbeiten oder manuell getriggert werden? | Cron-Jobs vs. manueller Start |
|
||||
|
||||
### Agentenanzahl richtig wählen
|
||||
|
||||
| Szenario | Empfehlung |
|
||||
|----------|-----------|
|
||||
| Eine klar abgegrenzte Aufgabe | 1 Agent |
|
||||
| Aufgabe mit getrennten Zuständigkeiten (z.B. Recherche + Bericht) | 2–3 Agenten |
|
||||
| Komplexes System mit Koordination | 3–5 Agenten, davon 1 Koordinator |
|
||||
| Mehr als 5 Agenten | Kritisch hinterfragen — Koordinationsoverhead steigt quadratisch |
|
||||
|
||||
**Faustregel:** Jeder Agent, der mit einem anderen kommuniziert, kostet Token auf beiden Seiten. Zwei Agenten, die jeweils 3 Nachrichten austauschen = 6 LLM-Runs.
|
||||
|
||||
---
|
||||
|
||||
## 2. Verzeichnisstruktur einer Instanz
|
||||
|
||||
Eine Instanz hat folgende Struktur. `CreateInstance()` und `CreateAgent()` im InstanceDirectoryManager erzeugen diese automatisch.
|
||||
|
||||
```
|
||||
Instance-{Name}/
|
||||
├── InstanceSettings.json ← Instanz-Konfiguration + API-Key
|
||||
├── TokenUsage.json ← Verbrauchsprotokoll (leer bei Start)
|
||||
├── state.db ← SQLite StateStore (wird zur Laufzeit erzeugt)
|
||||
└── Agents/
|
||||
├── AgentList.json ← Agenten-Register (Name, Beschreibung, Ordner)
|
||||
├── SharedWorkspace/ ← Geteilter Arbeitsbereich
|
||||
│ └── coordination/ ← Status-Dateien für Inter-Agenten-Kommunikation
|
||||
└── Agent-{Name}/
|
||||
├── AgentSettings.json ← Agent-Konfiguration (Modell, Tools, Scheduler)
|
||||
├── Identity.md ← WER ist der Agent (Rolle, Expertise)
|
||||
├── Soul.md ← WIE arbeitet der Agent (Persönlichkeit, Methodik)
|
||||
├── ChatContext.json ← Aktueller Konversationskontext
|
||||
├── ChatHistory.json ← Vollständiger Chatverlauf
|
||||
├── Logs/ ← Agent-spezifische Logs
|
||||
└── Workspace/ ← Persönlicher Arbeitsbereich
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Schritt-für-Schritt: Instanz erstellen
|
||||
|
||||
### 3.1 Instanz anlegen
|
||||
|
||||
Über die UI: **Instanz-Manager → Neue Instanz erstellen**
|
||||
|
||||
Oder manuell die Ordnerstruktur erzeugen. Die `InstanceSettings.json` hat dieses Format:
|
||||
|
||||
```json
|
||||
{
|
||||
"instanceId": "<8-Zeichen-GUID>",
|
||||
"instanceName": "MeinProjekt",
|
||||
"openRouterApiKey": "",
|
||||
"workingDirectory": "",
|
||||
"logDirectory": "./Logs",
|
||||
"webServerPort": 8080,
|
||||
"agents": [],
|
||||
"services": []
|
||||
}
|
||||
```
|
||||
|
||||
**Wichtig:** Der `openRouterApiKey` muss gesetzt werden, bevor Agenten funktionieren.
|
||||
|
||||
### 3.2 Agenten anlegen
|
||||
|
||||
Pro Agent wird ein Ordner `Agent-{Name}` unter `Agents/` erstellt.
|
||||
|
||||
**AgentList.json** — Register aller Agenten:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": [
|
||||
{
|
||||
"name": "Analyst",
|
||||
"description": "Recherchiert und analysiert Daten aus externen Quellen",
|
||||
"folderName": "Agent-Analyst"
|
||||
},
|
||||
{
|
||||
"name": "Reporter",
|
||||
"description": "Erstellt Berichte und versendet sie per E-Mail",
|
||||
"folderName": "Agent-Reporter"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 AgentSettings.json
|
||||
|
||||
Jeder Agent bekommt eine `AgentSettings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agentId": "analyst",
|
||||
"displayName": "Analyst",
|
||||
"model": "google/gemini-2.5-flash",
|
||||
"systemPrompt": "",
|
||||
"tools": {
|
||||
"WebFetch": {
|
||||
"allowedDomains": ["example.com", "api.example.com"]
|
||||
},
|
||||
"FileRW": {
|
||||
"sharedAccessLevel": "ReadWrite",
|
||||
"personalAllowedExtensions": [".txt", ".json", ".md", ".csv"],
|
||||
"sharedAllowedExtensions": [".txt", ".json", ".md"]
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"cron": "0 8 * * 1-5",
|
||||
"runOnStart": false,
|
||||
"taskMessage": "Recherchiere die neuesten Daten und speichere sie im SharedWorkspace."
|
||||
},
|
||||
"toolJobs": [],
|
||||
"loopGuard": {
|
||||
"maxSteps": 15,
|
||||
"maxTokens": 50000,
|
||||
"timeoutSeconds": 300,
|
||||
"maxContextTokens": 100000,
|
||||
"compactionThreshold": 0.8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Identity.md schreiben
|
||||
|
||||
Definiert **WER** der Agent ist. Kurz, prägnant, rollenspezifisch.
|
||||
|
||||
```markdown
|
||||
# Analyst
|
||||
|
||||
Du bist ein Datenanalyst im ClawdDotNet-Team "MeinProjekt".
|
||||
|
||||
## Rolle
|
||||
- Recherche und Aufbereitung externer Daten
|
||||
- Ergebnisse im SharedWorkspace als JSON ablegen
|
||||
|
||||
## Expertise
|
||||
- Web-Recherche, Datenextraktion, strukturierte Zusammenfassungen
|
||||
|
||||
## Kontext
|
||||
- Deine Ergebnisse werden vom Reporter-Agenten weiterverarbeitet
|
||||
- Lege Dateien im SharedWorkspace unter coordination/ ab
|
||||
```
|
||||
|
||||
### 3.5 Soul.md schreiben
|
||||
|
||||
Definiert **WIE** der Agent arbeitet.
|
||||
|
||||
```markdown
|
||||
# Arbeitsweise
|
||||
|
||||
## Persönlichkeit
|
||||
- Gründlich und faktenbasiert
|
||||
- Komprimiert Informationen auf das Wesentliche
|
||||
|
||||
## Methodik
|
||||
1. Quellen prüfen und Daten abrufen
|
||||
2. Relevante Informationen extrahieren
|
||||
3. Strukturiertes JSON erstellen
|
||||
4. Im SharedWorkspace ablegen
|
||||
|
||||
## Ausgabeformat
|
||||
- Ergebnisse immer als JSON mit Timestamp
|
||||
- Keine Prosa, nur strukturierte Daten
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Modellwahl: Kosten vs. Leistung
|
||||
|
||||
Die Modellwahl hat den größten Einfluss auf die laufenden Kosten. Nicht jeder Agent braucht das teuerste Modell.
|
||||
|
||||
### Modell-Empfehlungen nach Aufgabentyp
|
||||
|
||||
| Aufgabentyp | Empfohlenes Modell | Kosten (ca.) | Begründung |
|
||||
|-------------|-------------------|-------------|------------|
|
||||
| **Einfache Routineaufgaben** (Dateien verschieben, Status prüfen, weiterleiten) | `google/gemini-2.5-flash` | ~$0.15/M input | Schnell, günstig, zuverlässiger Tool-Use |
|
||||
| **Textverarbeitung** (Zusammenfassungen, Berichte, E-Mails) | `google/gemini-2.5-flash` oder `anthropic/claude-haiku-4-5` | $0.15–0.80/M input | Gutes Preis/Leistungs-Verhältnis |
|
||||
| **Analyse & Reasoning** (Datenanalyse, Entscheidungen, komplexe Logik) | `anthropic/claude-sonnet-4-5` | ~$3/M input | Starkes Reasoning bei akzeptablen Kosten |
|
||||
| **Koordinator-Agent** (orchestriert andere, trifft Entscheidungen) | `anthropic/claude-sonnet-4-5` | ~$3/M input | Braucht gutes Verständnis der Gesamtsituation |
|
||||
| **Maximale Qualität** (kritische Entscheidungen, kreative Aufgaben) | `anthropic/claude-opus-4` | ~$15/M input | Nur wenn Qualität wichtiger als Kosten |
|
||||
| **Budget-Minimum** | `google/gemini-2.5-flash-lite` | ~$0.02/M input | Für einfachste Aufgaben, schlechterer Tool-Use |
|
||||
|
||||
### Kostenberechnung
|
||||
|
||||
```
|
||||
Kosten pro Run ≈ (Input-Tokens × Inputpreis) + (Output-Tokens × Outputpreis)
|
||||
|
||||
Beispiel: Gemini 2.5 Flash, typischer Run (5000 in, 1000 out):
|
||||
= 5000 × $0.00000015 + 1000 × $0.0000006
|
||||
= $0.00075 + $0.0006
|
||||
= $0.00135 pro Run
|
||||
|
||||
Beispiel: Claude Sonnet, typischer Run (5000 in, 1000 out):
|
||||
= 5000 × $0.000003 + 1000 × $0.000015
|
||||
= $0.015 + $0.015
|
||||
= $0.03 pro Run
|
||||
```
|
||||
|
||||
**Cron alle 5 Minuten mit Gemini Flash:** ~288 Runs/Tag × $0.00135 = **~$0.39/Tag**
|
||||
**Cron alle 5 Minuten mit Claude Sonnet:** ~288 Runs/Tag × $0.03 = **~$8.64/Tag**
|
||||
|
||||
### LoopGuard für Kostenkontrolle anpassen
|
||||
|
||||
| Szenario | maxSteps | maxTokens | timeoutSeconds |
|
||||
|----------|----------|-----------|----------------|
|
||||
| Einfache Routine | 5–10 | 20.000 | 120 |
|
||||
| Standard-Aufgabe | 10–20 | 50.000 | 300 |
|
||||
| Komplexe Analyse | 20–30 | 80.000 | 600 |
|
||||
| Budget-kritisch | 3–5 | 10.000 | 60 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Tool-Zuweisung
|
||||
|
||||
### Verfügbare Tools
|
||||
|
||||
| Tool | Zweck | Benötigte Config | Background Jobs |
|
||||
|------|-------|-----------------|-----------------|
|
||||
| **FileRW** | Dateien lesen/schreiben im Workspace | (optional) sharedAccessLevel, allowedExtensions | Nein |
|
||||
| **Database** | SQL/NoSQL-Zugriff | type, connectionString | Nein |
|
||||
| **Mail** | E-Mails senden/empfangen | smtpHost, imapHost, username, password | Ja: `mail_check_unread` |
|
||||
| **Telegram** | Telegram-Nachrichten | botToken | Ja: `telegram_poll` |
|
||||
| **WebFetch** | Webseiten/RSS abrufen | allowedDomains | Nein |
|
||||
| **WebMonitor** | Webseiten auf Änderungen prüfen | monitors (mit url, parser, idPattern) | Nein |
|
||||
| **FTP** | Datei-Upload/Download | host | Nein |
|
||||
| **DirectAPI** | Finanzdaten (Aktien, Crypto, Forex) | providers (mit apiKey pro Provider) | Nein |
|
||||
| **SocialMediaManager** | X, Reddit, YouTube-Transkripte | (je nach Aktion) xApiKey, openRouterApiKey | Ja: `sm_yt_monitor`, `sm_transcript_notifier` |
|
||||
| **AgentComm** | Nachrichten an andere Agenten senden | (keine) | Nein |
|
||||
| **AgentSpawn** | Andere Agenten starten und beauftragen | (keine) | Nein |
|
||||
|
||||
### Zuweisungsregeln
|
||||
|
||||
1. **Minimalprinzip:** Nur Tools zuweisen, die der Agent tatsächlich braucht
|
||||
2. **Tool = Berechtigung:** Ein Tool in `tools` aufzunehmen gewährt sofort Zugriff (PermissionGate prüft nur Existenz im Dictionary)
|
||||
3. **Config = Sicherheitsgrenze:** `allowedDomains`, `allowedTables`, `allowedRecipients` etc. sind die echten Einschränkungen
|
||||
4. **AgentComm/AgentSpawn:** Nur dem Koordinator-Agenten zuweisen, nicht jedem
|
||||
5. **Tool Jobs prüfen:** Ein ToolJob funktioniert nur wenn der Agent das Tool auch zugewiesen hat — die Laufzeitprüfung deaktiviert den Job sonst automatisch
|
||||
|
||||
### Typische Tool-Kombinationen
|
||||
|
||||
| Agenten-Rolle | Tools |
|
||||
|--------------|-------|
|
||||
| Koordinator | FileRW, AgentComm, AgentSpawn |
|
||||
| Recherche-Agent | FileRW, WebFetch, WebMonitor |
|
||||
| Kommunikations-Agent | FileRW, Mail, Telegram |
|
||||
| Datenbank-Agent | FileRW, Database |
|
||||
| Social-Media-Agent | FileRW, SocialMediaManager, WebFetch |
|
||||
| Finanz-Agent | FileRW, DirectAPI, Database |
|
||||
|
||||
---
|
||||
|
||||
## 6. Kritische Analyse: Ist die Idee umsetzbar?
|
||||
|
||||
Vor dem Aufbau einer Instanz systematisch prüfen:
|
||||
|
||||
### Checkliste: Machbarkeit
|
||||
|
||||
| Prüfpunkt | Frage | Risiko wenn nein |
|
||||
|-----------|-------|-----------------|
|
||||
| **Tool-Abdeckung** | Gibt es für jeden externen Zugang ein passendes Tool? | Hoch — ohne Tool kein Zugriff |
|
||||
| **API-Verfügbarkeit** | Haben alle externen APIs die nötigen Endpunkte? | Hoch — Tool ist nutzlos ohne funktionierende API |
|
||||
| **Datenformat** | Kann das Tool die Daten in einem Format liefern, das das LLM verarbeiten kann? | Mittel — große/binäre Daten überfordern den Context |
|
||||
| **Autonomie-Level** | Kann die Aufgabe ohne menschliche Zwischenschritte laufen? | Mittel — wenn nicht, braucht es manuelle Trigger statt Cron |
|
||||
| **Budget** | Reicht das Token-Budget für die geplante Frequenz? | Hoch — unterschätzter Verbrauch ist der häufigste Fehler |
|
||||
| **Koordination** | Brauchen Agenten wirklich Echtzeit-Kommunikation oder reicht SharedWorkspace? | Mittel — AgentComm kostet Token auf beiden Seiten |
|
||||
|
||||
### Häufige Fallstricke und Lösungen
|
||||
|
||||
#### Fallstrick 1: Token-Explosion durch Polling
|
||||
|
||||
**Problem:** Agent wird per Cron alle 2 Minuten geweckt → 720 LLM-Runs/Tag, auch wenn nichts passiert ist.
|
||||
|
||||
**Lösung:** Tool Jobs (`IToolJobProvider`) statt Agent-Wakeup verwenden. Das Tool prüft selbst (kein LLM) und weckt den Agenten nur bei Bedarf. Kostet ~0 Token pro Leer-Check.
|
||||
|
||||
```json
|
||||
"scheduler": null,
|
||||
"toolJobs": [
|
||||
{
|
||||
"toolName": "Telegram",
|
||||
"jobTypeId": "telegram_poll",
|
||||
"cron": "*/1 * * * *",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Fallstrick 2: Context-Overflow bei großen Daten
|
||||
|
||||
**Problem:** WebFetch liefert 50 KB HTML, Database-Query liefert 200 Zeilen → Context voll, Compaction verliert wichtige Infos.
|
||||
|
||||
**Lösung:**
|
||||
- `maxResponseKb` in WebFetch begrenzen (Standard: 512 KB — oft zu viel)
|
||||
- Database-Queries mit LIMIT versehen (in SystemPrompt anweisen)
|
||||
- LoopGuard `maxContextTokens` angemessen setzen
|
||||
- Agent in der Identity anweisen, Ergebnisse sofort zu verarbeiten und zusammenzufassen
|
||||
|
||||
#### Fallstrick 3: Agenten reden aneinander vorbei
|
||||
|
||||
**Problem:** AgentComm-Nachrichten ohne klare Struktur → Missverständnisse, Endlosschleifen.
|
||||
|
||||
**Lösung:**
|
||||
- In Soul.md ein festes Nachrichtenformat definieren (JSON mit `type`, `content`, `expectedAction`)
|
||||
- SharedWorkspace für asynchronen Datenaustausch bevorzugen (Dateien statt Nachrichten)
|
||||
- Koordinator-Agent als einzigen mit AgentSpawn/AgentComm ausstatten
|
||||
|
||||
#### Fallstrick 4: Scheduler ohne sichtbares Ergebnis
|
||||
|
||||
**Problem:** Agent wird per Cron geweckt, arbeitet im Hintergrund, aber niemand sieht ob es funktioniert.
|
||||
|
||||
**Lösung:**
|
||||
- Agent soll Ergebnisse in den SharedWorkspace schreiben (prüfbar per FileRW)
|
||||
- Wichtige Ergebnisse per Mail oder Telegram melden lassen
|
||||
- TokenUsage.json regelmäßig prüfen (Kosten vs. erwarteter Output)
|
||||
|
||||
#### Fallstrick 5: Tool-Config Fehler erst zur Laufzeit sichtbar
|
||||
|
||||
**Problem:** Falscher `botToken`, fehlender `connectionString` → Agent startet, Tool schlägt beim ersten Aufruf fehl.
|
||||
|
||||
**Lösung:**
|
||||
- Vor dem Scheduler-Start einen manuellen Test-Run durchführen
|
||||
- Agent manuell starten mit einer Test-Aufgabe: "Sende eine Test-Nachricht über Telegram"
|
||||
- Logs unter `Agent-{Name}/Logs/` prüfen
|
||||
|
||||
#### Fallstrick 6: Kosten unterschätzt bei Multi-Agent-Setup
|
||||
|
||||
**Problem:** 3 Agenten × Cron alle 10 Min × Claude Sonnet = ~$130/Tag.
|
||||
|
||||
**Lösung:**
|
||||
- Billige Modelle für Routinearbeit (Gemini Flash, Haiku)
|
||||
- Teure Modelle nur für Koordinator oder komplexe Analyse
|
||||
- LoopGuard `maxTokens` aggressiv begrenzen für Routine-Agenten
|
||||
- Tool Jobs statt Agent-Wakeup wo möglich
|
||||
|
||||
#### Fallstrick 7: YouTube-Transkription braucht externes Tool
|
||||
|
||||
**Problem:** `SocialMediaManager` mit `sm_yt_monitor` Job braucht `yt-dlp` als externes CLI-Tool auf dem System installiert.
|
||||
|
||||
**Lösung:**
|
||||
- Vor Instanz-Setup prüfen ob `yt-dlp` installiert und im PATH ist
|
||||
- Ohne `yt-dlp` funktionieren `x_search` und `reddit_search` trotzdem
|
||||
|
||||
---
|
||||
|
||||
## 7. Beispiel-Instanz: "TelegramBot"
|
||||
|
||||
Einfache Instanz mit einem Agenten, der über Telegram kommuniziert.
|
||||
|
||||
### Planung
|
||||
|
||||
- **Aufgabe:** Auf Telegram-Nachrichten antworten, einfache Fragen beantworten, Dateien im Workspace verwalten
|
||||
- **Agenten:** 1 (kein Koordinationsbedarf)
|
||||
- **Budget:** ~$0.50/Tag
|
||||
- **Modell:** `google/gemini-2.5-flash` (günstig, ausreichend für Chat)
|
||||
|
||||
### Verzeichnisstruktur
|
||||
|
||||
```
|
||||
Instance-TelegramBot/
|
||||
├── InstanceSettings.json
|
||||
├── TokenUsage.json
|
||||
└── Agents/
|
||||
├── AgentList.json
|
||||
├── SharedWorkspace/
|
||||
└── Agent-Assistent/
|
||||
├── AgentSettings.json
|
||||
├── Identity.md
|
||||
├── Soul.md
|
||||
├── Logs/
|
||||
└── Workspace/
|
||||
```
|
||||
|
||||
### AgentList.json
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": [
|
||||
{
|
||||
"name": "Assistent",
|
||||
"description": "Beantwortet Telegram-Nachrichten und verwaltet Notizen",
|
||||
"folderName": "Agent-Assistent"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### AgentSettings.json
|
||||
|
||||
```json
|
||||
{
|
||||
"agentId": "assistent",
|
||||
"displayName": "Assistent",
|
||||
"model": "google/gemini-2.5-flash",
|
||||
"systemPrompt": "",
|
||||
"tools": {
|
||||
"Telegram": {
|
||||
"botToken": "123456:ABC-DEF...",
|
||||
"defaultChatId": "987654321",
|
||||
"allowedChatIds": ["987654321"]
|
||||
},
|
||||
"FileRW": {
|
||||
"sharedAccessLevel": "Denied",
|
||||
"personalAllowedExtensions": [".txt", ".json", ".md"]
|
||||
}
|
||||
},
|
||||
"scheduler": null,
|
||||
"toolJobs": [
|
||||
{
|
||||
"jobId": "tgpoll01",
|
||||
"toolName": "Telegram",
|
||||
"jobTypeId": "telegram_poll",
|
||||
"cron": "*/1 * * * *",
|
||||
"enabled": true,
|
||||
"runOnStart": true
|
||||
}
|
||||
],
|
||||
"loopGuard": {
|
||||
"maxSteps": 10,
|
||||
"maxTokens": 30000,
|
||||
"timeoutSeconds": 120,
|
||||
"maxContextTokens": 100000,
|
||||
"compactionThreshold": 0.8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Kostenabschätzung
|
||||
|
||||
- Telegram-Polling: 1440 Ticks/Tag, ~0 Token (Tool Job, kein LLM)
|
||||
- Agent-Wakeups: geschätzt 20 Nachrichten/Tag × ~$0.00135/Run = **~$0.03/Tag**
|
||||
- Deutlich unter dem $0.50 Budget
|
||||
|
||||
---
|
||||
|
||||
## 8. Beispiel-Instanz: "Recherche-Team"
|
||||
|
||||
Multi-Agent-Setup für automatisierte Web-Recherche mit Berichterstattung.
|
||||
|
||||
### Planung
|
||||
|
||||
- **Aufgabe:** Täglich Webseiten/RSS-Feeds prüfen, relevante Infos sammeln, Bericht per E-Mail senden
|
||||
- **Agenten:** 3 (Recherche, Analyse, Bericht)
|
||||
- **Budget:** ~$5/Tag
|
||||
|
||||
### Agenten-Design
|
||||
|
||||
| Agent | Modell | Tools | Scheduler | Zweck |
|
||||
|-------|--------|-------|-----------|-------|
|
||||
| Crawler | `google/gemini-2.5-flash` | FileRW, WebFetch, WebMonitor | Cron `0 7 * * *` | Daten sammeln, in SharedWorkspace ablegen |
|
||||
| Analyst | `anthropic/claude-sonnet-4-5` | FileRW | Cron `0 8 * * *` | Daten analysieren, Zusammenfassung erstellen |
|
||||
| Reporter | `google/gemini-2.5-flash` | FileRW, Mail | Cron `0 9 * * *` | Zusammenfassung als E-Mail versenden |
|
||||
|
||||
### Warum dieses Setup?
|
||||
|
||||
- **Crawler** braucht kein teures Modell — holt nur Daten ab und speichert sie
|
||||
- **Analyst** bekommt Sonnet weil Analyse-Qualität hier den Unterschied macht
|
||||
- **Reporter** braucht kein teures Modell — formatiert nur und sendet
|
||||
- **Zeitversatz** (7:00 → 8:00 → 9:00) statt AgentComm — günstiger und zuverlässiger
|
||||
- **SharedWorkspace** statt Echtzeit-Kommunikation — keine doppelten Token-Kosten
|
||||
|
||||
### Kostenabschätzung
|
||||
|
||||
- Crawler: 1 Run/Tag × ~$0.002 = $0.002
|
||||
- Analyst: 1 Run/Tag × ~$0.03 = $0.03
|
||||
- Reporter: 1 Run/Tag × ~$0.002 = $0.002
|
||||
- **~$0.034/Tag** — weit unter Budget
|
||||
|
||||
---
|
||||
|
||||
## 9. Checkliste: Neue Instanz aufsetzen
|
||||
|
||||
- [ ] Aufgabe und Agenten-Aufteilung definiert
|
||||
- [ ] Kostenabschätzung durchgeführt (Modell × Frequenz × Agenten)
|
||||
- [ ] Prüfung: Alle nötigen Tools vorhanden?
|
||||
- [ ] Prüfung: Alle externen APIs/Zugangsdaten verfügbar?
|
||||
- [ ] Prüfung: Externe Abhängigkeiten installiert? (z.B. yt-dlp)
|
||||
- [ ] Instanz-Ordner erstellt (manuell oder über UI)
|
||||
- [ ] `InstanceSettings.json` mit API-Key konfiguriert
|
||||
- [ ] `AgentList.json` mit allen Agenten erstellt
|
||||
- [ ] Pro Agent: `AgentSettings.json` mit Modell, Tools, LoopGuard
|
||||
- [ ] Pro Agent: `Identity.md` (Rolle, Expertise, Kontext)
|
||||
- [ ] Pro Agent: `Soul.md` (Persönlichkeit, Methodik, Ausgabeformat)
|
||||
- [ ] Pro Agent: Tool-Configs mit echten Zugangsdaten befüllt
|
||||
- [ ] Tool Jobs konfiguriert (statt Agent-Wakeup wo sinnvoll)
|
||||
- [ ] Manueller Test-Run pro Agent durchgeführt
|
||||
- [ ] Logs geprüft — keine Tool-Fehler?
|
||||
- [ ] Scheduler aktiviert
|
||||
- [ ] Nach 24h: TokenUsage.json prüfen, Kosten validieren
|
||||
@@ -0,0 +1,465 @@
|
||||
# ClawdDotNet – Tool-Entwicklungsanleitung
|
||||
|
||||
## Übersicht
|
||||
|
||||
Tools sind eigenständige Plugins, die Agenten Zugriff auf externe Systeme geben (Datenbanken, Dateisysteme, E-Mail, APIs etc.). Jedes Tool ist ein separates .NET-Projekt, das ausschließlich `ClawdDotNet.Core` referenziert.
|
||||
|
||||
**Kernregeln:**
|
||||
- Tools sind **niemals voneinander abhängig** (Tool A darf Tool B nicht kennen)
|
||||
- Der Core kompiliert und läuft **ohne jedes Tool**
|
||||
- Jedes Tool ist **pro Agent konfiguriert** (via `AgentConfig.Tools`)
|
||||
- Tools werden zur Laufzeit über die `ToolRegistry` registriert
|
||||
|
||||
---
|
||||
|
||||
## Projekt-Struktur
|
||||
|
||||
```
|
||||
ClawdDotNet.sln
|
||||
├── src/
|
||||
│ ├── ClawdDotNet.Core/ ← Klassenbibliothek (keine Tool-Verweise!)
|
||||
│ ├── ClawdDotNet.Tools.MeinTool/ ← Dein Tool-Plugin
|
||||
│ │ ├── ClawdDotNet.Tools.MeinTool.csproj
|
||||
│ │ └── MeinToolTool.cs
|
||||
│ └── ClawdDotNet.Host/ ← WinForms-App (verweist auf Core + alle Tools)
|
||||
```
|
||||
|
||||
### Neues Tool-Projekt anlegen
|
||||
|
||||
```xml
|
||||
<!-- ClawdDotNet.Tools.MeinTool.csproj -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>ClawdDotNet.Tools.MeinTool</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- NUR Core referenzieren, KEINE anderen Tools -->
|
||||
<ProjectReference Include="..\ClawdDotNet.Core\ClawdDotNet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Tool-spezifische NuGet-Packages hier -->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IAgentTool implementieren
|
||||
|
||||
Jedes Tool implementiert das Interface `ClawdDotNet.Core.Tools.IAgentTool`:
|
||||
|
||||
```csharp
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
|
||||
namespace ClawdDotNet.Tools.MeinTool;
|
||||
|
||||
public sealed class MeinToolTool : IAgentTool
|
||||
{
|
||||
// 1. Eindeutiger Name – wird vom LLM in tool_calls verwendet
|
||||
public string Name => "MeinTool";
|
||||
|
||||
// 2. Beschreibung für das LLM (geht in den System-Prompt)
|
||||
public string Description => "Beschreibung, was dieses Tool kann...";
|
||||
|
||||
// 3. JSON Schema des Input-Objekts (OpenAI Function Calling Format)
|
||||
public JsonElement InputSchema { get; } = JsonDocument.Parse("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["read", "write"],
|
||||
"description": "Die auszuführende Aktion"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "Eingabedaten"
|
||||
}
|
||||
},
|
||||
"required": ["action"]
|
||||
}
|
||||
""").RootElement.Clone();
|
||||
|
||||
// 4. Ausführung
|
||||
public async Task<ToolResult> ExecuteAsync(
|
||||
JsonElement input,
|
||||
AgentToolContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Action aus dem Input lesen
|
||||
var action = input.GetProperty("action").GetString()
|
||||
?? throw new ArgumentException("'action' is required");
|
||||
|
||||
// Konfiguration aus dem AgentToolContext lesen
|
||||
// NIEMALS hardcodierte Werte, immer aus context.ToolConfig!
|
||||
var meineSetting = context.ToolConfig.TryGetValue("meineSetting", out var val)
|
||||
? val?.ToString() ?? ""
|
||||
: "";
|
||||
|
||||
// Logger verwenden
|
||||
context.Logger.LogInformation("MeinTool executing action: {Action}", action);
|
||||
|
||||
return action switch
|
||||
{
|
||||
"read" => await HandleReadAsync(input, context, ct),
|
||||
"write" => await HandleWriteAsync(input, context, ct),
|
||||
_ => ToolResult.Fail($"Unknown action: {action}")
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<ToolResult> HandleReadAsync(
|
||||
JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
// Implementierung...
|
||||
await Task.CompletedTask; // Platzhalter
|
||||
return ToolResult.Ok("Ergebnis als JSON oder Text");
|
||||
}
|
||||
|
||||
private async Task<ToolResult> HandleWriteAsync(
|
||||
JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
// Implementierung...
|
||||
await Task.CompletedTask; // Platzhalter
|
||||
return ToolResult.Ok("Erfolgreich geschrieben");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration pro Agent
|
||||
|
||||
Die Tool-Konfiguration ist **pro Agent** definiert, nicht global. Das bedeutet: Agent A kann auf Datenbank X zugreifen, Agent B auf Datenbank Y.
|
||||
|
||||
### In der Config-Datei (z.B. `config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": [
|
||||
{
|
||||
"agentId": "analyst",
|
||||
"tools": {
|
||||
"MeinTool": {
|
||||
"meineSetting": "wert-fuer-agent-analyst",
|
||||
"andereOption": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"agentId": "developer",
|
||||
"tools": {
|
||||
"MeinTool": {
|
||||
"meineSetting": "wert-fuer-agent-developer",
|
||||
"andereOption": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Konfiguration im Tool auslesen:
|
||||
|
||||
```csharp
|
||||
// Im ExecuteAsync:
|
||||
var setting = context.ToolConfig["meineSetting"]?.ToString();
|
||||
var option = context.ToolConfig.TryGetValue("andereOption", out var v)
|
||||
&& v is JsonElement je
|
||||
&& je.GetBoolean();
|
||||
```
|
||||
|
||||
**Wichtig:** `context.ToolConfig` enthält nur die Config für dieses Tool und diesen Agent. Du bekommst nie die Config eines anderen Agents oder eines anderen Tools.
|
||||
|
||||
---
|
||||
|
||||
## Tool im Host registrieren
|
||||
|
||||
Im `Host/Program.cs` (oder einer Startup-Klasse) wird das Tool registriert:
|
||||
|
||||
```csharp
|
||||
var registry = new ToolRegistry();
|
||||
|
||||
// Jedes Tool einzeln registrieren
|
||||
registry.Register(new MeinToolTool());
|
||||
registry.Register(new DatabaseTool());
|
||||
registry.Register(new FileRWTool());
|
||||
// ...
|
||||
```
|
||||
|
||||
Der `AgentEngine` nutzt dann die `ToolRegistry`, um für jeden Agent nur die erlaubten Tools bereitzustellen.
|
||||
|
||||
---
|
||||
|
||||
## Geplante Tools (Referenz)
|
||||
|
||||
### Database (`ClawdDotNet.Tools.Database`)
|
||||
|
||||
| Eigenschaft | Wert |
|
||||
|---|---|
|
||||
| Name | `Database` |
|
||||
| NuGet | `MySqlConnector`, `MongoDB.Driver` |
|
||||
| Aktionen | `query`, `insert`, `upsert` |
|
||||
|
||||
**Config-Felder:**
|
||||
- `connectionString` – Datenbankverbindung (pro Agent!)
|
||||
- `type` – `"mysql"` oder `"mongodb"`
|
||||
- `allowedTables` – Liste erlaubter Tabellen (Whitelist)
|
||||
|
||||
**Sicherheit:**
|
||||
- Parameterized Queries gegen SQL-Injection
|
||||
- Nur Tabellen aus `allowedTables` erlaubt
|
||||
- Connection String kommt IMMER aus `context.ToolConfig`
|
||||
|
||||
---
|
||||
|
||||
### FileRW (`ClawdDotNet.Tools.FileRW`)
|
||||
|
||||
| Eigenschaft | Wert |
|
||||
|---|---|
|
||||
| Name | `FileRW` |
|
||||
| NuGet | (keine) |
|
||||
| Aktionen | `read`, `write`, `append`, `list`, `delete` |
|
||||
|
||||
**Config-Felder:**
|
||||
- `rootPath` – Basisverzeichnis (Agent ist darin eingesperrt)
|
||||
- `allowWrite` – `true`/`false`
|
||||
- `allowedExtensions` – z.B. `[".txt", ".json", ".html"]`
|
||||
|
||||
**Sicherheit (Pflicht):**
|
||||
- Path-Traversal-Check: `Path.GetFullPath(requested).StartsWith(Path.GetFullPath(rootPath))`
|
||||
- Nur erlaubte Dateiendungen
|
||||
- Schreibzugriff nur wenn `allowWrite = true`
|
||||
|
||||
---
|
||||
|
||||
### Mail (`ClawdDotNet.Tools.Mail`)
|
||||
|
||||
| Eigenschaft | Wert |
|
||||
|---|---|
|
||||
| Name | `Mail` |
|
||||
| NuGet | `MailKit` |
|
||||
| Aktionen | `send`, `read_inbox`, `read_message`, `mark_read` |
|
||||
|
||||
**Config-Felder:**
|
||||
- `imapHost`, `imapPort` – IMAP-Server
|
||||
- `smtpHost`, `smtpPort` – SMTP-Server
|
||||
- `username`, `password` – Zugangsdaten
|
||||
- `allowedRecipients` – Whitelist erlaubter Empfänger
|
||||
|
||||
**Sicherheit:**
|
||||
- Empfänger müssen in `allowedRecipients` stehen
|
||||
|
||||
---
|
||||
|
||||
## AgentToolContext – Referenz
|
||||
|
||||
```csharp
|
||||
public sealed record AgentToolContext(
|
||||
string AgentId, // ID des ausführenden Agents
|
||||
string InstanceId, // ID der laufenden ClawdDotNet-Instanz
|
||||
IReadOnlyDictionary<string, object?> ToolConfig, // Tool-Config für DIESEN Agent
|
||||
ILogger Logger, // Logger (schreibt in Tool_{ToolName}.log)
|
||||
CancellationToken CancellationToken
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ToolResult – Referenz
|
||||
|
||||
```csharp
|
||||
public sealed record ToolResult(
|
||||
bool Success,
|
||||
string Content, // Geht zurück ans LLM
|
||||
string? ErrorMessage = null
|
||||
);
|
||||
|
||||
// Hilfsmethoden:
|
||||
ToolResult.Ok("Ergebnis als JSON oder Text");
|
||||
ToolResult.Fail("Fehlerbeschreibung");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Background Jobs (optional)
|
||||
|
||||
Manche Tools müssen regelmäßig im Hintergrund arbeiten, ohne dabei jedes Mal den Agenten (und damit das LLM) aufzuwecken. Beispiel: Ein Telegram-Tool prüft alle 30 Sekunden auf neue Nachrichten, weckt den Agenten aber **nur** wenn tatsächlich eine neue Nachricht vorliegt.
|
||||
|
||||
Dafür gibt es das optionale Interface `IToolJobProvider`. Ein Tool kann es **zusätzlich** zu `IAgentTool` implementieren — es ist kein Ersatz.
|
||||
|
||||
### IToolJobProvider implementieren
|
||||
|
||||
```csharp
|
||||
using ClawdDotNet.Core.Tools;
|
||||
using ClawdDotNet.Core.State;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public sealed class MeinToolTool : IAgentTool, IToolJobProvider
|
||||
{
|
||||
// ... IAgentTool-Implementierung wie oben ...
|
||||
|
||||
// 1. Verfügbare Job-Typen deklarieren
|
||||
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
|
||||
[
|
||||
new("meintool_check", "MeinTool Check", "Prüft regelmäßig auf Änderungen")
|
||||
];
|
||||
|
||||
// 2. Job-Tick ausführen (kein LLM, nur Tool-Code!)
|
||||
public async Task<ToolJobResult> ExecuteJobAsync(
|
||||
string jobTypeId,
|
||||
IReadOnlyDictionary<string, object?> toolConfig,
|
||||
IStateStore stateStore,
|
||||
ILogger logger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (jobTypeId != "meintool_check")
|
||||
return ToolJobResult.NoAction($"Unbekannter Job: {jobTypeId}");
|
||||
|
||||
// Config auslesen (gleiche Felder wie in ExecuteAsync)
|
||||
var apiKey = toolConfig.TryGetValue("apiKey", out var v)
|
||||
? v?.ToString() ?? "" : "";
|
||||
|
||||
// Zustand über IStateStore persistieren (überlebt Neustarts)
|
||||
var lastOffset = await stateStore.GetAsync("meintool_last_offset") ?? "0";
|
||||
|
||||
// Prüfung durchführen...
|
||||
var newItems = await CheckForUpdatesAsync(apiKey, lastOffset, ct);
|
||||
|
||||
if (newItems.Count == 0)
|
||||
return ToolJobResult.NoAction("Keine neuen Einträge");
|
||||
|
||||
// Offset speichern
|
||||
await stateStore.SetAsync("meintool_last_offset", newItems.Last().Id);
|
||||
|
||||
// Agent aufwecken mit Zusammenfassung
|
||||
return ToolJobResult.Wake(
|
||||
$"{newItems.Count} neue Einträge gefunden:\n" +
|
||||
string.Join("\n", newItems.Select(i => $"- {i.Title}")),
|
||||
logSummary: $"{newItems.Count} neue Einträge"
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ToolJobResult
|
||||
|
||||
Das Tool gibt deklarativ zurück, ob der Agent geweckt werden soll:
|
||||
|
||||
| Methode | Effekt |
|
||||
|---------|--------|
|
||||
| `ToolJobResult.NoAction(logSummary?)` | Nichts tun, optional Log-Eintrag |
|
||||
| `ToolJobResult.Wake(wakeMessage, logSummary?)` | Agent **im bestehenden Chat** aufwecken (Default) |
|
||||
| `ToolJobResult.WakeStateless(wakeMessage, logSummary?)` | Agent in **isolierter Session** aufwecken (kein Chatverlauf) |
|
||||
|
||||
**`Wake` vs `WakeStateless`:**
|
||||
- `Wake` (Default) nutzt `ChatAsync` — die Nachricht erscheint im Chat-Tab, der Agent behält den gesamten Konversationsverlauf. Ideal für alles was Teil einer laufenden Interaktion ist (Telegram-Nachrichten, fertige Transkripte, E-Mail-Antworten).
|
||||
- `WakeStateless` nutzt `RunAsync` — komplett isolierte Ausführung ohne Kontext. Nur für einmalige, kontextfreie Aufgaben verwenden.
|
||||
|
||||
**Wichtig:** Das Tool hat keinen direkten Zugriff auf den `AgentEngine`. Der `ToolJobScheduler` wertet das Ergebnis aus und weckt den Agenten bei Bedarf.
|
||||
|
||||
### IStateStore für Zustandstracking
|
||||
|
||||
`IStateStore` ist ein einfacher Key-Value-Store (SQLite-basiert), der zwischen Job-Ticks und Neustarts persistiert:
|
||||
|
||||
```csharp
|
||||
// Wert lesen (null wenn nicht vorhanden)
|
||||
string? value = await stateStore.GetAsync("mein_key");
|
||||
|
||||
// Wert schreiben
|
||||
await stateStore.SetAsync("mein_key", "neuer_wert");
|
||||
```
|
||||
|
||||
Verwende aussagekräftige Keys, z.B. `meintool_poll_offset` oder `meintool_last_check`.
|
||||
|
||||
### JSON-Konfiguration
|
||||
|
||||
Tool Jobs werden pro Agent in der `config.json` konfiguriert, parallel zum bestehenden `scheduler`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agentId": "support-bot",
|
||||
"tools": {
|
||||
"MeinTool": { "apiKey": "..." }
|
||||
},
|
||||
"scheduler": [
|
||||
{ "cron": "0 8 * * 1-5", "taskMessage": "Morgenbericht erstellen" }
|
||||
],
|
||||
"toolJobs": [
|
||||
{
|
||||
"jobId": "abc12345",
|
||||
"toolName": "MeinTool",
|
||||
"jobTypeId": "meintool_check",
|
||||
"cron": "*/5 * * * *",
|
||||
"enabled": true,
|
||||
"runOnStart": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|-------------|
|
||||
| `jobId` | Eindeutige ID (wird automatisch generiert) |
|
||||
| `toolName` | Name des Tools (muss `IToolJobProvider` implementieren) |
|
||||
| `jobTypeId` | Einer der von `GetJobDefinitions()` deklarierten IDs |
|
||||
| `cron` | Cron-Ausdruck für die Ausführungsintervalle |
|
||||
| `enabled` | Job aktiv/inaktiv |
|
||||
| `runOnStart` | Beim Programmstart sofort einmal ausführen |
|
||||
|
||||
### Unterschied: Agent Wakeup vs Tool Job
|
||||
|
||||
| | Agent Wakeup (`scheduler`) | Tool Job (`toolJobs`) |
|
||||
|---|---|---|
|
||||
| **Ausführung** | Voller LLM-Run | Nur Tool-Code (kein LLM) |
|
||||
| **Kosten** | Token pro Tick | Kostenlos pro Tick |
|
||||
| **Agent-Aufruf** | Immer | Nur bei `ShouldWakeAgent` |
|
||||
| **Anwendungsfall** | Regelmäßige Aufgaben | Polling, Monitoring, Checks |
|
||||
|
||||
---
|
||||
|
||||
## Checkliste für neue Tools
|
||||
|
||||
- [ ] Eigenes Projekt `ClawdDotNet.Tools.{Name}` anlegen
|
||||
- [ ] Nur `ClawdDotNet.Core` referenzieren
|
||||
- [ ] `IAgentTool` implementieren
|
||||
- [ ] `InputSchema` als gültiges JSON Schema definieren
|
||||
- [ ] Alle Konfiguration aus `context.ToolConfig` lesen
|
||||
- [ ] Alle `await`-Aufrufe mit `CancellationToken` versehen
|
||||
- [ ] `context.Logger` für Logging verwenden (kein `Console.WriteLine`)
|
||||
- [ ] Sicherheitsprüfungen implementieren (je nach Tool-Typ)
|
||||
- [ ] Im Host registrieren: `registry.Register(new MeinToolTool());`
|
||||
- [ ] In der Solution-Datei (.slnx) einbinden
|
||||
- [ ] NuGet-Packages in `NuGet.Config` PackageSourceMapping ergänzen
|
||||
- [ ] *Optional:* `IToolJobProvider` implementieren (wenn Hintergrund-Polling nötig)
|
||||
- [ ] *Optional:* `GetJobDefinitions()` mit eindeutigen `JobTypeId`s definieren
|
||||
- [ ] *Optional:* `ExecuteJobAsync()` implementieren, `IStateStore` für Zustandstracking nutzen
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
Das Logging-System trennt automatisch nach Modul. Wenn dein Tool den Logger aus dem `AgentToolContext` verwendet, landen die Logs automatisch in:
|
||||
|
||||
```
|
||||
Logs/
|
||||
├── 2026-05-12/
|
||||
│ ├── Core.log ← Engine, Scheduler, Config
|
||||
│ ├── Host.log ← WinForms UI
|
||||
│ ├── Tool_Database.log ← Database-Tool
|
||||
│ ├── Tool_FileRW.log ← FileRW-Tool
|
||||
│ ├── Tool_MeinTool.log ← Dein Tool!
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
Die Zuordnung geschieht über den Namespace:
|
||||
- `ClawdDotNet.Core.*` → `Core.log`
|
||||
- `ClawdDotNet.Tools.{Name}.*` → `Tool_{Name}.log`
|
||||
- `ClawdDotNet.Host.*` → `Host.log`
|
||||
|
||||
Log-Level: `Debug`, `Info`, `Warn`, `Error`
|
||||
Reference in New Issue
Block a user