System Architecture

Studio Local Mode and the Portable Core

Date: 2026-07-21 | Status: Draft for review | Jira: AURA-797 (gate), AURA-798/799/800/801 (implementation)

1. Context and goals

Studio v1 ships as a single hosted deployment: the orchestrator runs as a Cloudflare Worker with Durable Objects at studio.aegeanai.com, sessions execute in Cloudflare Containers, artifacts land in R2, and the LLM path is hardwired to the Anthropic Messages API through one AI Gateway. v1 proved the product loop end to end (AURA-790 through AURA-794); this document designs the v2 deployment story around two requirements stated on 2026-07-21.

The first is a local developer experience: a user installs Studio on their own machine with docker compose, opens the same console at http://localhost:PORT that hosted users see at studio.aegeanai.com, onboards a GitHub repository as an auraison project, drives it with natural-language instructions executing in a localhost container, and pays for inference with their existing Claude Code subscription rather than an API key. Local mode is a product for end users, packaged as if strangers install it, with rough edges tolerated internally first.

The second is independence: the orchestration must not be hardwired to Anthropic as a model provider, and the platform must not be hardwired to Cloudflare as a cloud. Both are treated the same way — not as rewrites, but as seams.

2. Framing: a portable core with managed deployments

The orchestration core — the Worker, the Orchestrator Durable Object, the phase machine, the tool loop, the console UI — is portable code: workerd, the runtime wrangler dev embeds, is open source and runs in any container. The design therefore treats Cloudflare as the managed deployment of a portable core rather than the platform's foundation. Local mode is both a product and the standing proof of that portability: if the whole platform runs from a compose file on a laptop, it runs on any VM or Kubernetes cluster on any cloud.

Independence is enforced at five seams, described in §4. Everything outside the seams is one shared codebase; there is deliberately no "local fork" of the orchestrator.

3. Local topology

Local mode is one compose file with four services.

The studio service runs the exact Worker code from control-plane/studio/ under wrangler dev in a Node container, serving the console and API at http://localhost:8787. Catalog (D1) and Durable Object state ride on miniflare's local persistence — real SQLite files on a compose volume — using the same binding code as production; nothing is ported. The agent-gateway service is new and described in §4. The rustfs service is the S3-compatible object store (the same choice the data plane made for its lakehouse storage). The ros2-workspace service is the same image GHCR already distributes for the BYO substrate, running as a long-lived container the provider attaches to.

4. The five seams

Compute. WorkspaceProvider already abstracts session execution; v1 has MockProvider and CfContainerProvider. Local mode adds LocalDockerProvider, which drives the compose-managed workspace container's exec API (:9090) over the compose network. The AURA-794 semantics — double-fork detached launches, the boot-aware recorder, graph-truth sim.status — carry over unchanged because they live in the shell commands the provider issues, not in the transport. Session creation grows a substrate selector (cf-container today, localhost here) that picks the provider factory. (AURA-800)

LLM and agents. LlmClient becomes a capability-flagged contract with two implementations. AnthropicMessages is the existing client, kept as the hosted default because Anthropic prompt caching is what holds hosted session cost near $0.35; caching is a declared optional capability that other backends degrade gracefully without. OpenAICompat speaks the de-facto neutral dialect: chat completions with tool calling. In cloud it targets the AI Gateway's multi-provider endpoints (OpenAI, Google, Workers AI); locally it targets the agent-gateway sidecar or an Ollama directly. The sidecar exposes an OpenAI-compatible endpoint and translates each call into one claude -p invocation, which resolves the loop-ownership question deliberately: the orchestrator keeps driving its own tool loop, so tool caps, phase budgets, and honest sim.status are enforced identically on every backend, and Claude Code serves as a completion engine billed to the user's subscription. Running Claude Code as a full autonomous agent over MCP-exposed workspace tools is explicitly deferred. Backend selection is per-deployment-mode configuration now, per-session later. (AURA-801)

Storage. The Worker speaks the S3 API in every mode — request signing in-Worker (an aws4fetch-class client), R2 reached through its S3-compatible endpoint in cloud, RustFS locally, any S3 store on any cloud later. The R2 bindings in publish.ts and the provider snapshot path are rewritten once to this client and retired; R2 becomes an S3 vendor the hosted deployment happens to use. This is the seam the tenant-owned-bucket requirement (§5) rides on. (AURA-798/799)

Catalog. D1 needs no surgery: its binding is SQLite-semantics code that miniflare backs with real SQLite locally, so the same code path already runs in both modes. Moving off Cloudflare entirely would swap the binding for libsql or Postgres behind the existing db.ts call sites; that exit ramp is documented here and deliberately not built.

Auth. Hosted mode keeps Cloudflare Access (email allowlist plus the e2e service token). Local mode binds to localhost and adds nothing; an optional shared-token environment variable guards the case where a user exposes the port on a LAN. No new auth system is introduced.

5. Projects and repository onboarding

A new projects table joins the catalog:

ColumnMeaning
id, tenant_id, created_atidentity and ownership
repo_urlthe onboarded GitHub repository
bucket_endpoint, bucket_name, bucket_prefixwhere this project's artifacts live
credentials_refreference to the S3 credentials for that bucket

The bucket columns default to platform values (the compose RustFS locally, the platform R2 bucket hosted). That is the "column, not a migration" decision: hosted bring-your-own-storage later means a tenant writes non-default values — their own R2, AWS S3, or a MinIO on their NAS — with no schema change. The operational surface of serving share pages from foreign buckets is deferred with it.

Onboarding is one call: POST /api/projects with a repository URL creates the row and provisions the prefix. Starting a session for a project clones the repository into the workspace container's /ws, replacing the template bootstrap, so instructions operate on the user's actual codebase; local mode reuses the host's git credentials or a token environment variable, and hosted private-repo auth (a GitHub App) is explicitly deferred. The publish pipeline namespaces every artifact — workspace tar, git bundle, recording, share page — under the project prefix. The git bundle, best-effort in v1, becomes genuinely meaningful because the workspace is a real repository. (AURA-799)

6. Error handling

Backend and gateway failures surface through the existing turn_failed envelope the console already renders honestly. S3 failures during publish flow through the existing transactional cleanup (objects written before the D1 batch are removed on failure). A clone failure at session start fails session creation with a 4xx and the git error in the detail field, before any provider is provisioned.

7. Testing

The provider-contract suite finally runs complete: LocalDockerProvider executes it against a real container with no STAGING skip, closing the gap the CfContainer suite documented since v1. Compose acceptance is the golden-path e2e green at http://localhost:8787 with all AURA-794 realism gates, publishing to a RustFS prefix. The backend abstraction carries a three-way acceptance matrix: hosted Anthropic as regression, one OpenAI-compat cloud backend through the AI Gateway, and local claude -p with no ANTHROPIC_API_KEY configured anywhere.

8. What this does not cover

Multi-node or highly-available self-hosting (single-machine compose only), the hosted GitHub App for private repositories, per-session backend switching in the UI, serving share pages from tenant-foreign buckets, metering local sessions (local compute and subscription inference are the user's own), and running Claude Code as an autonomous MCP-tooled agent. Each is named where it was deferred.

On this page