Arquitetura
Como o Studio é conectado de ponta a ponta — edge, cloud cluster e desktop — e como requests, runs e sandboxes fluem entre as camadas.
Esta página descreve como um deployment do Studio em execução é montado: as camadas, o que cada uma faz e como um request vira um run de agent, uma chamada de tool ou um preview de sandbox. É um bom material de fundo tanto se você faz self-host quanto se usa a cloud, e é o complemento conceitual dos guias de deploy de Kubernetes e Docker Compose.
A topologia Kubernetes segue o chart oficial 0.16.1, revisado no commit f4cd364c do Studio. O diagrama de edge representa o caminho de referência hospedado; um deployment próprio configura seu ingress e roteamento público.
As três camadas
O Studio abrange três fronteiras de confiança/localidade:
- Edge — o caminho da internet pública: um CDN e um load balancer L4.
- Cloud cluster — o deployment no Kubernetes: web, API, workers, Postgres, NATS e cloud sandboxes.
- Desktop — a app nativa do Studio no laptop do usuário, onde o
local-apiembutido gerencia coding harnesses nativos e sandboxes locais.
O mesmo fluxo em texto:
EDGE CLOUD CLUSTER DESKTOP
──── ───────────── ───────
Client ─▶ CF ─┬─▶ NLB ─▶ Web(nginx) ─▶ API ──┬─▶ MCP Proxy ─▶ Downstream MCP (ext)
│ ├─▶ Files/Storage ─▶ Object Store (ext)
│ ├─▶ DB (Postgres)
│ ├─▶ NATS
│ └─▶ Worker ─┬─▶ LLM
│ ├─▶ Downstream MCP (in-process bridge)
│ └─▶ AgentSandbox ─┬─ Daemon API (/_sandbox/*)
│ └─ Org FS (sidecar) ─▶ /api/:org/fs ─▶ S3
└─▶ Gateway (k8s) ─▶ Preview (public dev-server)
Desktop app ─▶ local-api ─┬─▶ Studio API (dados upstream e MCP)
├─▶ Claude Code / Codex / OpenCode
└─▶ Desktop Sandbox ─▶ Org FS (mount)
Edge
| Componente | Função |
|---|---|
| CF (Cloudflare) | Terminação TLS, cache estático da SPA, mitigação de DDoS/bots. Primeiro salto de todo o tráfego. |
| NLB | Load balancer L4 na frente do cluster. Roteia para os pods de web (front-door); o seletor frontDoorLabels decide quais pods recebem o ingress. |
Cloud cluster
O front door e os workers escalam separadamente
O chart tem um Deployment principal com um container nginx e dois containers de API em cada pod. Eles escalam juntos como pods de front door. Os workers de fila rodam em outro Deployment e podem escalar independentemente do front door.
| Camada | Deployment | Responsabilidade |
|---|---|---|
| Web | nginx no pod principal | Serve a SPA em React e faz reverse-proxy das rotas de API e MCP para os containers de API do mesmo pod. Porta 8080. |
| API | Hono, STUDIO_DISPATCH_ROLE=api | Rotas HTTP, Better Auth, o MCP proxy, controle de acesso. Enfileira runs do Decopilot nas queues do DBOS e acompanha o NATS para fazer streaming do output de volta para a UI. Stateless. Não roda o agent loop. |
| Worker | Hono, STUDIO_DISPATCH_ROLE=worker | Desenfileira as queues do DBOS (via listenQueues) e roda o agent loop (streamText: model → tool → repete). Limitado por CPU. São os executores de run; escalam horizontalmente. |
A separação é por role, não por imagem — ambos rodam o mesmo build. STUDIO_DISPATCH_ROLE decide se um pod escuta as queues do DBOS (worker) ou apenas serve HTTP e enfileira (api). Um setup de deployment único pode usar all.
O conjunto de queues que um pod worker escuta é configurado por env (listenQueues). Por isso, os pools de workers podem ser separados por queue do DBOS — rodando workflows diferentes em pools separados com seus próprios recursos e escalonamento.
Datastores
| Componente | Função |
|---|---|
| DB (PostgreSQL, via Kysely) | Sistema de registro: orgs, connections, credential vault, audit, threads + mensagens e sandbox_runner_state. Também mantém as queues do DBOS e o journal workflow_status que tornam os runs duráveis e recuperáveis. |
| NATS | Infraestrutura de mensageria ao vivo com três tarefas: (1) o log de runs com fencing no JetStream (decopilot.stream.<thread>), usado por /stream e pelo projector durável; (2) broadcasts de cancelamento de runs entre pods; (3) estado em JetStream KV compartilhado entre réplicas para caches de listas MCP e circuit breakers de connections. |
O ciclo de vida de um run do Decopilot
- Uma mensagem (
POST /messages) ou o disparo de uma automation cria um run em uma thread. - A API o enfileira em uma queue do DBOS no Postgres:
THREAD_GATE_QUEUE— serializada por thread (concorrência 1 porthreadId).AUTOMATIONS_QUEUE— particionada por org, então uma org saturada só bloqueia a própria partição.
- Um Worker desenfileira o gate workflow e inicia um child workflow hosted. Runs in-process do Decopilot usam
HOSTED_HARNESS_QUEUE; runs de Claude Code hosted na sandbox usamHOSTED_HARNESS_SANDBOXED_QUEUE. O gate pai não espera o child — ele acompanha (live-tail) o mesmo stream do NATS que o child publica, enquanto o projector durável escreve mensagens e o status terminal. - Os chunks de output são publicados no NATS e acompanhados de volta para a UI via
/stream. - Se o pod cair, o replay do journal do DBOS retoma os steps retriáveis em outro pod — a recuperação é trabalho do framework, não feito à mão.
Os dois caminhos são hosted e usam o provider fixo AgentSandbox do Studio quando precisam de uma sandbox:
- Decopilot roda seu model loop in-process no worker e acessa a sandbox gerenciada para operações de repositório, filesystem, Git e shell.
- Claude Code roda seu harness loop dentro da sandbox gerenciada; o worker encaminha seu stream para o mesmo pipeline de NATS e projector.
Chats de coding agents nativos são uma superfície separada. A app desktop roda Claude Code, Codex ou OpenCode localmente e sincroniza a thread por meio do local-api embutido.
MCP: in-process vs. as rotas de proxy
Essa distinção importa para raciocinar sobre o sistema:
- O worker chama tools de MCP in-process. O agent loop monta um
PassthroughClientsobre um bridge em memória e chama as tools diretamente — sem salto HTTP dentro do cluster. Ele ainda se conecta para fora a downstream MCP servers. - As rotas do MCP proxy são para clients externos.
/mcp/virtual-mcp/:id,/mcp/:connectionId,/oauth-proxy/*e/.well-knownsão servidas pela API para IDEs externas (Cursor, Claude, VS Code). O worker não usa essas rotas.
Quais rotas são chamadas por quem
| Rotas | API / externo | Worker |
|---|---|---|
MCP proxy (/mcp/*, /oauth-proxy/*, .well-known) | ✅ clients externos via API | ❌ (in-process no lugar) |
File & object-storage (/api/:org/files/*, GET/PUT presigned, uploads, /api/:org/fs/*) | ✅ servindo clients | ✅ tools de agent leem/escrevem arquivos e geram presigned URLs |
| Apenas worker via HTTP | — | nenhuma — as chamadas de tool são in-process |
As rotas de file e object-storage são a verdadeira superfície "chamada por ambos", e são respaldadas por um Object Store compatível com S3. As rotas /api/:org/fs/* também dão suporte ao mount do org-filesystem dentro das sandboxes.
Sandboxes
Uma AgentSandbox hosted clona o repo, roda o dev server e expõe um daemon in-pod. Sua superfície HTTP se divide em duas:
| Superfície | Auth | Propósito | Quem chama |
|---|---|---|---|
Preview (catch-all *) | Nenhuma — o handle (subdomínio) é o segredo | Faz reverse-proxy do dev server em execução (o preview ao vivo da app); injeta HMR. /_sandbox/* é ativamente rejeitado aqui. | O navegador do usuário final em <handle>.preview.<domain>, através do Cloudflare (LB) → um Kubernetes Gateway (Istio Gateway API / HTTPRoute) → o daemon |
Daemon API (/_sandbox/*) | Bearer DAEMON_TOKEN | Superfície de controle: operações de fs (read/write/edit/bash/grep), git (status/diff/publish), exec de scripts, setup (clone → install → start), tasks, eventos SSE, dispatch do harness. | O cluster (worker para as tools de fs/git/bash do agent; API para setup + eventos da UI) |
Cloud vs. desktop sandboxes
| Cloud sandbox | Desktop sandbox | |
|---|---|---|
| Onde | operador agent-sandbox + um pod SandboxClaim por (user, projectRef) | O local-api e o SandboxManager da app nativa, com um worktree local por handle |
| Acessado via | port-forward do k8s / Service in-cluster (controle); ingress ou port-forward (preview) | interceptação em loopback; <handle>.localhost:<port> (preview) |
| Dono do roteamento | A API hosted do Studio sempre usa AgentSandbox e reporta agent-sandbox | A app nativa intercepta localmente as mesmas chamadas de lifecycle/filesystem e reporta local-api |
Org filesystem (org-fs)
Cada sandbox pode montar o org filesystem em <appRoot>/org/<volume>, para que o agent e o dev server leiam e escrevam arquivos da org como paths comuns. A stack de mount é rclone (NFS/FUSE) → o WebDAV de loopback do daemon → /api/:org/fs/* → S3 — o mesmo object store das rotas de arquivo, exposto como um volume montado. É o mesmo filesystem que você navega na Library.
Ele é conectado em ambas as superfícies, com mecânicas de mount diferentes:
| Cloud sandbox | Desktop sandbox | |
|---|---|---|
| Quem monta | um container sidecar privilegiado (o daemon não-privilegiado não consegue montar) | o local-api diretamente na máquina do usuário |
| Entrega da config | pós-bind: Studio POST /_sandbox/orgfs-config; o daemon a repassa para um control volume compartilhado que o sidecar observa (claims do warm-pool rejeitam spec.env) | o runtime nativo resolve os volumes da org e mantém o mount local |
| Propagação | rclone com allowOther para que o mount se propague ao container principal | client único — sem necessidade de propagação |
Desktop
A app nativa do Studio embute a UI de produção e um local-api em Axum na mesma origin autenticada de loopback. O local-api encaminha dados compartilhados e tráfego MCP para o upstream, mas termina no laptop as rotas exclusivas de thread, harness, sandbox, filesystem, Git, task, terminal e preview. Ele inicia o CLI do Claude Code, Codex ou OpenCode do usuário no worktree local da thread e persiste o estado nativo de threads e sandboxes no SQLite.
O roteamento do provider segue a superfície que recebe o request, e não uma opção do request: chamadas de lifecycle do browser chegam à API hosted e ao AgentSandbox; as mesmas chamadas feitas pela webview nativa são interceptadas pelo local-api e operam na sandbox local-api. Assim, os registros hosted e desktop continuam distinguíveis sem expor um seletor de provider aos callers.
Em resumo
- Web e API compartilham o Deployment principal; workers escalam separadamente como executores de run do DBOS.
- Os runs são duráveis via queues + journal do DBOS no Postgres; a recuperação é automática.
- As chamadas de tool de MCP são in-process no worker; as rotas de proxy
/mcp/*são apenas para clients externos. - As rotas de file/object-storage são a superfície compartilhada entre API e worker.
- O NATS dá suporte a streaming e coordenação entre pods.
- O roteamento de sandbox pertence à superfície: o Studio hosted usa AgentSandbox; a app nativa intercepta localmente e registra
local-api.