rote-cli 0.14.0__tar.gz → 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {rote_cli-0.14.0 → rote_cli-0.15.0}/PKG-INFO +139 -29
  2. {rote_cli-0.14.0 → rote_cli-0.15.0}/README.md +129 -20
  3. rote_cli-0.15.0/examples/expense-audit/README.md +156 -0
  4. rote_cli-0.15.0/examples/expense-audit/eval-input.json +199 -0
  5. rote_cli-0.15.0/examples/expense-audit/expected/eval.yaml +60 -0
  6. rote_cli-0.15.0/examples/expense-audit/expected/extracted/audit.py +171 -0
  7. rote_cli-0.15.0/examples/expense-audit/expected/extracted/checks.py +71 -0
  8. rote_cli-0.15.0/examples/expense-audit/expected/extracted/fx.py +62 -0
  9. rote_cli-0.15.0/examples/expense-audit/expected/extracted/report.py +186 -0
  10. rote_cli-0.15.0/examples/expense-audit/expected/pipeline-draft.yaml +242 -0
  11. rote_cli-0.15.0/examples/expense-audit/expected/pipeline.yaml +269 -0
  12. rote_cli-0.15.0/examples/expense-audit/input.json +21 -0
  13. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/compile-metrics.json +17 -0
  14. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/compile-report.md +220 -0
  15. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/eval.yaml +84 -0
  16. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/extracted/checks.py +94 -0
  17. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/extracted/fx.py +100 -0
  18. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/extracted/policy.py +92 -0
  19. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/extracted/report.py +145 -0
  20. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/extracted/save.py +51 -0
  21. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/pipeline.yaml +382 -0
  22. rote_cli-0.15.0/examples/expense-audit/runs/2026-10-18-first-agent-run/provenance.json +49 -0
  23. rote_cli-0.15.0/examples/expense-audit/scorecard.md +82 -0
  24. rote_cli-0.15.0/examples/expense-audit/skill/SKILL.md +94 -0
  25. rote_cli-0.15.0/examples/expense-audit/skill/references/expense-policy.md +48 -0
  26. rote_cli-0.15.0/examples/expense-audit/skill/references/fx-rates.md +30 -0
  27. rote_cli-0.15.0/examples/expense-audit/skill/references/report-format.md +43 -0
  28. {rote_cli-0.14.0 → rote_cli-0.15.0}/pyproject.toml +31 -8
  29. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/SKILL.md +42 -13
  30. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/references/crystallization-heuristics.md +19 -2
  31. rote_cli-0.15.0/skills/rote-compile/references/implementation.md +347 -0
  32. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/references/ir-schema.md +95 -7
  33. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/references/llm-judge-extraction.md +49 -3
  34. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/references/node-kinds.md +11 -1
  35. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/__init__.py +1 -1
  36. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/_dbos.py +16 -15
  37. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/__init__.py +2 -2
  38. rote_cli-0.15.0/src/rote/adapters/_common.py +1029 -0
  39. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/_py_common.py +486 -25
  40. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/_ts_common.py +975 -182
  41. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/cloudflare.py +75 -47
  42. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/dbos.py +135 -77
  43. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/dbos_ts.py +115 -71
  44. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/inngest.py +99 -46
  45. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/python.py +61 -80
  46. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/adapters/temporal.py +78 -18
  47. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/cli.py +861 -143
  48. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/__init__.py +163 -40
  49. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/__init__.py +12 -2
  50. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/anthropic_api.py +7 -7
  51. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/claude.py +276 -51
  52. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/openai_api.py +7 -7
  53. rote_cli-0.15.0/src/rote/compiler/provenance.py +679 -0
  54. rote_cli-0.15.0/src/rote/compiler/sandbox.py +316 -0
  55. rote_cli-0.15.0/src/rote/compiler/verify.py +636 -0
  56. rote_cli-0.15.0/src/rote/contracts.py +647 -0
  57. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/empirical.py +143 -18
  58. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/estimate.py +59 -3
  59. rote_cli-0.15.0/src/rote/eval/infra_pricing.py +298 -0
  60. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/pricing.py +17 -3
  61. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/priors.py +23 -0
  62. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/scorecard.py +148 -7
  63. rote_cli-0.15.0/src/rote/examples.py +184 -0
  64. rote_cli-0.15.0/src/rote/extras.py +138 -0
  65. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/inference/__init__.py +2 -0
  66. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/inference/_runtime_helper.py +478 -93
  67. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/init_wizard.py +2 -1
  68. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/ir.py +479 -14
  69. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/mcp/__init__.py +1 -3
  70. rote_cli-0.15.0/src/rote/mcp/_runtime_helper.py +741 -0
  71. rote_cli-0.15.0/src/rote/mcp/auth.py +864 -0
  72. rote_cli-0.15.0/src/rote/mcp/client_metadata.json +20 -0
  73. rote_cli-0.15.0/src/rote/mcp/learned.py +168 -0
  74. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/mcp/live_tools.py +20 -21
  75. rote_cli-0.15.0/src/rote/mcp/parked.py +626 -0
  76. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/mcp/registry.py +32 -23
  77. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/mcp/release.py +69 -37
  78. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/mcp/tokens.py +59 -13
  79. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/runners/__init__.py +70 -0
  80. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/runners/_node.py +2 -2
  81. rote_cli-0.15.0/src/rote/runners/cloudflare.py +313 -0
  82. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/runners/dbos_ts.py +58 -10
  83. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/runners/inngest.py +60 -6
  84. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/runners/temporal.py +21 -11
  85. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/serve/backends.py +3 -2
  86. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/serve/server.py +108 -30
  87. rote_cli-0.14.0/skills/rote-compile/references/implementation.md +0 -109
  88. rote_cli-0.14.0/src/rote/adapters/_common.py +0 -541
  89. rote_cli-0.14.0/src/rote/contracts.py +0 -330
  90. rote_cli-0.14.0/src/rote/mcp/_runtime_helper.py +0 -378
  91. rote_cli-0.14.0/src/rote/mcp/auth.py +0 -166
  92. rote_cli-0.14.0/src/rote/runners/cloudflare.py +0 -172
  93. {rote_cli-0.14.0 → rote_cli-0.15.0}/.gitignore +0 -0
  94. {rote_cli-0.14.0 → rote_cli-0.15.0}/LICENSE +0 -0
  95. {rote_cli-0.14.0 → rote_cli-0.15.0}/skills/rote-compile/references/eval-estimates.md +0 -0
  96. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/__main__.py +0 -0
  97. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/app_registry.py +0 -0
  98. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/cloud_auth.py +0 -0
  99. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/cloud_compile.py +0 -0
  100. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/_fs_tools.py +0 -0
  101. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/_heartbeat.py +0 -0
  102. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/drivers/codex.py +0 -0
  103. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/events.py +0 -0
  104. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/compiler/update.py +0 -0
  105. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/config.py +0 -0
  106. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/deploy.py +0 -0
  107. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/deploy_rote_cloud.py +0 -0
  108. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/__init__.py +0 -0
  109. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/baseline.py +0 -0
  110. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/sidecar.py +0 -0
  111. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/eval/tokens.py +0 -0
  112. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/probe.py +0 -0
  113. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/py.typed +0 -0
  114. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/serve/__init__.py +0 -0
  115. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/serve/registry.py +0 -0
  116. {rote_cli-0.14.0 → rote_cli-0.15.0}/src/rote/skill_source.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rote-cli
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: Compile AI agent skills into cheap, fast, deterministic pipelines that run without an LLM in the loop
5
5
  Project-URL: Homepage, https://roteskills.com
6
6
  Project-URL: Repository, https://github.com/trevhud/rote
@@ -25,30 +25,31 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
25
  Classifier: Typing :: Typed
26
26
  Requires-Python: >=3.11
27
27
  Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: pytest<10,>=9.0
28
29
  Requires-Dist: pyyaml>=6.0
29
30
  Provides-Extra: api
30
- Requires-Dist: anthropic>=0.89; extra == 'api'
31
+ Requires-Dist: anthropic>=1.15; extra == 'api'
31
32
  Provides-Extra: dbos
32
- Requires-Dist: dbos<3,>=2.26; extra == 'dbos'
33
+ Requires-Dist: dbos<4,>=3.3; extra == 'dbos'
33
34
  Provides-Extra: dev
34
- Requires-Dist: anthropic>=0.89; extra == 'dev'
35
- Requires-Dist: dbos<3,>=2.26; extra == 'dev'
36
- Requires-Dist: fastmcp<4,>=3.4.2; extra == 'dev'
35
+ Requires-Dist: anthropic>=1.15; extra == 'dev'
36
+ Requires-Dist: dbos<4,>=3.3; extra == 'dev'
37
+ Requires-Dist: fastmcp<5,>=4.1.1; extra == 'dev'
37
38
  Requires-Dist: httpx>=0.28; extra == 'dev'
38
39
  Requires-Dist: mypy>=1.10; extra == 'dev'
39
40
  Requires-Dist: openai>=2.45; extra == 'dev'
40
41
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
41
- Requires-Dist: pytest>=8.0; extra == 'dev'
42
42
  Requires-Dist: ruff>=0.6; extra == 'dev'
43
43
  Requires-Dist: temporalio>=1.7; extra == 'dev'
44
+ Requires-Dist: trustme>=1.2.1; extra == 'dev'
44
45
  Requires-Dist: types-pyyaml; extra == 'dev'
45
46
  Provides-Extra: mcp
46
- Requires-Dist: fastmcp<4,>=3.4.2; extra == 'mcp'
47
+ Requires-Dist: fastmcp<5,>=4.1.1; extra == 'mcp'
47
48
  Requires-Dist: httpx>=0.28; extra == 'mcp'
48
49
  Provides-Extra: openai-api
49
50
  Requires-Dist: openai>=2.45; extra == 'openai-api'
50
51
  Provides-Extra: serve
51
- Requires-Dist: fastmcp<4,>=3.4.2; extra == 'serve'
52
+ Requires-Dist: fastmcp<5,>=4.1.1; extra == 'serve'
52
53
  Requires-Dist: httpx>=0.28; extra == 'serve'
53
54
  Provides-Extra: temporal
54
55
  Requires-Dist: temporalio>=1.7; extra == 'temporal'
@@ -75,6 +76,91 @@ genuinely need judgment. A 10 to 20 minute agent loop becomes a
75
76
  background workflow that costs a fraction of the tokens and can be
76
77
  regression tested.
77
78
 
79
+ ### Try it free in 60 seconds
80
+
81
+ No API key, no subscription, no tokens, no clone. The
82
+ [`expense-audit`](examples/expense-audit/) example is a month-end expense
83
+ audit skill whose compiled pipeline ships with working code, and it comes
84
+ inside the package, so you can emit it and run it before you compile
85
+ anything of your own:
86
+
87
+ ```sh
88
+ uv tool install 'rote-cli[dbos]' # or: python3 -m venv .venv && . .venv/bin/activate && pip install 'rote-cli[dbos]'
89
+ rote example expense-audit # copies the skill, IR, code and sample input to ./expense-audit
90
+ # (zero-install: uvx --from rote-cli rote example expense-audit)
91
+
92
+ # IR -> a plain Python script, then run it on the sample batch
93
+ rote emit expense-audit/expected/pipeline-draft.yaml --runtime python --out expense-audit/app-python
94
+ rote run expense-audit/app-python --input expense-audit/input.json
95
+ ```
96
+
97
+ `rote example` prints these commands (and the dbos and eval ones) for
98
+ the copy it made. `rote example` alone lists what is bundled. Prefer the
99
+ repo? `git clone https://github.com/trevhud/rote.git && cd rote`, then
100
+ use `examples/expense-audit/` in place of `expense-audit/`.
101
+
102
+ ```
103
+ rote run (python): succeeded
104
+ 0.1s
105
+ {
106
+ "file_report": {
107
+ "report_path": "/home/you/expense-audit/app-python/reports/2026-09-expense-audit.md",
108
+ "status": "draft",
109
+ "reviewer": null,
110
+ "reimbursable_usd": 2395.59,
111
+ "pending_usd": 844.39,
112
+ "returned_usd": 64.17,
113
+ "batch_total_usd": 3304.15,
114
+ "auto_approved": ["Dev Patel", "Emi Tanaka"],
115
+ "review_items": 4,
116
+ "blocked_items": 2
117
+ }
118
+ }
119
+ ```
120
+
121
+ Sixteen expense lines in five currencies went through a parallel wave of
122
+ three mandatory checks, a per-employee audit fanned out five ways, and a
123
+ report written to disk, with no model anywhere in the loop. The full
124
+ skill also parks for the controller's approval, which needs a durable
125
+ runtime:
126
+
127
+ ```sh
128
+ rote emit expense-audit/expected/pipeline.yaml --runtime dbos --out expense-audit/app-dbos
129
+ rote run expense-audit/app-dbos --input expense-audit/input.json \
130
+ --signal controller_review='{"reviewer": "Priya Shah", "approved_ids": ["EXP-2403", "EXP-2413", "EXP-2420"], "rejected_ids": ["EXP-2410"]}'
131
+ # ... "status": "final", "reimbursable_usd": 2990.98, "returned_usd": 313.17
132
+ ```
133
+
134
+ What compiling it bought, measured: we ran the same batch twice each way
135
+ with `rote eval --run` (the skill as an agent via `claude -p` on Sonnet,
136
+ against the dbos app above):
137
+
138
+ | | Skill run as an agent | Compiled pipeline |
139
+ | --- | --- | --- |
140
+ | Wall clock per run | 42.9 s | 9.8 s, mostly DBOS startup |
141
+ | Cost per run | $0.22 | $0 |
142
+ | Identical output across runs | 1 of 2 | 2 of 2 |
143
+
144
+ So it's roughly 4x faster in practice, it drops from $0.22 a run to
145
+ nothing, and it gives the same answer every time. The agent's two runs
146
+ disagreed on where they filed the report. Full results are in
147
+ [`examples/expense-audit/scorecard.md`](examples/expense-audit/scorecard.md).
148
+
149
+ To get a scorecard for any pipeline without running anything, use
150
+ `rote eval`. It fetches the live price list once and runs no inference.
151
+ Its numbers are an estimate from turn counts, not a measurement:
152
+
153
+ ```sh
154
+ rote eval examples/expense-audit/expected/pipeline.yaml
155
+ ```
156
+
157
+ `pip` installs don't include `examples/`, hence the clone. The other
158
+ examples' extracted modules are `NotImplementedError` stubs waiting for
159
+ real vendor API clients, so `expense-audit` is the one that runs end to
160
+ end. Details in [its README](examples/expense-audit/README.md).
161
+
162
+ ### Compile your own skill
163
+
78
164
  ```sh
79
165
  pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
80
166
 
@@ -159,6 +245,19 @@ representation (`pipeline.yaml`), extracted Python modules for the
159
245
  deterministic parts, typed signature stubs for the LLM-judge parts, and
160
246
  runnable code for the durable execution engine of your choice.
161
247
 
248
+ The agent implements the deterministic `pure_function` steps the skill
249
+ spells out and writes tests for them. Before the compile reports
250
+ success, rote checks what the agent wrote and runs those tests, and any
251
+ failure goes back to the agent to fix. The tests are code an LLM wrote
252
+ from a skill you may not have written, so rote runs them in an OS
253
+ sandbox (`sandbox-exec` on macOS, bubblewrap on Linux): no network, no
254
+ reads outside the compile's work dir and the Python install, no programs
255
+ other than Python, no API keys in the environment, an empty `HOME`. If
256
+ no sandbox works on your machine, the tests are skipped and the compile
257
+ says the deliverable is unverified. `--trust-skill` runs them without a
258
+ sandbox, and `--no-verify-tests` never runs them; either way, review
259
+ `<out>/compiled/tests/` before running them yourself.
260
+
162
261
  `rote` is a three-layer system; each layer has one job and contracts on
163
262
  a small interface.
164
263
 
@@ -180,7 +279,7 @@ a small interface.
180
279
  ```
181
280
 
182
281
  1. **The compiler agent** (`skills/rote-compile/`): a regular
183
- Anthropic Skill (`SKILL.md` + four reference files). This is the
282
+ Anthropic Skill (`SKILL.md` + six reference files). This is the
184
283
  *brain*; it runs inside any Skills-compatible surface, and you don't
185
284
  need `rote` to use it.
186
285
  2. **The IR** (`src/rote/ir.py`): Pydantic models for the five node
@@ -210,7 +309,9 @@ touching Python tooling:
210
309
  Then say "compile this skill" (or run `/rote:compile`). It confirms the
211
310
  source directory, asks which runtime you want, runs the CLI via
212
311
  [uv](https://docs.astral.sh/uv/) in the background, and reports the
213
- emitted pipeline. A second skill, `/rote:serve`, wires compiled
312
+ emitted pipeline. `/rote:implement` then fills in the emitted stub
313
+ modules against each node's IR contract and verifies the result with
314
+ `rote run`. A third skill, `/rote:serve`, wires compiled
214
315
  pipelines up as MCP tools so Claude can trigger the deployed workflows
215
316
  (see [docs/mcp-trigger.md](docs/mcp-trigger.md)).
216
317
 
@@ -230,9 +331,10 @@ this README still works logged out.
230
331
 
231
332
  > **Naming note:** the `rote` package on PyPI is an unrelated
232
333
  > memoization library that also installs `import rote`, so the two can't
233
- > share an environment. This project's distribution is `rote-cli` while
234
- > the CLI command and import name stay `rote`, hence
235
- > `uvx --from rote-cli rote ...`. See [docs/releasing.md](docs/releasing.md).
334
+ > share an environment. This project's distribution is `rote-cli` and the
335
+ > import name stays `rote`. The package installs the same CLI under two
336
+ > commands, `rote` and `rote-cli`, so both `uvx rote-cli ...` and
337
+ > `uvx --from rote-cli rote ...` work. See [docs/releasing.md](docs/releasing.md).
236
338
 
237
339
  ### Run on the bundled example
238
340
 
@@ -246,7 +348,7 @@ rote compile examples/bdr-outreach/skill --out /tmp/bdr-compiled
246
348
 
247
349
  On that skill the compiler produces a 22-node IR that's **78.9%
248
350
  codifiable** (15 of 19 non-gate nodes), extracts 5 Python modules and 2
249
- typed judge signatures, and flags 4 mandatory nodes and 3 HITL gates,
351
+ typed judge signatures, and flags 3 mandatory nodes and 3 HITL gates,
250
352
  all in ~13 minutes for ~$0.70 (Sonnet via Claude Code). Along the way it
251
353
  independently lifts the three MANDATORY exclusion checks out of prose,
252
354
  pulls four batch-size constants out of prompt text, and models a
@@ -425,6 +527,8 @@ subscription path.
425
527
 
426
528
  ## How it differs from other tools
427
529
 
530
+ For a side-by-side with calcifier, skill-to-workflow, Claude Code dynamic workflows, and hand-written workflows, see [docs/comparison.md](docs/comparison.md).
531
+
428
532
  - **vs. raw durable engines (Temporal / Cloudflare / Inngest / Restate):**
429
533
  they give you the workflow *runtime*; they don't help you decide *what
430
534
  should be a workflow*. `rote` is the missing step that turns a working
@@ -443,16 +547,17 @@ subscription path.
443
547
  ## Status
444
548
 
445
549
  `rote` is **pre-1.0**. The end-to-end flow works on the BDR example. The
446
- fast suite (`pytest tests/`) makes no real API calls and is what CI runs
447
- on every push, alongside a Python e2e (DBOS over SQLite + the MCP server
448
- over real stdio). Each adapter also has a `slow`-marked e2e that runs its
449
- emitted code against the real runtime (Temporal's time-skipping server,
450
- the TypeScript targets via `tsc --noEmit` and live dev servers, the
451
- plain-Python subprocess); those need a Node toolchain / Docker, so they
452
- run locally with `pytest tests/ -m slow`, not in CI.
453
-
454
- Known gaps: the extracted modules are `NotImplementedError` stubs
455
- you fill in with real API-client code, and a Restate adapter is planned.
550
+ fast suite (`pytest tests/`) makes no real API calls, spends no tokens,
551
+ and runs on every push. CI also runs every `slow`-marked e2e suite on
552
+ every push: DBOS over SQLite, the MCP server over real stdio, the
553
+ TypeScript targets via `tsc --noEmit` and live dev servers (Cloudflare
554
+ `wrangler dev`, the Inngest dev server), and DBOS TypeScript against a
555
+ Docker Postgres. Locally, `pytest tests/ -m slow` runs them; they need a
556
+ Node toolchain and Docker.
557
+
558
+ Known gaps: extracted modules that need a vendor API client (HubSpot,
559
+ ZoomInfo, and so on) come out as `NotImplementedError` stubs you fill in,
560
+ and a Restate adapter is planned.
456
561
  Published on
457
562
  PyPI as [`rote-cli`](https://pypi.org/project/rote-cli/) via tag-driven
458
563
  Trusted Publishing ([docs/releasing.md](docs/releasing.md)).
@@ -472,7 +577,7 @@ measure your own workflow instead of reading someone else's, use
472
577
  ```
473
578
  rote/
474
579
  ├── docs/ agent-runtime · mcp-client · mcp-trigger · releasing
475
- ├── skills/rote-compile/ the compiler agent (SKILL.md + 4 reference files)
580
+ ├── skills/rote-compile/ the compiler agent (SKILL.md + 6 reference files)
476
581
  ├── src/rote/
477
582
  │ ├── cli.py rote compile / emit / eval / serve
478
583
  │ ├── ir.py Pydantic IR models + load_pipeline
@@ -480,6 +585,7 @@ rote/
480
585
  │ └── adapters/ dbos · temporal · python · cloudflare · dbos_ts · inngest
481
586
  │ (+ _common / _py_common / _ts_common emit helpers)
482
587
  ├── examples/
588
+ │ ├── expense-audit/ free first run: working extracted code · fan-out · HITL gate
483
589
  │ ├── bdr-outreach/ canonical: all 5 node kinds · IR baseline · run snapshots
484
590
  │ ├── ops-report/ 100% roteness: zero LLM nodes + a HITL gate
485
591
  │ ├── deal-monitor/ data-heavy: parallel waves · fan-out judges · template render
@@ -501,12 +607,16 @@ rote/
501
607
  emitted code uses under `--backend mcp`: endpoint/credential
502
608
  resolution and durable park-on-auth across every MCP-capable runtime
503
609
  - [`docs/mcp-trigger.md`](docs/mcp-trigger.md): `rote register` +
504
- `rote serve`: compiled pipelines as MCP tools (FastMCP 3.x)
610
+ `rote serve`: compiled pipelines as MCP tools (FastMCP 4.x)
505
611
  - [`docs/releasing.md`](docs/releasing.md): tag-driven PyPI Trusted
506
612
  Publishing
507
613
  - [`skills/rote-compile/`](skills/rote-compile/): the compiler's
508
- `SKILL.md` and its four rubric files (node kinds, crystallization
509
- heuristics, IR schema, LLM-judge extraction)
614
+ `SKILL.md` and its six rubric files (node kinds, crystallization
615
+ heuristics, IR schema, LLM-judge extraction, implementation, eval
616
+ estimates)
617
+ - [`examples/expense-audit/`](examples/expense-audit/): the free first
618
+ run: a hand-compiled pipeline with working extracted code that
619
+ `rote run` completes on python, dbos, and temporal with no credentials
510
620
  - [`examples/bdr-outreach/`](examples/bdr-outreach/): the canonical
511
621
  skill, its ground-truth IR, and snapshotted real compiler runs
512
622
  - [`examples/ops-report/`](examples/ops-report/): the 100%-roteness
@@ -19,6 +19,91 @@ genuinely need judgment. A 10 to 20 minute agent loop becomes a
19
19
  background workflow that costs a fraction of the tokens and can be
20
20
  regression tested.
21
21
 
22
+ ### Try it free in 60 seconds
23
+
24
+ No API key, no subscription, no tokens, no clone. The
25
+ [`expense-audit`](examples/expense-audit/) example is a month-end expense
26
+ audit skill whose compiled pipeline ships with working code, and it comes
27
+ inside the package, so you can emit it and run it before you compile
28
+ anything of your own:
29
+
30
+ ```sh
31
+ uv tool install 'rote-cli[dbos]' # or: python3 -m venv .venv && . .venv/bin/activate && pip install 'rote-cli[dbos]'
32
+ rote example expense-audit # copies the skill, IR, code and sample input to ./expense-audit
33
+ # (zero-install: uvx --from rote-cli rote example expense-audit)
34
+
35
+ # IR -> a plain Python script, then run it on the sample batch
36
+ rote emit expense-audit/expected/pipeline-draft.yaml --runtime python --out expense-audit/app-python
37
+ rote run expense-audit/app-python --input expense-audit/input.json
38
+ ```
39
+
40
+ `rote example` prints these commands (and the dbos and eval ones) for
41
+ the copy it made. `rote example` alone lists what is bundled. Prefer the
42
+ repo? `git clone https://github.com/trevhud/rote.git && cd rote`, then
43
+ use `examples/expense-audit/` in place of `expense-audit/`.
44
+
45
+ ```
46
+ rote run (python): succeeded
47
+ 0.1s
48
+ {
49
+ "file_report": {
50
+ "report_path": "/home/you/expense-audit/app-python/reports/2026-09-expense-audit.md",
51
+ "status": "draft",
52
+ "reviewer": null,
53
+ "reimbursable_usd": 2395.59,
54
+ "pending_usd": 844.39,
55
+ "returned_usd": 64.17,
56
+ "batch_total_usd": 3304.15,
57
+ "auto_approved": ["Dev Patel", "Emi Tanaka"],
58
+ "review_items": 4,
59
+ "blocked_items": 2
60
+ }
61
+ }
62
+ ```
63
+
64
+ Sixteen expense lines in five currencies went through a parallel wave of
65
+ three mandatory checks, a per-employee audit fanned out five ways, and a
66
+ report written to disk, with no model anywhere in the loop. The full
67
+ skill also parks for the controller's approval, which needs a durable
68
+ runtime:
69
+
70
+ ```sh
71
+ rote emit expense-audit/expected/pipeline.yaml --runtime dbos --out expense-audit/app-dbos
72
+ rote run expense-audit/app-dbos --input expense-audit/input.json \
73
+ --signal controller_review='{"reviewer": "Priya Shah", "approved_ids": ["EXP-2403", "EXP-2413", "EXP-2420"], "rejected_ids": ["EXP-2410"]}'
74
+ # ... "status": "final", "reimbursable_usd": 2990.98, "returned_usd": 313.17
75
+ ```
76
+
77
+ What compiling it bought, measured: we ran the same batch twice each way
78
+ with `rote eval --run` (the skill as an agent via `claude -p` on Sonnet,
79
+ against the dbos app above):
80
+
81
+ | | Skill run as an agent | Compiled pipeline |
82
+ | --- | --- | --- |
83
+ | Wall clock per run | 42.9 s | 9.8 s, mostly DBOS startup |
84
+ | Cost per run | $0.22 | $0 |
85
+ | Identical output across runs | 1 of 2 | 2 of 2 |
86
+
87
+ So it's roughly 4x faster in practice, it drops from $0.22 a run to
88
+ nothing, and it gives the same answer every time. The agent's two runs
89
+ disagreed on where they filed the report. Full results are in
90
+ [`examples/expense-audit/scorecard.md`](examples/expense-audit/scorecard.md).
91
+
92
+ To get a scorecard for any pipeline without running anything, use
93
+ `rote eval`. It fetches the live price list once and runs no inference.
94
+ Its numbers are an estimate from turn counts, not a measurement:
95
+
96
+ ```sh
97
+ rote eval examples/expense-audit/expected/pipeline.yaml
98
+ ```
99
+
100
+ `pip` installs don't include `examples/`, hence the clone. The other
101
+ examples' extracted modules are `NotImplementedError` stubs waiting for
102
+ real vendor API clients, so `expense-audit` is the one that runs end to
103
+ end. Details in [its README](examples/expense-audit/README.md).
104
+
105
+ ### Compile your own skill
106
+
22
107
  ```sh
23
108
  pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
24
109
 
@@ -103,6 +188,19 @@ representation (`pipeline.yaml`), extracted Python modules for the
103
188
  deterministic parts, typed signature stubs for the LLM-judge parts, and
104
189
  runnable code for the durable execution engine of your choice.
105
190
 
191
+ The agent implements the deterministic `pure_function` steps the skill
192
+ spells out and writes tests for them. Before the compile reports
193
+ success, rote checks what the agent wrote and runs those tests, and any
194
+ failure goes back to the agent to fix. The tests are code an LLM wrote
195
+ from a skill you may not have written, so rote runs them in an OS
196
+ sandbox (`sandbox-exec` on macOS, bubblewrap on Linux): no network, no
197
+ reads outside the compile's work dir and the Python install, no programs
198
+ other than Python, no API keys in the environment, an empty `HOME`. If
199
+ no sandbox works on your machine, the tests are skipped and the compile
200
+ says the deliverable is unverified. `--trust-skill` runs them without a
201
+ sandbox, and `--no-verify-tests` never runs them; either way, review
202
+ `<out>/compiled/tests/` before running them yourself.
203
+
106
204
  `rote` is a three-layer system; each layer has one job and contracts on
107
205
  a small interface.
108
206
 
@@ -124,7 +222,7 @@ a small interface.
124
222
  ```
125
223
 
126
224
  1. **The compiler agent** (`skills/rote-compile/`): a regular
127
- Anthropic Skill (`SKILL.md` + four reference files). This is the
225
+ Anthropic Skill (`SKILL.md` + six reference files). This is the
128
226
  *brain*; it runs inside any Skills-compatible surface, and you don't
129
227
  need `rote` to use it.
130
228
  2. **The IR** (`src/rote/ir.py`): Pydantic models for the five node
@@ -154,7 +252,9 @@ touching Python tooling:
154
252
  Then say "compile this skill" (or run `/rote:compile`). It confirms the
155
253
  source directory, asks which runtime you want, runs the CLI via
156
254
  [uv](https://docs.astral.sh/uv/) in the background, and reports the
157
- emitted pipeline. A second skill, `/rote:serve`, wires compiled
255
+ emitted pipeline. `/rote:implement` then fills in the emitted stub
256
+ modules against each node's IR contract and verifies the result with
257
+ `rote run`. A third skill, `/rote:serve`, wires compiled
158
258
  pipelines up as MCP tools so Claude can trigger the deployed workflows
159
259
  (see [docs/mcp-trigger.md](docs/mcp-trigger.md)).
160
260
 
@@ -174,9 +274,10 @@ this README still works logged out.
174
274
 
175
275
  > **Naming note:** the `rote` package on PyPI is an unrelated
176
276
  > memoization library that also installs `import rote`, so the two can't
177
- > share an environment. This project's distribution is `rote-cli` while
178
- > the CLI command and import name stay `rote`, hence
179
- > `uvx --from rote-cli rote ...`. See [docs/releasing.md](docs/releasing.md).
277
+ > share an environment. This project's distribution is `rote-cli` and the
278
+ > import name stays `rote`. The package installs the same CLI under two
279
+ > commands, `rote` and `rote-cli`, so both `uvx rote-cli ...` and
280
+ > `uvx --from rote-cli rote ...` work. See [docs/releasing.md](docs/releasing.md).
180
281
 
181
282
  ### Run on the bundled example
182
283
 
@@ -190,7 +291,7 @@ rote compile examples/bdr-outreach/skill --out /tmp/bdr-compiled
190
291
 
191
292
  On that skill the compiler produces a 22-node IR that's **78.9%
192
293
  codifiable** (15 of 19 non-gate nodes), extracts 5 Python modules and 2
193
- typed judge signatures, and flags 4 mandatory nodes and 3 HITL gates,
294
+ typed judge signatures, and flags 3 mandatory nodes and 3 HITL gates,
194
295
  all in ~13 minutes for ~$0.70 (Sonnet via Claude Code). Along the way it
195
296
  independently lifts the three MANDATORY exclusion checks out of prose,
196
297
  pulls four batch-size constants out of prompt text, and models a
@@ -369,6 +470,8 @@ subscription path.
369
470
 
370
471
  ## How it differs from other tools
371
472
 
473
+ For a side-by-side with calcifier, skill-to-workflow, Claude Code dynamic workflows, and hand-written workflows, see [docs/comparison.md](docs/comparison.md).
474
+
372
475
  - **vs. raw durable engines (Temporal / Cloudflare / Inngest / Restate):**
373
476
  they give you the workflow *runtime*; they don't help you decide *what
374
477
  should be a workflow*. `rote` is the missing step that turns a working
@@ -387,16 +490,17 @@ subscription path.
387
490
  ## Status
388
491
 
389
492
  `rote` is **pre-1.0**. The end-to-end flow works on the BDR example. The
390
- fast suite (`pytest tests/`) makes no real API calls and is what CI runs
391
- on every push, alongside a Python e2e (DBOS over SQLite + the MCP server
392
- over real stdio). Each adapter also has a `slow`-marked e2e that runs its
393
- emitted code against the real runtime (Temporal's time-skipping server,
394
- the TypeScript targets via `tsc --noEmit` and live dev servers, the
395
- plain-Python subprocess); those need a Node toolchain / Docker, so they
396
- run locally with `pytest tests/ -m slow`, not in CI.
397
-
398
- Known gaps: the extracted modules are `NotImplementedError` stubs
399
- you fill in with real API-client code, and a Restate adapter is planned.
493
+ fast suite (`pytest tests/`) makes no real API calls, spends no tokens,
494
+ and runs on every push. CI also runs every `slow`-marked e2e suite on
495
+ every push: DBOS over SQLite, the MCP server over real stdio, the
496
+ TypeScript targets via `tsc --noEmit` and live dev servers (Cloudflare
497
+ `wrangler dev`, the Inngest dev server), and DBOS TypeScript against a
498
+ Docker Postgres. Locally, `pytest tests/ -m slow` runs them; they need a
499
+ Node toolchain and Docker.
500
+
501
+ Known gaps: extracted modules that need a vendor API client (HubSpot,
502
+ ZoomInfo, and so on) come out as `NotImplementedError` stubs you fill in,
503
+ and a Restate adapter is planned.
400
504
  Published on
401
505
  PyPI as [`rote-cli`](https://pypi.org/project/rote-cli/) via tag-driven
402
506
  Trusted Publishing ([docs/releasing.md](docs/releasing.md)).
@@ -416,7 +520,7 @@ measure your own workflow instead of reading someone else's, use
416
520
  ```
417
521
  rote/
418
522
  ├── docs/ agent-runtime · mcp-client · mcp-trigger · releasing
419
- ├── skills/rote-compile/ the compiler agent (SKILL.md + 4 reference files)
523
+ ├── skills/rote-compile/ the compiler agent (SKILL.md + 6 reference files)
420
524
  ├── src/rote/
421
525
  │ ├── cli.py rote compile / emit / eval / serve
422
526
  │ ├── ir.py Pydantic IR models + load_pipeline
@@ -424,6 +528,7 @@ rote/
424
528
  │ └── adapters/ dbos · temporal · python · cloudflare · dbos_ts · inngest
425
529
  │ (+ _common / _py_common / _ts_common emit helpers)
426
530
  ├── examples/
531
+ │ ├── expense-audit/ free first run: working extracted code · fan-out · HITL gate
427
532
  │ ├── bdr-outreach/ canonical: all 5 node kinds · IR baseline · run snapshots
428
533
  │ ├── ops-report/ 100% roteness: zero LLM nodes + a HITL gate
429
534
  │ ├── deal-monitor/ data-heavy: parallel waves · fan-out judges · template render
@@ -445,12 +550,16 @@ rote/
445
550
  emitted code uses under `--backend mcp`: endpoint/credential
446
551
  resolution and durable park-on-auth across every MCP-capable runtime
447
552
  - [`docs/mcp-trigger.md`](docs/mcp-trigger.md): `rote register` +
448
- `rote serve`: compiled pipelines as MCP tools (FastMCP 3.x)
553
+ `rote serve`: compiled pipelines as MCP tools (FastMCP 4.x)
449
554
  - [`docs/releasing.md`](docs/releasing.md): tag-driven PyPI Trusted
450
555
  Publishing
451
556
  - [`skills/rote-compile/`](skills/rote-compile/): the compiler's
452
- `SKILL.md` and its four rubric files (node kinds, crystallization
453
- heuristics, IR schema, LLM-judge extraction)
557
+ `SKILL.md` and its six rubric files (node kinds, crystallization
558
+ heuristics, IR schema, LLM-judge extraction, implementation, eval
559
+ estimates)
560
+ - [`examples/expense-audit/`](examples/expense-audit/): the free first
561
+ run: a hand-compiled pipeline with working extracted code that
562
+ `rote run` completes on python, dbos, and temporal with no credentials
454
563
  - [`examples/bdr-outreach/`](examples/bdr-outreach/): the canonical
455
564
  skill, its ground-truth IR, and snapshotted real compiler runs
456
565
  - [`examples/ops-report/`](examples/ops-report/): the 100%-roteness
@@ -0,0 +1,156 @@
1
+ # Example: Expense Audit
2
+
3
+ The **free first run**. A month-end expense audit skill whose compiled
4
+ pipeline ships with *working* extracted code, so `rote emit` + `rote run`
5
+ complete end to end with no API key, no subscription, and no tokens. The
6
+ other examples show what compilation produces against real vendor APIs.
7
+ Their extracted modules are `NotImplementedError` stubs until someone
8
+ writes the HubSpot or Gmail client. This one shows the result actually
9
+ running.
10
+
11
+ The work is all arithmetic and rules: convert sixteen expense lines from
12
+ five currencies at the finance month-end rates, find duplicates and late
13
+ submissions, check every line against the policy's caps and receipt
14
+ rules, auto-approve clean reports, and file the audit for payroll. Run
15
+ as an agent, every month re-reads the policy and re-derives the same
16
+ rules from prose. Compiled, it is a few milliseconds of Python and one
17
+ human decision.
18
+
19
+ ## What it demonstrates
20
+
21
+ - **A parallel entry wave.** `convert_to_usd`, `find_duplicates`, and
22
+ `find_late_submissions` all read the raw export and nothing else, so
23
+ they run concurrently (a thread pool on python, `asyncio.gather` on
24
+ temporal, steps enqueued on a DBOS queue on dbos).
25
+ - **`fan_out`.** `audit_report` runs once per employee report, five
26
+ times on the sample batch, and the results come back as a list in
27
+ input order.
28
+ - **`mandatory: true`.** The duplicate and late checks, which SKILL.md
29
+ marks MANDATORY, are unconditional code. They can't be skipped on a
30
+ month that "looks small enough to eyeball".
31
+ - **Rules lifted out of prose.** Category caps, the $25.00 receipt
32
+ threshold, the $2,500.00 report limit, the 60-day window, and the FX
33
+ table live as constants in `expected/extracted/`, not prompt text.
34
+ - **`hitl_gate`.** `controller_review` parks the durable workflow until
35
+ the controller approves or rejects each review item, then the report
36
+ is filed as final.
37
+ - **No `llm_judge`.** Nothing in the skill needs judgment once the
38
+ policy is written down, so the compiled pipeline makes zero model
39
+ calls. Roteness 1.0.
40
+
41
+ ## Two IRs, one pipeline
42
+
43
+ | File | Gate | Runs on |
44
+ | --- | --- | --- |
45
+ | `expected/pipeline.yaml` | `controller_review` | `dbos`, `temporal` (and the TS runtimes for emission) |
46
+ | `expected/pipeline-draft.yaml` | none | `python` too |
47
+
48
+ The `python` runtime refuses pipelines with a `hitl_gate`: a plain script
49
+ can't durably park for days waiting on a human. `pipeline-draft.yaml` is
50
+ the same pipeline with the gate removed, so `file_report` files a
51
+ **draft** with every review item pending. A test
52
+ (`tests/test_expense_audit_example.py`) pins that the two IRs differ by
53
+ exactly the gate and its edges.
54
+
55
+ ## Run it
56
+
57
+ From the repo root, after `uv tool install 'rote-cli[dbos]'` (or `pip install` into a venv):
58
+
59
+ ```sh
60
+ # Plain Python, no gate: a draft report.
61
+ rote emit examples/expense-audit/expected/pipeline-draft.yaml --runtime python --out /tmp/expense-audit
62
+ rote run /tmp/expense-audit --input examples/expense-audit/input.json
63
+
64
+ # DBOS, with the controller's decision delivered to the parked gate.
65
+ rote emit examples/expense-audit/expected/pipeline.yaml --out /tmp/expense-audit-dbos
66
+ rote run /tmp/expense-audit-dbos --input examples/expense-audit/input.json \
67
+ --signal controller_review='{"reviewer": "Priya Shah", "approved_ids": ["EXP-2403", "EXP-2413", "EXP-2420"], "rejected_ids": ["EXP-2410"]}'
68
+
69
+ # Temporal: needs the temporal extra and a release newer than 0.14.0. The first run downloads the dev server.
70
+ rote emit examples/expense-audit/expected/pipeline.yaml --runtime temporal --out /tmp/expense-audit-temporal
71
+ rote run /tmp/expense-audit-temporal --input examples/expense-audit/input.json \
72
+ --signal controller_review='{"reviewer": "Priya Shah", "approved_ids": ["EXP-2403", "EXP-2413", "EXP-2420"], "rejected_ids": ["EXP-2410"]}'
73
+
74
+ # The before/after scorecard (fetches live model prices, no inference).
75
+ rote eval examples/expense-audit/expected/pipeline.yaml
76
+ ```
77
+
78
+ The run prints the totals as JSON and writes the markdown report to
79
+ `reports/2026-09-expense-audit.md` under the app dir on every runtime.
80
+ Set `EXPENSE_AUDIT_REPORT_DIR` to put it somewhere else.
81
+
82
+ Expected results on `input.json`:
83
+
84
+ | | Draft (python) | Final (dbos, temporal) |
85
+ | --- | --- | --- |
86
+ | Reimbursable | $2,395.59 | $2,990.98 |
87
+ | Pending review | $844.39 | $0.00 |
88
+ | Returned to employee | $64.17 | $313.17 |
89
+ | Batch total | $3,304.15 | $3,304.15 |
90
+
91
+ The review queue holds four review items (a meal over the cap, software
92
+ over the cap, a late line, entertainment without pre-approval) and two
93
+ blocks (a $25.97 ride with no receipt, a duplicate Lyft charge filed as
94
+ "lyft"). Blocks go back to the employee whatever the controller says. A
95
+ decision that names a block item fails the step with the ids to fix.
96
+
97
+ ## Layout
98
+
99
+ ```
100
+ examples/expense-audit/
101
+ ├── README.md
102
+ ├── input.json # the sample month export (2026-09)
103
+ ├── eval-input.json # input.json plus the gate payload, for `rote eval --run`
104
+ ├── scorecard.md # measured before/after (rote eval --run)
105
+ ├── skill/
106
+ │ ├── SKILL.md # the source skill, as an agent would run it
107
+ │ └── references/
108
+ │ ├── expense-policy.md # caps, receipts, severities
109
+ │ ├── fx-rates.md # finance month-end rates
110
+ │ └── report-format.md # the layout payroll reads
111
+ └── expected/
112
+ ├── pipeline.yaml # hand-compiled IR, with the controller gate
113
+ ├── pipeline-draft.yaml # the same IR without the gate
114
+ ├── eval.yaml # per-step agent-turn estimates for `rote eval`
115
+ └── extracted/ # working implementations, no stubs
116
+ ├── fx.py
117
+ ├── checks.py
118
+ ├── audit.py
119
+ └── report.py
120
+ └── runs/
121
+ └── 2026-10-18-first-agent-run/ # a real `rote compile` of skill/, for comparison
122
+ ```
123
+
124
+ `expected/` is hand-authored, like the other examples' baselines.
125
+
126
+ ## Measured, not estimated
127
+
128
+ `scorecard.md` is `rote eval --run --trials 2` on this example, with
129
+ `eval-input.json` (the batch plus the controller's decision). The agent
130
+ runs took 42.9 s and $0.22 each, and they disagreed on `report_path`.
131
+ The pipeline runs took 9.8 s, most of it DBOS startup, cost $0, and
132
+ produced identical output. The static estimate at the top of that file
133
+ (3.2 to 6.5 minutes before) is only a model of agent turns. Trust the
134
+ Measured section over it. To reproduce, emit the dbos app as above and
135
+ run this (it spends subscription tokens on the agent side):
136
+
137
+ ```sh
138
+ rote eval examples/expense-audit/expected/pipeline.yaml --run --trials 2 \
139
+ --input examples/expense-audit/eval-input.json --runtime-dir /tmp/expense-audit-dbos
140
+ ```
141
+
142
+ ## What the real compiler produced
143
+
144
+ `runs/2026-10-18-first-agent-run/` is a real `rote compile` of this
145
+ skill (claude driver, Sonnet, 46 turns, 12 minutes, $2.56). It agrees on
146
+ node kinds and on the three-way parallel entry wave, and it puts the
147
+ controller gate in the same place. It differs in three ways:
148
+
149
+ - It folded grouping and auditing into one `apply_policy_rules` node
150
+ instead of fanning `audit_report` out per employee.
151
+ - It marked 7 nodes mandatory where the hand-written IR marks 5.
152
+ - It left every extracted function as a `NotImplementedError` stub, even
153
+ though this skill's logic is plain Python.
154
+
155
+ The hand-written IR is the intended shape. The compiler gaps are being
156
+ fixed in a separate PR.