MicrosoftMicrosoft Certified: Multi-Agent AI Solutions Expert2026-102.4 Implement multi-agent orchestration
Terminate handoffs and group chats deliberately
Completed by the knowledge check
Learning objective
Implement peer-to-peer handoffs and group collaboration with termination conditions
Core idea
Peer handoff and group collaboration solve different coordination problems. A handoff lets the active agent transfer the conversation to another specialist. Group chat lets several participants contribute to a shared discussion while an orchestrator chooses the next speaker. Neither pattern makes completion inevitable.
You need an allowed routing map, a semantic completion condition, and an enforced limit for collaboration that does not converge. A pause for user input is another state, not evidence that the original task is complete. Keep those outcomes separate in the application’s response contract.
Microsoft Agent Framework provides HandoffBuilder and GroupChatBuilder in Python, with corresponding .NET builders. This lesson uses their documented controls rather than assuming every agent framework implements the same turn or termination semantics.
How it works
Permit specific peer transfers
Python HandoffBuilder(participants=[...]) registers the handoff mechanism. .with_start_agent(triage_agent) identifies the initial active participant. By default, all agents can hand off to each other; use .add_handoff(source, [targets]) to configure permitted transfers.
For example, an order agent can hand off to a return agent, which can hand off to a refund agent. This expresses an allowed control path. An agent deciding that another specialty would help cannot legitimately transfer to a target outside that configured path.
Handoff orchestration supports Agent participants that support local tool execution. It injects handoff tools so the active agent can transfer control through a tool call. The mechanism is not an arbitrary string in an assistant response naming another agent.
The .NET API uses AgentWorkflowBuilder.CreateHandoffBuilderWith(...), with WithHandoff() and WithHandoffs() configuring relationships. Keep language-specific identifiers distinct rather than translating a remembered method name into another SDK.
Set termination_condition on Python HandoffBuilder or call .WithTerminationCondition(...) on the .NET handoff builder to stop when the conversation meets your completion rule. The documented samples match substrings such as “welcome” or “RESOLVED”; prefer a validated completion outcome, as described in the termination section.
Handoff control does not imply private messages. Participants synchronize semantic conversation content even under custom routing rules; see 2.4.3 for communication enforcement and 2.1.5 for context handling. Configure the allowed transfers for responsibility changes, not as a substitute for data minimization.
Distinguish human input from autonomous continuation
Handoff is interactive by default. When the active agent answers without handing off, Python emits a WorkflowEvent with type="request_info" and a HandoffAgentUserRequest payload. The application supplies a response to continue the interaction.
Build that response with HandoffAgentUserRequest.create_response(user_input) and pass it through the workflow request/response mechanism. HandoffAgentUserRequest.terminate() lets the caller end the workflow early. User termination should remain distinguishable from the application’s verified successful completion.
By default, a user response returns to the agent that requested it. .enable_return_to_previous(False) sends user responses through the start agent instead, allowing triage to reevaluate the request. This setting governs input routing; it does not alter the specialist’s access rights or certify that a handoff was necessary.
Python autonomous handoff is documented as experimental; see 1.1.4 for the autonomy contract. .with_autonomous_mode() supplies continuation without waiting for the user when an agent does not hand off. It can target specific agents with agents=[...] and bound their autonomous turns with turn_limits={agent.name: 3}. Three is an example application choice, not a Microsoft service quota. The .NET handoff builder also supports WithAutonomousMode(turnLimit: ...).
A per-agent autonomous turn limit is not a whole-task completion test. An agent can reach that limit with unresolved work, and multiple agents can participate across a task. Use it alongside a workflow termination condition and a caller-owned deadline, rather than declaring that a bounded local loop proves the full task is bounded.
Coordinate group collaboration explicitly
GroupChatBuilder(participants=[...], selection_func=..., termination_condition=...).build() creates a managed discussion. A selector receives GroupChatState and returns the participant to speak next. Documented state includes participants, current_round, and conversation.
Use a deterministic selector when the review order is known. Use orchestrator_agent when an agent should choose the next speaker from the evolving discussion. The agent-based orchestrator is itself a full Agent with tools, context, and observability; routing intelligence adds work and another possible source of selection error.
Group chat participants receive synchronized history before their turns. They do not share the same session instance. The orchestrator broadcasts each response and then selects the next speaker. This differs from handing the conversation to a peer who owns the next interaction, and from concurrently asking independent workers for separate answers.
Set termination_condition for meaningful completion and enforce a hard bound with max_rounds on Python GroupChatBuilder. In .NET, pass a RoundRobinGroupChatManager with MaximumIterationCount to AgentWorkflowBuilder.CreateGroupChatBuilderWith(...); the documented sample uses five turns, not a universal maximum. A condition can inspect the conversation; the documented agent-orchestrator example counts assistant messages and stops after four. Four is that sample’s selected limit, not a universal group-chat maximum. Counting messages must be explicit about roles so user messages do not consume the intended assistant-turn allowance.
Make termination an enforced decision
A semantic stop condition should recognize a validated completion result from an authorized role. A raw substring such as “done” can appear in quoted evidence or an incomplete explanation. Prefer a normalized completion outcome that your application can validate, and keep source identity attached to the result.
A hard limit prevents further collaboration after the budget is consumed; it does not convert unfinished work into a correct answer. Return an incomplete or escalated outcome when that happens. The application must retain the reason for ending the run so the caller can distinguish success from exhaustion or cancellation.
For custom BaseGroupChatOrchestrator implementations in Python, configure participant_registry, max_rounds, and termination_condition on the orchestrator. The documentation says builder-level max_rounds and termination_condition are ignored in this case. Setting limits on the wrong object creates an apparently bounded workflow whose actual controller lacks those limits.
Output events are also not a substitute for termination. Python handoff defaults to surfacing participant responses as "output" events while supporting further interaction. Treat the event as a published response and inspect the workflow’s input and termination state before declaring the conversation closed.
Key distinctions and trade-offs
Handoff versus delegation: Handoff transfers conversation control. Agent-as-tool delegation returns a subtask result to the calling agent; see 2.4.5.
Group chat versus concurrency: Group chat builds an iterative shared conversation. Concurrent orchestration collects independent branches; see 2.4.2.
Semantic completion versus a limit: Completion means the requested result meets its contract. A turn limit only bounds work and can end with an incomplete result.
Interactive handoff supports clarification but needs a caller able to answer requests. Autonomous continuation avoids those pauses but can repeat unproductive work. Group discussion adds perspectives while increasing shared context and the need for explicit speaker and stopping policies.
Architecture / decision scenario
A support service starts with triage and permits order-to-return and return-to-refund transfers. It configures those paths with add_handoff instead of allowing an unrestricted peer mesh. A specialist can ask the user for missing information; the client handles HandoffAgentUserRequest rather than labeling the pause as resolved.
The application sends follow-up user input through triage by setting enable_return_to_previous(False). The handoff builder’s termination_condition ends the workflow when the responsible specialist supplies a validated application resolution outcome. User cancellation ends the workflow through terminate() with a separate cancelled outcome.
For a disputed policy interpretation, the team opens a group chat among policy, operations, and a reviewer. Its selector chooses the next relevant speaker, and the completion condition accepts the reviewer’s validated resolution. Python max_rounds ends unresolved discussions as escalation, rather than letting repeated agreement phrases substitute for a decision.
If the team supplies a custom BaseGroupChatOrchestrator, it places the registry and limits on that object. Experimental autonomous handoff is enabled only for triage with an explicit local turn limit; it does not replace group-chat termination or the service’s task deadline.
Common exam traps
- Confusing a handoff with calling a specialist as a function tool.
- Assuming handoff participants cannot share context under restricted routes.
- Treating a user-input pause as task completion.
- Presenting Python autonomous handoff as a nonexperimental feature.
- Configuring builder termination while a custom orchestrator ignores it.
- Treating a turn-limit exit or any output event as verified success.
What to remember
Configure allowed peer transfers, handle interactive pauses, and distinguish collaboration from independent parallel work. Termination needs both a meaningful completion rule and enforced limits on the actual controller. A bounded run can still end incomplete.
Knowledge check
An order specialist must transfer ownership of the next user interaction to a returns specialist, and only approved transfer paths are permitted. Which design matches that requirement?
add_handoff pathadd_handoff restricts which transfers are allowed. Agent-as-tool composition returns a subtask result to the caller, which keeps ownership; group chat only selects the next speaker in a shared discussion, and concurrency collects independent answers.A specialist in a Python handoff workflow asks for an order number, and the workflow emits a request_info event carrying a HandoffAgentUserRequest. The task is unresolved. How should the client continue it?
terminate() and rerun with the order number appendedcreate_response(order_number)create_response(...) and keyed to that request continues the same run. A new run loses the paused state, terminate() ends the workflow, and experimental autonomous mode sends a default continuation message rather than the number the user holds.A team supplies a custom Python BaseGroupChatOrchestrator, but sets max_rounds and termination_condition only on GroupChatBuilder. Where must the team set them for them to take effect?
BaseGroupChatOrchestrator , builder-level max_rounds and termination_condition are ignored, so the run looks bounded while its actual controller is not. The orchestrator itself must carry participant_registry , max_rounds , and termination_condition . A per-agent turn limit bounds one agent’s turns, not the discussion.A policy group chat reaches its max_rounds limit while the responsible reviewer still reports unresolved evidence. Several participants have emitted output containing the word “done.” Which terminal result should the application record?
References
- Microsoft, Handoff orchestration
- Microsoft, Group chat orchestration