The workspace canvas, CAUs, and the DAG underneath
Where you are in the journey: you can run, use, and create plays, and you've met all three reaches. This is the deepest section — the substrate that makes crystallization possible rather than magical. Read it when you want to know why the product works, not just how.
The primitive: Context Addressable Units
The product's definition, verbatim:
"Every request/response pair that passes through rote lands on disk as a Context Addressable Unit — @1, @2, @3. These are not logs. They are persistent, queryable artifacts."
Four properties make a CAU more than a saved response:
| Property | Meaning |
|---|---|
| Immutable once created | @2 is @2 forever; re-running creates @5, it never rewrites history |
| Queryable without re-execution | read any field out of it, any number of times, at zero API cost |
| Typed by origin | an adapter call, a process capture, and a page snapshot carry their provenance |
| Linked by dependency | when a request references an earlier response, that link is recorded |
The filesystem analogy is deliberate: CAUs are to context what inodes are to files — stable references that can be stored, queried, composed, and versioned. The @N you see in the terminal is the address.
The workspace: a flight recorder
"A rote workspace is a flight recorder."
A workspace (product surface name: canvas) is one addressing space that all three reaches write into. Its life is a handful of commands:
01rote init my-task --seq # open the recorder02rote proc run … # explore — every call lands as @N03rote browse …04rote query @N '<jq>' -r # read fields by reference05rote query schema @N # (don't guess shapes — ask)06rote play pending write my-task … # anchor reusable results07rote workspace export my-task … # crystallize (section 3)08rote play pending discard my-task # tidy when doneTwo query habits that pay off immediately:
01# MCP-style responses wrap JSON in text — unwrap with fromjson02rote query @1 '.content[0].text | fromjson | .items[0].name' -r03 04# save a scalar for reuse in later requests as $pr_number05rote query @2 '.number' -s pr_numberEvery query read is itself recorded — with the tokens the source response would have cost versus the tokens the result actually cost. The gap is the point: a 40k-token API response read three times by reference costs three field-reads, not 120k tokens of context.
The bridge: a trace of references IS a dataflow graph
Here is the sentence the whole product balances on:
"A trace made of references is a dataflow graph. The record of what happened is, by construction, a program that can happen again."
Walk it slowly, because it explains everything you did in section 3:
- You fetched a package record →
@1. - You fetched download counts →
@2. Nothing about@2referenced@1— they're independent. - You composed a report using
@1{.dist-tags.latest}and@2{.downloads}→@3depends on both, and the recorded references say exactly which fields flowed.
That's not "notes about your session". That's a graph: two roots and a join, with typed edges. The crystallizer (Filter → Reify → Resolve → Fingerprint → Generate) doesn't invent your play's structure — it recovers the structure your exploration already had:
| In the workspace | In the play |
|---|---|
independent captures (@1, @2) | root steps — same layer, run in parallel |
a capture that references others (@3) | a step with depends_on — ordering edges |
the @1{.path} field references | '@step{$.stdout.text | fromjson | .path}' — value edges |
the values you happened to use (left-pad) | typed parameters ($package_name) |
This is why the guidance keeps repeating "explore in the shape of the play you will crystallize". It isn't discipline for its own sake. The DAG is not something you design at export time — it's something you lay down during exploration, one reading per capture. Cram three API calls into one shell one-liner and the recorder sees one opaque blob: one step, no edges, nothing to parallelize, nothing to resume. Keep readings separate and the graph writes itself.
Why the DAG is the foundation for everything downstream
Once work is a declared graph rather than a script, each hard runtime feature becomes a graph traversal:
- Parallelism — steps with no path between them run together;
[layer 2 — 4 parallel]is free. - Resume — a failed run knows precisely which nodes completed;
--resumere-executes only the frontier. Completed steps restore their recorded outcomes — CAU immutability again, now at run time. - Per-source blame — the stage ledger can say which source degraded, because each source is a node with its own outcome.
- Inspection —
play-dagcan draw any play's structure because the structure is data, not vibes. - Drift detection — fingerprints pin the API identity each adapter step was compiled against, so "the API changed under this play" is detectable rather than mysterious.
And the run itself feeds back into the memory: each executing step's outcome is recorded just like exploration captures were — which is what the presentation layer reads, and why re-rendering a past run (--resume … --output=json) produces byte-identical facts in a different format.
The full circle
01 explore (three reaches) crystallize share02 ┌──────────────────────┐ ┌──────────────────────┐ ┌───────────────────┐03 │ workspace canvas │ │ Filter · Reify · │ │ hub URI, versioned │04 │ CAUs: @1 @2 @3 … │ → │ Resolve · Fingerprint│ → │ inspectable card │05 │ references = dataflow │ │ · Generate │ │ anyone can run │06 └──────────────────────┘ └──────────────────────┘ └───────────────────┘07 ↑ │08 └────────── someone runs it, learns, explores ───────┘The five gears the README names — Adapt → Persist → Guide → Crystallize → Share — are this circle. Plays are its center of gravity: everything upstream exists so a play can be born; everything downstream exists so a play can be trusted, found, and run.
Next: 9 · Reference →