Create your first play
Where you are in the journey: you searched, and nobody had crystallized your task. Good — that means the work you're about to do once is worth keeping. This section walks the full arc: explore in a workspace, prove the result, and compile the trace of what worked into a play anyone can run.
Here's the idea that makes this different from "write a script":
You don't write the play. You do the work once — carefully, in a recorded workspace — and then compile the recording.
The product's own words for it: "This is not code generation. This is compilation from execution traces."
Step 0 · Open a workspace
A workspace is a flight recorder for a task. Everything you call — every API response, process capture, page snapshot — lands in it as an addressable, queryable unit.
rote init check-npm-package --seqThe canonical workflow the workspace banner prints is the map for everything below:
01WORKFLOW: Explore → Export → Inspect → Lint → RunStep 1 · Explore — do the work once, on the record
Say your task is: "given a package name, tell me its latest npm version and weekly downloads." Do it with recorded calls instead of raw shell:
rote proc run curl -s https://registry.npmjs.org/left-pad01@1 captured (process · 34ms)That @1 is the first big idea of the product meeting you in the terminal. The response didn't scroll away — it landed on disk as a Context Addressable Unit: immutable, typed by origin, queryable forever. Don't re-fetch; query:
01rote query @1 '.stdout.text | fromjson | ."dist-tags".latest' -r02# 1.3.0Now the second reading — downloads come from a different endpoint:
01rote proc run curl -s https://api.npmjs.org/downloads/point/last-week/left-pad02# @2 captured03rote query @2 '.stdout.text | fromjson | .downloads' -r04# 2843701Explore in the shape of the play it will become. One reading per call — don't cram both fetches into one mega-command. Independent readings should be independent captures, because in a moment each capture becomes a step, and independent steps run in parallel. The trace you leave behind is the dataflow graph of your future play.
(This example uses the shell reach. The same exploration works through API adapters or the browser — section 7 — and all three land in the workspace identically.)
Step 2 · Anchor it — the pending stub
Before you present results to anyone (including yourself tomorrow), write a pending stub — a context anchor that survives session restarts and marks "reusable work happened here":
01rote play pending write check-npm-package \02 --name check-npm-package \03 --description "Latest version and weekly downloads for an npm package" \04 --notes "two independent registry reads + one join"This is the product's save-gate discipline: the pending lifecycle is mandatory for reusable results. It costs one command and it means the work can't silently evaporate.
Step 3 · Crystallize — compile the trace
rote workspace export check-npm-package --params package_nameThe exporter runs five operations over your trace — this is the compiler pass:
- Filter — failed attempts, retries, and dead ends don't ship
- Reify — the hardcoded
left-padbecomes a typedpackage_nameparameter - Resolve — your
@1/@2references become explicit step dependencies - Fingerprint — API identities are embedded so drift is detectable later
- Generate — out comes a runnable play: a frontmatter
steps:DAG plus a presentation
What you get is a main.ts whose top half looks like this (trimmed):
01steps:02 fetch_registry:03 type: process.exec04 timeout_ms: 4500005 argv: [curl, -s, "https://registry.npmjs.org/$package_name"]06 fetch_downloads:07 type: process.exec08 timeout_ms: 4500009 argv: [curl, -s, "https://api.npmjs.org/downloads/point/last-week/$package_name"]10 compose_report:11 type: process.exec12 timeout_ms: 1500013 depends_on: [fetch_registry, fetch_downloads]14 argv:15 - python316 - -c17 - "…join the two readings…"18 - '@fetch_registry{$.stdout.text | fromjson | ."dist-tags".latest}'19 - '@fetch_downloads{$.stdout.text | fromjson | .downloads}'Look at what the compiler preserved: your two independent explorations became two root steps (they'll run in parallel), and your queries became value edges — declared dataflow with the exact jq paths you proved during exploration.
Step 4 · Inspect, lint, run
Trust, but verify — x-ray your own creation:
01rote play run https://play.modiqo.ai/modiqo/play-dag play=./main.ts02# 3 steps · 2 layers ← two parallel roots + a join. Good shape.Then the gate everything must pass:
rote play lint main.tsLint checks the contract end to end: frontmatter validity, the FlowOutput three-mode contract (out.human / out.summary / out.result), literal stepName() references, declared dependencies. Fix what it names; it's specific.
Then run it for real — including the runs you hope fail:
01rote play run main.ts package_name=left-pad # happy path02rote play run main.ts package_name=surely-not-a-real-pkg-zz # degrades with a labeled unknown?03rote play run main.ts 'package_name=!!' # fails closed at validation?Test the negative space before release. A play earns trust by being honest when the world misbehaves: expected absence should complete with a labeled degraded/skipped row in the stage ledger; genuinely bad input should fail closed with downstream steps BLOCKED and a working --resume. (Section 4 explains the two-lane failure model.)
Step 5 · Release and share
01rote play release check-npm-package # draft → released02rote registry play push main.ts myorg # → immutable version on the hubThe push readout hands you the thing this was all for:
01play_reference: myorg/check-npm-package@0.1.002play_uri: https://play.modiqo.ai/myorg/check-npm-package@0.1.0Close the loop with a canonical readback — run the published URI and confirm the hub serves what you shipped:
cd /tmp && rote play run https://play.modiqo.ai/myorg/check-npm-package package_name=left-pad --yesAnd tidy the trail:
rote play pending discard check-npm-packageThe shape standards, in one screen
The full craft — "Effective play standards" in rote guidance typescript play-creation — is twelve rules. The ones that change how your plays feel to users:
| Standard | Why it matters |
|---|---|
| One reading = one step | multi-source work as one script can't parallelize, checkpoint, resume, or blame per source |
| Declare both edge kinds | depends_on for ordering, @step{jq} for dataflow — provable, drawable, honest |
| Maximize root steps | independence you declare is speed the runner gives you free |
| Degrade, never die — only where honest | one flaky source shouldn't kill an eight-source report; a labeled unknown should |
| Non-interactive subprocesses need consent flags | steps have no TTY; a subcommand that prompts will hang — pass its --yes |
| Test the negative space | the failure behaviors are the product; verify them before anyone else finds them |
| Verify the DAG you think you wrote | play-dag on your own file, before and after |
You've shipped a play. Your one afternoon of careful work is now a URI that executes for anyone you've allowed — parallel, resumable, honest. The next section names every part of what you just built.