{"id":5377,"date":"2026-05-07T17:58:12","date_gmt":"2026-05-08T00:58:12","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/agent-framework\/?p=5377"},"modified":"2026-05-07T17:58:12","modified_gmt":"2026-05-08T00:58:12","slug":"a-tour-of-handoff-orchestration-pattern","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/agent-framework\/a-tour-of-handoff-orchestration-pattern\/","title":{"rendered":"A Tour of Handoff Orchestration Pattern"},"content":{"rendered":"<h1 id=\"a-tour-of-the-handoff-orchestration-pattern\">A Tour of the Handoff Orchestration Pattern<\/h1>\n<p><span data-teams=\"true\">Most multi-agent systems start out simple: a router agent receives a user request, picks the right specialist, and forwards the conversation. As long as each specialist can complete its task in one pass, that model works fine.\n<\/span><\/p>\n<p><span data-teams=\"true\">The first time it breaks is when an agent needs more: a follow-up question for the user, additional context from another specialist, or a realization mid-turn that the request belongs somewhere else entirely. At that point, a fixed pipeline or one-shot router isn\u2019t enough. What you need is a small, bounded graph where agents themselves decide who should speak next, without losing conversation context or violating guardrails.<\/span><\/p>\n<p><strong>Handoff Orchestration<\/strong>\u00a0in Microsoft Agent Framework is the pattern built for that case. The developer declares the participating agents and the directed edges between them, and the framework injects the tool calls each agent uses to transfer control along those edges. Routing decisions stay with the agents; topology and guardrails stay with the developer.<\/p>\n<p>Handoff is a good fit when:<\/p>\n<ul>\n<li>Agents may need follow-up information before completing their work, and the response should go back to that agent\u2014not a central router.<\/li>\n<li>Ownership can change mid-conversation. An agent may realize partway through that another agent should continue.<\/li>\n<li>Back-edges matter. Agents sometimes need to revisit earlier steps (for example, \u201cI need more research\u201d or \u201cthis should be a refund, not a replacement\u201d) without restarting the flow.<\/li>\n<li>Conversation context must be shared. Each agent should see the full transcript, not operate in isolated threads.<\/li>\n<li>Routing decisions are conversational. The choice to hand off is fuzzy, contextual, and better made by the model than by typed predicates.<\/li>\n<\/ul>\n<p><span data-teams=\"true\">This post is a practical tour of the Handoff orchestration pattern. It explains how Handoff works, what shapes of agent topologies it enables, and how conversation flows, routing, and termination behave at runtime. Through progressively richer examples, it shows how Handoff supports shared context, asymmetric routing, and human\u2011in\u2011the\u2011loop interactions\u2014and how it provides a natural evolution path from simple pipelines once a workflow grows beyond a fixed sequence.<\/span><\/p>\n<h2 id=\"what-handoff-actually-is\">What Handoff Actually Is<\/h2>\n<p>A handoff workflow is decentralized routing. The developer declares a graph of agents and the directed edges between them; the framework gives each agent a synthetic handoff tool per outbound edge so it can pass control by calling one. That single mechanic gives the authoring model three properties that matter day-to-day:<\/p>\n<ul>\n<li><strong>The conversation is one shared transcript<\/strong>, not a fan-out of independent threads. The next agent sees what the previous agent said.<\/li>\n<li><strong>The topology is enforced.<\/strong>\u00a0An agent can only hand off to targets that have been declared. Misrouting is prevented at the workflow level rather than in prompts.<\/li>\n<li><strong>The graph terminates naturally.<\/strong>\u00a0When the active agent finishes a turn without invoking a handoff tool, the workflow yields control back to the user in the default loop, or to a downstream executor if one is wired in.<\/li>\n<\/ul>\n<p>These three properties are what make Handoff a good fit for conversational, agent-driven topologies. The remainder of the post stays at the authoring level: what topology can be expressed, how user input flows, how termination works, and when to choose Handoff over Sequential or a workflow built with explicit branching.<\/p>\n<h2 id=\"the-smallest-possible-handoff\">The Smallest Possible Handoff<\/h2>\n<p>In .NET, the workflow is constructed from a static factory by adding edges. Agents are anything that implements\u00a0<code>AIAgent<\/code>, typically created with\u00a0<code>IChatClient.AsAIAgent<\/code>:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">using Microsoft.Agents.AI;\r\nusing Microsoft.Agents.AI.Workflows;\r\n\r\nAIAgent triage = chatClient.AsAIAgent(\r\n    instructions: \"You receive a user request and route it to the right specialist.\",\r\n    name: \"Triage\");\r\n\r\nAIAgent billing = chatClient.AsAIAgent(\r\n    instructions: \"You handle billing questions.\",\r\n    name: \"Billing\");\r\n\r\nAIAgent tech = chatClient.AsAIAgent(\r\n    instructions: \"You handle technical support questions.\",\r\n    name: \"Tech\");\r\n\r\nWorkflow workflow = AgentWorkflowBuilder\r\n    .CreateHandoffBuilderWith(triage)\r\n    .WithHandoff(triage, billing)\r\n    .WithHandoff(triage, tech)\r\n    .Build();<\/code><\/pre>\n<p><code>Build()<\/code>\u00a0returns a\u00a0<code>Workflow<\/code>\u00a0that can be driven with\u00a0<code>InProcessExecution.OpenStreamingAsync<\/code>. Each user message is fed to\u00a0<code>triage<\/code>, which sees two synthetic handoff tools (one per declared edge) whose descriptions tell the model what each target is for. If\u00a0<code>triage<\/code>\u00a0answers the user directly, the conversation pauses for the next user turn. If it calls a handoff tool, control moves to the target.<\/p>\n<p>The same workflow in Python is shaped almost identically:<\/p>\n<pre><code class=\"python\">from agent_framework_orchestrations import HandoffBuilder\r\n\r\nworkflow = (\r\n    HandoffBuilder(participants=[triage, billing, tech])\r\n    .with_start_agent(triage)\r\n    .add_handoff(triage, [billing, tech])\r\n    .build()\r\n)\r\n<\/code><\/pre>\n<p>Participants and the start agent are passed up front rather than threaded through a factory, but the resulting graph is the same.<\/p>\n<h2 id=\"workshop-a-topology-that-isnt-just-a-star\">Workshop: A Topology That Isn&#8217;t Just a Star<\/h2>\n<p>The canonical handoff diagram is a star: a single triage agent at the center with arrows out to specialists, optionally with arrows back. The next example is an e-commerce support workflow with four agents and asymmetric edges:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow.webp\"><img decoding=\"async\" class=\"aligncenter wp-image-5393\" src=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow-1024x414.webp\" alt=\"e-Commerce Workflow A Triage agent chooses between an Orders agent and a Returns agent, who may return control back to Triage as appropriate or the Returns agent can forward the case to the Fraud handling agent, in a one-way manner.\" width=\"791\" height=\"320\" srcset=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow-1024x414.webp 1024w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow-300x121.webp 300w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow-768x311.webp 768w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ECommerceWorkflow.webp 1518w\" sizes=\"(max-width: 791px) 100vw, 791px\" \/><\/a><\/p>\n<p>Two characteristics distinguish this topology from the simple star:<\/p>\n<ul>\n<li><strong><code>Returns<\/code>\u00a0\u2194\u00a0<code>Orders<\/code>\u00a0is a side edge.<\/strong>\u00a0A user who started with a return may need a replacement order, or a user who started with an order may want to convert it to a refund. Routing through\u00a0<code>Triage<\/code>\u00a0again would cost a turn and risk losing context.<\/li>\n<li><strong><code>Returns \u2192 Fraud<\/code>\u00a0is one-way, and\u00a0<code>Fraud<\/code>\u00a0is terminal.<\/strong>\u00a0The returns specialist can escalate when a request looks suspicious, but\u00a0<code>Fraud<\/code>\u00a0has no outbound handoffs of its own. Once control rests there, the workflow stops routing and the conversation drains to output.<\/li>\n<\/ul>\n<p>The .NET wiring is direct.\u00a0<code>WithHandoffs<\/code>\u00a0is used for the runs of edges that use default target-derived reasons, and the single\u00a0<code>WithHandoff<\/code>\u00a0call is reserved for the one edge that needs a custom reason:<\/p>\n<pre><code class=\"csharp\">HandoffWorkflowBuilder builder = AgentWorkflowBuilder\r\n    .CreateHandoffBuilderWith(triage)\r\n    .WithHandoffs(triage, [returns, orders])      \/\/ star\r\n    .WithHandoffs(returns, [triage, orders])      \/\/ back-edge + side edge\r\n    .WithHandoffs(orders, [triage, returns])      \/\/ back-edge + side edge\r\n    .WithHandoff(returns, fraud,                  \/\/ one-way escalation\r\n        handoffReason: \"Suspected return fraud or abuse.\")\r\n    .EnableReturnToPrevious();                    \/\/ follow-ups go to last specialist\r\n\r\nWorkflow workflow = builder.Build();\r\n<\/code><\/pre>\n<p>Three behaviors are worth highlighting in this example:<\/p>\n<ol>\n<li><strong>Per-edge handoff reasons matter<\/strong>\u00a0because they become the\u00a0<em>tool descriptions<\/em>\u00a0the LLM reads when choosing whether to route. The reason on each\u00a0<code>WithHandoff(from, to, handoffReason?)<\/code>\u00a0call is attached to the source-to-target edge: different sources can use different reasons for the same target. The default reason is the target agent&#8217;s\u00a0<code>Description<\/code>\u00a0(or its name); the explicit reason on\u00a0<code>Returns \u2192 Fraud<\/code>\u00a0is what teaches the model when escalation is appropriate without putting that logic in\u00a0<code>Returns<\/code>&#8216;s system prompt.<\/li>\n<li><strong><code>EnableReturnToPrevious()<\/code><\/strong>\u00a0changes which agent receives the\u00a0<em>next user message<\/em>. Without it, every user turn returns to\u00a0<code>Triage<\/code>. With it, follow-up questions land on the specialist that was last speaking, which matches the user&#8217;s expectation mid-conversation.<\/li>\n<li><strong>Terminal agents are just agents with no outbound\u00a0<code>WithHandoff<\/code>.<\/strong>\u00a0No special API is needed. The workflow&#8217;s\u00a0<code>HandoffEndExecutor<\/code>\u00a0yields a\u00a0<code>List&lt;ChatMessage&gt;<\/code>\u00a0as the workflow output once\u00a0<code>Fraud<\/code>\u00a0finishes a turn.<\/li>\n<\/ol>\n<p>The Python build of the same topology is the same set of declarations:<\/p>\n<pre><code class=\"python\">workflow = (\r\n    HandoffBuilder(participants=[triage, returns, orders, fraud])\r\n    .with_start_agent(triage)\r\n    .add_handoff(triage, [returns, orders])\r\n    .add_handoff(returns, [triage, orders])\r\n    .add_handoff(orders, [triage, returns])\r\n    .add_handoff(\r\n        returns, [fraud],\r\n        description=\"Suspected return fraud or abuse.\",\r\n    )\r\n    .build()\r\n)\r\n<\/code><\/pre>\n<p>Both definitions describe the same graph and the same terminal\u00a0<code>fraud<\/code>\u00a0node. Follow-up user turns are routed to the most recent specialist on both runtimes: in .NET this is enabled by\u00a0<code>EnableReturnToPrevious()<\/code>, and in Python it is the default behavior once a start agent is set.<\/p>\n<h2 id=\"workshop-from-sequential-to-handoff-for-hitl\">Workshop: From Sequential to Handoff for HITL<\/h2>\n<p>When does a Sequential workflow want to become a Handoff one? The most common trigger is &#8220;someone needs to ask the user a question.&#8221; Consider a content pipeline:<a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline.webp\"><img decoding=\"async\" class=\"aligncenter wp-image-5390\" src=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline-1024x408.webp\" alt=\"Content Pipeline User sends a request to the Researcher, who passes findings to the Writer, whose draft goes to the Editor and then is published as Output.\" width=\"804\" height=\"320\" srcset=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline-1024x408.webp 1024w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline-300x120.webp 300w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline-768x306.webp 768w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline-1536x612.webp 1536w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/ContentPipeline.webp 1540w\" sizes=\"(max-width: 804px) 100vw, 804px\" \/><\/a>In .NET:<\/p>\n<pre><code class=\"csharp\">Workflow pipeline = AgentWorkflowBuilder.BuildSequential(researcher, writer, editor);\r\n<\/code><\/pre>\n<p>This works well when each agent can complete its step from the original input. But Sequential has no built-in pause: the\u00a0<code>Writer<\/code>\u00a0cannot ask the\u00a0<code>Researcher<\/code>\u00a0for an additional source, and the\u00a0<code>Editor<\/code>\u00a0cannot stop and ask the user &#8220;Did you want this in British or American English?&#8221; before publishing. To add either, there are two paths.<\/p>\n<p><strong>Path 1: build the routing yourself.<\/strong>\u00a0Drop down to\u00a0<code>WorkflowBuilder<\/code>\u00a0and use typed conditional edges or\u00a0<code>AddSwitch<\/code>, the same primitives Kinfey Lo&#8217;s\u00a0<a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/unlocking-enterprise-ai-complexity-multi-agent-orchestration-with-the-microsoft-agent-framework\/\" target=\"_blank\" rel=\"nofollow noopener\">Multi-Agent Orchestration post<\/a>\u00a0uses for the Conditional pattern. The decision executor, branch predicates, and owning agent are all chosen explicitly. The result is total control over routing at the cost of writing the bridging code, including the prompt engineering needed for the agent to format its decision in a way the predicate can read.<\/p>\n<p>For the content-pipeline case, the wiring looks roughly like this (schematic; see note below):<\/p>\n<pre><code class=\"csharp\">Workflow pipeline = new WorkflowBuilder(researcher)\r\n    .AddEdge(researcher, writer)\r\n    .AddSwitch(writer, sw =&gt; sw\r\n        .AddCase&lt;DraftReview&gt;(d =&gt; d.NeedsMoreSources, researcher)\r\n        .WithDefault(editor))\r\n    .Build();\r\n<\/code><\/pre>\n<p>This snippet is schematic because workflow agents speak chat protocol (<code>List&lt;ChatMessage&gt;<\/code>\u00a0and\u00a0<code>TurnToken<\/code>), not typed application payloads such as\u00a0<code>DraftReview<\/code>. Adding a typed switch on the writer&#8217;s output requires inserting a custom executor (for example, one derived from\u00a0<code>ChatProtocolExecutor<\/code>) that converts the writer&#8217;s chat output into a\u00a0<code>DraftReview<\/code>\u00a0before the switch can inspect it. The agents themselves are the same as in the Sequential pipeline; what differs is that the routing decision now lives in\u00a0<code>NeedsMoreSources<\/code>\u00a0(a typed predicate over the writer&#8217;s output) and in the adapter executor that produces the\u00a0<code>DraftReview<\/code>\u00a0payload. That is the tradeoff: explicit branch logic, typed edges, and the bridging code in the developer&#8217;s hands, instead of agent-driven routing.<\/p>\n<p><strong>Path 2: switch to Handoff.<\/strong> Promote the agents into a small graph and let them decide where to route:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow.webp\"><img decoding=\"async\" class=\"aligncenter wp-image-5392\" src=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow-1024x408.webp\" alt=\"Handoff Workflow The same general content pipeline, with the ability for the Writer to hand off the work back to the Researcher when additional information is required.\" width=\"803\" height=\"320\" srcset=\"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow-1024x408.webp 1024w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow-300x120.webp 300w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow-768x306.webp 768w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow-1536x612.webp 1536w, https:\/\/devblogs.microsoft.com\/agent-framework\/wp-content\/uploads\/sites\/78\/2026\/05\/HandoffWorkflow.webp 1540w\" sizes=\"(max-width: 803px) 100vw, 803px\" \/><\/a><\/p>\n<pre><code class=\"csharp\">Workflow pipeline = AgentWorkflowBuilder\r\n    .CreateHandoffBuilderWith(researcher)\r\n    .WithHandoff(researcher, writer)\r\n    .WithHandoff(writer, editor)\r\n    .WithHandoff(writer, researcher,\r\n        handoffReason: \"Need additional research, sources, or fact-checking.\")\r\n    .EnableReturnToPrevious()\r\n    .Build();\r\n<\/code><\/pre>\n<p>What changed:<\/p>\n<ul>\n<li><strong><code>Writer \u2192 Researcher<\/code>\u00a0is a back-edge<\/strong>\u00a0with an explicit description. The\u00a0<code>Writer<\/code>\u00a0model now sees a synthetic handoff tool described as &#8220;Need additional research&#8230;&#8221; and can call it whenever it notices a gap. No decision executor is required.<\/li>\n<li><strong><code>Editor<\/code>\u00a0is terminal<\/strong>\u00a0(no outbound handoffs). When the editor finishes a turn, the workflow yields its conversation as output and pauses for the next user message.<\/li>\n<li><strong><code>EnableReturnToPrevious()<\/code>\u00a0keeps the workflow HITL-friendly with no additional code.<\/strong>\u00a0When the workflow pauses after the editor, the user&#8217;s next message (&#8220;Make it more formal&#8221;) goes to the editor rather than back to the researcher. Conversational follow-ups land where the user expects.<\/li>\n<\/ul>\n<p>The Sequential version is shorter. The Handoff version is appropriate the first time the writer needs to revisit research, or the editor needs to consult the user before publishing. The structure is still declarative; the difference is that the graph contains a back-edge, and the routing decision along that edge is delegated to the agents.<\/p>\n<blockquote><p><strong>Python callout.<\/strong>\u00a0Python&#8217;s\u00a0<code>SequentialBuilder<\/code>\u00a0already exposes per-step HITL via\u00a0<code>.with_request_info(agents=[...])<\/code>, which pauses the chain for user input between steps. That is a different shape of HITL than what Handoff provides: it is a fixed pause point, not a routing decision the agent makes. Use it when the human should always inspect the result before the next step; choose Handoff when the agent should decide whether the human (or another agent) needs to weigh in.<\/p><\/blockquote>\n<h2 id=\"when-to-pick-which\">When to Pick Which<\/h2>\n<table>\n<thead>\n<tr>\n<th>If you want\u2026<\/th>\n<th>Use<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Fixed pipeline, deterministic order<\/td>\n<td><code>AgentWorkflowBuilder.BuildSequential<\/code>\u00a0(.NET) \/\u00a0<code>SequentialBuilder<\/code>\u00a0(Python)<\/td>\n<\/tr>\n<tr>\n<td>Same as above, with a guaranteed pause for review between steps<\/td>\n<td>Sequential + Python&#8217;s\u00a0<code>.with_request_info<\/code>, or insert a custom executor in .NET<\/td>\n<\/tr>\n<tr>\n<td>Dynamic routing among a fixed cast of specialists, conversation-shaped flow<\/td>\n<td>Handoff<\/td>\n<\/tr>\n<tr>\n<td>Routing decisions that depend on\u00a0<strong>non-agent<\/strong>\u00a0logic, typed payloads, fan-out\/fan-in<\/td>\n<td>Drop down to\u00a0<code>WorkflowBuilder<\/code>\u00a0and use\u00a0<code>AddSwitch<\/code>\u00a0\/ conditional edges<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The mental model: Sequential covers pipelines whose shape is fixed in advance. Conditional workflows cover branches that the developer decides. Handoff covers branches that the agents decide, within boundaries set by the developer.<\/p>\n<h2 id=\"what-this-demonstrates\">What This Demonstrates<\/h2>\n<p>Handoff keeps the topology in code and delegates routing decisions to the agents. This division of responsibility (declarative graph, agent-driven routing within it) is what enables the rest of the model: a shared conversation across specialists, asymmetric edges, terminal endpoints, return-to-previous follow-ups for HITL, and a clean migration path from Sequential the first time a workflow grows a back-edge.<\/p>\n<p>Both .NET and Python expose the same set of capabilities. The next section covers the runtime differences worth knowing when authoring the same pattern in each language.<\/p>\n<h2 id=\"runtime-differences-between-net-and-python\">Runtime differences between .NET and Python<\/h2>\n<p>The two implementations agree on the model: shared conversation, auto-injected tools, per-edge natural-language descriptions, topology guardrails. They diverge on what is first-class versus what the developer composes.<\/p>\n<table>\n<thead>\n<tr>\n<th>Capability<\/th>\n<th>.NET<\/th>\n<th>Python<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Declare an edge<\/td>\n<td><code>WithHandoff(from, to, handoffReason?)<\/code>; reason attached to the source-to-target edge, so different sources can use different reasons for the same target<\/td>\n<td><code>add_handoff(source, [targets], description=...)<\/code>; description per call, can vary by source<\/td>\n<\/tr>\n<tr>\n<td>Default mesh<\/td>\n<td>None; every edge is declared explicitly<\/td>\n<td>If no\u00a0<code>add_handoff<\/code>\u00a0is called, every agent can hand off to every other<\/td>\n<\/tr>\n<tr>\n<td>Autonomous mode<\/td>\n<td>No first-class API; the same shape of behavior can be approximated through topology, for example by adding a handoff back to the starting agent from a specialist that would otherwise terminate<\/td>\n<td><code>with_autonomous_mode(turn_limits={agent: N})<\/code><\/td>\n<\/tr>\n<tr>\n<td>Route follow-ups to the last specialist<\/td>\n<td>One-line\u00a0<code>EnableReturnToPrevious()<\/code><\/td>\n<td>Default once a start agent is set; controlled via topology<\/td>\n<\/tr>\n<tr>\n<td>Stop the conversation programmatically<\/td>\n<td>Terminal node only<\/td>\n<td>Terminal node,\u00a0<code>with_termination_condition(callable)<\/code>, or\u00a0<code>HandoffBuilder.terminate()<\/code>\u00a0from a user-input handler<\/td>\n<\/tr>\n<tr>\n<td>Streaming events<\/td>\n<td>Builder toggles\u00a0<code>EmitAgentResponseEvents<\/code>\u00a0\/\u00a0<code>EmitAgentResponseUpdateEvents<\/code>; consumers switch on\u00a0<code>WorkflowEvent<\/code>\u00a0subclasses<\/td>\n<td>Uniform\u00a0<code>WorkflowEvent<\/code>\u00a0stream including a typed\u00a0<code>HandoffSentEvent<\/code>\u00a0for source\/target breadcrumbs<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Two of those rows materially change what a developer can express:<\/p>\n<ul>\n<li><strong>Autonomous mode<\/strong>\u00a0is a first-class API in Python. .NET does not expose a dedicated equivalent, but the same shape of behavior can be approximated through topology, for example by adding a handoff back to the coordinator from a specialist that would otherwise terminate, so control returns without requiring a user turn.<\/li>\n<li><strong><code>termination_condition<\/code><\/strong>\u00a0is Python-only. .NET&#8217;s idiom is to make the &#8220;we&#8217;re done&#8221; agent terminal, which works well when the topology has a clear endpoint and less well when termination depends on conversation content.<\/li>\n<\/ul>\n<p>The other rows are surface differences. The runtimes share the same mental model, and most day-to-day authoring intent reads almost the same on both sides.<\/p>\n<h2 id=\"learn-more\">Learn More<\/h2>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/agent-framework\/\" target=\"_blank\" rel=\"nofollow noopener\">Microsoft Agent Framework documentation<\/a><\/li>\n<li><a href=\"https:\/\/microsoft-my.sharepoint.com\/personal\/dotnet\/src\/Microsoft.Agents.AI.Workflows\/HandoffWorkflowBuilder.cs\" rel=\"nofollow\"><code>HandoffWorkflowBuilder<\/code>\u00a0source<\/a><\/li>\n<li><a href=\"https:\/\/microsoft-my.sharepoint.com\/personal\/dotnet\/samples\/03-workflows\/Orchestration\/Handoff\/Program.cs\" rel=\"nofollow\">.NET Handoff sample<\/a><\/li>\n<li><a href=\"https:\/\/microsoft-my.sharepoint.com\/personal\/python\/packages\/orchestrations\/agent_framework_orchestrations\/_handoff.py\" rel=\"nofollow\">Python\u00a0<code>HandoffBuilder<\/code>\u00a0source<\/a><\/li>\n<li><a href=\"https:\/\/microsoft-my.sharepoint.com\/personal\/python\/samples\/03-workflows\/orchestrations\/\" rel=\"nofollow\">Python handoff samples<\/a>:\u00a0<code>handoff_simple.py<\/code>\u00a0and\u00a0<code>handoff_autonomous.py<\/code><\/li>\n<li>Evan Mattson,\u00a0<a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/ag-ui-multi-agent-workflow-demo\/\" target=\"_blank\" rel=\"nofollow noopener\">Building a Real-Time Multi-Agent UI with AG-UI and MAF Workflows<\/a><\/li>\n<li>Kinfey Lo,\u00a0<a href=\"https:\/\/devblogs.microsoft.com\/agent-framework\/unlocking-enterprise-ai-complexity-multi-agent-orchestration-with-the-microsoft-agent-framework\/\" target=\"_blank\" rel=\"nofollow noopener\">Unlocking Enterprise AI Complexity: Multi-Agent Orchestration with the Microsoft Agent Framework<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A Tour of the Handoff Orchestration Pattern Most multi-agent systems start out simple: a router agent receives a user request, picks the right specialist, and forwards the conversation. As long as each specialist can complete its task in one pass, that model works fine. The first time it breaks is when an agent needs more: [&hellip;]<\/p>\n","protected":false},"author":212468,"featured_media":5048,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[78,143,34],"tags":[],"class_list":["post-5377","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","category-agent-framework","category-python-2"],"acf":[],"blog_post_summary":"<p>A Tour of the Handoff Orchestration Pattern Most multi-agent systems start out simple: a router agent receives a user request, picks the right specialist, and forwards the conversation. As long as each specialist can complete its task in one pass, that model works fine. The first time it breaks is when an agent needs more: [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5377","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/users\/212468"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/comments?post=5377"}],"version-history":[{"count":3,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5377\/revisions"}],"predecessor-version":[{"id":5432,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5377\/revisions\/5432"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media\/5048"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media?parent=5377"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/categories?post=5377"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/tags?post=5377"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}