Архитектура динамических воркеров
Phase 26: Dynamic Worker Registry. Заменяет жёстко прописанный двух-агентный flow из Phase 24.5. Последнее обновление: 2026-04-06
Обзор
Phase 26 заменяет жёстко запрограммированную бинарную логику 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.
Встроенные воркеры:
- Consultant (chat, sonnet) — анализ только для чтения, мозговой штурм, создание спеков. Маршрутизируется через
/cили/w:consultant. - Developer (terminal, opus) — полная реализация. Маршрутизируется через
/dили/w:developer. - UI/UX Designer (chat, sonnet) — анализ фронтенда, дизайн-предложения. Маршрутизируется через
/w:ui-designer.
CEO выбирает воркер через команды Telegram. Спеки — это мост между воркерами: Consultant их создаёт, CEO одобряет, Developer выполняет.
CEO (Telegram)
│
├── /c <message> ──► Consultant subprocess (read-only Claude)
│ │
│ ├── Анализирует код, ищет в вебе
│ └── Выводит блоки SPEC
│ │
│ ▼
│ spec_queue.json (status: "draft")
│
├── /approve <id> ──► статус спека → "approved"
│ │
│ ▼
├── /d <message> ──► Developer (основной GSD loop)
│ │
│ ├── Читает одобренные спеки из очереди
│ ├── Реализует изменения
│ └── Помечает спек → "done"
│
├── /reject <id> ──► статус спека → "rejected"
│
└── /specs ──► Список всех спеков с статусом
Модель данных
Интерфейс Spec
interface Spec {
id: string; // nanoid(8), напр. "a1b2c3d4"
title: string; // Однострочный заголовок из блока SPEC
problem: string; // Какую задачу решает
approach: string; // Технический подход (2-3 предложения)
acceptance: string[]; // Список критериев приёмки
files: string[]; // Затронутые пути к файлам
complexity: "low" | "medium" | "high";
status: SpecStatus;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
consultantThread?: string; // ID треда, создавшего этот спек
}
type SpecStatus = "draft" | "approved" | "rejected" | "executing" | "done";
Машина состояний
ИДЕЯ ──► КОНСУЛЬТАЦИЯ ──► ЧЕРНОВИК ──► РЕВЬЮ ──► ОДОБРЕН ──► ВЫПОЛНЯЕТСЯ ──► ГОТОВО
↑ │
+──── ОТКЛОНЁН ───────────+
Переходы:
| Откуда | Куда | Триггер |
|---|---|---|
| — | draft |
Consultant выводит блок SPEC |
draft |
approved |
CEO отправляет /approve <id> |
draft |
rejected |
CEO отправляет /reject <id> |
rejected |
draft |
Consultant пересматривает после обратной связи |
approved |
executing |
Developer начинает реализацию |
executing |
done |
Developer выполняет все критерии приёмки |
API Endpoints
Все эндпоинты внутренние (маршрутизация master-бота), не HTTP API.
| Эндпоинт | Метод | Auth | Описание |
|---|---|---|---|
/c <msg> |
Telegram | только CEO | Маршрутизировать сообщение к Consultant subprocess |
/d <msg> |
Telegram | только CEO | Маршрутизировать сообщение к Developer (GSD loop) |
/approve <id> |
Telegram | только CEO | Одобрить черновой спек |
/reject <id> |
Telegram | только CEO | Отклонить черновой спек с опциональной обратной связью |
/specs |
Telegram | только CEO | Список всех спеков с фильтром по статусу |
/role |
Telegram | только CEO | Показать текущую активную роль |
| default | Telegram | только CEO | Маршрутизировать к активной роли (consultant или developer) |
Маршрутизация сообщений
handleMessage() master-бота маршрутизирует входящие сообщения 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 и категорию логов. Маршрутизация по умолчанию использует active_role.json. Начальное умолчание: developer.
Компоненты фронтенда
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 |
| Ввод | Полная панель ввода с выбором модели, меню инструментов, голосовым вводом, вложениями файлов |
| Dev Result | Кнопка для просмотра последнего ответа терминала Developer |
TerminalPanelView
Отображает вывод воркеров типа terminal (Developer и др.).
| Аспект | Деталь |
|---|---|
| Источник данных | /api/sse/logs/:name?category=${worker.log_category} |
| Отображение | Записи логов в моноширинном шрифте с иконками инструментов, индикаторами обдумывания, состоянием обработки |
| Ввод | Простая командная строка с кнопкой Send |
Хук useSSEStream
Унифицированный SSE хук для обоих типов панелей. Обрабатывает дедупликацию (seenIds), определение состояния обработки и трансформацию записей в зависимости от worker.type.
Workers Bar
Верхняя панель, показывающая всех зарегистрированных воркеров в виде кнопок-переключателей. Клик добавляет/убирает панели. Раскладка сохраняется в 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
Визуальный индикатор в заголовке pod-а проекта, показывающий активную роль/воркер.
Клик на переключатель отправляет информацию команды /role; реальное переключение ролей — только через команды Telegram (безопасность: никаких мутаций с фронтенда).
Модель безопасности
Ограничения инструментов воркеров
Инструменты воркеров объявлены в workers_registry.json. Функция callWorker() читает worker.tools и передаёт --allowedTools в Claude CLI:
"tools": ["Read", "Glob", ...]— явный whitelist"tools": "all"— полный доступ (только Developer)
Это гарантирует, что воркеры типа chat (Consultant, UI Designer) НЕ МОГУТ:
- Записывать или редактировать файлы
- Выполнять shell-команды
- Делать deploy, commit или push
Изоляция воркеров
- Каждый воркер запускается в собственном процессе/потоке Claude
- История разговора воркера отслеживается в Map
workerThreads(ключ — ID воркера) - Обратная совместимость: массив
consultantThreadсинхронизируется вместе сworkerThreads - Только chatId CEO может выдавать команды
/approveи/reject
Структура хранилища
Все файлы состояния хранятся в рабочей директории проекта в state/:
state/
├── active_role.json # { "role": "developer"|"consultant", "since": ISO8601 }
├── consultant_thread.json # { "threadId": "...", "startedAt": ISO8601 }
├── spec_queue.json # Spec[] массив — все спеки с статусом
└── logs/
├── consultant-YYYY-MM-DD.log # JSONL разговор Consultant
└── specs-YYYY-MM-DD.log # JSONL переходы состояний спеков
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
Все воркеры объявлены в 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-иконка для pill/заголовка |
type |
"chat" | "terminal" |
Тип панели: chat-пузыри или моноширинные логи |
model |
string | ID модели Claude |
max_turns |
number | Максимальное количество агентных ходов за вызов |
tools |
string[] | "all" |
Whitelist разрешённых инструментов или "all" для полного доступа |
system_prompt_skill |
string | null | Путь к системному prompt в директории skills/ |
prompt_style |
"history" | "gsd" |
Стиль разговора (добавлять историю vs GSD prompt) |
output_format |
"text" | "stream-json" |
Режим обработки вывода |
focus_dirs |
string[] | Директории для фокусировки (вставляются в prompt) |
log_category |
string | Категория JSONL-лога (маппится на 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.