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:
| Column | Meaning |
|---|---|
id, tenant_id, created_at | identity and ownership |
repo_url | the onboarded GitHub repository |
bucket_endpoint, bucket_name, bucket_prefix | where this project's artifacts live |
credentials_ref | reference 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.