The routing graph is the set of permitted handoffs between sub-agents. Each sub-agent carries an allowed-targets list; the union of those lists is the graph. It is validated when the design is saved and re-checked at run time — a handoff to a target outside the caller's list is rejected with route_forbidden.
The typical shape
A well-formed graph is usually not symmetric:
┌──────────────┐
│ triage/entry │ default sub-agent
└──────┬───────┘
│ TRANSFER (ownership moves)
┌──────▼───────┐
│ specialist │
└──────┬───────┘
│ DELEGATE (result comes back)
┌──────▼───────┐
│ helpers │
└──────────────┘
A triage or router sub-agent transfers to one owning specialist; specialists delegate to helpers. What you want to avoid is a full mesh of transfers, where every sub-agent can hand ownership to every other.
Two failure modes that validate cleanly
Unreachable specialists. A sub-agent no one can hand off to will never run. The design saves without error; the specialist is simply dead. Check that every sub-agent is reachable from the default one.
Oscillation. A→B→A ownership bouncing. The runtime issues correction nudges against immediate bounce-backs, but allows them through after a couple of nudges so graphs that genuinely need back-and-forth still work. Persistent oscillation burns the transfer budget and ends the run with transfer_limit_exceeded.
Dual-listing
The same peer may be listed for both transfer and delegation, but each edge should have a distinct role. Dual-listing "for flexibility" without a specific reason makes the model's routing choice less predictable, not more capable.