Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,26 @@
"security",
"compliance"
]
},
{
"name": "rote",
"description": "Compile a proven skill (a SKILL.md plus references) into a deterministic pipeline that runs without an LLM in the loop, then serve the compiled pipelines back to Claude as MCP tools.",
"version": "0.12.0",
"author": {
"name": "Trevor Hudson",
"url": "https://github.com/trevhud"
},
"source": "./plugins/rote",
"category": "Workflow Orchestration",
"homepage": "https://github.com/trevhud/rote",
"keywords": [
"workflow",
"compilation",
"deterministic",
"temporal",
"dbos",
"mcp"
]
}
]
}
1 change: 1 addition & 0 deletions README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
- [lyra](./plugins/lyra)
- [model-context-protocol-mcp-expert](./plugins/model-context-protocol-mcp-expert)
- [problem-solver-specialist](./plugins/problem-solver-specialist)
- [rote](./plugins/rote)
- [studio-coach](./plugins/studio-coach)
- [ultrathink](./plugins/ultrathink)

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Install or disable them dynamically with the `/plugin` command — enabling you
- [lyra](./plugins/lyra)
- [model-context-protocol-mcp-expert](./plugins/model-context-protocol-mcp-expert)
- [problem-solver-specialist](./plugins/problem-solver-specialist)
- [rote](./plugins/rote)
- [studio-coach](./plugins/studio-coach)
- [ultrathink](./plugins/ultrathink)

Expand Down
23 changes: 23 additions & 0 deletions plugins/rote/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "rote",
"description": "Compile a proven skill (a SKILL.md plus references) into a deterministic pipeline that runs without an LLM in the loop. /rote:compile emits DBOS, Temporal, Cloudflare Workflows, Inngest, or plain Python/TS; /rote:serve exposes the compiled pipelines back to Claude as MCP tools.",
"version": "0.12.0",
"author": {
"name": "Trevor Hudson",
"email": "trevhud@gmail.com"
},
"homepage": "https://github.com/trevhud/rote",
"repository": "https://github.com/trevhud/rote",
"license": "Apache-2.0",
"keywords": [
"workflow",
"compilation",
"deterministic",
"temporal",
"dbos",
"cloudflare",
"inngest",
"mcp",
"skills"
]
}
131 changes: 131 additions & 0 deletions plugins/rote/skills/compile/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
name: compile
description: >-
Compile an Anthropic-style skill — a directory with a SKILL.md and optional
references/ — into a deterministic, runnable workflow via the rote CLI. Use
when the user says "compile this skill", "graduate this skill" (the retired
name for the same operation), "make this skill deterministic", "make this
skill faster/cheaper", "turn this skill into a workflow", "turn this skill
into code", "harden this skill for production", or complains that a skill
is slow, expensive, or unreliable as a background job. Output: a
pipeline.yaml IR, extracted code modules, typed LLM-judge signatures, and
runtime code for Temporal, Cloudflare Workflows, or DBOS.
---

# Compile a skill

You orchestrate the `rote` CLI. It runs an LLM compiler agent over a
source skill and emits a deterministic pipeline. Your job: resolve the
inputs, run the CLI, then interpret the output for the user. You never
classify nodes or write pipeline.yaml yourself — the CLI's agent does.

## 1. Identify the source skill

The source is a **directory containing a `SKILL.md`** (optionally a
`references/` folder). The user names it, or you infer it from context
(a skill just discussed, a path in the conversation, `.claude/skills/*`
or `skills/*` in the project).

**Confirm the resolved absolute path with the user before running.**
Compilation costs real time and tokens; never guess-and-go. If the
directory has no `SKILL.md`, stop and ask.

## 2. Pick a runtime target

Ask the user which runtime, with these tradeoffs (one line each):

| Runtime | Choose when | Emits |
|---|---|---|
| `dbos` | No infra to run — durability lives in SQLite/Postgres, runs anywhere Python runs | Python |
| `cloudflare` | You want serverless, fully managed execution on Cloudflare Workers | TypeScript |
| `temporal` | You already operate (or want) a Temporal cluster | Python |

If the user has no opinion and no existing infra, use `dbos` — it is
the CLI's default and the only target with zero standing
infrastructure (you can omit `--runtime` entirely in that case).

## 3. Resolve the CLI (uv)

The CLI ships on PyPI as the `rote-cli` package and is run via `uvx` —
no virtualenv, no pip, nothing to install beyond uv itself. The
package's executable is named `rote`, so every invocation is
`uvx --from rote-cli rote <args>`. Do **not** run `uvx rote-cli ...` —
uvx looks for an executable named after the package and the published
wheel doesn't ship one.

1. Check uv: `uv --version`. If missing, tell the user to install it
with one command, then re-check:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

2. Confirm the CLI resolves:

```sh
uvx --from rote-cli rote --version
```

3. Only if the user needs unreleased features (or PyPI is
unreachable), substitute the GitHub source — same CLI, different
origin:

```sh
uvx --from git+https://github.com/trevhud/rote rote --version
```

Do **not** clone the repo or build a venv; `uvx` handles isolation.

## 4. Run the compilation

```sh
uvx --from rote-cli rote compile <skill-dir> --runtime <runtime> --out <out-dir>
```

Pick an out-dir the user will find, e.g. `./compiled/<skill-name>`
next to the source skill. Ensure it does not clobber existing work.

Set expectations **before** launching — this is not a quick command:

- It spawns `claude -p` as a subprocess. The driver deliberately
scrubs `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from the child
environment so the run bills against the user's Claude
subscription, not per-token API charges. Do not "fix" auth by
exporting an API key; if the user explicitly wants API billing,
pass `--agent api` instead.
- A realistic skill takes **~13 minutes wall clock and 30-40 agent
turns** (Sonnet, ~$0.70 on subscription). Small skills are faster.
- Therefore **run it in the background** and tell the user you did.
Poll the process and check in rather than blocking the session.

If the run exits nonzero, check whether `<out-dir>/compiled/pipeline.yaml`
exists anyway — the CLI recovers completed work from transient
subprocess failures and says so in its output. Surface stderr to the
user either way.

## 5. Report the result

Read `<out-dir>/compiled/pipeline.yaml` and
`<out-dir>/compiled/compile-report.md`, then summarize:

1. **Node-kind table** — count nodes per kind and what each kind means
here:

| Kind | Count | Meaning |
|---|---|---|
| `pure_function` | n | deterministic code, LLM removed |
| `external_call` | n | direct API call with retry/timeout |
| `llm_judge` | n | typed LLM signature (kept, but bounded) |
| `agent_loop` | n | still agentic (genuinely exploratory) |
| `hitl_gate` | n | durable human approval point |

2. **Codified fraction** — nodes that no longer need an LLM, mandatory
nodes, and what each HITL gate blocks on.
3. **Where things landed** — `<out-dir>/compiled/` (IR, `extracted/`,
`signatures/`, report) and `<out-dir>/runtime/<runtime>/` (the
deployable code).
4. **Next steps** — the `extracted/*` modules are scaffolds that raise
`NotImplementedError`; the user fills in real API client code, then
deploys the runtime output. Once deployed, `rote register` +
`rote serve` expose the pipeline as an MCP tool so Claude can
trigger runs — the `serve` skill in this plugin walks through that.
100 changes: 100 additions & 0 deletions plugins/rote/skills/serve/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
name: serve
description: >-
Wire a compiled rote pipeline up as an MCP tool so Claude can trigger the
deployed workflow directly. Use when the user says "register my compiled
pipeline", "serve my pipelines over MCP", "trigger the workflow from
Claude", "hook the pipeline up to Claude", or asks what to do after
`rote compile` and deployment. Covers `rote register` and `rote serve`
plus the `claude mcp add` wiring.
---

# Serve compiled pipelines as MCP tools

`rote serve` is one MCP server exposing every registered pipeline as a
callable tool. The flow:

```
rote compile → deploy the runtime → rote register → rote serve → call from Claude
```

`rote serve` **triggers deployed workflows; it does not host them.**
MCP triggering supports the `dbos` (default), `temporal`, and
`cloudflare` runtimes.

The CLI ships on PyPI as `rote-cli` with an executable named `rote`,
so every invocation is `uvx --from rote-cli rote <args>` (never
`uvx rote-cli ...`). For unreleased features, substitute the source:
`uvx --from git+https://github.com/trevhud/rote rote <args>`.

## 1. Check preconditions

- A compile output directory exists (contains `compiled/pipeline.yaml`).
- The runtime side is running: for DBOS, the emitted app in worker mode
(`python main.py --serve` or `dbos start`) against the system
database you'll register — enqueued runs sit in status `enqueued`
until that process exists; for Temporal, a worker against the user's
cluster; for Cloudflare, `wrangler deploy` done. If not, stop and
help with that first.

## 2. Register the pipeline

```sh
# DBOS (the default). System DB URL: --system-database-url, else
# $DBOS_SYSTEM_DATABASE_URL, else the emitted app's SQLite file
# (derived from <out-dir>/runtime/dbos/main.py).
uvx --from rote-cli rote register <out-dir>

# Temporal (defaults: localhost:7233, namespace "default",
# task queue = pipeline.name, workflow type = the emitted versioned name)
uvx --from rote-cli rote register <out-dir> --runtime temporal

# Cloudflare
uvx --from rote-cli rote register <out-dir> --runtime cloudflare \
--url https://<worker>.workers.dev
```

This upserts `~/.rote/registry.json`. Re-registering updates in place.
**After re-compiling a changed skill, register again** — the DBOS and
Temporal workflow names are derived from the pipeline content hash and
must stay in sync with the emitted code.

## 3. Add the MCP server to Claude

`rote serve` needs the `serve` extra (FastMCP) plus `dbos` when any
registered pipeline runs on DBOS, so the spec includes both:

```sh
claude mcp add --scope user rote -- uvx --from 'rote-cli[serve,dbos]' rote serve
```

For unreleased features, use the GitHub source instead:

```sh
claude mcp add --scope user rote -- \
uvx --from 'rote-cli[serve,dbos] @ git+https://github.com/trevhud/rote' rote serve
```

Verify with `claude mcp list`. Each registry entry becomes two tools
(three for DBOS): `<name>` (starts a run, returns `{workflow_id,
status: "started"}` immediately — compiled pipelines run minutes to
days), `<name>_status` (polls a run by `workflow_id`), and for DBOS
`<name>_signal` (resumes a run parked at a HITL gate: `workflow_id` +
gate signal name + resume payload — so Claude can deliver approvals
itself). A DBOS run whose status stays `enqueued` means the emitted app
process isn't running against the registered system database.

## 4. Explain the reconnect caveat

A running `rote serve` picks up registry changes live — no restart of
the server, ever. But clients differ:

- **Claude Code** refreshes its tool list on the server's
`list_changed` notification: newly registered pipelines appear
immediately.
- **Claude Desktop and claude.ai** snapshot tools at connect time. A
pipeline registered while they're connected appears only after a
reconnect — restart Desktop or toggle the server off/on; on
claude.ai, re-enable the connector.

Tell the user this proactively if they plan to use Desktop or claude.ai.