Where the work runs: seats, the queue and the account ceiling
How the engine chooses a seat for a leg, how to use the dispatch queue when no seat admits, how to read an account's weekly usage, and the account live-run ceiling.
Placing a leg (Placing a leg) asks one question: which seat
runs it? This page is for the operator who wants to read the engine's answer, wait for a better
one, or change a limit that decides it. Every command below takes --workspace <workspace-root>,
the root of your workspace.
A seat is one agent identity on one machine, on one backend. Several seats can spend one vendor account, and the vendor's weekly allowance belongs to the account, not to the seat. Most of what follows turns on that difference.
How the engine chooses a seat
When you place a leg without naming a seat, the engine ranks every seat it may use and tries them
in rank order. The candidates are the seats that saphan fleet dispatchable --role executor --workspace <workspace-root> marks as able to dispatch, restricted to backends that have a model
configured for the role. A seat is admitted only if it clears four floors, and the smallest floor
decides:
- the slot's session limit: how many sessions the seat's slot may run;
- the machine's live-run limit:
scheduler.host_max_live, per host; - the seat's own state: its role, confinement, credential and usage headroom;
- the account's live-run ceiling: how many runs one vendor account may carry at once, counted over every seat on that account (see The account live-run ceiling).
A seat whose deciding column nobody has read is reported UNMEASURED. In the engine's words, that is neither a refusal nor a pass: nothing was measured to refuse it with, and absence is never consent.
Reading the "considered" block
A placement prints a note naming the winner, then one line per seat in the order tried. This block is an illustration of the shape; the names and numbers are invented:
Read each line left to right:
| part | what it tells you |
|---|---|
#N | the seat's rank. The engine tries seats in this order; the first that admits wins |
seat (backend on machine) | which seat, and where it runs |
account 1111aaaa…aa01 | the vendor account the seat spends, shortened. Two seats with the same value draw on one allowance |
week N% used | how much of the account's weekly allowance is spent |
resets … (in …) | when the weekly allowance renews, and how long from now |
score | the ranking figure, higher first |
read … ago via … | when the reading was taken and from which record. A reading too old to decide on is refused as stale (see below) |
How the score behaves. In the block above the score equals the share of the week still unspent
divided by the hours until the reset: 70 ÷ 20.0 = 3.50, 100 ÷ 40.0 = 2.50, 50 ÷ 25.0 = 2.00.
That reading fits every line of a block printed by the installed engine, and it explains the
ordering a newcomer finds odd: a seat on an account at 0% used can rank below a seat on an
account at 30% used, because the second account resets sooner, so its remaining allowance is lost
first if nobody spends it. Two seats on the same account share one score, as #1 and #2 do.
Treat the formula as a way to predict the order, not as a documented contract: the printed score is
what the engine used.
⚠ What the block does not tell you. The rank numbers printed in a real block are not always
consecutive, and this page does not state what a missing number stands for. The block lists the
seats it considered; it does not list the seats that were never candidates. Use
saphan fleet dispatchable --role executor --workspace <workspace-root> to see why a seat is not
among them.
When no seat admits: the queue
If no seat admits the order now, you can wait (--wait, see
Placing a leg) or put the order on the dispatch queue,
which places it later when a seat admits it.
| verb | what it does |
|---|---|
enqueue | puts one ready order on the queue. --role is executor or review; a review also needs --reviewed <stream> |
list | prints the queue in drain order, queued and placed entries. --all also lists cancelled, refused and expired entries |
cancel <id> | cancels a queued entry; it is never placed |
drain | one drain step: places the head of the queue when a seat admits it, and sends the start and end notices |
An empty queue prints the dispatch queue is empty.
enqueue takes three optional controls:
--priority N: higher drains first; ties go to the oldest entry. A value below 0 is LOW.--not-before <RFC3339>: hold the entry until that time.--deadline <RFC3339>: an entry still queued after this time is marked expired, and the enqueuing seat is told.
What the enqueuing seat is told, and when. The drain step that places an entry sends the
start and end notices, and the enqueuing seat is told when an entry expires at its
deadline. drain is one step per invocation; this page does not say whether your
installation runs it on a schedule, so after enqueuing, check saphan dispatch queue list and run
drain yourself if the entry is not moving.
Waiting for an account's reset. A low-priority entry (--priority below 0) may wait for an
account whose weekly reset is near, rather than being placed on a seat of an account with less
allowance left. Near is fleet.queue_reset_wait_hours: the default is 24, so such an entry
can wait up to a day for a reset, and 0 disables the wait.
The wait is stated only for entries below priority 0.
What one account-week buys
reads each seat's usage, and the placement block repeats the same fields (week N% used,
resets …). You can narrow the read with --machine <machine>, and to one slot with --slot <n>,
which requires --machine.
- The percentage is the share of the account's weekly allowance already spent. What remains is
100 − N, shared by every seat on the account. - The reset is when that share renews. The same remaining share is worth more on an account that resets in a day than on one that resets in four, because unspent allowance does not carry over; that is what the score expresses.
- This page does not convert a percentage into tokens, runs or money. No engine output states such a conversion, so none is given here; measure a typical leg on your own account.
An absent reading is reported as absent. A seat whose usage was never read answers
headroom-unknown: the engine found no obstacle and authorised nothing, neither a refusal nor a
pass. A reading too old to decide on answers headroom-stale. Neither is treated as free
capacity.
What taking a reading costs. A refusal that asks for a fresh reading says: the usage read is
free in the common case, with zero model turns. Only a claude-code seat that the free read does
not unblock can cost anything, and then at most one ping-sized model turn for that seat, and at
most one per seat per 30 minutes. Each row prints rescue=<rung> pings=<0|1>, so the report is its
own receipt. ⚠ The refusal for an unknown account (below) states that the account observation
dials the vendor, so it bills. Read the receipt on the first run rather than assuming it is
free.
The account live-run ceiling
Several seats can spend one vendor account, and the vendor's allowance does not care how many machines they sit on. The account live-run ceiling is the number of runs one account may carry at once, counted over the live runs of every seat on that account.
-
The key.
scheduler.<backend>.account_max_livefor one backend, elsescheduler.account_max_livefor all of them, else the engine's default of 1. A seat uses the first of these that is set, and every surface that prints the ceiling names which one answered. -
It is declared, never measured. The engine does not ask a vendor for a concurrency limit. The default of 1 is deliberately conservative.
-
Reading the current value.
saphan config showlists the configuration, and a refusal for a full account names the key that answered. -
Changing it.
This is the owner's act. The key is written to the owner's own configuration file (
<prefix>/etc/saphan.conf) and takes effect for every workspace on that host, not only the one named.
When raising it is sound. When you have measured weekly headroom on the account: the usage read shows plenty of the week unspent and the reset is not near, so a second concurrent run does not exhaust it, and you know that your vendor's plan allows that many concurrent sessions.
When it is not. When the account's week is already largely spent, when you do not know the vendor's concurrency policy for that account, or only because a run is waiting. Raising the ceiling spends someone else's allowance faster; it adds no capacity. A seat on a different account, or waiting for a live run to end, are the other two levers.
The owner's own seat
The owner can give their own interactive session a seat of its own, so it does not spend the
allowance of the accounts that run legs. Seat it with the role owner:
saphan cli then opens the owner's session as /owner on that seat.
An owner seat is nobody's capacity. saphan dispatch place, saphan fleet dispatchable (under
any --role) and the seat ranking never offer it, whatever role a run declares. A fleet-wide
saphan seat auth --probe skips it instead of dialling it: it prints one line naming the seat and
the command that probes it on its own, and its --json report lists the seat as skipped with the
reason owner-seat.
When it refuses
Run saphan refusals for the full vocabulary. A refusal reads: what was measured, NEXT (what to
do), the recorded reason, then its codes. The ones this path meets, by meaning:
| refusal | meaning | NEXT |
|---|---|---|
vendor-account-is-at-its-live-run-ceiling, account-at-capacity, floor-4-account-max-live | The vendor account behind the seat already carries as many live runs as the ceiling allows. Every seat on that account is excluded by the same measurement | Wait for a live run on any seat of that account to end, or use a seat of a different account (the refusal prints a WHERE ELSE block of seats on other accounts, when there are any). Raising the ceiling is the owner's act, and a decision about someone else's allowance |
headroom-exhausted, seat-headroom-measured-exhausted | The seat's allowance was measured and is spent. It returns on the vendor's own clock | Wait for the reset the row names, or place on a seat the report shows free. Do not re-authenticate; the credential is not the problem |
seat-usage-reading-is-spent-and-an-alternative-has-headroom | The seat's latest reading is spent while another seat of the same backend shows headroom, so the engine will not spend the exhausted one | Dispatch onto the alternative seat the reason names, or wait for this seat's reset |
headroom-stale | The latest reading is too old to decide on | Take a fresh reading with saphan fleet usage --workspace <workspace-root>, then ask again. One reading clears every seat of the account |
headroom-unknown | The deciding column was never read: neither a refusal nor a pass | Take a usage reading with saphan fleet usage --workspace <workspace-root>, then ask again. A seat whose credential was never read names the seat-auth probe instead, which is a priced vendor turn |
account-unknown, floor-4-account-unknown | The engine does not know which vendor account the seat spends, so floor 4 states no verdict. Two such seats may be one account | Take the reading with saphan fleet usage --workspace <workspace-root> (it dials the vendor and bills), or declare the account without dialling anything with saphan machine set-account --identity <seat> --account <opaque id> --added-by <owner> --workspace <workspace-root> |
A stream that already has a live run is refused on every seat at once; see Placing a leg.
Before a dollar is spent
Capacity checks, usage windows, the refusals that happen before a run spawns, and an honest statement of which ceiling is enforced by whom.
One yardstick across many vendors
Running several backends is a cost instrument, not a preference — because the same order can be given to two actors and judged by the same tests.