NVIDIANVIDIA-Certified Professional: Agentic AI2026-101.5 Orchestrate multi-agent workflows and coordination.
Coordinate specialist execution through explicit outcomes
Completed by the knowledge check
Learning objective
Coordinate specialist agents: handoffs, shared state, cancellation, and failure propagation.
Core idea
Execution semantics define what happens when specialists overlap, fail, or stop. Distinguish control transfer, state updates, cancellation, and failure reporting.
For a production workflow, decide which results are required before synthesis. NeMo Agent Toolkit’s parallel executor can include branch failures as text alongside successful outputs. That is useful for partial analysis, but a downstream model can otherwise turn an incomplete set of results into a confident answer. The coordinator must preserve the difference between a completed assessment and an unavailable assessment.
How it works
Transfer control with the required context
In LangChain’s handoffs pattern, a tool updates a routing field such as active_agent. With separate agent subgraphs, Command combines goto for the destination, update for state changes, and graph=Command.PARENT for navigation through the parent graph. The next specialist receives the context selected by that transition.
A message-based handoff must preserve a valid tool exchange. The documented subgraph example passes both the AIMessage containing the handoff tool call and a corresponding ToolMessage; its tool_call_id matches the call. Passing the tool call without its response can produce invalid history and downstream errors. The example assumes no parallel tool calls, so do not copy its two-message shortcut into a transition with several outstanding calls without accounting for them.
Choose the context deliberately. Send the task, relevant findings, and unresolved questions rather than every specialist’s internal conversation. The handoffs documentation warns that forwarding full histories can confuse recipients and inflate tokens. Tenant memory retention belongs to 1.4.2.
Merge state through defined update rules
LangGraph defines shared execution state through a schema and per-key reducers. Nodes return partial updates. With the default reducer, an update replaces the previous value; with a custom reducer such as operator.add, updates can accumulate a list. The add_messages reducer appends messages and updates existing messages by ID.
Choose merge semantics for what the field represents. Accumulate specialist findings, but give a routing field one decision owner. Appending incompatible eligibility decisions does not resolve them; a coordinator node must choose the authoritative outcome.
When a subgraph updates a parent-shared key through Command.PARENT, the graph documentation requires a reducer for that key in the parent state. Also avoid mixing Command(goto=...) with a static outgoing edge from the same node: the static edge still runs, potentially starting another specialist unintentionally. See 1.6.3 for graph design and 5.4.2 for durable checkpoints; this lesson concerns the exchange between specialists within an execution.
Make partial failure explicit
NeMo Agent Toolkit’s workflow._type: parallel_executor fans out the same input to the functions in tool_list. It runs those branches concurrently and appends their outputs as separate text blocks in configured order. It requires the nvidia-nat[langchain] plugin.
Its return_error_on_exception setting controls the returned failure representation. With true, the executor captures branch exceptions and appends ERROR: blocks. With false, the documented default, it raises the first branch exception. Set detailed_logs: true when branch-start, completion, and fan-in logs are needed to diagnose which specialist failed.
Use the text-error mode when partial results are useful and the next stage explicitly handles missing assessments. For a required specialist, a completed fan-in containing an ERROR: block is insufficient to authorize the final action. In a LangGraph coordinator, use add_conditional_edges after the fan-in node with a routing function that checks required specialists’ blocks for ERROR:. Route missing required assessments to an incomplete-outcome node and complete assessments to synthesis. The routing function implements business eligibility; return_error_on_exception chooses the failure representation.
A fan-in that contains an ERROR: block for a required specialist routes to the incomplete-outcome node, not to synthesis.
A ReAct supervisor also needs an explicit specialist-error policy. Its pass_tool_call_errors_to_agent defaults to true, turning a specialist tool-call exception into an observation the supervisor can respond to; false raises the exception to the caller. A specialist configured with raise_on_parsing_failure: false returns parsing-error text as its answer, so its supervisor can receive an ordinary tool result that still represents failure. With the default true, exhausted parsing retries raise ReActAgentParsingFailedError. See 1.2.2 for these loop controls; here the decision is whether a required specialist’s failure reaches the coordinator as an exception or as text that needs checking.
Give cancellation a runtime owner
For custom Python coordination, asyncio.TaskGroup provides structured concurrency: when a child raises an exception other than CancelledError, it cancels the remaining tasks, waits for them, and raises grouped failures through ExceptionGroup or BaseExceptionGroup. In contrast, asyncio.gather() with its default return_exceptions=False propagates the first exception while other awaitables continue running.
This distinction matters when wrapping specialist calls. Receiving an exception does not by itself prove that siblings stopped. Use TaskGroup when all branches form one required computation and sibling failure should end the others. For independent optional analyses, collecting individual outcomes can preserve useful work instead.
Cancellation is cooperative. Task.cancel() requests cancellation; CancelledError is raised at the next opportunity. Coroutines should use try/finally for cleanup and normally propagate CancelledError after cleanup. Swallowing it can break structured-concurrency behavior. An asyncio.timeout() scope uses cancellation internally and exposes TimeoutError outside the scope, providing a concrete deadline scope for a custom coordinator. Cleanup can extend observed completion beyond that deadline; cancellation is not an instantaneous stop.
These are Python runtime semantics, separate from the parallel executor’s return_error_on_exception switch. Test the actual wrapper’s cancellation path. User-facing overrides and cancellation acknowledgments are covered in 10.4.2.
Key distinctions and trade-offs
The lifecycle decisions have different mechanisms and consequences:
| Decision | Mechanism | What it establishes | What still needs a decision |
|---|---|---|---|
| Handoff | Command(goto, update, graph=Command.PARENT) | Destination and transferred state | Whether the recipient has sufficient context |
| Shared update | State schema and per-key reducer | How findings combine | Who resolves conflicting findings |
| Supervisor tool failure | pass_tool_call_errors_to_agent | Error observation or raised exception | Whether the required specialist actually succeeded |
| Partial failure | return_error_on_exception: true | Successful outputs plus ERROR: blocks | Whether missing results permit a final answer |
| Fail-fast execution | Python asyncio.TaskGroup | Sibling cancellation after a child failure | Cleanup and interpretation of grouped errors |
| Deadline | Python asyncio.timeout() | Cancellation-backed time scope | Which incomplete outcome the caller receives |
Fail-fast reduces wasted local work when every branch is mandatory. Partial-result collection preserves availability when some branches are optional. Neither choice repairs an invalid tool history or an ambiguous state merge.
Architecture / decision scenario
A manufacturer’s procurement assistant compares a supplier’s price and mandatory export-eligibility assessment. An optional delivery-risk specialist adds context. The application deadline is eight seconds. In a hypothetical incident, price would take five seconds, export eligibility fails after three, and delivery analysis would take seven.
The tempting design is NVIDIA’s own composition: a sequential_executor with tool_list: [parallel_analysis, final_synthesis_agent], where parallel_analysis is a parallel_executor with return_error_on_exception: true, so the concatenated text, ERROR: blocks included, goes straight to synthesis. The procurement workflow owner rejects it: a price and an ERROR: block do not establish export eligibility, and sequential_executor documents no branch. The replacement coordinator is a LangGraph graph whose fan-in node collects the three results and whose add_conditional_edges routing function sends the missing required assessment to the incomplete-outcome node, preventing a purchase recommendation.
For a custom Python implementation, put the required price and eligibility calls in a TaskGroup within asyncio.timeout(8). At three seconds, eligibility failure cancels the still-running price task; cleanup runs in finally, and grouped errors reach the coordinator. With default gather(), the price task instead continues toward its five-second completion. The coordinator collects optional delivery analysis separately within the same eight-second deadline: it attaches the result if it completes and reports it as unavailable otherwise. Delivery context cannot turn the failed eligibility assessment into permission to recommend a purchase.
If a sourcing specialist hands control to an export specialist, use the declared Command destination and carry a valid tool-call/response pair. Store their findings through the parent state’s reducers, with export eligibility owned by its designated specialist. Safe completion requires correct handoff context, unambiguous state, observed cancellation, and visible failure.
Common exam traps
- Treating an
ERROR:text block as successful completion of a required assessment. - Assuming an exception raised to the caller automatically cancels every sibling task.
- Suppressing
CancelledErrorand expecting structured cancellation to behave normally. - Sending a handoff tool call without its matching
ToolMessage. - Adding a static edge beside
Command(goto=...)and expecting only one destination to run.
What to remember
Make control transfer, state merging, stopping, and error reporting explicit. Choose partial-result or fail-fast semantics according to which assessments the business requires. The next specialist needs valid context, and the final coordinator needs outcomes that preserve missing work rather than disguising it as a completed answer.
Knowledge check
A procurement workflow runs price, export_eligibility, and an optional delivery_risk specialist in a NeMo Agent Toolkit parallel_executor with return_error_on_exception: true, so that a delivery-risk failure still lets the other results through. The workflow must never recommend a purchase without a completed export-eligibility assessment. What should follow the fan-in?
return_error_on_exception: false on the parallel executorERROR: block to an incomplete outcomesequential_executor that passes the combined text to a synthesis agentreturn_error_on_exception: true , a failed branch arrives as an ERROR: text block beside the successful outputs, so the fan-in completes even when the required assessment is missing; a routing function after the fan-in turns that into an incomplete outcome before synthesis sees it. Switching to false raises the first branch exception, including a delivery-risk failure the workflow must tolerate, and a sequential executor documents no branch at all.A custom Python coordinator runs two required specialist calls, price and eligibility, with asyncio.gather() and its default arguments. When eligibility fails after three seconds, the coordinator receives the exception, but logs show the price call keeps running to completion. Because both results are required, the remaining call should be cancelled as soon as either fails. Which change achieves that?
asyncio.TaskGroupasyncio.gather() with return_exceptions=Truegather() call in an asyncio.timeout() scopeCancelledError inside each call and return a fallback valueTaskGroup cancels its remaining tasks when a child raises an exception other than CancelledError , waits for them, and groups ordinary failures in an exception group. Default gather() propagates the first exception while the other awaitables keep running; a timeout scope cancels at its deadline, not when a sibling fails; return_exceptions=True only collects outcomes, which suits optional analyses, and swallowing CancelledError can break the structured cancellation the fix depends on.A sourcing specialist hands control to an export specialist through a handoff tool that returns Command(goto="export_agent", update=..., graph=Command.PARENT). The update passes the AIMessage containing the handoff tool call, and the export specialist then fails with errors about its message history. No other tool calls are outstanding. What is missing from the update?
recursion_limit in the parent graph’s configToolMessage whose tool_call_id matches the handoff callToolMessage to complete the exchange; without it the history is malformed. Forwarding more conversation does not supply the missing tool response, and adding a static edge beside Command adds a route rather than repairing the message history.In a LangGraph coordinator, the triage node returns Command(goto="fraud_review") for suspicious cases. An earlier version also left graph.add_edge("triage", "standard_review") in place. For a suspicious case, what runs after triage?
fraud_review onlyfraud_review and standard_reviewstandard_review onlyCommand adds a dynamic edge and does not cancel static edges already defined on the same node, so both destinations run. Use one routing mechanism per node: remove the static edge and let Command , or a conditional edge, choose the single next specialist.References
- NVIDIA, Configure the ReAct Agent
- NVIDIA, Configure the NVIDIA NeMo Agent Toolkit Parallel Executor
- LangChain, Handoffs
- LangChain, Graph API overview
- Python Software Foundation, Coroutines and Tasks