Agents run in containers, on runtime configurations their sponsors bring
Agents ran on the server's host with full trust (ADR 0004), on agent runtimes the organization set up, signed in with the host's own CLIs (ADR 0006). That gave agents no isolation, and it put everyone's work on one sign-in, which subscription terms do not allow once several people share a server. We decided that people bring the sign-ins: each person, owner or member alike, sets up runtime configurations of the runtime kinds the server offers, with their own credentials, a model, the people and roles each serves, and a priority. Every run is for a sponsor, the last person who gave its task to a role, and starts its agent in an ephemeral Docker container on a configuration that serves that sponsor and the role. This pool of configurations is the only way agents run: organization agent runtimes and running on the host go away.
How a run finds its configuration:
- A role may require a runtime kind and a model. A configuration qualifies when it serves the sponsor and the role and is of the required kind; the role's model replaces the configuration's.
- The sponsor's own configurations come first, by priority, then those shared with them, by priority and then age. Priority only orders one owner's configurations: one owner's number means nothing next to another's.
- A run nothing serves waits, shows why on its task, and starts on its own once a configuration serves it, as a blocked task does once its last blocking task is finished.
- Any person can make themselves a task's sponsor, so a task waiting on someone with no configurations can run on theirs. A conversation's sponsor is always its person; to cover someone's conversations, a configuration is shared with them.
What an agent runtime is:
- The agent image, one for every kind: each kind's CLI, Mission Control's bridge, git, common
toolchains and
mise. By default it is the one published with the server's own release, tagged with its version under a name built into the server, so a server starts and runs agents with no configuration of its own; the version ties the bridge in the image to the server that drives it. The operator can name another (MC_AGENT_IMAGE), builtFROMit. Its entrypoint is the bridge, speaking ACP on stdio as runtimes always have, so runs start it withdocker run -ibehind a launcher and drive it as before. - Mounted into it: the run's working directory, a self-contained clone; the run's directory,
with its skills; a home directory for the run, kept in the run's directory, where the runtime
keeps its sessions; and Mission Control's Unix socket. The bridge listens on
127.0.0.1inside the container and forwards to the socket, so the agent reaches Mission Control's tools at a plain local URL. - The configuration's credentials, as variables. Each kind declares the values it takes.
- It lives as long as an agent process did: ended after the idle timeout, and started again, with the same home, when the run continues, so its session resumes.
Considered Options
- People's own machines contributing agent runtimes (a
mission-control acp-clientservice connected over WebSocket): the first draft. Rejected; what is needed is credentials, not compute, and a runtime on another machine cannot see the run's working directory, which is on the server. The bridges we ship do not send files or commands back over ACP, so it would mean syncing directories or moving checkouts and git to the client. - A pool ACP server in its own package, given the sponsor and role in
_meta, choosing and starting runtimes: rejected; choosing needs sponsors, sharing, roles and waking waiting runs, all Mission Control's data, and nothing else would use the boundary. - The sponsor's configuration alone deciding, with roles saying nothing: rejected; organizations want to choose what a role runs on, such as a model per role, and instructions are often written for one kind.
- One list per person ordering their own and shared configurations together: rejected; everyone would have to keep it in order, and their own first is what people expect.
- Failing a run nothing serves: rejected; nothing changes until a person adds or shares a configuration, so failing only adds a step, and a routine's tasks would fail each time it is due.
- Keeping worktrees and mounting their bare clones: rejected; a checkout made during a run needs
its bare clone mounted too, so in practice all of
git/repositories, and a bare clone is shared by every organization using its repository, unpushed commits included. - Reaching Mission Control at the Docker host gateway or
MC_PUBLIC_URL: rejected; on Linux the gateway needs the server listening on the Docker network, and the public URL must be set and reachable and goes out and back in. Runtimes only reach MCP servers over HTTP, hence the forwarder rather than handing them the socket. - Keeping a container until its run ends, or never resuming sessions: rejected; runs wait for days, and the prompts that continue a run assume the session it had.
- A sign-in directory per configuration, kept between runtimes: deferred; sign-ins that refresh themselves (Codex with ChatGPT) need it, along with somewhere to sign in and care for runs that refresh at once.
- An image per kind: rejected; more to publish and pull, for kinds that differ only in their command, their values and what they mount.
- The server building the image on first start: rejected; on macOS the server's own executable is no Linux one to put in it, so the image's parts would be downloaded anyway, by every server.
- Images an organization or project names, built
FROMthe agent image: deferred, as the next step. ADR 0006 refused programs organizations choose because they ran on the host as the server's user; in a container that reason is gone. - The repository's devcontainer: rejected for now; a heavy build per repository, and a format to support.
Consequences
- This replaces ADR 0006 and ADR 0004's running on the host. Agents are kept from the host and from each other's work, and still approve every permission request, now inside their container. Runtime kinds stay in server code: organizations choose neither a program nor an image. Docker is required to run any agent.
- Each release publishes the agent image beside its executables, for arm64 as well as amd64, since
servers run on both. Where it is built is open: the tag pipeline (the forge's
kaniko-buildbuilds one architecture at a time), a GitHub mirror publishing to ghcr.io, or by hand until either works; the registry it lands in is the name built into the server. A development server has no release of its own, so it builds the image from the repository and names it inMC_AGENT_IMAGE. - It revises ADR 0007: working directories and their submodules become clones that hard-link their bare clone's objects. Mission Control fetches and pushes from them on the host with the forge's token, which agents still never get.
- Configurations hold plain values only, write-only (ADR 0002). Claude Code takes an API key or a
token from
claude setup-token; Codex an API key. Codex's ChatGPT sign-in waits for sign-ins that refresh, and Oh my pi for handling its own authentication broker. - A configuration belongs to a person within one organization; someone in two sets up each.
- Whoever owns the configuration a run used pays for it; the sponsor is who it ran for. A configuration serves only its owner until they share it, so sharing a personal subscription is always its owner's choice.
- A task records its sponsor. A comment does not change it; whether a commenter should become the sponsor is left open.
- Decrypted MCP server headers and environment variables taken from secrets reach the container, as they reached the agent process (ADR 0018).
- Containers need outbound network access, to model APIs and organizations' MCP servers.
- A run that continues on a configuration of another kind starts a new session, as one did on another agent runtime.
- Mission Control serves its API on a Unix socket as well as over HTTP. Corrected when it was built:
where Docker runs in a virtual machine, as Docker Desktop and Colima do on macOS, a host's Unix
socket cannot be mounted into a container at all. There containers reach the server's port on the
Docker host (
host.docker.internal) instead, the default off Linux and chosen withMC_AGENT_API; the bridge forwards the same local port to either, so agents see no difference. The server already listens on every interface, so the gateway needs nothing new of it. - An organization's agent runtimes become requirements on the roles that used them, their kind and model, the default's on the roles that used the default. No sign-in carries over from the host, so runs wait until people set up configurations.