Skip to content

Close Claude dynamic-workflow API gaps in rig workflows - #292

Merged
pelikhan merged 4 commits into
mainfrom
copilot/deep-comparison-sdk-api-surface
Jul 30, 2026
Merged

Close Claude dynamic-workflow API gaps in rig workflows#292
pelikhan merged 4 commits into
mainfrom
copilot/deep-comparison-sdk-api-surface

Conversation

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Compares the Claude Code dynamic-workflow scripting surface (export const meta, args, agent(), parallel(), pipeline(), phase(), log(), budget, nested workflow(), top-level return) against rig's workflow() API, and implements the missing primitives so porting a dynamic workflow is mechanical rather than a redesign.

rig already matched meta / args / parallel / phase / log / return. Six gaps remained:

Dynamic workflow rig before rig now
agent(prompt, { schema }) call.json(prompt, schema, options?)
pipeline(items, ...stages) single stage only multi-stage, (previous, item, index)
{ phase } per call WorkflowCallOptions.phase
budget.total / spent() / remaining() context.budget (metered in agent calls)
workflow(ref, args) call.workflow(child, args?, options?)
meta.phases details, whenToUse phases: string[], required phases?: (string | { title, detail })[], whenToUse

Runtime (skills/rig/rig.ts)

  • call.json builds a one-off agent constrained to an s.* schema; result typed through InferSchema, null on failure like call.
  • pipeline gains overloads for up to four stages plus a variadic fallback. Items stream independently (no barrier between stages). Single-stage pipeline(items, (item) => ...) is source-compatible.
  • call.workflow runs a child workflow inline on the parent's limiter, agent budget, cancellation signal, and event stream, bracketed by log events and restoring the ambient phase.
  • budget is denominated in agent calls (limits.maxAgents), not tokens — the one deliberate divergence, documented.

Docs

  • New references/claude-workflow-conversion.md: primitive mapping, JSON Schema → s.* translation, a worked before/after port, and behavior differences (failure holes, budget units, nesting, sandbox, resume/human gates as non-goals).
  • dynamic-workflows.md context table, SKILL.md decision rows + reference router, README.md links.
  • Sample 310-workflow-audit-verify.md — fan-out/verify workflow in the ported shape.

Tests

  • src/workflow.test.ts: stage threading, per-call phase override, budget metering, nested workflow sharing limits/events, and call.text + call.json against a stub engine.

Ported shape:

const audit = workflow({
  meta: { name: "audit", description: "Find and verify issues", phases: [{ title: "Find" }, { title: "Verify" }] },
  body: async ({ call, parallel, phase, pipeline }) => {
    phase("Find");
    const found = await parallel(areas.map((area) => () =>
      call.json(`Audit ${area}.`, s.object({ findings: s.array(finding) }), { label: area })));

    phase("Verify");
    return pipeline(
      found.flatMap((r) => r?.findings ?? []),
      (f: Finding) => call.json(`Verify "${f.title}".`, s.object({ real: s.boolean }), { phase: "Verify" }),
    );
  },
});

Not ported (Claude Code runtime features, not API surface): effort, agentType: 'Explore', worktree isolation, resume journals, live human gates. Each is called out in the conversion reference with the rig equivalent.

Copilot AI and others added 2 commits July 30, 2026 06:53
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 32.2 AIC · ⌖ 4.05 AIC · ⊞ 6.3K
Comment /matt to run again

Comment thread skills/rig/rig.ts
return Promise.all(items.map(async (item, index) => {
let value: unknown = item;
for (const stage of stages) {
value = await stage(value, item, index);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Pipeline stage exceptions propagate to the caller — unlike call, which swallows errors and returns null, a throwing stage rejects the whole Promise.all for all items. This creates an undocumented failure hole the conversion guide mentions but the implementation doesn't handle.

💡 Suggested test or guard

Add a test that pins the current semantics so the choice is intentional and visible:

it('stage throw rejects the entire pipeline', async () => {
  await expect(
    pipeline([1, 2, 3], (_prev, item) => {
      if (item === 2) throw new Error('boom');
      return item;
    })
  ).rejects.toThrow('boom');
});

Alternatively, wrap each stage body in try/catch and return null to match call's contract. Either way, callers need a clear signal about which contract this follows.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot review comment

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot a rig program exports an agent, it should be internally wrapped into a workflow so that top level phase and other constructs are allowed.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot a rig program exports an agent, it should be internally wrapped into a workflow so that top level phase and other constructs are allowed.

Done in fdabea2. The launcher now evaluates the program module and runs its root inside a workflow run (runInRootWorkflow), for every root kind — agent, string, prompt builder, or workflow. Because module evaluation happens inside the run, top-level constructs work:

import { agent, log, phase } from "rig";

phase("Review");
log("reviewing the staged diff");

export default agent({ instructions: "Review the staged diff." });
  • phase(name) / log(message) are exported from rig and forward to the active run (no-ops outside one).
  • currentWorkflow() returns the run context (call, budget, signal) or undefined.
  • Ambient context is propagated with AsyncLocalStorage, so nested call.workflow children and concurrent calls see the right context.
  • A workflow default export now nests into the launcher run instead of starting a second one, sharing the limiter, budget, and event stream.
  • Run events are emitted under the workflow:event debug category.

Covered by new tests in src/workflow.test.ts and a launcher fixture test; npm run typecheck and npm test (361) pass.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts on this branch.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts on this branch.

Merged origin/main in 37ae5b1. The only conflict was in skills/rig/rig.ts: main's new debug loggers (agent:tools, agent:parse, launcher:*) plus the launcher's debugLauncherProgram/debugLauncherResult events, versus this branch's runInRootWorkflow wrapping of program execution. Both are kept — the program import and root resolution stay inside the root workflow run, with the debug events emitted from inside it and the result event after it.

npm run typecheck, npm test (363 passing), and the markdown sample typecheck all pass on the merged tree.

@pelikhan
pelikhan merged commit 394b566 into main Jul 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants