Routing

By the Syncfusion DevTutorials team · Last updated

Send each ticket down the right path: a cheap path for easy work, a careful path with a human gate for risky work, and a safe fallback when the router is unsure.

Watch a Payment Change Take the Cheap Path

Forty tickets are waiting. Most are small: a docs fix, a version bump, a button. A few touch logins, payments or the database.

A graph routes them. A router node reads each ticket and says "low risk" or "high risk". Low-risk tickets go to a quick fix with a small model. High-risk ones go to a careful fix with a large model, then to a person for approval.

Run it and look for red lines.

A small router and a cheap fallback

Some auth and payment tickets were called "low risk" and took the quick path. Nothing crashed. The tests passed. Each one completed successfully, down the wrong path. That is how routing fails: quietly.

TL;DR

  • Routing sends each item down the path that matches its risk or difficulty. What it buys: cost matched to risk.
  • The router is often the weakest node. A misrouted item finishes without an error.
  • Check the router first on labelled examples. Aim for 90% or better.
  • Give it a safe fallback: a when: else edge to the careful path.
  • Send unsure items to the fallback with a confidence threshold.
  • Don't route everything to the careful path. Safe, but it costs as much as having no router.

A Real-World Example

Sam's team routes support tickets. A small model labels them: "billing", "bug", "question". Billing goes to a careful agent that can issue refunds only with a person's approval.

One week, refunds go out wrong. The cause: tickets that said "charged twice" were labelled "bug" by the small router and went to the bug path, which could change code but had no billing checks.

The team did three things. They tested the router on 200 old tickets with known labels: it was right 81% of the time. They switched it to a larger model: 94%. And they added a rule: when the router is unsure, the ticket goes to the careful path. Wrong refunds stopped.

What You Will Learn

  • What routing buys, and how it fails
  • How to write conditional edges and a safe fallback
  • How to check a router on labelled examples
  • How a confidence threshold catches the router's mistakes
  • How to balance safety against cost

By Risk and Difficulty

A router looks at an item and picks the next edge. In the graph file, the edges from a router have a when: condition:

edges:
  - { from: route, to: quick_fix, when: risk == low }
  - { from: route, to: careful_fix, when: risk == high }
  - { from: route, to: careful_fix, when: else }
  - { from: careful_fix, to: approve }

Low-risk work takes the cheap path. High-risk work takes the careful path and ends at a human gate. Anything unsure falls back to the careful path.

The two paths differ in what matters: the model (small or large), the checks (tests only, or tests plus review) and the ending (merge, or a person's approval). Routing is worth it only when the paths really differ like this.

The risk often comes from the area of the code:

Risk from the area of the code

const HIGH = ["auth", "payments", "migration"];
const tickets = [
  { id: 21, area: "docs" },
  { id: 22, area: "auth" },
  { id: 25, area: "payments" },
  { id: 27, area: "auth" },
  { id: 29, area: "migration" },
  { id: 30, area: "deps" },
];

const risk = (t) => (HIGH.includes(t.area) ? "high" : "low");
const counts = { low: 0, high: 0 };
for (const t of tickets) counts[risk(t)]++;
console.log(tickets.map((t) => "#" + t.id + " " + risk(t)).join(", "));
console.log("low: " + counts.low + ", high: " + counts.high);

Expected output:

#21 low, #22 high, #25 high, #27 high, #29 high, #30 low
low: 2, high: 4

But in real work, the router does not get the area for free. It reads the ticket text and guesses. That guess can be wrong.

Label the Tickets Yourself

Before a router labels anything, try it yourself. This is what a router does for every ticket, all day.

You probably got most right, and maybe one or two wrong. A router makes the same kind of mistakes, and you measure it the same way: against known answers.

Check the Router First

Before you trust a router, test it on labelled examples: old tickets where you already know the right answer. Count how often it agrees.

Test a router on labelled tickets

const labelled = [
  { text: "Fix typo in README",              truth: "low" },
  { text: "Charge customers twice on retry", truth: "high" },
  { text: "Bump vite to 5.4.8",              truth: "low" },
  { text: "Session not cleared on logout",   truth: "high" },
  { text: "Align filter buttons",            truth: "low" },
];

// A weak router: only looks for a few scary words.
const weakRouter = (text) => (/password|payment|database/i.test(text) ? "high" : "low");

let right = 0;
for (const t of labelled) if (weakRouter(t.text) === t.truth) right++;
console.log("right on " + right + " of " + labelled.length + " = " + Math.round((right / labelled.length) * 100) + "%");
for (const t of labelled) if (weakRouter(t.text) !== t.truth) console.log("missed: " + t.text);

Expected output:

right on 3 of 5 = 60%
missed: Charge customers twice on retry
missed: Session not cleared on logout

This weak router missed both risky tickets. "Charge twice" and "session not cleared" are high risk, but they contain none of its words. If you only watched it run, every ticket would finish "successfully".

A misrouted item does not fail loudly. It completes, the tests pass, and it is merged. The only way to see routing mistakes is to measure the router against known answers before you trust it.

In the lab, the router is checked on the 40 labelled tickets before each run. A small model is right about 80% of the time. A large model is right about 93% of the time. For a router that decides how carefully a payment change is treated, the larger model is worth paying for.

A Safe Fallback

Even a good router is sometimes wrong. The fix is to know when it might be wrong. Routers can report a confidence: a number from 0 to 1 for how sure they are.

A good router is wrong mostly when it is unsure. Below the threshold, the ticket goes to the careful path.

Add min_confidence: to the router. Any ticket below it takes the else edge, and else must go to the careful path.

Route with a confidence threshold

const MIN = 0.7;
const predictions = [
  { id: 2,  truth: "high", says: "low",  conf: 0.58 },
  { id: 5,  truth: "low",  says: "low",  conf: 0.92 },
  { id: 13, truth: "high", says: "high", conf: 0.88 },
  { id: 27, truth: "high", says: "low",  conf: 0.64 },
];

function route(p) {
  if (p.conf < MIN) return "careful (unsure)";
  return p.says === "high" ? "careful" : "quick";
}

for (const p of predictions) {
  const path = route(p);
  const bad = p.truth === "high" && path === "quick";
  console.log("#" + p.id + " " + p.truth + " risk -> " + path + (bad ? "   MISROUTED" : ""));
}

Expected output:

#2 high risk -> careful (unsure)
#5 low risk -> quick
#13 high risk -> careful
#27 high risk -> careful (unsure)

Both wrong labels had low confidence, so both went to the careful path. Nothing risky took the quick path.

A fallback only works if the router is wrong when it is unsure. A small router is sometimes confidently wrong, and no threshold catches that. That is why the exercise needs both: a better router, and a fallback.

Safe, but Not Too Safe

You could set min_confidence: 0.99. Then almost every ticket falls back to the careful path. Nothing risky slips through. But now easy tickets pay the careful price too, and a person must approve every docs fix.

Safe, but everything is careful

That graph is as expensive as having no router at all, and it buries people in approvals they will start to click through without reading. Routing exists to match cost to risk. Keep the quick path busy with the work that belongs there.

Cost of three routing settings

const lowTickets = 31, highTickets = 9;
const QUICK = 1200, CAREFUL = 4000;

function cost(lowOnQuick) {
  const lowCareful = lowTickets - lowOnQuick;
  return lowOnQuick * QUICK + (lowCareful + highTickets) * CAREFUL;
}

console.log("no fallback (risky!):  " + cost(31).toLocaleString("en-US") + " tokens");
console.log("threshold 0.7:         " + cost(28).toLocaleString("en-US") + " tokens");
console.log("threshold 0.99:        " + cost(0).toLocaleString("en-US") + " tokens");

Expected output:

no fallback (risky!):  73,200 tokens
threshold 0.7:         81,600 tokens
threshold 0.99:        160,000 tokens

All Three Checks Together

The lab's check asks three things of a router graph at once. Here they are in code:

The three routing checks

const result = { accuracy: 0.93, highOnQuick: 0, lowOnQuick: 28, lows: 31 };

const checks = [
  ["router accuracy 90%+",         result.accuracy >= 0.9],
  ["0 high-risk on the quick path", result.highOnQuick === 0],
  ["half of low-risk on quick",     result.lowOnQuick / result.lows >= 0.5],
];
for (const [name, ok] of checks) console.log((ok ? "pass  " : "FAIL  ") + name);

Expected output:

pass  router accuracy 90%+
pass  0 high-risk on the quick path
pass  half of low-risk on quick

In Claude Code, Codex and Cursor, a router is usually the main agent following graph.md, choosing which subagent to call. Give the routing rules and the fallback in plain words in graph.md, and set a stronger model: on the careful subagent.

Lab Exercise: Make the Router Safe and Cheap

Make the router safe and cheap

Open the graph file. Change it so that:

  1. The router uses a stronger model: model: large.
  2. The router has min_confidence: 0.7.
  3. The when: else edge goes to careful_fix, not quick_fix.

Then press Check my graph. The check routes all 40 tickets on 3 versions, each with different router mistakes. You need: router accuracy of 90% or more, 0 high-risk tickets on the quick path, and at least half the low-risk tickets on the quick path.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Cheap router"Labelling is easy; use the smallest model."The router is the weakest node; give it a strong model.Risky tickets are called low risk.
Cheap fallback"Unsure tickets are usually easy."else goes to the careful path.Every router mistake becomes a risky merge.
Never testing the router"The runs look fine."Measure accuracy on labelled examples.Misroutes complete successfully and nobody notices.
Threshold too high"Safer is always better."Match cost to risk.Everything is careful, and approvals become rubber stamps.
Routing with identical paths"A router makes the graph smarter."Route only if the paths differ.Extra cost, no benefit.

Exercises

Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.

What to Remember

  • Routing matches cost to risk. It fails quietly: a misroute completes successfully.
  • The router is often the weakest node. Give it a strong model and measure it on labelled examples.
  • Every router needs a when: else edge to the careful path.
  • A confidence threshold sends the router's unsure guesses to the fallback.
  • Too much caution costs as much as no router. Keep easy work on the quick path.

Further Reading

Next: Parallelization. Three audit branches write the same field and one hangs forever, and you add one-writer fields, a timeout and a join policy.