rote
Compile AI agent skills into cheap, fast, deterministic pipelines thatrun without an LLM in the loop.
Your Claude skill works. Running it a thousand times does not.
rote is an open source CLI that compiles a proven Claude skill oragent skill (a SKILL.md plus references/) into a typed,deterministic pipeline. It moves the fixed logic and tool orchestrationinto reviewable code, and calls a model only for the steps thatgenuinely need judgment. A 10 to 20 minute agent loop becomes abackground workflow that costs a fraction of the tokens and can beregression tested.
pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
# `rote compile` runs an LLM agent, so it needs a driver: Claude Code
# (`claude`) or Codex (`codex`) installed and authed, or ANTHROPIC_API_KEY
# for the in-process `api` driver. The BDR run below takes ~13 min and
# ~$0.70 with Sonnet. (`rote emit` needs no LLM; see below.)
# Default target is DBOS: durable execution as a plain Python library,
# no orchestrator to run, SQLite for dev / Postgres for prod:
rote compile ./examples/bdr-outreach/skill --out ./compiled/
# Or pick another runtime (see the table below):
rote compile ./examples/bdr-outreach/skill --runtime temporal --out ./compiled/
rote compile ./examples/bdr-outreach/skill --runtime cloudflare --out ./compiled/
The name comes from rote learning: doing something so many times, soreliably, that it becomes mechanical. That's what compilation does to askill.
Why
Agent skills work, but repeating them in production is expensive, slow,and non-deterministic. Token cost is what bites first: every runre-reads the skill text, the tool schemas, and a growing transcript tore-derive a procedure the author already wrote down. The two productionskills adapted into examples/ averaged ~0.9M and ~1.6M cache-readtokens per run before compilation, for work that is mostly arithmeticand fixed API calls. Latency is next (a 10 to 20 minute agent loop isunacceptable as a background job), then determinism: a "MANDATORY"check enforced only by prose can be silently skipped, and there's noway to regression-test a behavior the LLM has to remember.
The fix is to separate the parts of a skill that are actually fuzzyfrom the deterministic procedures wearing fuzzy clothing. Move thedeterministic parts into code, keep the LLM only where the input isgenuinely unbounded (parsing, classifying, drafting), and wrap the wholething in a durable execution engine with explicit human-in-the-loopgates. That compilation step is what rote automates.
| Run the skill as an agent, every time | Compile it with rote |
|
|---|---|---|
| Tokens per run | Full agent loop | Only the judgment steps |
| Latency | 10 to 20 minutes | Background, seconds to minutes |
| Reproducibility | Prose MANDATORY can be silently skipped |
Deterministic nodes always run |
| Testing | No per-step regression tests | Typed nodes, per-step tests, eval seeds |
| Failure recovery | Restart the loop | Durable retries and resume |
| Human approval | Ad hoc | Explicit HITL gates that suspend and resume |
There's third-party data for what this buys. "Compiled AI: DeterministicCode Generation for LLM-Based Workflow Automation"(Trooskens et al., Apr 2026) measured compiling LLM workflows intodeterministic code: 57× fewer tokens at 1,000 transactions, 450×lower median latency, 100% reproducibility (vs. 95% for directinference at temperature 0), and ~40× lower TCO at a milliontransactions a month. The multiples grow with volume. Once a workflowis proven, every run through an agent loop pays LLM prices for work codedoes for free.
A distinction worth being precise about: durable-execution vendors makefuzzy agents durable (wrap the loop in retries and state so it survivescrashes, still fuzzy inside). rote removes the fuzzy loop. The twocompose: Temporal, Cloudflare Workflows, and the rest are rote'scompile targets, not its rivals.
When not to use rote: exploratory and one-off work should stay anagent loop. Flexibility is the whole point there, and there's nothingproven to compile yet. rote is for the skill you've run twenty timesand want to run a thousand more, unattended.
How it works
One rote compile run does the whole thing. An LLM agent (itselfdefined as a skill) reads the source skill, applies a structuredcompilation rubric, and emits a runtime-agnostic intermediaterepresentation (pipeline.yaml), extracted Python modules for thedeterministic parts, typed signature stubs for the LLM-judge parts, andrunnable code for the durable execution engine of your choice.
rote is a three-layer system; each layer has one job and contracts ona small interface.
SKILL.md + references/ Source skill bundle (untouched)
│ rote compile
▼
compiler agent An LLM agent (Claude / Codex /
(pluggable driver) Anthropic SDK) runs the rote-compile
│ skill against the source bundle.
│ filesystem contract: work_dir/pipeline.yaml
▼ + extracted/ + signatures/
Pipeline IR (pipeline.yaml) Pydantic-validated DAG of typed
│ nodes. Five node kinds. Runtime-agnostic.
│ rote.adapters.<runtime>
▼
emitted runtime code Native code for the target durable
execution engine.
- The compiler agent (
skills/rote-compile/): a regularAnthropic Skill (SKILL.md+ four reference files). This is thebrain; it runs inside any Skills-compatible surface, and you don'tneedroteto use it. - The IR (
src/rote/ir.py): Pydantic models for the five nodekinds plus edges, retries, HITL gates, and metadata. The IR is thesource of truth; everything downstream is template substitution. - Runtime adapters (
src/rote/adapters/): pluggable modules thatconsume an IR and emit runnable code for one engine.
The compiler's job ends when it has produced a valid pipeline.yaml.Code emission is deterministic Python, never agent-driven, so thesame IR always produces byte-identical output.
Quickstart
From Claude Code (recommended)
rote ships as a Claude Code plugin, so you can compile a skill withouttouching Python tooling:
/plugin marketplace add trevhud/rote
/plugin install rote@rote
Then say "compile this skill" (or run /rote:compile). It confirms thesource directory, asks which runtime you want, runs the CLI viauv in the background, and reports theemitted pipeline. A second skill, /rote:serve, wires compiledpipelines up as MCP tools so Claude can trigger the deployed workflows(see docs/mcp-trigger.md).
Prefer a terminal? The same thing is one uvx command:
uvx --from rote-cli rote compile ./my-skill --runtime dbos --out ./compiled
Hosted platform: roteskills.com is theproject site (concepts, benchmarks methodology, worked examples).app.roteskills.com is Rote Cloud, amanaged path for teams that would rather not operate a runtime: runrote login and rote compile then runs server-side, streams progressback, auto-deploys, and downloads the artifacts locally. Everything inthis README still works logged out.
Naming note: the
rotepackage on PyPI is an unrelatedmemoization library that also installsimport rote, so the two can'tshare an environment. This project's distribution isrote-cliwhilethe CLI command and import name stayrote, henceuvx --from rote-cli rote .... See docs/releasing.md.
Run on the bundled example
The repo includes a real BDR outreach skill (lead generation, contactvetting, CRM upload, mandatory exclusion checks, email personalization,manual enrollment handoff) in examples/bdr-outreach/skill/:
rote compile examples/bdr-outreach/skill --out /tmp/bdr-compiled
On that skill the compiler produces a 22-node IR that's 78.9%codifiable (15 of 19 non-gate nodes), extracts 5 Python modules and 2typed judge signatures, and flags 4 mandatory nodes and 3 HITL gates,all in ~13 minutes for ~$0.70 (Sonnet via Claude Code). Along the way itindependently lifts the three MANDATORY exclusion checks out of prose,pulls four batch-size constants out of prompt text, and models aparallel entry path the hand-written baseline missed.
rote auto-detects a driver in the order claude → codex → api;override with --agent. The output directory splits into compiled/(the agent's pipeline.yaml, extracted/, signatures/, eval seeds,and a compile-report.md) and runtime/<runtime>/ (the adapter'semitted code + a README on how to run, signal gates, and deploy).
Other commands
rote emit <pipeline.yaml> --out <dir>: run just the adapter stepon an existing IR. LLM-free, so no cost and no driver needed, whichmakes it the cheap inner loop while iterating on adapters or IRshapes. Re-emitting is safe: a.rote-manifest.jsontracks whatrotewrote, and files you've edited are left untouched(the fresh version lands as<name>.new).rote compile --update: re-compile incrementally when the skillchanges.rotediffs the skill against the previous run'sprovenance.jsonand re-derives only the nodes whose source sectionschanged; unchanged nodes keep their ids (so in-flight durable workflowsaren't orphaned) and implemented stubs are kept. No change → no agent run.rote run <path>: one-off local execution of either side. Askill directory runs as an agent viaclaude -p(your registered MCPservers injected, read-only tool gate unless--allow-writes); anemitted runtime directory, or acompile --outdirectory, runsthe pipeline itself on any of the six runtimes (python/dbos/temporalin-process or on a managed local dev server,cloudflareunderwrangler dev,inngestagainst a managedinngest-cli dev,dbos-tsagainst your Postgres or a throwaway Docker one). HITLgate payloads via--signal name='{...}'or an interactive prompt.Runtimes that bundle a dev UI surface it: temporal runs print a liveTemporal Web UI URL and inngest runs print the dev-server dashboard,both live for the duration of the run. Output JSON on stdout, statuson stderr, so it pipes.rote deploy <path>: push an emitted pipeline where it runs:cloudflarewrapsnpx wrangler deploy(with--dry-run),dbos/dbos-tswrapnpx dbos-cloud app deploy, and the vendor CLI owns authand output; rote adds detection and preflights (including surfacingwhich account your wrangler session belongs to before uploading).Runtimes with no push model (temporal, inngest, python) print honesthosting guidance with doc links instead of a fake action.--target rote-cloudbundles a cloudflare-emitted app (esbuild vianpx) and uploads it to a hosted rote-cloud instance. With a storedrote login, no flags or env vars are needed (--url/--tokenand$ROTE_CLOUD_URL/$ROTE_CLOUD_TOKENstill override).rote login: connect the CLI to a rote-cloud account via theOAuth device flow: your browser opens with a one-time code pre-filled(over SSH,--deviceprints the code + URL instead), you clickApprove, and the CLI stores a tenant API key at~/.local/share/rote/cloud.json(mode 0600). Once logged in,rote compileruns on rote cloud by default: the skill bundlesyncs up (sha-diffed, so unchanged files don't re-upload), theplatform runs the compilation server-side, live progress streams backthrough the same renderer as a local run, the result auto-deploys,and the artifacts download into your--outdirectory in the exactlocal layout.--localkeeps the compilation on your machine (thenthe cloudflare-emit + auto-deploy flow applies),--no-deployor aconfig opt-out (runtime:pinned to a local target, ordeploy: none) keeps everything local;--cloudforces the servereven where config says otherwise. Logged out, everything workslocally exactly as before.rote whoamishows the account (verifiedlive);rote logoutrevokes the key server-side and clears the store.rote init: one-time interactive onboarding: pick wherecompiled pipelines run (rote cloud, with login offered inline, ora local runtime, with a one-line pitch for each), which compilerdriver does the work (availability probed live), and optionally amodel. Answers are saved to~/.config/rote/config.yaml(--projectwrites a./rote.yamlthat overrides it per-repo) andevery later command reads them. It's the only interactive commandbesides login; CI never hits a prompt.rote config: print every configurable default with itseffective value and the layer that set it. Resolution everywhere isflag > ROTE_* env (ROTE_RUNTIME, ROTE_DEPLOY, ROTE_AGENT, ROTE_MODEL) > project rote.yaml > user config > built-in. Configfiles are strict: a typo'd key or value is a loud error, never asilent fallback.--jsonfor automation.rote eval <compiled>: render the before/after scorecard (wallclock, cost across the current model lineup at live prices, and howmuch of the run is still LLM-decided).rote compilewrites this tocompiled/scorecard.mdautomatically. Add--runto measureinstead of estimate: it executes both sides for real and appendsmeasured cost, turns, and output agreement across trials.- Per-node inference: emitted judges read
ROTE_MODEL_<ID>andROTE_BASE_URL_<ID>at runtime, so you can swap the model or point atany OpenAI-compatible endpoint (Ollama, vLLM, a gateway) withoutre-emitting.
The five node kinds
Every step in a compiled pipeline is exactly one of five kinds. Fullguidance:references/node-kinds.md.
| Kind | What it is | Where the LLM lives |
|---|---|---|
pure_function |
Fixed logic, deterministic I/O | Not involved |
external_call |
Vendor API call with fixed semantics + retries | Not involved |
llm_judge |
Fuzzy classification against a rubric, typed I/O | Typed signature (DSPy/BAML in Python; Zod + vendor SDK in TS), from the IR's runtime-agnostic signature_spec |
agent_loop |
Genuinely exploratory tool use | Bounded agent loop |
hitl_gate |
Explicit human approval, suspend until signal | Durable suspend/resume |
The guiding rule: keep the LLM at points where the input is unboundedor ambiguous, and codify everything else. When a step could go eitherway, prefer the more deterministic kind.
Runtimes
Pick with --runtime; the same IR drives all of them. Under--backend api, none of the emitted code references MCP: thecrystallization step replaces tool calls with direct vendor API calls.Under the default --backend mcp, tool-using nodes emit a working MCPclient call (with durable park-on-auth on every MCP-capable runtime);see docs/mcp-client.md.
| Runtime | --runtime |
Language | Shape | Notes |
|---|---|---|---|---|
| DBOS (default) | dbos |
Python | main.py with @DBOS.workflow + @DBOS.step per node |
No orchestrator to deploy; SQLite (dev) / Postgres (prod) |
| Temporal | temporal |
Python | workflow.py + activities.py |
Signal handlers for HITL gates |
| Plain Python | python |
Python | single main.py script |
Max legibility, stdlib only; refuses HITL-gate pipelines |
| Cloudflare Workflows | cloudflare |
TypeScript | WorkflowEntrypoint + wrangler.jsonc |
wrangler deploy-ready |
| DBOS (TypeScript) | dbos-ts |
TypeScript | src/main.ts (DBOS Transact) |
Zero-orchestrator; Postgres-only |
| Inngest | inngest |
TypeScript | one inngest.createFunction |
Mounts into an existing Node/Next.js app; retries are function-level |
Drivers
rote ships three interchangeable compiler drivers. Pick whichevermatches your auth. The same pipeline.yaml comes out either way.
| Driver | Backend | Auth | Install |
|---|---|---|---|
claude (default) |
claude -p subprocess |
Claude Max/Pro OAuth or CLAUDE_CODE_OAUTH_TOKEN |
Install Claude Code separately |
codex |
codex exec subprocess |
ChatGPT Plus/Pro OAuth | Install Codex CLI separately |
api |
anthropic Python SDK |
ANTHROPIC_API_KEY |
pip install 'rote-cli[api]' |
The claude driver scrubs ANTHROPIC_API_KEY from the subprocess so asubscription login wins, and limits the agent to read/write/glob/greptools. The default model is Sonnet rather than Opus, because thetask is structured-rubric-following, not deep reasoning; Sonnet bringsper-run cost from ~$3.50 to ~$0.70. Override with --model for skillswhere Opus earns its cost. Full design record, including the auth gotcha:docs/agent-runtime.md.
rote explicitly does not depend on claude-agent-sdk: Anthropic'sToS forbids third-party agents built on the Agent SDK from usingclaude.ai login credentials without approval, which would defeat thesubscription path.
How it differs from other tools
- vs. raw durable engines (Temporal / Cloudflare / Inngest / Restate):they give you the workflow runtime; they don't help you decide whatshould be a workflow.
roteis the missing step that turns a workingskill into something worth running on one. - vs. LangGraph: LangGraph is an excellent state machine, but itsgraph is hand-built.
roteproduces a graph from prose, classifiesnodes by determinism, and pushes work out of the agent loop whereverthe data supports it. - vs. using Skills directly: Skills run great interactively.
roteis what you reach for when a skill becomes business-critical and needsto run unattended with hard reliability guarantees and per-stepregression tests.
Status
rote is pre-1.0. The end-to-end flow works on the BDR example. Thefast suite (pytest tests/) makes no real API calls and is what CI runson every push, alongside a Python e2e (DBOS over SQLite + the MCP serverover real stdio). Each adapter also has a slow-marked e2e that runs itsemitted code against the real runtime (Temporal's time-skipping server,the TypeScript targets via tsc --noEmit and live dev servers, theplain-Python subprocess); those need a Node toolchain / Docker, so theyrun locally with pytest tests/ -m slow, not in CI.
Known gaps: the extracted modules are NotImplementedError stubsyou fill in with real API-client code, a Restate adapter is planned, andfan_out nodes currently receive the whole upstream list in oneinvocation (per-element dispatch is a planned enhancement). Published onPyPI as rote-cli via tag-drivenTrusted Publishing (docs/releasing.md).
On the numbers: static scorecard estimates, observed production-agentbaselines, and independent research are three different kinds ofevidence, and mixing them produces marketing rather than benchmarks.They're kept separate, with the assumptions written out, atroteskills.com/benchmarks. Tomeasure your own workflow instead of reading someone else's, userote eval --run.
Repository layout
rote/
├── docs/ agent-runtime · mcp-client · mcp-trigger · releasing
├── skills/rote-compile/ the compiler agent (SKILL.md + 4 reference files)
├── src/rote/
│ ├── cli.py rote compile / emit / eval / serve
│ ├── ir.py Pydantic IR models + load_pipeline
│ ├── compiler/ orchestrator + drivers/ (claude · codex · anthropic_api)
│ └── adapters/ dbos · temporal · python · cloudflare · dbos_ts · inngest
│ (+ _common / _py_common / _ts_common emit helpers)
├── examples/
│ ├── bdr-outreach/ canonical: all 5 node kinds · IR baseline · run snapshots
│ ├── ops-report/ 100% roteness: zero LLM nodes + a HITL gate
│ ├── deal-monitor/ data-heavy: parallel waves · fan-out judges · template render
│ └── invoice-push/ agent-loop archetype: bounded browser loop · turn-dominated cost
└── tests/ fast + slow suites (pytest -m slow)
Documentation
AGENTS.md: operating manual for a coding agent drivingroteas an installed tool (invocation contract, the slow/costs-moneycompileflow, auth, failure recovery, the stub-filling job,--json)docs/agent-runtime.md: design record for thedriver abstraction (theclaude -penv gotcha; the non-use ofclaude-agent-sdk)docs/mcp-client.md: the OAuth MCP clientemitted code uses under--backend mcp: endpoint/credentialresolution and durable park-on-auth across every MCP-capable runtimedocs/mcp-trigger.md:rote register+rote serve: compiled pipelines as MCP tools (FastMCP 3.x)docs/releasing.md: tag-driven PyPI TrustedPublishingskills/rote-compile/: the compiler'sSKILL.mdand its four rubric files (node kinds, crystallizationheuristics, IR schema, LLM-judge extraction)examples/bdr-outreach/: the canonicalskill, its ground-truth IR, and snapshotted real compiler runsexamples/ops-report/: the 100%-rotenessarchetype: every step deterministic, one durable HITL gate, zero LLMnodes after compilationexamples/deal-monitor/: the data-heavyarchetype: parallel entry waves, fan-out judges, and a template renderreplacing per-run LLM-generated HTMLexamples/invoice-push/: theagent_looparchetype: a bounded browser-automation loop stays one agent nodewhile the date math, filtering, and reporting around it compile tocode, plus the measured runs that forced the loop-aware cost model
Roadmap
In rough priority order:
- Re-compile BDR end-to-end with
signature_spec: the bundled IRwas hand-extended with structured schemas; the rubric now teaches thefield, but no real run has produced one yet. - Pre-filter as a
pure_functionnode: today hard thresholds arelifted into a judge'sforward(), which works for Temporal but notCloudflare; a separate node makes the short-circuit uniform. - More example skills: BDR is one shape; research-heavy,retrieval-heavy, and code-review skills stress the IR differently.
fan_outper-element dispatch: currently the whole upstream listarrives in one invocation.- The compiler compiling itself:
rote-compileis a SKILL.md;pointingrote compileat it should crystallize its rubric-gradepieces and leave only the genuinely fuzzy judgments in the loop.
FAQ
What is rote?
rote is an open source CLI, Apache-2.0 licensed and published asrote-cli on PyPI, that compilesa proven AI agent skill into a typed, deterministic pipeline. It readsan Anthropic-style SKILL.md, classifies each step, moves fixed logicand tool orchestration into reviewable code, and calls a model only forthe steps that genuinely require judgment.
How does rote reduce AI agent token costs?
It removes model calls rather than making them cheaper. A repeatingagent spends tokens re-reading instructions, tool schemas, and historyto re-derive a procedure it already established. rote compiles thatprocedure into code, so a repeated run pays only for the steps stillclassified as needing judgment.
When should I compile a skill instead of leaving it as an agent?
Keep one-off exploration in an agent, which is what agents are good at.Compile a skill once the procedure is proven, repeats often, and needslower cost, faster execution, regression tests, explicit approvals, orreliable retries.
Does rote replace my agent framework or MCP?
No. A compiled workflow can still call authenticated MCP servers andretain bounded agent loops. rote decides which parts of a processshould stop being inference; your runtime and your integrations staywhere they are.
Where does the compiled workflow run?
Anywhere you already run durable work. rote emits DBOS, Temporal,Cloudflare Workflows, plain Python, DBOS TypeScript, and Inngest.Rote Cloud is an optional managed path forteams that would rather not operate the runtime themselves.
Contributing
The most useful contribution right now is to run rote compile on areal skill of your own and report what happens. The rubric wasdesigned against one skill and needs more. Adding a runtime adapter or acompiler driver, or improving the rubric, are all good next steps. SeeCONTRIBUTING.md for dev setup, the test layout, andthe adapter/driver how-tos.
License
Apache-2.0. See LICENSE.