Runs
Start one run with run, follow it with watch, and read the result with status, checks, receipt, diff and verify.
A run moves through seven states: queued, running, awaiting_confirmation, awaiting_sample_approval, succeeded, failed and cancelled. It stops in three of them, succeeded, failed and cancelled. There is no released state. A succeeded run’s version is a workspace candidate the moment it exists. The first succeeded run of a table makes that table live.
Every failed run carries a failure triple: failure_code, failure_detail and failed_stage. The triple sits on the run record, in the terminal event and in every refusal built from it. This client refuses a record whose triple is incomplete.
mr-data run --recipe RECIPE_ID --digest HEX (--sample | --full | --refresh | --backfill | --mode MODE) [--max-rows N] [--max-source-bytes N] [--window START END] [--sources-from RUN_ID] [--resource-class CLASS] [--confirm] [--no-activity] [--json]mr-data run --cancel RUN_ID [--json]mr-data run starts one run against a registered recipe under stated ceilings. The digest names which bytes of the recipe to run. This client states it and Studio checks it, never derives it. A digest that does not match the registered recipe refuses the run.
Start with --full. Full builds use progressive acquisition by default: an inspection checkpoint becomes available after about five minutes, then the same acquisition continues automatically. It needs no second approval or full run. Over the projection threshold Studio holds the run for confirmation before acquisition. Use --sample with a ceiling only for a deliberately bounded experiment; row limits do not bound acquisition time.
The client settles every argument before it resolves a credential. A command line it cannot accept never reaches the network.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--recipe |
RECIPE_ID |
yes | The registered recipe to run. | none |
--digest |
HEX |
yes | The recipe digest those bytes carry, bare lowercase hex. Checked, never derived. | none |
--mode |
sample|full|refresh|backfill|compact|replay |
one of these or a shorthand | The run mode to request. | none |
--sample |
none | see --mode |
Shorthand for --mode sample. Run an explicit bounded experiment with a ceiling; row limits do not limit acquisition time. |
none |
--full |
none | see --mode |
Shorthand for --mode full. Full builds use progressive acquisition automatically. Over the projection threshold it is held for confirmation first. |
none |
--refresh |
none | see --mode |
Shorthand for --mode refresh. Run forward from where the last successful run reached. |
none |
--backfill |
none | see --mode |
Shorthand for --mode backfill. Run one exact window, stated with --window. |
none |
--max-rows |
N |
no | Stop at this many rows from each source. A recipe with eight sources can deliver eight times this many rows. | server ceiling |
--max-source-bytes |
N |
no | Stop before fetching more than this from the sources. The run fails in every mode if it exceeds this limit because bytes already transferred cannot be truncated. | server ceiling |
--window |
START END |
with --backfill |
The half-open time range to cover. The start is included and the end is not. | none |
--sources-from |
RUN_ID |
with --mode replay |
Replay the retained raw inputs of this successful run. A replay never becomes live. Refused on any other mode. | none |
--resource-class |
CLASS |
no | The capacity class to ask for. Studio refuses one it cannot satisfy. | server default |
--confirm |
none | no | Authorize the run if it is held for a confirmation, instead of printing the projection. | off |
--cancel |
RUN_ID |
no | Cancel this run instead of starting one. Legal while it is queued, held or running. | none |
--no-activity |
none | no | Do not tell the dataset page what this command is doing. | off |
--json |
none | no | One JSON object instead of human lines. | off |
--sample with no ceiling at all is refused. A sample with no ceiling is a full run, dispatched and billed as one.
{ "schema_version": "mostlyright-thin-client-v4-run.v1", "status": "run_queued", "lane": "hosted", "dashboard_url": "https://mostlyright.md/datasets/d31017e0/runs/7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "watch_command": "mr-data watch 7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "run": { "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "workspace_id": "39ffafde-…", "recipe_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "recipe_digest": "9f3c…", "dataset_id": "d31017e0", "table_id": "3f8c2d10-6a4b-4c8e-9d1f-5b7a0c2e4d68", "mode": "sample", "status": "queued", "clamps": { "max_rows": 500 }, "resource_class": null, "created_at": "2026-09-12T09:00:00Z", "completed_at": null, "coverage": null, "table_version_id": null, "candidate_digest": null, "sample_run_id": null, "full_run_id": null }}Values are illustrative. status is run_queued when the returned run is queued, run_returned when submission returns a run already in another state, run_cancelled under --cancel, and run_confirmed when --confirm settled the confirmation gate. The run_confirmed payload also carries confirmed_from_status and the three projection numbers.
{ "schema_version": "mostlyright-thin-client-v4-run.v1", "status": "run_awaiting_confirmation", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "projected_bytes": 41000000000, "projected_runtime_s": 3600, "projected_cost": "12.40", "confirm_command": "mr-data run --recipe 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d --digest 9f3c… --mode full --confirm"}The run already exists, persisted awaiting_confirmation. confirm_command re-states the request that was sent, always spelled --mode MODE for every mode. --confirm authorizes the projection on it. This payload exits 2.
Errors:
| Code | When |
|---|---|
THIN_REQUEST_INVALID |
No --recipe. No --digest. No mode. A --sample with no ceiling. A --backfill with no --window. |
THIN_ARGUMENT_INVALID |
--mode replay with no --sources-from. --sources-from on a mode that acquires its own inputs. A --sources-from that is not a run identifier. |
THIN_STUDIO_CONFIRM_REQUIRED |
Studio raises it and this client handles it. Without --confirm it becomes the run_awaiting_confirmation payload. |
THIN_RESPONSE_INVALID |
Studio held the run for a confirmation without saying what it projected. |
THIN_STUDIO_* |
A digest that does not match the registered recipe, a resource class Studio cannot satisfy, a cancel of a run that already finished, and the rest of Studio’s refusals. |
mr-data run --recipe "$RECIPE_ID" --digest "$DIGEST" --full --jsonmr-data watch "$RUN_ID"mr-data checks "$RUN_ID" --jsonmr-data runs [--status STATUS] [--mode MODE] [--limit N] [--json]mr-data runs reports this workspace’s own runs, newest first, narrowed by status and by mode. Use it to find a run identifier, or to see what has been happening.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--status |
one of seven | no | queued, running, awaiting_confirmation, awaiting_sample_approval, succeeded, failed, cancelled. A word outside this list is refused before the network. |
every status |
--mode |
one of six | no | sample, full, refresh, backfill, compact, replay. |
every mode |
--limit |
N |
no | How many runs to report. The command follows the workspace’s own pages until it has that many or there are no more, up to 200 pages. | 20 |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-runs.v1", "status": "runs_listed", "lane": "hosted", "count": 2, "runs": { "run 1": { "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "mode": "refresh", "status": "failed", "created_at": "2026-09-12T08:00:00Z", "failure": { "failure_code": "CONTENT_LENGTH", "failure_detail": "…", "failed_stage": "acquire" } } }, "next_cursor": null, "filters": { "status": "failed", "mode": null }}Values are illustrative. Every listed row carries those four members. A row carries failure, the nested triple, only on a run that failed. It carries pages, one sentence about page coverage, only on a run whose record holds a page-collection block.
Errors: THIN_ARGUMENT_INVALID for a --status or --mode naming a word this contract does not have.
mr-data runs --limit 5 --json | jq -r '.runs[] | "\(.run_id) \(.mode) \(.status)"'status
Section titled “status”mr-data status RUN_ID [--receipts] [--json]mr-data status reports which of the seven states one run is in, what it delivered, and why it failed. Studio refuses a run in another workspace the same way it refuses one that does not exist.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
RUN_ID |
id | yes | The run to report, by the identifier the start printed. | none |
--receipts |
none | no | No effect here. The run record this command reads already carries the digests, sources, timings, checks and causes. The payload reports the flag under flags_without_effect. |
off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-run-status.v1", "status": "run_status_reported", "lane": "hosted", "terminal": true, "run": { "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "mode": "full", "status": "failed", "failed_stage": "acquire", "…": "…" }, "pages": "…", "failure": { "failure_code": "CONTENT_LENGTH", "failure_detail": "…", "failed_stage": "acquire" }, "refusal": { "code": "CONTENT_LENGTH", "detail": "…" }}Values are illustrative. run includes the full run record projected onto the same members mr-data run prints. failure and refusal appear only on a failed run. projection appears only on one held at the confirmation gate. pages is a sentence about page coverage when the run has one. The command exits 2 when run.status is failed.
Errors: THIN_ARGUMENT_INVALID for a positional that is not a run identifier. THIN_NOT_FOUND. THIN_RESPONSE_INVALID when a failed run’s triple is incomplete.
mr-data status "$RUN_ID" --json | jq '{state: .run.status, why: .failure}'mr-data watch RUN_ID [--from-seq N] [--last-event-id CURSOR] [--json]mr-data watch follows one run’s live progress and prints what it stopped on. The stream resumes across cuts on its own. --last-event-id resumes a watch that ended on this side. The run keeps going when nothing is watching it.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
RUN_ID |
id | yes | The run to follow, by the identifier the start printed. | none |
--from-seq |
N |
no | Replay from after this log position. Ignored once a cursor is known. | none |
--last-event-id |
CURSOR |
no | Resume from this cursor, as a previous watch reported it. | none |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-watch.v1", "status": "run_terminal", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "dashboard_url": "https://mostlyright.md/datasets/d31017e0/runs/7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "terminal_event": "run_succeeded", "stream": { "last_event_id": "88-…", "last_sequence": 88, "events_seen": 88, "progress_seen": 41, "stage": "persist", "stage_index": 5, "stage_total": 5, "status": "succeeded", "end_reason": "run_succeeded", "reconnects": 1, "halted": false, "expired": false, "failure": null, "diagnostic": null }}Values are illustrative. terminal_event is run_succeeded, run_failed or null. On run_failed the payload also includes failure and refusal built from the same triple, and the command exits 2. Progress events are narration. Nothing in stream is evidence about the build.
Errors: THIN_STREAM_UNAVAILABLE, THIN_STREAM_FRAME_INVALID, THIN_STREAM_LINE_TOO_LONG, THIN_STREAM_REFUSED, THIN_NOT_FOUND.
mr-data watch "$RUN_ID" || echo "the run failed; mr-data status $RUN_ID says why"checks
Section titled “checks”mr-data checks RUN_ID [--no-activity] [--json]mr-data checks reports how each check the recipe declared came out for one run. It exits 2 when any declared check failed.
The command reads the run record first, to learn whether the run has finished. It reads no version, no promotion and no approval.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
RUN_ID |
id | yes | The run to read. | none |
--no-activity |
none | no | Do not tell the dataset page what this command is doing. | off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-checks.v1", "status": "checks_reported", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "dashboard_url": "https://mostlyright.md/datasets/d31017e0/runs/7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "run_status": "succeeded", "checks_scope": "version", "checks": { "check 1": { "check_id": "max_temp_within_range", "passed": true, "detail": null, "scope": "version" } }, "check_count": 4, "checks_passed": 4, "checks_failed": 0}Values are illustrative. checks_scope comes off the run’s own receipt, and is null when that receipt could not be read. On any scope but version, the payload carries a note. The checks ran over the rows this run added, not over every row of the version it composed.
On a failed run the payload includes empty checks, zero counts and the failure and refusal blocks. A required check that fails terminalizes the run, so the failure names the failed check. The command exits 2 when checks_failed is non-zero or the run failed.
Errors: THIN_NO_CHECK_RESULTS in three cases. The run has not finished, and the message names the state and points at mr-data watch. The run was cancelled before its checks ran. Studio has the run as succeeded and its log carries no terminal record.
mr-data checks "$RUN_ID" --json && echo "safe to publish"receipt
Section titled “receipt”mr-data receipt RUN_ID [--no-activity] [--json]mr-data receipt prints the receipt, the record of how one run was built: the sources it fetched, their digests, the timings, the checks and the causes. The document arrives verbatim under receipt.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
RUN_ID |
id | yes | The run to read. | none |
--no-activity |
none | no | Do not tell the dataset page what this command is doing. | off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-receipt.v1", "status": "receipt_reported", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "dashboard_url": "https://mostlyright.md/datasets", "artifact_id": "5e2a9c74-1d6b-4308-b9c5-8f0a3d7e2b41", "digest": "9f3c…", "size_bytes": 18422, "receipt": {}}Studio caps a run at one artifact per kind per attempt. A run that never retried wrote one receipt. A run that retried wrote one per attempt, and this command reads the first listed. artifact_id says which one.
Errors: THIN_NO_MATCHING_ARTIFACT when the run wrote no receipt. THIN_ARTIFACT_TOO_LARGE, THIN_NOT_FOUND.
mr-data receipt "$RUN_ID" --json | jq '.receipt'mr-data note --run RUN [--heading TEXT] [--cell-id ID] [--markdown-file FILE] [--blocks-file FILE] [--phase {acquire,parse,transform,check,persist}] [--checkpoint N] [--revise CELL_ID] [--list] [--json]mr-data note writes one cell of the decision record into a run’s log while the run is going. A cell is one heading and a markdown body, with typed blocks beside it. The dataset page lays out the blocks.
Writing the same cell identifier again revises that cell in place, and the superseded version stays in the log. A run’s log closes when the run reaches a terminal state. Research a person will read next week goes on the dataset instead.
A cell is not sealed. No receipt quotes it, and it is not evidence about the build.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--run |
RUN |
yes | The run this cell is about. | none |
--list |
none | no | List the cells this run holds instead of writing one, superseded ones flagged. | off |
--heading |
TEXT |
to write | The cell’s one outcome-led line. At most 200 characters. | none |
--cell-id |
ID |
no | A stable identifier. Writing it again revises the cell in place. Letters, digits, dot, dash and underscore, opening on a letter or digit, at most 64 characters. | derived from the heading |
--markdown-file |
FILE |
no | The cell body as UTF-8 markdown. At most 65536 bytes. | standard input |
--blocks-file |
FILE |
no | The cell’s typed blocks as a JSON array of at most 64 source, decision or clarification objects. |
none |
--phase |
one of five | no | acquire, parse, transform, check, persist, the worker protocol’s own stages, so the cell lands in a bucket the log already has. |
none |
--checkpoint |
N or cursor |
no | The run-event sequence this cell is about, as the number or as the cursor mr-data watch printed beside it. |
the cell’s own append sequence |
--revise |
CELL_ID |
no | The identifier of a different cell this one supersedes. Revising a cell under its own identifier needs no flag. | none |
--json |
none | no | One JSON object instead of human lines. | off |
Block fields, by kind:
| Kind | Required | Optional |
|---|---|---|
source |
name, provides, status (using, considered or rejected) |
host, grain, coverage, reason |
decision |
chose, because |
over |
clarification |
question |
answer, answered_at (only beside an answer) |
Labels are at most 200 characters, hosts 255, text 1024. A key the kind does not declare refuses rather than going unread.
{ "schema_version": "mostlyright-thin-client-v4-narrative.v1", "status": "narrative_cell_appended", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "workspace_id": "39ffafde-…", "cell": { "cell_id": "dropped-station-kbos", "heading": "Dropped KBOS: the feed stopped in 2023", "checkpoint_seq": 41, "revision_of": null, "sequence": 3, "cursor": "3-…", "appended_at": "2026-09-12T09:00:00Z", "payload_digest": "9f3c…", "superseded": null, "markdown_bytes": 420, "block_count": 1, "phase": "acquire" }, "sealed": false, "not_evidence": "…", "flags_without_effect": {}}--list answers status: "narrative_listed" with cell_count, rendered_count, source_truncated and cells.
Errors: THIN_NARRATIVE_CELL_TOO_LARGE, THIN_NARRATIVE_CELL_CONTROL_CHARACTER and THIN_NARRATIVE_REVISION_INVALID. THIN_REQUEST_INVALID for a bad --cell-id, a --blocks-file over its ceiling or holding more than 64 blocks, or a block field the kind does not declare. THIN_RESPONSE_INVALID when Studio does not describe the cell as unsealed.
mr-data note --run "$RUN_ID" \ --heading "Dropped KBOS: the feed stopped in 2023" \ --phase acquire \ --blocks-file blocks.json < body.mdmr-data diff LEFT RIGHT [--columns-only] [--json]mr-data diff compares two runs and says what changed. Without --columns-only it compares the row count, the window covered and the data’s own fingerprint as well as the columns. A run that failed at acquire wrote no column profile. That side reads as unknown.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
LEFT |
run id | yes | The first run. | none |
RIGHT |
run id | yes | The second run. | none |
--columns-only |
none | no | Compare the columns alone, out of each run’s column profile. | off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-comparison.v1", "status": "builds_differ", "lane": "hosted", "summary": "…", "change count": 2, "changes": { "change 1": "…", "change 2": "…" }, "first run": { "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "row_count": 4100000, "columns": {}, "columns_known": true, "column_profile": "5e2a9c74-1d6b-4308-b9c5-8f0a3d7e2b41", "candidate_digest": "9f3c…" }, "second run": { "run_id": "run_02J…", "row_count": 4210000, "columns": {}, "columns_known": true, "column_profile": "art_02J…", "candidate_digest": "a1b2…" }, "columns not compared": [], "columns only": false}Values are illustrative. status is builds_identical or builds_differ. columns not compared appears only when a side wrote no column profile. columns only appears only under the flag.
Errors: THIN_NO_FINGERPRINT, THIN_NOT_FOUND, THIN_ARGUMENT_INVALID.
mr-data diff "$YESTERDAY_RUN" "$TODAY_RUN" --json | jq '.summary, .changes'verify
Section titled “verify”mr-data verify RUN_ID [--deep] [--json]mr-data verify holds this run’s table manifest against the parts it names. Without --deep it fetches no part, so a clean answer does not prove every part’s bytes still match the manifest. With --deep it fetches every part this run wrote and re-hashes it against the digest the manifest states.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
RUN_ID |
id | yes | The run to read. | none |
--deep |
none | no | Fetch every part and re-hash its bytes. | off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-table-verification.v1", "lane": "hosted", "run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90", "dashboard_url": "https://mostlyright.md/datasets", "deep": true, "status": "table_verified", "layout_format_version": 2, "manifest_digest": "9f3c…", "part_count": 128, "row_count": 4100000, "total_bytes": 811000000, "parts_read": 128, "findings": []}Values are illustrative. status is table_verified or table_disagrees. findings names each disagreement, and a non-empty findings exits 2. parts_read is 0 without --deep.
Errors: THIN_VERSION_PART_UNREACHABLE, THIN_NO_SEALED_RESULT, THIN_RESULT_DIGEST_MISMATCH, THIN_NOT_FOUND.
mr-data verify "$RUN_ID" --deep --json | jq '{status, part_count, findings}'See also
Section titled “See also”- CLI reference: conventions, exit codes, environment variables
- Reading a table:
peek,query,download,parts - Tables: what a succeeded run makes live
When the same run is returned
Section titled “When the same run is returned”Identical submissions are idempotent. A returned failed or completed run is not a new attempt. Read run.status; run_returned does not mean queued. Failed runs return exit code 2 with the recorded failure. For recognized execution failures, the CLI supplies the existing --retry command. A deterministic source or recipe failure needs a corrected recipe.