diff --git a/skills/rig/references/claude-workflow-conversion.md b/skills/rig/references/claude-workflow-conversion.md index ad2fac9..4b2f75e 100644 --- a/skills/rig/references/claude-workflow-conversion.md +++ b/skills/rig/references/claude-workflow-conversion.md @@ -40,6 +40,27 @@ The launcher runs every program — including one whose root export is an `agent inside a workflow run, so a partially ported script can call `phase()` and `log()` at top level before the orchestration itself moves into `workflow({ body })`. +For flat scripts that use `call`, `pipeline`, or `parallel` at the top level +(the Claude dynamic workflow style), import those from `"rig/globals"`: + +```ts +import { call, parallel, pipeline } from "rig/globals"; +import { phase, log, workflow, s } from "rig"; + +// Flat Claude-style code: runs inside the launcher's workflow context +phase("Find"); +const result = await call.json("List issues.", s.object({ issues: s.array(s.string) })); +log(`found ${result?.issues.length ?? 0} issues`); + +export default workflow({ meta: { name: "flat-port", description: "direct port" }, + body: async () => result }); +``` + +The `rig/globals` proxies route through whatever workflow run is active. +They throw if called outside a run. Import from `"rig/globals"` only in +top-level launcher programs ported from flat scripts; prefer explicit +`body({ call })` destructuring inside `workflow()` bodies. + ## Schema conversion Dynamic workflows pass OpenAI-strict JSON Schema literals. rig schemas are @@ -159,6 +180,7 @@ workflow patterns — use them as starting points when converting a script: | Sample | Demonstrates | | --- | --- | +| [340-flat-workflow-port.md](../samples/340-flat-workflow-port.md) | Flat/top-level script port using `"rig/globals"` ambient `call`/`pipeline` — minimal-change first step when porting a Claude flat workflow | | [310-workflow-audit-verify.md](../samples/310-workflow-audit-verify.md) | `args`→`input`, `parallel`, `pipeline`, `phase`, `call.json` — mirrors the canonical find-and-verify pattern | | [320-budget-aware-crawler.md](../samples/320-budget-aware-crawler.md) | `log`, `budget.remaining()`, `until` convergence loop | | [330-nested-workflow-composition.md](../samples/330-nested-workflow-composition.md) | `call.workflow` (rig equivalent of `workflow(ref, args)`) sharing the parent's limiter and budget | diff --git a/skills/rig/samples/340-flat-workflow-port.md b/skills/rig/samples/340-flat-workflow-port.md new file mode 100644 index 0000000..00da4fd --- /dev/null +++ b/skills/rig/samples/340-flat-workflow-port.md @@ -0,0 +1,63 @@ +# 340 - Flat Workflow Port (rig/globals direct port) + +Demonstrates the **minimal migration path** for a flat Claude dynamic workflow +script whose orchestration code lives at the module's top level rather than +inside a `body` function. Use `"rig/globals"` to get `call`, `pipeline`, and +`parallel` as ambient proxies that delegate to the active launcher context — +the same pattern Claude dynamic workflows use for injected globals. + +Compare with [310-workflow-audit-verify.md](310-workflow-audit-verify.md), which +shows the fully idiomatic `workflow({ body })` form. Prefer the `body` +destructuring style for new programs; use `"rig/globals"` only when porting an +existing flat script one step at a time. + +See [claude-workflow-conversion.md](../references/claude-workflow-conversion.md) +for the full primitive mapping. + +**Claude dynamic workflow** (original flat script): +```js +export const meta = { name: "triage", description: "Triage open issues", + phases: ["Fetch", "Triage"] } + +phase("Fetch") +const raw = await agent("List the 5 most recent open GitHub issues, one per line.") +const issues = (raw ?? "").split("\n").map((s) => s.trim()).filter(Boolean) + +phase("Triage") +const verdicts = await pipeline(issues, (issue) => + agent(`Classify priority for: ${issue}`, { + schema: { type: "object", properties: { + priority: { type: "string", enum: ["high", "medium", "low"] } + }, required: ["priority"], additionalProperties: false } + })) + +return verdicts.filter(Boolean).filter((v) => v.priority === "high").length +``` + +**Rig port — flat style** (step 1: minimal changes, `rig/globals` for ambient context): + +```rig +import { call, pipeline } from "rig/globals"; +import { phase, workflow, s } from "rig"; + +// Mirrors the Claude dynamic workflow's injected `call`/`pipeline` globals. +// `rig/globals` proxies delegate to the launcher's active workflow run. + +phase("Fetch"); +const raw = await call.text("List the 5 most recent open GitHub issues, one per line."); +const issues = (raw ?? "").split("\n").map((s: string) => s.trim()).filter(Boolean); + +phase("Triage"); +const verdicts = await pipeline(issues, (issue: string) => + call.json(`Classify priority for: ${issue}`, s.object({ priority: s.enum("high", "medium", "low") }))); + +// Workflow role: expose metadata for progress displays and tooling. +export default workflow({ + meta: { name: "triage", description: "Triage open issues", phases: ["Fetch", "Triage"] }, + body: async () => verdicts.filter((v) => v?.priority === "high").length, +}); +``` + +> **Step 2 (idiomatic):** move `phase`, `call`, and `pipeline` inside `body` +> and remove the `"rig/globals"` import — see +> [310-workflow-audit-verify.md](310-workflow-audit-verify.md) for the result.