rote-cli 0.8.0__tar.gz → 0.10.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.8.0 → rote_cli-0.10.0}/PKG-INFO +77 -7
- {rote_cli-0.8.0 → rote_cli-0.10.0}/README.md +70 -6
- {rote_cli-0.8.0 → rote_cli-0.10.0}/pyproject.toml +8 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/SKILL.md +26 -1
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/crystallization-heuristics.md +34 -0
- rote_cli-0.10.0/skills/rote-graduate/references/implementation.md +109 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/ir-schema.md +43 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/node-kinds.md +12 -4
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/__init__.py +1 -1
- rote_cli-0.10.0/src/rote/__main__.py +12 -0
- rote_cli-0.10.0/src/rote/_dbos.py +65 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/__init__.py +28 -11
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/_common.py +47 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/_py_common.py +79 -16
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/_ts_common.py +670 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/cloudflare.py +363 -43
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/dbos.py +243 -48
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/dbos_ts.py +240 -22
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/inngest.py +259 -15
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/python.py +5 -2
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/adapters/temporal.py +103 -29
- rote_cli-0.10.0/src/rote/app_registry.py +85 -0
- rote_cli-0.10.0/src/rote/cli.py +3899 -0
- rote_cli-0.10.0/src/rote/cloud_auth.py +296 -0
- rote_cli-0.10.0/src/rote/cloud_graduate.py +628 -0
- rote_cli-0.10.0/src/rote/config.py +222 -0
- rote_cli-0.10.0/src/rote/deploy.py +206 -0
- rote_cli-0.10.0/src/rote/deploy_rote_cloud.py +182 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/__init__.py +4 -0
- rote_cli-0.10.0/src/rote/eval/baseline.py +655 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/empirical.py +51 -35
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/scorecard.py +66 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/graduator/__init__.py +165 -4
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/graduator/drivers/__init__.py +22 -0
- rote_cli-0.10.0/src/rote/graduator/drivers/_fs_tools.py +280 -0
- rote_cli-0.10.0/src/rote/graduator/drivers/anthropic_api.py +472 -0
- rote_cli-0.10.0/src/rote/graduator/drivers/claude.py +544 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/graduator/drivers/codex.py +13 -2
- rote_cli-0.10.0/src/rote/graduator/drivers/openai_api.py +431 -0
- rote_cli-0.10.0/src/rote/graduator/events.py +255 -0
- rote_cli-0.10.0/src/rote/init_wizard.py +168 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/ir.py +121 -0
- rote_cli-0.10.0/src/rote/mcp/__init__.py +52 -0
- rote_cli-0.10.0/src/rote/mcp/_runtime_helper.py +346 -0
- rote_cli-0.10.0/src/rote/mcp/auth.py +166 -0
- rote_cli-0.10.0/src/rote/mcp/registry.py +124 -0
- rote_cli-0.10.0/src/rote/mcp/release.py +317 -0
- rote_cli-0.10.0/src/rote/mcp/tokens.py +292 -0
- rote_cli-0.10.0/src/rote/probe.py +301 -0
- rote_cli-0.10.0/src/rote/runners/__init__.py +449 -0
- rote_cli-0.10.0/src/rote/runners/_node.py +87 -0
- rote_cli-0.10.0/src/rote/runners/cloudflare.py +172 -0
- rote_cli-0.10.0/src/rote/runners/dbos_ts.py +257 -0
- rote_cli-0.10.0/src/rote/runners/inngest.py +259 -0
- rote_cli-0.10.0/src/rote/runners/temporal.py +160 -0
- rote_cli-0.8.0/src/rote/cli.py +0 -1352
- rote_cli-0.8.0/src/rote/graduator/drivers/anthropic_api.py +0 -423
- rote_cli-0.8.0/src/rote/graduator/drivers/claude.py +0 -325
- {rote_cli-0.8.0 → rote_cli-0.10.0}/.gitignore +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/LICENSE +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/eval-estimates.md +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/llm-judge-extraction.md +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/estimate.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/pricing.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/priors.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/sidecar.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/eval/tokens.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/graduator/update.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/serve/__init__.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/serve/backends.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/serve/registry.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/serve/server.py +0 -0
- {rote_cli-0.8.0 → rote_cli-0.10.0}/src/rote/skill_source.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rote-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -32,11 +32,17 @@ Requires-Dist: dbos>=2.26; extra == 'dev'
|
|
|
32
32
|
Requires-Dist: fastmcp>=3.4.2; extra == 'dev'
|
|
33
33
|
Requires-Dist: httpx>=0.28; extra == 'dev'
|
|
34
34
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
35
|
+
Requires-Dist: openai>=2.45; extra == 'dev'
|
|
35
36
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
36
37
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
38
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
38
39
|
Requires-Dist: temporalio>=1.7; extra == 'dev'
|
|
39
40
|
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
41
|
+
Provides-Extra: mcp
|
|
42
|
+
Requires-Dist: fastmcp>=3.4.2; extra == 'mcp'
|
|
43
|
+
Requires-Dist: httpx>=0.28; extra == 'mcp'
|
|
44
|
+
Provides-Extra: openai-api
|
|
45
|
+
Requires-Dist: openai>=2.45; extra == 'openai-api'
|
|
40
46
|
Provides-Extra: serve
|
|
41
47
|
Requires-Dist: fastmcp>=3.4.2; extra == 'serve'
|
|
42
48
|
Requires-Dist: httpx>=0.28; extra == 'serve'
|
|
@@ -221,8 +227,8 @@ emitted code + a README on how to run, signal gates, and deploy).
|
|
|
221
227
|
|
|
222
228
|
### Other commands
|
|
223
229
|
|
|
224
|
-
- **`rote emit <pipeline.yaml>`** — run just the adapter step
|
|
225
|
-
existing IR (no LLM, no cost). The cheap inner loop while iterating on
|
|
230
|
+
- **`rote emit <pipeline.yaml> --out <dir>`** — run just the adapter step
|
|
231
|
+
on an existing IR (no LLM, no cost). The cheap inner loop while iterating on
|
|
226
232
|
adapters or IR shapes. Re-emitting is safe: a `.rote-manifest.json`
|
|
227
233
|
tracks what `rote` wrote, and files you've edited are left untouched
|
|
228
234
|
(the fresh version lands as `<name>.new`).
|
|
@@ -231,6 +237,61 @@ emitted code + a README on how to run, signal gates, and deploy).
|
|
|
231
237
|
`provenance.json` and re-derives only the nodes whose source sections
|
|
232
238
|
changed; unchanged nodes keep their ids (so in-flight durable workflows
|
|
233
239
|
aren't orphaned) and implemented stubs are kept. No change → no agent run.
|
|
240
|
+
- **`rote run <path>`** — one-off local execution of either side. A
|
|
241
|
+
skill directory runs as an agent via `claude -p` (your registered MCP
|
|
242
|
+
servers injected, read-only tool gate unless `--allow-writes`); an
|
|
243
|
+
emitted runtime directory — or a `graduate --out` directory — runs
|
|
244
|
+
the pipeline itself — all six runtimes (`python`/`dbos`/`temporal`
|
|
245
|
+
in-process or on a managed local dev server, `cloudflare` under
|
|
246
|
+
`wrangler dev`, `inngest` against a managed `inngest-cli dev`,
|
|
247
|
+
`dbos-ts` against your Postgres or a throwaway Docker one). HITL
|
|
248
|
+
gate payloads via `--signal name='{...}'` or an interactive prompt.
|
|
249
|
+
Runtimes that bundle a dev UI surface it: temporal runs print a live
|
|
250
|
+
Temporal Web UI URL and inngest runs print the dev-server dashboard,
|
|
251
|
+
both live for the duration of the run. Output JSON on stdout, status
|
|
252
|
+
on stderr, so it pipes.
|
|
253
|
+
- **`rote deploy <path>`** — push an emitted pipeline where it runs:
|
|
254
|
+
`cloudflare` wraps `npx wrangler deploy` (with `--dry-run`), `dbos` /
|
|
255
|
+
`dbos-ts` wrap `npx dbos-cloud app deploy` — the vendor CLI owns auth
|
|
256
|
+
and output; rote adds detection and preflights (including surfacing
|
|
257
|
+
*which* account your wrangler session belongs to before uploading).
|
|
258
|
+
Runtimes with no push model (temporal, inngest, python) print honest
|
|
259
|
+
hosting guidance with doc links instead of a fake action.
|
|
260
|
+
`--target rote-cloud` bundles a cloudflare-emitted app (esbuild via
|
|
261
|
+
npx) and uploads it to a hosted rote-cloud instance — with a stored
|
|
262
|
+
`rote login`, no flags or env vars needed (`--url`/`--token` and
|
|
263
|
+
`$ROTE_CLOUD_URL`/`$ROTE_CLOUD_TOKEN` still override).
|
|
264
|
+
- **`rote login`** — connect the CLI to a rote-cloud account via the
|
|
265
|
+
OAuth device flow: your browser opens with a one-time code pre-filled
|
|
266
|
+
(over SSH, `--device` prints the code + URL instead), you click
|
|
267
|
+
Approve, and the CLI stores a tenant API key at
|
|
268
|
+
`~/.local/share/rote/cloud.json` (mode 0600). Once logged in,
|
|
269
|
+
`rote graduate` runs **on rote cloud by default**: the skill bundle
|
|
270
|
+
syncs up (sha-diffed, so unchanged files don't re-upload), the
|
|
271
|
+
platform runs the graduation server-side, live progress streams back
|
|
272
|
+
through the same renderer as a local run, the result auto-deploys,
|
|
273
|
+
and the artifacts download into your `--out` directory in the exact
|
|
274
|
+
local layout. `--local` keeps the graduation on your machine (then
|
|
275
|
+
the cloudflare-emit + auto-deploy flow applies), `--no-deploy` or a
|
|
276
|
+
config opt-out (`runtime:` pinned to a local target, or
|
|
277
|
+
`deploy: none`) keeps everything local; `--cloud` forces the server
|
|
278
|
+
even where config says otherwise. Logged out, everything works
|
|
279
|
+
locally exactly as before. `rote whoami` shows the account (verified
|
|
280
|
+
live); `rote logout` revokes the key server-side and clears the store.
|
|
281
|
+
- **`rote init`** — one-time interactive onboarding: pick where
|
|
282
|
+
graduated pipelines run (rote cloud — with login offered inline — or
|
|
283
|
+
a local runtime, with a one-line pitch for each), which graduator
|
|
284
|
+
driver does the work (availability probed live), and optionally a
|
|
285
|
+
model. Answers are saved to `~/.config/rote/config.yaml`
|
|
286
|
+
(`--project` writes a `./rote.yaml` that overrides it per-repo) and
|
|
287
|
+
every later command reads them. It's the only interactive command
|
|
288
|
+
besides login — CI never hits a prompt.
|
|
289
|
+
- **`rote config`** — print every configurable default with its
|
|
290
|
+
effective value *and the layer that set it*. Resolution everywhere is
|
|
291
|
+
`flag > ROTE_* env (ROTE_RUNTIME, ROTE_DEPLOY, ROTE_AGENT,
|
|
292
|
+
ROTE_MODEL) > project rote.yaml > user config > built-in`. Config
|
|
293
|
+
files are strict: a typo'd key or value is a loud error, never a
|
|
294
|
+
silent fallback. `--json` for automation.
|
|
234
295
|
- **`rote eval <graduated>`** — render the before/after scorecard (wall
|
|
235
296
|
clock, cost across the current model lineup at live prices, and how
|
|
236
297
|
much of the run is still LLM-decided). `rote graduate` writes this to
|
|
@@ -266,9 +327,12 @@ way, prefer the more deterministic kind.
|
|
|
266
327
|
|
|
267
328
|
## Runtimes
|
|
268
329
|
|
|
269
|
-
Pick with `--runtime`; the same IR drives all of them.
|
|
270
|
-
emitted code references MCP — the
|
|
271
|
-
calls with direct vendor API calls.
|
|
330
|
+
Pick with `--runtime`; the same IR drives all of them. Under
|
|
331
|
+
`--backend api`, none of the emitted code references MCP — the
|
|
332
|
+
crystallization step replaces tool calls with direct vendor API calls.
|
|
333
|
+
Under the default `--backend mcp`, tool-using nodes emit a working MCP
|
|
334
|
+
client call (with durable park-on-auth on every MCP-capable runtime);
|
|
335
|
+
see [`docs/mcp-client.md`](docs/mcp-client.md).
|
|
272
336
|
|
|
273
337
|
| Runtime | `--runtime` | Language | Shape | Notes |
|
|
274
338
|
| --- | --- | --- | --- | --- |
|
|
@@ -348,7 +412,7 @@ Trusted Publishing ([docs/releasing.md](docs/releasing.md)).
|
|
|
348
412
|
|
|
349
413
|
```
|
|
350
414
|
rote/
|
|
351
|
-
├── docs/ agent-runtime · mcp-trigger · releasing
|
|
415
|
+
├── docs/ agent-runtime · mcp-client · mcp-trigger · releasing
|
|
352
416
|
├── skills/rote-graduate/ the graduator agent (SKILL.md + 4 reference files)
|
|
353
417
|
├── src/rote/
|
|
354
418
|
│ ├── cli.py rote graduate / emit / eval / serve
|
|
@@ -367,9 +431,15 @@ rote/
|
|
|
367
431
|
|
|
368
432
|
## Documentation
|
|
369
433
|
|
|
434
|
+
- [`AGENTS.md`](AGENTS.md) — operating manual for a coding agent *driving*
|
|
435
|
+
`rote` as an installed tool (invocation contract, the slow/costs-money
|
|
436
|
+
`graduate` flow, auth, failure recovery, the stub-filling job, `--json`)
|
|
370
437
|
- [`docs/agent-runtime.md`](docs/agent-runtime.md) — design record for the
|
|
371
438
|
driver abstraction (the `claude -p` env gotcha; the non-use of
|
|
372
439
|
`claude-agent-sdk`)
|
|
440
|
+
- [`docs/mcp-client.md`](docs/mcp-client.md) — the OAuth MCP client
|
|
441
|
+
emitted code uses under `--backend mcp`: endpoint/credential
|
|
442
|
+
resolution and durable park-on-auth across every MCP-capable runtime
|
|
373
443
|
- [`docs/mcp-trigger.md`](docs/mcp-trigger.md) — `rote register` +
|
|
374
444
|
`rote serve`: graduated pipelines as MCP tools (FastMCP 3.x)
|
|
375
445
|
- [`docs/releasing.md`](docs/releasing.md) — tag-driven PyPI Trusted
|
|
@@ -175,8 +175,8 @@ emitted code + a README on how to run, signal gates, and deploy).
|
|
|
175
175
|
|
|
176
176
|
### Other commands
|
|
177
177
|
|
|
178
|
-
- **`rote emit <pipeline.yaml>`** — run just the adapter step
|
|
179
|
-
existing IR (no LLM, no cost). The cheap inner loop while iterating on
|
|
178
|
+
- **`rote emit <pipeline.yaml> --out <dir>`** — run just the adapter step
|
|
179
|
+
on an existing IR (no LLM, no cost). The cheap inner loop while iterating on
|
|
180
180
|
adapters or IR shapes. Re-emitting is safe: a `.rote-manifest.json`
|
|
181
181
|
tracks what `rote` wrote, and files you've edited are left untouched
|
|
182
182
|
(the fresh version lands as `<name>.new`).
|
|
@@ -185,6 +185,61 @@ emitted code + a README on how to run, signal gates, and deploy).
|
|
|
185
185
|
`provenance.json` and re-derives only the nodes whose source sections
|
|
186
186
|
changed; unchanged nodes keep their ids (so in-flight durable workflows
|
|
187
187
|
aren't orphaned) and implemented stubs are kept. No change → no agent run.
|
|
188
|
+
- **`rote run <path>`** — one-off local execution of either side. A
|
|
189
|
+
skill directory runs as an agent via `claude -p` (your registered MCP
|
|
190
|
+
servers injected, read-only tool gate unless `--allow-writes`); an
|
|
191
|
+
emitted runtime directory — or a `graduate --out` directory — runs
|
|
192
|
+
the pipeline itself — all six runtimes (`python`/`dbos`/`temporal`
|
|
193
|
+
in-process or on a managed local dev server, `cloudflare` under
|
|
194
|
+
`wrangler dev`, `inngest` against a managed `inngest-cli dev`,
|
|
195
|
+
`dbos-ts` against your Postgres or a throwaway Docker one). HITL
|
|
196
|
+
gate payloads via `--signal name='{...}'` or an interactive prompt.
|
|
197
|
+
Runtimes that bundle a dev UI surface it: temporal runs print a live
|
|
198
|
+
Temporal Web UI URL and inngest runs print the dev-server dashboard,
|
|
199
|
+
both live for the duration of the run. Output JSON on stdout, status
|
|
200
|
+
on stderr, so it pipes.
|
|
201
|
+
- **`rote deploy <path>`** — push an emitted pipeline where it runs:
|
|
202
|
+
`cloudflare` wraps `npx wrangler deploy` (with `--dry-run`), `dbos` /
|
|
203
|
+
`dbos-ts` wrap `npx dbos-cloud app deploy` — the vendor CLI owns auth
|
|
204
|
+
and output; rote adds detection and preflights (including surfacing
|
|
205
|
+
*which* account your wrangler session belongs to before uploading).
|
|
206
|
+
Runtimes with no push model (temporal, inngest, python) print honest
|
|
207
|
+
hosting guidance with doc links instead of a fake action.
|
|
208
|
+
`--target rote-cloud` bundles a cloudflare-emitted app (esbuild via
|
|
209
|
+
npx) and uploads it to a hosted rote-cloud instance — with a stored
|
|
210
|
+
`rote login`, no flags or env vars needed (`--url`/`--token` and
|
|
211
|
+
`$ROTE_CLOUD_URL`/`$ROTE_CLOUD_TOKEN` still override).
|
|
212
|
+
- **`rote login`** — connect the CLI to a rote-cloud account via the
|
|
213
|
+
OAuth device flow: your browser opens with a one-time code pre-filled
|
|
214
|
+
(over SSH, `--device` prints the code + URL instead), you click
|
|
215
|
+
Approve, and the CLI stores a tenant API key at
|
|
216
|
+
`~/.local/share/rote/cloud.json` (mode 0600). Once logged in,
|
|
217
|
+
`rote graduate` runs **on rote cloud by default**: the skill bundle
|
|
218
|
+
syncs up (sha-diffed, so unchanged files don't re-upload), the
|
|
219
|
+
platform runs the graduation server-side, live progress streams back
|
|
220
|
+
through the same renderer as a local run, the result auto-deploys,
|
|
221
|
+
and the artifacts download into your `--out` directory in the exact
|
|
222
|
+
local layout. `--local` keeps the graduation on your machine (then
|
|
223
|
+
the cloudflare-emit + auto-deploy flow applies), `--no-deploy` or a
|
|
224
|
+
config opt-out (`runtime:` pinned to a local target, or
|
|
225
|
+
`deploy: none`) keeps everything local; `--cloud` forces the server
|
|
226
|
+
even where config says otherwise. Logged out, everything works
|
|
227
|
+
locally exactly as before. `rote whoami` shows the account (verified
|
|
228
|
+
live); `rote logout` revokes the key server-side and clears the store.
|
|
229
|
+
- **`rote init`** — one-time interactive onboarding: pick where
|
|
230
|
+
graduated pipelines run (rote cloud — with login offered inline — or
|
|
231
|
+
a local runtime, with a one-line pitch for each), which graduator
|
|
232
|
+
driver does the work (availability probed live), and optionally a
|
|
233
|
+
model. Answers are saved to `~/.config/rote/config.yaml`
|
|
234
|
+
(`--project` writes a `./rote.yaml` that overrides it per-repo) and
|
|
235
|
+
every later command reads them. It's the only interactive command
|
|
236
|
+
besides login — CI never hits a prompt.
|
|
237
|
+
- **`rote config`** — print every configurable default with its
|
|
238
|
+
effective value *and the layer that set it*. Resolution everywhere is
|
|
239
|
+
`flag > ROTE_* env (ROTE_RUNTIME, ROTE_DEPLOY, ROTE_AGENT,
|
|
240
|
+
ROTE_MODEL) > project rote.yaml > user config > built-in`. Config
|
|
241
|
+
files are strict: a typo'd key or value is a loud error, never a
|
|
242
|
+
silent fallback. `--json` for automation.
|
|
188
243
|
- **`rote eval <graduated>`** — render the before/after scorecard (wall
|
|
189
244
|
clock, cost across the current model lineup at live prices, and how
|
|
190
245
|
much of the run is still LLM-decided). `rote graduate` writes this to
|
|
@@ -220,9 +275,12 @@ way, prefer the more deterministic kind.
|
|
|
220
275
|
|
|
221
276
|
## Runtimes
|
|
222
277
|
|
|
223
|
-
Pick with `--runtime`; the same IR drives all of them.
|
|
224
|
-
emitted code references MCP — the
|
|
225
|
-
calls with direct vendor API calls.
|
|
278
|
+
Pick with `--runtime`; the same IR drives all of them. Under
|
|
279
|
+
`--backend api`, none of the emitted code references MCP — the
|
|
280
|
+
crystallization step replaces tool calls with direct vendor API calls.
|
|
281
|
+
Under the default `--backend mcp`, tool-using nodes emit a working MCP
|
|
282
|
+
client call (with durable park-on-auth on every MCP-capable runtime);
|
|
283
|
+
see [`docs/mcp-client.md`](docs/mcp-client.md).
|
|
226
284
|
|
|
227
285
|
| Runtime | `--runtime` | Language | Shape | Notes |
|
|
228
286
|
| --- | --- | --- | --- | --- |
|
|
@@ -302,7 +360,7 @@ Trusted Publishing ([docs/releasing.md](docs/releasing.md)).
|
|
|
302
360
|
|
|
303
361
|
```
|
|
304
362
|
rote/
|
|
305
|
-
├── docs/ agent-runtime · mcp-trigger · releasing
|
|
363
|
+
├── docs/ agent-runtime · mcp-client · mcp-trigger · releasing
|
|
306
364
|
├── skills/rote-graduate/ the graduator agent (SKILL.md + 4 reference files)
|
|
307
365
|
├── src/rote/
|
|
308
366
|
│ ├── cli.py rote graduate / emit / eval / serve
|
|
@@ -321,9 +379,15 @@ rote/
|
|
|
321
379
|
|
|
322
380
|
## Documentation
|
|
323
381
|
|
|
382
|
+
- [`AGENTS.md`](AGENTS.md) — operating manual for a coding agent *driving*
|
|
383
|
+
`rote` as an installed tool (invocation contract, the slow/costs-money
|
|
384
|
+
`graduate` flow, auth, failure recovery, the stub-filling job, `--json`)
|
|
324
385
|
- [`docs/agent-runtime.md`](docs/agent-runtime.md) — design record for the
|
|
325
386
|
driver abstraction (the `claude -p` env gotcha; the non-use of
|
|
326
387
|
`claude-agent-sdk`)
|
|
388
|
+
- [`docs/mcp-client.md`](docs/mcp-client.md) — the OAuth MCP client
|
|
389
|
+
emitted code uses under `--backend mcp`: endpoint/credential
|
|
390
|
+
resolution and durable park-on-auth across every MCP-capable runtime
|
|
327
391
|
- [`docs/mcp-trigger.md`](docs/mcp-trigger.md) — `rote register` +
|
|
328
392
|
`rote serve`: graduated pipelines as MCP tools (FastMCP 3.x)
|
|
329
393
|
- [`docs/releasing.md`](docs/releasing.md) — tag-driven PyPI Trusted
|
|
@@ -50,6 +50,9 @@ temporal = [
|
|
|
50
50
|
api = [
|
|
51
51
|
"anthropic>=0.89",
|
|
52
52
|
]
|
|
53
|
+
openai-api = [
|
|
54
|
+
"openai>=2.45",
|
|
55
|
+
]
|
|
53
56
|
dbos = [
|
|
54
57
|
"dbos>=2.26",
|
|
55
58
|
]
|
|
@@ -57,6 +60,10 @@ serve = [
|
|
|
57
60
|
"fastmcp>=3.4.2",
|
|
58
61
|
"httpx>=0.28",
|
|
59
62
|
]
|
|
63
|
+
mcp = [
|
|
64
|
+
"fastmcp>=3.4.2",
|
|
65
|
+
"httpx>=0.28",
|
|
66
|
+
]
|
|
60
67
|
dev = [
|
|
61
68
|
"pytest>=8.0",
|
|
62
69
|
"pytest-asyncio>=0.23",
|
|
@@ -65,6 +72,7 @@ dev = [
|
|
|
65
72
|
"types-pyyaml",
|
|
66
73
|
"temporalio>=1.7",
|
|
67
74
|
"anthropic>=0.89",
|
|
75
|
+
"openai>=2.45",
|
|
68
76
|
"dbos>=2.26",
|
|
69
77
|
"fastmcp>=3.4.2",
|
|
70
78
|
"httpx>=0.28",
|
|
@@ -46,6 +46,19 @@ re-deriving a known-fixed procedure at runtime is waste.
|
|
|
46
46
|
|
|
47
47
|
## Pipeline
|
|
48
48
|
|
|
49
|
+
**Progress markers.** At the START of each Phase N, rewrite
|
|
50
|
+
`progress.ndjson` in the work directory so it holds one JSON line per
|
|
51
|
+
phase entered so far, in order:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
{"phase": 1, "name": "Intake"}
|
|
55
|
+
{"phase": 2, "name": "Node Classification"}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Rewrite the whole file each time (append the new phase's line to the
|
|
59
|
+
ones already there). This file is machine-read for live progress
|
|
60
|
+
display; it is not a deliverable and never affects the IR.
|
|
61
|
+
|
|
49
62
|
### Phase 1: Intake
|
|
50
63
|
|
|
51
64
|
Read the target directory exhaustively:
|
|
@@ -86,7 +99,13 @@ justification per row.
|
|
|
86
99
|
|
|
87
100
|
### Phase 3: Codification Scan
|
|
88
101
|
|
|
89
|
-
Read `references/crystallization-heuristics.md`
|
|
102
|
+
Read `references/crystallization-heuristics.md` and
|
|
103
|
+
`references/implementation.md` before starting this phase. The second
|
|
104
|
+
file governs how far to take each extracted module: contract-documented
|
|
105
|
+
stubs by default, **working implementations plus golden-fixture tests**
|
|
106
|
+
when ground truth is available (a `probe-context/` directory of observed
|
|
107
|
+
MCP traffic in your work dir, and/or web research tools to verify
|
|
108
|
+
current vendor API shapes).
|
|
90
109
|
|
|
91
110
|
For each node classified as `pure_function` or `external_call`, surface every
|
|
92
111
|
codification opportunity:
|
|
@@ -174,6 +193,12 @@ Checklist before declaring Phase 5 done:
|
|
|
174
193
|
|
|
175
194
|
- Every `pure_function` / `external_call` node has an `impl:` pointing
|
|
176
195
|
at a real file you created in Phase 3.
|
|
196
|
+
- Every step that calls an MCP tool in the source skill carries the
|
|
197
|
+
matching `mcp:` binding (see ir-schema.md — and cross-check against
|
|
198
|
+
`probe-context/observed-tools.json` when it exists: observed traffic
|
|
199
|
+
with no binding is a graduation bug).
|
|
200
|
+
- If you wrote working implementations, `tests/test_extracted.py`
|
|
201
|
+
exists, is deterministic and offline, and you believe it passes.
|
|
177
202
|
- Every `llm_judge` node has a `signature:` pointing at a real file
|
|
178
203
|
you created in Phase 4.
|
|
179
204
|
- Every `agent_loop` node has `tools:` listed.
|
{rote_cli-0.8.0 → rote_cli-0.10.0}/skills/rote-graduate/references/crystallization-heuristics.md
RENAMED
|
@@ -110,6 +110,17 @@ code makes skipping impossible.
|
|
|
110
110
|
"always", "must", "never skip", "do not skip", "be sure to". Audit
|
|
111
111
|
every hit.
|
|
112
112
|
|
|
113
|
+
**Calibration — err toward marking.** On real production skills MOST
|
|
114
|
+
deterministic nodes end up `mandatory: true`, not a handful: every
|
|
115
|
+
fixed threshold, every filter/exclusion rule, every data pull the
|
|
116
|
+
output cannot be correct without, every fixed template render. (The
|
|
117
|
+
first agent run on the ops-report example marked 1 of 13 nodes when
|
|
118
|
+
9 of 10 was right — a daily report is *made of* rules that must all
|
|
119
|
+
fire.) Ask per node: "if the agent version silently skipped this
|
|
120
|
+
step, would the output be wrong or dangerous?" If yes, mark it.
|
|
121
|
+
Reserve unmarked for genuinely optional enrichment — a nice-to-have
|
|
122
|
+
field, a best-effort lookup with a fallback.
|
|
123
|
+
|
|
113
124
|
---
|
|
114
125
|
|
|
115
126
|
### Pattern 4 — Fixed string templates for reports and outputs
|
|
@@ -200,6 +211,29 @@ but they're actually hard thresholds.
|
|
|
200
211
|
**How to detect:** look for numeric comparisons in rubrics ("below X",
|
|
201
212
|
"above Y", "at least Z"). Every one is a potential pre-filter.
|
|
202
213
|
|
|
214
|
+
### Pattern 8 — Independent data pulls hiding in sequential prose
|
|
215
|
+
|
|
216
|
+
**What it looks like:** the skill lists data-gathering steps in
|
|
217
|
+
sequence ("First pull the Slack channel. Then run the Gmail
|
|
218
|
+
searches.") but no step consumes another's output — the ordering
|
|
219
|
+
exists only because an agent executes one tool call at a time.
|
|
220
|
+
|
|
221
|
+
**What to do:** give the independent pulls no connecting edges so
|
|
222
|
+
they land in the same execution wave, and list every independent
|
|
223
|
+
entry point in `entry_nodes` (plural!). Adapters run same-wave nodes
|
|
224
|
+
concurrently — the graduated pipeline should be *faster* than the
|
|
225
|
+
agent, and parallel data pulls are usually the single biggest
|
|
226
|
+
wall-clock win available.
|
|
227
|
+
|
|
228
|
+
**How to detect:** for each pair of adjacent data-pull steps, ask
|
|
229
|
+
"does step B's input mention anything step A produced?" If not, they
|
|
230
|
+
are parallel regardless of the prose order. The deal-monitor example
|
|
231
|
+
is the canonical case: the Slack intake pull and the fixed Gmail
|
|
232
|
+
searches share nothing and must be two entry nodes — the first agent
|
|
233
|
+
run serialized them into one entry and left the speedup on the table.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
203
237
|
## When NOT to crystallize
|
|
204
238
|
|
|
205
239
|
The hunt for crystallization should be aggressive, but not blind. Leave
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Writing Extracted Implementations
|
|
2
|
+
|
|
3
|
+
Phase 3 produces `extracted/*.py` modules. This file governs **how much
|
|
4
|
+
of each module you implement** and how to prove what you wrote is
|
|
5
|
+
right. The bar depends on what evidence you have.
|
|
6
|
+
|
|
7
|
+
## The two regimes
|
|
8
|
+
|
|
9
|
+
### Regime 1 — No ground truth (default)
|
|
10
|
+
|
|
11
|
+
No `probe-context/` directory exists in your work dir, and you have no
|
|
12
|
+
web research tools. Emit **contract-documented stubs**: the function
|
|
13
|
+
signature, a docstring stating the input/output contract as precisely
|
|
14
|
+
as the skill text allows, and a `raise NotImplementedError(...)` body.
|
|
15
|
+
Do NOT write vendor API calls from memory — SDK and endpoint shapes
|
|
16
|
+
drift faster than any model's training data, and a plausible-but-wrong
|
|
17
|
+
implementation is worse than an honest stub because it fails at
|
|
18
|
+
runtime instead of at review.
|
|
19
|
+
|
|
20
|
+
### Regime 2 — Ground truth available (implement for real)
|
|
21
|
+
|
|
22
|
+
`probe-context/` exists (observed MCP traffic + inferred schemas from
|
|
23
|
+
an instrumented run of this exact skill), and/or you have WebSearch /
|
|
24
|
+
WebFetch. Now you are expected to write **working implementations**:
|
|
25
|
+
|
|
26
|
+
1. **Contract from observation.** The observed input payload keys are
|
|
27
|
+
your function parameters; the observed result shape is your return
|
|
28
|
+
contract. If the skill prose disagrees with the observed traffic,
|
|
29
|
+
trust the traffic and note the discrepancy in the graduation report.
|
|
30
|
+
2. **Endpoint from current docs, never memory.** Before writing a
|
|
31
|
+
vendor call, use WebSearch/WebFetch to confirm the current endpoint,
|
|
32
|
+
auth scheme, request/response shape, and official SDK name. Cite the
|
|
33
|
+
doc URL in the module docstring. If you cannot verify a shape, fall
|
|
34
|
+
back to Regime 1 for that function and say why in the report.
|
|
35
|
+
3. **Credentials via environment variables only.** One variable per
|
|
36
|
+
vendor, named `<VENDOR>_API_KEY` (or the vendor's own documented
|
|
37
|
+
convention, e.g. `GOOGLE_APPLICATION_CREDENTIALS`). Read it at call
|
|
38
|
+
time, and raise a clear `RuntimeError` naming the variable when it
|
|
39
|
+
is missing. NEVER hardcode a credential, NEVER invent a
|
|
40
|
+
plausible-looking value, NEVER default to a dummy that would send a
|
|
41
|
+
request with fake auth.
|
|
42
|
+
4. **Minimal dependencies.** Prefer the vendor's official Python SDK
|
|
43
|
+
when one exists and is current; otherwise plain `httpx`. Every
|
|
44
|
+
third-party dependency you introduce must be listed in the module
|
|
45
|
+
docstring so the adapter's requirements emission can pick it up.
|
|
46
|
+
5. **Honest error surfaces.** No silent fallbacks, no bare excepts, no
|
|
47
|
+
returning empty results on failure. A 4xx/5xx should raise with the
|
|
48
|
+
response body attached — errors are how the durable runtime knows
|
|
49
|
+
to retry or park.
|
|
50
|
+
|
|
51
|
+
## Tests are part of the deliverable
|
|
52
|
+
|
|
53
|
+
When you implement for real (Regime 2), also write
|
|
54
|
+
`tests/test_extracted.py` in your work dir:
|
|
55
|
+
|
|
56
|
+
- **Golden-fixture tests from observed payloads.** Every parse /
|
|
57
|
+
transform / filter function gets called with a real observed payload
|
|
58
|
+
(copy it from `probe-context/observed-tools.json` into the test as a
|
|
59
|
+
fixture literal) and asserted against the real result. These are the
|
|
60
|
+
highest-value tests you can write: they prove the implementation
|
|
61
|
+
handles what production actually sends.
|
|
62
|
+
- **Network functions get a transport-stubbed test.** Monkeypatch the
|
|
63
|
+
HTTP layer (or inject a fake client) and assert the function builds
|
|
64
|
+
the documented request and parses the documented response — using
|
|
65
|
+
the observed payloads as the canned response body. **No test may
|
|
66
|
+
touch the network**, require credentials, or be nondeterministic:
|
|
67
|
+
they must pass with plain `pytest` in an offline environment.
|
|
68
|
+
- **Constants get pinned.** Every constant lifted in Phase 3 (batch
|
|
69
|
+
sizes, thresholds, day windows) gets one assertion, so a silent edit
|
|
70
|
+
fails a test instead of shipping.
|
|
71
|
+
|
|
72
|
+
Structure the file so `pytest tests/` passes from the work dir root —
|
|
73
|
+
imports as `from extracted.<module> import <fn>` with a conftest or
|
|
74
|
+
sys.path line if needed. If a test would need something you can't
|
|
75
|
+
provide deterministically, don't write a flaky approximation; note the
|
|
76
|
+
gap in the graduation report instead.
|
|
77
|
+
|
|
78
|
+
## What NOT to do, in any regime
|
|
79
|
+
|
|
80
|
+
- Do not write to `signatures/` from this file's rules — judge modules
|
|
81
|
+
are governed by `llm-judge-extraction.md`.
|
|
82
|
+
- Do not call MCP at runtime from extracted code. The `mcp:` binding on
|
|
83
|
+
the node (see `ir-schema.md`) is how the MCP backend wires itself;
|
|
84
|
+
your `extracted/` implementation is the *direct-API* backend the
|
|
85
|
+
user selects with `--backend api`. Keep both true to the same
|
|
86
|
+
contract.
|
|
87
|
+
- Do not invent data the observation doesn't show. An inferred schema
|
|
88
|
+
from one sample marks fields it saw; absence of a field is not
|
|
89
|
+
license to add speculative ones.
|
|
90
|
+
- Do not soften a failing contract to make a test pass. The observed
|
|
91
|
+
payload is the spec; if your implementation can't reproduce it,
|
|
92
|
+
the implementation is wrong.
|
|
93
|
+
|
|
94
|
+
## BDR worked example
|
|
95
|
+
|
|
96
|
+
`enrich_contact_batch` under Regime 2, with an observed
|
|
97
|
+
`zoominfo_enrich_contacts` call in probe-context:
|
|
98
|
+
|
|
99
|
+
- Function takes `contacts: list[dict]` (the observed input key),
|
|
100
|
+
chunks by the lifted `BATCH_SIZE = 10`, calls the documented
|
|
101
|
+
ZoomInfo enrich endpoint with `ZOOMINFO_API_KEY` from the env, and
|
|
102
|
+
returns the observed response shape (`{"contacts": [...]}`)
|
|
103
|
+
normalized per the inferred schema.
|
|
104
|
+
- `tests/test_extracted.py` asserts: chunking math at the boundary
|
|
105
|
+
(10, 11, 20 contacts), request shape against a stubbed transport
|
|
106
|
+
using the observed input, response parsing against the observed
|
|
107
|
+
result payload verbatim, and `BATCH_SIZE == 10`.
|
|
108
|
+
- Module docstring cites the ZoomInfo enrich doc URL fetched during
|
|
109
|
+
implementation and lists `httpx` as the dependency.
|
|
@@ -99,8 +99,51 @@ kind. The validator enforces kind-specific requirements.
|
|
|
99
99
|
strategy: persistent
|
|
100
100
|
ttl: 30d
|
|
101
101
|
fan_out: false # optional, if true node invoked per input element
|
|
102
|
+
mcp: # external_call only — see "MCP bindings" below
|
|
103
|
+
server: hubspot # logical MCP server name (identifier)
|
|
104
|
+
tool: hubspot_batch_upsert # exact MCP tool name the skill calls
|
|
102
105
|
```
|
|
103
106
|
|
|
107
|
+
### MCP bindings (`mcp:`) — REQUIRED when the source step is an MCP tool call
|
|
108
|
+
|
|
109
|
+
If the skill step you are graduating calls an MCP tool — and in
|
|
110
|
+
production skills, almost every data pull and vendor write does — the
|
|
111
|
+
`external_call` node **must** carry an `mcp:` binding recording the
|
|
112
|
+
logical server name and the exact tool name:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
- id: fetch_intake_messages
|
|
116
|
+
kind: external_call
|
|
117
|
+
impl: extracted/slack.py:fetch_intake_messages
|
|
118
|
+
mcp:
|
|
119
|
+
server: slack # the MCP server the skill's tool lives on
|
|
120
|
+
tool: slack_read_channel # the tool name, exactly as the skill calls it
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This is not documentation — the binding is load-bearing:
|
|
124
|
+
|
|
125
|
+
- With `--backend mcp` (the default), adapters emit a **working call to
|
|
126
|
+
that tool** instead of a NotImplementedError stub; without the binding
|
|
127
|
+
the user has to hand-write the integration you could have wired.
|
|
128
|
+
- The pipeline's required-MCP-servers manifest, preflight auth
|
|
129
|
+
recommendations, and durable park-on-auth all derive from bindings —
|
|
130
|
+
an unbound node is invisible to all three.
|
|
131
|
+
- `rote graduate` cross-checks bindings against traffic observed in a
|
|
132
|
+
baseline run; a tool the skill demonstrably calls with no binding is
|
|
133
|
+
reported as a missed requirement (i.e., a graduation bug).
|
|
134
|
+
|
|
135
|
+
Rules:
|
|
136
|
+
|
|
137
|
+
- One binding per node — the server + tool pair the step actually uses.
|
|
138
|
+
If a step uses two tools, it's two nodes (or an `agent_loop`).
|
|
139
|
+
- `server` is the *logical* name (identifier: letters/digits/underscore);
|
|
140
|
+
it resolves to an endpoint at runtime via the rote registry — do not
|
|
141
|
+
put URLs in the binding unless the skill pins an explicit endpoint.
|
|
142
|
+
- Keep `impl:` too: the binding and the extracted stub are the two
|
|
143
|
+
backends (`mcp` vs `api`) of the same node.
|
|
144
|
+
- Steps that are *not* MCP tool calls (plain HTTP the skill documents,
|
|
145
|
+
browser automation, file I/O) take no binding — never invent one.
|
|
146
|
+
|
|
104
147
|
### Data-flow bindings (`inputs:`)
|
|
105
148
|
|
|
106
149
|
`input:` documents *types*; `inputs:` binds *where the values come
|
|
@@ -66,10 +66,18 @@ deterministic. If the agent decides which tool to call and what to pass
|
|
|
66
66
|
each iteration, it's an `agent_loop`.
|
|
67
67
|
|
|
68
68
|
**MCP → deterministic API:** this is the kind that most often originates
|
|
69
|
-
as an MCP tool call in the source skill.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
as an MCP tool call in the source skill. Graduating it has TWO required
|
|
70
|
+
outputs, not one:
|
|
71
|
+
|
|
72
|
+
1. An **`mcp:` binding on the node** — `server` + `tool`, exactly as the
|
|
73
|
+
skill calls it (see "MCP bindings" in ir-schema.md). This is
|
|
74
|
+
load-bearing: the default backend emits a working call to that tool
|
|
75
|
+
from the binding, and requirements/auth features derive from it.
|
|
76
|
+
Omitting it on a step that calls an MCP tool is a graduation bug.
|
|
77
|
+
2. The underlying vendor API the tool wraps (e.g.,
|
|
78
|
+
`POST /crm/v3/objects/contacts/batch/upsert`) recorded in the
|
|
79
|
+
`impl`'s docstring, so the trace from skill → MCP tool → REST
|
|
80
|
+
endpoint is visible in the code.
|
|
73
81
|
|
|
74
82
|
---
|
|
75
83
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``python -m rote`` — the same CLI as the ``rote`` console script.
|
|
2
|
+
|
|
3
|
+
Exists so generated configs can invoke rote through a known
|
|
4
|
+
interpreter (``sys.executable -m rote``) instead of guessing at PATH —
|
|
5
|
+
the eval harness's ``headersHelper`` wiring depends on this.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from rote.cli import main
|
|
11
|
+
|
|
12
|
+
sys.exit(main())
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Shared introspection helpers for emitted DBOS apps.
|
|
2
|
+
|
|
3
|
+
Used anywhere rote needs to talk to an emitted app's system database
|
|
4
|
+
from outside the app process: the eval harness (gate signaling) and the
|
|
5
|
+
MCP auth-release path (``rote mcp login`` waking parked workflows).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_dbos_config(app_dir: Path) -> dict[str, object]:
|
|
18
|
+
config = yaml.safe_load((app_dir / "dbos-config.yaml").read_text(encoding="utf-8"))
|
|
19
|
+
return config if isinstance(config, dict) else {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dbos_system_database_url(app_dir: Path) -> str:
|
|
23
|
+
"""The system database URL an emitted DBOS *Python* app will use.
|
|
24
|
+
|
|
25
|
+
Must mirror the emitted main.py's resolution order exactly —
|
|
26
|
+
``DBOS_SYSTEM_DATABASE_URL`` env override first, then the default
|
|
27
|
+
SQLite file in the app dir — or messages get delivered to a
|
|
28
|
+
database the app isn't watching.
|
|
29
|
+
"""
|
|
30
|
+
override = os.environ.get("DBOS_SYSTEM_DATABASE_URL")
|
|
31
|
+
if override:
|
|
32
|
+
return override
|
|
33
|
+
config = _load_dbos_config(app_dir)
|
|
34
|
+
name = str(config.get("name", "pipeline"))
|
|
35
|
+
return f"sqlite:///{(app_dir / f'{name}.dbos.sqlite').resolve()}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dbos_ts_system_database_url(app_dir: Path) -> str:
|
|
39
|
+
"""The system database URL an emitted DBOS *TypeScript* app will use.
|
|
40
|
+
|
|
41
|
+
Mirrors the TS SDK's resolution (verified against dbos-transact-ts
|
|
42
|
+
``src/config.ts``, SDK 4.23.x): the emitted main.ts passes
|
|
43
|
+
``process.env.DBOS_SYSTEM_DATABASE_URL`` to ``DBOS.setConfig``; a
|
|
44
|
+
``system_database_url`` key in dbos-config.yaml (with ``${VAR}``
|
|
45
|
+
substitution — unset vars become empty, exactly like the SDK) wins
|
|
46
|
+
next; otherwise the SDK default: Postgres at ``PGHOST``/``PGPORT``
|
|
47
|
+
with ``PGUSER``/``PGPASSWORD`` (falling back to
|
|
48
|
+
``postgres:dbos@localhost:5432``) and database ``<name>_dbos_sys``.
|
|
49
|
+
The TS SDK is Postgres-only — there is no SQLite branch.
|
|
50
|
+
"""
|
|
51
|
+
override = os.environ.get("DBOS_SYSTEM_DATABASE_URL")
|
|
52
|
+
if override:
|
|
53
|
+
return override
|
|
54
|
+
config = _load_dbos_config(app_dir)
|
|
55
|
+
configured = config.get("system_database_url")
|
|
56
|
+
if isinstance(configured, str) and configured:
|
|
57
|
+
expanded = re.sub(r"\$\{(\w+)\}", lambda m: os.environ.get(m.group(1), ""), configured)
|
|
58
|
+
if expanded:
|
|
59
|
+
return expanded
|
|
60
|
+
name = re.sub(r"[^a-zA-Z0-9_]", "_", str(config.get("name", "pipeline")))
|
|
61
|
+
host = os.environ.get("PGHOST", "localhost")
|
|
62
|
+
port = os.environ.get("PGPORT", "5432")
|
|
63
|
+
user = os.environ.get("PGUSER", "postgres")
|
|
64
|
+
password = os.environ.get("PGPASSWORD", "dbos")
|
|
65
|
+
return f"postgresql://{user}:{password}@{host}:{port}/{name}_dbos_sys"
|