Skip to main content
Glama

rote

Compile AI agent skills into cheap, fast, deterministic pipelines that run without an LLM in the loop.

CI PyPI Downloads Python versions License: Apache 2.0

Your Claude skill works. Running it a thousand times does not.

rote is an open source CLI that compiles a proven Claude skill or agent skill (a SKILL.md plus references/) into a typed, deterministic pipeline. It moves the fixed logic and tool orchestration into reviewable code, and calls a model only for the steps that genuinely need judgment. A 10 to 20 minute agent loop becomes a background workflow that costs a fraction of the tokens and can be regression tested.

Try it free in 60 seconds

No API key, no subscription, no tokens, no clone. The expense-audit example is a month-end expense audit skill whose compiled pipeline ships with working code, and it comes inside the package, so you can emit it and run it before you compile anything of your own:

uv tool install rote-cli          # or: python3 -m venv .venv && . .venv/bin/activate && pip install rote-cli
                                   # (the first dbos run offers to add the dbos extra; you confirm it)
rote example expense-audit         # copies the skill, IR, code and sample input to ./expense-audit
                                   # (zero-install: uvx --from rote-cli rote example expense-audit)

# IR -> a plain Python script, then run it on the sample batch
rote emit expense-audit/expected/pipeline-draft.yaml --runtime python --out expense-audit/app-python
rote run expense-audit/app-python --input expense-audit/input.json

rote example prints these commands (and the dbos and eval ones) for the copy it made. rote example alone lists what is bundled. Prefer the repo? git clone https://github.com/trevhud/rote.git && cd rote, then use examples/expense-audit/ in place of expense-audit/.

rote run (python): succeeded
  0.1s
{
  "file_report": {
    "report_path": "/home/you/expense-audit/app-python/reports/2026-09-expense-audit.md",
    "status": "draft",
    "reviewer": null,
    "reimbursable_usd": 2395.59,
    "pending_usd": 18.75,
    "returned_usd": 889.81,
    "batch_total_usd": 3304.15,
    "auto_approved": ["Dev Patel", "Emi Tanaka"],
    "review_items": 1,
    "blocked_items": 5
  }
}

Sixteen expense lines in five currencies went through a parallel wave of three mandatory checks, a per-employee audit fanned out five ways, and a report written to disk, with no model anywhere in the loop. The full skill also parks for the controller's approval, which needs a durable runtime:

rote emit expense-audit/expected/pipeline.yaml --runtime dbos --out expense-audit/app-dbos
rote run expense-audit/app-dbos --input expense-audit/input.json \
  --signal controller_review='{"reviewer": "Priya Shah", "approved_ids": ["EXP-2403", "EXP-2413", "EXP-2420"], "rejected_ids": ["EXP-2410"]}'
# ... "status": "final", "reimbursable_usd": 2414.34, "returned_usd": 889.81

What compiling it bought, measured: we ran the same batch twice each way with rote eval --run (the skill as an agent via claude -p on Sonnet, against the dbos app above):

Skill run as an agent

Compiled pipeline

Wall clock per run

42.9 s

9.8 s, mostly DBOS startup

Cost per run

$0.22

$0

Identical output across runs

1 of 2

2 of 2

So it's roughly 4x faster in practice, it drops from $0.22 a run to nothing, and it gives the same answer every time. The agent's two runs disagreed on where they filed the report. Full results are in examples/expense-audit/scorecard.md.

To get a scorecard for any pipeline without running anything, use rote eval. It fetches the live price list once and runs no inference. Its numbers are an estimate from turn counts, not a measurement:

rote eval examples/expense-audit/expected/pipeline.yaml

pip installs don't include examples/, hence the clone. The other examples' extracted modules are NotImplementedError stubs waiting for real vendor API clients, so expense-audit is the one that runs end to end. Details in its README.

Compile your own skill

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. (`rote emit` needs no LLM; see below.)

# Default target is a compiled skill: the same skill name and trigger,
# with its fixed steps in code, run by the agent you already use:
rote compile ./examples/docs-digest/skill --out ./compiled/
# rote compile: ✓ docs-digest v...
#   next: rote skill install .../compiled/runtime/skill/docs-digest
#     then swap it in for the original docs-digest skill (Claude Code:
#     ln -s .../runtime/skill/docs-digest ~/.claude/skills/docs-digest)
#     or see it give the original's answer first: rote try ./examples/docs-digest/skill

rote checks ~/.claude/skills/<name> before it prints the link. When your original skill is already there, ln -s would nest the link inside it (and exit 0), so rote prints the swap with the original moved aside instead, plus the undo. Nothing is deleted, and rote runs none of it:

    then, for Claude Code: your original docs-digest skill is at ~/.claude/skills/docs-digest.
    To use the compiled one instead, move the original out of the skills folder (nothing is
    deleted): mv ~/.claude/skills/docs-digest ~/docs-digest.original && ln -s .../runtime/skill/docs-digest ~/.claude/skills/docs-digest
      undo: rm ~/.claude/skills/docs-digest && mv ~/docs-digest.original ~/.claude/skills/docs-digest
# Or emit a background workflow instead (see the table below):
rote compile ./examples/bdr-outreach/skill --runtime dbos       --out ./compiled/
rote compile ./examples/bdr-outreach/skill --runtime cloudflare --out ./compiled/

Measured on claude-sonnet-4-6, original skill against its compiled skill, with every compared field matching within the original's own run-to-run noise:

Skill

Original

Compiled skill

expense-audit

154 s, $0.34

43 s, $0.13

ops-report

55 s, $0.13

42 s, $0.12

docs-digest

17.9 s, $0.093

16.3 s, $0.049

rote try <skill> runs that comparison on a skill of yours: it compiles, installs, and runs both sides on the same input, after showing the cost.

The name comes from rote learning: doing something so many times, so reliably, that it becomes mechanical. That's what compilation does to a skill.


Why

Agent skills work, but repeating them in production is expensive, slow, and non-deterministic. Token cost is what bites first: every run re-reads the skill text, the tool schemas, and a growing transcript to re-derive a procedure the author already wrote down. The two production skills adapted into examples/ averaged ~0.9M and ~1.6M cache-read tokens per run before compilation, for work that is mostly arithmetic and fixed API calls. Latency is next (a 10 to 20 minute agent loop is unacceptable as a background job), then determinism: a "MANDATORY" check enforced only by prose can be silently skipped, and there's no way to regression-test a behavior the LLM has to remember.

The fix is to separate the parts of a skill that are actually fuzzy from the deterministic procedures wearing fuzzy clothing. Move the deterministic parts into code, keep the LLM only where the input is genuinely unbounded (parsing, classifying, drafting), and wrap the whole thing in a durable execution engine with explicit human-in-the-loop gates. 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: Deterministic Code Generation for LLM-Based Workflow Automation" (Trooskens et al., Apr 2026) measured compiling LLM workflows into deterministic code: 57× fewer tokens at 1,000 transactions, 450× lower median latency, 100% reproducibility (vs. 95% for direct inference at temperature 0), and ~40× lower TCO at a million transactions a month. The multiples grow with volume. Once a workflow is proven, every run through an agent loop pays LLM prices for work code does for free.

A distinction worth being precise about: durable-execution vendors make fuzzy agents durable (wrap the loop in retries and state so it survives crashes, still fuzzy inside). rote removes the fuzzy loop. The two compose: Temporal, Cloudflare Workflows, and the rest are rote's compile targets, not its rivals.

When not to use rote: exploratory and one-off work should stay an agent loop. Flexibility is the whole point there, and there's nothing proven to compile yet. rote is for the skill you've run twenty times and want to run a thousand more, unattended.


Related MCP server: OpenAI-Compatible MCP Gateway

How it works

One rote compile run does the whole thing. First rote decides the pipeline's structure: which nodes exist and of which kind, how they are wired, which run once per item, which are mandatory, where a person must approve, and which constants they carry (System-1, see docs/design/system1-compile.md). One Claude Haiku call reads the whole skill and proposes that structure, and rote checks every node against the skill's text, its tools' schemas and the sample input before accepting it. --structure rules decides it with rules over the skill's text instead, with no model call. Then an LLM agent (itself defined as a skill) writes only the text those decisions need: the extracted Python modules for the deterministic parts, their tests, and the typed signatures and prompts for the LLM-judge parts. rote holds the agent to its decisions, and emits a runtime-agnostic intermediate representation (pipeline.yaml) plus runnable code for the durable execution engine of your choice.

--no-system1 runs the original path instead, where the agent applies the full compilation rubric and decides the structure too. --update always uses that path, because System-1 does not support incremental re-compilation yet.

The agent implements the deterministic pure_function steps the skill spells out and writes tests for them. Before the compile reports success, rote checks what the agent wrote and runs those tests, and any failure goes back to the agent to fix. The tests are code an LLM wrote from a skill you may not have written, so rote runs them in an OS sandbox (sandbox-exec on macOS, bubblewrap on Linux): no network, no reads outside the compile's work dir and the Python install, no programs other than Python, no API keys in the environment, an empty HOME. If no sandbox works on your machine, the tests are skipped and the compile says the deliverable is unverified. --trust-skill runs them without a sandbox, and --no-verify-tests never runs them; either way, review <out>/compiled/tests/ before running them yourself.

rote is a three-layer system; each layer has one job and contracts on a 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.
  1. The compiler agent (skills/rote-compile/): a regular Anthropic Skill (SKILL.md + six reference files). This is the brain; it runs inside any Skills-compatible surface, and you don't need rote to use it.

  2. The IR (src/rote/ir.py): Pydantic models for the five node kinds plus edges, retries, HITL gates, and metadata. The IR is the source of truth; everything downstream is template substitution.

  3. Runtime adapters (src/rote/adapters/): pluggable modules that consume 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 the same IR always produces byte-identical output.


Quickstart

rote ships as a Claude Code plugin, so you can compile a skill without touching Python tooling:

/plugin marketplace add trevhud/rote
/plugin install rote@rote

New here? Run /rote:setup, or uvx --from rote-cli rote setup in a terminal. It imports the MCP servers you already use, compiles one of your skills, and shows that the compiled skill gives the same answer, with time and cost on each side. It asks before every step and shows the cost before anything is spent.

Then say "compile this skill" (or run /rote:compile). It confirms the source directory, asks which runtime you want, runs the CLI via uv in the background, and reports the emitted pipeline. /rote:implement then fills in the emitted stub modules against each node's IR contract and verifies the result with rote run. A third skill, /rote:serve, wires compiled pipelines 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 --out ./compiled

Hosted platform: roteskills.com is the project site (concepts, benchmarks methodology, worked examples). app.roteskills.com is Rote Cloud, a managed path for teams that would rather not operate a runtime: run rote login, then rote compile --cloud (or set deploy: rote-cloud with rote init) runs server-side, streams progress back, auto-deploys, and downloads the artifacts locally. Everything in this README still works logged out.

Naming note: the rote package on PyPI is an unrelated memoization library that also installs import rote, so the two can't share an environment. This project's distribution is rote-cli and the import name stays rote. The package installs the same CLI under two commands, rote and rote-cli, so both uvx rote-cli ... and uvx --from rote-cli rote ... work. See docs/releasing.md.

Run on the bundled example

The repo includes a real BDR outreach skill (lead generation, contact vetting, 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 2 typed judge signatures, and flags 3 mandatory nodes and 3 HITL gates, all in ~13 minutes for ~$0.70 (Sonnet via Claude Code). Along the way it independently lifts the three MANDATORY exclusion checks out of prose, pulls four batch-size constants out of prompt text, and models a parallel 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's emitted code + a README on how to run, signal gates, and deploy). compiled/compile-metrics.json preserves final token counts, session ID, and the driver's cost estimate before emission starts, so an adapter failure does not lose the paid run's metrics. Missing final output stays null with usage_complete: false; unchanged updates retain prior metrics. Its usage block says, for each stage that called a model, whether its tokens were measured, so a driver that reports none reads as unavailable, not as a free run.

Local api and openai-api compilation automatically discovers your registered, authenticated MCP servers and exposes their read-only tools. Pass --no-mcp to skip that discovery and compile without live MCP calls:

rote compile examples/bdr-outreach/skill --local --agent api --no-mcp --out /tmp/bdr-compiled

This leaves the emitted pipeline's MCP bindings intact. The flag also works with rote analyze and with an API driver selected through config or auto-detection. It rejects cloud compilation, subprocess drivers, and --baseline, whose raw skill run uses its own MCP tools. API drivers bill through your API credentials.

Other commands

  • rote emit <pipeline.yaml> --out <dir>: run just the adapter step on an existing IR. LLM-free, so no cost and no driver needed, which makes it the cheap inner loop while iterating on adapters or IR shapes. Re-emitting is safe: a .rote-manifest.json tracks what rote wrote, and files you've edited are left untouched (the fresh version lands as <name>.new).

  • rote compile --update: re-compile incrementally when the skill changes. rote diffs the skill against the previous run's provenance.json and re-derives only the nodes whose source sections changed; unchanged nodes keep their ids (so in-flight durable workflows aren't orphaned) and implemented stubs are kept. No change → no agent run.

  • rote run <path>: one-off local execution of either side. A skill directory runs as an agent via claude -p (your registered MCP servers injected, read-only tool gate unless --allow-writes); an emitted runtime directory, or a compile --out directory, runs the pipeline itself on any of the six runtimes (python/dbos/temporal in-process or on a managed local dev server, cloudflare under wrangler dev, inngest against a managed inngest-cli dev, dbos-ts against your Postgres or a throwaway Docker one). HITL gate payloads via --signal name='{...}' or an interactive prompt. Runtimes that bundle a dev UI surface it: temporal runs print a live Temporal Web UI URL and inngest runs print the dev-server dashboard, both live for the duration of the run. Output JSON on stdout, status on stderr, so it pipes.

  • rote deploy <path>: push an emitted pipeline where it runs: cloudflare wraps npx wrangler deploy (with --dry-run), dbos / dbos-ts wrap npx dbos-cloud app deploy, and the vendor CLI owns auth and output; rote adds detection and preflights (including surfacing which account your wrangler session belongs to before uploading). Runtimes with no push model (temporal, inngest, python) print honest hosting guidance with doc links instead of a fake action. --target rote-cloud bundles a cloudflare-emitted app (esbuild via npx) and uploads it to a hosted rote-cloud instance. With a stored rote login, no flags or env vars are needed (--url/--token and $ROTE_CLOUD_URL/$ROTE_CLOUD_TOKEN still override).

  • rote login: connect the CLI to a rote-cloud account via the OAuth device flow: your browser opens with a one-time code pre-filled (over SSH, --device prints the code + URL instead), you click Approve, and the CLI stores a tenant API key at ~/.local/share/rote/cloud.json (mode 0600). Once logged in, rote compile --cloud runs on rote cloud, and so does a compile whose runtime is cloudflare or whose deploy is rote-cloud (flag, env or config). A flagless compile stays local and emits a compiled skill, logged in or not. On rote cloud the skill bundle syncs up (sha-diffed, so unchanged files don't re-upload), the platform runs the compilation server-side, live progress streams back through the same renderer as a local run, the result auto-deploys, and the artifacts download into your --out directory in the exact local layout. --local keeps the compilation on your machine (a cloudflare compile then emits locally and auto-deploys), --no-deploy or deploy: none keeps everything local; --cloud forces the server even where config says otherwise. rote whoami shows the account (verified live); rote logout revokes the key server-side and clears the store.

  • rote init: one-time onboarding: pick where compiled pipelines run (locally, with skill first and recommended, or rote cloud, with login offered), which compiler driver and model do the work, and who decides a local compile's structure. It shows what it will write and asks before writing. Answers are saved to ~/.config/rote/config.yaml (--project writes a ./rote.yaml that overrides it per-repo) and every later command reads them. Every question has a flag, so CI and agents run it with flags and --yes; it never prompts without a person at a terminal.

  • rote config: print every configurable default with its effective value and the layer that set it. Resolution everywhere is flag > ROTE_* env (ROTE_RUNTIME, ROTE_DEPLOY, ROTE_AGENT, ROTE_MODEL, ROTE_EFFORT) > project rote.yaml > user config > built-in. Config files are strict: a typo'd key or value is a loud error, never a silent fallback. --json for automation.

  • rote eval <compiled>: render the before/after scorecard (wall clock, cost across the current model lineup at live prices, and how much of the run is still LLM-decided). rote compile writes this to compiled/scorecard.md automatically. Add --run to measure instead of estimate: it executes both sides for real and appends measured cost, turns, and output agreement across trials. With --run --runtime skill the after side is the compiled skill, run by an agent through rote skill run, so both sides are agents and the table compares context, tokens, time, cost and field fidelity (--budget is required).

  • Per-node inference: emitted judges read ROTE_MODEL_<ID> and ROTE_BASE_URL_<ID> at runtime, so you can swap the model or point at any OpenAI-compatible endpoint (Ollama, vLLM, a gateway) without re-emitting.


The five node kinds

Every step in a compiled pipeline is exactly one of five kinds. Full guidance: 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 unbounded or ambiguous, and codify everything else. When a step could go either way, 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: the crystallization step replaces tool calls with direct vendor API calls. The compiler agent gets web search and fetch tools for that, writes each call from the vendor's current reference page, and cites the page as # docs: <url>; compile-report.md lists every cited page beside whether the agent fetched it during the compile. The api backend's calls are Python, so it needs a Python runtime (dbos, python, temporal). Under the default --backend mcp, tool-using nodes emit a working MCP client call (with durable park-on-auth on every MCP-capable runtime); see docs/mcp-client.md.

Runtime

--runtime

Language

Shape

Notes

Compiled skill (default)

skill

Python driver + SKILL.md

a skill dir: the original's name and description, scripts/ runs the fixed steps, the agent answers only judges and gates

Install with rote skill install; runs in Claude Code, Codex and other skill clients through rote skill run

DBOS

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 whichever matches 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 a subscription login wins, and limits the agent to read/write/glob/grep tools. The default model is Sonnet rather than Opus, because the task is structured-rubric-following, not deep reasoning; Sonnet brings per-run cost from ~$3.50 to ~$0.70. Override with --model for skills where 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's ToS forbids third-party agents built on the Agent SDK from using claude.ai login credentials without approval, which would defeat the subscription path.


How it differs from other tools

For a side-by-side with calcifier, skill-to-workflow, Claude Code dynamic workflows, and hand-written workflows, see docs/comparison.md.

  • vs. raw durable engines (Temporal / Cloudflare / Inngest / Restate): they give you the workflow runtime; they don't help you decide what should be a workflow. rote is the missing step that turns a working skill into something worth running on one.

  • vs. LangGraph: LangGraph is an excellent state machine, but its graph is hand-built. rote produces a graph from prose, classifies nodes by determinism, and pushes work out of the agent loop wherever the data supports it.

  • vs. using Skills directly: Skills run great interactively. rote is what you reach for when a skill becomes business-critical and needs to run unattended with hard reliability guarantees and per-step regression tests.


Status

rote is pre-1.0. The end-to-end flow works on the BDR example. The fast suite (pytest tests/) makes no real API calls, spends no tokens, and runs on every push. CI also runs every slow-marked e2e suite on every push: DBOS over SQLite, the MCP server over real stdio, the TypeScript targets via tsc --noEmit and live dev servers (Cloudflare wrangler dev, the Inngest dev server), and DBOS TypeScript against a Docker Postgres. Locally, pytest tests/ -m slow runs them; they need a Node toolchain and Docker.

Known gaps: extracted modules that need a vendor API client (HubSpot, ZoomInfo, and so on) come out as NotImplementedError stubs you fill in, and a Restate adapter is planned. Published on PyPI as rote-cli via tag-driven Trusted Publishing (docs/releasing.md).

On the numbers: static scorecard estimates, observed production-agent baselines, and independent research are three different kinds of evidence, and mixing them produces marketing rather than benchmarks. They're kept separate, with the assumptions written out, at roteskills.com/benchmarks. To measure your own workflow instead of reading someone else's, use rote eval --run.


Repository layout

rote/
├── docs/                  agent-runtime · mcp-client · mcp-trigger · releasing
├── skills/rote-compile/  the compiler agent (SKILL.md + 6 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/
│   ├── expense-audit/     free first run: working extracted code · fan-out · HITL gate
│   ├── 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 driving rote as an installed tool (invocation contract, the slow/costs-money compile flow, auth, failure recovery, the stub-filling job, --json)

  • docs/agent-runtime.md: design record for the driver abstraction (the claude -p env gotcha; the non-use of claude-agent-sdk)

  • docs/mcp-client.md: the OAuth MCP client emitted code uses under --backend mcp: endpoint/credential resolution and durable park-on-auth across every MCP-capable runtime

  • docs/mcp-trigger.md: rote register + rote serve: compiled pipelines as MCP tools (FastMCP 4.x)

  • docs/releasing.md: tag-driven PyPI Trusted Publishing

  • skills/rote-compile/: the compiler's SKILL.md and its six rubric files (node kinds, crystallization heuristics, IR schema, LLM-judge extraction, implementation, eval estimates)

  • examples/expense-audit/: the free first run: a hand-compiled pipeline with working extracted code that rote run completes on python, dbos, and temporal with no credentials

  • examples/bdr-outreach/: the canonical skill, its ground-truth IR, and snapshotted real compiler runs

  • examples/ops-report/: the 100%-roteness archetype: every step deterministic, one durable HITL gate, zero LLM nodes after compilation

  • examples/deal-monitor/: the data-heavy archetype: parallel entry waves, fan-out judges, and a template render replacing per-run LLM-generated HTML

  • examples/invoice-push/: the agent_loop archetype: a bounded browser-automation loop stays one agent node while the date math, filtering, and reporting around it compile to code, plus the measured runs that forced the loop-aware cost model

  • examples/rote-compile/: the compiler compiling its own core skill, with preserved artifacts and an offline audit of the generated workflow's runtime and data-flow failures


Roadmap

In rough priority order:

  1. Pre-filter as a pure_function node: today hard thresholds are lifted into a judge's forward(), which works for the Python runtimes but not the TypeScript ones; a separate node makes the short-circuit uniform across all six.

  2. Make self-compilation executable: the preserved core-skill run produced valid IR, and runtime emission now succeeds. Its offline audit still has data-flow and agent-result contract failures. Close those gaps before treating it as a working replacement compiler.

  3. More example skills: four archetypes are covered today (BDR outreach, 100%-roteness, data-heavy, agent-loop). Research-heavy, retrieval-heavy, and code-review skills each stress the IR in ways none of those four do.


FAQ

What is rote?

rote is an open source CLI, Apache-2.0 licensed and published as rote-cli on PyPI, that compiles a proven AI agent skill into a typed, deterministic pipeline. It reads an Anthropic-style SKILL.md, classifies each step, moves fixed logic and tool orchestration into reviewable code, and calls a model only for the steps that genuinely require judgment.

How does rote reduce AI agent token costs?

It removes model calls rather than making them cheaper. A repeating agent spends tokens re-reading instructions, tool schemas, and history to re-derive a procedure it already established. rote compiles that procedure into code, so a repeated run pays only for the steps still classified 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 needs lower cost, faster execution, regression tests, explicit approvals, or reliable retries.

Does rote replace my agent framework or MCP?

No. A compiled workflow can still call authenticated MCP servers and retain bounded agent loops. rote decides which parts of a process should stop being inference; your runtime and your integrations stay where 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 for teams that would rather not operate the runtime themselves.


Contributing

The most useful contribution right now is to run rote compile on a real skill of your own and report what happens. The rubric was designed against one skill and needs more. Adding a runtime adapter or a compiler driver, or improving the rubric, are all good next steps. See CONTRIBUTING.md for dev setup, the test layout, and the adapter/driver how-tos.

License

Apache-2.0. See LICENSE.

Available Tools

2 tools
rote_describe_pipelineRote Describe PipelineA
Read-onlyIdempotent

Return one registered pipeline's full registry entry: the JSON Schema its trigger tool accepts, its runtime trigger configuration with any URL passwords redacted, and its trigger, status, and signal tool names. Fails with the list of registered names when the name is unknown.

NameRequiredDescriptionDefault
nameYesRegistered pipeline name, exactly as rote_list_pipelines reports it (it is also the name of the pipeline's trigger tool).

Output Schema

NameRequiredDescription
nameYesRegistered tool name, which is also the pipeline's trigger tool
toolsYesThe MCP tools ``rote serve`` synthesizes for one registered pipeline.
runtimeYesWorkflow runtime the pipeline is deployed on
triggerYesThe registered trigger configuration for the runtime (addresses, queue and workflow names, URLs, gate signals). Passwords in URLs are replaced by ***.
descriptionYesThe pipeline's description from its pipeline.yaml
input_schemaYesJSON Schema the trigger tool accepts (the pipeline's input contract)
pipeline_yamlYesAbsolute path of the compiled pipeline.yaml
registered_atYesISO-8601 time of the last rote register

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only, idempotent, closed-world behavior, so the description's added value is real: it discloses that URL passwords in the runtime trigger config are redacted and that unknown names fail with the list of registered names, both non-obvious behaviors. It stops short of describing size limits or config shape edge cases, so not a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single dense sentence with the return contents front-loaded and the error behavior trailing, with no filler. It is long but every clause carries information; slightly more structure (e.g., splitting the failure case) would improve scanning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values needn't be re-explained, yet the description still summarizes the entry's parts and pairs that with the unknown-name failure path. For a one-parameter read tool this is everything an agent needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the parameter's schema text already explains it is the registered name exactly as rote_list_pipelines reports it and doubles as the trigger tool name. The description reinforces but does not extend that meaning, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Return') and a tightly scoped resource ('one registered pipeline's full registry entry'), then enumerates exactly what the entry contains: trigger JSON Schema, runtime trigger config, and tool names. The 'one registered pipeline' framing contrasts naturally with the singleton sibling rote_list_pipelines, so an agent can distinguish them without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is clear: use this to inspect a single pipeline's full registry entry, and the name must come 'exactly as rote_list_pipelines reports it', which routes the agent through the sibling first. It also states the failure behavior, so an agent knows a bad name returns the registered list rather than an opaque error. No explicit when-not/exclusion is given, which keeps it below 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rote_list_pipelinesRote List PipelinesA
Read-onlyIdempotent

List every compiled pipeline this server can trigger: its name, description, runtime, where its trigger points, and the names of its trigger, status, and (dbos only) signal tools. Reads the registry file on each call, so pipelines added by rote register show up immediately. An empty or missing registry returns an empty list rather than an error.

NameRequiredDescriptionDefault
runtimeNoOnly return pipelines deployed on this runtime. Omit to list every registered pipeline.

Output Schema

NameRequiredDescription
pipelinesYesEvery registered pipeline matching the filter, in registry order
registry_pathYesRegistry file this server reads
registry_existsYesFalse when the file has not been written yet (nothing registered)

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description offers significant behavioral context beyond the annotations and schema. It explains that it reads the registry file on each call (implying real-time updates) and explicitly defines error handling—an empty registry results in an empty list, not an error.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is highly efficient, grouping all essential information (what it lists, how often it refreshes, error handling) into a cohesive and logically structured paragraph without any padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The definition is complete; it clearly dictates what the tool does, how it interacts with the system (registry read), and how it behaves under non-ideal conditions (empty registry), making agent invocation straightforward.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Given that the schema description coverage is 100%, the baseline is 3. The description does not elaborate on the meaning of the 'runtime' enum values or how they interact with the output list.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description provides a precise definition of the action (List) and the resource (compiled pipelines), detailing exactly what information is returned (name, runtime, trigger points, etc.). It immediately establishes its function and scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

While it doesn't explicitly say 'do X instead of Y', it defines the scope of the data source (the server's registry) and its real-time nature, which implicitly guides when this tool is necessary (e.g., for current, live pipeline status).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.16.0
    • Addedrote_describe_pipeline
    • Addedrote_list_pipelines

TDQS

A4.1/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one lists all pipelines, the other describes a single pipeline in detail. There is no overlap or ambiguity between them.

Naming Consistency5/5

Both tools follow the same predictable pattern: `rote_` prefix followed by a verb and noun (`rote_list_pipelines`, `rote_describe_pipeline`). The convention is consistent and readable.

Tool Count2/5

Only two tools are provided, which is too few for a server that can trigger pipelines and manage their lifecycle. The descriptions imply trigger, status, and signal tools exist, but they are absent, making the set severely under-scoped.

Completeness2/5

The surface only covers listing and describing pipelines, but the core operations of triggering, checking status, and sending signals are missing. This is a significant gap that will prevent agents from completing actual pipeline workflows.

Maintenance

ActivityMaintained
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers