Decision log
Architectural decisions, the alternatives considered, and why each one was chosen.
Each entry records a decision that would be expensive to reverse. New entries are appended; existing entries are superseded rather than edited.
ADR-001 — Five tiers, declared not inferred
Date: 2026-10-06 · Status: Accepted
Context. Agent execution spans in-process tool calls to full desktops. One runtime cannot serve that range at a sensible cost or a defensible boundary.
Decision. Five tiers, each a fixed bundle of capability, isolation, start-latency class and price. The tier is a required field on every create. No implicit escalation.
Alternatives.
- One runtime with feature flags. Simplest API; worst security (one boundary for everything) and worst economics (tool calls pay desktop prices).
- Infer the tier from the request. Convenient; means a caller can change which security boundary their code runs in by changing nothing, and can change their bill by an order of magnitude the same way.
- A continuous capability vector rather than discrete tiers. Expressive; makes warm pools impossible, because pools are keyed by a discrete combination.
Consequences. Callers must choose, and sometimes choose wrong. Mitigated with
choosing a tier and a tier_capability_required error naming the tier
needed. The payoff is that the boundary and the price are both predictable.
ADR-002 — gVisor at tier 1, microVMs at tiers 2 and 3
Date: 2026-10-06 · Status: Accepted
Context. Tier 1 runs model-authored code, which is attacker-influenced by construction, and must start in under 250 ms p95.
Decision. gVisor for tier 1; Firecracker-class microVMs for tiers 2 and 3.
Alternatives.
- Plain containers at tier 1. 50 ms starts; the boundary is the full Linux syscall surface, which is not defensible against untrusted code.
- MicroVMs at tier 1. Stronger; 3–5× container start time, against gVisor's 2–3×, and much lower density. The latency and cost that define tier 1 would both be lost.
- WebAssembly at tier 1. Excellent isolation and start time; the ecosystem gap for Python and Node is too large for the workloads that matter.
Consequences. A sentry bug combined with a seccomp bypass reaches the host kernel at tier 1. Accepted, documented in isolation model, with a 24-hour patch target. Customers needing hardware virtualisation for code execution use tier 2.
ADR-003 — Warm pools keyed by discrete size classes
Date: 2026-10-06 · Status: Accepted
Context. Cold starts are 11–33× warm starts. Hit ratio is the metric that determines whether a tier's latency promise holds.
Decision. Pools keyed by (tier, template@digest, size_class, zone). Resource requests are
rounded up to a discrete size class and billed on the class.
Alternatives.
- Continuous sizing. Precise billing; every request is a unique pool key, so nothing is ever warm.
- Resize a pooled instance on claim. Appealing; memory ballooning is slow and unreliable enough to defeat the point of claiming.
Consequences. A customer requesting 3 vCPU gets and pays for 4. Documented, and the alternative is worse in a way customers would feel on every start.
ADR-004 — Provisioning is not billed
Date: 2026-10-06 · Status: Accepted
Context. Cold starts cost real capacity. Someone pays.
Decision. Billing starts when the session reports running. Provisioning, warm or cold, is
free.
Alternatives.
- Bill from acceptance. Simpler accounting; pays the platform more for being slower, which inverts the incentive that the whole warm-pool investment depends on.
- Bill cold starts at a reduced rate. Splits the difference and makes every invoice harder to explain.
Consequences. Cold starts are a direct platform cost, which is correct — they are a platform failure to have a warm instance.
ADR-005 — Egress default-deny through a mandatory proxy
Date: 2026-10-06 · Status: Accepted
Context. Network reach is the exfiltration path for a prompt-injected agent.
Decision. No egress until policy allows it. All egress through a filtering, logging, metering proxy. No bypass.
Alternatives.
- Default-open with a blocklist. Friendlier onboarding; an allowlist is the only posture that works against an adversary who can choose the destination.
- Per-session firewall rules without a proxy. Lower latency; no DNS filtering, no request logging, and no way to meter bandwidth the guest cannot under-report.
Consequences. Every customer configures egress before anything works. Under a millisecond of added latency. The denial log is the best available prompt-injection detector, which is a benefit that was not the original motivation.
ADR-006 — Attribution by end_user_id, organization always the payer
Date: 2026-10-06 · Status: Accepted
Context. Many customers resell to their own customers and need per-seat metering, quotas and caps.
Decision. An optional end_user_id on every create tags the usage. The organization is always
the payer; end users are attribution subjects only.
Alternatives.
- Full sub-account hierarchy. More powerful; vastly more surface, and it would make the platform a billing system for someone else's business.
- Rely on labels. No extra concept; labels are not a billing dimension and have no quota or cap semantics.
Consequences. end_user_id cannot be backfilled, because the association exists only in the
caller's head at request time. Documented prominently in
end-user billing; the usage API exposes an explicit untagged group so
the gap is visible.
ADR-007 — Sessions do not fail over across regions
Date: 2026-10-06 · Status: Accepted
Context. A session is a live machine with live memory and disk.
Decision. A session dies with its region. The failure is explicit in the API and in a webhook. Recovery is the caller's decision, from a snapshot if one exists.
Alternatives.
- Live migration across regions. Transparent; moving gigabytes of state across a WAN mid-action is slow and a residency violation waiting to happen.
- Automatic recreation in another region. Convenient; silently moves data across a boundary the customer may have pinned, and recreates state the agent believed it had.
Consequences. Customers must handle host_failure and decide for themselves. The snapshot
pattern makes recovery seconds rather than a loss.
ADR-008 — OpenAPI is the source of truth
Date: 2026-10-06 · Status: Accepted
Context. Prose documentation and an implementation drift apart. So do hand-written SDKs.
Decision. openapi/workspaces-build.yaml is authoritative. The gateway validates against it,
SDKs are generated from it, the published reference is rendered from it, and a divergence between
spec and implementation is a build failure.
Alternatives.
- Generate the spec from code. Always accurate; the spec becomes a description of what was built rather than a contract agreed before building.
- Hand-written docs only. Faster to write; no machine-readable contract for codegen, mocks or validation.
Consequences. API changes start in the spec. Slower per change, and it keeps the reference honest.
ADR-009 — Metering at the source, deduplicated at ingest
Date: 2026-10-06 · Status: Accepted
Context. Lost usage is unbilled work; duplicated usage overcharges a customer.
Decision. Each resource owner emits its own usage. Nodes write to a local write-ahead log before
acknowledging, and retry until accepted. Ingest deduplicates on
(session_id, meter, window_start).
Alternatives.
- Compute usage centrally from lifecycle events. Fewer moving parts; cannot measure actual CPU or bandwidth, and a lost lifecycle event silently extends a bill.
- Exactly-once delivery. Attractive; expensive and fragile. At-least-once plus an idempotent key achieves the same guarantee with far less machinery.
Consequences. Nodes need durable local storage and a WAL depth alert. A node unable to reach the metering pipeline for 24 hours refuses new placements rather than doing unbilled work.
ADR-010 — Two consoles, not one with a role flag
Date: 2026-10-06 · Status: Accepted
Context. Customers need their own data; platform operators need fleet-wide metadata and must not have content access.
Decision. Two applications on one design system: console.workspaces.build and
ops.workspaces.build.
Alternatives.
- One application with an operator role. Less to build; one authorization bug becomes a cross-tenant leak, and the cross-tenant query paths would live in the same codebase as the customer-facing ones.
Consequences. Two deployments and some duplicated components. The shared design system limits the duplication. The access model stays structurally enforced rather than conditionally enforced.
ADR-011 — Idempotency keys on every unsafe method
Date: 2026-10-06 · Status: Accepted
Context. Agent loops retry constantly. An unsafe retry on a session create is a duplicate billable session.
Decision. Every unsafe method accepts Idempotency-Key, scoped to (api_key, route, key),
retained 24 hours, with the response replayed on a matching retry and 409 on a key reused with a
different body.
Alternatives.
- Server-side deduplication by request fingerprint. No client change; two legitimately identical requests would be collapsed into one.
- Idempotency only on session creation. Covers the expensive case; tool calls with side effects are equally dangerous.
Consequences. Clients should send a key derived from stable application state, not a fresh UUID per attempt — a fresh UUID on each retry defeats the mechanism entirely.
ADR-012 — No managed tool or MCP catalog; Tier 0 is the lightest compute tier, not a tool-call layer
Date: 2026-10-22 · Status: Accepted
Context. Earlier pages (including this one's own ADR-001, and the original Tier 0 framing)
described Tier 0 as a managed tool/MCP catalog: a platform-registered set of handlers an agent calls
by name, with the platform owning invocation, validation and a tool_call meter. Implementation
confirmed this was never built and was explicitly ruled out: this platform is a compute provisioner
of isolation tiers, not an agent, tool-catalog or MCP-management layer. Built reality: Tier 0 is a
plain runc container running the caller's own code, with the same code-execution capability as
every other tier — just the lightest isolation boundary.
Decision. Drop the managed tool/MCP catalog from this product's scope, permanently, not as a
not-yet-built roadmap item. Tier 0 is redefined as the lightest compute tier: a runc container
with a shared kernel, fastest and cheapest, running the same kind: code/kind: command model every
tier uses. Nothing about tool registration, invocation or an MCP server connection pool is owned by
the platform. A caller's own code is free to implement a tool-calling loop or talk to MCP servers
directly — that's ordinary code the platform happens to run, not a platform-managed catalog.
Alternatives.
- Keep the catalog, mark it roadmap. Rejected: unlike Tiers 3/4 (still a live, intended future tier), the catalog isn't a timing question — it's a scope boundary this platform has decided not to cross. Marking it "roadmap" would misrepresent a decided-against feature as eventually coming.
- Keep both framings, let Tier 0 mean two things. Rejected: a tier numbered consistently with isolation strength (T0 < T1 < T2) cannot simultaneously be "no code execution, tool calls only" — the two descriptions are mutually exclusive, not complementary.
Consequences. Tools and MCP and the tool_call/tool_call_ms meters in
Metering are kept as pages, marked as describing a capability this platform
does not provide, rather than deleted outright — removing them would also orphan their generated
OpenAPI reference pages and several cross-links elsewhere in the billing section. Tier
0, Tiers overview and Choosing a
tier are updated to the runc-container framing. ADR-001's own capability
table (tier 0 as "in-process tool calls") is now stale prose; this entry is the correction, per this
log's own rule that existing entries are superseded, not edited.
Template
## ADR-NNN — Title **Date:** YYYY-MM-DD · **Status:** Proposed | Accepted | Superseded by ADR-MMM **Context.** The forces at play. What makes this decision necessary. **Decision.** What was decided, stated plainly. **Alternatives.** Each one considered, with the honest case for it and the reason it lost. **Consequences.** What this costs, including what it makes harder. An entry with only upsides has not been thought through.