Quantum jobs on a provider outlive what sent them: the record, the command line, the desktop app and the Studio #590

Merged
dcharlot merged 5 commits from wai/quantum-provider-jobs-2 into main 2026-11-03 00:42:50 -05:00
Owner

This replaces #581, which conflicted in the changelog with #578. It is #581's two commits, followed by three for the desktop app and the Studio, which need #581's API and come with it.

A job on a person's own provider now outlives whatever sent it.

Until now a run waited in its process. A job not finished in two hours was cancelled at the provider, losing its place in the queue. If the process stopped first, the job went on and its id was gone, so its counts could not be read, and it may still have been billed. A device's queue can take days.

What changes

A job is written down before it is sent, and nothing is held while it waits.

Moment What happens now
Before sending The job, its compiled program and what its receipt will say are recorded. If that cannot be written, nothing is sent
The provider takes it Its id is added to the record
The wait runs out The job is left at the provider. Only someone asking cancels it
The process stops The job goes on. Any later process collects it by its handle
Collecting Counts are read against the program that was sent, whatever the device's calibration has become since
A request that did not complete It may have reached the provider, so it is never sent again: that could be a second bill. The record says so

wai-quantum-qpu: job::start, check, follow, cancel and progress_of, and a ledger module. job::run is unchanged: it keeps no record, so it still cancels what outlasts its bound, and nothing goes on being spent with no one to collect it.

wai-quantum-cli: run --provider records its job and says its handle, and --no-wait answers at once. New: providers jobs, providers collect and providers cancel. collect exits 0 done, 1 ended otherwise, 2 still at the provider, and 75 when it could not ask, which says nothing of the job.

MCP: run_on_provider answers within a call (50 s, as the job service does) with the job running and its id, in job.v1's form. New tools: get_provider_job, cancel_provider_job, list_provider_jobs. Their answers use compact schemas: pointing them at job.v1 added about 770 KB to the tool list every host loads, and this adds about 12 KB.

wai.quantum.provider/1: a new §5.7 on what a job outlasting its client asks of a provider and a client. Nothing on the wire changes.

What a caller sees differently

A run --provider that outlasts its two hours now exits 2 with the job still at the provider, where before it cancelled the job and failed. The change is in the changelog. wai breaking reports no unacknowledged breaking change.

Checked

  • wai-quantum-qpu: cargo test --locked, every target passed; clippy clean. The workflow's pinned library count goes from 34 to 46 for the twelve new tests, and five of them are named so CI fails if any stops running; both checks were replayed against the first CI run's log.
  • wai-quantum-cli: cargo test --locked --release, every target passed, including two new end-to-end tests over HTTP: the sending command is killed mid-wait and the job is collected later; an agent comes back to a job by id and cancels it. Clippy clean with and without default features. smoke.sh, every check held.
  • The promises themselves: ten deliberate breakages, each caught by the test that names it. Among them: cancelling when the wait runs out, sending before recording, treating an incomplete request as refused, asking after a job left mid-send, and letting any handle reach a file.
  • A collected job against a run: on a seeded reference provider, the sealed counts, compiled program, provider, calibration, cost and energy are identical to a direct run, and the receipt verifies.
  • A real bug these found: the count of status checks reset between commands, so a job collected over several processes sealed the wrong count.
  • wai breaking, the repository checks and the public-name check are clean. The docs checker passes the changed pages apart from blocks that need the service or Rust options, which CI runs in full.

Not checked

  • No real device. Everything ran against the reference provider. The built-in protocols go through the same code and the same recorded program, but have not collected a job across processes against a live service.
  • API compatibility runs in CI only; cargo-semver-checks is not on the hosts I ran this on.

The desktop app and the Studio: a job on your own hardware outlives the window

The desktop app called job::run, so a run waited in the app for up to two hours with the Studio held, then cancelled the job.

wai-quantum-desktop (src/main.rs)

  • run_on_provider writes the job down, sends it and waits up to ten seconds (job::start, then job::follow). It answers with the job done, still running with its id (pj-…), or ended with why. A run that is not free is still refused, with its plan, until the interface's dialog has asked.
  • provider_job {id, cancel} asks after a job once (job::check), or cancels it (job::cancel) when the person pressed Cancel. A job that has ended is answered from its record, with no provider asked.
  • provider_jobs lists the jobs sent, asking no provider.
  • Waiting never cancels a job.

Quantum Studio (src/hardware.js, src/main.js, locales in English and Chinese)

  • While a job waits, the status line says where it is and how many times it has been asked after. The page asks every 2 seconds at first, then less often, up to once a minute.
  • Cancel cancels the job at the provider. A job that finished before the cancel arrived gives its result.
  • Wait in the background leaves the job at the provider and frees the Studio.
  • A job left waiting, from this window or one closed earlier, is followed while the app is open. Its result goes to the history when it finishes.
  • Your jobs lists every job sent, shows a finished one, and cancels one still waiting.
  • A check that fails during the wait does not end it. A cancel that fails says so, and the job is followed in the background.
  • A job that ended (failed, cancelled) is read as an answer, not as a failed command: one with an error and no status is the failure.

The release's cask (release/manifests.py, wai-quantum-desktop.rb.in): it now passes brew style, with a blank line between each disk image's checksum and its address and depends_on :macos. Written from the 0.1.1 release's own assets, the cask has no finding, and the Scoop manifest and latest.json are byte for byte the ones published.

The views page is rebuilt for the new words in catthehacker/ubuntu:act-22.04 with Rust 1.98.0 on x86_64, as the views workflow rebuilds it. Its engine had not been rebuilt since cc08f85a0 and is current again.

Tests

  • The app: a_job_still_queued_is_answered_by_its_id_and_cancelled_only_when_asked.
    • A job held in the reference provider's queue comes back running with its id and is listed.
    • Asking by that id gives the counts sealed with the app's key, and the same answer after with no provider asked.
    • A second job is cancelled only when asked.
    • A handle that tries to leave the records is refused.
    • The existing provider test now also checks that a refused run is not recorded.
    • The studio workflow names the new test on macOS, Linux and Windows.
  • The page (test/hardware.test.mjs): asking by id until done, the status line, Cancel at the provider, a cancel that arrives after the job finished, a failed check, Wait in the background with its result later, a reopened window following a job left waiting, and the jobs list's words.
  • release/manifests_test.py checks the new cask lines.

Checked

  • On the build host, with this branch:
    • the Studio's hardware, telemetry, update and locale tests;
    • build-views.mjs --check;
    • the Studio in a browser (studio.test.mjs);
    • the desktop app's tests, clippy with -D warnings, and check;
    • manifests_test.py.
  • The public-name check is clean.
  • No real device was used: the provider in the tests is the reference provider.

🤖 Generated with Claude Code

This replaces #581, which conflicted in the changelog with #578. It is #581's two commits, followed by three for the desktop app and the Studio, which need #581's API and come with it. A job on a person's own provider now outlives whatever sent it. Until now a run waited in its process. A job not finished in two hours was **cancelled at the provider**, losing its place in the queue. If the process stopped first, the job went on and its id was gone, so its counts could not be read, and it may still have been billed. A device's queue can take days. ## What changes A job is written down before it is sent, and nothing is held while it waits. | Moment | What happens now | |---|---| | Before sending | The job, its compiled program and what its receipt will say are recorded. If that cannot be written, nothing is sent | | The provider takes it | Its id is added to the record | | The wait runs out | The job is left at the provider. Only someone asking cancels it | | The process stops | The job goes on. Any later process collects it by its handle | | Collecting | Counts are read against the program that was sent, whatever the device's calibration has become since | | A request that did not complete | It may have reached the provider, so it is **never sent again**: that could be a second bill. The record says so | **`wai-quantum-qpu`:** `job::start`, `check`, `follow`, `cancel` and `progress_of`, and a `ledger` module. `job::run` is unchanged: it keeps no record, so it still cancels what outlasts its bound, and nothing goes on being spent with no one to collect it. **`wai-quantum-cli`:** `run --provider` records its job and says its handle, and `--no-wait` answers at once. New: `providers jobs`, `providers collect` and `providers cancel`. `collect` exits 0 done, 1 ended otherwise, 2 still at the provider, and 75 when it could not ask, which says nothing of the job. **MCP:** `run_on_provider` answers within a call (50 s, as the job service does) with the job running and its id, in `job.v1`'s form. New tools: `get_provider_job`, `cancel_provider_job`, `list_provider_jobs`. Their answers use compact schemas: pointing them at `job.v1` added about 770 KB to the tool list every host loads, and this adds about 12 KB. **`wai.quantum.provider/1`:** a new §5.7 on what a job outlasting its client asks of a provider and a client. Nothing on the wire changes. ## What a caller sees differently A `run --provider` that outlasts its two hours now exits 2 with the job still at the provider, where before it cancelled the job and failed. The change is in the changelog. `wai breaking` reports no unacknowledged breaking change. ## Checked - **`wai-quantum-qpu`:** `cargo test --locked`, every target passed; clippy clean. The workflow's pinned library count goes from 34 to 46 for the twelve new tests, and five of them are named so CI fails if any stops running; both checks were replayed against the first CI run's log. - **`wai-quantum-cli`:** `cargo test --locked --release`, every target passed, including two new end-to-end tests over HTTP: the sending command is killed mid-wait and the job is collected later; an agent comes back to a job by id and cancels it. Clippy clean with and without default features. `smoke.sh`, every check held. - **The promises themselves:** ten deliberate breakages, each caught by the test that names it. Among them: cancelling when the wait runs out, sending before recording, treating an incomplete request as refused, asking after a job left mid-send, and letting any handle reach a file. - **A collected job against a run:** on a seeded reference provider, the sealed counts, compiled program, provider, calibration, cost and energy are identical to a direct run, and the receipt verifies. - **A real bug these found:** the count of status checks reset between commands, so a job collected over several processes sealed the wrong count. - `wai breaking`, the repository checks and the public-name check are clean. The docs checker passes the changed pages apart from blocks that need the service or Rust options, which CI runs in full. ## Not checked - **No real device.** Everything ran against the reference provider. The built-in protocols go through the same code and the same recorded program, but have not collected a job across processes against a live service. - **API compatibility** runs in CI only; `cargo-semver-checks` is not on the hosts I ran this on. ## The desktop app and the Studio: a job on your own hardware outlives the window The desktop app called `job::run`, so a run waited in the app for up to two hours with the Studio held, then cancelled the job. **wai-quantum-desktop** (`src/main.rs`) - `run_on_provider` writes the job down, sends it and waits up to ten seconds (`job::start`, then `job::follow`). It answers with the job done, still running with its id (`pj-…`), or ended with why. A run that is not free is still refused, with its plan, until the interface's dialog has asked. - `provider_job {id, cancel}` asks after a job once (`job::check`), or cancels it (`job::cancel`) when the person pressed Cancel. A job that has ended is answered from its record, with no provider asked. - `provider_jobs` lists the jobs sent, asking no provider. - Waiting never cancels a job. **Quantum Studio** (`src/hardware.js`, `src/main.js`, locales in English and Chinese) - While a job waits, the status line says where it is and how many times it has been asked after. The page asks every 2 seconds at first, then less often, up to once a minute. - **Cancel** cancels the job at the provider. A job that finished before the cancel arrived gives its result. - **Wait in the background** leaves the job at the provider and frees the Studio. - A job left waiting, from this window or one closed earlier, is followed while the app is open. Its result goes to the history when it finishes. - **Your jobs** lists every job sent, shows a finished one, and cancels one still waiting. - A check that fails during the wait does not end it. A cancel that fails says so, and the job is followed in the background. - A job that ended (`failed`, `cancelled`) is read as an answer, not as a failed command: one with an `error` and no `status` is the failure. **The release's cask** (`release/manifests.py`, `wai-quantum-desktop.rb.in`): it now passes `brew style`, with a blank line between each disk image's checksum and its address and `depends_on :macos`. Written from the 0.1.1 release's own assets, the cask has no finding, and the Scoop manifest and `latest.json` are byte for byte the ones published. **The views page** is rebuilt for the new words in `catthehacker/ubuntu:act-22.04` with Rust 1.98.0 on x86_64, as the views workflow rebuilds it. Its engine had not been rebuilt since cc08f85a0 and is current again. ### Tests - **The app:** `a_job_still_queued_is_answered_by_its_id_and_cancelled_only_when_asked`. - A job held in the reference provider's queue comes back running with its id and is listed. - Asking by that id gives the counts sealed with the app's key, and the same answer after with no provider asked. - A second job is cancelled only when asked. - A handle that tries to leave the records is refused. - The existing provider test now also checks that a refused run is not recorded. - The studio workflow names the new test on macOS, Linux and Windows. - **The page** (`test/hardware.test.mjs`): asking by id until done, the status line, Cancel at the provider, a cancel that arrives after the job finished, a failed check, Wait in the background with its result later, a reopened window following a job left waiting, and the jobs list's words. - `release/manifests_test.py` checks the new cask lines. ### Checked - On the build host, with this branch: - the Studio's hardware, telemetry, update and locale tests; - `build-views.mjs --check`; - the Studio in a browser (`studio.test.mjs`); - the desktop app's tests, clippy with `-D warnings`, and `check`; - `manifests_test.py`. - The public-name check is clean. - No real device was used: the provider in the tests is the reference provider. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A run on a person's provider waited in its process, and a job that had not
finished in two hours was cancelled at the provider, losing its place in the
queue. If the process stopped first, the job went on and its id was gone, so its
counts could not be read. A device's queue can take days.

A job is now written down before it is sent, with everything sealing it will
need, and the provider's id as soon as the provider gives one. Nothing that
cannot be recorded is sent. Any process can come back to the record, and its
counts are read against the program that was sent, whatever the device's
calibration has become. Running out of patience leaves the job where it is. A
request that did not complete may have reached the provider, so it is never sent
again: that could be a second bill.

The command line gains run --no-wait and providers jobs, collect and cancel. The
local MCP server's run_on_provider answers within a call, and gains
get_provider_job, cancel_provider_job and list_provider_jobs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The library's pinned count goes from 34 to 46, for the twelve tests the provider
job record adds. Five of them are named so CI fails if any stops running: a
recorded job outlives what sent it, running out of patience does not cancel it,
nothing is sent that was not recorded, a job that may have been sent is never
sent again, and a handle reaches no file but a record's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run on one of the person's providers waited inside the desktop app for up
to two hours, holding the Studio, and then cancelled the job, giving up its
place in a device's queue. The app now writes the job down before it sends
it, with the command line's records of jobs on a provider (#581), and asks
after it by its id.

- wai-quantum-desktop: run_on_provider answers within ten seconds, done or
  still running with its id; provider_job asks after a job once or cancels
  it; provider_jobs lists the jobs sent, asking no provider.
- Quantum Studio: while a job waits, Cancel cancels it at the provider and
  "Wait in the background" leaves it there and frees the Studio. A job left
  waiting, from this window or an earlier one, is followed while the app is
  open, and its result goes to the history when it finishes. Your jobs lists
  every job sent. A check that fails during the wait does not end it.
- The tests: the app's queued job answered by its id, sealed with the app's
  key, the same answer after, and cancelled only when asked; the page's
  wait, cancel, cancel-after-finish, failed check, background and reopened
  window. The studio workflow names the new app test on every leg.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 0.1.1 cask had three findings in the tap: no blank line between each disk
image's sha256 and its url, and no depends_on :macos. The writer now puts both
in, and manifests_test.py checks them. Written from the 0.1.1 release's own
assets, the cask has no finding, and the Scoop manifest and latest.json are
byte for byte the ones published. (A minimum version is not given: the Intel
build states macOS 10.13, below what Homebrew supports, so brew style calls
it redundant.)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum views: the page rebuilt for the Studio's new hardware words, in the image CI rebuilds it in
All checks were successful
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 14s
wai-quantum service / service · macos (pull_request) Successful in 4m17s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m30s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 23s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 23s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 16m32s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (pull_request) Successful in 1m47s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (pull_request) Successful in 1m34s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m31s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 12m51s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (pull_request) Successful in 1m42s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 2m8s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 29s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 3m11s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 54s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 4m57s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 8m23s
wai-quantum extension points / the registry and the example · linux-arm64 (pull_request) Successful in 2m38s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 1m58s
wai-quantum views in agent hosts / views · the page and its engine (pull_request) Successful in 3m28s
wai-quantum views in agent hosts / views · a scripted host (pull_request) Successful in 3m31s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m40s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (pull_request) Successful in 1m51s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m37s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 8m1s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 6m58s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m59s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 10m57s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 16m55s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h3m24s
8c296c402f
The views page carries the Studio's locales, so the words for a job waiting
at a provider changed its script. Rebuilt with wai/wai-quantum-views/build.sh
in catthehacker/ubuntu:act-22.04 with Rust 1.98.0 on x86_64, as the views
workflow rebuilds it: build-views.mjs --check passes. The engine inside it is
rebuilt too, from the engine as it is now; it had not been rebuilt since
cc08f85a0, and the 0.8.0 release and the registry, refusal and spd changes
since then changed its bytes, not its size.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!590
No description provided.