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:

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:

Zapewnia to, że workerzy typu chat (Consultant, UI Designer) NIE MOGĄ:

Izolacja workerów

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.