Arquitetura de Workers Dinâmicos

Phase 26: Registry Dinâmico de Workers. Substitui o fluxo hardcoded dual-agent da Phase 24.5. Última atualização: 2026-04-06

Visão Geral

A Phase 26 substitui a lógica binária hardcoded Consultant/Developer por um sistema de registry dinâmico de workers.

Os workers são declarados em config/workers_registry.json. Cada worker define: id, label, icon, type (chat/terminal), model, max_turns, tools, system_prompt_skill, prompt_style (history/gsd), output_format, focus_dirs, log_category, builtin.

Workers nativos:

O CEO escolhe o worker via comandos do Telegram. As specs são uma ponte entre workers: o Consultant as cria, o CEO aprova e o Developer executa.

CEO (Telegram)
 │
 ├── /c <message>  ──► Subprocesso Consultant (Claude somente leitura)
 │                      │
 │                      ├── Analisa código, pesquisa na web
 │                      └── Gera blocos SPEC
 │                           │
 │                           ▼
 │                      spec_queue.json (status: "draft")
 │
 ├── /approve <id>  ──► status da spec → "approved"
 │                           │
 │                           ▼
 ├── /d <message>   ──► Developer (loop principal GSD)
 │                      │
 │                      ├── Lê specs aprovadas da fila
 │                      ├── Implementa mudanças
 │                      └── Marca spec → "done"
 │
 ├── /reject <id>   ──► status da spec → "rejected"
 │
 └── /specs         ──► Lista todas as specs com status

Modelo de Dados

Interface 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";

Máquina de Estado

  IDEIA ──► CONSULTA ──► RASCUNHO SPEC ──► REVISÃO ──► APROVADA ──► EXECUTANDO ──► CONCLUÍDA
                               ↑                           │
                               +──── REJEITADA ────────────+

Transições:

De Para Gatilho
draft Consultant gera um bloco SPEC
draft approved CEO envia /approve <id>
draft rejected CEO envia /reject <id>
rejected draft Consultant revisa após feedback
approved executing Developer inicia a implementação
executing done Developer conclui todos os critérios de aceite

Endpoints de API

Todos os endpoints são internos (roteamento pelo master bot), não são HTTP API.

Endpoint Método Auth Descrição
/c <msg> Telegram Somente CEO Roteia mensagem para subprocesso Consultant
/d <msg> Telegram Somente CEO Roteia mensagem para Developer (loop GSD)
/approve <id> Telegram Somente CEO Aprova uma spec em rascunho
/reject <id> Telegram Somente CEO Rejeita uma spec em rascunho com feedback opcional
/specs Telegram Somente CEO Lista todas as specs com filtro de status
/role Telegram Somente CEO Mostra o papel ativo atual
padrão Telegram Somente CEO Roteia para o papel ativo (consultant ou developer)

Roteamento de Mensagens

handleMessage() do master bot roteia mensagens Telegram recebidas:

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)
}

Dispatch unificado: getWorkerConfig(workerId)callWorker(workerId, text, options). A configuração do worker determina model, tools, estilo de prompt e categoria de log. O roteamento padrão usa active_role.json. Padrão inicial: developer.

Componentes Frontend

WorkerPanel (Phase 26)

Dispatcher de painel genérico. Renderiza ChatPanelView ou TerminalPanelView com base em worker.type.

ChatPanelView

Exibe conversas de workers do tipo chat (Consultant, UI/UX Designer, etc.).

Aspecto Detalhe
Fonte de dados /api/sse/logs/:name?category=${worker.log_category}
Exibição Balões de mensagem estilo chat (CEO à direita, Worker à esquerda)
Detecção de SPEC Faz parse de blocos ### SPEC:, renderiza como cards com botões aprovar/rejeitar
Entrada Barra de entrada completa com seletor de modelo, menu de tools, entrada de voz, anexos de arquivo
Resultado do Dev Botão para ver a última resposta do terminal do developer

TerminalPanelView

Exibe saída de workers do tipo terminal (Developer, etc.).

Aspecto Detalhe
Fonte de dados /api/sse/logs/:name?category=${worker.log_category}
Exibição Entradas de log monoespaçadas com ícones de tool, indicadores de "thinking", estado de processamento
Entrada Barra de comando simples com botão Enviar

Hook useSSEStream

Hook SSE unificado para ambos os tipos de painel. Trata deduplicação (seenIds), detecção de estado de processamento e transformação de entradas com base em worker.type.

Workers Bar

Barra superior exibindo todos os workers registrados como pills alternáveis. Clique para adicionar/remover painéis. Layout persiste no localStorage por projeto (citadel-workspace-active-${project.name}).

SpecPanel

Board no estilo Kanban mostrando o ciclo de vida das specs.

Coluna Specs exibidas
Rascunho status: "draft" — com botões aprovar/rejeitar
Aprovada status: "approved" — aguardando o developer
Em Andamento status: "executing" — developer trabalhando
Concluída status: "done" — finalizada
Rejeitada status: "rejected" — recolhida por padrão

RoleToggle

Indicador visual no cabeçalho do pod do projeto mostrando o papel/worker ativo.

Clicar no toggle envia informação do comando /role; a troca de papel em si é feita apenas por comandos Telegram (segurança: sem mutações pelo frontend).

Modelo de Segurança

Restrições de Tools por Worker

As tools dos workers são declaradas em workers_registry.json. A função callWorker()worker.tools e passa --allowedTools para o Claude CLI:

Isso garante que workers do tipo chat (Consultant, UI Designer) NÃO possam:

Isolamento de Workers

Layout de Armazenamento

Todos os arquivos de estado são armazenados no diretório de trabalho do projeto sob 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"
  }
]

Registry de Workers

Todos os workers declarados em 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
    }
  ]
}
Campo Tipo Descrição
id string Identificador único do worker (usado em roteamento, armazenamento, SSE)
label string Nome de exibição na UI
icon string Emoji para pill/cabeçalho
type "chat" | "terminal" Tipo de painel: balões de chat vs logs monoespaçados
model string ID do modelo Claude
max_turns number Máximo de turns agênticos por invocação
tools string[] | "all" Whitelist de tools permitidas, ou "all" para acesso total
system_prompt_skill string | null Path para o system prompt no diretório skills/
prompt_style "history" | "gsd" Estilo de conversa (append de histórico vs prompt GSD)
output_format "text" | "stream-json" Modo de tratamento da saída
focus_dirs string[] Diretórios para focar (injetados no prompt)
log_category string Categoria de log JSONL (mapeia para o parâmetro SSE ?category=)
builtin boolean Indica se este worker é nativo do Arc OS

Configuração de Papel

Configuração de papel por projeto em config/project_roles.json:

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

Logging

Todas as interações do consultant e transições de spec são registradas em formato JSONL.

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

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

Mantido por Rick (Orchestrator). Phase 26 — Workers Dinâmicos.