Архітектура динамічних воркерів

Phase 26: Dynamic Worker Registry. Замінює hardcoded dual-agent flow із Phase 24.5. Останнє оновлення: 2026-04-06

Огляд

Phase 26 замінює hardcoded бінарну логіку Consultant/Developer на систему динамічного реєстру воркерів.

Воркери задекларовані у config/workers_registry.json. Кожен воркер визначає: id, label, icon, type (chat/terminal), model, max_turns, tools, system_prompt_skill, prompt_style (history/gsd), output_format, focus_dirs, log_category, builtin.

Вбудовані воркери:

CEO обирає воркера через Telegram-команди. Спеки — це міст між воркерами: Consultant їх створює, CEO підтверджує, Developer виконує.

CEO (Telegram)
 │
 ├── /c <message>  ──► Consultant subprocess (read-only Claude)
 │                      │
 │                      ├── Analyzes code, searches web
 │                      └── Outputs SPEC blocks
 │                           │
 │                           ▼
 │                      spec_queue.json (status: "draft")
 │
 ├── /approve <id>  ──► spec status → "approved"
 │                           │
 │                           ▼
 ├── /d <message>   ──► Developer (main GSD loop)
 │                      │
 │                      ├── Reads approved specs from queue
 │                      ├── Implements changes
 │                      └── Marks spec → "done"
 │
 ├── /reject <id>   ──► spec status → "rejected"
 │
 └── /specs         ──► List all specs with status

Модель даних

Інтерфейс Spec

interface Spec {
  id: string;            // nanoid(8), e.g. "a1b2c3d4"
  title: string;         // One-line title from SPEC block
  problem: string;       // What issue this solves
  approach: string;      // Technical approach (2-3 sentences)
  acceptance: string[];  // Acceptance criteria list
  files: string[];       // Affected file paths
  complexity: "low" | "medium" | "high";
  status: SpecStatus;
  createdAt: string;     // ISO 8601
  updatedAt: string;     // ISO 8601
  consultantThread?: string; // Thread ID that produced this spec
}

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

Машина станів

  IDEA ──► CONSULTATION ──► SPEC DRAFT ──► REVIEW ──► APPROVED ──► EXECUTING ──► DONE
                                ↑                         │
                                +──── REJECTED ───────────+

Переходи:

З До Тригер
draft Consultant видає SPEC-блок
draft approved CEO надсилає /approve <id>
draft rejected CEO надсилає /reject <id>
rejected draft Consultant переробляє після фідбеку
approved executing Developer починає імплементацію
executing done Developer виконує всі критерії приймання

API-ендпоінти

Всі ендпоінти внутрішні (маршрутизація через master bot), не HTTP API.

Ендпоінт Метод Auth Опис
/c <msg> Telegram CEO only Маршрутизація повідомлення до Consultant subprocess
/d <msg> Telegram CEO only Маршрутизація повідомлення до Developer (GSD loop)
/approve <id> Telegram CEO only Підтвердити чернетку спеки
/reject <id> Telegram CEO only Відхилити чернетку спеки з опційним фідбеком
/specs Telegram CEO only Перелічити всі спеки з фільтром за статусом
/role Telegram CEO only Показати поточну активну роль
default Telegram CEO only Маршрутизація до активної ролі (consultant або developer)

Маршрутизація повідомлень

Master bot handleMessage() маршрутизує вхідні 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)
}

Уніфікована диспетчеризація: getWorkerConfig(workerId)callWorker(workerId, text, options). Конфіг воркера визначає модель, інструменти, стиль prompt і log-категорію. Маршрутизація за замовчуванням використовує active_role.json. Початкове значення: developer.

Frontend-компоненти

WorkerPanel (Phase 26)

Універсальний диспетчер панелей. Рендерить ChatPanelView або TerminalPanelView залежно від worker.type.

ChatPanelView

Відображає розмови воркерів chat-типу (Consultant, UI/UX Designer тощо).

Аспект Деталь
Джерело даних /api/sse/logs/:name?category=${worker.log_category}
Відображення Бульбашки повідомлень у стилі чату (CEO справа, воркер зліва)
Виявлення SPEC Парсить ### SPEC:-блоки, рендерить як картки з кнопками approve/reject
Введення Повна input bar з селектором моделі, меню інструментів, голосовим введенням, файловими вкладеннями
Dev Result Кнопка для перегляду останньої відповіді developer-терміналу

TerminalPanelView

Відображає вивід воркерів terminal-типу (Developer тощо).

Аспект Деталь
Джерело даних /api/sse/logs/:name?category=${worker.log_category}
Відображення Monospace log-записи з іконками інструментів, індикаторами thinking, станом processing
Введення Простий command bar з кнопкою Send

Hook useSSEStream

Уніфікований SSE-hook для обох типів панелей. Обробляє дедуплікацію (seenIds), виявлення стану processing і трансформацію записів на основі worker.type.

Workers Bar

Верхня панель, що показує всіх зареєстрованих воркерів як toggle-пігулки. Клік додає/прибирає панелі. Layout зберігається у localStorage для кожного проєкту (citadel-workspace-active-${project.name}).

SpecPanel

Kanban-дошка, що показує життєвий цикл спек.

Колонка Які спеки показано
Draft status: "draft" — з кнопками approve/reject
Approved status: "approved" — чекають на developer
In Progress status: "executing" — developer працює
Done status: "done" — завершено
Rejected status: "rejected" — згорнуто за замовчуванням

RoleToggle

Візуальний індикатор у заголовку project pod, що показує активну роль/воркера.

Клік по toggle надсилає /role команду для отримання інформації; реальне перемикання ролей відбувається лише через Telegram-команди (безпека: жодних мутацій із фронтенду).

Модель безпеки

Обмеження інструментів воркера

Інструменти воркерів задекларовані у workers_registry.json. Функція callWorker() читає worker.tools і передає --allowedTools до Claude CLI:

Це гарантує, що chat-воркери (Consultant, UI Designer) НЕ можуть:

Ізоляція воркерів

Структура зберігання

Всі state-файли зберігаються у робочій директорії проєкту під state/:

state/
├── active_role.json          # { "role": "developer"|"consultant", "since": ISO8601 }
├── consultant_thread.json    # { "threadId": "...", "startedAt": ISO8601 }
├── spec_queue.json           # Spec[] array — all specs with status
└── logs/
    ├── consultant-YYYY-MM-DD.log   # JSONL consultant conversation
    └── specs-YYYY-MM-DD.log        # JSONL spec state transitions

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"
  }
]

Реєстр воркерів

Всі воркери задекларовані у 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
    }
  ]
}
Поле Тип Опис
id string Унікальний ідентифікатор воркера (використовується у маршрутизації, сховищі, SSE)
label string Назва для відображення в UI
icon string Emoji-іконка для пігулки/заголовка
type "chat" | "terminal" Тип панелі: бульбашки чату vs monospace-логи
model string ID моделі Claude
max_turns number Макс. кількість agentic-кроків за виклик
tools string[] | "all" Whitelist дозволених інструментів, або "all" для повного доступу
system_prompt_skill string | null Шлях до system prompt у директорії skills/
prompt_style "history" | "gsd" Стиль розмови (append history vs GSD-prompt)
output_format "text" | "stream-json" Режим обробки виводу
focus_dirs string[] Директорії, на яких фокусуватись (інжектяться у prompt)
log_category string JSONL log-категорія (мапиться на SSE ?category= параметр)
builtin boolean Чи постачається цей воркер з Arc OS

Конфігурація ролей

Конфігурація ролей для кожного проєкту у config/project_roles.json:

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

Логування

Всі взаємодії з consultant і переходи спек логуються у форматі JSONL.

Записи 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"]}

Записи 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"}

Підтримується Rick (Orchestrator). Phase 26 — Dynamic Workers.