Saphan StudioDocs
Cost management

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:

note: --machine auto chose seat seat-a on machine host-1 slot 5 (backend claude-code, model claude-sonnet-5-5, source=config:dispatch.model.executor.claude-code) — the best-ranked seat `fleet dispatchable` marks MAY DISPATCH whose backend has a model
    considered, in the order tried:
    #1 seat-a (claude-code on host-1): account 1111aaaa…aa01 — week 30% used, resets 2026-10-24T10:00:00Z (in 20h0m0s), score 3.50, read 1m0s ago via seat_usage_reading
    #2 seat-b (claude-code on host-2): account 1111aaaa…aa01 — week 30% used, resets 2026-10-24T10:00:00Z (in 20h0m0s), score 3.50, read 1m0s ago via seat_usage_reading
    #3 seat-c (claude-code on host-3): account 2222bbbb…bb02 — week 0% used, resets 2026-10-25T06:00:00Z (in 40h0m0s), score 2.50, read 6m0s ago via seat_usage_reading
    #4 seat-d (claude-code on host-1): account 3333cccc…cc03 — week 50% used, resets 2026-10-24T15:00:00Z (in 25h0m0s), score 2.00, read 10m0s ago via seat_usage_reading

Read each line left to right:

partwhat it tells you
#Nthe 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…aa01the vendor account the seat spends, shortened. Two seats with the same value draw on one allowance
week N% usedhow much of the account's weekly allowance is spent
resets … (in …)when the weekly allowance renews, and how long from now
scorethe 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.

saphan dispatch queue enqueue --order <path-to-order> --role executor --workspace <workspace-root>
saphan dispatch queue list --workspace <workspace-root>
saphan dispatch queue cancel <id> --workspace <workspace-root>
saphan dispatch queue drain --workspace <workspace-root>
verbwhat it does
enqueueputs one ready order on the queue. --role is executor or review; a review also needs --reviewed <stream>
listprints 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
drainone 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.

saphan config set fleet.queue_reset_wait_hours <hours> --workspace <workspace-root>

The wait is stated only for entries below priority 0.

What one account-week buys

saphan fleet usage --workspace <workspace-root>

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_live for one backend, else scheduler.account_max_live for 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 show lists the configuration, and a refusal for a full account names the key that answered.

  • Changing it.

    saphan config set scheduler.<backend>.account_max_live <n> --workspace <workspace-root>

    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 machine seat --identity <seat> --machine <machine> --role owner --added-by <owner> --workspace <workspace-root>

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:

refusalmeaningNEXT
vendor-account-is-at-its-live-run-ceiling, account-at-capacity, floor-4-account-max-liveThe 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 measurementWait 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-exhaustedThe seat's allowance was measured and is spent. It returns on the vendor's own clockWait 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-headroomThe seat's latest reading is spent while another seat of the same backend shows headroom, so the engine will not spend the exhausted oneDispatch onto the alternative seat the reason names, or wait for this seat's reset
headroom-staleThe latest reading is too old to decide onTake a fresh reading with saphan fleet usage --workspace <workspace-root>, then ask again. One reading clears every seat of the account
headroom-unknownThe deciding column was never read: neither a refusal nor a passTake 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-unknownThe engine does not know which vendor account the seat spends, so floor 4 states no verdict. Two such seats may be one accountTake 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.

On this page