Documentation/03 · Create your first play

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."

Crystallization

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

The canonical workflow the workspace banner prints is the map for everything below:

ROTE/text1 line
WORKFLOW: Explore → Export → Inspect → Lint → Run

Step 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-pad
ROTE/text1 line
@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:

ROTE/bash2 lines
rote query @1 '.stdout.text | fromjson | ."dist-tags".latest' -r# 1.3.0

Now the second reading — downloads come from a different endpoint:

ROTE/bash4 lines
rote proc run curl -s https://api.npmjs.org/downloads/point/last-week/left-pad# @2 capturedrote query @2 '.stdout.text | fromjson | .downloads' -r# 2843701

Explore 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":

ROTE/bash4 lines
rote play pending write check-npm-package \  --name check-npm-package \  --description "Latest version and weekly downloads for an npm package" \  --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_name

The exporter runs five operations over your trace — this is the compiler pass:

  1. Filter — failed attempts, retries, and dead ends don't ship
  2. Reify — the hardcoded left-pad becomes a typed package_name parameter
  3. Resolve — your @1/@2 references become explicit step dependencies
  4. Fingerprint — API identities are embedded so drift is detectable later
  5. 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):

ROTE/yaml19 lines
steps:  fetch_registry:    type: process.exec    timeout_ms: 45000    argv: [curl, -s, "https://registry.npmjs.org/$package_name"]  fetch_downloads:    type: process.exec    timeout_ms: 45000    argv: [curl, -s, "https://api.npmjs.org/downloads/point/last-week/$package_name"]  compose_report:    type: process.exec    timeout_ms: 15000    depends_on: [fetch_registry, fetch_downloads]    argv:    - python3    - -c    - "…join the two readings…"    - '@fetch_registry{$.stdout.text | fromjson | ."dist-tags".latest}'    - '@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:

ROTE/bash2 lines
rote play run https://play.modiqo.ai/modiqo/play-dag play=./main.ts# 3 steps · 2 layers  ← two parallel roots + a join. Good shape.

Then the gate everything must pass:

rote play lint main.ts

Lint 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:

ROTE/bash3 lines
rote play run main.ts package_name=left-pad          # happy pathrote play run main.ts package_name=surely-not-a-real-pkg-zz   # degrades with a labeled unknown?rote 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

ROTE/bash2 lines
rote play release check-npm-package        # draft → releasedrote registry play push main.ts myorg      # → immutable version on the hub

The push readout hands you the thing this was all for:

ROTE/text2 lines
play_reference: myorg/check-npm-package@0.1.0play_uri: https://play.modiqo.ai/myorg/check-npm-package@0.1.0
The release chain

Close 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 --yes

And tidy the trail:

rote play pending discard check-npm-package

The 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:

StandardWhy it matters
One reading = one stepmulti-source work as one script can't parallelize, checkpoint, resume, or blame per source
Declare both edge kindsdepends_on for ordering, @step{jq} for dataflow — provable, drawable, honest
Maximize root stepsindependence you declare is speed the runner gives you free
Degrade, never die — only where honestone flaky source shouldn't kill an eight-source report; a labeled unknown should
Non-interactive subprocesses need consent flagssteps have no TTY; a subcommand that prompts will hang — pass its --yes
Test the negative spacethe failure behaviors are the product; verify them before anyone else finds them
Verify the DAG you think you wroteplay-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.

Next: 4 · How the play travels →