swarm — dashboard (arquitetura)
Swarm — Dashboard
Canal primário de monitoramento e human-in-loop. Substitui o Telegram como interface principal — Telegram fica só pra push de alertas críticos. Ver spec.md pra contexto e schema.
Objetivo
Ver os agentes trabalhando em tempo real: qual skill está rodando agora, últimos ciclos, pipeline de nichos, produtos vivos/mortos, decisões do Treasurer, e uma fila de aprovações humanas que destrava ações bloqueadas das skills.
Stack
| Camada | Tecnologia |
|---|---|
| Framework | Astro 5 SSR (adapter Node) |
| Interatividade | Ilhas React (hidratadas só onde precisa) |
| Estilo | Tailwind v4, tema dark/premium (Linear-ish) |
| ORM | Drizzle (Postgres swarm, porta 5433) |
| Realtime | SSE (text/event-stream); WS só se precisar bidirecional depois |
| Auth | OAuth 2.0 via vault SSO (vault.kodama.solutions) |
| Deploy | Container Docker em infra/docker-compose.yml, Caddy proxy |
Rotas
| Rota | Conteúdo | Ilha React |
|---|---|---|
/ |
Overview: revenue 30d, products live/killed, budget burn por playbook, feed live de eventos | EventFeed, MetricsGauge |
/playbooks/:id |
Niches pendentes + products + decisões Treasurer do playbook | — |
/products/:id |
Métricas diárias (chart), treasurer log, assets, custos | MetricsChart |
/niches |
Pipeline kanban (pending → validated → rejected → used) com fit-scores | — |
/agents |
Skills em execução, last cycle scout/master/analytics/treasurer, gateway health | EventFeed, GatewayHealth |
/approvals |
Queue de aprovações pendentes; aprovar/rejeitar destrava a skill | ApprovalCard |
/treasurer |
Log auditável com filtros (decision, playbook, date) | — |
Realtime (SSE)
Endpoint único multiplexado: GET /api/events?channels=agents,metrics,approvals.
Fonte dos eventos:
- Postgres LISTEN/NOTIFY — triggers em
agent_events,approvals,alerts,metrics_dailyemitemNOTIFY swarm_events, '<json>'. O endpoint SSE fazLISTEN swarm_eventse repassa. - OpenClaw Gateway WS — worker conecta em
ws://host.docker.internal:18789, escuta eventos do gateway (skill lifecycle), e insere emagent_events(que por sua vez dispara NOTIFY). Mantém reconnect com backoff.
Browser: EventSource('/api/events?channels=agents') → atualiza feed sem polling.
OAuth (vault SSO)
Mesmo fluxo do Claude Desktop MCP (setup/claude-desktop-mcp):
- Dashboard sem sessão → redirect
/api/oauth/login - Descobre
/.well-known/oauth-authorization-serverdo vault - Registra client dinâmico (
POST /api/oauth/register) na primeira vez; guarda client_id - Redirect pro
authorization_endpointcom PKCE (S256), scopemcp - Callback
/api/oauth/callbacktroca code → access+refresh token - Sessão em cookie httpOnly assinado; refresh automático antes de expirar
Sem login = sem dashboard. Single-user por enquanto (Eu/Kodama1); estrutura permite multi-user depois.
Approval flow (destrava skills)
skill (ex: treasurer decide boost_ads)
→ INSERT approvals (skill, action, payload, decided_at=null)
→ skill faz polling: SELECT decision FROM approvals WHERE id=? até != null
(ou LISTEN se a skill suportar)
dashboard /approvals
→ mostra card com action + payload (contexto factual)
→ humano clica Aprovar/Rejeitar
→ POST /api/approvals/:id/decide { decision, decided_by }
→ UPDATE approvals SET decision, decided_at, decided_by
skill destrava: se approved → executa; se rejected → loga e segue
timeout: job marca decision='timeout' após 24h + INSERT alerts(critical) → Telegram
Telegram bridge (só crítico)
Worker no backend do dashboard:
LISTEN swarm_eventsfiltrandoalertscomseverity='critical'- Dispara mensagem no bot (
TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID) - Conteúdo: o que houve + link
https://swarm.kodama.solutions/... - NÃO recebe comandos. É só notificação. Decisão sempre no dashboard.
Gatilhos críticos: gateway down >5min, budget estourado, approval pendente >24h, API falha 3x consecutivas.
Estrutura de pastas (dashboard/)
dashboard/
├── astro.config.mjs
├── package.json
├── tailwind.config.ts
├── drizzle.config.ts
├── Dockerfile
├── src/
│ ├── pages/
│ │ ├── index.astro
│ │ ├── playbooks/[id].astro
│ │ ├── products/[id].astro
│ │ ├── niches.astro
│ │ ├── agents.astro
│ │ ├── approvals.astro
│ │ ├── treasurer.astro
│ │ └── api/
│ │ ├── events.ts # SSE multiplexer (LISTEN/NOTIFY)
│ │ ├── approvals/[id].ts # POST decide
│ │ └── oauth/
│ │ ├── login.ts
│ │ └── callback.ts
│ ├── components/
│ │ ├── astro/ # Layout, PlaybookCard, StatCard, NicheColumn
│ │ └── react/ # EventFeed, ApprovalCard, MetricsGauge, MetricsChart, GatewayHealth
│ ├── lib/
│ │ ├── db.ts # Drizzle client + schema (mirror de db/schema.sql)
│ │ ├── pg-notify.ts # LISTEN helper
│ │ ├── gateway-ws.ts # OpenClaw WS client + reconnect
│ │ ├── oauth.ts # vault SSO (PKCE, refresh)
│ │ ├── session.ts # cookie assinado
│ │ └── telegram.ts # critical alerts
│ └── styles/global.css
└── .env.example
Variáveis de ambiente
DATABASE_URL=postgres://swarm:***@swarm-db:5432/swarm
VAULT_OAUTH_BASE=https://vault.kodama.solutions
PUBLIC_BASE_URL=https://swarm.kodama.solutions
SESSION_SECRET=<32+ chars>
GATEWAY_WS=ws://host.docker.internal:18789
TELEGRAM_BOT_TOKEN=***
TELEGRAM_CHAT_ID=***
Decisões de design
- SSE em vez de WS no browser: tráfego é server→client (monitoramento). Aprovação é POST normal. Mais simples, reconecta sozinho.
- LISTEN/NOTIFY em vez de polling: Postgres já é fonte única; triggers empurram. Zero polling no browser.
- Drizzle espelha
db/schema.sql: schema canônico é o SQL (rodado no container db). Drizzle só pra type-safe queries no dashboard, não pra migration (migrations ficam emdb/migrations/). - Ilhas React mínimas: só onde tem estado client (feed, approvals, charts). Resto é Astro estático SSR.
Ver também
- projects/swarm/spec — spec técnica completa
- projects/swarm/context — visão geral
- setup/claude-desktop-mcp — mesmo fluxo OAuth do vault