Tier model
The five agent tiers, what each one runs on, what it costs, and the rules that govern them.
A tier is a fixed bundle of four things: capability, isolation boundary, start-latency class and price. They move together. You cannot buy tier 4 capability at tier 1 isolation, and you cannot get tier 1 latency out of a tier 4 runtime.
Tiers 0–3 are built and confirmed live. runc, gVisor, and Kata (a real microVM) are strictly
increasing isolation; Tier 3's browser capability runs on that same Kata runtime as Tier 2 — not a
separate isolation tier, confirmed by actually launching Chromium through the mandatory egress proxy,
on both the dev cluster and production. Tier 4 is this site's product vision, not the current
engineering plan. The engineering side's own tier decision (ADR-0013 in workspaces-build-compute,
final, nothing supersedes it) is four tiers, not five: T0 Tool agents (runc), T1 Code agents
(gVisor), T2 Workspace agents (Kata, browser included in its own capability list), T3 Machine agents
— full cloud VMs via a provider SDK, outside Kubernetes, for GPUs and long-lived workloads. There is
no tier matching this page's Tier 4 in that plan at all. See Tier
2, Tier 3 and Tier
4 for the detail.
The five tiers
| Tier | Agent type | Capability | Isolation | Start latency | Relative cost |
|---|---|---|---|---|---|
| 0 | Lightest compute | Your own code, same capability as every tier | Plain runc container, shared kernel | Fastest | 1× |
| 1 | Code agent | Python or Node evaluation; a shell command run directly works, but that code can't spawn its own subprocess | gVisor container | Sub-second | ~8× |
| 2 | CLI agent | Shell, any binary, workspace filesystem | MicroVM per run | Sub-second | ~25× |
| 3 | Browser agent | Headless browser, DOM automation — Tier 2's own runtime, confirmed live | MicroVM per run (same as Tier 2) | Sub-second | ~25× |
| 4 | Computer-use agent | Full desktop, screen, keyboard and mouse | Full VM from a warm pool | Seconds | ~150× |
Relative cost is per unit of wall-clock time at the same nominal resources, normalised to tier 0. Actual rates are in the rate card.
Start-latency targets
These are the numbers the warm-pool design exists to hold. "Warm" means the session was claimed from a pool; "cold" means it had to boot.
| Tier | Warm p50 | Warm p95 | Cold p95 | What dominates the cold path |
|---|---|---|---|---|
| 0 | 5 ms | 25 ms | 200 ms | A plain runc container — the lightest thing this platform schedules |
| 1 | 90 ms | 250 ms | 900 ms | gVisor sentry start, rootfs overlay mount |
| 2 | 180 ms | 600 ms | 2.5 s | MicroVM snapshot restore, or kernel boot plus init |
| 3 | 180 ms | 600 ms | 2.5 s | Same runtime as Tier 2 — Chromium's own start time is additional, on top of the session boot |
| 4 | 2.5 s | 6 s | 45 s | Full VM boot, desktop session start, display attach |
Cold is not a failure, it is the fallback. The fleet is sized so cold starts stay under a few percent of session creates per tier; see capacity planning.
What each tier can and cannot do
| Capability | T0 | T1 | T2 | T3 | T4 |
|---|---|---|---|---|---|
| Evaluate Python or Node | ✅ | ✅ | ✅ | ✅ | ✅ |
| Install packages at runtime | — | — | — | — | ✅ |
| Run a shell command directly | ✅ | ◐ | ✅ | ✅ | ✅ |
| Arbitrary binaries | ✅ | ◐ | ✅ | ✅ | ✅ |
| Persistent workspace filesystem | — | ◐ | ✅ | ✅ | ✅ |
| Outbound network | ◐ | ◐ | ◐ | ◐ | ✅ |
| Inbound exposed port | ✅ | ✅ | ✅ | ✅ | ✅ |
| Headless browser | — | — | ✅ | ✅ | ✅ |
| Graphical desktop, screenshots, input | — | — | — | — | ✅ |
| Memory snapshot and restore | — | — | ✅ | ✅ | ✅ |
| GPU attach | — | — | ◐ | ◐ | ◐ |
✅ available · ◐ available under policy, or narrower than the row name suggests — see the tier's own page · — not available at this tier
T2 and T3 are the same row for every capability above except one: "headless browser" is the only thing that actually distinguishes T3 from a plain T2 session — same runtime, same resource model, same egress rules, just a Chromium workload instead of a generic one. Confirmed live, twice (dev cluster and production): see Tier 2's browser recipe.
A platform-managed tool or MCP catalog is explicitly out of scope for this product, at every tier — this is a compute provisioner, not an agent/tool-management layer (ADR-012 in the decision log). Your own code can call whatever tools or MCP servers it wants to, the same way it can on any tier; the platform just runs the code.
T1's ◐ on shell/binaries means a command run directly (not from inside running code) works fully;
what's actually blocked is code already running inside T1 spawning a subprocess of its own — see
Tier 1. "Install packages at runtime" is — across T0–T2 by design (the
standard runtime images have pip/npm/apt/compilers removed, not merely discouraged) — bring a
custom image with dependencies already baked in instead.
Rules that hold across all tiers
Tier is declared, never inferred
The caller sets tier on POST /sessions or POST /runs. The platform does not guess a tier from
the payload and does not promote a session that turns out to need more. Asking for a tier this
deployment doesn't offer fails outright, not with a silent fallback to one that is:
{
"type": "https://docs.workspaces.build/api/errors#tier_unavailable_in_region",
"title": "Tier not offered",
"status": 422,
"detail": "Tier 3 is not offered on this backend."
}
This is deliberate. Implicit escalation would move a workload across a security boundary and a price boundary without the caller ever deciding to, which is unacceptable on both counts.
Isolation is monotonic
Every tier's boundary strictly contains the one below it in attack surface terms. Tier 1 code cannot reach the host kernel directly; tier 2 code cannot reach the host kernel at all; tier 3 adds a mandatory network chokepoint; tier 4 adds full device virtualisation with no co-tenancy on the physical core. See isolation model.
Cost is dominated by the boundary, not the work
A tier 4 session doing nothing costs far more per second than a tier 1 session running flat out, because the price pays for a reserved, warm, non-oversubscribed full VM. This is why tier selection matters more to a bill than optimising the agent's code.
The single highest-leverage cost decision in an agent product is not model choice or prompt length. It is not running a browser when a HTTP request would do, and not running a desktop when a browser would do.
Every tier is metered the same way
Tiers differ in rate, not in mechanism. All five emit usage events into the same pipeline, on the same schedule, with the same dedupe key, and are visible in the same usage API. See metering model.
Execution mode: one_shot vs long_running
Every session also declares execution_mode, independent of which tier it's on — the two axes
compose freely, tier picks the isolation boundary, execution_mode picks the lifecycle shape.
one_shot | long_running | |
|---|---|---|
| Shape | Boot, run once, release | Stays up until stopped or max_lifetime_s |
Declares expose_ports | Rejected — nothing stable to expose a port to | Yes, up to 4 |
Detached process (/start) | Also rejected today | Yes |
Still needs an explicit stop | Yes, today — see below | Yes |
The auto-release-on-exit behaviour the name implies — stop the instant the process exits, no
stop call needed — is a designed, not-yet-built increment. Until it lands, one_shot exists to
declare forward-looking intent and to reject expose_ports//start; it does not yet skip the
explicit stop a long_running session also needs. Default to long_running unless the thing
you're testing is specifically the boot → run → stop shape itself.
curl -sS "$WB_BASE_URL/sessions" -H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "tier": 0, "execution_mode": "one_shot", "runtime": "python3.12" }'
# run once, then:
curl -sS -X DELETE "$WB_BASE_URL/sessions/$SESSION" -H "Authorization: Bearer $WB_API_KEY"
Full detail: Sessions.
A declared port, start to finish
A worked example tying expose_ports, a detached process, and the two ways to reach the port
together — the shape that's actually been exercised end to end, on Tiers 0–2:
# 1. boot long_running, declaring the port up front -- fixed for the session's life, not addable later
curl -sS "$WB_BASE_URL/sessions" -H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "tier": 0, "execution_mode": "long_running", "image": "'"$IMAGE"'",
"expose_ports": [{ "port": 8080, "protocol": "http" }] }'
# 2. start the process that listens on it -- detached, outlives this request
curl -sS "$WB_BASE_URL/sessions/$SESSION/start" -H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "argv": ["python3", "-m", "http.server", "8080"] }'
# 3a. reach it under this API's own domain -- always available, your own credential
curl -sS "$WB_BASE_URL/sessions/$SESSION/proxy/8080/" -H "Authorization: Bearer $WB_API_KEY"
# 3b. or mint a capability URL on the port's own subdomain instead
curl -sS "$WB_BASE_URL/sessions/$SESSION/proxy-url/8080" -H "Authorization: Bearer $WB_API_KEY"
# -> { "url": "https://sess-<hash>.sandboxes.workspaces.build/?t=<token>" }
Declaring the same port "public": true instead mints a URL with no token at all — reachable by
anyone who has it, for exactly as long as the session runs, nothing to rotate or refresh. Mark a port
public deliberately, not as a default — it trades away the only thing that makes the signed form a
real credential. Full detail, including the token's revocation model: Filesystem and
ports.
Choosing between tiers
Choosing a tier has the decision table, the common mis-selections and what they cost.