Skip to main content

Sandbox lifecycle

Workloads on this platform are not meant to run continuously. A sandbox exists while its task does; an MCP server can be started on first use and stopped once nobody is calling it. This page covers how they come up, and how they go away. Reclaim is a security property as much as a cost one. A workload that runs forever is a workload whose compromise lasts forever.

The problem

Starting a container on demand is slow in a way that agents feel. Pulling an image, unpacking it, and starting a process takes seconds; an agent may make dozens of tool calls in one task, and a user is watching. Paying that on every call is not viable. Never stopping anything is the other extreme, and it is what the platform did originally. A workspace with thirty MCP connections ran thirty containers whether or not an agent had called any of them that month. The cost is continuous, the utilization is near zero, and each idle container is still a piece of third-party code with a network connection. The two workloads also differ in what stopping them costs, which is why they get different treatment.

How AgentArea approaches it

The warm pool removes provisioning from the request path

Rather than creating a pod when work arrives, the platform keeps a set of pods already running and unassigned. Assigning one is a label update, so the expensive part — scheduling, image pull, container creation — happened before anyone was waiting. A pooled pod is not yet the workload it will become. It runs an activation service listening on port 8080. Activating it posts the target image, and the service fetches and extracts that image into an overlay directory, reads the image config for its ENTRYPOINT, CMD, and WORKDIR, and starts the process under chroot into the extracted root filesystem, so relative entrypoints behave as they would under a container runtime. Pods move through recorded states: waiting, activating, ready, assigned, then idle once the work is over. Activation endpoints are authorized with scoped tokens — separate scopes for activate, execute, writeback, and files — signed by the control plane, so a pod’s executor cannot be driven by anything that has not been issued the right scope. Runtime policy is deployment-owned. The built-in Kubernetes provider may use a warm pool, while external providers allocate their own sessions. In every case, the Go manager chooses exactly one configured provider and runtime policy; task payloads do not carry an image or package-install profile. If the selected provider cannot attest the required isolation boundary, creation fails loudly.

Sandboxes are reclaimed from observed HTTP activity

The Go manager is the lifecycle authority. A command or file request provisions or reuses the task binding and renews its active lease; command heartbeats keep that lease alive. When the operation finishes, the manager moves the binding to SANDBOX_TASK_IDLE_TTL. A later request wakes or recreates it and materializes the durable workspace. No Temporal activity names or invokes sandbox creation, retirement, or cleanup, so switching data-plane providers does not change the agent workflow.

MCP instances can be serverless, because stopping them costs nothing

An MCP instance is stateless with respect to startup: it is an image plus environment variables from the database. Stopping and restarting it returns the same instance, so reclaiming it is lossless. That is why serverless is available for MCP and not, currently, for sandboxes — a sandbox holds a working directory that dies with the pod. When serverless is on, an instance is started on its first call and stopped after it goes idle. Only the workload goes; the database row, the credentials, and the discovered tool list are untouched, and the next call provisions it again through the path that started it the first time. Reclaim works by sweep. Every sweepInterval (default 60s) the manager selects instances that are lazily provisioned, currently provisioned, and have not been called for longer than idleTimeout (default 10m). It stops each workload and resets the instance’s verification state so the next caller provisions it rather than dispatching into nothing. That reset is a compare-and-set against succeeded, so a verification that started concurrently wins instead of being overwritten. Sweeping is serialized across manager replicas with a Postgres advisory lock. If a manager dies mid-sweep the lock goes with its connection; there is nothing to clear by hand. Two categories are deliberately excluded. Remote (url-type) connections have no container to stop. Instances that have never been called are treated as new rather than idle — reclaiming requires evidence of disuse, not absence of evidence of use. The mode is recorded per instance at creation. Turning serverless on later does not retroactively shorten the life of existing connections, and turning it off does not strand ones created serverless.

Why not keep everything running

Always-on is predictable. No cold starts, no first-call latency, no question of whether the wake path works, and verification failures surface when a user creates the connection rather than when an agent first calls it. The platform’s position is that this does not survive multiplication. Utilization of a typical MCP connection is very low, and the resident cost is paid continuously by every workspace. The security argument is the stronger one: an idle container is still attack surface with a network path, and reclaiming it shrinks the window in which a compromised or vulnerable server is reachable. The costs are real and are not defects to be fixed. The first call after an idle period pays a cold start whose length depends entirely on the server — a small published image starts quickly, while a uvx or npx server that clones and installs on boot can take substantially longer, and the call waits rather than failing. And because verification is what starts the container and lists its tools, deferring the start defers the check: a bad image reference or a missing environment variable surfaces on first use instead of in the connection form. Serverless is off by default for these reasons.

Why not scale to zero with a queue in front

The tidier design gives each MCP server a queue and a scaler, so requests buffer while the workload comes up, and there is no user-visible failure mode at all. It adds a broker per connection and moves failure from “the call was slow” to “the call is somewhere in a queue”, which is harder to reason about and harder to attribute when an agent’s tool call hangs. It also does not remove the cold start; it hides it behind a component that now has to be operated and monitored. The current design accepts the visible wait in exchange for one fewer moving part per connection, on the reasoning that an agent tool call is already an operation where a several-second wait is normal.

Limits

Reclaim does not observe agent traffic. The idle sweep selects on last_used_at, and that column is written in exactly one place: the HTTP proxy path used by registered CLI harnesses. The agent tool-call path resolves the instance URL and calls the container directly, then enqueues a usage record onto an in-process queue. The consumer of that queue, _flush_last_dispatch_loop, has no production caller anywhere in the tree, and it writes a different column (last_dispatch) in any case. Two consequences, both bad:
  • An instance used only by agents keeps last_used_at as NULL, is excluded by the selection query, and is never reclaimed — the main scenario, in which nothing is freed.
  • An instance called once through the proxy and thereafter only by agents has a timestamp that goes stale under live traffic, so the sweep can stop an instance that is actively in use.
A comment in the proxy describing it as the only component that observes MCP traffic is inaccurate, and the existing serverless page inherited that error. A serverless instance may not be callable at all. Verification is skipped when a lazy instance is created, and the tools column is written only by verification. Tool discovery has an escape hatch for exactly this case — a lazy instance passes if it declared its tools in advance — but nothing populates json_spec.available_tools: its only writer, set_available_tools, has no callers. The tool list is therefore empty, the escape hatch does not trigger, the agent is offered no tools from that instance, and it never makes the first call that would wake it. Stopping races with waking. The sweep selects, deletes, then resets state. A caller that has already read the instance as provisioned can dispatch to a Deployment deleted moments earlier and get an error with no retry. Each reclamation under live traffic can cost a failed tool call. The reaper does not start if the database is not configured, and says so only at warning level. A manager without database credentials logs a warning and continues without reclaiming anything. A live task’s sandbox is not reclaimed when idle. Only end-of-task teardown and lease expiry release a sandbox. An agent holding an open task that does nothing for twenty minutes keeps its pod for up to the full lease, two hours by default. The warm pool is off by default. mcpManager.warmPool.enabled is false, and the pool also requires the warm_pool feature flag. Without it, sandbox pods are created on demand and pay full provisioning, and the sandbox NetworkPolicies described in isolation are not rendered. Activation falls back to running without chroot. If chroot fails — for example without CAP_SYS_CHROOT — the activation service logs a warning and starts the image’s entrypoint directly in the pod’s own filesystem, rather than inside the extracted image root. The comment describes this as expected under a VM-isolating runtime where the VM boundary substitutes, but the fallback fires on any chroot failure, including one where no such boundary exists. Published activation timings are not reproducible from this repository. The existing warm pool and serverless pages quote several different figures for activation and cold start, none citing where they were measured, and the chart comments give a different range again. Treat them as illustrative and measure your own deployment.