MicrosoftMicrosoft Certified: Multi-Agent AI Solutions Expert2026-102.4 Implement multi-agent orchestration
Resume LangGraph work through explicit state
Completed by the knowledge check
Learning objective
Implement stateful LangGraph workflows with nodes, conditional edges, checkpoints, and interrupts.
Core idea
LangGraph makes a stateful agent workflow depend on explicit state rather than conversational convention. A node performs work and returns state updates. Edges determine the next work. A checkpointer records execution state, and an interrupt pauses execution for external input.
The critical implementation decision is what resumption repeats. A checkpoint does not mean that every line of Python or every external side effect executes exactly once. An interrupted node restarts from its beginning when resumed. Design the state, approval point, and side effects around that behavior.
See 2.4.6 for approval authorization and override policy. This lesson focuses on the LangGraph mechanisms that carry such a policy, including the difference between routing after a node and pausing inside it.
How it works
Define state and node updates
Construct StateGraph(State) with an explicit state schema. The Graph API accepts schemas such as TypedDict and Pydantic models. Register processing functions with add_node(name, function). A node receives state and returns an update; it does not have to return every field.
Each state key has a reducer that determines how updates combine with existing values. Without a custom reducer, an update replaces the field’s value. An accumulating field needs an explicit reducer, such as an Annotated field with a combining function. Do not expect a returned list to append automatically.
For conversation messages, add_messages appends messages with new IDs and updates messages whose IDs already exist. This differs from ordinary list addition, which can append a second copy instead of replacing a corrected message. Select the reducer according to the meaning of the state, not merely its Python container type.
Store a stable request ID, the proposed action, and its validated status as distinct application-defined fields. This makes a route depend on an explicit decision rather than a model’s unstructured explanation. A schema organizes that data; your application still owns which values are trusted.
Express normal and conditional control flow
Use add_edge(START, "intake") to identify the entry path and an edge to END to terminate a completed path. Ordinary add_edge(source, target) connects fixed stages.
Use add_conditional_edges(source, route_function, path_map) when the next stage depends on state. The routing function returns a node name or a routing value mapped to a node. Make accepted, rejected, and unresolved cases explicit in the routing contract.
Do not add an unconditional edge to a destination and assume a conditional route cancels it. Normal and conditional edges both contribute to execution. If the business rule requires exactly one branch, express that choice in the conditional route rather than creating a competing fixed route.
Command(update=..., goto=...) can combine state updates and routing when returned from a node. It is a different use of Command from resuming an interrupt. Use Command(resume=...) as the input when continuing an interrupted invocation; do not substitute a node’s routing command for the resume protocol.
Compile with a checkpointer
Call builder.compile(checkpointer=...) to attach checkpoint storage. A checkpointer records graph state at super-step boundaries. A super-step groups the nodes scheduled for one execution step, potentially in parallel.
Pass {"configurable": {"thread_id": THREAD_ID}} to graph invocation so the checkpointer knows which thread to save and load. Reusing a thread ID addresses its existing execution state. A different thread ID creates a separate thread rather than resuming the pending one.
InMemorySaver stores checkpoints in RAM and loses them when the process restarts. It is useful for demonstrations, but a restart-resilient design needs persistent storage, such as PostgresSaver. Do not equate attaching a checkpointer with choosing a durable backend. For Microsoft Foundry hosted agents, the durable state store can hold framework checkpoints, including LangGraph state; that store is preview and available only to hosted agents. Your agent explicitly writes those items, so it is not an automatically installed LangGraph checkpointer.
Checkpointing also records successful node writes within a super-step. If another node fails in that step, those pending writes support recovery without rerunning the successful nodes. This protects completed graph work; it does not turn an unrelated external service into a transactional participant.
Use graph.get_state(config) to inspect the saved StateSnapshot. Keep checkpoint inspection separate from a cross-thread memory store: the checkpointer owns a thread’s execution state, while a store holds application-defined information across threads.
Pause and resume with interrupts
Call interrupt(payload) inside the node that needs external input. The payload must be JSON-serializable. With the default invoke() API, the returned result exposes pending interrupts under __interrupt__. The caller reads the request, obtains an authorized decision, and invokes the graph again with Command(resume=decision) and the same thread configuration.
On resumption, the supplied decision becomes the return value of interrupt(). Execution starts again at the beginning of the interrupted node, so code before the interrupt executes again. Move irreversible work after the decision, separate it into a subsequent node, or make repeated pre-interrupt work idempotent. Resumption under the same thread re-enters the interrupted node from its beginning.
Do not wrap interrupt() in a broad try/except block that swallows its control-flow exception. Do not reorder multiple interrupt calls within a node: resume values match those calls by position. A code change that alters their order can pair a stored answer with the wrong question.
An interrupt waits until resumed; it is not an application timeout policy. Enforce waiting deadlines in the surrounding application as described in 2.4.6. Checkpoint storage preserves the pending work but does not authorize whoever submits the resume value.
Key distinctions and trade-offs
State update versus side effect: Returning a dictionary updates graph state through reducers. Sending a payment or publishing a record changes an external system. The latter needs its own repetition policy.
Conditional edge versus interrupt: A conditional edge chooses the next node after work returns. An interrupt suspends execution inside a node for input.
Thread versus store: A thread identifies checkpointed execution. A store supports information shared across threads. Neither identifier is proof of user authorization.
Memory versus durable persistence: InMemorySaver enables checkpoint semantics inside one process. Persistent checkpointers extend those semantics across process restarts, with storage operation and retention costs.
Architecture / decision scenario
A procurement graph drafts a purchase, checks policy, requests approval if required, and executes an accepted purchase. Define state fields for the request ID, proposal, policy outcome, and approval decision. Nodes return only their relevant updates.
After the policy node, add_conditional_edges() maps rejection to a terminal outcome, automatic acceptance to execution, and required review to an approval node. That node calls interrupt() with the proposed amount and request ID. Compile with a persistent checkpointer and invoke under the purchase’s stable thread_id.
The approval service verifies the approver and unchanged proposal, then submits Command(resume=...) using that same thread. The approval node re-executes its pre-interrupt code and records the decision. A later execution node performs the purchase using the request ID as the application’s deduplication key. If the worker restarts during approval, checkpoint recovery preserves the proposal; it does not create a fresh purchase or accept a decision for another thread.
Common exam traps
- Assuming node updates append to every list automatically.
- Treating an unconditional edge as canceled by conditional routing.
- Resuming with a different
thread_id. - Choosing
InMemorySaverfor process-restart persistence. - Performing a non-idempotent operation before
interrupt(). - Catching the interrupt’s control-flow exception.
- Confusing
Command(resume=...)input with a node’sCommand(goto=...).
What to remember
Use state schemas and reducers to define data meaning, nodes to produce updates, and edges to express control flow. Checkpointers preserve thread execution; interrupts require that persistence and a stable thread ID. Resumption repeats the interrupted node, so protect external side effects explicitly.
Knowledge check
Several LangGraph nodes return new items for an evidence list field. The field has no explicit reducer, but the application needs evidence to accumulate across nodes. What should change?
Command(update=...) insteadevidence fieldA policy node has a fixed edge to execution and a conditional route that sends rejected requests to END. A rejected request still reaches execution. Which routing from the policy node ensures that exactly one branch runs?
Command(goto=END) returned on rejectionCommand(goto=...) adds a path but does not cancel a fixed edge, whatever the registration order. A guard still runs the execution node. A mutually exclusive branch needs one routing mechanism with explicit destinations for every outcome.A purchase graph must preserve a pending approval across worker restarts and continue it when the approver answers. Which design meets that requirement?
InMemorySaver ; resume under the original thread IDCommand(goto=...)InMemorySaver loses its RAM state on restart, a new thread ID is separate work, and a store holds cross-thread information rather than a thread’s execution state. Continuation uses Command(resume=...) , not a node’s routing command.An approval node sends a non-idempotent external reservation before calling interrupt(). Resuming it creates a second reservation. Which redesign addresses the replay boundary?
Command(goto=...) in place of the approval valueinterrupt() runs again. Put irreversible work after the authorized decision, in a subsequent node, or make it idempotent. Catching the interrupt’s control-flow exception breaks the pause, and a new thread abandons the pending execution.A node requests two decisions through separate interrupt() calls. Pending executions exist when the developer reverses those calls’ order. Which behavior must the developer account for?
References
LangChain, Graph API
LangChain, Checkpointers
LangChain, Persistence
LangChain, Interrupts
Microsoft, Durable state store for Microsoft Foundry hosted agents (preview)