Graph Workflow
This skill is the opt-in entry point for Claude Code's Workflow tool: dynamic multi-agent graphs
scripted in plain JavaScript. Workflows are never auto-selected — they fire only because this
skill (or the breadth cue) told Claude to reach for one. If the task is one file, one job, or a
quick conversational answer, do it directly; see "When not to use this" below.
Before writing a script, read docs/graph-orchestration.md once. It is the single source of
truth for the schemas, the hard runtime constraints, and the topology-selection guide referenced
throughout this skill — this file applies that mapping, it does not restate it. In particular:
- Invocation. Repo-authored workflows resolve only via
scriptPath, nevername:Workflow({ scriptPath: '~/.claude/workflows/<file>.js' }). Named lookup (Workflow({name: '...'})) does not see user-level workflows — confirmed empirically (docs/graph-orchestration.md§6). - Runtime constraints (§2): plain JavaScript only (no TS syntax), no filesystem/Node API access
inside the script body (schemas must be inlined per script, copy-pasted from §3 — they cannot be
imported),
Date.now()/Math.random()/arglessnew Date()throw, concurrency capped atmin(16, cores-2), keep to roughly ≤15 agents per workflow,metamust be a pure literal, and a thunk oragent()call that fails resolves tonullrather than rejecting the batch — every result array must be.filter(Boolean)-ed before use.
Now the seven-step procedure for shaping the graph itself.
1. Find the edges
For every "and then" in the task, ask: does the next step read the previous step's output? If no data crosses the boundary, there is no edge, and the two steps can run concurrently instead of waiting on each other.
- "Audit every route for auth bugs, then write one report" — the per-route audits don't read each other's output, so they have no edges between them; only the final synthesis step has an edge from all of them (a fan-in).
- "Read the schema, then generate migrations from it" — real edge; the second step needs the first step's actual output as input.
- Draw this out before touching topology. Most breadth-shaped tasks turn out to be one fan-out with zero internal edges plus a single fan-in at the end — not a long serial chain.
2. Pick a topology
Default to pipeline(). Reach for a parallel() barrier inside a stage only when that stage
genuinely needs the whole prior set together — cross-set dedupe, an early-exit on a total count, or
a prompt that compares one finding against "the other findings." Full selection guide with worked
examples (pipeline, diamond, loop-until-dry, router, plus the judge-panel/adversarial-verify
variants) is docs/graph-orchestration.md §4 — use that table to pick, don't re-derive it here.
Quick heuristic: "one agent per file/route/service, then combine" is almost always a diamond (fan-out → reduce in code → synthesize), not a pipeline of pipelines.
3. Give every node a contract
Every node gets: a JSON schema for its output, a bounded input passed explicitly in args (never
"figure out the context yourself"), and exactly one job. Copy the exact inline schema you need from
docs/graph-orchestration.md §3 — TASK_RESULT for execution nodes, FINDING for discovery nodes,
VERDICT for verifier nodes. Do not invent a fourth shape; extend one of these three if a field is
missing, and note the extension in your script's comments.
4. Keep edges in code — never spawn an agent to do plumbing
Flatten, dedupe, and filter are flatMap and a Set. They are free, deterministic, and belong in
the script body between nodes — not a job for another agent call.
Wrong:
// DON'T: spawning an agent to merge/dedupe results is plumbing-by-agent
const merged = await agent({ prompt: `Combine and dedupe these findings: ${JSON.stringify(results)}` });
Right:
const merged = results
.filter(Boolean) // drop nulls from failed/skipped agents
.flatMap(r => r.findings)
.filter((f, i, arr) => arr.findIndex(g => g.id === f.id) === i); // dedupe by id, plain code
If you catch yourself drafting a prompt whose job is "combine/merge/dedupe/filter the following,"
stop and rewrite it as the plain-JS equivalent above. This is a hard constraint, not a style
preference (TR §1, docs/graph-orchestration.md §2/§4 principle 4).
5. Tier the models
Use the existing Rule 1 worker-routing table verbatim —
skills/start-phase-execute/references/lean-orchestrator.md — as each node's model/effort.
Do not author a second tiering table for graph nodes. Down-route to haiku only when a node's spec is
fully complete, self-contained, and has a runnable gate; default to sonnet/medium for ordinary
implementation or per-item audit nodes; escalate to opus only for cross-cutting-design or
security-sensitive nodes, per that table's own criteria — never blanket-escalate a whole graph
because the task "feels big."
6. Verify before reporting, when the output is findings
If a node's output is a claim someone will act on (a bug, a duplication, a security finding), run it
through a refute-by-default verifier fan-out before it reaches the final report: independent
verifier nodes each try to refute the finding using the VERDICT schema (§3 of the mapping
doc), defaulting to refuted: true under uncertainty. A finding survives only on majority
non-refutation. This is the diamond-with-a-verification-stage pattern (docs/graph-orchestration.md
§4, "judge panel" / "adversarial verify"). Skip this step only when the node's output is not a
finding someone will act on (e.g. a mechanical transform, a status contract).
7. Isolate only when nodes write in parallel
isolation: 'worktree' on an agent() call costs roughly 200-500ms plus disk per agent — it is a
seatbelt, not a default. Apply it only to nodes that write files or repo state concurrently with
other nodes in the same fan-out. Read-only nodes (auditing, scanning, reviewing) never need it, even
inside a wide fan-out.
When not to use this skill
Do the work directly instead of authoring a graph when: it's a single-file change with no
independent sub-units to fan out; it's a conversational turn answerable in the current response; or
the coordination overhead (node contracts + topology choice) would take longer than just doing the
two or three things directly. Full rationale: docs/graph-orchestration.md §5. --team in
start-phase-execute remains the default execution path — this skill is additive and opt-in, never
auto-selected.
Choosing among the three orchestration skills
| Skill | Domain | Reach for it when |
|---|---|---|
graph-workflow (this skill) |
In-session; coordination in code, zero extra tokens | Breadth within one session on one machine — "audit every route," "one agent per file," "fan them out and verify" |
fleet-dispatch-and-watch |
Cross-machine; tmux + SSH + remote worktrees | Work must leave this machine — heavy compute, battery, hardware limits, background/long-running workers to poll |
orchestration |
Orca app coordination | Orca-managed workers, threaded ask/reply, worker_done/escalation waits, Orca task DAGs |
These compose rather than compete: a graph-workflow node's agent() call may itself kick off fleet
dispatch or Orca coordination as part of doing its one job — picking this skill for the in-session
graph doesn't preclude a node reaching into either of the other two.
Red flags
| Anti-pattern | Fix |
|---|---|
| Spawning an agent to combine/merge/dedupe/filter results | Plain code: flatMap + Set/findIndex between nodes (§4) |
parallel() barrier on every stage "to be safe" |
Default pipeline(); barrier only when a stage needs the whole prior set (§2) |
| A new per-node model-tiering scheme | Reuse Rule 1 verbatim (§5) |
| Reporting a finding straight from one node | Refute-by-default verifier fan-out first, when output is findings (§6) |
isolation: 'worktree' on every node |
Only nodes that write concurrently (§7) |
Workflow({ name: '...' }) for a repo-authored script |
Always scriptPath (see invocation note above) |
| Using this skill for a one-file change | Do it directly — see "When not to use this skill" |