writing-agent-relay-workflows-fe814b — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited writing-agent-relay-workflows-fe814b (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The @relayflows/core workflow system orchestrates multiple AI agents (Claude, Codex, Gemini, Aider, Goose) through typed DAG-based workflows. Workflows can be written in TypeScript (preferred), Python, or YAML.
Language preference: TypeScript > Python > YAML. Use TypeScript unless the project is Python-only or a simple config-driven workflow suits YAML.
Pattern selection: Do not default to dag blindly. If the job needs a different swarm/workflow type, consult the choosing-swarm-patterns skill when available and select the pattern that best matches the coordination problem.
Every generated workflow should satisfy this checklist before it is considered complete:
failOnError: false, hand it to a repair owner, then rerun the same check.BLOCKED_NO_COMMIT with exact evidence and skip commit/PR creation instead of crashing the workflow.captureOutput: true.failOnError: false for intermediate validation gates so the workflow can pass the output to a repair agent.{{steps.<gate>.output}}, fixes source/tests/config, reruns the same command locally, and exits only after the gate is green or the blocker is external.FAILED..reliable() or .repairable() on SDK versions that support it, especially for product-contract workflows. As of AgentWorkforce/relay#827, retry-mode workflows with agents are repair-aware by default, repair agents run before retrying malformed/failed agent steps, and the SDK covers DAG, pipeline, fan-out, worktree-backed, deterministic-only, and agent-plus-gate shapes.#### Every workflow must include two comprehensive fresh-eyes review/fix loops before final acceptance, commit, PR creation, or handoff: first Claude, then Codex. This applies even to small workflows and even when deterministic tests pass. Tests prove commands passed; the fresh-eyes loops make independent agents read the actual resulting files and artifacts as if they did not author them.
verdict: FINDINGS | NO_ISSUES_FOUND | BLOCKED
finding_id: short stable id
severity: blocker | high | medium | low
file: path/to/file
issue: what is wrong
fix_required: concrete change needed
test_required: test, fixture, assertion, or proof command needed
status: open | fixed | wontfix | blocked
evidence: commands run, file paths, or blocker detailsBefore writing the workflow, decide _how the agents will coordinate_. The relay primitive supports two very different shapes, and picking the wrong one wastes the most valuable thing the SDK gives you.
| Shape | What it is | Use when |
|---|---|---|
| Conversation (chat-native) | Interactive agents share a channel; messages, @-mentions, and ambient awareness drive coordination. Lead and workers spawn in parallel and self-organize. The relay is the coordination layer, not just transport. | Multi-file work, peer review loops, cross-agent feedback, dynamic re-planning, multi-PR coordination, anything with a human-in-the-loop escape, swarms where workers pick up each other's output. |
| Pipeline (one-shot DAG) | Each step runs as a one-shot subprocess (claude -p, codex exec); steps hand off via {{steps.X.output}} text injection. No agents are alive at the same time; no chat happens. | Linear, well-specified transformations; deterministic data passing; no live agent-to-agent coordination during implementation. The mandatory final Claude-then-Codex review/fix loops still apply. |
Default to Conversation for any non-trivial work. Pipeline DAGs are simpler to reason about but they do not exercise the relay primitive — they are a Unix pipe with extra steps. If you would happily write the same task as a single shell pipeline, pipeline-shape is fine. Otherwise, you almost certainly want a Conversation shape.
The two shapes can mix within one workflow: pipeline-style deterministic preflight → conversation in the middle → pipeline-style commit-and-PR at the end. See Quick Reference (Conversation) below and [Common Patterns → Interactive Team](#interactive-team-lead--workers-on-shared-channel) for the canonical recipe.
A blunt rule of thumb: if your workflow only usesagentsteps withpreset: 'worker'chained by{{steps.X.output}}, you are not using the relay — you are usingclaude -p | codex exec. That may still be the right answer; just make it a deliberate choice.
#### > Use this when steps are linear, well-specified, and need no agent-to-agent feedback. For anything with iteration, review, or coordination, jump to Quick Reference (Conversation shape) below.
import { workflow } from '@relayflows/core';
async function runWorkflow() {
const result = await workflow('my-workflow')
.description('What this workflow does')
.pattern('dag') // or 'pipeline', 'fan-out', etc.
.channel('wf-my-workflow') // dedicated channel (auto-generated if omitted)
.maxConcurrency(3)
.timeout(3_600_000) // global timeout (ms)
.repairable()
.agent('lead', { cli: 'claude', role: 'Architect', retries: 2 })
.agent('worker', { cli: 'codex', role: 'Implementer', retries: 2 })
.agent('claude-reviewer', {
cli: 'claude',
role: 'First-pass fresh-eyes reviewer',
retries: 1,
preset: 'reviewer',
})
.agent('claude-fixer', { cli: 'claude', role: 'First-pass review-finding fixer', retries: 2 })
.agent('codex-reviewer', {
cli: 'codex',
role: 'Second-pass fresh-eyes reviewer',
retries: 1,
preset: 'reviewer',
})
.agent('codex-fixer', { cli: 'codex', role: 'Review-finding fixer', retries: 2 })
.step('preflight', {
type: 'deterministic',
command: 'git rev-parse --show-toplevel >/dev/null && echo PREFLIGHT_OK',
captureOutput: true,
failOnError: true,
})
.step('plan', {
agent: 'lead',
dependsOn: ['preflight'],
task: `Analyze the codebase and produce a plan.`,
retries: 2,
verification: { type: 'output_contains', value: 'PLAN_COMPLETE' },
})
.step('implement', {
agent: 'worker',
task: `Implement based on this plan:\n{{steps.plan.output}}`,
dependsOn: ['plan'],
verification: { type: 'exit_code' },
})
.step('claude-review', {
agent: 'claude-reviewer',
dependsOn: ['implement'],
task: `Fresh-eyes review the completed workflow output. Read the actual files, diff, repo rules, and available evidence.
Write findings to .workflow-artifacts/my-workflow/claude-review.md.
If there are no actionable issues, write NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('claude-fix', {
agent: 'claude-fixer',
dependsOn: ['claude-review'],
task: `Read .workflow-artifacts/my-workflow/claude-review.md.
Fix every valid issue, add or update appropriate tests/proofs for the fix, rerun relevant checks, and update .workflow-artifacts/my-workflow/claude-fix.md.
If the review says NO_ISSUES_FOUND, record that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('claude-review-final', {
agent: 'claude-reviewer',
dependsOn: ['claude-fix'],
task: `Fresh-eyes review the post-fix state from scratch. Do not rely on the prior review or fix summary.
Write .workflow-artifacts/my-workflow/claude-review-final.md with either actionable findings or NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('claude-fix-final', {
agent: 'claude-fixer',
dependsOn: ['claude-review-final'],
task: `If .workflow-artifacts/my-workflow/claude-review-final.md contains findings, fix them, add or update appropriate tests/proofs, and rerun relevant checks.
If no fix is possible, write .workflow-artifacts/my-workflow/BLOCKED_NO_COMMIT.md with exact evidence.
If it says NO_ISSUES_FOUND, record Claude review signoff.`,
verification: { type: 'exit_code' },
})
.step('codex-review', {
agent: 'codex-reviewer',
dependsOn: ['claude-fix-final'],
task: `Second-pass fresh-eyes review of the post-Claude-fix state. Read the actual files, diff, repo rules, and available evidence.
Write findings to .workflow-artifacts/my-workflow/codex-review.md.
If there are no actionable issues, write NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('codex-fix', {
agent: 'codex-fixer',
dependsOn: ['codex-review'],
task: `Read .workflow-artifacts/my-workflow/codex-review.md.
Fix every valid issue, add or update appropriate tests/proofs for the fix, rerun relevant checks, and update .workflow-artifacts/my-workflow/codex-fix.md.
If the review says NO_ISSUES_FOUND, record that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('codex-review-final', {
agent: 'codex-reviewer',
dependsOn: ['codex-fix'],
task: `Fresh-eyes review the post-Codex-fix state from scratch. Do not rely on the prior review or fix summary.
Write .workflow-artifacts/my-workflow/codex-review-final.md with either actionable findings or NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('codex-fix-final', {
agent: 'codex-fixer',
dependsOn: ['codex-review-final'],
task: `If .workflow-artifacts/my-workflow/codex-review-final.md contains findings, fix them, add or update appropriate tests/proofs, and rerun relevant checks.
If no fix is possible, write .workflow-artifacts/my-workflow/BLOCKED_NO_COMMIT.md with exact evidence.
If it says NO_ISSUES_FOUND, record final review signoff.`,
verification: { type: 'exit_code' },
})
.step('acceptance-after-review', {
type: 'deterministic',
dependsOn: ['codex-fix-final'],
command: 'test ! -f .workflow-artifacts/my-workflow/BLOCKED_NO_COMMIT.md && echo ACCEPTANCE_OK',
captureOutput: true,
failOnError: true,
})
.onError('retry', { maxRetries: 2, retryDelayMs: 10_000 })
.run({ cwd: process.cwd() });
console.log('Result:', result.status);
}
runWorkflow().catch((error) => {
console.error(error);
process.exit(1);
});#### > Use this for any non-trivial work — peer review, multi-file edits, cross-agent feedback, dynamic re-planning. Lead and workers spawn in parallel on a shared channel and self-organize via messages. The relay primitive does the coordinating; verification gates downstream of the lead close the workflow.
import { workflow } from '@relayflows/core';
import { ClaudeModels, CodexModels } from '@agent-relay/config';
async function runWorkflow() {
const result = await workflow('my-workflow')
.description('Multi-file change with peer review')
.pattern('dag')
.channel('wf-my-feature') // dedicated channel — agents share it
.maxConcurrency(4)
.timeout(3_600_000)
.repairable()
// Interactive agents — no preset, they live on the channel
.agent('lead', {
cli: 'claude',
model: ClaudeModels.OPUS,
role: 'Architect + reviewer. Plans, assigns, reviews, posts feedback.',
retries: 1,
})
.agent('impl-a', {
cli: 'codex',
model: CodexModels.GPT_5_4,
role: 'Implementer. Listens on channel for assignments and feedback.',
retries: 2,
})
.agent('impl-b', {
cli: 'codex',
model: CodexModels.GPT_5_4,
role: 'Implementer. Listens on channel for assignments and feedback.',
retries: 2,
})
.agent('claude-reviewer', {
cli: 'claude',
model: ClaudeModels.OPUS,
preset: 'reviewer',
role: 'First-pass fresh-eyes reviewer. Reads the final diff and artifacts from scratch.',
retries: 1,
})
.agent('claude-fixer', {
cli: 'claude',
model: ClaudeModels.SONNET,
role: 'First-pass review-finding fixer. Repairs valid findings, adds tests/proofs, and reruns checks.',
retries: 2,
})
.agent('codex-reviewer', {
cli: 'codex',
model: CodexModels.GPT_5_4,
preset: 'reviewer',
role: 'Second-pass fresh-eyes reviewer. Reviews the post-Claude-fix state from scratch.',
retries: 1,
})
.agent('codex-fixer', {
cli: 'codex',
model: CodexModels.GPT_5_4,
role: 'Review-finding fixer. Repairs valid findings, adds tests/proofs, and reruns checks.',
retries: 2,
})
// Deterministic context — pre-reads files once, posts to the channel for everyone
.step('preflight', {
type: 'deterministic',
command: 'git rev-parse --show-toplevel >/dev/null && echo PREFLIGHT_OK',
captureOutput: true,
failOnError: true,
})
.step('context', {
type: 'deterministic',
dependsOn: ['preflight'],
command: 'git ls-files src/',
captureOutput: true,
})
// Lead and workers all depend on `context` — they start CONCURRENTLY.
// They coordinate over #wf-my-feature, not via {{steps.X.output}}.
.step('lead-coordinate', {
agent: 'lead',
dependsOn: ['context'],
task: `You are the lead on #wf-my-feature. Workers: impl-a, impl-b.
Post the plan. Assign files. Review their PRs/diffs. Post feedback in-channel.
Workers iterate based on your feedback. Exit when both files pass review.`,
})
.step('impl-a-work', {
agent: 'impl-a',
dependsOn: ['context'], // SAME dep as lead → starts in parallel, no deadlock
task: `You are impl-a on #wf-my-feature. Wait for the lead's plan.
Implement your assigned file. Post a completion message. Address feedback.`,
})
.step('impl-b-work', {
agent: 'impl-b',
dependsOn: ['context'], // SAME dep as lead
task: `You are impl-b on #wf-my-feature. Wait for the lead's plan.
Implement your assigned file. Post a completion message. Address feedback.`,
})
// Downstream gates on the lead — lead exits when satisfied.
// Capture failures, then hand them to an agent for repair.
.step('verify', {
type: 'deterministic',
dependsOn: ['lead-coordinate'],
command: 'npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: false,
})
.step('repair-verify', {
agent: 'lead',
dependsOn: ['verify'],
task: `If verification passed, summarize evidence.
If it failed, use this output to assign and fix issues, then rerun the command until green:
{{steps.verify.output}}`,
verification: { type: 'exit_code' },
})
.step('verify-final', {
type: 'deterministic',
dependsOn: ['repair-verify'],
command: 'npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: false,
})
.step('claude-review', {
agent: 'claude-reviewer',
dependsOn: ['verify-final'],
task: `First-pass fresh-eyes review of the post-implementation state.
Read the actual changed files, git diff, repo instructions, task spec, and verification output:
{{steps.verify-final.output}}
Write .workflow-artifacts/my-feature/claude-review.md with:
- actionable findings, each with file paths and required fix
- or NO_ISSUES_FOUND if there are no remaining issues`,
verification: { type: 'exit_code' },
})
.step('claude-fix', {
agent: 'claude-fixer',
dependsOn: ['claude-review'],
task: `Read .workflow-artifacts/my-feature/claude-review.md.
If there are findings, fix every valid one and add or update appropriate tests/proofs. After each fix, rerun the relevant check and review the changed files again.
Keep iterating locally until this round has no remaining valid issues.
Write .workflow-artifacts/my-feature/claude-fix.md with fixes and commands run.
If the review says NO_ISSUES_FOUND, write that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('claude-review-final', {
agent: 'claude-reviewer',
dependsOn: ['claude-fix'],
task: `Perform a fresh post-fix review from scratch. Do not rely on previous review text or the fixer's summary.
Read files, diff, repo rules, task spec, and evidence. Write .workflow-artifacts/my-feature/claude-review-final.md.
Use NO_ISSUES_FOUND only if there are no actionable issues left.`,
verification: { type: 'exit_code' },
})
.step('claude-fix-final', {
agent: 'claude-fixer',
dependsOn: ['claude-review-final'],
task: `If the final Claude review found issues, fix them, add or update appropriate tests/proofs, and rerun the relevant checks until green.
If no fix is possible, write .workflow-artifacts/my-feature/BLOCKED_NO_COMMIT.md with exact evidence and do not commit.
If the final review says NO_ISSUES_FOUND, record signoff in .workflow-artifacts/my-feature/claude-signoff.md.`,
verification: { type: 'exit_code' },
})
.step('verify-after-claude-review', {
type: 'deterministic',
dependsOn: ['claude-fix-final'],
command:
'test ! -f .workflow-artifacts/my-feature/BLOCKED_NO_COMMIT.md && npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: false,
})
.step('codex-review', {
agent: 'codex-reviewer',
dependsOn: ['verify-after-claude-review'],
task: `Second-pass fresh-eyes review of the post-Claude-fix state.
Read the actual changed files, git diff, repo instructions, task spec, and verification output:
{{steps.verify-after-claude-review.output}}
Write .workflow-artifacts/my-feature/codex-review.md with:
- actionable findings, each with file paths and required fix
- or NO_ISSUES_FOUND if there are no remaining issues`,
verification: { type: 'exit_code' },
})
.step('codex-fix', {
agent: 'codex-fixer',
dependsOn: ['codex-review'],
task: `Read .workflow-artifacts/my-feature/codex-review.md.
If there are findings, fix every valid one and add or update appropriate tests/proofs. After each fix, rerun the relevant check and review the changed files again.
Keep iterating locally until this round has no remaining valid issues.
Write .workflow-artifacts/my-feature/codex-fix.md with fixes and commands run.
If the review says NO_ISSUES_FOUND, write that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('codex-review-final', {
agent: 'codex-reviewer',
dependsOn: ['codex-fix'],
task: `Perform a fresh post-Codex-fix review from scratch. Do not rely on previous review text or the fixer's summary.
Read files, diff, repo rules, task spec, and evidence. Write .workflow-artifacts/my-feature/codex-review-final.md.
Use NO_ISSUES_FOUND only if there are no actionable issues left.`,
verification: { type: 'exit_code' },
})
.step('codex-fix-final', {
agent: 'codex-fixer',
dependsOn: ['codex-review-final'],
task: `If the final Codex review found issues, fix them, add or update appropriate tests/proofs, and rerun the relevant checks until green.
If no fix is possible, write .workflow-artifacts/my-feature/BLOCKED_NO_COMMIT.md with exact evidence and do not commit.
If the final review says NO_ISSUES_FOUND, record signoff in .workflow-artifacts/my-feature/codex-signoff.md.`,
verification: { type: 'exit_code' },
})
.step('verify-after-review', {
type: 'deterministic',
dependsOn: ['codex-fix-final'],
command:
'test ! -f .workflow-artifacts/my-feature/BLOCKED_NO_COMMIT.md && npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: true,
})
.onError('retry', { maxRetries: 2, retryDelayMs: 10_000 })
.run({ cwd: process.cwd() });
console.log('Result:', result.status);
}
runWorkflow().catch((error) => {
console.error(error);
process.exit(1);
});package.json for "type": "module" — if ESM, use import; if CJS, use require(). In both cases, wrap execution in an async function instead of raw top-level await.agent-relay local run <file.ts> executes the file as a standalone subprocess — it does NOT inspect exports. The file MUST call .run()..run({ cwd: process.cwd() }) — createWorkflowRenderer does not exist.run({ dryRun: true, cwd: process.cwd() }) or runWorkflow(path, { dryRun: true }) from TypeScript. Use agent-relay local run <file> for execution.#### Cross-Workflow Parallelism: Wave Planning
# BAD — sequential (14 hours for 27 workflows at ~30 min each)
agent-relay local run workflows/34-sst-wiring.ts
agent-relay local run workflows/35-env-config.ts
agent-relay local run workflows/36-loading-states.ts
# ... one at a time
# GOOD — parallel waves (3-4 hours for 27 workflows)
# Wave 1: independent infra (parallel)
agent-relay local run workflows/34-sst-wiring.ts &
agent-relay local run workflows/35-env-config.ts &
agent-relay local run workflows/36-loading-states.ts &
agent-relay local run workflows/37-responsive.ts &
wait
git add -A && git commit -m "Wave 1"
# Wave 2: testing (parallel — independent test suites)
agent-relay local run workflows/40-unit-tests.ts &
agent-relay local run workflows/41-integration-tests.ts &
agent-relay local run workflows/42-e2e-tests.ts &
wait
git add -A && git commit -m "Wave 2"#### Declare File Scope for Planning
workflow('48-comparison-mode')
.packages(['web', 'core']) // monorepo packages touched
.isolatedFrom(['49-feedback-system']) // explicitly safe to parallelize
.requiresBefore(['46-admin-dashboard']); // explicit ordering constraint#### Within-Workflow Parallelism
// BAD — unnecessary sequential chain
.step('fix-component-a', { agent: 'worker', dependsOn: ['review'] })
.step('fix-component-b', { agent: 'worker', dependsOn: ['fix-component-a'] }) // why wait?
// GOOD — parallel fan-out, merge at the end
.step('fix-component-a', { agent: 'impl-1', dependsOn: ['review'] })
.step('fix-component-b', { agent: 'impl-2', dependsOn: ['review'] }) // same dep = parallel
.step('verify-all', { agent: 'reviewer', dependsOn: ['fix-component-a', 'fix-component-b'] })#### 1. Do not use raw top-level await
async function runWorkflow() {
const result = await workflow('my-workflow')
// ...
.run({ cwd: process.cwd() });
console.log('Workflow status:', result.status);
}
runWorkflow().catch((error) => {
console.error(error);
process.exit(1);
});#### 2b. Standard preflight template for resumable workflows
.step('preflight', {
type: 'deterministic',
command: [
'set -e',
'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
'echo "branch: $BRANCH"',
'if [ "$BRANCH" != "fix/your-branch-name" ]; then echo "ERROR: wrong branch"; exit 1; fi',
// Files the workflow is allowed to find dirty on entry:
// - package-lock.json: npm install is idempotent and often touches it
// - every file the workflow's edit steps will rewrite: a prior partial
// run may have left them dirty, and the edit step will rewrite
// them cleanly before commit
// Everything else is unexpected drift and must fail preflight.
'ALLOWED_DIRTY="package-lock.json|path/to/file1\\\\.ts|path/to/file2\\\\.ts"',
'DIRTY=$(git diff --name-only | grep -vE "^(${ALLOWED_DIRTY})$" || true)',
'if [ -n "$DIRTY" ]; then echo "ERROR: unexpected tracked drift:"; echo "$DIRTY"; exit 1; fi',
'if ! git diff --cached --quiet; then echo "ERROR: staging area is dirty"; git diff --cached --stat; exit 1; fi',
'gh auth status >/dev/null 2>&1 || (echo "ERROR: gh CLI not authenticated"; exit 1)',
'echo PREFLIGHT_OK',
].join(' && '),
captureOutput: true,
failOnError: true,
}),#### 2c. Picking the right .join() for multi-line shell commands
command: [
'set -e',
'HITS=$(grep -c diag src/cli/commands/setup.ts || true)',
'if [ "$HITS" -lt 6 ]; then echo "FAIL"; exit 1; fi',
'echo OK',
].join(' && '),#### 3. Keep final verification boring and deterministic
grep -Eq "foo|bar|baz" file.ts#### 6. Be explicit about shell requirements
/opt/homebrew/bin/bash workflows/your-workflow/execute.sh --wave 2#### 9. Factor repo-specific setup into a shared helper
// workflows/lib/cloud-repo-setup.ts
export interface CloudRepoSetupOptions {
branch: string;
committerName?: string;
extraSetupCommands?: string[];
skipWorkspaceBuild?: boolean;
}
export function applyCloudRepoSetup<T>(wf: T, opts: CloudRepoSetupOptions): T {
// adds two steps: setup-branch, install-deps
// install-deps runs: npm install + workspace prebuilds (build:platform, build:core, etc.)
// ...
}#### The minimal "open a PR" recipe
Current @relayflows/core does not provide createGitHubStep. Model shipping as deterministic workflow steps. Locally, use git plus gh after a preflight that proves gh auth status works. When a runtime adapter is required, write a small deterministic script that imports GitHubClient from @relayflows/github-primitive and calls createBranch, createFile or updateFile, and createPR.
import { workflow } from '@relayflows/core';
const BRANCH = `agent-relay/run-${Date.now()}`;
async function runWorkflow() {
await workflow('feature-x')
// ... your real implementation, repair, review loops, and final acceptance ...
.step('write-marker', {
type: 'deterministic',
command: `echo "fix landed at $(date -u)" >> CHANGELOG.md`,
})
.step('create-branch', {
type: 'deterministic',
dependsOn: ['write-marker'],
command: `git switch -c ${BRANCH}`,
})
.step('commit-change', {
type: 'deterministic',
dependsOn: ['create-branch'],
command: 'git add CHANGELOG.md && git commit -m "chore: changelog entry"',
})
.step('push-branch', {
type: 'deterministic',
dependsOn: ['commit-change'],
command: `git push -u origin ${BRANCH}`,
})
.step('open-pr', {
type: 'deterministic',
dependsOn: ['push-branch'],
command: `gh pr create --base main --head ${BRANCH} --title "feat: ship feature X" --body-file .workflow-artifacts/feature-x/pr-body.md`,
verification: { type: 'pr_url', value: 'AgentWorkforce/cloud' },
})
.run({ cwd: process.cwd() });
}
runWorkflow().catch((error) => {
console.error(error);
process.exit(1);
});#### Verification Gates
verification: { type: 'exit_code' } // preferred for code-editing steps
verification: { type: 'output_contains', value: 'DONE' } // optional accelerator
verification: { type: 'file_exists', value: 'src/out.ts' } // deterministic file check
verification: { type: 'pr_url', value: 'owner/repo' } // step must leave behind a PR#### DAG Dependencies
.step('fix-types', { agent: 'worker', dependsOn: ['review'], ... })
.step('fix-tests', { agent: 'worker', dependsOn: ['review'], ... })
.step('final', { agent: 'lead', dependsOn: ['fix-types', 'fix-tests'], ... })#### SDK API
// Subscribe an agent to additional channels post-spawn
relay.subscribe({ agent: 'security-auditor', channels: ['review-pr-456'] });
// Unsubscribe — agent leaves the channel entirely
relay.unsubscribe({ agent: 'security-auditor', channels: ['general'] });
// Mute — agent stays subscribed (history access) but messages are NOT injected into PTY
relay.mute({ agent: 'security-auditor', channel: 'review-pr-123' });
// Unmute — resume PTY injection
relay.unmute({ agent: 'security-auditor', channel: 'review-pr-123' });#### Events
relay.onChannelSubscribed = (agent, channels) => {
/* ... */
};
relay.onChannelUnsubscribed = (agent, channels) => {
/* ... */
};
relay.onChannelMuted = (agent, channel) => {
/* ... */
};
relay.onChannelUnmuted = (agent, channel) => {
/* ... */
};#### ```typescript
.agent('name', {
cli: 'claude' | 'codex' | 'gemini' | 'aider' | 'goose' | 'opencode' | 'droid',
role?: string,
preset?: 'lead' | 'worker' | 'reviewer' | 'analyst',
retries?: number,
model?: string,
interactive?: boolean, // default: true
})#### Model Constants
import { ClaudeModels, CodexModels, GeminiModels } from '@agent-relay/config';
.agent('planner', { cli: 'claude', model: ClaudeModels.OPUS }) // not 'opus'
.agent('worker', { cli: 'claude', model: ClaudeModels.SONNET }) // not 'sonnet'
.agent('coder', { cli: 'codex', model: CodexModels.GPT_5_4 }) // not 'gpt-5.4'#### Agent Steps
.step('name', {
agent: string,
task: string, // supports {{var}} and {{steps.NAME.output}}
dependsOn?: string[],
verification?: VerificationCheck,
retries?: number,
})#### Deterministic Steps (Shell Commands)
.step('verify-files', {
type: 'deterministic',
command: 'test -f src/auth.ts && echo "FILE_EXISTS"',
dependsOn: ['implement'],
captureOutput: true,
failOnError: false,
})
.step('repair-files', {
agent: 'worker',
dependsOn: ['verify-files'],
task: `If verify-files failed, create or fix the missing file and rerun the check.
Output:
{{steps.verify-files.output}}`,
verification: { type: 'exit_code' },
})
.step('verify-files-final', {
type: 'deterministic',
command: 'test -f src/auth.ts && echo "FILE_EXISTS"',
dependsOn: ['repair-files'],
captureOutput: true,
failOnError: true,
})#### Mandatory Claude-Then-Codex Review/Fix Loops
.agent('claude-reviewer', {
cli: 'claude',
preset: 'reviewer',
role: 'First-pass fresh-eyes reviewer. Reads actual files, diffs, rules, and evidence from scratch.',
retries: 1,
})
.agent('claude-fixer', {
cli: 'claude',
role: 'Fixer for valid Claude review findings. Adds or updates tests/proofs for each fix.',
retries: 2,
})
.agent('codex-reviewer', {
cli: 'codex',
preset: 'reviewer',
role: 'Second-pass fresh-eyes reviewer. Reviews the post-Claude-fix state from scratch.',
retries: 1,
})
.agent('codex-fixer', {
cli: 'codex',
role: 'Fixer for valid Codex review findings. Adds or updates tests/proofs for each fix.',
retries: 2,
})
.step('claude-review', {
agent: 'claude-reviewer',
dependsOn: ['verify-final'],
task: `First-pass fresh-eyes review.
Read the task spec, AGENTS.md / CLAUDE.md, changed files, final diff, artifacts, and verification evidence:
{{steps.verify-final.output}}
Write .workflow-artifacts/<workflow>/claude-review.md.
Use actionable findings with file paths, severity, and required fixes.
If there are no issues, write NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('claude-fix', {
agent: 'claude-fixer',
dependsOn: ['claude-review'],
task: `Read .workflow-artifacts/<workflow>/claude-review.md.
If it contains findings, fix every valid issue and add or update appropriate tests/proofs. After each fix, rerun targeted checks and review the touched files again.
Keep iterating locally until this round has no remaining valid issues.
Write .workflow-artifacts/<workflow>/claude-fix.md with fixes and commands run.
If the review says NO_ISSUES_FOUND, record that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('claude-review-final', {
agent: 'claude-reviewer',
dependsOn: ['claude-fix'],
task: `Review the post-Claude-fix state from scratch. Do not rely on prior review text or fixer summaries.
Read the files, diff, rules, spec, and evidence. Write .workflow-artifacts/<workflow>/claude-review-final.md.
Use NO_ISSUES_FOUND only if there are no actionable issues left.`,
verification: { type: 'exit_code' },
})
.step('claude-fix-final', {
agent: 'claude-fixer',
dependsOn: ['claude-review-final'],
task: `If the final Claude review contains findings, fix them, add or update appropriate tests/proofs, rerun relevant checks, and write .workflow-artifacts/<workflow>/claude-fix-final.md.
If a finding cannot be fixed, write .workflow-artifacts/<workflow>/BLOCKED_NO_COMMIT.md with exact evidence.
If the final review says NO_ISSUES_FOUND, write .workflow-artifacts/<workflow>/claude-signoff.md.`,
verification: { type: 'exit_code' },
})
.step('verify-after-claude-review', {
type: 'deterministic',
dependsOn: ['claude-fix-final'],
command: 'test ! -f .workflow-artifacts/<workflow>/BLOCKED_NO_COMMIT.md && npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: false,
})
.step('codex-review', {
agent: 'codex-reviewer',
dependsOn: ['verify-after-claude-review'],
task: `Second-pass fresh-eyes review of the post-Claude-fix state.
Read the task spec, AGENTS.md / CLAUDE.md, changed files, final diff, artifacts, and verification evidence:
{{steps.verify-after-claude-review.output}}
Write .workflow-artifacts/<workflow>/codex-review.md.
Use actionable findings with file paths, severity, and required fixes.
If there are no issues, write NO_ISSUES_FOUND.`,
verification: { type: 'exit_code' },
})
.step('codex-fix', {
agent: 'codex-fixer',
dependsOn: ['codex-review'],
task: `Read .workflow-artifacts/<workflow>/codex-review.md.
If it contains findings, fix every valid issue and add or update appropriate tests/proofs. After each fix, rerun targeted checks and review the touched files again.
Keep iterating locally until this round has no remaining valid issues.
Write .workflow-artifacts/<workflow>/codex-fix.md with fixes and commands run.
If the review says NO_ISSUES_FOUND, record that no fix was needed.`,
verification: { type: 'exit_code' },
})
.step('codex-review-final', {
agent: 'codex-reviewer',
dependsOn: ['codex-fix'],
task: `Review the post-fix state from scratch. Do not rely on prior review text or fixer summaries.
Read the files, diff, rules, spec, and evidence. Write .workflow-artifacts/<workflow>/codex-review-final.md.
Use NO_ISSUES_FOUND only if there are no actionable issues left.`,
verification: { type: 'exit_code' },
})
.step('codex-fix-final', {
agent: 'codex-fixer',
dependsOn: ['codex-review-final'],
task: `If the final review contains findings, fix them, add or update appropriate tests/proofs, rerun relevant checks, and write .workflow-artifacts/<workflow>/codex-fix-final.md.
If a finding cannot be fixed, write .workflow-artifacts/<workflow>/BLOCKED_NO_COMMIT.md with exact evidence.
If the final review says NO_ISSUES_FOUND, write .workflow-artifacts/<workflow>/codex-signoff.md.`,
verification: { type: 'exit_code' },
})
.step('acceptance-after-codex-review', {
type: 'deterministic',
dependsOn: ['codex-fix-final'],
command: 'test ! -f .workflow-artifacts/<workflow>/BLOCKED_NO_COMMIT.md && npm run typecheck && npm test 2>&1',
captureOutput: true,
failOnError: true,
})#### Interactive Team (lead + workers on shared channel)
.agent('lead', {
cli: 'claude',
model: ClaudeModels.OPUS,
role: 'Architect and reviewer — assigns work, reviews, posts feedback',
retries: 1,
// No preset — interactive by default
})
.agent('impl-new', {
cli: 'codex',
model: CodexModels.GPT_5_4,
role: 'Creates new files. Listens on channel for assignments and feedback.',
retries: 2,
// No preset — interactive, receives channel messages
})
.agent('impl-modify', {
cli: 'codex',
model: CodexModels.GPT_5_4,
role: 'Edits existing files. Listens on channel for assignments and feedback.',
retries: 2,
})
// All three share the same dependsOn — they start concurrently (no deadlock)
.step('lead-coordinate', {
agent: 'lead',
dependsOn: ['context'],
task: `You are the lead on #channel. Workers: impl-new, impl-modify.
Post the plan. Assign files. Review their work. Post feedback if needed.
Workers iterate based on your feedback. Exit when all files are correct.`,
})
.step('impl-new-work', {
agent: 'impl-new',
dependsOn: ['context'], // same dep as lead = parallel start
task: `You are impl-new on #channel. Wait for the lead's plan.
Create files as assigned. Report completion. Fix issues from feedback.`,
})
.step('impl-modify-work', {
agent: 'impl-modify',
dependsOn: ['context'], // same dep as lead = parallel start
task: `You are impl-modify on #channel. Wait for the lead's plan.
Edit files as assigned. Report completion. Fix issues from feedback.`,
})
// Downstream gates on lead (lead exits when satisfied)
.step('verify', { type: 'deterministic', dependsOn: ['lead-coordinate'], ... })#### 1. Question / Answer (blocking ask)
.step('integrate', {
agent: 'integrator',
dependsOn: ['context'],
task: `You are the integrator on #wf-feature.
Before writing code, post a direct question to @schema-owner asking which
table owns the new field. Do NOT proceed until @schema-owner replies in
channel. If no reply arrives in 5 minutes, @-mention the lead.`,
})#### 2. Broadcast / Ack
.step('lead-coordinate', {
agent: 'lead',
dependsOn: ['context'],
task: `Post the plan to #wf-feature, then @impl-a @impl-b @impl-c.
Wait for each to reply with "ACK <agent-name>" before issuing assignments.
If any worker hasn't acked in 3 minutes, re-post and ping again.
Only after all three have acked, post per-worker assignments.`,
})#### 3. Peer Review Handoff
.step('impl-a-work', {
agent: 'impl-a',
dependsOn: ['context'],
task: `Implement src/foo.ts per the lead's assignment.
When done, post to #wf-feature: "@reviewer ready: src/foo.ts" — include the
commit SHA. Then wait for @reviewer's verdict in channel.
- If "APPROVED", you're done.
- If "CHANGES_REQUESTED <notes>", apply the notes and re-post.
- If no verdict in 5 min, @-mention the lead.`,
})#### 4. Standup / Status Probe
.step('lead-coordinate', {
agent: 'lead',
task: `... coordinate the team ...
Every 10 minutes, post a status probe: "@impl-a @impl-b status?"
Each worker should reply with one of:
- "RUNNING <step>" (still working)
- "BLOCKED <reason>" (@-mention the lead with the blocker)
- "DONE <artifact>" (ready for review)
If a worker is silent for two probes in a row, mark them stalled and
reassign their work to a peer.`,
})#### 5. Hand-Off with Context
.step('impl-a-work', {
agent: 'impl-a',
task: `... finish your part ...
When done, post a handoff to #wf-feature targeting the next worker:
"@impl-b HANDOFF: src/foo.ts ready. Touched: <files>. Open question: <if any>.
Tests: <pass/fail summary>. Commit: <sha>."`,
})#### Pipeline (sequential handoff)
.pattern('pipeline')
.step('analyze', { agent: 'analyst', task: '...' })
.step('implement', { agent: 'dev', task: '{{steps.analyze.output}}', dependsOn: ['analyze'] })
.step('test', { agent: 'tester', task: '{{steps.implement.output}}', dependsOn: ['implement'] })#### Error Handling
.onError('fail-fast') // stop on first failure (default)
.onError('continue') // skip failed branches, continue others
.onError('retry', { maxRetries: 3, retryDelayMs: 5000 })#### When a workflow needs to modify multiple existing files, use one agent step per file with a deterministic verify gate after each. Agents reliably edit 1-2 files per step but fail on 4+.
steps:
- name: read-types
type: deterministic
command: cat src/types.ts
captureOutput: true
- name: edit-types
agent: dev
dependsOn: [read-types]
task: |
Edit src/types.ts. Current contents:
{{steps.read-types.output}}
Add 'pending' to the Status union type.
Only edit this one file.
verification:
type: exit_code
- name: verify-types
type: deterministic
dependsOn: [edit-types]
command: 'if git diff --quiet src/types.ts; then echo "NOT MODIFIED"; exit 1; fi; echo "OK"'
captureOutput: true
failOnError: false
- name: fix-types-verification
agent: dev
dependsOn: [verify-types]
task: |
If verify-types failed, fix src/types.ts and rerun the verify command.
Output:
{{steps.verify-types.output}}
verification:
type: exit_code
- name: verify-types-final
type: deterministic
dependsOn: [fix-types-verification]
command: 'if git diff --quiet src/types.ts; then echo "NOT MODIFIED"; exit 1; fi; echo "OK"'
captureOutput: true
failOnError: true
- name: read-service
type: deterministic
dependsOn: [verify-types-final]
command: cat src/service.ts
captureOutput: true
- name: edit-service
agent: dev
dependsOn: [read-service]
task: |
Edit src/service.ts. Current contents:
{{steps.read-service.output}}
Add a handlePending() method.
Only edit this one file.
verification:
type: exit_code
- name: verify-service
type: deterministic
dependsOn: [edit-service]
command: 'if git diff --quiet src/service.ts; then echo "NOT MODIFIED"; exit 1; fi; echo "OK"'
captureOutput: true
failOnError: false
- name: fix-service-verification
agent: dev
dependsOn: [verify-service]
task: |
If verify-service failed, fix src/service.ts and rerun the verify command.
Output:
{{steps.verify-service.output}}
verification:
type: exit_code
- name: verify-service-final
type: deterministic
dependsOn: [fix-service-verification]
command: 'if git diff --quiet src/service.ts; then echo "NOT MODIFIED"; exit 1; fi; echo "OK"'
captureOutput: true
failOnError: true
# Deterministic commit — never rely on agents to commit
- name: commit
type: deterministic
dependsOn: [verify-service-final]
command: npm run typecheck && npm test && git add src/types.ts src/service.ts && git commit -m "feat: add pending status"
captureOutput: true
failOnError: false
- name: repair-commit
agent: dev
dependsOn: [commit]
task: |
If commit failed, fix the blocker, rerun npm run typecheck && npm test, and create the commit.
If commit passed, confirm the commit subject.
Output:
{{steps.commit.output}}
verification:
type: exit_code
- name: verify-commit-created
type: deterministic
dependsOn: [repair-commit]
command: 'git log -1 --pretty=%s | grep -q "^feat: add pending status$" && echo "COMMIT_OK" || (echo "COMMIT_MISSING"; exit 1)'
captureOutput: true
failOnError: true#### After any step that creates files, add a deterministic file_exists check before proceeding. Non-interactive agents may exit 0 without writing anything (wrong cwd, stdout instead of disk).
- name: verify-files
type: deterministic
dependsOn: [impl-auth, impl-storage]
command: |
missing=0
for f in src/auth/credentials.ts src/storage/client.ts; do
if [ ! -f "$f" ]; then echo "MISSING: $f"; missing=$((missing+1)); fi
done
if [ $missing -gt 0 ]; then echo "$missing files missing"; exit 1; fi
echo "All files present"
captureOutput: true
failOnError: false
- name: fix-missing-files
agent: impl-auth
dependsOn: [verify-files]
task: |
If verify-files found missing files, create/fix them and rerun the check.
Output:
{{steps.verify-files.output}}
verification:
type: exit_code
- name: verify-files-final
type: deterministic
dependsOn: [fix-missing-files]
command: |
missing=0
for f in src/auth/credentials.ts src/storage/client.ts; do
if [ ! -f "$f" ]; then echo "MISSING: $f"; missing=$((missing+1)); fi
done
if [ $missing -gt 0 ]; then echo "$missing files missing"; exit 1; fi
echo "All files present"
captureOutput: true
failOnError: true#### Edit Gates Must See Untracked Files
- name: provider-edit-gate-capture
type: deterministic
dependsOn: [implement-providers]
command: |
if [ -z "$(git status --short -- packages/new-provider .workflow-artifacts/my-flow)" ]; then
echo "NO_PROVIDER_CHANGES"
exit 1
fi
echo "PROVIDER_EDIT_GATE_OK"
captureOutput: true
failOnError: false
- name: repair-edit-gate
agent: provider-worker
dependsOn: [provider-edit-gate-capture]
task: |
If provider-edit-gate-capture reported NO_PROVIDER_CHANGES, inspect git
status including untracked files and add the missing provider artifacts.
If it already passed, do nothing.
verification:
type: exit_code
- name: provider-edit-gate-final
type: deterministic
dependsOn: [repair-edit-gate]
command: |
if [ -z "$(git status --short -- packages/new-provider .workflow-artifacts/my-flow)" ]; then
echo "NO_PROVIDER_CHANGES"
exit 1
fi
echo "PROVIDER_EDIT_GATE_FINAL_OK"
captureOutput: true
failOnError: false
- name: repair-provider-edit-gate-final
agent: provider-worker
dependsOn: [provider-edit-gate-final]
task: |
If provider-edit-gate-final is still red, repair the missing provider
artifacts and rerun the check. If repair is impossible, write
.workflow-artifacts/my-flow/BLOCKED_NO_COMMIT.md with exact evidence and
do not commit.
Output:
{{steps.provider-edit-gate-final.output}}
verification:
type: exit_code#### Interactive lead-and-worker teams are useful, but they are still process
.step('runtime-implementation', {
agent: 'impl-runtime',
dependsOn: ['context'],
task: 'Implement the runtime slice and write .workflow-artifacts/runtime.md',
})
.step('adapter-implementation', {
agent: 'impl-adapters',
dependsOn: ['context'],
task: 'Implement adapter wiring and write .workflow-artifacts/adapters.md',
})
.step('implementation-reconcile', {
type: 'deterministic',
dependsOn: ['context'],
command: `git status --short -- packages/core packages/*/src/writeback.ts scripts tests .workflow-artifacts
test -f scripts/verify-e2e.mjs || echo "MISSING_E2E"
test -f packages/core/src/runtime/router.ts || echo "MISSING_ROUTER"`,
captureOutput: true,
failOnError: false,
})
.step('repair-implementation-reconcile', {
agent: 'qa',
dependsOn: ['implementation-reconcile'],
task: `Finish anything missing before gates run:\n{{steps.implementation-reconcile.output}}`,
verification: { type: 'exit_code' },
})
.step('run-e2e', {
type: 'deterministic',
dependsOn: ['repair-implementation-reconcile'],
command: 'npm run verify:e2e',
captureOutput: true,
failOnError: false,
})#### ```yaml
# WRONG — deadlock: coordinate depends on context, work-a depends on coordinate
steps:
- name: coordinate
dependsOn: [context] # lead waits for WORKER_DONE...
- name: work-a
dependsOn: [coordinate] # ...but work-a can't start until coordinate finishes
# RIGHT — workers and lead start in parallel
steps:
- name: context
type: deterministic
- name: work-a
dependsOn: [context] # starts with lead
- name: coordinate
dependsOn: [context] # starts with workers
- name: merge
dependsOn: [work-a, coordinate]#### One agent, one deliverable. A step's task prompt should be 10-20 lines max.
# Team pattern: lead + workers on a shared channel
steps:
- name: track-lead-coord
agent: track-lead
dependsOn: [prior-step]
task: |
Lead the track on #my-track. Workers: track-worker-1, track-worker-2.
Post assignments to the channel. Review worker output.
- name: track-worker-1-impl
agent: track-worker-1
dependsOn: [prior-step] # same dep as lead — starts concurrently
task: |
Join #my-track. track-lead will post your assignment.
Implement the file as directed.
verification:
type: exit_code
- name: next-step
dependsOn: [track-lead-coord] # downstream depends on lead, not workersWhen you set .pattern('supervisor') (or hub-spoke, fan-out), the runner auto-assigns a supervisor agent as owner for worker steps. The supervisor monitors progress, nudges idle workers, and issues OWNER_DECISION.
Auto-hardening only activates for hub patterns — not pipeline or dag.
| Use case | Pattern | Why |
|---|---|---|
| Sequential, no monitoring | pipeline | Simple, no overhead |
| Workers need oversight | supervisor | Auto-owner monitors |
| Local/small models | supervisor | Supervisor catches stuck workers |
| All non-interactive | pipeline or dag | No PTY = no supervision needed |
Cap `maxConcurrency` at 4-6. Spawning 10+ agents simultaneously causes broker timeouts.
| Parallel agents | maxConcurrency |
|---|---|
| 2-4 | 4 (default safe) |
| 5-10 | 5 |
| 10+ | 6-8 max |
| Mistake | Fix | ||
|---|---|---|---|
Treating relay as transport, not as a coordination layer (every step is preset: 'worker', every handoff is {{steps.X.output}}) | Default to Conversation shape for non-trivial work — interactive agents on a shared channel. Pipeline-shape is only correct when the work could be expressed as a `bash \ | bash \ | bash` pipe. |
| Interactive agents on a channel whose task strings don't tell them to talk to each other | Pick a Chat-Native Coordination Recipe (Q/A, Broadcast/Ack, Peer Review, Standup, Hand-Off) and bake it into the task prompt — otherwise you're paying for a chat substrate you're not using | ||
| All workflows run sequentially | Group independent workflows into parallel waves (4-7x speedup) | ||
| Every step depends on the previous one | Only add dependsOn when there's a real data dependency | ||
| Self-review step with no timeout | Set timeout: 300_000 (5 min) — Codex hangs in non-interactive review | ||
| One giant workflow per feature | Split into smaller workflows that can run in parallel waves | ||
| Adding exit instructions to tasks | Runner handles self-termination automatically | ||
| Interactive PTY Codex for one-shot artifact steps | Use preset: 'worker' plus file_exists or custom verification | ||
Setting timeoutMs on agents/steps | Use global .timeout() only | ||
Using general channel | Set .channel('wf-name') for isolation | ||
{{steps.X.output}} without dependsOn: ['X'] | Output won't be available yet | ||
| Requiring exact sentinel as only completion gate | Use exit_code or file_exists verification | ||
| Writing 100-line task prompts | Split into lead + workers on a channel | ||
maxConcurrency: 16 with many parallel steps | Cap at 5-6 | ||
| Non-interactive agent reading large files via tools | Pre-read in deterministic step, inject via {{steps.X.output}} | ||
| Workers depending on lead step (deadlock) | Both depend on shared context step | ||
| Validation gates depending directly on long interactive implementation agents | Add a deterministic implementation-reconcile step and make gates depend on its repair step | ||
fan-out/hub-spoke for simple parallel workers | Use dag instead | ||
pipeline but expecting auto-supervisor | Only hub patterns auto-harden. Use .pattern('supervisor') | ||
Workers without preset: 'worker' in one-shot DAG lead+worker flows | Add preset for clean stdout when chaining {{steps.X.output}} (not needed for interactive team patterns) | ||
Using _ in YAML numbers (timeoutMs: 1_200_000) | YAML doesn't support _ separators | ||
| Workflow timeout under 30 min for complex workflows | Use 3600000 (1 hour) as default | ||
Using require() in ESM projects | Check package.json for "type": "module" — use import if ESM | ||
Raw top-level await in workflow files | Executor paths may compile as CJS. Wrap .run() in async function runWorkflow() for both ESM and CJS files | ||
| Using `cre |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.