Dynamische Worker-Architektur

Phase 26: Dynamische Worker-Registry. Ersetzt den fest verdrahteten Dual-Agent-Flow aus Phase 24.5. Zuletzt aktualisiert: 2026-04-06

Überblick

Phase 26 ersetzt die fest verdrahtete binäre Consultant/Developer-Logik durch ein dynamisches Worker-Registry-System.

Worker werden in config/workers_registry.json deklariert. Jeder Worker definiert: id, label, icon, type (chat/terminal), model, max_turns, tools, system_prompt_skill, prompt_style (history/gsd), output_format, focus_dirs, log_category, builtin.

Eingebaute Worker:

Der CEO wählt den Worker über Telegram-Befehle. Specs sind die Brücke zwischen Workern: Consultant erstellt sie, CEO genehmigt, Developer setzt um.

CEO (Telegram)
 │
 ├── /c <message>  ──► Consultant-Subprocess (nur-lese Claude)
 │                      │
 │                      ├── Analysiert Code, durchsucht das Web
 │                      └── Gibt SPEC-Blöcke aus
 │                           │
 │                           ▼
 │                      spec_queue.json (status: "draft")
 │
 ├── /approve <id>  ──► Spec-Status → "approved"
 │                           │
 │                           ▼
 ├── /d <message>   ──► Developer (Haupt-GSD-Schleife)
 │                      │
 │                      ├── Liest genehmigte Specs aus der Queue
 │                      ├── Implementiert Änderungen
 │                      └── Markiert Spec → "done"
 │
 ├── /reject <id>   ──► Spec-Status → "rejected"
 │
 └── /specs         ──► Alle Specs mit Status auflisten

Datenmodell

Spec-Interface

interface Spec {
  id: string;            // nanoid(8), z.B. "a1b2c3d4"
  title: string;         // Einzeiliger Titel aus dem SPEC-Block
  problem: string;       // Welches Problem dies löst
  approach: string;      // Technischer Ansatz (2–3 Sätze)
  acceptance: string[];  // Liste der Akzeptanzkriterien
  files: string[];       // Betroffene Dateipfade
  complexity: "low" | "medium" | "high";
  status: SpecStatus;
  createdAt: string;     // ISO 8601
  updatedAt: string;     // ISO 8601
  consultantThread?: string; // Thread-ID, die diese Spec erzeugt hat
}

type SpecStatus = "draft" | "approved" | "rejected" | "executing" | "done";

Zustandsmaschine

  IDEE ──► BERATUNG ──► SPEC-ENTWURF ──► REVIEW ──► GENEHMIGT ──► IN BEARBEITUNG ──► FERTIG
                            ↑                            │
                            +──── ABGELEHNT ─────────────+

Übergänge:

Von Nach Auslöser
draft Consultant gibt einen SPEC-Block aus
draft approved CEO sendet /approve <id>
draft rejected CEO sendet /reject <id>
rejected draft Consultant überarbeitet nach Feedback
approved executing Developer beginnt die Implementierung
executing done Developer erfüllt alle Akzeptanzkriterien

API-Endpunkte

Alle Endpunkte sind intern (Master-Bot-Routing), keine HTTP-API.

Endpunkt Methode Auth Beschreibung
/c <msg> Telegram Nur CEO Nachricht an Consultant-Subprocess routen
/d <msg> Telegram Nur CEO Nachricht an Developer (GSD-Schleife) routen
/approve <id> Telegram Nur CEO Einen Entwurfs-Spec genehmigen
/reject <id> Telegram Nur CEO Einen Entwurfs-Spec mit optionalem Feedback ablehnen
/specs Telegram Nur CEO Alle Specs mit Status-Filter auflisten
/role Telegram Nur CEO Aktuelle aktive Rolle anzeigen
Standard Telegram Nur CEO An aktive Rolle routen (Consultant oder Developer)

Nachrichten-Routing

Master Bot handleMessage() routet eingehende Telegram-Nachrichten:

function handleMessage(text: string, chatId: number) {
  if (text.startsWith("/c "))        → callWorker("consultant", text.slice(3))
  if (text.startsWith("/d "))        → callWorker("developer", text.slice(3))
  if (text.startsWith("/w:"))        → callWorker(parsedWorkerId, parsedText)
  if (text.startsWith("/approve "))  → approveSpec(text.slice(9).trim())
  if (text.startsWith("/reject "))   → rejectSpec(text.slice(8).trim())
  if (text === "/specs")             → listSpecs()
  if (text === "/role")              → showActiveRole()
  else                               → callWorker(activeWorkerId, text)
}

Einheitlicher Dispatch: getWorkerConfig(workerId)callWorker(workerId, text, options). Die Worker-Konfiguration bestimmt Modell, Tools, Prompt-Stil und Log-Kategorie. Standard-Routing verwendet active_role.json. Initialstandard: developer.

Frontend-Komponenten

WorkerPanel (Phase 26)

Generischer Panel-Dispatcher. Rendert ChatPanelView oder TerminalPanelView basierend auf worker.type.

ChatPanelView

Zeigt Konversationen von Chat-Typ-Workern an (Consultant, UI/UX Designer usw.).

Aspekt Detail
Datenquelle /api/sse/logs/:name?category=${worker.log_category}
Anzeige Chat-artige Nachrichtenblasen (CEO rechts, Worker links)
SPEC-Erkennung Parst ### SPEC:-Blöcke, rendert als Karten mit Genehmigen/Ablehnen-Buttons
Eingabe Vollständige Eingabeleiste mit Modell-Auswahl, Tools-Menü, Spracheingabe, Dateianhänge
Dev-Ergebnis Button zur Anzeige der letzten Developer-Terminal-Antwort

TerminalPanelView

Zeigt Terminal-Typ-Worker-Ausgabe an (Developer usw.).

Aspekt Detail
Datenquelle /api/sse/logs/:name?category=${worker.log_category}
Anzeige Monospace-Log-Einträge mit Tool-Icons, Thinking-Indikatoren, Verarbeitungszustand
Eingabe Einfache Befehlsleiste mit Senden-Button

useSSEStream Hook

Einheitlicher SSE-Hook für beide Panel-Typen. Verarbeitet Dedup (seenIds), Verarbeitungszustandserkennung und Eintrags-Transformation basierend auf worker.type.

Workers Bar

Obere Leiste, die alle registrierten Worker als Toggle-Pills anzeigt. Klicken fügt Panels hinzu/entfernt sie. Layout wird pro Projekt in localStorage gespeichert (citadel-workspace-active-${project.name}).

SpecPanel

Kanban-artiges Board für den Spec-Lebenszyklus.

Spalte Angezeigte Specs
Entwurf status: "draft" — mit Genehmigen/Ablehnen-Buttons
Genehmigt status: "approved" — wartet auf Developer
In Bearbeitung status: "executing" — Developer arbeitet
Fertig status: "done" — abgeschlossen
Abgelehnt status: "rejected" — standardmäßig eingeklappt

RoleToggle

Visueller Indikator im Projekt-Pod-Header, der die aktive Rolle/Worker anzeigt.

Ein Klick auf den Toggle sendet /role-Befehlsinformationen; das tatsächliche Rollenwechseln erfolgt nur über Telegram-Befehle (Sicherheit: keine Frontend-Mutationen).

Sicherheitsmodell

Worker-Tool-Einschränkungen

Worker-Tools werden in workers_registry.json deklariert. Die callWorker()-Funktion liest worker.tools und übergibt --allowedTools an das Claude CLI:

Dies stellt sicher, dass Chat-Typ-Worker (Consultant, UI Designer) NICHT:

Worker-Isolation

Speicher-Layout

Alle State-Dateien liegen im Arbeitsverzeichnis des Projekts unter state/:

state/
├── active_role.json          # { "role": "developer"|"consultant", "since": ISO8601 }
├── consultant_thread.json    # { "threadId": "...", "startedAt": ISO8601 }
├── spec_queue.json           # Spec[]-Array — alle Specs mit Status
└── logs/
    ├── consultant-YYYY-MM-DD.log   # JSONL Consultant-Konversation
    └── specs-YYYY-MM-DD.log        # JSONL Spec-Zustandsübergänge

active_role.json

{
  "role": "developer",
  "since": "2026-04-05T10:00:00Z"
}

consultant_thread.json

{
  "threadId": "thread_abc123",
  "startedAt": "2026-04-05T10:00:00Z",
  "messageCount": 12
}

spec_queue.json

[
  {
    "id": "a1b2c3d4",
    "title": "Add rate limiting to CRM endpoints",
    "problem": "CRM API has no rate limiting, vulnerable to abuse",
    "approach": "Add sliding window rate limiter middleware using in-memory Map with IP-based tracking",
    "acceptance": [
      "Rate limiter middleware applied to all /api/crm/* routes",
      "100 requests per minute per IP",
      "429 response with Retry-After header"
    ],
    "files": ["shared/crm-routes.ts", "shared/rate-limiter.ts"],
    "complexity": "medium",
    "status": "approved",
    "createdAt": "2026-04-05T10:05:00Z",
    "updatedAt": "2026-04-05T10:12:00Z"
  }
]

Worker-Registry

Alle Worker deklariert in config/workers_registry.json:

{
  "version": 1,
  "workers": [
    {
      "id": "consultant",
      "label": "Consultant",
      "icon": "\ud83d\udcac",
      "type": "chat",
      "model": "claude-sonnet-4-5",
      "max_turns": 5,
      "tools": ["Read", "Glob", "Grep", "WebSearch", "WebFetch"],
      "system_prompt_skill": "consultant_system.md",
      "prompt_style": "history",
      "output_format": "text",
      "focus_dirs": [],
      "log_category": "consultant",
      "builtin": true
    }
  ]
}
Feld Typ Beschreibung
id string Eindeutige Worker-ID (verwendet im Routing, Storage, SSE)
label string Anzeigename in der UI
icon string Emoji-Icon für Pill/Header
type "chat" | "terminal" Panel-Typ: Chat-Blasen vs. Monospace-Logs
model string Claude-Modell-ID
max_turns number Maximale agentische Turns pro Aufruf
tools string[] | "all" Erlaubte Tool-Whitelist oder "all" für vollständigen Zugriff
system_prompt_skill string | null Pfad zum System-Prompt in skills/-Verzeichnis
prompt_style "history" | "gsd" Konversationsstil (History anhängen vs. GSD-Prompt)
output_format "text" | "stream-json" Ausgabe-Verarbeitungsmodus
focus_dirs string[] Verzeichnisse zum Fokussieren (in Prompt eingespritzt)
log_category string JSONL-Log-Kategorie (entspricht SSE ?category=-Parameter)
builtin boolean Ob dieser Worker mit Arc OS mitgeliefert wird

Rollenkonfiguration

Projekteigene Rollenkonfiguration in config/project_roles.json:

{
  "version": 1,
  "defaults": {
    "activeRole": "developer",
    "specAutoApprove": false,
    "maxDraftSpecs": 20
  }
}

Logging

Alle Consultant-Interaktionen und Spec-Übergänge werden im JSONL-Format protokolliert.

consultant-*.log-Einträge

{"ts":"2026-04-05T10:05:00Z","role":"ceo","text":"How should we add caching?"}
{"ts":"2026-04-05T10:05:15Z","role":"consultant","text":"Based on analysis...","specs":["a1b2c3d4"]}

specs-*.log-Einträge

{"ts":"2026-04-05T10:05:15Z","event":"created","specId":"a1b2c3d4","title":"Add caching layer"}
{"ts":"2026-04-05T10:12:00Z","event":"approved","specId":"a1b2c3d4","by":"ceo"}
{"ts":"2026-04-05T10:30:00Z","event":"executing","specId":"a1b2c3d4"}
{"ts":"2026-04-05T11:00:00Z","event":"done","specId":"a1b2c3d4"}

Gepflegt von Rick (Orchestrator). Phase 26 — Dynamische Worker.