Architektura dynamicznych workerów
Phase 26: Dynamiczny Rejestr Workerów. Zastępuje hardkodowany przepływ dwóch agentów z Phase 24.5. Ostatnia aktualizacja: 2026-04-06
Ogólny opis
Phase 26 zastępuje hardkodowaną binarną logikę Consultant/Developer dynamicznym systemem rejestru workerów.
Workerzy zadeklarowani są w config/workers_registry.json. Każdy worker definiuje: id, etykietę, ikonę, typ (chat/terminal), model, max_turns, narzędzia, system_prompt_skill, prompt_style (history/gsd), output_format, focus_dirs, log_category, builtin.
Wbudowani workerzy:
- Consultant (chat, sonnet) — analiza tylko do odczytu, burza mózgów, tworzenie specyfikacji. Routowany przez
/club/w:consultant. - Developer (terminal, opus) — pełna implementacja. Routowany przez
/dlub/w:developer. - UI/UX Designer (chat, sonnet) — analiza frontendu, sugestie projektowe. Routowany przez
/w:ui-designer.
CEO wybiera workera za pomocą komend Telegram. Specyfikacje stanowią pomost między workerami: Consultant je tworzy, CEO zatwierdza, Developer wykonuje.
CEO (Telegram)
│
├── /c <wiadomość> ──► podproces Consultant (Claude tylko do odczytu)
│ │
│ ├── Analizuje kod, przeszukuje sieć
│ └── Wyjściowe bloki SPEC
│ │
│ ▼
│ spec_queue.json (status: "draft")
│
├── /approve <id> ──► status specyfikacji → "approved"
│ │
│ ▼
├── /d <wiadomość> ──► Developer (główna pętla GSD)
│ │
│ ├── Odczytuje zatwierdzone specyfikacje z kolejki
│ ├── Implementuje zmiany
│ └── Oznacza specyfikację → "done"
│
├── /reject <id> ──► status specyfikacji → "rejected"
│
└── /specs ──► Lista wszystkich specyfikacji ze statusem
Model danych
Interfejs Spec
interface Spec {
id: string; // nanoid(8), np. "a1b2c3d4"
title: string; // Jednolinijkowy tytuł z bloku SPEC
problem: string; // Jaki problem rozwiązuje
approach: string; // Podejście techniczne (2–3 zdania)
acceptance: string[]; // Lista kryteriów akceptacji
files: string[]; // Ścieżki do zmienianych plików
complexity: "low" | "medium" | "high";
status: SpecStatus;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
consultantThread?: string; // ID wątku który stworzył tę specyfikację
}
type SpecStatus = "draft" | "approved" | "rejected" | "executing" | "done";
Maszyna stanów
POMYSŁ ──► KONSULTACJA ──► DRAFT SPEC ──► PRZEGLĄD ──► ZATWIERDZONA ──► WYKONYWANIE ──► GOTOWE
↑ │
+──── ODRZUCONA ──────────────+
Przejścia:
| Z | Do | Wyzwalacz |
|---|---|---|
| — | draft |
Consultant wyprowadza blok SPEC |
draft |
approved |
CEO wysyła /approve <id> |
draft |
rejected |
CEO wysyła /reject <id> |
rejected |
draft |
Consultant rewizuje po feedbacku |
approved |
executing |
Developer rozpoczyna implementację |
executing |
done |
Developer spełnia wszystkie kryteria akceptacji |
Endpointy API
Wszystkie endpointy są wewnętrzne (routing master bota), nie HTTP API.
| Endpoint | Metoda | Auth | Opis |
|---|---|---|---|
/c <msg> |
Telegram | Tylko CEO | Przekaż wiadomość do podprocesu Consultant |
/d <msg> |
Telegram | Tylko CEO | Przekaż wiadomość do Developera (pętla GSD) |
/approve <id> |
Telegram | Tylko CEO | Zatwierdź draft spec |
/reject <id> |
Telegram | Tylko CEO | Odrzuć draft spec z opcjonalnym feedbackiem |
/specs |
Telegram | Tylko CEO | Lista wszystkich specyfikacji z filtrem statusu |
/role |
Telegram | Tylko CEO | Pokaż aktualnie aktywną rolę |
| domyślny | Telegram | Tylko CEO | Routuj do aktywnej roli (consultant lub developer) |
Routing wiadomości
handleMessage() master bota routuje przychodzące wiadomości Telegram:
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)
}
Ujednolicony dispatch: getWorkerConfig(workerId) → callWorker(workerId, text, options). Konfiguracja workera określa model, narzędzia, styl promptu i kategorię logów. Domyślny routing używa active_role.json. Domyślnie: developer.
Komponenty frontendu
WorkerPanel (Phase 26)
Generyczny dispatcher paneli. Renderuje ChatPanelView lub TerminalPanelView zależnie od worker.type.
ChatPanelView
Wyświetla konwersacje workerów typu chat (Consultant, UI/UX Designer itp.).
| Aspekt | Szczegół |
|---|---|
| Źródło danych | /api/sse/logs/:name?category=${worker.log_category} |
| Wyświetlanie | Bąbelki wiadomości w stylu chatu (CEO po prawej, Worker po lewej) |
| Wykrywanie SPEC | Parsuje bloki ### SPEC:, renderuje jako karty z przyciskami zatwierdzenia/odrzucenia |
| Wejście | Pełny pasek wejściowy z selektorem modelu, menu narzędzi, wejściem głosowym, załącznikami |
| Wynik Dev | Przycisk do wyświetlenia ostatniej odpowiedzi terminala developera |
TerminalPanelView
Wyświetla wyniki workerów typu terminal (Developer itp.).
| Aspekt | Szczegół |
|---|---|
| Źródło danych | /api/sse/logs/:name?category=${worker.log_category} |
| Wyświetlanie | Wpisy logów w stałej czcionce z ikonami narzędzi, wskaźnikami myślenia, stanem przetwarzania |
| Wejście | Prosty pasek poleceń z przyciskiem Wyślij |
Hook useSSEStream
Ujednolicony hook SSE dla obu typów paneli. Obsługuje dedup (seenIds), wykrywanie stanu przetwarzania i transformację wpisów na podstawie worker.type.
Pasek workerów
Górny pasek pokazujący wszystkich zarejestrowanych workerów jako przełączalne pigułki. Kliknięcie dodaje/usuwa panele. Układ przechowywany w localStorage per projekt (citadel-workspace-active-${project.name}).
SpecPanel
Tablica w stylu Kanban pokazująca cykl życia specyfikacji.
| Kolumna | Pokazywane specyfikacje |
|---|---|
| Draft | status: "draft" — z przyciskami zatwierdzenia/odrzucenia |
| Zatwierdzone | status: "approved" — oczekujące na developera |
| W toku | status: "executing" — developer pracuje |
| Gotowe | status: "done" — ukończone |
| Odrzucone | status: "rejected" — domyślnie zwinięte |
RoleToggle
Wskaźnik wizualny w nagłówku poda projektu pokazujący aktywną rolę/workera.
Kliknięcie toggle wysyła info o komendzie /role; faktyczne przełączanie ról odbywa się wyłącznie przez komendy Telegram (bezpieczeństwo: brak mutacji z frontendu).
Model bezpieczeństwa
Ograniczenia narzędzi workera
Narzędzia workerów zadeklarowane są w workers_registry.json. Funkcja callWorker() odczytuje worker.tools i przekazuje --allowedTools do Claude CLI:
"tools": ["Read", "Glob", ...]— explicite whitelist"tools": "all"— pełny dostęp (tylko Developer)
Zapewnia to, że workerzy typu chat (Consultant, UI Designer) NIE MOGĄ:
- Zapisywać ani edytować plików
- Wykonywać poleceń shell
- Deployować, commitować ani pushować
Izolacja workerów
- Każdy worker działa we własnym procesie/wątku Claude
- Historia konwersacji workera śledzona w mapie
workerThreads(kluczowana po ID workera) - Wsteczna kompatybilność: tablica
consultantThreadsynchronizowana obokworkerThreads - Tylko CEO chatId może wydawać komendy
/approvei/reject
Układ przechowywania
Wszystkie pliki stanu przechowywane są w katalogu roboczym projektu pod state/:
state/
├── active_role.json # { "role": "developer"|"consultant", "since": ISO8601 }
├── consultant_thread.json # { "threadId": "...", "startedAt": ISO8601 }
├── spec_queue.json # tablica Spec[] — wszystkie specyfikacje ze statusem
└── logs/
├── consultant-YYYY-MM-DD.log # JSONL konwersacja consultant
└── specs-YYYY-MM-DD.log # JSONL przejścia stanów specyfikacji
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"
}
]
Rejestr workerów
Wszyscy workerzy zadeklarowani w 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
}
]
}
| Pole | Typ | Opis |
|---|---|---|
id |
string | Unikalny identyfikator workera (używany w routingu, przechowywaniu, SSE) |
label |
string | Nazwa wyświetlana w UI |
icon |
string | Ikona emoji dla pigułki/nagłówka |
type |
"chat" | "terminal" |
Typ panelu: bąbelki czatu vs logi w stałej czcionce |
model |
string | ID modelu Claude |
max_turns |
number | Maksymalna liczba rund agentycznych per wywołanie |
tools |
string[] | "all" |
Whitelist dozwolonych narzędzi lub "all" dla pełnego dostępu |
system_prompt_skill |
string | null | Ścieżka do system promptu w katalogu skills/ |
prompt_style |
"history" | "gsd" |
Styl konwersacji (dołącz historię vs prompt GSD) |
output_format |
"text" | "stream-json" |
Tryb obsługi wyjścia |
focus_dirs |
string[] | Katalogi do skupienia (wstrzykiwane do promptu) |
log_category |
string | Kategoria logów JSONL (mapuje na parametr SSE ?category=) |
builtin |
boolean | Czy ten worker jest dostarczany z Arc OS |
Konfiguracja ról
Konfiguracja ról per projekt w config/project_roles.json:
{
"version": 1,
"defaults": {
"activeRole": "developer",
"specAutoApprove": false,
"maxDraftSpecs": 20
}
}
Logowanie
Wszystkie interakcje consulta i przejścia specyfikacji logowane są w formacie JSONL.
Wpisy consultant-*.log
{"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"]}
Wpisy specs-*.log
{"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"}
Prowadzone przez Ricka (Orkiestratora). Phase 26 — Dynamiczni Workerzy.