Which machine does an AI agent run on?
Every sandbox starts through a runner, even on a single machine. How covey picks the host, what tags exclude, and what a runner has to see to start one.
Ten agents wake at the same time. Each gets a container, and each container wants memory, disk and a couple of cores. The control plane runs on that same machine, with Postgres beside it.
The question is where the eleventh one starts. Under a product launch on Hacker News in May 2026, a developer reported many requests to run the agent on a separate box (comment 48239451). Verbatim: "in the cloud or some pc gathering dust in the corner".
Covey answers that with a runner: a process on an arbitrary host that registers with the control plane and gets sandboxes assigned from there, the same pattern as GitLab runners. A score decides which host gets it.
Every sandbox starts through a runner, even on a single machine
Install covey on one machine and you still have one; covey serve runs a built-in runner inside its own process, one per organisation. The setup stays what it was, and the registration token, the configuration file and the second artefact all fall away.
The built-in path and the remote path are the same code. What separates them is a transport: a channel pair in one case, a WebSocket in the other. A built-in runner that reached past the protocol for the Docker CLI would leave the remote path unexercised in almost every installation.
A second host registers and says what it is
A registration token is created in the interface and holds for 24 hours (RegistrationTokenTTL in internal/runner/store/store.go line 345). Three lines run on the new host: the install script from your instance, covey-runner register with the address and the token, then covey-runner run. register writes the server address, the runner token and the working directory into /etc/covey-runner/config.toml with mode 0600, and the database keeps that token as a hash.
At registration the host also says what it is and what it can carry. A tag such as --tag arm64 says what it is. max_sandboxes = 4 caps how many sandboxes it carries at once, and the default 0 leaves it unbounded.
Both sides hold the cap.
The scheduler stops picking a host that is full, and the host refuses a start beyond its limit itself, naming the running count and the cap in its log. Only the host knows what is actually running on it, and only it falls over when that number is wrong.
The choice is a score, and only the tags exclude
A host becomes a candidate only if it belongs to the organisation, is connected and carries the agent's tags. The ranking after that is an addition in internal/runner/pool.go, lines 1590 to 1624: the host the agent last ran on gets two points off, a host that already holds the image gets one.
Equal scores go to the host with the fewest running sandboxes.
The larger discount goes to the last host, where its working copy of the home lies. Pulling an image costs seconds. Materialising a 7 GB home out of the store costs minutes.
Only tags keep a host out.
Once it was otherwise: a host reported the images it held and got only matching agents. In August 2026 one registered host claimed covey-sandbox:latest, while the agents of that organisation needed the deploy image, and the candidate list came up empty. Every wake failed for half an hour, every thirty seconds, beside a machine that could have carried the work.
So the rule that replaced it tells two states of a host apart. A tag says what a host is, and no other machine can stand in for that. An image is a state that repairs itself: docker run fetches what is missing.
The built-in runner is asked last
That ranking adds ten points to the built-in runner.
As long as a registered host can carry the work, it carries it. When it does step in, the log says so (no connected runner fits — the built-in one takes it), with the image, the tags and the reason. The pick runs again after that, so tags exclude it like any other host. An agent that asks for arm64 waits for a host with arm64.
Whoever wants no compute on the control plane's machine pauses it. A pause keeps the token, the tags and the working copies, and lets what is running finish; new sandboxes go elsewhere. COVEY_BUILTIN_RUNNER=off is the same sentence in the instance's configuration.
It used to stand down as soon as an organisation registered a host of its own. That rule inferred an intention from an event, and it cost covey.work its data plane twice in one afternoon. A pause says the same thing, set by a person and readable in the runner view.
A runner sees the tokens of the agents it carries
With every start it receives that agent's daemon and egress token, to inject both into the container. With those it can impersonate every agent it hosts, the same trust level as a CI runner that sees job tokens.
So the documentation draws the line in one sentence: a runner is trusted infrastructure of the organisation. Bringing untrusted compute in through it gives that line up. The database and the object store credentials stay out of its reach, and the brokered credentials of a target system travel directly to the daemon. The runner dials out, so no port has to be open for it, and TLS is mandatory as soon as it runs on a machine other than the control plane's.
When no sandbox starts, the message names the case
Several cases get their own message: no runner in this organisation, none carrying the tags asked for, all of them paused, or all of them at their sandbox limit. Only the last describes a data plane that works, with a queue in front of it.
So it gets its own sentence in the code.