disclaw/ARCHITECTURE.md
Nick Tabeling 69e0b7d727 initial
2026-04-08 14:21:19 +02:00

16 KiB

DisClaw -- Architektur

Projektuebersicht

DisClaw ist ein Discord-Bot, der einen Discord-Server in einen Multi-Agenten-Arbeitsbereich verwandelt. Jeder Discord-Kanal wird einem lokalen Workspace-Ordner zugeordnet, in dem ein eigenstaendiger Claude-Code-Agent lebt. Ein zentraler Management-Agent (DisClaw selbst) orchestriert das Erstellen neuer Agenten und deren Kanaele. Die Kommunikation mit den Agenten erfolgt ausschliesslich ueber Discord-Nachrichten.

Engine: Claude Code CLI -- DisClaw verwendet die lokal installierte Claude Code CLI (claude) als KI-Engine. Es wird kein API-Key benoetigt. Die CLI wird pro Nachricht als Kindprozess gestartet und liest die CLAUDE.md im Workspace-Ordner automatisch als Agenten-Identitaet.


MVP-Umfang

Enthalten

  1. Discord-Bot verbindet sich mit einem Server (Token wird beim Setup angegeben)
  2. DisClaw erstellt beim ersten Start automatisch seinen eigenen Management-Kanal (#disclaw)
  3. /new-agent-Befehl im Management-Kanal -- legt neuen Discord-Kanal, Workspace-Ordner und Agenten-Identitaet an
  4. Jeder Agent-Kanal hat seinen eigenen Ordner, eigene CLAUDE.md, eigene .claude/-Konfiguration
  5. Nachrichten in Agent-Kanaelen werden direkt von Claude Code beantwortet (kein @mention noetig)
  6. Keine Berechtigungseinschraenkungen -- jeder Agent hat vollen Zugriff
  7. Setup ueber /setup Custom Command in Claude Code
  8. Agenten-Erstellung ueber /new-agent Custom Command in Claude Code (ohne Discord)

Explizit zurueckgestellt (siehe "Zukuenftige Phasen")

  • Berechtigungssystem / rollenbasierter Zugriff
  • Weitere Slash-Commands ueber /new-agent hinaus
  • Agent-zu-Agent-Koordination / Delegation
  • Docker-Isolation
  • Kostentracking / Budgetlimits
  • Web-UI
  • Dateisystem-Watcher

Systemarchitektur

Discord Server
    |
    v
+-------------------+
|   Discord Bot     |   discord.js v14 -- empfaengt Nachrichten und Slash-Commands
+-------------------+
    |
    v
+-------------------+
|  Message Router   |   Ordnet Nachrichten dem richtigen Agent zu (anhand channel_id)
+-------------------+
    |
    +---> Management-Kanal? ---> DisClaw Command Handler (/new-agent)
    |
    +---> Agent-Kanal? -------> Agent Runner
                                    |
                                    v
                              +-------------------+
                              |  Agent Runner     |  Startet Claude Code CLI als Kindprozess
                              +-------------------+
                                    |
                                    v
                              +-------------------+
                              |  Claude Code CLI  |  claude -p "prompt" --output-format json
                              +-------------------+  cwd = workspace-ordner (liest CLAUDE.md)
                                    |
                                    v
                              Antwort --> Discord-Kanal

Komponentenverantwortlichkeiten

Komponente Verantwortlichkeit
Discord Bot Verbindung zum Server, Empfang von Events, Senden von Antworten
Message Router Entscheidet anhand der channel_id, ob eine Nachricht an den DisClaw-Handler oder einen Agent Runner geht
Command Handler Verarbeitet /new-agent -- erstellt Kanal, Ordner, DB-Eintrag, agent.yaml, CLAUDE.md, .claude/
Agent Runner Startet claude CLI als Kindprozess mit dem Workspace als cwd, parst JSON-Ausgabe
SQLite DB Speichert Kanal-Workspace-Zuordnungen und Konversationshistorie

Ordnerstruktur

disclaw/
  src/
    index.ts              # Einstiegspunkt -- Bot starten, Events registrieren
    bot.ts                # Discord-Bot-Setup und Event-Handler
    router.ts             # Message Router -- Nachricht -> Handler-Zuordnung
    commands/
      new-agent.ts        # /new-agent Slash-Command-Handler
    agent/
      runner.ts           # Agent Runner -- Claude CLI als Kindprozess starten
      identity.ts         # CLAUDE.md und agent.yaml erstellen und laden
    db/
      database.ts         # SQLite-Verbindung und Queries
    config/
      loader.ts           # .env und disclaw.yaml laden
  workspaces/             # Dynamisch erzeugt -- ein Unterordner pro Agent
    agent-name/
      CLAUDE.md           # Agenten-Identitaet (wird von Claude Code automatisch gelesen)
      agent.yaml          # DisClaw-Routing-Metadaten (Name, Rolle, Channel-ID)
      .claude/
        commands/          # Agenten-spezifische Custom Commands (leer fuer den Anfang)
        settings.json      # Agenten-spezifische Claude-Code-Einstellungen
      ...                  # Arbeitsdateien des Agenten
  .claude/
    commands/
      setup.md            # /setup -- Gefuehrtes Erstsetup
      new-agent.md        # /new-agent -- Agent ueber CLI erstellen
  CLAUDE.md               # Projekt-CLAUDE.md (DisClaw-Beschreibung fuer Claude Code)
  disclaw.yaml            # Globale Konfiguration
  .env                    # DISCORD_BOT_TOKEN, DISCORD_GUILD_ID, CLAUDE_PATH
  package.json
  tsconfig.json

Datenmodell (SQLite)

Tabelle: workspaces

Bildet Discord-Kanaele auf lokale Workspace-Ordner ab.

CREATE TABLE workspaces (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    channel_id    TEXT NOT NULL UNIQUE,       -- Discord Channel-ID
    guild_id      TEXT NOT NULL,              -- Discord Server-ID
    agent_name    TEXT NOT NULL UNIQUE,       -- Eindeutiger Agentenname
    workspace_path TEXT NOT NULL,             -- Absoluter Pfad zum Workspace-Ordner
    created_at    TEXT NOT NULL DEFAULT (datetime('now'))
);

Tabelle: conversations

Speichert die Konversationshistorie pro Agent (fuer Kontext bei Neustart).

CREATE TABLE conversations (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    workspace_id  INTEGER NOT NULL REFERENCES workspaces(id),
    discord_msg_id TEXT NOT NULL UNIQUE,      -- Discord Message-ID (Deduplizierung)
    role          TEXT NOT NULL,              -- 'user' oder 'assistant'
    content       TEXT NOT NULL,
    author_name   TEXT,                       -- Discord-Username des Absenders
    created_at    TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_conversations_workspace ON conversations(workspace_id, created_at);

Entscheidung: Die Konversationstabelle dient primaer dazu, bei einem Bot-Neustart den Kontext wiederherstellen zu koennen. Sie ist kein Chat-Archiv -- alte Eintraege koennen bei Bedarf gekuerzt werden.


Konfiguration

.env

DISCORD_BOT_TOKEN=...
DISCORD_GUILD_ID=...          # Optionaler Fallback, wenn nur ein Server unterstuetzt wird
CLAUDE_PATH=...               # Optional: Pfad zur claude CLI (Standard: "claude" im PATH)

Kein ANTHROPIC_API_KEY noetig. DisClaw verwendet die Claude Code CLI, die mit einem Claude Pro/Max-Abonnement funktioniert.

disclaw.yaml (globale Konfiguration)

# Pfad, unter dem Workspace-Ordner erstellt werden
workspaces_root: "./workspaces"

# Name des Management-Kanals
management_channel: "disclaw"

# Claude Code CLI Befehl (Standard: "claude")
claude_command: "claude"

# Timeout in Sekunden fuer Claude-CLI-Prozesse (Standard: 120)
claude_timeout_seconds: 120

agent.yaml (pro Workspace -- nur DisClaw-Metadaten)

name: "frontend-dev"
display_name: "Frontend Dev"
role: "Frontend-Entwickler fuer das Webprojekt"
channel_id: "1234567890"

CLAUDE.md (pro Workspace -- Agenten-Identitaet)

Die CLAUDE.md-Datei ist der primaere Identitaetsmechanismus. Claude Code liest sie automatisch, wenn der Workspace als Arbeitsverzeichnis gesetzt wird. Sie enthaelt:

  • Name und Rolle des Agenten
  • Persoenlichkeitsmerkmale
  • Verhaltensrichtlinien
  • Projektspezifischen Kontext

Nachrichtenfluss

Schritt-fuer-Schritt-Ablauf, wenn ein Benutzer eine Nachricht in einem Agent-Kanal schreibt:

1. Discord Event: messageCreate
   - Bot prueft: Nachricht von einem Bot? -> Ignorieren
   - Bot prueft: Nachricht in einem bekannten Kanal? -> Weiter

2. Message Router
   - DB-Abfrage: SELECT * FROM workspaces WHERE channel_id = ?
   - Kein Treffer? -> Nachricht ignorieren
   - Treffer im Management-Kanal? -> Command Handler
   - Treffer in Agent-Kanal? -> Agent Runner

3. Agent Runner
   - Lade agent.yaml aus dem Workspace-Ordner (Validierung)
   - Baue Prompt zusammen:
     - Konversationshistorie aus der DB
     - Laufzeitkontext (Channel-Name, Datum)
     - Neue Benutzernachricht
   - Starte Claude Code CLI als Kindprozess:
       claude -p "<prompt>" --output-format json
     mit cwd = workspace_path
   - Claude Code liest automatisch die CLAUDE.md im Workspace
   - Parse JSON-Ausgabe und extrahiere Textantwort

4. Antwort zurueck
   - Speichere User-Nachricht in conversations (role: 'user')
   - Speichere Agent-Antwort in conversations (role: 'assistant')
   - Sende Antwort in den Discord-Kanal
   - Bei langen Antworten: automatisch aufteilen (Discord-Limit: 2000 Zeichen)

Sonderfaelle

  • Lange Antworten: Nachrichten ueber 2000 Zeichen werden an sinnvollen Stellen aufgeteilt (Zeilenumbrueche bevorzugt)
  • Typing-Indikator: Bot zeigt "tippt..." an, waehrend Claude arbeitet
  • Fehlerbehandlung: Bei CLI-Fehlern wird eine verstaendliche Nachricht im Kanal gepostet
  • Timeout: Claude-CLI-Prozesse werden nach 120 Sekunden (konfigurierbar) abgebrochen
  • CLI nicht gefunden: Verstaendliche Fehlermeldung mit Installationshinweis

Agenten-Identitaet

Jeder Agent hat eine eigene Identitaet, die aus der CLAUDE.md-Datei im Workspace-Ordner geladen wird. Claude Code liest diese Datei automatisch.

Identitaetsmechanismus

CLAUDE.md (im Workspace)  =  Primaere Identitaet
  - Name, Rolle, Persoenlichkeit
  - Verhaltensrichtlinien
  - Projektspezifischer Kontext

agent.yaml (im Workspace)  =  DisClaw-Routing-Metadaten
  - Name (fuer DB-Lookup)
  - Channel-ID (fuer Kanal-Zuordnung)
  - Display-Name (fuer Anzeige)

Konversationshistorie (aus DB)  =  Laufzeitkontext
  - Letzte 30 Nachrichten werden dem Prompt vorangestellt

Im Gegensatz zur vorherigen Architektur (Claude Agent SDK mit System-Prompt) wird die Identitaet nicht programmatisch zusammengebaut, sondern direkt von Claude Code aus der CLAUDE.md gelesen. Das bedeutet:

  • Aenderungen an der CLAUDE.md werden sofort wirksam (bei der naechsten Nachricht)
  • Benutzer koennen die CLAUDE.md direkt bearbeiten, um das Verhalten anzupassen
  • Die gleiche CLAUDE.md funktioniert auch, wenn man Claude Code interaktiv im Workspace-Ordner startet

Workspace-Erstellung

Beim /new-agent-Befehl werden Name und Rolle als Parameter uebergeben. Daraus werden generiert:

  1. agent.yaml -- Routing-Metadaten
  2. CLAUDE.md -- Reichhaltige Identitaetsdatei mit Rolle, Persoenlichkeit und Richtlinien
  3. .claude/commands/ -- Verzeichnis fuer agenten-spezifische Custom Commands
  4. .claude/settings.json -- Claude-Code-Einstellungen
/new-agent name:frontend-dev role:Frontend-Entwickler

Setup-Ablauf

Voraussetzungen

  • Node.js v20+
  • Claude Code CLI (npm install -g @anthropic-ai/claude-code)
  • Claude Pro oder Max Abonnement (fuer die CLI)
  • Discord-Bot-Token

Ersteinrichtung

Die einfachste Methode ist der /setup Custom Command in Claude Code:

cd disclaw/
claude
# Dann im Claude-Code-Chat:
/setup

Alternativ manuell:

1. Benutzer erstellt Discord-Bot ueber Discord Developer Portal
2. Benutzer konfiguriert .env (DISCORD_BOT_TOKEN, DISCORD_GUILD_ID)
3. Benutzer fuehrt `npm install && npm run build && npm run start` aus

4. Bot startet:
   a. Laedt .env und disclaw.yaml
   b. Initialisiert SQLite-Datenbank (erstellt Tabellen falls noetig)
   c. Verbindet sich mit Discord
   d. Prueft: Existiert der Management-Kanal (#disclaw)?
      - Nein -> Erstellt ihn, speichert channel_id in der DB
      - Ja   -> Laedt bestehende Zuordnung
   e. Registriert /new-agent Slash-Command
   f. Sendet Begruessungsnachricht im Management-Kanal

5. Bot wartet auf Events (messageCreate, interactionCreate)

/new-agent Ablauf

1. Benutzer schreibt /new-agent name:researcher role:Recherche-Agent
2. Command Handler:
   a. Validiert: Name eindeutig? Keine Sonderzeichen?
   b. Erstellt Discord-Kanal: #researcher
   c. Erstellt Ordner: workspaces/researcher/
   d. Generiert: workspaces/researcher/CLAUDE.md (Agenten-Identitaet)
   e. Generiert: workspaces/researcher/agent.yaml (Metadaten)
   f. Generiert: workspaces/researcher/.claude/ (Konfiguration)
   g. Speichert Zuordnung in DB (workspaces-Tabelle)
   h. Antwortet im Management-Kanal: "Agent 'researcher' erstellt in #researcher"
3. Ab sofort: Nachrichten in #researcher werden vom Researcher-Agent beantwortet

Architekturentscheidungen

ADR-001: Claude Code CLI statt Agent SDK

Status: Akzeptiert (ersetzt vorherige Entscheidung fuer Claude Agent SDK)

Kontext: Agenten koennten ueber das Claude Agent SDK (@anthropic-ai/claude-agent-sdk), durch die Claude Code CLI oder direkt ueber die Anthropic API angesprochen werden.

Entscheidung: Wir verwenden die Claude Code CLI (claude -p "prompt" --output-format json). Vorteile:

  • Kein API-Key noetig -- funktioniert mit Claude Pro/Max-Abonnement
  • Automatische CLAUDE.md-Erkennung als Identitaetsmechanismus
  • Zugang zu allen Claude-Code-Tools (Dateisystem, Bash, etc.)
  • Einfacherer Setup-Prozess fuer Endbenutzer

Konsequenzen: Pro Nachricht wird ein neuer Prozess gestartet (kein persistenter Agent-Zustand). Die Konversationshistorie wird ueber die DisClaw-Datenbank verwaltet und dem Prompt vorangestellt. Leichter Overhead durch Prozess-Spawn, aber akzeptabel fuer den Chat-Anwendungsfall.

ADR-002: SQLite statt reiner Dateispeicherung

Status: Akzeptiert

Kontext: Kanal-Zuordnungen und Konversationen koennten in JSON-Dateien oder in SQLite gespeichert werden.

Entscheidung: SQLite ueber better-sqlite3. Synchrone API passt zum Message-Handler-Modell, atomare Schreibvorgaenge verhindern Datenverlust.

Konsequenzen: Eine Abhaengigkeit mehr (better-sqlite3 ist eine native Erweiterung). Dafuer zuverlaessige Persistenz, einfache Abfragen und gute Performance bei kleinen bis mittleren Datenmengen.

ADR-003: Ein Bot-Prozess, CLI-Prozesse pro Nachricht

Status: Akzeptiert (aktualisiert)

Kontext: Man koennte pro Agent einen persistenten Prozess starten, alle Agenten in einem Bot-Prozess verwalten, oder pro Nachricht einen CLI-Prozess starten.

Entscheidung: Ein einzelner Bot-Prozess (Node.js) empfaengt Discord-Nachrichten und startet pro Nachricht einen claude CLI-Prozess. Der CLI-Prozess terminiert nach der Antwort.

Konsequenzen: Einfacheres Ressourcenmanagement -- kein Speicher fuer idle Agenten. Leichter Overhead durch Prozess-Spawn. Konversationskontext muss explizit ueber die DB verwaltet werden. Ein abgestuerzter CLI-Prozess betrifft nur eine einzelne Nachricht, nicht den gesamten Bot.

ADR-004: CLAUDE.md als Identitaetsmechanismus

Status: Akzeptiert

Kontext: Die Agenten-Identitaet (Name, Rolle, Persoenlichkeit, Anweisungen) muss Claude mitgeteilt werden. Optionen: System-Prompt via API, CLAUDE.md-Datei, oder beides.

Entscheidung: Die CLAUDE.md-Datei im Workspace-Ordner ist der primaere Identitaetsmechanismus. Claude Code liest sie automatisch. Kein programmatisch zusammengebauter System-Prompt mehr.

Konsequenzen: Benutzer koennen Agenten-Verhalten durch direkte Bearbeitung der CLAUDE.md anpassen. Die gleiche Konfiguration funktioniert interaktiv und ueber DisClaw. Weniger Code in der Identity-Schicht.


Zukuenftige Phasen

In der Reihenfolge der voraussichtlichen Prioritaet:

  1. Berechtigungssystem -- Rollenbasierter Zugriff, definieren welche Agenten welche Dateien / Befehle nutzen duerfen
  2. Weitere Slash-Commands -- /list-agents, /delete-agent, /agent-config
  3. Agent-zu-Agent-Kommunikation -- Agenten koennen einander Aufgaben delegieren
  4. Docker-Isolation -- Jeder Agent laeuft in einem eigenen Container
  5. Streaming-Antworten -- Teilantworten waehrend Claude arbeitet in Discord anzeigen
  6. Web-Dashboard -- Uebersicht ueber Agenten, Konversationen und Nutzung
  7. Dateisystem-Watcher -- Agenten reagieren auf Datei-Aenderungen in ihrem Workspace