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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions skills/rig/references/claude-workflow-conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Expand Down
63 changes: 63 additions & 0 deletions skills/rig/samples/340-flat-workflow-port.md
Original file line number Diff line number Diff line change
@@ -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.