Observability and Replay
By the Syncfusion DevTutorials team · Last updated
When a graph run goes wrong, compare it with a run that worked. Find the first node that differs, read per-node tokens and verdicts, and see where the money goes.
Watch a Run Fail With No Clue Why
Last night's review run went wrong three times:
- a payment ticket was merged without anyone approving it;
- a GPL licence problem was missed;
- one run cost four times as much as usual and fixed nothing.
The final result of each run tells you that something broke, not where. In a graph, the cause is often several nodes before the place where you notice it.
Open the Inspector and look at the first failed run next to a run that worked on the same kind of ticket.
Compare a failed run with a working one
The merge at the end looks like the problem. It is not. Read the two logs from the top: the audits match, the verifier matches, and then the router gave a different answer. Everything after that is an effect.
TL;DR
- Log every node: what it read, what it wrote, its tokens, its time and its verdict.
- To debug, diff a failed run against a run that worked. The first node that differs is the cause. Later differences are effects.
- Keep run logs as files you can replay: step through a past run without paying for it again.
- Read cost per node. Cut cost where the tokens actually go.
- Set a spend cap per node for the expensive ones, so one node cannot eat the whole budget.
A Real-World Example
Dev's team has a five-node graph that answers support questions. One week, answers get worse. The team first rewrites the final "answer" prompt. No change. Then they rewrite it again.
Finally someone compares the logs of a bad answer and a good one, node by node. The first difference is in the search node: an index update had stopped returning the help-centre pages. The answer node was doing its best with the wrong input. Two days of prompt changes had been fixing the wrong node.
Now every run writes a small log line per node, and the first step of every bug is: diff it against a good run.
What You Will Learn
- What to log for every node
- How to diff a failed run against a working one
- Why the first difference is the cause, and the rest are effects
- How to read cost per node and cut it where it matters
- How replay saves you from paying for the same run twice
Cost and Finding the Node That Broke
A graph run is a sequence of node steps. If each step leaves a short log line, you can read the run like a story.
A per-node log
const log = [
{ node: "security", tokens: 3100, seconds: 42, verdict: "0 issues" },
{ node: "licence", tokens: 2600, seconds: 35, verdict: "1 GPL package flagged" },
{ node: "verify", tokens: 900, seconds: 4, verdict: "2 of 2 audits" },
{ node: "route", tokens: 600, seconds: 2, verdict: "high (0.91) -> careful_fix" },
{ node: "careful_fix", tokens: 7400, seconds: 180, verdict: "pass after 3 tries" },
];
for (const l of log) console.log(l.node.padEnd(12) + String(l.tokens).padStart(6) + " tok " + String(l.seconds).padStart(4) + " s " + l.verdict);
const total = log.reduce((s, l) => s + l.tokens, 0);
console.log("total: " + total + " tokens");
Expected output:
security 3100 tok 42 s 0 issues
licence 2600 tok 35 s 1 GPL package flagged
verify 900 tok 4 s 2 of 2 audits
route 600 tok 2 s high (0.91) -> careful_fix
careful_fix 7400 tok 180 s pass after 3 tries
total: 14600 tokens
Five things per node are enough to start: the node, its inputs (which fields it read), its tokens, its time and its verdict. In a graph pack from this course, the supervisor appends one such line per node to graph-state.json.
Diff Two Runs
Read both runs from the top. The first row that differs is the cause; everything below it follows from it.
Find the first node that differs
const working = [
["security", "0 issues"], ["licence", "1 GPL package"], ["verify", "2 of 2"],
["route", "high (0.91)"], ["careful_fix", "pass"], ["approve", "waiting"],
];
const failed = [
["security", "0 issues"], ["licence", "1 GPL package"], ["verify", "2 of 2"],
["route", "low (0.62)"], ["quick_fix", "pass"], ["merge", "merged"],
];
const first = failed.findIndex((row, i) => row[0] !== working[i][0] || row[1] !== working[i][1]);
console.log("first difference: " + failed[first][0] + " (" + working[first][1] + " vs " + failed[first][1] + ")");
console.log("effects after it: " + failed.slice(first + 1).map((r) => r[0]).join(", "));
Expected output:
first difference: route (high (0.91) vs low (0.62))
effects after it: quick_fix, merge
The router said "low" with a confidence of 0.62. That is the cause. Fixing the merge step, or the quick fixer, would change nothing.
The node where you notice a problem is usually not the node that caused it. A bad merge, a wrong answer or a high bill is where the trouble shows up. Always walk the log from the top.
Replay
A replay steps through a past run's log without running any node again. You can scrub back and forth, read what each node saw, and compare it with another run. It costs nothing, because it only reads files.
That is what the Run tab in this lab does: every scenario's events are a log you can step through and scrub. Each node shows a live badge with its tokens, steps and tries.
Your own runs are logged too. Run a graph twice, for example before and after a fix. Open the Inspector. Under Your runs, pick run A and run B. The lab shows the first node where they differ.
Replay a Log Step by Step
A replay is just a loop over the saved log, with a position you can move:
Replay to a position
const log = ["security: 0 issues", "licence: timed out", "verify: 1 of 2 (partial)", "route: high", "careful_fix: pass"];
function replay(log, upTo) {
return log.slice(0, upTo).map((line, i) => (i + 1) + ". " + line).join("\n");
}
console.log(replay(log, 3));
console.log("(no node ran again: 0 tokens)");
Expected output:
1. security: 0 issues
2. licence: timed out
3. verify: 1 of 2 (partial)
(no node ran again: 0 tokens)
Where the Tokens Go
The same log answers a second question: where does the money go?
Where the tokens go
Approximate tokens for 40 tickets. The fixers spend the most; the router and verifier are cheap.
Cost by node
const spend = { careful_fix: 46800, quick_fix: 37200, security: 28000, licence: 28000, route: 12000, verify: 6000 };
const total = Object.values(spend).reduce((a, b) => a + b, 0);
const rows = Object.entries(spend).sort((a, b) => b[1] - a[1]);
for (const [node, t] of rows) console.log(node.padEnd(12) + String(Math.round((t / total) * 100)).padStart(3) + "%");
Expected output:
careful_fix 30%
quick_fix 24%
security 18%
licence 18%
route 8%
verify 4%
Two lessons from this table:
- Cut where the tokens are. Shortening the router's prompt saves almost nothing. Sending one fewer ticket to the careful fixer saves a lot. A better router is often the cheapest way to cut cost.
- Parallel branches multiply shared context. Every audit reads the ticket. Ten audits read it ten times. Give each branch only the fields it needs.
A Spend Cap per Node
A single stuck node can eat the whole run's budget, as Lesson 7 showed. Give the expensive nodes their own budget: in exit:, and the run a total limit.
Which node would break a per-node cap?
const caps = { careful_fix: 12000, quick_fix: 3000 };
const runs = [
{ node: "careful_fix", ticket: 25, tokens: 7400 },
{ node: "careful_fix", ticket: 13, tokens: 30100 },
{ node: "quick_fix", ticket: 21, tokens: 2100 },
];
for (const r of runs) {
const over = r.tokens > caps[r.node];
console.log("#" + r.ticket + " " + r.node + ": " + r.tokens + (over ? " OVER CAP -> stopped on bound: budget" : " ok"));
}
Expected output:
#25 careful_fix: 7400 ok
#13 careful_fix: 30100 OVER CAP -> stopped on bound: budget
#21 quick_fix: 2100 ok
All token numbers in this lab are approximate. They are there to show where cost goes and how it changes, not to predict your bill. Your tool's own usage report gives the real numbers for a real run.
Lab Exercise: Find the Node That Broke
Open the Inspector tab. It shows three failed runs. Each one sits next to a run that worked on the same kind of ticket.
Find the node that broke
For each failed run:
- Read both logs from the top.
- Find the first row that differs.
- Press This broke on that row.
You have 2 tries per run. When all three are done, press Check my answers.
The graph pack you export gives your real runs the same log: the supervisor writes one line per node into graph-state.json, under run_log. Diff two of those logs exactly as you do in this lab.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Fixing the last node | "The merge was wrong, so fix the merge." | The first difference is the cause. | You fix an effect; the bug stays. |
| No good run to compare | "I'll read the failed log carefully." | Diff it against a run that worked. | Normal behaviour looks suspicious, and the real cause hides. |
| No per-node log | "The final result is enough." | One line per node. | You cannot tell where it went wrong. |
| Cutting cheap nodes | "Every token counts the same." | Cut where the tokens go. | Effort spent, bill unchanged. |
| Re-running to debug | "Run it again and watch." | Replay the log. | You pay again, and the run may not repeat. |
Exercises
Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.
What to Remember
- Log node, inputs, tokens, time and verdict for every step.
- Diff a failed run against a good one. The first difference is the cause.
- Replay reads the log, so debugging costs nothing.
- Read cost per node and cut where the tokens go.
- Give expensive nodes their own spend cap.
Further Reading
- How to Implement Graph Engineering, by QA Wolf: tracking token budgets and spotting rising cost with a flat score.
- Building Effective Agents, by Anthropic: keep agent systems simple and transparent, so you can see each step.
Next: Human Gates. A payment change is merged with no one looking, and you put a person on the risky path, with the evidence they need.