Services, sandboxes, and sessions
Nimbus models long-running and interactive workloads with three nouns: services (named capabilities your app depends on), sandboxes (isolated execution environments), and sessions (scoped, expiring connections to a running service or sandbox). This page explains what each noun means and why the model is shaped the way it is. For the hands-on walkthrough — creating, starting, and connecting to each with the JavaScript SDK — see the Agents guides.
Three nouns, three addressing rules
Section titled “Three nouns, three addressing rules”The model is intentionally asymmetric about how things are named:
- Services are addressed by name, scoped to a tenant. A name like
searchorworkeris part of your application’s dependency contract — stable across restarts, redeploys, and backend changes. - Sandboxes are addressed by id. Creating a sandbox returns a handle; every later operation — get, list, stop, open a session — targets that id. Sandboxes can carry labels for filtering, but there is no resolve-sandbox-by-name API.
- Sessions target exactly one of the other two. A session opens
against
{ service: { name } }or{ sandbox: { id } }— never both, never neither — and is itself addressed by the id the server returns.
This asymmetry is the core of the model. A name is a promise that other code can depend on; an id is a receipt for a resource you created. Keeping them distinct means a throwaway execution environment never silently becomes something other code can resolve, and a named dependency never depends on which particular instance happens to be running behind it.
Services: named capabilities with a chosen backend
Section titled “Services: named capabilities with a chosen backend”A service is a tenant-scoped definition: a name, a backend, labels, and a generation counter that guards concurrent edits. The backend tells Nimbus how the capability is provided, and there are three kinds:
- Sandbox-backed — Nimbus launches and supervises a sandbox from the
spec embedded in the definition. This is the workhorse backend: the
lifecycle verbs (
start,stop,restart, readiness waiting) operate on sandbox-backed services, and it is also how Compose files lower — each Composeservices:entry becomes a sandbox-backed service definition in the tenant’s catalog. - Built-in — the capability is implemented by the Nimbus server itself. These are a small fixed set — for example a browser service and a model gateway.
- External — an endpoint Nimbus does not run. The definition records
an absolute
http(s)URL (embedded credentials are rejected), an auth policy, and an HTTP health-check path. Nimbus owns the definition, validation, and authorization around the endpoint; the process behind it is yours.
Today only sandbox-backed services can actually be launched by the service
manager; built-in and external services exist as validated definitions and
report a declared lifecycle state rather than a running one.
Two distinctions worth internalizing:
- A service is not its sandbox. The service is the name, readiness contract, and policy surface; the sandbox is the isolation mechanism currently running it. When a sandbox runs on behalf of a service, its spec carries owner metadata naming that service, and launch is rejected if the owner metadata and the service name disagree — but the sandbox’s id never becomes the way applications address the capability.
- Definitions come from two sources. Services declared statically (such as those lowered from Compose) and services created dynamically through the API are the same resource kind with the same verbs. Dynamic definitions additionally carry generation-checked update and delete semantics, so two writers cannot silently overwrite each other.
Sandboxes: isolated execution environments
Section titled “Sandboxes: isolated execution environments”A sandbox is a single isolated world: a root filesystem and a process to run, created for one purpose and addressed by id for its whole life. Its resource limits, mounts, and deny-by-default network egress are set by the server, not the caller — the public create spec answers two separate questions:
- What root material runs? Either a prepared root filesystem or OCI image material — and image material is in turn obtained either by reference (a registry image) or by build (a Dockerfile and context). A build is a way to obtain an image, not a different kind of sandbox.
- Who owns it? A sandbox is either owned by a service (launched as that service’s backend) or standalone (created directly, with an optional display name). Creating a standalone sandbox never implicitly registers a service name.
Sandboxes are created under a profile — currently worker or
desktop — which states the intended workload shape, and run on one of
two isolation backends: an OCI container backend (driven through crun,
the default for standalone sandboxes) and a libkrun-based microVM backend
that executes workloads on Linux with per-sandbox egress enforcement and
is the default for services run through nimbus compose. The important design
point is that the backend is a property of the deployment, not of the
resource: the same spec, id, lifecycle, and session semantics apply
regardless of which isolation mechanism is underneath.
Whatever the backend, sandboxes inherit the tenant-isolation posture: launches are validated against the admitted tenant decision, outbound network access is deny-by-default, and the API redacts launch inputs (command lines, environment values, build paths) when it returns a sandbox, reporting only value counts. See Tenant isolation for the full model.
Sessions: scoped, expiring connections
Section titled “Sessions: scoped, expiring connections”Simple service use needs no session — start the service, let your app use the named dependency. A session models the interactive cases — a shell or stdio stream into a running workload, file exchange, or a browser-control channel — as a lease, not a registry entry. The session lifecycle and channel declaration are what the SDK and HTTP surface expose today; byte transport over those channels is not yet wired:
- Every session expires. Opening one takes an optional requested TTL;
the server applies a bounded default TTL with a hard cap. Sessions move
through
open,closed(explicitly closed, with a recorded reason), andexpiredstates. See the resources reference for the session lifecycle. - Channels are declared up front and validated against what the
target can actually offer: sandbox targets and sandbox-backed services
support
stdioandfiles; the built-inbrowserservice supportscdpandpage; other built-in providers and external services expose no session channels at all. - The target is pinned at open time. The response carries a target
snapshot — the service name or sandbox id, its generation, and its
backend — so you can always tell exactly what a session attached to,
even if the definition changes later. Opening against a dynamic service
whose definition changed mid-open is rejected as a conflict rather than
silently attaching to something else, and a sandbox target must be in
the
readystate. - Opening is an authorization event. An application caller opening a service-targeted session needs an exact grant for that service name; a sandbox-targeted session needs reach to that specific sandbox id. Session opens, lookups, and closes are recorded as audit events.
How the pieces compose
Section titled “How the pieces compose”The flow for a Compose-style dependency ties all three together:
service name ("db") -> service definition (sandbox-backed) -> sandbox launch (owner: service "db") -> ready service, addressed by name -> optional session (stdio/files), addressed by session idFor agentic and long-running workloads, the model gives you a default shape: reach for a standalone sandbox when an agent needs an isolated world for one task — it has an id, a lifecycle, and session access, and it disappears without leaving a name behind. Promote work to a service only when other code should be able to depend on it by name. Open a session when something needs to interact with a running resource — stream output, exchange files, drive a browser — under a lease that expires on its own instead of an ambient connection that outlives its purpose.
What is not a resource
Section titled “What is not a resource”Ordinary Nimbus functions run in V8 isolates inside the server process — see How the Node runtime works. Those invocation isolates are execution machinery, not resources: they have no name, do not appear in sandbox listings, and cannot be a session target, even though isolate execution is an isolation mechanism in the general sense. The resource model covers things you create and address; function invocation stays an internal concern of the engine.
The same boundary applies to compatibility surfaces: adapter contexts
(Convex, Cloud Functions, and the other compatibility APIs) do not grow
ctx.services-style shortcuts. Code running on those surfaces that needs
Nimbus resources imports the @nimbus/nimbus SDK explicitly, and the same
authorization applies regardless of where the call originates.
Related pages
Section titled “Related pages”- Agents — quickstart and how-to guides for every verb described here.
- Tenant isolation — the admission and enforcement model these resources run inside.
- Manage tenants — operating the isolation boundary.
- HTTP API reference and error reference — the wire surface and error catalog beneath the SDK.