Composing Patterns
By the Syncfusion DevTutorials team · Last updated
Real graphs combine patterns. Build a review graph from parallel audits, a verifier join, a router, a fixer loop and a human gate, and grade it on a scorecard over 40 tickets.
Watch Five Small Mistakes Add Up
You know every pattern now. Real graphs combine them. Here is a first try at a review graph for 40 tickets: two audits run in parallel, then a fixer works on each ticket.
Run it and count the problems.
Audits straight into a fixer
Each problem is one you have already fixed in an earlier lesson:
- the two audits write the same field, so one report is lost (Lesson 5);
- nothing checks the audits before the fixer starts (no verifier);
- every ticket takes the same path, so risky ones get no extra care (Lesson 4);
- the fixer has no test checker, so some fixes are marked done while the tests fail (Lesson 7);
- nothing stops a risky change before it is merged (the human gate, Lesson 10).
Composing is not about adding more nodes. It is about wiring the right patterns together through the state contract, and proving the whole thing works on a scorecard.
TL;DR
- Real graphs compose patterns: the output of one pattern is the input of the next.
- The state contract is the glue. Each pattern writes its own fields; the next one reads only what it needs.
- A common shape: parallel audits → verifier join → router → fixer loop against tests → human gate.
- Grade the graph on a scorecard over real work, never on how the diagram looks.
- A simpler graph that meets the bar always beats a bigger one with the same score.
A Real-World Example
Kai's team runs a nightly review of dependency updates. They started with one agent per update. Then they added a security audit, then a licence audit, then a "double check" agent, then a "summary" agent. Twelve nodes later, nobody could say what each one was for, and the bill had tripled.
They drew it out and asked of each node: which pattern is this, and what does it buy? Four nodes bought nothing: their job was already done by another node. They removed them. The scorecard stayed the same, the run got faster, and the graph fit on one screen.
What You Will Learn
- How to combine the five patterns into one graph
- How the state contract connects one pattern to the next
- How to grade a graph on a scorecard
- Why the simplest graph that meets the bar wins
- How to tell which part of a composed graph causes a failure
Building a Review Graph
This is the review-and-fix graph, one of the three graphs you can build in the capstone. Each part is one pattern you know.
Five parts: parallel audits, a verifier join, a router, a fixer loop against the tests, and a human gate on the careful path.
| Part | Pattern | What it buys |
|---|---|---|
| Security and licence audits | Parallelization | Independent checks at the same time |
| Verify | Join with a timeout and a policy | One trusted summary of the audits |
| Route | Routing with a fallback | Cost matched to risk |
| Careful fix | Evaluator-optimizer | Fix until the tests pass, with real exits |
| Approve | Human gate | A person on the risky path |
Here is how the parts connect through the state. Each part writes its own fields, and the next one reads only what it needs:
Fields flow from pattern to pattern
const steps = [
{ node: "security", reads: ["jobs"], writes: ["security_report"] },
{ node: "licence", reads: ["jobs"], writes: ["licence_report"] },
{ node: "verify", reads: ["security_report", "licence_report"], writes: ["audit"] },
{ node: "route", reads: ["ticket", "audit"], writes: ["route"] },
{ node: "careful_fix", reads: ["ticket", "audit"], writes: ["careful_diff"] },
{ node: "approve", reads: ["careful_diff"], writes: ["approval"] },
];
const written = new Set(["jobs", "ticket"]);
for (const s of steps) {
const missing = s.reads.filter((f) => !written.has(f));
console.log(s.node.padEnd(12) + (missing.length ? "MISSING " + missing.join(", ") : "ok") + " -> " + s.writes.join(", "));
s.writes.forEach((f) => written.add(f));
}
Expected output:
security ok -> security_report
licence ok -> licence_report
verify ok -> audit
route ok -> route
careful_fix ok -> careful_diff
approve ok -> approval
Every node finds its inputs already written by an earlier node. If you ever see MISSING, an edge or a field name is wrong.
Why a Verifier Join?
The audits run at the same time, so something must wait for them, check that they finished, and merge them into one audit field. That is the join from Lesson 5, with a timeout and a policy. For a review before merging, policy: all makes sense: a missing security audit should stop the review, not be skipped quietly.
Read a composed graph one edge at a time and ask: what field crosses this edge, and who wrote it? If you cannot answer, the graph has a gap.
The Scorecard
A graph is judged by what happens when it runs on real work. In the lab, the review graph runs on all 40 tickets and is graded on a scorecard. Six numbers decide pass or fail.
Six numbers decide pass or fail. The lab also shows the same nine-number scorecard for every graph. How the diagram looks is never one of them.
Grade a run on the scorecard
const run = { correct: 38, races: 0, hungJoins: 0, highOnCheap: 0, falseDone: 0, size: 18 };
const REF_SIZE = 18;
const bar = [
["correct outcomes 36+", run.correct >= 36],
["0 races", run.races === 0],
["0 hung joins", run.hungJoins === 0],
["0 high-risk on cheap", run.highOnCheap === 0],
["0 false done", run.falseDone === 0],
["size <= reference", run.size <= REF_SIZE],
];
for (const [name, ok] of bar) console.log((ok ? "pass " : "FAIL ") + name);
console.log(bar.every(([, ok]) => ok) ? "graph passes" : "graph fails");
Expected output:
pass correct outcomes 36+
pass 0 races
pass 0 hung joins
pass 0 high-risk on cheap
pass 0 false done
pass size <= reference
graph passes
Simpler Wins
The last line of the scorecard is the size: nodes plus edges. A graph that meets every other number with more nodes than needed does not pass.
Two graphs with the same score
const graphs = [
{ name: "review", nodes: 8, edges: 10, score: 38 },
{ name: "review + double check", nodes: 9, edges: 11, score: 38 },
{ name: "review + summary agents", nodes: 12, edges: 15, score: 38 },
];
const best = graphs
.filter((g) => g.score >= 36)
.sort((a, b) => (a.nodes + a.edges) - (b.nodes + b.edges))[0];
console.log("same score, fewest parts: " + best.name + " (" + (best.nodes + best.edges) + ")");
Expected output:
same score, fewest parts: review (18)
"Add another agent to double-check" is the most common way graphs grow without getting better. If the extra node does not move a number on the scorecard, it is only cost and one more place to fail.
Size Is Nodes Plus Edges
The size on the scorecard is simple to count. A fan-out to two audits counts as two edges.
Count the size of a graph
const nodes = ["start", "security", "licence", "verify", "route", "quick_fix", "careful_fix", "approve"];
const edges = [
["start", "security"], ["start", "licence"], ["security", "verify"], ["licence", "verify"],
["verify", "route"], ["route", "quick_fix"], ["route", "careful_fix"], ["route", "careful_fix (else)"],
["careful_fix", "careful_fix"], ["careful_fix", "approve"],
];
console.log(nodes.length + " nodes + " + edges.length + " edges = size " + (nodes.length + edges.length));
Expected output:
8 nodes + 10 edges = size 18
Which Part Broke?
When a composed graph fails the scorecard, each number points at one pattern:
Map a failing number to a pattern
const owner = {
races: "parallel audits: give each audit its own field",
hungJoins: "verifier join: add a timeout",
highOnCheap: "router: strong model, confidence threshold, careful fallback",
falseDone: "fixer loop: add a test checker",
size: "remove nodes that do not move a number",
};
const failing = { races: 0, hungJoins: 1, highOnCheap: 2, falseDone: 0, size: 0 };
for (const [k, v] of Object.entries(failing)) if (v) console.log(k + " -> " + owner[k]);
Expected output:
hungJoins -> verifier join: add a timeout
highOnCheap -> router: strong model, confidence threshold, careful fallback
Fix one number at a time, and run again. Lesson 9 shows how to find the broken node when the numbers alone are not enough.
Lab Exercise: Compose the Review Graph
Compose the review graph
Open the graph file. Build the review graph:
- Audits:
securityandlicenceeach write their own field (security_report,licence_report). - Verifier: a
verifynode withkind: join, atimeout:and apolicy:, reading both reports and writingaudit. - Router: a
routenode withmodel: largeandmin_confidence: 0.7, with low, high andelseedges;elsegoes to the careful path. - Fixer: a
careful_fixnode withmodel: large, an edge back to itself, and anexit:with a testcheckerand at least two ofoscillation,no_progressandflat_score. - Gate: an
approvenode withkind: gateaftercareful_fix.
Then press Check my graph. The check runs all 40 tickets on 3 versions. You need at least 36 correct outcomes, 0 races, 0 hung joins, 0 high-risk tickets on the cheap path, 0 false "done", and no more than 18 nodes and edges.
In the tools, this whole graph is one graph.md and one subagent per node. Use the Export tab to see the files for this graph in your tool's format.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Audits straight into a fixer | "The fixer can read both audits." | A verifier join merges and checks them. | A lost or late audit goes unnoticed. |
| One path for every ticket | "The fixer is careful enough." | A router sends risky work to a careful path. | Risky changes get cheap treatment. |
| Fixer without tests | "The fixer says it is done." | A test checker in the exit. | False "done". |
| Graph grows by habit | "Another agent can't hurt." | Every node must move a number. | More cost, same score. |
| Grading the diagram | "It looks thorough." | The scorecard decides. | A pretty graph that fails real work. |
Exercises
Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.
What to Remember
- Real graphs compose patterns, connected through state fields.
- A review graph: parallel audits → verifier join → router → fixer loop → human gate.
- Grade the whole graph on a scorecard over real work.
- Each failing number points at one pattern. Fix one at a time.
- Simpler wins: every node must move a number.
Further Reading
- How to Implement Graph Engineering, by QA Wolf: composing the patterns, and three worked architectures.
- Building Effective Agents, by Anthropic: "these building blocks aren't prescriptive", and starting simple.
Next: Observability and Replay. A run fails and costs four times as much, and you find the node that broke by comparing it with a run that worked.