Seats: binding an executor profile to a run
What a seat is, the only way to bind one to a run, and why a status read is not proof.
A seat is a row in the machine registry: (identity, machine, slot) plus a
config-dir (for claude-code, codex, qwen-code and the other CLI backends) or a
lane (for openai-compat and agent-loop seats) — exactly one of the two, never both. It
binds a tool profile — CLAUDE_CONFIG_DIR for claude-code, CODEX_HOME for codex — to a
machine slot, so that a named identity always spawns against the same credentials instead of
whatever profile the process happened to inherit. The security model of seats — including the
role that can reserve a seat and the billing class that tags its economics — is in
Seats: separating agents that share a machine;
the money side is Cost management.
The machine verbs are listed in saphan machine. The ones that
touch seating directly: admit (register a machine), seat (write the identity+machine+slot
row), set-slots (resize how many identities a machine carries), unseat (free one slot), and retire (withdraw a machine —
retirement is a mark on the row, never a delete, and casting onto a retired machine is refused).
The only way to bind a seat to a run
--identity alone, without --machine, is refused — it does not fall back to
anything:
(The sibling case — a bare --slot with no --machine — refuses with the same shape.)
The engine never chooses a profile for the operator. It either binds the seat you named, or it refuses by name. There is no third outcome where it quietly substitutes a default and calls it success — a run that did would carry no information about whether the named seat paid for it.
A status read is not proof a seat is usable
A green machine probe or a vendor auth status reporting "loggedIn": true tells you the
profile directory parses and holds some stored credential. It does not tell you that
credential still works — token expiry is a separate axis neither check reads.
Two mechanisms respond to this:
- The engine's own pre-spawn check: before casting onto a
claude-codeseat, the engine asks the vendor for the seat's usage through the profile — a read that spends no model turn and costs nothing — and refuses the spawn before it starts if the credential is dead. A good answer stands for two minutes, so a spawn right after another does not ask again. - The only full verification is a real spawn:
A recorded run that completes — or fails with a dated auth error rather than a stale
loggedIn: true — is the signal that the seat's credential is live right now.
Limits are per profile — check, do not assume
Two config-dir profiles on the same vendor account can carry separate session budgets, so one
can be refused for a session limit while the other runs. Do not assume either direction on your
own fleet: saphan fleet usage, which reads every seated profile's own usage, answers it per seat.
One run per slot
A slot carries exactly one live run. A second dispatch onto a slot that already has one is
refused, never queued; the refusal names the machine, the slot and the live run, and ends
"double-dispatch refused, never a silent queue". The retired machine set-max-sessions verb
refuses by name: there is no per-slot capacity to raise. See
Runners: what happens during a run for the
mechanism that makes this race-proof.
A second machine on the same host
machine admit --mint-device-key writes the device private key to
~/.saphan/machine/device.key by default, and never overwrites a key that is already there.
Admitting a second machine on the same host with the default path is refused: the refusal
names the existing key and prints the two commands that reuse it (machine csr --reuse-key, then
machine admit --csr). To give the second machine its own key, pass an explicit
--device-key <path>.