Graph-engineer a goal — decompose it into a DAG of single-job specialist
Scanned 9/3/2026
Install to Claude Code
npx -y skills add ayaangazali/graph-engineering --skill graph --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Graph?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ayaangazali-graph)More formats (shields.io, HTML) on the badges page.
---
name: graph
description: Graph-engineer a goal — decompose it into a DAG of single-job specialist
subagents with JSON-schema artifacts on every edge, then execute it as a background
workflow (or an Agent-tool fan-out when workflows are unavailable). Use when the
user says "graph this", "/graph <goal>", "graph-engineer ...", or hands over a
multi-step goal that benefits from parallel specialists and verification. Not for
trivial single-file tasks.
argument-hint: <goal>
---
# Graph: engineer a goal as a DAG of specialist agents
You are a **graph engineer**. Your input is a goal:
> $ARGUMENTS
Your output is an executed DAG and a final report. A DAG here means: **nodes** are
single-job specialist subagents, **edges** are JSON-schema'd artifacts (findings,
diffs, verdicts, reports) — never prose. The **sink** is the artifact the user
actually wants. The next node consumes the previous node's artifact
deterministically.
**Never start executing before the DAG is written down.** Design first, run second.
If no goal was provided, ask for one and stop.
Vocabulary convention used throughout: **nodes** count the DAG design; **agents**
count the spend. Fan-out multiplies them — a 6-node design with 3 finders capped at
8 findings each, 3 votes per finding, can legally spawn ~77 agents.
---
## Step 0 — Qualify: should this even be a graph?
Graphs add coordination overhead. A badly modeled graph is worse than a good loop.
Pick the smallest tier that fits (adapted from Anthropic's production scaling rules):
| Tier | Shape | Node tool budget |
|---|---|---|
| One focused change, single question | Do it inline — no graph. Say so in one sentence and just do it. | — |
| 2–4 genuinely independent subtasks | 2–4 specialist nodes | ~10–15 tool calls each |
| Large + mechanically parallel | 3–5 *thinker* nodes; more nodes only as mechanical fan-out over items (files, sources, routes) | ~3–10 tool calls per fan-out node |
Additional do-not-graph signals — prefer a single loop when:
- **Every node would re-read the same large context.** N agents each re-briefed on
the same 50 files loses to one persistent-context loop.
- **The goal converges on one artifact through revision cycles.** DAGs are acyclic;
iterative refinement of a single thing is a loop, not a graph.
- **The goal is a project, not a task** — it spans sessions, needs progress that
survives restarts, or will be worked autonomously over time. Route it to
`/graph-goal` (a persistent goal graph ticked by `/graph-next`) instead of a
one-shot run.
- **The goal needs human judgment mid-flow.** Workflows cannot pause for input —
split at the decision point into two sequential graphs: run graph 1, surface the
decision, run graph 2 with the answer (see the gated-sequence note in
`reference/patterns.md`).
**Cost gate — computed in projected agents, not nodes.** Before executing, estimate
total agent invocations: sources + Σ(schema `maxItems` × lenses × votes) + loop
worst-case rounds. Every fan-out edge schema MUST carry `maxItems`, or this number
is uncomputable. Above **~25 projected agents** (the threshold of Claude Code's own
large-workflow warning): show the user the DAG and the projected count and get
confirmation before running — **but only when a user can actually answer.**
**Non-interactive default — this is a hard rule, not a preference.** If you cannot
get a mid-run reply — a headless `-p`/print run, a piped/automated invocation, an
autonomous loop, or the goal text says to proceed without asking — you must **never
end your turn with a question**, because nothing will answer it and the entire run
produces zero output (the worst possible result). When the cost gate would
otherwise pause: silently auto-select the largest plan that fits under ~25
projected agents — first drop verification 3 votes → 1, then cap/chunk the fan-out
(fewer finders, tighter `maxItems`) — state the reduction in one line
("non-interactive: 1-vote verify, N finders, ~M projected agents") and **run it in
the same turn.** Do not print options and stop. Only if even the floor (single
vote, minimum fan-out) exceeds the ceiling do you refuse — and then execute a
single-loop inline audit instead of dead-ending. A degraded result beats nothing;
a dead-ended plan is a failed run.
(When the session *is* interactive, keep the pause: show the DAG and projected
count and wait for the user — that's the safe, correct behavior there.)
## Step 0.5 — Choose the categorical mechanism by the goal's failure mode
This is the most important routing decision, and it's a hedged heuristic, not an
instrument. **The A/B lesson, baked in:** a capable single loop is already ~100%
precise on a *clean* task, so opinion-verification (out-voting) pays only when the
base task is **noisy**. On a clean task, out-voting is moot — `/graph` must instead
win by producing an **artifact the loop structurally cannot** (a receipt).
**Work these gates top-down and stop at the first that matches** (order encodes
precedence, so a goal that matches two lands in exactly one home):
1. **Does the claim/goal have an executable consequence, and does the repo
build/run?** → **card 9 oracle-forge** (rerunnable proof harness). This includes
produce/fix/refactor goals whose result runs — verify with a card-9 harness seeded
by an intent-blind attacker. *This gate has precedence over everything below —
an exit code is a non-model oracle; don't out-vote what you can run.*
2. **Else, is there a given, written, enumerable success contract** (an RFC,
acceptance criteria, compliance controls)? → **card 11 proof-obligation ledger**
(coverage over the stated contract).
3. **Else, is there one answer that already HAS a truth you are estimating** —
which DB, will it deadlock, root cause among rival mechanisms? → **card 10
consensus & disagreement distribution**. (Not a design you *author* → that's
card 3; not a contract you *enumerate* → card 11; not a corpus you *sweep* →
gate 4.) Ground any factual sub-claims a hypothesis cites with **card 12**;
escalate a *contested* split that pivots on one discrete external fact to
**card 13** (never a first pick).
4. **Else, open discovery** (find the bugs/issues/gaps) → **fan-out + verify
(cards 2/4)**. Noise sets the vote count: 3 adversarial votes if noisy, 1
spot-vote if a cheap probe looks clean (spend the freed budget on coverage).
**Hard caveat:** pure-normative "is this *sound / good / well-designed*?" answers
are **ungroundable by type** — they land at gate 3 (card 10), never 12/13, which
need a citeable factual spine or a discrete discriminating fact. And a
*produce/fix* goal with **no** runnable result skips gate 1 → validation chain
(card 1) + adversarial verify (card 4).
Non-executable reviews with a factual spine use **card 12** to ground the spans
their claims cite (this is a sub-step under gate 3/4, not its own gate). The router
picks the *primary* receipt; cards 1–8 still do the thorough legwork underneath.
(Calibration-probe and cost-gate details live in card 4's notes and Step 0.)
## Step 1 — Decompose the goal into a DAG
Name nodes **verb-first**: `scope`, `find:auth`, `fetch:src-3`, `verify:claim-2`,
`judge:perf-lens`, `synthesize`. One job per node — if a node's description needs
"and", split it.
Write the DAG as an adjacency list grouped into layers, e.g.:
```
L0 scope → find:*
L1 find:auth, find:input, find:secrets (parallel) → dedup
L2 dedup (plain code, not an agent) → verify:*
L3 verify:<each finding> ×3 votes (parallel) → synthesize
L4 synthesize → report (sink)
```
Structural rules:
- **Decompose by context boundaries, not job titles.** A plan→implement→test→review
role chain is one context chopped into four lossy telephone hops — keep it one
node. Split where the *inputs* genuinely differ (different files, different
sources, different lenses), not where the org chart would.
- **Parallel branches must be read-only, write to disjoint files, or converge on a
merge/judge node.** Never fan out open-ended creative work whose outputs must fit
together — sibling nodes can't see each other's implicit decisions.
- **Every node that produces claims or creative output must feed a verification
node — and the verifier is a DIFFERENT node with fresh context.** A "double-check
your own work" step appended to the producer is not verification; intrinsic
self-correction without new information degrades answers. Unverified claims do
not reach the final report.
- Identify sources (no inbound edges), merge points (multiple inbound), and the sink.
- **Chunk mechanical fan-out — don't spawn one agent per item.** A same-check
sweep over N files (audit every catch block, rename across the tree) is a handful
of finders each handed a *batch* of files, not N finders. One agent reads 15–20
files fine; 22 one-file finders is 22× the cost for no gain and often trips the
cost gate. Reserve one-node-per-item for items that genuinely need isolation
(parallel mutation in separate worktrees) or that individually exhaust a context.
- Pure data transforms (dedup, flatten, filter, rank) are **plain code between
stages, not agent nodes**. Don't spend an agent on a `.filter()`.
- Edges carry artifacts. If you catch yourself passing a paragraph of prose between
nodes, you skipped Step 2.
## Step 2 — Contract the edges and brief the nodes
### Edge contracts
Define **one JSON Schema constant per edge type** before writing any orchestration.
Schema discipline (distilled from Anthropic's bundled `deep-research` workflow plus
the evaluator-bias literature):
- `required` on every load-bearing field — optional fields get silently omitted.
- `enum` for anything ranked or judged: `"severity": {"enum": ["critical","high","medium","low"]}`.
- **Verdict enums are three-valued**: `{"enum": ["confirmed","refuted","unverifiable"]}`.
Infra failures and missing evidence are *unverifiable*, never refutations or
confirmations.
- **Evidence before verdict.** Order schema properties so evidence fields (`quote`,
`file_line`, `command_output`) come BEFORE `verdict`/`score`. Generation is
autoregressive — this conditions the verdict on the evidence instead of letting
evidence be rationalized after. A verdict whose evidence field is empty is an
invalid vote: count it like a null.
- `maxItems` caps on arrays to bound downstream fan-out (mandatory on any edge that
feeds a fan-out — see the Step 0 cost gate).
- Downstream nodes reference upstream items **by index** (`"source_findings": [0, 3]`)
rather than re-emitting their text — smaller artifacts, no drift.
- **Claims travel verbatim, one hop.** When the final artifact must contain a
claim's text, the synthesizer copies it verbatim from the producing node's
artifact — never re-summarizes a summary. Each generation hop distorts facts in
an unpredictable direction.
Schemas constrain the artifact, **not the thinking**: prompts for reasoning nodes
(find/verify/judge/synthesize) should say "work through the task in free text
first; then return the JSON artifact" — strict format-only output measurably
degrades reasoning. Mechanical extract/classify nodes (`effort: 'low'`) are the
exception: strict format helps those.
### The node brief
The orchestrator's brief is the #1 failure point in multi-agent systems (~42% of
production failures are specification quality). Every node prompt contains exactly
four parts:
1. **Objective** — the single job, restated. Plus a one-line domain persona for
judgment nodes only ("You are a staff security engineer reviewing findings for a
production release"); skip personas for mechanical nodes.
2. **Inputs** — the upstream artifacts (or indices into a numbered list).
3. **Tool & source guidance** — where to look and where not to ("read only
`src/routes/`", "prefer official docs over aggregators"), plus a tool-call
budget from the Step 0 tier table.
4. **Boundaries & done-condition** — an explicit NOT-list ("do NOT fix, only
report") and what finished looks like.
If `.claude/graph-memory/<task-class>.md` exists (a `/graph-eval` champion
brief), fold it into the finder briefs as a prior — it is a synthetic-plant
selection signal, never a recall guarantee.
The `scope` node's schema must include required `assumptions` and `out_of_scope`
arrays; propagate both into every downstream brief. Workflows can't ask clarifying
questions mid-run — ambiguity gets resolved once, upfront, in writing.
**Cache-align fan-out briefs**: build one `PREAMBLE` constant (role + rubric +
schema semantics + shared context, byte-identical across siblings) and append the
per-item data at the very end. Never interpolate item data into the middle — an
identical prefix makes N sibling prompts ~90% cheaper via prompt caching.
(Exception: card 10's proposer layer deliberately breaks this — divergent prefixes
decorrelate a consensus estimate, which is worth more there than the cache saving.)
## Step 3 — Choose the shape
If your DAG is anything beyond a single linear chain, read
`reference/patterns.md` in this skill's directory now — it has thirteen shapes
(validation chain, fan-out+merge, judge panel, adversarial verify, loop-until-dry,
map-reduce sweep, evaluator-optimizer, gated sequence, and the five categorical
receipt shapes: oracle-forge, consensus & disagreement, proof-obligation ledger,
grounded claim audit, crucial-experiment ledger) with corrected skeletons. Match your DAG to one or compose several. (If that read
is permission-blocked — plugin files can sit outside a session's sandbox — proceed
on the rules below; they are the load-bearing subset.)
Four load-bearing rules:
1. **`pipeline()` is the default.** A barrier (`parallel()` between stages) is
correct only when stage N needs cross-item context from ALL of stage N−1:
dedup/merge across the full set, early-exit when the total is zero, or a prompt
that says "compare against the other findings". "The stages are conceptually
separate" does not justify a barrier.
2. **Route cost with `effort`, not `model`.** Omit `model` (nodes inherit the
session model). `effort: 'low'` for mechanical parse/extract nodes, inherit for
build nodes, `'high'`/`'xhigh'` only for verify/judge/synthesize nodes. Override
`model` only when you're highly confident (e.g. haiku-class for log parsing).
3. **Verification is adversarial, diverse, and blind.** Default: 3 votes per claim,
each with a DISTINCT aspect+strategy lens (logical refute / reproduce-or-execute
/ edge-case probe) — identical refuters only when the claim has exactly one
failure mode. Each voter is prompted to REFUTE ("default to refuted=true if
uncertain") and must cite concrete evidence. ≥2 valid refutations kill; fewer
than 2 valid votes means *unverifiable*, not refuted. Voters never see another
voter's verdict, a running tally, or a debate round — deliberation flips correct
verdicts via conformity; aggregate blind votes in code. And scale votes
honestly: majority-vote replication only helps where a single attempt is more
likely right than wrong; past 3–5 votes you amplify the majority failure mode.
For hard or creative outputs use a judge panel or evaluator-optimizer instead.
Reducing below 3 votes is allowed only in fallback mode or for low-stakes
mechanical checks — and the reduction must be stated in the final report.
4. **Executable claims get an ORACLE, not a vote** (Step 0.5 gate 1 has
precedence): forge a card-9 harness, prove it discriminates via a mutation
flip, read exit codes in **code**, ship the rerunnable harness. Votes
(card 4) are the non-executable fallback. And when independent framings
split, the split itself is the deliverable (card 10) — never collapse it
to a mean.
## Step 4 — Execute
Branch on your own tool list (if `Workflow` isn't visible, confirm with a tool
search before falling back):
### Path A — the `Workflow` tool is available
**Read `reference/workflow-api.md` first — always, before writing the script.**
It is the single source of truth for primitive semantics, determinism rules, caps,
and footguns. If that read is permission-blocked, these are the essentials:
`meta` is a pure literal; plain JS, no TypeScript; `Date.now()`/`Math.random()`/
argless `new Date()` throw; `parallel()` takes thunks (`() => agent(...)`), never
bare promises; pass `{phase}` opts on `agent()` calls inside concurrent stages;
guard `budget.remaining()` loops on `budget.total`; caps are 16 concurrent /
1,000 agents / 4,096 items per call.
Script checklist beyond the API sheet:
- Guard the input: `const GOAL = (typeof args === "string" && args.trim()) || ""`
— return an error object if empty.
- `.filter(Boolean)` after every `parallel()`/`pipeline()` — and it only works if
your `.then()` wrappers propagate null. Never bury a null agent result inside a
truthy object: `.then(v => v && ({...f, verdict: v}))`, not
`.then(v => ({...f, verdict: v}))`.
- Schemas from Step 2 go in the `schema` option — output is validated at the tool
layer and the node retries on mismatch. That's what makes edges deterministic.
- Return a compact result object: the sink artifact plus stats
(`{nodes_run, findings, confirmed, refuted, unverifiable, dropped}`).
**Permissions:** workflow subagents inherit the session allowlist and acceptEdits
covers edits only. If nodes need Bash or network access, tell the user which
commands to allowlist *before* launching — a permission-denied node returns null
and would otherwise vanish into `.filter(Boolean)`. Count null-dropped nodes in
the report.
Then invoke the Workflow tool with the script inline.
**If the harness denies the Workflow call** (e.g. "Review dynamic workflow before
running" — the dynamic-script review gate fires under acceptEdits and headless
`-p` runs, where no one can approve it): do NOT stall asking for a confirmation
that can't arrive. Say one line — "Workflow call was gated; running the same DAG
via subagent fan-out instead" — and execute the identical DAG through Path B.
**On node failure:** first validation failure retries automatically (schema layer).
If a node fails again or returns "cannot complete", don't retry a third time and
don't silently drop the branch — **split that node into 2–3 narrower sub-nodes**
(as-needed decomposition beats both fixed plans and blind retries), edit the
persisted script, and resume with `{scriptPath, resumeFromRunId}` — completed
nodes return cached results instantly.
### Path B — no `Workflow` tool (or the call was denied)
Read `fallback.md` in this skill's directory and follow it. If that read is
permission-blocked, this condensed core is sufficient:
- Announce once: *"Workflow engine unavailable — running the graph as a layered
subagent fan-out (on Pro, enable Dynamic workflows in /config; if Claude Code
< 2.1.154, update)."*
- Cap at ~8 nodes, ~4 concurrent. Topologically sort into layers; run each
layer's nodes as parallel `Agent` calls in a single message (spawn as
`graph:dag-node` agent type; if that type is unknown, `general-purpose`).
- Every node prompt: the four-part brief (Step 2) + the edge schema verbatim +
"reason in free text first; your FINAL message must be exactly one JSON object
matching this schema — no fences, no prose after".
- Validate each artifact against its schema between layers; re-prompt once on
mismatch; second failure → drop the branch and say so.
- Artifacts over ~1 KB: node writes to `.claude/graph-runs/<run>/<node>.json` and
returns `{path, summary, counts}`; append completed layers to `ledger.json`
(that's your resume).
- Verification still runs (1–2 votes acceptable here — state it); unverifiable
votes never count as refutations; report which engine ran the graph.
## Step 5 — Report
When the graph completes, give the user:
1. **The sink artifact** — the thing they asked for, front and center.
2. **The receipt** — this is what makes `/graph` more than a fancy loop. Ship the
receipt — *rerunnable* for card 9 (a real non-model oracle), *auditable* for
10–13 (same-model judgments a third party can re-check) — not just the claim:
- Oracle-forge goals (card 9): the **rerunnable harness path** for every Tier-A
finding, plus the two exit codes that flipped. The user reruns it themselves.
- Consensus goals (card 10): the **disagreement distribution** (the raw
answer-key tally + agreement fraction), with the monoculture caveat — never a
bare point answer.
- Given-contract goals (card 11): the **coverage ledger** (code proves every
obligation is accounted for; each met/unmet is a witness verdict) and the
named blocking rows.
- Grounded-audit goals (card 12): the **claim→verbatim-span table + named crux**,
the two *unfused* scalars (`citation_integrity` and the decoy `VOID` status),
and the groundable-by-type % — never a single fused "grounding score". Report
the **structured** status verbatim: `VOID` means the checker rubber-stamped
decoys (escalate); an `audited` run that merely found an unsupported/uncited
claim is a normal finding — **do not call it VOID.** The crux always carries
the claim text (and, if uncited, says no corpus span exists) — never null.
- Crucial-experiment goals (card 13): the **sealed prediction matrix + commit
hash**, the chosen observation's external locator and observed enum — and, when
it fires, an honest `no-discriminating-observation` / off-matrix result.
3. **The route audit** — one line naming which Step-0.5 gate matched and why
("gate 2: RFC 9110 §8 is a written enumerable contract → card 11"). The
router is a prose heuristic, and a mis-route ships the wrong *kind* of
receipt with full confidence — this line is what lets a reader catch that
after the fact instead of never.
4. **DAG stats** — nodes run, findings produced, **confirmed / refuted /
unverifiable**, and nodes dropped to null (permission denials and infra
failures — visible, not absorbed into "fewer findings"). For discovery goals,
add the honest completeness note: *"estimated coverage is a lower bound; finders
are correlated, treat as optimistic"* — a calibrated hedge the loop's
uncalibrated "I think I'm done" cannot emit.
5. One line: *"Replay this anytime — `/graph-save <name>` persists this graph as
your own `/command`."* If the goal was long-horizon (a project, not a task),
also mention `/graph-goal` — it compiles the goal into a persistent graph
that `/graph-next` ticks across sessions.
The framing that separates this from `/goal`: **`/graph` returns receipts
(rerunnable or auditable), not just claims.** If verification killed everything,
say that plainly — an honest "all candidate findings were refuted" beats a padded
report. Unverifiable ≠ refuted: surface unverifiable claims separately.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!