Skip to lesson

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.

The caller can distinguish success from exhaustion or cancellationThe run starts in collaboration. When the active agent answers without handing off, the run pauses for user input; a response returns it to collaboration, and terminate ends it as cancelled. From collaboration, the termination condition ends it with a validated completion, and a hard limit ends it incomplete or escalated. Each ending is a separate outcome.create_responseterminate()hard limitCollaborationPause foruser inputValidatedcompletionIncomplete orescalatedCancelled

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

References