deco Studio

Architecture

How Studio is wired end to end — edge, cloud cluster, and desktop — and how requests, runs, and sandboxes flow between the tiers.

This page describes how a running Studio deployment is wired together: the tiers, what each one does, and how a request becomes an agent run, a tool call, or a sandbox preview. It is the conceptual companion to the Kubernetes and Docker Compose deploy guides.

The three tiers

Studio spans three trust/locality boundaries:

  • Edge — the public internet path: a CDN and an L4 load balancer.
  • Cloud cluster — the Kubernetes deployment: web, API, workers, Postgres, NATS, and cloud sandboxes.
  • Desktop — the user’s laptop, connected by deco link , where the desktop loop and desktop sandbox can run.
Edge public internet Cloud Cluster kubernetes Desktop deco link · laptop Cloud Sandbox · pod Object Store s3 · external LLM anthropic · external Downstream MCP tool servers · external Client browser / mcp CF edge / cdn NLB l4 lb Web nginx · spa ×N · scales independently API hono · role=api ×N · scales independently Worker role=worker · dbos ×N · scales independently MCP Proxy api routes Files / Storage api · /files /fs NATS messaging DB postgres Gateway k8s gateway api Daemon API protected · /_sandbox/* Org FS mount · sidecar Preview public Link Daemon deco link Desktop Loop native loop Desktop Sandbox local daemon Org FS mount · daemon
EdgeWebAPIWorkerMCPFilesPostgresNATSSandboxPreviewLLMDesktop
Request path, agent runs and sandboxes across the edge, cloud cluster and desktop tiers.

The same flow as text:

 EDGE                CLOUD CLUSTER                                     DESKTOP
────                ─────────────                                     ───────

Client ─▶ CF ─┬─▶ NLB ─▶ Web(nginx) ─▶ API ──┬─▶ MCP Proxy ─▶ Downstream MCP (ext)
              │                               ├─▶ Files/Storage ─▶ Object Store (ext)
              │                               ├─▶ DB (Postgres)
              │                               ├─▶ NATS ───────────────▶ Link Daemon
              │                               └─▶ Worker                    │
              │                                     │  model                ▼
              │                                     ├──────────▶ LLM   Desktop Loop ─▶ Desktop Sandbox
              │                                     ├─▶ DB                   │              └─ Org FS (mount)
              │                                     ├─▶ Files/Storage        └─(MCP presigned)─▶ API
              │                                     ├─▶ Downstream MCP (in-process bridge)
              │                                     └─▶ Cloud Sandbox ─┬─ Daemon API (/_sandbox/*)
              │                                                        └─ Org FS (sidecar) ─▶ /api/:org/fs ─▶ S3
              └─▶ Gateway (k8s) ─▶ Preview (public dev-server) 

Edge

Component Role
CF (Cloudflare) TLS termination, static SPA caching, DDoS/bot mitigation. First hop for all traffic.
NLB L4 load balancer fronting the cluster. Routes to the web (front-door) pods; the frontDoorLabels selector decides which pods receive ingress.

Cloud cluster

Web, API, and Worker scale independently

These three deployments are separate and scale on their own. The web tier was split out from the API (the “front-door split”) so the static front door can deploy and scale on a different cadence than the application server.

Tier Deployment Responsibility
Web nginx Serves the React SPA and reverse-proxies /api , /mcp , /oauth-proxy , /.well-known to the API Service. Port 8080.
API Hono, MESH_DISPATCH_ROLE=api HTTP routes, Better Auth, the MCP proxy, access control. Enqueues decopilot runs onto DBOS queues and tails NATS to stream output back to the UI. Stateless. Does not run the agent loop.
Worker Hono, MESH_DISPATCH_ROLE=worker Dequeues DBOS queues (via listenQueues ) and runs the agent loop ( streamText : model → tool → repeat). CPU-bound. These are the run executors; scale horizontally.

The split is by role, not by image — both run the same build. MESH_DISPATCH_ROLE decides whether a pod listens on the DBOS queues ( worker ) or only serves HTTP and enqueues ( api ). A single-deployment setup can use all .

The set of queues a worker pod listens on is configured by env ( listenQueues ). Because of that, worker pools can be split per DBOS queue — running different workflows on separate pools with their own resources and scaling.

Datastores

Component Role
DB (PostgreSQL, via Kysely) System of record: orgs, connections, credential vault, audit, threads + messages, and sandbox_runner_state . It also holds the DBOS queues and workflow_status journal that make runs durable and recoverable.
NATS Live messaging infrastructure with three jobs: (1) JetStream /stream fan-out ( decopilot.stream.<thread> ) → UI live tail; (2) the pull work-queue ( link.work.<user> ) → desktop; (3) the link-claim KV ( studio_links ) tracking which pod owns each user’s link.

The event bus is dormant. The CloudEvents pub/sub feature ( EVENT_PUBLISH / EVENT_SUBSCRIBE , the durable event queue, ON_EVENTS subscribers) is only consumed by the workflow plugin, which is not in use. NATS itself is not dormant — it serves the live jobs listed above. Don’t conflate the two.

The decopilot run lifecycle

  1. A message ( POST /messages ) or an automation fire creates a run on a thread.
  2. The API enqueues it onto a DBOS queue in Postgres:
    • THREAD_GATE_QUEUE — serialized per thread (concurrency 1 per threadId ).
    • AUTOMATIONS_QUEUE — per-org parallelism.
  3. A Worker dequeues it and runs the agent loop: call the model, execute any tool calls, repeat (up to a step limit).
  4. Output chunks are published to NATS and tailed back to the UI over /stream .
  5. If the pod crashes, DBOS journal replay resumes retriable steps on another pod — recovery is the framework’s job, not hand-rolled.

There are two transports for step 3:

  • Hosted (default) — the run executes in-process on the worker and uses a cloud sandbox.
  • Pull — the run is published to NATS link.work.<user> ; the user’s desktop picks it up and runs the loop locally against a desktop sandbox.

MCP: in-process vs. the proxy routes

This distinction matters for reasoning about the system:

  • The worker calls MCP tools in-process. The agent loop builds a PassthroughClient over an in-memory bridge and calls tools directly — no HTTP hop inside the cluster. It still connects outward to downstream MCP servers.
  • The MCP proxy routes are for external clients. /mcp/virtual-mcp/:id , /mcp/:connectionId , /oauth-proxy/* , and /.well-known are served by the API to external IDEs (Cursor, Claude, VS Code). The worker does not use these.

Which routes are called by whom

Routes API / external Worker
MCP proxy ( /mcp/* , /oauth-proxy/* , .well-known ) ✅ external clients via the API ❌ (in-process instead)
File & object-storage ( /api/:org/files/* , presigned GET/PUT, uploads, /api/:org/fs/* ) ✅ serving clients ✅ agent tools read/write files & mint presigned URLs
Worker-only over HTTP none — tool calls are in-process

File and object-storage routes are the genuine “called by both” surface, and they are backed by an S3-compatible Object Store. The /api/:org/fs/* routes also back the org-filesystem mount inside sandboxes.

Sandboxes

A sandbox clones the repo, runs the dev server, and exposes an in-pod daemon. Its HTTP surface splits in two:

Surface Auth Purpose Caller
Preview (catch-all * ) None — the handle (subdomain) is the secret Reverse-proxies the running dev server (the live app preview); injects HMR. /_sandbox/* is actively rejected here. The end user’s browser at <handle>.preview.<domain> , through Cloudflare (LB) → a Kubernetes Gateway (Istio Gateway API / HTTPRoute) → the daemon
Daemon API ( /_sandbox/* ) Bearer DAEMON_TOKEN Control surface: fs ops (read/write/edit/bash/grep), git (status/diff/publish), exec scripts, setup (clone → install → start), tasks, SSE events, harness dispatch. The cluster (worker for agent fs/git/bash tools; API for UI setup + events)

Cloud vs. desktop sandboxes

Cloud sandbox Desktop sandbox
Where agent-sandbox operator + a SandboxClaim pod per (user, projectRef) Same daemon, spawned locally on the laptop
Reached over k8s port-forward / in-cluster Service (control); ingress or port-forward (preview) loopback (control); <handle>.localhost:<port> (preview)
Selected when the default for hosted runs a deco link is live — user-desktop is the default provider then

Org filesystem (org-fs)

Each sandbox can mount the org filesystem at <appRoot>/org/<volume> , so the agent and dev server read and write org files as ordinary paths. The mount stack is rclone (NFS/FUSE) → the daemon's loopback WebDAV → /api/:org/fs/* → S3 — the same object store as the file routes, surfaced as a mounted volume.

It is wired on both providers, with different mount mechanics:

Cloud sandbox Desktop sandbox
Who mounts a privileged sidecar container (the unprivileged daemon can’t mount) the daemon directly (it has full permissions)
Config delivery post-bind: mesh POST /_sandbox/orgfs-config ; the daemon relays it to a shared control volume the sidecar watches (warm-pool claims reject spec.env ) boot env: ORGFS_CONFIG , mounted at daemon startup
Propagation rclone with allowOther so the mount propagates to the main container single client — no propagation needed

Desktop

When a user runs deco link , a Link Daemon on their laptop long-polls the cluster ( /api/links/work for chat, /api/links/proxy for sandbox control) and heartbeats presence into the NATS link-claim KV. Pulled runs execute in the Desktop Loop ( runNativeAgentLoopCore ) — a portable copy of the agent loop. The thinking model is injected by the cluster, and MCP is reached over HTTP via a presigned URL back to the cluster.

At a glance

  • Web / API / Worker are independent deployments; workers are DBOS run executors.
  • Runs are durable via DBOS queues + journal in Postgres; recovery is automatic.
  • MCP tool calls are in-process on the worker; the /mcp/* proxy routes are for external clients only.
  • File/object-storage routes are the shared API+worker surface.
  • NATS is live; the CloudEvents event-bus feature is dormant.
  • Sandboxes expose a public preview and a token-protected control API, in the cloud (k8s) or on the desktop ( deco link ).

Found an error or want to improve this page?

Edit this page