Why Linear Agent Pipelines Fall Short
When you first design a multi-agent system, the architecture looks clean. A user message arrives, a triage agent routes it to a specialist, the specialist responds, and the conversation ends. In practice, that rarely happens.
A research agent discovers it needs clarification from the user. A fraud detection agent flags a transaction and needs the orders agent to pull additional context. A writer realizes the research wasn’t thorough enough and wants to send the message back for another pass. Linear pipelines force you to either restart the entire workflow or route everything through a central triage agent on every turn.
That’s where back-edges matter. They let agents hand control directly back to a previous specialist mid-conversation, preserving the full context of what’s been discussed. No restarting. No lost information. Just natural conversational flow.
Understanding Back-Edges and Asymmetric Routing
In Microsoft Agent Framework, agents communicate through handoffs. By default, most systems use symmetric routing: Agent A hands off to Agent B, and Agent B hands off back to Agent A or to a central triage. A back-edge is a directed connection that allows Agent B to hand off to Agent A, creating a loop without requiring a restart.
The key insight is that back-edges are asymmetric. You might declare that a Writer agent can hand off to a Researcher agent, but not the reverse. Or a Fraud Detection agent might have no outbound handoffs at all, acting as a terminal node. This asymmetry lets you model real workflows where some agents are specialists that feed into others, and some are escalation points that don’t hand off further.
In Microsoft Agent Framework, you declare these relationships when you build your workflow topology. Each edge represents a potential handoff. If Agent A can hand off to Agent B, and Agent B can hand off back to Agent A, you have a back-edge loop.
Declaring Bidirectional Handoffs with HandoffWorkflowBuilder
Here’s how you set up agents that can hand off to each other. In this example, a Returns agent and an Orders agent form a loop where each can hand off to the other:
var triage = chatClient.AsAIAgent(
name: "Triage",
instructions: "Route customer requests to the appropriate specialist.");
var returns = chatClient.AsAIAgent(
name: "Returns",
instructions: "Handle customer return requests. Ask about order details if needed.");
var orders = chatClient.AsAIAgent(
name: "Orders",
instructions: "Retrieve and manage order information. Escalate returns to the Returns agent.");
var fraud = chatClient.AsAIAgent(
name: "Fraud",
instructions: "Detect and flag suspicious transactions.");
var workflow = AgentWorkflowBuilder
.CreateHandoffBuilderWith(triage)
.WithHandoffs(triage, [returns, orders])
.WithHandoffs(returns, [triage, orders])
.WithHandoffs(orders, [triage, returns])
.WithHandoff(returns, fraud,
handoffReason: "Suspected return fraud or abuse.")
.EnableReturnToPrevious()
.Build();
This declares the topology: Triage sits at the center with edges to Returns and Orders. Returns and Orders can hand off to each other (the back-edges), and Returns can escalate to Fraud with a custom reason. The framework injects synthetic handoff tools into each agent based on these declarations, so the agent can call them when needed.
Using EnableReturnToPrevious() for Context-Aware Routing
The real magic happens with EnableReturnToPrevious(). This method tells the framework that when a user sends a follow-up message, it should route to the last active specialist instead of always returning to triage.
Consider a conversation flow. The user asks about a return. The Returns agent handles it but realizes it needs order details. It hands off to Orders. The Orders agent retrieves the information and hands control back to Returns so Returns can complete the return request. The user then sends a follow-up clarification. Without EnableReturnToPrevious(), that message would go back to triage. With it enabled, the message goes directly to Returns, the last active specialist, so the conversation stays in context.
This is especially valuable in multi-turn conversations where context matters. The user doesn’t have to repeat themselves, and the agent doesn’t have to re-read the entire conversation history to understand where they left off.
Real-World Scenarios for Back-Edges
Back-edges solve several practical problems that emerge in production systems.
Escalation with Return Path: A fraud detection agent flags a high-value transaction. Instead of terminating the conversation, it hands off back to the Orders agent with the flag, asking Orders to verify the transaction details. Once verified, Orders can hand back to Fraud for final clearance.
Clarification Loops: A research agent discovers ambiguity in the user’s request. It hands off to a Triage or Clarification agent to ask for more details. Once clarification arrives, control returns to Research with the additional context, and Research continues from where it left off.
Multi-Stage Content Creation: A Writer hands off to a Researcher for fact-checking. The Researcher returns findings. The Writer then hands off to an Editor for style review. The Editor returns suggestions. The Writer incorporates feedback and completes the content. Each agent sees the full conversation history, so context is never lost.
Preserving Context Across Back-Edges
Context preservation is the foundation of conversational feedback loops. When an agent hands off, the framework passes the full conversation transcript to the receiving agent, not just the current message.
Each agent in the workflow sees the complete message history from the start of the conversation. This means when a Writer hands off to a Researcher and then back to the Writer, the Writer sees what the Researcher discovered. When the Researcher hands back to the Writer, the Writer sees the Researcher’s findings in the transcript. No context is lost because there’s one shared transcript, not isolated threads.
The framework manages this automatically through the workflow’s conversation state. You don’t need to manually thread context between agents. Just declare your topology, and the framework ensures each agent receives the full history.
Implementing Terminal Agents
Not every agent should have outbound handoffs. Terminal agents are specialists that receive handoffs but don’t hand off further. A Fraud Detection agent is often terminal.
In the workflow builder, a terminal agent is simply an agent with no outbound WithHandoff declarations. When a terminal agent finishes its turn without calling 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.
var workflow = AgentWorkflowBuilder
.CreateHandoffBuilderWith(triage)
.WithHandoffs(triage, [returns, orders, fraud])
.WithHandoffs(returns, [orders])
.WithHandoffs(orders, [returns])
// Fraud has no outbound handoffs, so it's terminal
.EnableReturnToPrevious()
.Build();
When Fraud finishes analyzing a transaction, it returns a response. Since Fraud has no declared outbound handoffs, the workflow stops routing and the conversation drains to output. The user sees the Fraud agent’s final response.
Common Pitfalls and How to Avoid Them
Infinite Loops: If you declare that Agent A can hand off to Agent B and Agent B can hand off to Agent A, you can create a loop where they keep handing off to each other indefinitely. Prevent this by setting a maximum handoff depth in your agent instructions or by designing your topology so that agents have clear termination conditions. Each agent should know when its work is done and when to return a final response instead of handing off again.
Lost Context During Handoffs: The framework handles context preservation automatically through the shared transcript. The key is to ensure your agent instructions make it clear that agents should read the full conversation history to understand what’s already been discussed.
Unclear Handoff Reasons: When you declare a WithHandoff with a custom handoffReason, that reason becomes the tool description the agent sees. Make it clear and specific. Instead of “Handoff to Researcher,” use “Need additional research, sources, or fact-checking.” The agent uses this description to decide when to call the handoff tool.
Terminal Agents That Aren’t Really Terminal: Be explicit about which agents can handoff and which can’t. Use code reviews and tests to verify the topology matches your intent. If an agent should be terminal, don’t declare any outbound handoffs for it.
Testing Back-Edge Workflows
Testing becomes more complex with back-edges because you’re testing multi-step conversations with handoffs. Focus on testing the specific handoff paths you expect.
For a Returns-Orders-Fraud topology, test that Returns can hand off to Orders, Orders can hand back to Returns, and Returns can escalate to Fraud. Verify that context is preserved at each step by checking that agents see the full conversation history. Confirm that terminal agents stop the chain and return a final response.
Use mock agents or test doubles to control which agents hand off to which, and verify that the workflow routes messages to the correct agent at each step. This is especially important when you have multiple back-edges, because the routing logic becomes more complex.
Conclusion
Conversational feedback loops transform multi-agent systems from rigid pipelines into flexible, context-aware workflows. Back-edges let agents hand control to previous specialists mid-conversation. EnableReturnToPrevious() ensures follow-up messages stay in context instead of restarting. These patterns work together to build systems where agents collaborate naturally, each seeing the full conversation history and making routing decisions based on what’s actually been discussed.
What’s the difference between a back-edge and a regular handoff?
A regular handoff is one-directional: Agent A hands off to Agent B. A back-edge is a loop: Agent B can hand off directly back to Agent A. This lets agents revisit previous specialists mid-conversation without restarting the workflow.
Why is EnableReturnToPrevious() important?
Without it, every follow-up message from the user goes back to the initial agent, forcing the conversation to restart. With it enabled, follow-up messages route to the last active specialist, keeping the conversation in context and preserving the user’s mental model of who they’re talking to.
How do I prevent infinite loops between agents?
Design your agent instructions so each agent has a clear termination condition. If an agent has nothing new to contribute, it should return a final response instead of handing off again. You can also set a maximum handoff depth in your topology or add logic to track how many times agents have handed off to each other.
What should a terminal agent do if it needs to escalate?
A terminal agent can still hand off if the situation warrants it. The difference is that a terminal agent should not hand off as part of its normal operation. If a Fraud Detection agent detects fraud, it might hand off to a Human Review agent. Just declare that edge in your workflow builder.
How do I test workflows with multiple back-edges?
Use mock agents to control which agents hand off to which, and verify that context is preserved at each step. Test the specific handoff paths you expect and confirm terminal agents stop the chain. Also test that EnableReturnToPrevious() routes follow-up messages to the correct agent.