On this page

Workload sessions

Sessions

An attachment lives exactly as long as the caller's streams: close the socket, quit the editor, or let the caller's machine die, and the managed Jeden on the host dies with it. Work that must survive that is started as a session with stado workload start, and runs on the fleet, not on the machine that started it: a start of twenty sessions costs that machine twenty queue records, not twenty harness processes.

stado workload start omp-session --task-file task.md --count 20 --workspace stado --max-time 2h
stado workload start omp-session --task-file task.md --repo stado --repo oko@<commit> --max-time 2h
stado workload start jeden-session --workspace stado --task "…" --max-steps 40
stado workload sessions [--run RUN] [--finished] [--state STATE] [--host HOST] [--by ACCOUNT@HOST] [--json]
stado workload show JOB [--json]
stado workload logs JOB [--follow] [--raw]
stado cancel --run RUN --reason TEXT [--json]

Only a kind whose declaration carries "detachable": true may be started; today that is jeden-session, omp-session, claude-session, codex-session and kimi-session. A harness kind (one whose declaration carries harness) runs that harness in print mode; a harness that declares a transcript (harness.invocation.transcript, today omp-session with --mode json in the pi-agent-events format) writes its event stream to standard output, which show and logs read back as turns, and any other harness's output is read as it was written.

Starting sessions

  • --task TEXT, --task-file FILE — the task every session of the start works on; one of the two is required. The file is read on this machine. A file that cannot be read, or a task that holds only whitespace, is refused before anything is published or queued.
  • --count N — how many sessions; one when omitted, and 0 is refused. jeden-session keeps its session ledger in the host's one checkout of its workspace, so a start of it places exactly one session.
  • --workspace WORKSPACE — the repository whose checkout the sessions work in, by its canonical checkout's name under ~/Documents/CodingProjects/Wisent; without it and without --repo, the workspace the current directory belongs to. Every session on one host works in that host's one checkout of the repository (below the host's declared work root), cloned from the origin this machine's checkout fetches from when the host has none: sessions of one start that land on one host share that tree, and no session gets a tree of its own. A session working in the checkout is handed the kind's clone token, so it can fetch.
  • --repo NAME[@REVISION] — instead of a workspace, a repository exported to each session's own job tree; repeat for several. The revision is a commit that checkout holds, HEAD when omitted. Each is exported once per start as git archive of that commit, published to the job store as runs/agents/<RUN>/repos/<NAME>@<COMMIT>.tar and declared as every session's input; the session unpacks it into repos/<NAME> with the commit in repos/<NAME>/.stado-revision. An uncommitted edit is never read, and such a session is never handed a forge credential: what it changes comes back in its report, and reaches a repository only through the submit queue (stado change submit). jeden-session never works in exported trees.
  • --max-time DURATION — a harness session's bound: seconds, or 30m, 8h. Required by a harness that bounds its own time (omp-session), which is handed it too; Stado keeps the bound itself, whatever the harness does (Time bound). jeden-session is bounded by --max-steps N instead, which it requires.
  • --budget USD — the most each session may spend on its model, in US dollars above zero (5, 2.50), for a harness that declares how it keeps one (harness.invocation.budget: OMP's --max-cost, which exits 3 once spent). A session its harness stopped there ends with {"type":"stado_session_ended","cause":"budget",…} in its log, and show says ended by its harness: its --budget was spent. Refused as budget-malformed or, for a harness that declares no budget, budget-untaken.
  • --context FILE — instructions the session starts with, published as the job's input and handed to the harness with the option its declaration names (harness.session.context: OMP's --append-system-prompt); repeat for several, read in order.
  • --model MODEL — handed to the harness; its default otherwise.
  • --target HOST — place every session on one host, refused when its published room does not hold them all (with --partial, the sessions it holds are queued). Without it the start spreads the sessions over the fleet and leaves the machine it runs on free, naming it among the hosts passed over; --allow-local places sessions on it too.
  • --priority N — 50,000,000 when omitted: a person is waiting on a session, so it is not left behind an overnight batch, and it never outranks a release at 90,000,000.
  • --yield — let a build that needs the session's host take it back: the worker ends the session's current run the way interrupt does, puts the job back in the queue, and a later run continues the kept session.
  • --partial — when the room, of the fleet or of the --target host, holds fewer sessions than --count, queue those that fit; without it such a start queues nothing (fleet-partial, target-short).
  • --fallback-model MODEL — when a run on the session's own model ends in failure (not its budget, not an interrupt), the session continues once on this model ({"type":"stado_session_fallback",…} in its log); refused as fallback-untaken for a harness that does not continue a kept session.
  • --allow-write, --allow-command — the grants the session runs with. Nobody is watching it, so without them a session only reads.
  • --json — the start record: schema_version (1), run_id, batch_id, kind, requested, queued, sessions (each job_id, target, host_id, workspace and cwd), workspace, origin, repos (each name and commit), task, model, harness_version, max_time, budget_usd, priority, grants, reservation, inference (the answer stado capabilities resolve gave), passed (each host passed over, with its reason) and moved (for --resume-from).

The publications are read from the job store, which on a fleet served by an object API is a request the vault authorizes. Every read the store answers is kept in ~/.stado/capacity-publications.json (STADO_CAPACITY_COPY_FILE moves it), and a read the store refuses places from that copy, saying on stderr stado: <why the read failed>; placing from the copy of them this machine kept when it last read them from <STORE> at <TIME> (<N>s ago), each row judged by the promise its agent made. A kept row past its author's promise refuses its host exactly as a fresh one does, so the copy never makes a silent host look ready. With no copy the start is refused (exit 69) as <why the read failed>; this machine keeps no copy of an earlier read either, so no host can be judged ready for a session. A placement keeps one at every read the store answers; once the store's authorization answers again (stado doctor names the dependency that does not), place again, and a copy that cannot be read as <why the read failed>; and the kept copy of the fleet's capacity publications <PATH> is not a kept copy of the fleet's capacity publications: <parse error>, so no host can be judged ready for a session. Remove or repair that file and place again once the store answers.

Real test: tests/workload/placement_capacity.rs.

The start prints one line per session (<JOB> queued on <HOST>), then one naming the run and the commands that read it, and returns. Every session of a start carries the batch agents-<RUN> and is written to the run's index (runs/agent-runs/open/<RUN>.json) as it is queued, jeden-session included, so the queue is the only store: its claim, heartbeats, reaper, retained output and stado cancel apply to each. A session is submitted with its kind's reservation as its sizing (omp-session 2 cores and 2 GiB, jeden-session 2 cores and 4 GiB), as CPU work, so the host's worker admits it only while that much is free and holds it while the session runs. The harness keeps its session store in the job's own tree (--session-dir, harness.session.store), so nothing the session does is written into the host account's own sessions.

The worker is not the operator's shell, so the session's command preflights itself on the host and the job's log carries the reason rather than the harness's usage text:

  • the worker cannot enter <DIR> on this host; grant the Stado agent access to that directory or start the session in a workspace it can read — observed on macOS, where an agent started by launchd has no access to ~/Documents until it is given some.
  • the managed Jeden is missing or not executable at <PATH> on this host; install it with stado product install jeden``

Time bound

The bound is kept by Stado, never left to the harness. The session's command starts with a watcher, before the repositories are unpacked, the workplace is made or the harness is asked anything, and writes the first event of the log: {"type":"stado_session_bound","seconds":N,"given":"<--max-time>","from":"<time>"}. Once the bound has passed since that line, the watcher writes {"type":"stado_session_ended","cause":"max-time",…} and ends the job's whole process group with SIGKILL (the worker starts every job as its own group leader), so a harness that does not stop itself is stopped all the same. The job's record carries the same bound (max_run_seconds), and the worker ends the job's process tree at it whatever happened to the watcher (ended_by: run-bound). A session that ends inside its bound stops the watcher with it.

Rules every session carries

The operator declares, for his fleet, the files every session of a kind must have on its host: stado config set workload.rules '{"<KIND>": ["<FILE>", "<DIRECTORY>"]}' (environment STADO_WORKLOAD_RULES), each path relative to the home directory, a directory standing for every file below it. Stado knows no harness's rule files by name; the declaration says which. They are read on the machine that starts sessions, with their SHA-256, and every host is held to those copies:

  • stado workload harness install <KIND> --target <HOST> brings them to the host after the harness, moves each below the host account's home and checks it there (rules-undelivered when it cannot).
  • stado workload check and a start pass over a host whose copy is missing or differs, naming the files and the verb that brings them, and the session's own command refuses to start its harness on a host that lost them since.
  • A declaration of another shape is refused as rules-malformed, a rule this machine cannot read as rule-unread, both before any host is reached. The run's provenance keeps each rule's path and SHA-256, so show says what a session started with.

Paused on a full disk

While the volume the fleet writes to on a session's host is at the disk-full rule's threshold (80% used), the host's worker pauses the session instead of ending it: it stops the session's whole process group, so a build the session started stops too, and records a paused event naming the reason. Once a janitor pass brings the volume under the threshold, the worker continues the group and records continued. The worker's own run bound still ends a paused session at its --max-time. A volume the worker cannot read pauses nothing and is said in its log.

Each run of the harness also writes the harness version it starts on ({"type":"stado_session_harness",…}), so a harness replaced under a running session shows in its log as the run that moved to the new version; the run that was going keeps the program it started with.

Continuing after a worker restart

A harness that declares how its store continues a kept session (harness.session.resume: for OMP --continue, a session kept as a *.jsonl file in --session-dir) is continued instead of started again when the job runs a second time in the same tree, as it does after its worker was restarted or replaced on the same host and the reaper put the job back: the command finds the kept session, writes {"type":"stado_session_resumed",…} into the log, and starts the harness with the resume arguments. show says how many times a session was continued (resumed). The turns before the restart are in the harness's own store; the log the worker uploads holds what the job printed since it ran again.

Placement by room

Every host's agent publishes its room every tick: free cores and RAM net of what it already runs and holds, whether it accepts work, and the vault fields (role#field) its own grant resolves. A start reads those publications once and gives each local host, and each rented cloud host the registry declares, as many sessions as both its free cores and its free RAM hold at the kind's reservation, filling the host with the most free cores first. A host that publishes nothing live (host-unbroadcast), publishes accepting_jobs: false (host-not-accepting), does not say whether it accepts (host-accepting-unstated), cannot be matched to its publication (host-room-unmatched), does not state its free cores and RAM (host-room-unstated), has no room for one session (host-room-short), or offers none of a secret the kind's sessions read (host-secret-unoffered, naming the field and the grant command; its worker would otherwise decline the session and leave it queued) is passed over with that declared cause.

A host publishes its room net of what it runs and holds, not of sessions already pinned to it that it has not claimed yet. A start therefore takes the queued sessions pinned to each host off its published room, so two starts a moment apart do not both count the same last room: the second places its sessions elsewhere, or passes the host over as host-room-taken, naming the queued sessions that hold it.

Every host with room is then asked, all at once over its host channel, whether it holds the managed harness under ~/.stado/bin at the version this machine runs and the kind's rules. A host that lacks either is brought them first, as stado workload harness install brings them, so a host behind this machine takes its sessions instead of being passed over; the start's record names each under brought (its text says <PROGRAM> <VERSION> brought to <HOST>), and a host they cannot be brought to is passed over as host-unprepared with what it lacked and why bringing failed. stado workload check brings nothing: it names such a host as one a start brings them to (brought_by_start in --json). Every host left is then asked, for the model route the start resolved, for a sign-in the harness can use; a host without one is passed over as host-unprepared, with what it said it lacks, so no session is pinned to a host that would leave it waiting in its queue. The session's own command checks the same on its host before the harness starts and ends with that sentence when the host lost it since.

Sessions the fleet has no room for are not queued. When its room holds only part of a start, the start queues nothing and is refused with fleet-partial, naming the room and every host it passed over; --partial queues those that fit instead and answers with requested and queued in its record. A start no host has room for at all is refused with fleet-short. Either way the sessions the fleet had no room for are recorded as an unmet placement (capacity_exhausted, the cores and RAM they would have taken and each host passed over), so stado fleet needs counts them as what the fleet lacks and stado fleet expansion plan prices more hosts for them; a record the store does not take is said on standard error beside the refusal. Stado places sessions only on hosts the registry declares, a rented cloud host included once it is one: a machine rented for a queued job has neither the harness, the rules nor a sign-in a session's model is reached through, so no session is placed on one.

Model access

Before anything is queued, the start resolves the inference alternative its sessions' consumer is served by: stado capabilities resolve inference --consumer <harness.inference.consumer>, in the selected order (stado capabilities select inference --order …, else the declared one: brama, omp-auth-gateway, harness-sign-in; see alternatives), among the alternatives the harness declares a route to (harness.inference.routes, by provider id). Every other alternative is named in the answer's unreachable as one <consumer> declares no route to, at harness.inference.routes in data/work/workloads.json, and is never proven. A recorded statement does not serve a harness, since a harness cannot read one. For omp-session the routes are omp-auth-gateway (the account gateway the host's service directory places for consumer omp-session, answered by stado service directory connect omp-auth-gateway --consumer omp-session --no-verify; the harness's anthropic requests go there with the job's OMP_SESSION_GATEWAY_TOKEN, read from omp-session-gateway#token, in a harness directory of the job's own) and harness-sign-in (the anthropic accounts omp usage accounts --provider anthropic --json lists, kept by omp login anthropic; the harness keeps the host account's own directory, where that sign-in lives, and --context instructions are appended by name). Only the gateway route hands the job a token; no provider credential is handed to it.

Each host a session would be pinned to answers for its own sign-in: stado workload harness signed-in <KIND> --alternative <PROVIDER> [--json] runs the route's command on the host's managed PATH and exits non-zero naming what it lacks (sign-in-absent, sign-in-unasked, sign-in-unread, sign-in-unrouted in stado.workload.omp). A host that does not answer is passed over as host-unprepared with that sentence, and the session's own command asks again before the harness starts.

A route may also declare how its accounts' room under their provider's usage limits is read (harness.inference.routes.<PROVIDER>.headroom): the harness's own usage command, the pointers its JSON answer lists the accounts and each account's limits at, which limits count (for OMP, the limits every model of the account shares, so a limit of one model tier does not stop the rest), the value that marks a limit spent and where its reset time is. For harness-sign-in on omp-session that is omp usage --provider anthropic --json. signed-in then answers how many of the listed accounts have room (headroom in --json: listed, with_room, resets), and a host whose every listed account has spent a limit is refused as sign-in-spent (exit 69), naming when the earliest has room again; the placement passes it over with that sentence, so sessions go to hosts whose accounts have room and a start no host can take names the time to start again. An account the harness holds that the usage answer does not list (one signed in by key, whose provider reports no limit) counts as having room. An answer the declaration cannot read is refused as headroom-unread, naming why.

A run of a session that fails while every account of its route has spent a limit is continued once one has room: the session's command asks its host's Stado signed-in --until-room, which waits, as a wait it says (czekam: …), until the earliest listed account resets, reads the accounts again, and answers <harness> waited for an account with room until <TIME> (waited_until in --json); the session then continues its kept session and its log says stado: the run ended with status <N> while every account of its model route had spent a usage limit; the session continues at <TIME>, once one has room ({"type":"stado_session_room",…}). When an account has room the answer is at once and the failure is the run's own (the fallback model, when declared, takes it). A reset the usage answer does not state is not waited on: --until-room then refuses as sign-in-spent. The wait counts against the session's --max-time.

When no alternative answers, the start is refused with none-answers (exit 69), naming each alternative it tried, its route and why it did not answer, the ones the harness has no route to among them. An inference order that leaves the consumer no alternative is refused with consumer-unserved (exit 2).

Reading sessions

The session's standard output is its transcript. The host's agent uploads the job's command log to the job store on every heartbeat, so the transcript is readable from any machine that reads the store while the session runs.

  • stado workload sessions [--run RUN] [--finished] [--state STATE] [--host HOST] [--by ACCOUNT@HOST] [--json] — the queued and running sessions of every session run, newest first, with run, job, kind, workspace, state, host, who started it (submitted_by, account@host) and, for an ended one, the error its record holds. The kind is the one its run started and the workspace the repository whose checkout it works in, as the run's index names them; a session that works in the run's exported trees (--repo) names no workspace. --finished adds the completed, failed and cancelled ones. --state is one of queued, running, completed, uploaded, failed, cancelled; any other is refused with state-unknown. The listing reads the runs' indexes and one record per session, never every job the queue holds and no command log. A record that cannot be read is listed with why.
  • stado workload show JOB [--json] — one session: its run, state, host, who started it and from where (submitted_by, submitted_from), start and end, who cancelled it and why (cancelled_by, cancel_reason), its error, its bound (bound) and what ended it at that bound (ended_by), what it started from (provenance: the harness version, model, inference alternative, budget, workspace and origin, and each rule it was held to with its sha256), the commit the host's checkout stood on when the session entered it (commit, from the stado_session_workplace line its command writes; none for the run's exported trees, whose commits repos names), the repositories and commits it was given, how many turns and tool calls its transcript holds so far, and its report — the text of its last assistant message. A session still running past its bound is said so: when its host uploaded its log after the bound passed and the log holds no end, show exits with session-overran.
  • stado workload logs JOB [--follow] [--raw] — the transcript as readable lines: each assistant message, each tool call with its arguments, each tool result's first line, each turn's end, and every other line as written. --raw prints the log as stored. --follow holds on the store's change watch, as stado job watch --follow does, and ends when the session ends.
  • stado cancel JOB --reason TEXT, stado cancel --run RUN --reason TEXT [--json] — one session, or every queued or running session of one run, cancelled through the queue's durable cancellation. Each cancelled session's record keeps who cancelled it (cancelled_by, the canceller's account@host) and why (cancel_reason), and its error says both. --json gives run_id, batch_id, cancelled_by, reason, cancelled and failed.

A session writes result lines into $STADO_SESSION_RESULTS/<name>.jsonl, below its job's output; stado workload results JOB --into DIR collects them: from the job's tree on its host while it runs, and from the job store once it ended (read_from in --json names which). A session not started yet is refused with results-not-started, naming why a host has not taken it (stado host gates); a running one whose tree is not on its host with results-tree-absent.

Moving a running session to the fleet

stado workload start <KIND> --resume-from <SESSION FILE> --task <TEXT> [--workspace REPO] --max-time … moves a session that runs on this machine to a fleet host and continues it there with the task as its next message, through the harness's own handover verbs (harness.handover; for OMP omp session lease|pack|unpack|moved and --resume). The file is read before any host is; then the harness is asked whether a running process of it still owns the session (lease); one it owns is refused as session-held, so two copies never go on apart, and one it holds no session of is refused as session-file-foreign (another harness's file, or none). Once a host is chosen the session is packed with what it refers to (pack), published as the session's input, unpacked on its host into the directory the session works in and continued there (unpack, --resume); once it is queued the copy here is marked gone to that host (moved), and the start record's moved says so, or what the harness answered when it would not mark it. Refusals: handover-undeclared (a harness that declares no handover), handover-count (--count above one), session-file-unread, session-file-foreign, session-held, handover-failed (a handover verb that did not end successfully; nothing is queued).

Steering a running session

  • stado workload send JOB (--text TEXT | --text-file FILE) [--json] leaves a message in the running session's inbox on its host (below its directory, one file per message, taken in name order). When the harness's current run ends, the session's command continues the session with the message as its next task ({"type":"stado_session_message",…} in its log), until no message waits.
  • stado workload interrupt JOB [--json] ends the current run now, with SIGINT to the harness process the session's command keeps (stado-harness.pid in the job's tree) and to every process below it, deepest first, so a command a tool of the harness started stops with it; the job's shell and its bound's watcher go on. A message sent first is taken at once; with none waiting, the session ends. --yield ends a run the same way when a build takes the host back.

Only a harness that continues a kept session (harness.session.resume) takes a message after its task (steer-undeclared otherwise). Refusals: session-not-running, message-empty, steer-undeclared, steer-failed (the host's channel did not take it, naming what the host answered), not-agent.

Team access

A member of a team reaches sessions through Stado's machine API as a machine client (machine_api.clients.<NAME>, its bearer the token of Skarbiec item <NAME>-machine-api). The client's actions are its grants, and an optional session_limit caps how many sessions it keeps live at once:

Grant Route
sessions-start POST /api/machine/sessions/start with the start as JSON: kind, task, and optionally count, workspace, repos, max_time, budget, model, fallback_model, target, priority, allow_write, allow_command, yield, partial
sessions-read GET /api/machine/sessions (the client's live sessions), GET /api/machine/sessions/logs?job_id=JOB[&cursor=N] (one session's transcript as stored, with the cursor the next read starts from)
sessions-steer POST /api/machine/sessions/send?job_id=JOB with {"text": "…"}, POST /api/machine/sessions/interrupt?job_id=JOB
sessions-cancel POST /api/machine/sessions/cancel?job_id=JOB with {"reason": "…"}

Each start keeps the client in its run's provenance (show names it), so a client reads, steers and cancels only the sessions started for it; any other session is refused FORBIDDEN (session-foreign), as is a start that would pass the client's session_limit (session-limit-reached, naming its live sessions). A bearer of a client that lacks the route's grant is FORBIDDEN, naming the grant its actions must hold; a missing bearer, or one no client holds, is UNAUTHORIZED. A start is refused for the same causes as stado workload start, with its fix in the message. Every message, interrupt and cancellation is kept as a session event with by: machine-api:<NAME>. Every answer is the machine plane's envelope (schema_version, ok, result or error).

A start and its answer, the start's record in result as stado workload start --json prints it:

$ curl -sS -X POST "$STADO_API/api/machine/sessions/start" \
    -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
    -d '{"kind": "omp-session", "task": "Read the README and report what it says.", "max_time": "30m", "workspace": "<REPOSITORY>"}'
{"schema_version": 1, "ok": true, "result": {"run_id": "<RUN>", "requested": 1, "queued": 1, "sessions": [{"job_id": "<JOB>", "target": "<HOST>", …}], …}}

A refusal carries the cause's sentence and its fix in error.message; past the client's limit:

{"schema_version": 1, "ok": false, "error": {"code": "FORBIDDEN", "retryable": false,
  "message": "starting 1 session(s) of omp-session for machine API client <NAME>: the client may keep at most 2 sessions live at once, and that many more would pass it; observed: 2 live session(s) of the client: <JOB>, <JOB>; fix: wait for one of them to end, …"}}

Events, alert and retention

Each session's start (queued) and end (the state it ended in) is one event in the job store (runs/agent-events/, one document each, named by when it happened, with the job, the run and what its record says). So is each steering of a session, with the account that did it: message (send), interrupted (interrupt) and cancel-asked (stado cancel JOB of one session or stado cancel --run of a run's, each with who cancelled it and its reason; a job that is no session's keeps none). The kinds named in stado config set workload.events '["failed", "cancelled"]' are pushed to the alert channels under the topic workload-sessions as they are recorded, so another product reacts without polling. Every coordinator tick settles the session runs whose sessions all ended (their end events are recorded then), and:

  • with workload.alert set to {"window_minutes": N, "ratio": R, "min_sessions": M}, raises one alert per window once at least M sessions ended in the last N minutes and the share of them that failed or were cancelled reaches R, naming each such session and the error its record holds;
  • with workload.retention_days set, removes the events and the sessions' transcripts older than that, marks each run's summary with when and by what (transcript_removed in show), and records what it removed as a removed event; logs of such a session is refused as transcript-removed, naming both. A session's tree on its host is the host janitor's (results-tree-absent once it is gone).

stado workload events list [--since TIME] [--job JOB] [--follow] [--json] reads the events back, oldest first (the last day when --since is omitted; a time that is not RFC 3339 is refused as events-since-malformed). With --follow it keeps reading, woken by the job store's own change watch rather than a timer, each event as it is recorded (with --json, one JSON line per event), so a product that reacts to sessions holds one read instead of polling. stado workload events keep runs the routine's pass now.

Pools

A pool is a declaration in the job store, kept by Stado's routine, not a process on anybody's machine: one session per item of work its source hands out, up to the most sessions it keeps at once.

stado workload pool set NAME --kind omp-session --source '["PROGRAM", "WORD"]' --release '["PROGRAM", "WORD"]' --complete '["PROGRAM", "WORD"]' --max N --max-time 2h [--workspace REPO] [--budget USD [--budget-total USD]] [--model M] [--fallback-model M] [--priority P] [--yield] [--allow-local] [--allow-write] [--allow-command] [--json]
stado workload pool show [NAME] [--json]
stado workload pool pause NAME --reason TEXT
stado workload pool resume NAME
stado workload pool remove NAME --reason TEXT
stado workload pool tick [NAME]
stado workload sessions --pool NAME

Each command is a JSON list of its words, the program first, so no word is split or joined by a shell. The source answers one JSON object with a text id and a text task for an item, or nothing when there is no work. Every coordinator tick, and pool tick when asked, runs one pass over each pool: a session that ended is settled first (its item is reported with --complete when the session completed, given back with --release otherwise), then, unless the pool is paused or being removed, the pass takes items one at a time and starts one session for each until the pool keeps its --max. The commands run on the machine the routine runs on, with STADO_POOL, STADO_POOL_ITEM, STADO_POOL_JOB and STADO_POOL_STATE set. A source that has no work, fails or answers something else, and a fleet with no room for the session, stop the pass and are kept as the pool's waiting; an item taken for a session that could not start is given back at once. A command that does not take an item back is kept on the session as unsettled and asked again at the next pass. pause starts nothing more and lets the sessions go on; remove does the same and drops the pool once no session holds an item of it. show prints each pool's declaration, the item each live session holds, and the last pass: how many it wanted and started, how many it settled, and what it waits on.

The source may say what an item comes after (after, a list of item ids) and which files it changes (files, a list of paths). The pass does not start an item that comes after one a live session of the pool still works on, nor one that changes a file a live session's item changes too: it gives the item back with --release and keeps why it waits in pool show, naming the item and the session (and the shared files), so two sessions never work at once on dependent items or on the same files of one checkout. An item the source hands out again while a live session holds it is not given back, since that would release it from the session: it stays with that session, nothing is started for it, and the pass waits naming both.

--budget is each session's budget, read as start --budget reads it (budget-malformed, budget-untaken). --budget-total is the most the pool's sessions together may spend: each session started commits its --budget (kept as committed_usd in pool show), and the pass starts none that would commit more, waiting with the amount committed and the command that raises it. It is counted by the sessions' own budgets, so it needs --budget (budget-total-uncounted), and it is US dollars above zero (budget-total-malformed).

Refusals (stado errors show stado.workload.pool): name-unsafe, max-zero, command-missing, command-malformed, kind-untaken (a kind without a harness), kind-fixed (a pool's kind changed under its sessions), budget-total-malformed, budget-total-uncounted, pool-unknown, reason-missing, pool-unread; kept on the pool: source-failed, source-unread, settle-failed.

Session refusals

stado errors show stado.workload.sessions lists each with its sentence and remedy:

  • count-zero, count-unsupported — --count 0, or more than one jeden-session.
  • task-unread, task-empty — the task file cannot be read, or the task holds nothing; task-missing (from stado.workload) — neither --task nor --task-file.
  • repos-untaken — --repo for jeden-session.
  • transcript-format-unknown — a transcript format this Stado cannot read.
  • max-time-missing, max-time-malformed, max-steps-missing, budget-malformed, budget-untaken (from stado.workload) — the bound and the budget.
  • repo-unknown, repo-revision-absent, repo-twice, repo-export-failed — a repository that is no checkout here, a revision it does not hold, one named twice, an export git refused.
  • none-answers, consumer-unserved (from stado.capabilities.alternatives) — the model alternative.
  • target-short — --target cannot hold every session and --partial was not given, or it holds none; nothing is queued.
  • fleet-partial — the fleet's room holds only part of the start and --partial was not given; nothing is queued.
  • fleet-short — no host has room for the start, or a host refused sessions while they were queued; what was not queued is named with each host passed over and its declared cause.
  • not-agent — show and logs read sessions only, and name the job's batch otherwise.
  • state-unknown — sessions --state names no queue state.
  • session-overran — show found the session running past its bound with a log uploaded after it and no end in it.
  • follow-failed — the store's change watch failed while logs --follow held on it.
  • run-unknown, run-index-unread — sessions --run or cancel --run names a run the store holds no index of, or an index that does not read.
  • run-not-live, run-partly-cancelled — cancel --run names a run every session of which has ended, or the queue refused some of its sessions.
  • reason-missing (from stado.cancel) — a cancel without --reason.

Source: this website