Skip to main content

Transitions: Wire Nodes Together

Transitions define how the conversation moves from one node to another. They are the edges of your graph connecting nodes into a complete conversation flow. The graph engine follows these transitions deterministically, so the path is always predictable and auditable.

Linear Transitions

Use add_transition() to chain nodes in a fixed sequence. The conversation will move through each node in the order you specify, from START to END.

graph.add_transition(START, "welcome", "collect_name", "collect_email", "confirm", END)
START -> welcome -> collect_name -> collect_email -> confirm -> END

Conditional Transitions

Use add_conditional_transition() when a node needs to route to different targets based on runtime state. This lets you create branching logic. For example, approving or rejecting a loan based on the user's credit score.

1. The source node sets a state field:

async def credit_decision_node(state, ctx):
score = state.credit_score or 0
if score >= 700:
return {"decision": "pending_docs"}
elif score >= 600:
return {"decision": "review"}
else:
return {"decision": "rejected"}

2. The transition reads that field and routes:

graph.add_conditional_transition(
"credit_decision",
mapping={
"pending_docs": "document_check",
"review": "manual_review",
"rejected": "reject",
},
decide=lambda state: state.decision,
)
                        decide(state) returns:
+-- "pending_docs" --> document_check
credit_decision --------+-- "review" --> manual_review
+-- "rejected" --> reject
ParameterTypeDescription
from_nodestrThe source node. decide runs after this node completes.
mappingdict[str, str]Maps decision keys to target node names.
decideCallable(state) -> strReturns one of the keys from mapping.

If decide returns a key not in mapping, an InvalidTransitionError is raised.

Registering Nodes

Before you can wire transitions, every node function must be registered with the graph. You can use add_node() for standard nodes, or the convenience methods add_start_node() and add_end_node() which automatically create the START or END transitions for you.


graph.add_node("name of node", func name)

graph.add_node("collect_name", collect_name_node) # standard node
graph.add_start_node("welcome", welcome_node) # add_node + add_transition(START, name)
graph.add_end_node("farewell", farewell_node) # add_node + add_transition(name, END)

GraphConfig

GraphConfig controls the runtime behavior of your graph including logging, retry limits, hangup timing, and persistence. Pass it when creating a ConversationalGraph instance to customize how the engine operates.

config = GraphConfig(
name="loan_assistant",
graph_id="loan_v1",
debug=True,
max_retries=5,
hangup_delay=4.0,
checkpointer=my_saver,
)
graph = ConversationalGraph(LoanState, config)
OptionTypeDefaultDescription
namestr"conversational_graph"Human-readable name for logs
graph_idstrUUIDUnique version identifier
debugboolFalseEnable verbose logging
max_retriesint10Max Interrupt retries per node
hangup_delayfloat4.0Seconds before hangup after graph ends
checkpointerBaseCheckpointerInMemorySaver()Persistence backend
streamboolFalseWhether to give runtime updates of each node

Graph Methods

Once your graph is built and compiled, you can use these methods to inspect its topology, manage state, and control execution at runtime.

MethodDescription
graph.compile(user_config)Initialize runtime. Must be called before processing input. Agent is set automatically by the pipeline.
graph.visualize()Print graph topology.
graph.get_graph_status()Print current state, active node, pause status.
graph.update_states(updates)Apply state updates and save a checkpoint.
graph.get_states()Return dict of all state values.
graph.get_nodes()Return list of node names.
graph.get_transitions()Return dict of all edges.

Got a Question? Ask us on discord