Rote compile
Graduate AI skills into cheap, fast, deterministic workflows
npx -y skills add trevhud/rote --skill rote-compileAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Analyze an Anthropic-style skill bundle (SKILL.md + references) and produce a compilation plan that separates deterministic work into extracted Python modules, LLM-as-judge work into typed DSPy/BAML signatures, agentic work into bounded agent loops, and human-in-the-loop gates into durable suspend points. Emit the whole pipeline as a runtime-agnostic intermediate representation (pipeline.yaml) plus runnable code for a target workflow engine (Temporal, Inngest, Restate, or plain async) via pluggable adapters. Trigger phrases: "compile this skill", "turn this skill into a workflow", "harden this skill", "extract deterministic parts of this skill", "compile skill to pipeline", "make this skill reliable in production", "analyze skill for codification", "emit this skill as a temporal workflow", "rote this skill".
SKILL.md
11.9 KB, as published. Nobody here has run it
Skill Compilation
You are the rote compiler. You read fuzzy AI skills and produce reliable workflows.
Input: a directory containing a SKILL.md and an optional references/
folder with sub-files.
Output: an intermediate representation (pipeline.yaml) plus
runtime-specific emitted code that the user can drop into their durable
execution engine. Along the way you also produce a human-readable compilation
report that highlights what was codified, what stayed agentic, and what
judgment calls you made.
The guiding philosophy: keep the LLM at points where the input is unbounded or ambiguous (parsing, classifying, drafting, summarizing). Codify everything else — control flow, retries, parallelism, idempotency, side-effecting tool calls, fixed batching — into deterministic code. Every token spent re-deriving a known-fixed procedure at runtime is waste.
Phase Routing
| Entry Point | Start At | Example Triggers |
|---|---|---|
| Full compilation | Phase 1 → 7 | "compile the bdr-outreach skill" |
| Report only | Phase 1 → 5, skip 6 | "analyze this skill, don't emit code" |
| Re-emit for different runtime | Phase 6 | "emit the existing pipeline.yaml for inngest" |
| Update compilation after skill changed | Phase 1 → 7, diff | "recompile bdr-outreach, it changed" |
Pipeline
Progress markers. At the START of each Phase N, rewrite
progress.ndjson in the work directory so it holds one JSON line per
phase entered so far, in order:
{"phase": 1, "name": "Intake"}
{"phase": 2, "name": "Node Classification"}
Rewrite the whole file each time (append the new phase's line to the ones already there). This file is machine-read for live progress display; it is not a deliverable and never affects the IR.
Phase 1: Intake
Read the target directory exhaustively:
SKILL.md— parse the frontmatter (name, description, trigger phrases), then read the full body. Note every named phase, gate, and rule.references/— read every sub-file. These are the rubric detail, the API choreography, and the domain constraints.- Build a mental model of: the skill's goal, its preconditions, its tools, its explicit HITL gates ("present to user", "wait for approval"), and any MANDATORY checklists enforced only by prose.
Do not proceed to Phase 2 until you can restate the skill's phase structure in your own words.
Phase 2: Node Classification
Read references/node-kinds.md before starting this phase.
Walk through the skill from start to finish. For every distinct step, assign it exactly one of the five node kinds:
| Kind | Signal |
|---|---|
pure_function | Fixed logic, deterministic I/O, no LLM reasoning needed |
llm_judge | Fuzzy classification against a rubric, typed input/output |
agent_loop | Exploratory tool use, variable path, genuinely needs LLM orchestration |
hitl_gate | Explicit human review/approval required before proceeding |
external_call | Deterministic API call that needs retry/timeout semantics |
When a step could be classified two ways, prefer the more deterministic
kind. The goal is to push as much as possible into pure_function and
external_call, and reserve agent_loop for steps where the procedure
genuinely varies from run to run.
Output: a table mapping each step to its kind, with a one-sentence justification per row.
Phase 3: Codification Scan
Read references/crystallization-heuristics.md and
references/implementation.md before starting this phase. The second
file governs how far to take each extracted module: contract-documented
stubs by default, working implementations plus golden-fixture tests
when ground truth is available (a probe-context/ directory of observed
MCP traffic in your work dir, and/or web research tools to verify
current vendor API shapes).
For each node classified as pure_function or external_call, surface every
codification opportunity:
- Literal pseudocode or Python pasted into the prompt. The skill's author already wrote it — you just need to lift it into a real module.
- Fixed constants (batch sizes, taxonomy IDs, thresholds, retry counts, timeouts). If the prompt says "batch of 10", that 10 is a constant, not a judgment call.
- Deterministic loops with clear termination conditions.
- String templates for reports, output formats, or structured responses.
- MANDATORY checklists enforced by English prose. These are the highest value extractions — moving a required check from prose into code makes it impossible to skip accidentally.
For each extraction, produce a record of: source file and line, the current prose, the proposed extracted function name and signature, and any constants to lift into config.
Phase 4: LLM-Judge Extraction
Read references/llm-judge-extraction.md before starting this phase.
For each llm_judge node, draft a typed signature using DSPy or BAML
conventions:
- Input schema (Pydantic / TypedDict): every field the LLM needs, sourced from upstream node outputs.
- Output schema: enum-bounded decisions plus structured rationale. Prefer enums over free-text wherever the skill's rubric implies a discrete choice.
- Seed examples: harvest 3–5 examples directly from the skill's own
rubric. If the rubric says "discard MSLs", construct an example with an
MSL contact and the
discarddecision. - Eval stub: scaffold a pytest-style test file that asserts the signature against each rule the rubric names.
Output: a signatures/<node_name>.py file per llm_judge node, plus a
matching evals/<node_name>.jsonl seed.
Phase 5: IR Assembly
Read references/ir-schema.md before starting this phase.
Assemble the full DAG as pipeline.yaml. Include:
input.input_schema: the full JSON Schema for the pipeline input payload, promoted from the entry nodes' signature design (seeir-schema.md).nodes: every step from Phase 2, with its kind, input/output types,inputs:data-flow bindings (param →pipeline.input[.field]/<node_id>.output[.field]), timeout, retry policy, and (forpure_function/llm_judge) a reference to the extracted module. Give every node asource.sectionnaming the exact SKILL.md heading it was derived from — this provenance is what makes incremental re-compilation (rote compile --update) possible.edges: data flow between nodes, including fan-out for batch processing.hitl_gates: for everyhitl_gatenode, the signal name, timeout, and notification target.config: top-level schedule, on_failure handler, observability settings.
The pipeline.yaml is the runtime-agnostic source of truth. Everything
Phase 6 emits is derived from it.
Alongside it, write the eval sidecar. Read
references/eval-estimates.md, then produce eval.yaml next to
pipeline.yaml: per-step agent-turn estimates for running the source
skill as raw instructions. You just classified every step in Phase 2 —
this captures, at near-zero extra cost, the cost model that
rote eval's before/after scorecard is built on.
Phase 6: Adapter Invocation
You do not emit runtime code yourself. The rote project ships
runtime adapters as Python code (rote/adapters/temporal.py,
rote/adapters/inngest.py, etc.) that consume the pipeline.yaml you
produced in Phase 5 and emit runnable code. Your job in this phase is
to validate that your IR is complete enough for the adapter to
succeed, then let the adapter run.
Checklist before declaring Phase 5 done:
- Every
pure_function/external_callnode has animpl:pointing at a real file you created in Phase 3. - Every step that calls an MCP tool in the source skill carries the
matching
mcp:binding (see ir-schema.md — and cross-check againstprobe-context/observed-tools.jsonwhen it exists: observed traffic with no binding is a compilation bug). - If you wrote working implementations,
tests/test_extracted.pyexists, is deterministic and offline, and you believe it passes. - Every
llm_judgenode has asignature:pointing at a real file you created in Phase 4. - Every
agent_loopnode hastools:listed. - Every
hitl_gatenode has asignal:. - Every node mentioned in
entry_nodes,exit_nodes,edges.from,edges.to, andloop_bodyexists innodes:. rote.ir.load_pipeline(pipeline.yaml)loads without validation errors. If you can call this function, run it as a dry check.
The rote emit <pipeline.yaml> --runtime temporal --out <dir> CLI
command (or its programmatic equivalent) takes it from there. You do
not need to read the adapter's implementation — treat it as a black
box that accepts a valid IR and produces code.
Why the split: the IR and its validation logic are the load-bearing parts of compilation. Code emission is template substitution and belongs in deterministic Python. Keeping the agent out of the emission step means the emitted output is reproducible and reviewable — every change to what Temporal code looks like is a change to the adapter, not a new agent trajectory.
Phase 7: Compilation Report
Produce a human-readable Markdown report in compile-report.md covering:
- Summary metrics: total nodes, breakdown by kind, estimated token cost reduction per run, list of HITL gates and what they block on.
- Crystallization log: every prose-to-code extraction from Phase 3, with before/after snippets.
- Open questions: ambiguous judgment calls where you made a decision but the human reviewer should verify. Be explicit about what you chose and why.
- Suggested next steps: what to dogfood first, which evals to run, which node to recompile after the first production run reveals new information.
Invariants
These rules apply across every phase:
- Never silently drop a MANDATORY check. If the skill says a check is required and you can't codify it, surface it as an open question in Phase 7 — never as a silent omission.
- Prefer deterministic over agentic. When in doubt, codify. Leaving something agentic is a last resort for genuinely exploratory work.
- The IR is the source of truth, not the emitted code. If the IR and a runtime-specific artifact disagree, the IR wins and Phase 6 re-runs.
- Two adapters minimum before declaring the IR generic. Until two runtimes work, assume the IR is secretly shaped like the first one.
- Dogfood yourself. Once a stable version exists, point rote-compile at its own SKILL.md. The parts that can be crystallized should be; the parts that stay agentic are the real source of judgment.
Related Concepts
- "Workflow vs. agent" — the distinction from Anthropic's Building effective agents essay. This tool is fundamentally about moving steps from the agent column to the workflow column when the data supports it.
- "Blueprint First, Model Second" — the EBF paper's framing. The
blueprint is the
pipeline.yaml; the LLM is a specialized tool at specific nodes. - DSPy compile — what the
llm_judgesignatures compile into once you have enough examples. rote produces the signature; dspy.compile optimizes the prompt and few-shots against your eval set.