5. When a node fails¶
Keyword rules are reliable but dull. A language model reads the ticket better: it can tell the topic, the urgency and the mood. It can also be unreachable, rate-limited, or refused for lack of a key.
That is not a reason to put a try in Python. A failure here is a fork in the
graph like any other: if the model cannot answer, fall back to the rules.
Retry belongs to the node¶
Some failures are worth simply repeating. Rate limits, for example:
{"id": "triage", "type": "stage", "stage": "LlmTriageStage",
"arguments": {"vars": {"text": "text", "subject": "subject", "api_key": "OPENAI_API_KEY"}},
"outputs": {"topic": "topic", "urgency": "urgency", "summary": "summary"},
"retry": [{"error_equals": ["LlmRateLimited", "LlmUnavailable"],
"max_attempts": 3, "interval_seconds": 2}]}
max_attempts counts the first run too, so 3 means one attempt and two
retries. Any node can carry retry, not only stage nodes.
Try covers a region¶
A try node does not wrap a single node, it covers a piece of the graph:
everything reachable from body but not from next.
{"id": "guard", "type": "try", "body": "triage", "next": "gather",
"except": [{"error_equals": ["LlmAuthError"], "next": "rules", "result_var": "llm_error"},
{"error_equals": ["*"], "next": "rules", "result_var": "llm_error"}]}
error_equals takes a class name, a fully qualified path, or * for
anything. result_var puts the error into the frame as an object with type,
full_type, message and node, so a later node can see what went wrong.

The editor frames both regions: the body around triage, the handler around
rules.
Where a road ends¶
Both roads of the block end the same way. A road that simply stops
("next": null) hands control back to the try node, and the graph goes on
at the node's own next — whether that road was the body or a handler.
So the rules stage can either name where it continues or leave next out; in
both cases the graph carries on at gather:
{"id": "rules", "type": "stage", "stage": "ClassifyByRulesStage",
"arguments": {"vars": {"text": "text", "subject": "subject"}},
"outputs": {"topic": "topic", "urgency": "urgency"},
"next": "gather"}
A terminal inside the block is the exception: it ends the whole run, and
nothing after the block is executed.
What it looks like when it fires¶
Run the bot without a working key. The model stage fails, the handler catches it, and the rules take over:
stage_failed triage stage: LlmTriageStage, error: no OpenAI API key …
try_caught guard type: LlmAuthError, next: rules
node_enter rules
The path of the run, from the start to the reply:
start → load → guard → triage → rules → gather → customer → search → route → render → send → answered
The result is the same {'status': 'answered'} as before. The bot lost the
model's reading of the ticket and kept working.
Next: a graph inside a node.