rote-cli 0.3.0__tar.gz → 0.5.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.
- {rote_cli-0.3.0 → rote_cli-0.5.0}/PKG-INFO +63 -23
- {rote_cli-0.3.0 → rote_cli-0.5.0}/README.md +62 -22
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/__init__.py +1 -1
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/adapters/__init__.py +23 -2
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/adapters/_common.py +54 -0
- rote_cli-0.5.0/src/rote/adapters/_py_common.py +642 -0
- rote_cli-0.5.0/src/rote/adapters/_ts_common.py +338 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/adapters/cloudflare.py +21 -302
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/adapters/dbos.py +73 -586
- rote_cli-0.5.0/src/rote/adapters/dbos_ts.py +906 -0
- rote_cli-0.5.0/src/rote/adapters/inngest.py +996 -0
- rote_cli-0.5.0/src/rote/adapters/python.py +671 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/adapters/temporal.py +6 -5
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/cli.py +107 -19
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/ir.py +99 -0
- rote_cli-0.5.0/src/rote/serve/backends.py +343 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/serve/registry.py +49 -2
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/serve/server.py +65 -0
- rote_cli-0.3.0/src/rote/serve/backends.py +0 -187
- {rote_cli-0.3.0 → rote_cli-0.5.0}/.gitignore +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/LICENSE +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/pyproject.toml +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/skills/rote-graduate/SKILL.md +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/skills/rote-graduate/references/crystallization-heuristics.md +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/skills/rote-graduate/references/ir-schema.md +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/skills/rote-graduate/references/llm-judge-extraction.md +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/skills/rote-graduate/references/node-kinds.md +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/graduator/__init__.py +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/graduator/drivers/__init__.py +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/graduator/drivers/anthropic_api.py +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/graduator/drivers/claude.py +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/graduator/drivers/codex.py +0 -0
- {rote_cli-0.3.0 → rote_cli-0.5.0}/src/rote/serve/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rote-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Graduate fuzzy AI skills into deterministic, reliable workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/trevhud/rote
|
|
6
6
|
Project-URL: Repository, https://github.com/trevhud/rote
|
|
@@ -59,8 +59,13 @@ applies a structured graduation rubric, and emits:
|
|
|
59
59
|
- and runtime code for your durable execution engine of choice.
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
|
|
63
|
+
|
|
64
|
+
# Default target is DBOS — durable execution as a plain Python library,
|
|
65
|
+
# no orchestrator to run, SQLite for dev / Postgres for prod:
|
|
66
|
+
rote graduate ./examples/bdr-outreach/skill --out ./graduated/
|
|
67
|
+
|
|
68
|
+
# Or target Temporal (Python) or Cloudflare Workflows (TypeScript):
|
|
64
69
|
rote graduate ./examples/bdr-outreach/skill --runtime temporal --out ./graduated/
|
|
65
70
|
rote graduate ./examples/bdr-outreach/skill --runtime cloudflare --out ./graduated/
|
|
66
71
|
```
|
|
@@ -140,6 +145,10 @@ The graduator independently:
|
|
|
140
145
|
After the agent finishes, a runtime adapter consumes the IR and emits
|
|
141
146
|
the target runtime's native code shape:
|
|
142
147
|
|
|
148
|
+
- The **DBOS** adapter (the default) emits a single `main.py` — one
|
|
149
|
+
`@DBOS.workflow` DAG plus a `@DBOS.step` per node, checkpointing to
|
|
150
|
+
SQLite or Postgres. `python main.py` *is* the runtime; there is no
|
|
151
|
+
orchestrator to deploy.
|
|
143
152
|
- The **Temporal** adapter emits `workflow.py` (the orchestration
|
|
144
153
|
class with `@workflow.defn` and signal handlers for the HITL gates)
|
|
145
154
|
and `activities.py` (one `@activity.defn` per node, lazy-importing
|
|
@@ -150,6 +159,24 @@ the target runtime's native code shape:
|
|
|
150
159
|
`signatures/*.ts` (Zod schemas + Anthropic SDK calls) and the
|
|
151
160
|
supporting `wrangler.jsonc` / `package.json` / `tsconfig.json`. The
|
|
152
161
|
output is `wrangler deploy`-ready.
|
|
162
|
+
- The **DBOS (TypeScript)** adapter (`--runtime dbos-ts`) emits a
|
|
163
|
+
Node.js app for DBOS Transact: `src/main.ts` registers one durable
|
|
164
|
+
workflow (`DBOS.registerWorkflow`) running the DAG waves and one
|
|
165
|
+
`DBOS.registerStep` per node, with `DBOS.recv(...)` parking each
|
|
166
|
+
HITL gate durably in Postgres until `DBOS.send(...)` resumes it.
|
|
167
|
+
Zero-orchestrator like the Python DBOS target — `node dist/main.js`
|
|
168
|
+
*is* the runtime — but note the TS SDK is Postgres-only (no SQLite
|
|
169
|
+
mode; `npx dbos postgres start` covers local dev).
|
|
170
|
+
- The **Inngest** adapter (`--runtime inngest`) emits a TypeScript
|
|
171
|
+
Inngest app for teams whose graduated skill should live inside the
|
|
172
|
+
Node/Next.js service they already deploy: `src/inngest/pipeline.ts`
|
|
173
|
+
is one durable `inngest.createFunction` running the DAG waves
|
|
174
|
+
(`step.run` per node, `Promise.all` fan-out, `step.waitForEvent`
|
|
175
|
+
parking each HITL gate until its namespaced resume event), plus a
|
|
176
|
+
framework-neutral `inngest/node` serve entrypoint. The emitted
|
|
177
|
+
README shows the drop-in Next.js mount (`inngest/next`). Note:
|
|
178
|
+
Inngest v4 retries are function-level only — the emitter sets the
|
|
179
|
+
max across per-node policies and documents the deltas as comments.
|
|
153
180
|
|
|
154
181
|
None of the emitted code references an MCP runtime, in either
|
|
155
182
|
language — the agent's crystallization step replaces tool calls with
|
|
@@ -297,11 +324,12 @@ The repository includes a real BDR outreach skill in
|
|
|
297
324
|
`examples/bdr-outreach/skill/`. Graduate it:
|
|
298
325
|
|
|
299
326
|
```sh
|
|
300
|
-
rote graduate examples/bdr-outreach/skill
|
|
301
|
-
--runtime temporal \
|
|
302
|
-
--out /tmp/bdr-graduated
|
|
327
|
+
rote graduate examples/bdr-outreach/skill --out /tmp/bdr-graduated
|
|
303
328
|
```
|
|
304
329
|
|
|
330
|
+
This targets DBOS by default; pass `--runtime temporal` or
|
|
331
|
+
`--runtime cloudflare` for the other adapters.
|
|
332
|
+
|
|
305
333
|
By default `rote` auto-detects an available agent driver in this
|
|
306
334
|
order: `claude` (Claude Code CLI) → `codex` (Codex CLI) → `api`
|
|
307
335
|
(Anthropic SDK with `ANTHROPIC_API_KEY`). Override with `--agent`:
|
|
@@ -320,10 +348,12 @@ The output directory is structured as:
|
|
|
320
348
|
│ ├── signatures/*.py # typed LLM-judge signatures
|
|
321
349
|
│ ├── evals/*.jsonl # seed eval examples
|
|
322
350
|
│ └── graduation-report.md # human-readable summary
|
|
323
|
-
└── runtime/
|
|
324
|
-
├── workflow.
|
|
325
|
-
├──
|
|
326
|
-
|
|
351
|
+
└── runtime/dbos/ # produced by the adapter
|
|
352
|
+
├── main.py # @DBOS.workflow + one @DBOS.step per node
|
|
353
|
+
├── extracted/*.py # copied deterministic functions
|
|
354
|
+
├── signatures/*.py # generated Pydantic + vendor-SDK judges
|
|
355
|
+
├── dbos-config.yaml
|
|
356
|
+
└── README.md # how to run, signal HITL gates, deploy
|
|
327
357
|
```
|
|
328
358
|
|
|
329
359
|
### Render an IR without re-running the agent
|
|
@@ -332,6 +362,7 @@ If you already have a `pipeline.yaml` (hand-written or from a previous
|
|
|
332
362
|
graduation), `rote emit` runs just the adapter step:
|
|
333
363
|
|
|
334
364
|
```sh
|
|
365
|
+
rote emit /path/to/pipeline.yaml --out /tmp/emitted/ # dbos (default)
|
|
335
366
|
rote emit /path/to/pipeline.yaml --runtime temporal --out /tmp/emitted/
|
|
336
367
|
```
|
|
337
368
|
|
|
@@ -393,10 +424,12 @@ Opus's extra reasoning earns its cost.
|
|
|
393
424
|
## Status
|
|
394
425
|
|
|
395
426
|
`rote` is **pre-1.0**. The end-to-end flow works on the BDR example
|
|
396
|
-
and the test suite covers each layer (
|
|
397
|
-
plus
|
|
398
|
-
|
|
399
|
-
|
|
427
|
+
and the test suite covers each layer (231 tests in the fast suite,
|
|
428
|
+
plus 5 slow tests that run the emitted code against real runtimes:
|
|
429
|
+
a DBOS runtime over SQLite, Temporal's time-skipping test server, the
|
|
430
|
+
emitted Cloudflare TypeScript compiled against real
|
|
431
|
+
`@cloudflare/workers-types` and driven through both HITL gates via
|
|
432
|
+
`wrangler dev`, and the MCP server over a real stdio transport).
|
|
400
433
|
Run `pytest tests/` for the fast suite; `pytest tests/ -m slow` for
|
|
401
434
|
the toolchain-dependent integration tests.
|
|
402
435
|
|
|
@@ -406,15 +439,18 @@ the toolchain-dependent integration tests.
|
|
|
406
439
|
| Temporal adapter | working (validated with mocked-activities e2e test) |
|
|
407
440
|
| Cloudflare Workflows adapter | working (validated with `tsc --noEmit` over the real emitted output) |
|
|
408
441
|
| DBOS adapter | working (validated against a real DBOS runtime over SQLite in the e2e test) |
|
|
442
|
+
| DBOS TypeScript adapter (`dbos-ts`) | working (validated with `tsc --noEmit` over the real emitted output and a live run on the DBOS TS runtime against Docker Postgres) |
|
|
443
|
+
| Raw Python adapter (`python`) | working (emitted script executed as a subprocess in the e2e test: wave order, payload threading, and a live retry loop; refuses `hitl_gate` pipelines at emit time) |
|
|
409
444
|
| Graduator orchestrator | working |
|
|
410
445
|
| `rote graduate` / `rote emit` CLI commands | working |
|
|
411
446
|
| `claude` driver | working |
|
|
412
447
|
| `api` (Anthropic SDK) driver | working |
|
|
413
448
|
| `codex` driver | stub (`is_available` works; `run` not implemented) |
|
|
414
|
-
| Inngest
|
|
449
|
+
| Inngest adapter (`inngest`) | working (validated with `tsc --noEmit` over the real emitted output and a live run through both HITL gates against the Inngest dev server) |
|
|
450
|
+
| Restate adapter | planned |
|
|
415
451
|
| Real implementations of the extracted modules | the agent produces stubs that raise `NotImplementedError`; humans fill them in with real API client code |
|
|
416
|
-
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all
|
|
417
|
-
| Distribution via PyPI |
|
|
452
|
+
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all five adapters (Temporal, Cloudflare, DBOS, DBOS-TS, Inngest) thread real payloads through the DAG, validated in the runtime e2e tests |
|
|
453
|
+
| Distribution via PyPI | published as [`rote-cli`](https://pypi.org/project/rote-cli/) (`pip install rote-cli`); tag-driven Trusted Publishing releases — see [docs/releasing.md](docs/releasing.md) |
|
|
418
454
|
|
|
419
455
|
The project explicitly **does not** depend on `claude-agent-sdk`.
|
|
420
456
|
Anthropic's terms of service forbid third-party agents built on the
|
|
@@ -523,11 +559,15 @@ In rough priority order:
|
|
|
523
559
|
graduator the new field, but no real graduator run has produced
|
|
524
560
|
one yet. Re-running `rote graduate examples/bdr-outreach/skill`
|
|
525
561
|
should produce the structured form natively.
|
|
526
|
-
3. **A third runtime adapter.**
|
|
527
|
-
programming model is meaningfully
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
562
|
+
3. **A third runtime adapter.** *(Done — the Inngest adapter shipped,
|
|
563
|
+
alongside DBOS Python/TS.)* Its programming model is meaningfully
|
|
564
|
+
different from both Temporal and Cloudflare — retries are
|
|
565
|
+
function-level, the platform drives execution from outside the
|
|
566
|
+
process — and the IR survived without changes (the per-node retry
|
|
567
|
+
gap is documented at emission, not papered over in the IR). Each
|
|
568
|
+
new adapter remains a stress test on whether the IR is genuinely
|
|
569
|
+
runtime-agnostic vs. accidentally shaped like one of the existing
|
|
570
|
+
targets.
|
|
531
571
|
4. **Pre-filter as `pure_function` node.** Today the rubric lifts
|
|
532
572
|
hard thresholds into a Python `forward()` method, which works for
|
|
533
573
|
Temporal but not for Cloudflare. Modeling the pre-filter as a
|
|
@@ -561,7 +601,7 @@ The most useful contributions right now are:
|
|
|
561
601
|
needs to be tested against more.
|
|
562
602
|
- **Add a runtime adapter.** The Temporal adapter in
|
|
563
603
|
`src/rote/adapters/temporal.py` is ~450 lines and follows a clear
|
|
564
|
-
pattern.
|
|
604
|
+
pattern. Restate and Hatchet are both good targets.
|
|
565
605
|
- **Add a graduator driver.** The Protocol in
|
|
566
606
|
`src/rote/graduator/drivers/__init__.py` is simple. Aider, Gemini
|
|
567
607
|
CLI, and Cursor Agent are reasonable additions.
|
|
@@ -13,8 +13,13 @@ applies a structured graduation rubric, and emits:
|
|
|
13
13
|
- and runtime code for your durable execution engine of choice.
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
pip install rote-cli # or zero-install: uvx --from rote-cli rote ...
|
|
17
|
+
|
|
18
|
+
# Default target is DBOS — durable execution as a plain Python library,
|
|
19
|
+
# no orchestrator to run, SQLite for dev / Postgres for prod:
|
|
20
|
+
rote graduate ./examples/bdr-outreach/skill --out ./graduated/
|
|
21
|
+
|
|
22
|
+
# Or target Temporal (Python) or Cloudflare Workflows (TypeScript):
|
|
18
23
|
rote graduate ./examples/bdr-outreach/skill --runtime temporal --out ./graduated/
|
|
19
24
|
rote graduate ./examples/bdr-outreach/skill --runtime cloudflare --out ./graduated/
|
|
20
25
|
```
|
|
@@ -94,6 +99,10 @@ The graduator independently:
|
|
|
94
99
|
After the agent finishes, a runtime adapter consumes the IR and emits
|
|
95
100
|
the target runtime's native code shape:
|
|
96
101
|
|
|
102
|
+
- The **DBOS** adapter (the default) emits a single `main.py` — one
|
|
103
|
+
`@DBOS.workflow` DAG plus a `@DBOS.step` per node, checkpointing to
|
|
104
|
+
SQLite or Postgres. `python main.py` *is* the runtime; there is no
|
|
105
|
+
orchestrator to deploy.
|
|
97
106
|
- The **Temporal** adapter emits `workflow.py` (the orchestration
|
|
98
107
|
class with `@workflow.defn` and signal handlers for the HITL gates)
|
|
99
108
|
and `activities.py` (one `@activity.defn` per node, lazy-importing
|
|
@@ -104,6 +113,24 @@ the target runtime's native code shape:
|
|
|
104
113
|
`signatures/*.ts` (Zod schemas + Anthropic SDK calls) and the
|
|
105
114
|
supporting `wrangler.jsonc` / `package.json` / `tsconfig.json`. The
|
|
106
115
|
output is `wrangler deploy`-ready.
|
|
116
|
+
- The **DBOS (TypeScript)** adapter (`--runtime dbos-ts`) emits a
|
|
117
|
+
Node.js app for DBOS Transact: `src/main.ts` registers one durable
|
|
118
|
+
workflow (`DBOS.registerWorkflow`) running the DAG waves and one
|
|
119
|
+
`DBOS.registerStep` per node, with `DBOS.recv(...)` parking each
|
|
120
|
+
HITL gate durably in Postgres until `DBOS.send(...)` resumes it.
|
|
121
|
+
Zero-orchestrator like the Python DBOS target — `node dist/main.js`
|
|
122
|
+
*is* the runtime — but note the TS SDK is Postgres-only (no SQLite
|
|
123
|
+
mode; `npx dbos postgres start` covers local dev).
|
|
124
|
+
- The **Inngest** adapter (`--runtime inngest`) emits a TypeScript
|
|
125
|
+
Inngest app for teams whose graduated skill should live inside the
|
|
126
|
+
Node/Next.js service they already deploy: `src/inngest/pipeline.ts`
|
|
127
|
+
is one durable `inngest.createFunction` running the DAG waves
|
|
128
|
+
(`step.run` per node, `Promise.all` fan-out, `step.waitForEvent`
|
|
129
|
+
parking each HITL gate until its namespaced resume event), plus a
|
|
130
|
+
framework-neutral `inngest/node` serve entrypoint. The emitted
|
|
131
|
+
README shows the drop-in Next.js mount (`inngest/next`). Note:
|
|
132
|
+
Inngest v4 retries are function-level only — the emitter sets the
|
|
133
|
+
max across per-node policies and documents the deltas as comments.
|
|
107
134
|
|
|
108
135
|
None of the emitted code references an MCP runtime, in either
|
|
109
136
|
language — the agent's crystallization step replaces tool calls with
|
|
@@ -251,11 +278,12 @@ The repository includes a real BDR outreach skill in
|
|
|
251
278
|
`examples/bdr-outreach/skill/`. Graduate it:
|
|
252
279
|
|
|
253
280
|
```sh
|
|
254
|
-
rote graduate examples/bdr-outreach/skill
|
|
255
|
-
--runtime temporal \
|
|
256
|
-
--out /tmp/bdr-graduated
|
|
281
|
+
rote graduate examples/bdr-outreach/skill --out /tmp/bdr-graduated
|
|
257
282
|
```
|
|
258
283
|
|
|
284
|
+
This targets DBOS by default; pass `--runtime temporal` or
|
|
285
|
+
`--runtime cloudflare` for the other adapters.
|
|
286
|
+
|
|
259
287
|
By default `rote` auto-detects an available agent driver in this
|
|
260
288
|
order: `claude` (Claude Code CLI) → `codex` (Codex CLI) → `api`
|
|
261
289
|
(Anthropic SDK with `ANTHROPIC_API_KEY`). Override with `--agent`:
|
|
@@ -274,10 +302,12 @@ The output directory is structured as:
|
|
|
274
302
|
│ ├── signatures/*.py # typed LLM-judge signatures
|
|
275
303
|
│ ├── evals/*.jsonl # seed eval examples
|
|
276
304
|
│ └── graduation-report.md # human-readable summary
|
|
277
|
-
└── runtime/
|
|
278
|
-
├── workflow.
|
|
279
|
-
├──
|
|
280
|
-
|
|
305
|
+
└── runtime/dbos/ # produced by the adapter
|
|
306
|
+
├── main.py # @DBOS.workflow + one @DBOS.step per node
|
|
307
|
+
├── extracted/*.py # copied deterministic functions
|
|
308
|
+
├── signatures/*.py # generated Pydantic + vendor-SDK judges
|
|
309
|
+
├── dbos-config.yaml
|
|
310
|
+
└── README.md # how to run, signal HITL gates, deploy
|
|
281
311
|
```
|
|
282
312
|
|
|
283
313
|
### Render an IR without re-running the agent
|
|
@@ -286,6 +316,7 @@ If you already have a `pipeline.yaml` (hand-written or from a previous
|
|
|
286
316
|
graduation), `rote emit` runs just the adapter step:
|
|
287
317
|
|
|
288
318
|
```sh
|
|
319
|
+
rote emit /path/to/pipeline.yaml --out /tmp/emitted/ # dbos (default)
|
|
289
320
|
rote emit /path/to/pipeline.yaml --runtime temporal --out /tmp/emitted/
|
|
290
321
|
```
|
|
291
322
|
|
|
@@ -347,10 +378,12 @@ Opus's extra reasoning earns its cost.
|
|
|
347
378
|
## Status
|
|
348
379
|
|
|
349
380
|
`rote` is **pre-1.0**. The end-to-end flow works on the BDR example
|
|
350
|
-
and the test suite covers each layer (
|
|
351
|
-
plus
|
|
352
|
-
|
|
353
|
-
|
|
381
|
+
and the test suite covers each layer (231 tests in the fast suite,
|
|
382
|
+
plus 5 slow tests that run the emitted code against real runtimes:
|
|
383
|
+
a DBOS runtime over SQLite, Temporal's time-skipping test server, the
|
|
384
|
+
emitted Cloudflare TypeScript compiled against real
|
|
385
|
+
`@cloudflare/workers-types` and driven through both HITL gates via
|
|
386
|
+
`wrangler dev`, and the MCP server over a real stdio transport).
|
|
354
387
|
Run `pytest tests/` for the fast suite; `pytest tests/ -m slow` for
|
|
355
388
|
the toolchain-dependent integration tests.
|
|
356
389
|
|
|
@@ -360,15 +393,18 @@ the toolchain-dependent integration tests.
|
|
|
360
393
|
| Temporal adapter | working (validated with mocked-activities e2e test) |
|
|
361
394
|
| Cloudflare Workflows adapter | working (validated with `tsc --noEmit` over the real emitted output) |
|
|
362
395
|
| DBOS adapter | working (validated against a real DBOS runtime over SQLite in the e2e test) |
|
|
396
|
+
| DBOS TypeScript adapter (`dbos-ts`) | working (validated with `tsc --noEmit` over the real emitted output and a live run on the DBOS TS runtime against Docker Postgres) |
|
|
397
|
+
| Raw Python adapter (`python`) | working (emitted script executed as a subprocess in the e2e test: wave order, payload threading, and a live retry loop; refuses `hitl_gate` pipelines at emit time) |
|
|
363
398
|
| Graduator orchestrator | working |
|
|
364
399
|
| `rote graduate` / `rote emit` CLI commands | working |
|
|
365
400
|
| `claude` driver | working |
|
|
366
401
|
| `api` (Anthropic SDK) driver | working |
|
|
367
402
|
| `codex` driver | stub (`is_available` works; `run` not implemented) |
|
|
368
|
-
| Inngest
|
|
403
|
+
| Inngest adapter (`inngest`) | working (validated with `tsc --noEmit` over the real emitted output and a live run through both HITL gates against the Inngest dev server) |
|
|
404
|
+
| Restate adapter | planned |
|
|
369
405
|
| Real implementations of the extracted modules | the agent produces stubs that raise `NotImplementedError`; humans fill them in with real API client code |
|
|
370
|
-
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all
|
|
371
|
-
| Distribution via PyPI |
|
|
406
|
+
| Workflow data flow between activities | working — nodes declare `inputs:` bindings and all five adapters (Temporal, Cloudflare, DBOS, DBOS-TS, Inngest) thread real payloads through the DAG, validated in the runtime e2e tests |
|
|
407
|
+
| Distribution via PyPI | published as [`rote-cli`](https://pypi.org/project/rote-cli/) (`pip install rote-cli`); tag-driven Trusted Publishing releases — see [docs/releasing.md](docs/releasing.md) |
|
|
372
408
|
|
|
373
409
|
The project explicitly **does not** depend on `claude-agent-sdk`.
|
|
374
410
|
Anthropic's terms of service forbid third-party agents built on the
|
|
@@ -477,11 +513,15 @@ In rough priority order:
|
|
|
477
513
|
graduator the new field, but no real graduator run has produced
|
|
478
514
|
one yet. Re-running `rote graduate examples/bdr-outreach/skill`
|
|
479
515
|
should produce the structured form natively.
|
|
480
|
-
3. **A third runtime adapter.**
|
|
481
|
-
programming model is meaningfully
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
516
|
+
3. **A third runtime adapter.** *(Done — the Inngest adapter shipped,
|
|
517
|
+
alongside DBOS Python/TS.)* Its programming model is meaningfully
|
|
518
|
+
different from both Temporal and Cloudflare — retries are
|
|
519
|
+
function-level, the platform drives execution from outside the
|
|
520
|
+
process — and the IR survived without changes (the per-node retry
|
|
521
|
+
gap is documented at emission, not papered over in the IR). Each
|
|
522
|
+
new adapter remains a stress test on whether the IR is genuinely
|
|
523
|
+
runtime-agnostic vs. accidentally shaped like one of the existing
|
|
524
|
+
targets.
|
|
485
525
|
4. **Pre-filter as `pure_function` node.** Today the rubric lifts
|
|
486
526
|
hard thresholds into a Python `forward()` method, which works for
|
|
487
527
|
Temporal but not for Cloudflare. Modeling the pre-filter as a
|
|
@@ -515,7 +555,7 @@ The most useful contributions right now are:
|
|
|
515
555
|
needs to be tested against more.
|
|
516
556
|
- **Add a runtime adapter.** The Temporal adapter in
|
|
517
557
|
`src/rote/adapters/temporal.py` is ~450 lines and follows a clear
|
|
518
|
-
pattern.
|
|
558
|
+
pattern. Restate and Hatchet are both good targets.
|
|
519
559
|
- **Add a graduator driver.** The Protocol in
|
|
520
560
|
`src/rote/graduator/drivers/__init__.py` is simple. Aider, Gemini
|
|
521
561
|
CLI, and Cursor Agent are reasonable additions.
|
|
@@ -6,8 +6,8 @@ adapters are template substitution.
|
|
|
6
6
|
|
|
7
7
|
The "two adapters minimum" rule applies: until at least two adapters
|
|
8
8
|
work end-to-end, assume the IR shape is secretly leaking the first
|
|
9
|
-
runtime's mental model.
|
|
10
|
-
Temporal.
|
|
9
|
+
runtime's mental model. (Satisfied — five adapters share the IR today:
|
|
10
|
+
Temporal, Cloudflare, DBOS Python, DBOS TypeScript, and Inngest.)
|
|
11
11
|
|
|
12
12
|
Adapters are registered in :data:`ADAPTERS` and dispatched by name from
|
|
13
13
|
the CLI (``rote emit --runtime <name>``).
|
|
@@ -48,12 +48,33 @@ def _dbos_adapter_factory() -> Adapter:
|
|
|
48
48
|
return DbosAdapter()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _dbos_ts_adapter_factory() -> Adapter:
|
|
52
|
+
from rote.adapters.dbos_ts import DbosTsAdapter
|
|
53
|
+
|
|
54
|
+
return DbosTsAdapter()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _inngest_adapter_factory() -> Adapter:
|
|
58
|
+
from rote.adapters.inngest import InngestAdapter
|
|
59
|
+
|
|
60
|
+
return InngestAdapter()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _python_adapter_factory() -> Adapter:
|
|
64
|
+
from rote.adapters.python import PythonAdapter
|
|
65
|
+
|
|
66
|
+
return PythonAdapter()
|
|
67
|
+
|
|
68
|
+
|
|
51
69
|
#: Name → factory. Keep the values as zero-arg callables so adapters can
|
|
52
70
|
#: lazy-import their heavy dependencies.
|
|
53
71
|
ADAPTERS: dict[str, Callable[[], Adapter]] = {
|
|
54
72
|
"temporal": _temporal_adapter_factory,
|
|
55
73
|
"cloudflare": _cloudflare_adapter_factory,
|
|
56
74
|
"dbos": _dbos_adapter_factory,
|
|
75
|
+
"dbos-ts": _dbos_ts_adapter_factory,
|
|
76
|
+
"inngest": _inngest_adapter_factory,
|
|
77
|
+
"python": _python_adapter_factory,
|
|
57
78
|
}
|
|
58
79
|
|
|
59
80
|
|
|
@@ -13,9 +13,63 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import re
|
|
16
|
+
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
from rote.ir import Node, Pipeline, parse_input_ref
|
|
18
19
|
|
|
20
|
+
# ───────── Prose → safe code comment/docstring ─────────
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def safe_docstring_line(text: str, fallback: str = "") -> str:
|
|
24
|
+
"""First line of ``text``, escaped so it cannot break out of a docstring.
|
|
25
|
+
|
|
26
|
+
Node ``description`` is prose (it legitimately contains quotes and
|
|
27
|
+
backslashes), yet adapters splice its first line straight into a
|
|
28
|
+
``\"\"\"…\"\"\"`` Python docstring / block comment. An unescaped
|
|
29
|
+
``\"\"\"`` — or a trailing backslash that escapes the closing quote —
|
|
30
|
+
lets a crafted pipeline.yaml close the docstring early and inject
|
|
31
|
+
code. Escaping backslashes and double-quotes neutralizes both without
|
|
32
|
+
charset-restricting prose at the IR layer.
|
|
33
|
+
"""
|
|
34
|
+
stripped = text.strip() if text else ""
|
|
35
|
+
first = stripped.splitlines()[0] if stripped else fallback
|
|
36
|
+
return first.replace("\\", "\\\\").replace('"', '\\"')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def safe_block_comment_line(text: str, fallback: str = "") -> str:
|
|
40
|
+
"""First line of ``text``, safe to splice into a ``/* … */`` block comment.
|
|
41
|
+
|
|
42
|
+
The TypeScript adapter emits ``description`` first-lines into JSDoc
|
|
43
|
+
comments. An unescaped ``*/`` would close the comment early and let a
|
|
44
|
+
crafted pipeline.yaml inject code after it; neutralize the sequence by
|
|
45
|
+
inserting a space (``*/`` → ``* /``). Newlines are already dropped by
|
|
46
|
+
taking the first line.
|
|
47
|
+
"""
|
|
48
|
+
stripped = text.strip() if text else ""
|
|
49
|
+
first = stripped.splitlines()[0] if stripped else fallback
|
|
50
|
+
return first.replace("*/", "* /")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ───────── Output-path containment ─────────
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def resolve_within(base: Path, *parts: str) -> Path:
|
|
57
|
+
"""Join ``parts`` onto ``base`` and assert the result stays inside it.
|
|
58
|
+
|
|
59
|
+
Defense-in-depth for emitted-file paths. IR validation already pins
|
|
60
|
+
``node.id`` to an identifier, but this guard means any future field
|
|
61
|
+
that reaches a filename cannot escape the output directory via an
|
|
62
|
+
absolute segment or ``..`` traversal — it fails loudly at emit time.
|
|
63
|
+
"""
|
|
64
|
+
base_resolved = base.resolve()
|
|
65
|
+
target = base_resolved.joinpath(*parts).resolve()
|
|
66
|
+
if target != base_resolved and base_resolved not in target.parents:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Refusing to write outside output directory: {target} is not within {base_resolved}"
|
|
69
|
+
)
|
|
70
|
+
return target
|
|
71
|
+
|
|
72
|
+
|
|
19
73
|
# ───────── Case conversion ─────────
|
|
20
74
|
|
|
21
75
|
|