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:
- Consultant (chat, sonnet) — nur-lese Analyse, Brainstorming, Spec-Erstellung. Erreichbar via
/coder/w:consultant. - Developer (terminal, opus) — vollständige Implementierung. Erreichbar via
/doder/w:developer. - UI/UX Designer (chat, sonnet) — Frontend-Analyse, Design-Vorschläge. Erreichbar via
/w:ui-designer.
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:
"tools": ["Read", "Glob", ...]— explizite Whitelist"tools": "all"— vollständiger Zugriff (nur Developer)
Dies stellt sicher, dass Chat-Typ-Worker (Consultant, UI Designer) NICHT:
- Dateien schreiben oder bearbeiten können
- Shell-Befehle ausführen können
- Deployen, committen oder pushen können
Worker-Isolation
- Jeder Worker läuft in seinem eigenen Claude-Prozess/Thread
- Worker-Konversations-Historie wird in der
workerThreads-Map verfolgt (Schlüssel: Worker-ID) - Rückwärtskompatibilität:
consultantThread-Array wird nebenworkerThreadssynchronisiert - Nur CEO-ChatId kann
/approve- und/reject-Befehle ausgeben
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.