On this page

Workloads

stado workload places declared work on an eligible registry host and returns either the interactive stream or the workload's report/receipt. Workload names, product ownership, interaction mode, registry gates, accepted plan schemas, and report fields are data rather than CLI variants. A new product workload is one row in the declaration.

Invocations

  • stado workload list [--json]

    • Lists every compiled workload kind. --json returns {schema_version, workloads} and each workload carries kind, product, interactive, registry_allowance, plan_schema, and report.
  • stado workload run <KIND> [--target <TARGET>] [--plan <FILE>] [--json]

    • Runs a non-interactive kind. --target pins placement; without it Stado chooses an eligible local registry target. --plan supplies the declared JSON schema. --json returns the workload report or receipt. A workload whose process failed reports that process's own last output: weles-browser-task carries stdout_tail and stderr_tail in its report, and its refusal sentence quotes both, so the failing browser step is read instead of the newest warning line.
  • stado workload status <KIND|ID> [--target <TARGET>] [--json]

    • Reads a kind report. Capture and diagnostic receipts may be addressed as weles-capture:<batch> and weles-diagnostics:<run-id>; a bare receipt ID is treated as a capture batch. --target pins the report host. --json returns the full report.
  • stado workload attach <KIND> [--target <TARGET>] [--workspace <WORKSPACE>] [--resume <SESSION>]

    • Attaches inherited stdin/stdout/stderr to an interactive kind. jeden-session derives the workspace from the current directory, selects by live capacity when unpinned, and attaches to the managed Jeden runtime whose durable ledger is ~/.jeden/sessions. --resume requires the selected host to own that named ledger; the refusal is session ledger is missing: <ledger>/<SESSION>. When this machine is the selected host and the caller's environment carries JEDEN_SESSION_ROOT, that directory is the ledger: the readiness probe looks there, the placement line reports it as ledger, and the managed Jeden inherits the variable, so a session Jeden wrote under a moved root is the one Stado reopens. Another host never sees the caller's environment and keeps ~/.jeden/sessions. Nothing but the managed jeden and stado binaries is required on the host: the attachment runs the managed Jeden with ~/.stado/bin ahead of its PATH, and Jeden reads its own credentials through Stado.
  • stado workload start <KIND> --task <TEXT> [--workspace <WORKSPACE>] [--target <TARGET>] [--model <MODEL>] [--max-steps <N>] [--priority <N>] [--allow-write] [--allow-command] [--json]

    • Starts a detachable kind as a durable queue job and returns at once. The session keeps running when the terminal, the editor or the machine that started it goes away. --json returns the placement record described under Detached sessions.
  • stado workload sessions [--json]

    • Lists every detached session the fleet holds, newest first, with its kind, job, state, host, workspace and sizing.

What a placed workload holds

Every kind declares a reservation — {"cpu_cores", "ram_gb", "vram_gb"} — and stado workload run and stado workload attach take exactly that hold on the chosen host before the process starts, keep it heartbeated while the process lives, and release it when the process ends.

An interactive kind may also declare park_after_seconds. stado workload attach hands it to the runtime — JEDEN_PARK_AFTER_SECONDS for jeden-session, which declares 900 — and prints it in the STADO_JEDEN_PLACEMENT line. The runtime parks itself when nothing of its own has run for that long, or at once when every open session carries a completion blocker: it sends a parked frame, disposes its sessions to their ledgers and exits 0, and the attach releases the hold. Nothing that is running is ended. Parking releases capacity held by an idle interactive session so that the fleet can admit other work. A declaration of 0 is refused with workload kind '<KIND>' declares park_after_seconds 0; declare a whole number of seconds above zero in stado-rs/data/work/workloads.json, and one on a kind that is not interactive with workload kind '<KIND>' declares park_after_seconds but is not interactive; only an attached session parks, so remove it from stado-rs/data/work/workloads.json. The host's agent subtracts every live hold from what it publishes, so a host running three Jeden sessions is no longer read as free. The declared sizes are: jeden-session 2 cores and 4 GiB; weles-browser-task and weles-capture 2 cores and 3 GiB; weles-image-inspect and mobile-runtime 1 core and 2 GiB; every other kind 1 core and 1 GiB. None holds VRAM.

A detached session is sized the same way, but the hold is the queue job's own: stado workload start submits the work with the kind's cpu_cores and ram_gb as the job's declared sizing, so the worker admits it only while that much is really free and subtracts it for as long as the session runs.

A host whose published room, net of the holds already on it, is below the kind's declaration refuses the placement with <TARGET> has no room for <KIND>: needs <c> cores and <r> GiB, host publishes <c2> cores and <r2> GiB net of <n> reservation(s); pick another host with --target or wait for a reservation to end; a host publishing accepting_jobs: false refuses with <TARGET> is not accepting placements (<admission_reason>); pick another host with --target or wait for it to clear. An unpinned jeden-session moves on to the next candidate after either; a pinned placement and a run stop there. Each refusal is recorded for stado fleet needs. The object, its lifetime and the commands that read it are on the reservation page.

Detached 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 detached.

stado workload start jeden-session --workspace <WORKSPACE> --task <TEXT> places the session the same way an attachment does — the reachable local targets are ordered by what their agents published, a host that is accepting work outranks one that is not, then free CPU cores — and then submits the work to the queue pinned to the host it chose. From that moment the session belongs to the fleet: the queue's single claim, per-job heartbeats, the reaper that requeues a worker that died, the retained output and stado cancel all apply to it. The starting command returns immediately.

A session runs the managed Jeden in that host's checkout of the workspace (~/Documents/CodingProjects/Wisent/<WORKSPACE>, or the home directory for __home__) with --json, so the session's final answer is machine-readable in the job's retained output. Nobody is watching it, so it is granted nothing by default: --allow-write and --allow-command are the only way it may write files or run commands, and --max-steps bounds it.

The record --json returns carries schema_version, kind, target, workspace, cwd, ledger, job_id, batch_id, run_id, task, model, state, grants, reservation and the exact command the worker will run.

Every detached session of one kind carries the batch workload-<KIND>, so stado status workload-jeden-session finds them too. Follow one with stado job watch <JOB-ID> --follow, download what it produced with stado results <JOB-ID> <DIR>, and stop it with stado cancel <JOB-ID>.

A session is queued at priority 50,000,000 unless --priority says otherwise: a person is waiting on it, so it is not left behind an overnight batch, and it never outranks a release at 90,000,000.

It is also submitted as CPU work, because a session reaches its model over HTTP through Brama and needs no accelerator. That is a declaration, not a guess, and the claim honours it: the queue's model-name sizing scan reads jeden run … --model <route> as a GPU workload, and on a host with one GiB of VRAM every session naming a model was refused by the claim on every poll, silently, until that CPU resolution was carried through to the claim.

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``

Only a kind whose declaration carries "detachable": true may be started this way; today that is jeden-session.

Fixed browser steps

A weles-browser-task plan can supply replay: a nonempty array of steps, each with a nonempty tool string and an optional args object. This requires action: "generic_browser_task". Stado forwards the steps to Weles with GENERIC_TASK_REPLAY_ONLY=1 and GENERIC_TASK_SKIP_SAVED_FLOW_REPLAY=1, so a failed step does not switch to model-selected browser actions. A read step may still use a model to describe the page; fixed steps do not mean model-free execution.

Run a saved plan through the selected host:

stado workload run weles-browser-task --target <host> --plan observation.plan.json --json

The plan has this shape:

{
  "schema": "wisent.weles-browser-task-plan.v1",
  "action": "generic_browser_task",
  "url": "https://www.figma.com/files",
  "objective": "Describe the visible page without signing in or changing files.",
  "session_label": "figma-team-observation",
  "allow_login": false,
  "windowed": false,
  "replay": [
    {"tool": "read", "args": {"question": "Is a file listing visible, or a login page?"}},
    {"tool": "done", "args": {"value": {"inspection_only": true}}}
  ]
}

Malformed replay plans are refused before the browser runner reads the host allowlist or issues sign-in capabilities:

  • workload plan replay must be a nonempty array of steps
  • workload plan replay step <INDEX> requires a nonempty tool string and optional args object (zero-based index)
  • workload plan replay requires action generic_browser_task

A plan's opt-in flags (fresh_profile, allow_login, defer_fills, prefill_all, windowed, a runtime's repair, a GUI grant's apple_only) are off when the plan does not name them, and a flag named with anything but true or false is refused with workload plan <field> must be true or false, not <value> rather than read as off. A login_item is checked by the grammar of every Weles identifier and must begin with a letter or digit; one that does not is refused as weles-browser-task plan login_item is not a valid Weles item id: <why>.

A successful observation is not proof that a requested file was created. A run can execute every step of its plan while the page it observed is a login page, and recording can report ENOSPC: no space left on device; exit code zero does not establish that the requested change happened or that the recording was retained.

Read the run's retained diagnostic inventory with stado workload status weles-diagnostics:<RUN-ID> --target <host> --json. Preserve the observed page, failed operation, and recording errors alongside the receipt. An inaccessible file listing does not establish that files are absent, and a supplied done value is not independent evidence of an external change.

The action's own parameters

A named action reads more than the page and the objective: an Overleaf version-history scan takes the project and the phrase, a key follow-up takes its query. A weles-browser-task plan carries them as params, an object whose entries reach the run's parameters under their own names, beside the ones the task sets itself:

{
  "schema": "wisent.weles-browser-task-plan.v1",
  "action": "overleaf_version_history_scan",
  "url": "https://www.overleaf.com/project/<id>",
  "objective": "Find who changed the phrase in the project's history.",
  "session_label": "lem-overleaf-history",
  "params": {"project": "<id>", "query_text": "the phrase", "main_tex": "main.tex"}
}

The action is gated by the host's allowlist like every other. Two refusals come before any host is contacted:

  • workload plan params must be an object of the action's own parameters
  • workload plan params names "<name>", which the task itself sets; declare it in the plan's own field — for url, objective, flow_name, session_label, proxy, headless, constraints and env, so one parameter never has two sources.

Real test: tests/workload/browser_task_params.rs.

What a finished run reports

The report (--json) is the run document Weles answered, read field by field. result is the trajectory's own: its last JSON line on standard output, or the result document it left, null when it left neither — an Overleaf scan prints its summary pretty-printed, so its result is null. outputs is what such a trajectory leaves instead: the named documents under the run's recording tree, each under the name Weles reads it by (overleaf_version_history_summary, pangram, ban_signal, generic_browser_task, capture, …), and a caller that wants the scan's summary or Pangram's verdict reads it there. output_errors appears only when one of those documents could not be read — the file by name and the reader's error — so an unreadable output is told from an absent one. The text form prints output: <name> per document and unreadable output: <file>: <error> per failure. A run document missing a declared field (ok, exitCode, run_id, result, outputs, stdout_tail, stderr_tail) is refused as the Weles API completed <action> and its run document carries no \`: , never read as a run that left nothing; exitCodenull is a run a signal ended, reported asexit_code: null`.

Native API

GET /api/operator/workload/attach upgrades one authenticated request to a WebSocket that owns one interactive attachment. It answers to the same authorization as POST /api/operator/run: loopback callers are trusted by the Host guard, and every other caller presents an operator bearer.

  • The first client frame is the attachment request: {"kind", "target", "workspace", "resume", "confirmation"}, and confirmation must be RUN_MUTATION because an interactive attachment can change the target.
  • The server answers {"type":"attached","protocol":"stado.workload.v1"} once the workload process is running.
  • Server binary frames carry one channel byte and then bytes: 1 is the workload's standard output, 2 is its standard error. Client text and binary frames are that process's standard input, byte for byte; an empty binary frame closes it.
  • The final server frame is {"type":"exit","code","ok"}, or {"type":"error","message"} when the attachment was refused. The refusal is the CLI's own sentence.
  • Closing the socket ends the process group of the attached workload, so a disconnected operator leaves nothing running on the host.

Weles activity connection evidence

stado workload status weles-activity --json reads the selected host's retained runs and attempts one TCP connection to its Weles API port. The connection ends on the socket's own connect or error event, not a guessed interval. api.listening records TCP connectivity only; it does not establish that an HTTP request or browser journey works.

The host's WELES_ACTIVITY_RUN_LIMIT, read on the host, limits how many recorded runs are listed; unset, every run is listed. A value that is not a positive whole number is refused before anything is read: <host>: WELES_ACTIVITY_RUN_LIMIT is "<value>", which is not a positive whole number of runs: <parse error>.

On failure, api.error retains operation: connect, the system's code and message, and the observed address and port when supplied. On success it is null. The text command prints the same connection error beside the run inventory. A successfully collected inventory may therefore contain a failed API connection; inspect that field instead of treating the report command's exit status as API health. Stado Desktop exposes this evidence through Workloads → Read selected status → Complete workload receipt.

Declaration

The declaration is stado-rs/data/work/workloads.json, compiled into Stado with include_str!. It currently declares:

  • jeden-session: product jeden; interactive and detachable; no Weles action gate; stream report carries target, workspace, stdio stream, durable session ledger, and the detached job.
  • weles-capture: product weles-worker; gated by targets[].weles.actions entry generic_capture; plan schema wisent.weles-capture-plan.v1; receipt carries target, batch, accepted actions, and artifact prefixes. The whole plan is validated before target admission is contacted, so a malformed plan starts no capture. Execution is not atomic: captures run one at a time, every finished capture stays recorded, and the first failed capture stops the batch there, so a batch can end partly captured. A batch runs once: before its first capture Stado creates stado://weles-captures/<batch>/batch-claim.json only if it is absent, and a batch that already has one is refused with capture batch <batch> was already started (<uri> exists); a batch runs once, so plan a new batch id, and read this one with stado workload status weles-capture:<batch>. To finish a partly captured batch, plan the remaining captures under a new batch id. After every capture Stado rewrites batch-record.json beside it with each run's id, site, axis, artifact prefix, state (done or failed) and refusal. A capture whose trajectory failed keeps its run id, and the refusal names stado workload status weles-diagnostics:<run-id>. stado workload status weles-capture:<batch> reads that record plus one storage listing and never asks the Weles host; a batch Stado never recorded is refused as unknown, and a record the store cannot read now is reported as unreadable.
  • weles-browser-task: product weles-worker; gated by the plan's action in targets[].weles.actions; plan schema wisent.weles-browser-task-plan.v1; report carries host, action, run ID, ok, exit code, profile, the trajectory's result, the run's named outputs (and output_errors for any it could not read), and the run process's own retained standard output and standard error.
  • weles-diagnostics: product weles-worker; no action gate; plan schema wisent.weles-diagnostics-plan.v1; report carries target, run ID, artifact inventory, or exact artifact content.
  • weles-image-inspect: product weles-worker; gated by generic_browser_task; plan schema wisent.weles-image-inspect-plan.v1; report carries target, run ID, source URL, and image diagnostics.
  • weles-activity: product weles-worker; no action gate; no plan; report carries target, worker release, API runtime, and recorded runs.
  • weles-recordings: product weles-worker; no action gate; plan schema wisent.weles-recordings-plan.v1; report carries target, recordings directory, registry generation, updated_launch_agents (the names of the com.wisent.weles-*.plist files under the account's LaunchAgents this host rewrote; empty when the target is another host, with launch_agents_local: false), and whether they are local.
  • weles-browser-runtime: product weles-worker; no action gate; plan schema wisent.weles-browser-runtime-plan.v1; report carries required/observed Playwright components, browser-engine readiness, and repairs.
  • gui-automation: product cua-driver; no action gate; plan schema wisent.gui-automation-plan.v1; operations are enable, disable, and grant-accessibility; report carries autologin, remote management, Accessibility, and runtime state.
  • mobile-runtime: product weles-worker; gated by the target's mobile_runtime registry declaration; plan schema wisent.mobile-runtime-plan.v1; report carries Appium, drivers, platform-tools, and repairs. The declaration also carries address, the URL the Appium server listens on as reached from that host; a mobile crawl coordinator reads it from the registry, and no port is assumed when it is absent.

A Weles workload with an action gate is declared on a target only when weles.enabled is true and that target's weles.actions contains the resolved action. Other Weles reports require a weles declaration but do not invent an action gate. mobile-runtime requires targets[].mobile_runtime; gui-automation requires a local Darwin target; jeden-session requires a local target. With no target Stado chooses the first eligible declared host, except jeden-session, which retains its live-capacity scoring and durable-session host probing.

detachable is the field that decides whether a kind may run with nobody attached. It defaults to false, so a new kind is attach-only until its declaration says otherwise.

Exact declaration refusals

  • workload kind '<KIND>' is not declared; add it to stado-rs/data/work/workloads.json
  • <TARGET> declares no <KIND>; add it to stado-rs/data/work/workloads.json
  • the fleet declares no <KIND>; add it to stado-rs/data/work/workloads.json — also recorded as an unmet placement, with the platform the kind needs when it needs one (gui-automation needs darwin-arm64), so stado fleet needs can name the missing machine
  • workload kind '<KIND>' declares no reservation; add it to stado-rs/data/work/workloads.json
  • workload kind '<KIND>' declares a negative reservation; fix it in stado-rs/data/work/workloads.json
  • <KIND> plan declares schema <OBSERVED>, not <EXPECTED>; fix the whole plan before any work is enqueued
  • <KIND> requires --plan FILE with schema <SCHEMA>; add the plan declared by stado-rs/data/work/workloads.json
  • <KIND> accepts no plan; remove --plan because stado-rs/data/work/workloads.json declares none
  • " is interactive and cannot produce JSON; use stado workload attach <KIND>"
  • " is interactive; use stado workload attach <KIND>"
  • " is not interactive; use stado workload run <KIND>"
  • workload kind '<KIND>' is not detachable; add "detachable": true to stado-rs/data/work/workloads.json
  • <KIND> requires --task TEXT for a detached session
  • no Stado host can start <KIND> in <WORKSPACE>; <reason per candidate host>
  • <TARGET> declares no recordings directory; add it to the canonical registry
  • <TARGET> declares no mobile-runtime; add it to the canonical registry

Kind-specific plan refusals name the missing field and the plan that must be fixed. Host/runtime refusals retain the target, missing runtime component or credential, and the declaration or repair command the operator must change.

Weles is one process, com.wisent.weles, installed from its release by stado product update weles; no workload kind builds or restarts a separate Weles API unit on a host.

Stado Desktop

The selected host inspector includes a Workloads section. It reads stado workload list --json through the selected Stado API and lists every declared kind with its product, stream/receipt mode, and report fields. Non-interactive kinds expose Run through the shared operation form; kinds with a plan schema accept Plan JSON, including the replay field above. Read selected status accepts a workload kind and optional batch or run receipt ID. Complete workload receipt shows the returned standard output and standard error, and failures remain visible instead of being converted to success. These controls use the same workload command contract; they do not establish that a Figma copy succeeded.

Every interactive kind carries an Attach… button beside it. The sheet states the target, the selected Stado endpoint, the workspace, and an optional existing session ledger, then connects the stream through GET /api/operator/workload/attach. Output and diagnostics arrive live in two selectable views, Send input writes one line to the attached process's standard input, Finish input closes it, and the sheet reports the workload's own exit status or its refusal sentence. Changing the selected endpoint or closing the sheet disconnects the stream, which ends the attached process on the host.

Source: this website