Seats: separating agents that share a machine
A seat is a named executor identity assigned to one machine and one execution slot, for one agent backend, with its own configuration home.
Enrolling an OAuth client, not a seat? Which streams a client may read is a separate human act — see the MCP server. Enrolling a client binding requires
--stream, and a binding that carries no grant reads zero rows, not every row.--stream '*'is how you say "the whole fleet", and saying it is deliberate.
A seat is a named executor identity assigned to one machine and one execution slot, for one agent backend, with its own configuration home. Seats are created by an owner act and recorded on the same signed identity log as machines — signed by the root key, or by the identity-signing key the root granted. Each identity holds at most one machine-and-slot pair fleet-wide.
The problem seats solve
Agent backends keep their session state, credentials, authentication tokens and history in a single per-user configuration directory. Run two agents on one machine as the same operating system user and they silently rejoin into that directory: they share sessions, they share credentials, and — the part that surprises people — whichever authenticated last wins for both. There is no error and no warning. You discover it when work appears under the wrong account, or when one session's logout signs the other one out.
A seat gives each executor identity its own configuration home, bound through the environment variable that the specific backend reads for its profile location. In practice this is a soft chroot for agent identity: the same machine, the same user, the same binary — but each seat sees only its own profile, its own credentials, its own session history, and its own authentication state.
What this buys you operationally:
- Several different accounts on one machine, concurrently. Two subscriptions, a personal and a team account, or one account per client — each on its own seat, running at the same time, without touching each other's tokens.
- Different backends side by side. One seat driving one agent tool, another seat driving a different one, each reading its own profile variable.
- No cross-contamination of session state. A crash, a logout, a token refresh, or a corrupted profile is contained to one seat.
- Concurrency you can reason about. Slot count is what a machine advertises; a seat occupies exactly one slot; and a seat is one run — enforced by the database (see Runners), so parallelism is bounded by a number you set (how many seats) rather than by whatever happens to be running. Concurrency is bought in seats, not in sessions: three seats on one machine are three separate accounts running three concurrent runs, and adding a fourth concurrent run means adding a fourth seat. A machine may additionally cap the total across all its slots.
Be precise about how hard this boundary is. It is a soft chroot, and the adjective is load-bearing. What it separates is profile state — configuration, credentials, sessions, history. It is not a kernel boundary and not a separate operating system user. Two seats on one machine run as the same user: a confined run cannot write another seat's profile, but it can read it. Seats prevent accidental rejoin, which is the failure that actually happens in practice and happens silently; they do not contain a determined process. Where you need a hard boundary between two workloads on one machine, use separate operating system users or separate machines — and see Documented limits.
Binding a run to a seat is fail-closed. It refuses:
| Condition | Why it is refused |
|---|---|
| Machine not admitted, or retired | No identity to bind to |
| Identity not seated on the named machine | Seats are fleet-wide unique |
| Ambiguity — zero or two-plus seated identities | The engine names the candidates and requires an explicit choice. It does not guess. |
| Explicit slot disagreeing with the identity's actual seat | One identity must not occupy another's seat |
| Backend / seat mismatch | Casting the wrong tool exports the configuration home under a variable that tool never reads — landing back in the shared directory |
| Seat with no configuration home | Re-seat it with a configuration home |
| Configuration path failing validation | Checked on write and on every read |
The configuration path is checked when it is written and again on every read. Separately, everything that reaches a remote command line is shell-quoted, so a hostile value that somehow entered a record would be inert.
Summary of the boundary: a seat separates profile state, credentials and sessions, and does so reliably. It is a configuration boundary, not an operating-system boundary — see Documented limits.
The economic guard: billing class
A seat can carry a billing class: subscription, metered, or local-energy, set with
machine seat --billing-class (a closed vocabulary — an unrecognized value refuses, nothing is
written). It exists because a vendor account's billing relationship is part of that account's
identity, and changing which relationship a seat uses is consequential enough that it must be
an explicit owner act, never something a tool infers from behaviour or defaults to for
convenience. Concretely: nothing should ever be able to move a subscription seat onto
metered, pay-per-token billing without the owner deciding so, on the record.
local-energy names a seat whose model endpoint the owner runs directly: there is no vendor
account to meter, and marginal USD is declared zero by the owner's own act — never measured,
never inferred. It is an affirmative member in its own right, not an alias for subscription:
a consumer that gates spend must name it explicitly alongside subscription rather than
treating "anything but metered" as free.
The field lives on the same signed seat row every other seat fact does — not in
configuration, not in an environment variable. Changing it means appending a new signed
machine seat record, exactly like changing a seat's backend or configuration home. There is
no lower-ceremony path to it.
Absent reads as unknown, never as permission. A seat without a billing class — seated without
--billing-class — carries no tag at all — and an absent tag is projected as
unknown, not as subscription and not as "anything goes." This is deliberate and is the
single most load-bearing default in this section: a billing-aware consumer that only proceeds
on an explicit subscription treats every seat that predates this field exactly as it would
treat an explicit metered one — refused until an owner act says otherwise. A permissive
default here would make the guard decorative on every seat that already existed.
machine list and fleet show both project the current billing class alongside the rest of a
seat's identity, so an operator can audit it without a separate query.
Capability: what a machine is trusted to be able to do
Fleet configuration accumulates claims — "this machine has the toolchain", "this machine can run that backend".
Runners: what happens during a run
The boundary each agent run gets on a machine: who enforces it, what it allows, what it refuses, and what it does not cover.