# 3 · 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](assets/crystallize.svg)

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

```bash
rote init check-npm-package --seq
```

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

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

```bash
rote proc run curl -s https://registry.npmjs.org/left-pad
```

```
@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*:

```bash
rote query @1 '.stdout.text | fromjson | ."dist-tags".latest' -r
# 1.3.0
```

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

```bash
rote proc run curl -s https://api.npmjs.org/downloads/point/last-week/left-pad
# @2 captured
rote 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":

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

```bash
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):

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

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

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

```bash
rote play run main.ts package_name=left-pad          # happy path
rote 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

```bash
rote play release check-npm-package        # draft → released
rote registry play push main.ts myorg      # → immutable version on the hub
```

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

```
play_reference: myorg/check-npm-package@0.1.0
play_uri: https://play.modiqo.ai/myorg/check-npm-package@0.1.0
```

![The release chain](assets/lifecycle.svg)

Close the loop with a **canonical readback** — run the published URI and confirm the hub serves what you shipped:

```bash
cd /tmp && rote play run https://play.modiqo.ai/myorg/check-npm-package package_name=left-pad --yes
```

And tidy the trail:

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

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

**Next:** [4 · How the play travels →](04-how-the-play-travels.md)
