refactor: move handoff guidance into workflow builder

Move handoff routing and ownership rules out of per-agent system
prompts and into the Agent Framework handoff builder guidance.

This keeps AgentInstructionComposer focused on Aryx-owned system
prompt content while letting WithHandoffInstructions supply the
workflow-level handoff semantics.

- remove handoff-mode runtime guidance from AgentInstructionComposer
- expand HandoffWorkflowGuidance with the triage/specialist rules
- update tests to pin the new split of responsibilities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-01 19:05:01 +02:00
co-authored by Copilot
parent 235ddf7e56
commit 1dd13588a0
4 changed files with 19 additions and 31 deletions
@@ -51,27 +51,7 @@ internal static class AgentInstructionComposer
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance, groupChatGuidance);
}
if (!string.Equals(pattern.Mode, "handoff", StringComparison.OrdinalIgnoreCase))
{
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance);
}
string runtimeGuidance = agentIndex == 0
? """
You are the routing gate for this handoff workflow.
Your job is to classify the request and hand it off to the most appropriate specialist as soon as you know who should own the substantive work.
For any substantive task, your next meaningful action must be the actual handoff rather than a plain-text promise to delegate later.
Do not inspect files, call tools, draft the implementation, or produce the final user-facing answer yourself once a specialist is appropriate.
Do not claim that you handed work off unless you actually executed the handoff.
Only answer directly if the user is asking for pure triage or a minimal clarification that must happen before delegation.
"""
: """
You are a specialist participating in a handoff workflow.
Once the triage agent hands work to you, you own the substantive answer within your specialty and should carry it through.
Do not push the actual work back to triage unless you are blocked or the request is clearly outside your specialty.
""";
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance, runtimeGuidance);
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance);
}
private static string JoinInstructionBlocks(params string[] blocks)
@@ -8,11 +8,15 @@ internal static class HandoffWorkflowGuidance
{
return """
This workflow uses explicit handoffs to transfer ownership between agents.
If you are acting as the routing or triage agent, classify the request and hand it off to the best specialist as soon as ownership is clear.
If another agent should do the substantive work, perform an actual handoff instead of answering as though the handoff already happened.
For any substantive task, your next meaningful action must be the actual handoff rather than a plain-text promise to delegate later.
Do not claim that you delegated unless you actually executed the handoff.
The triage agent should route to the best specialist promptly once ownership is clear.
In a specialist workflow, the triage agent should hand off before inspecting files, calling tools, or drafting the substantive implementation.
If a specialist is appropriate, do not inspect files, call tools, draft the implementation, or produce the final user-facing answer before handing off.
Only answer directly when the request is pure triage or a minimal clarification is required before delegation.
Do not narrate a handoff in plain text without executing the handoff itself.
If you receive work as a specialist, own the substantive answer within your specialty and carry it through.
Do not push the work back to triage unless you are blocked or the request is clearly outside your specialty.
Specialists should complete the substantive work after handoff and only hand control back when the task needs re-routing, broader coordination, or is outside their specialty.
""";
}
@@ -54,7 +54,7 @@ public sealed class AgentInstructionComposerTests
}
[Fact]
public void Compose_StrengthensHandoffTriageInstructions()
public void Compose_LeavesHandoffTriagePromptFocusedOnAgentInstructions()
{
PatternDefinitionDto pattern = new()
{
@@ -70,14 +70,13 @@ public sealed class AgentInstructionComposerTests
string instructions = AgentInstructionComposer.Compose(pattern, triage, agentIndex: 0);
Assert.Contains("routing gate", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Do not inspect files", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("actual handoff", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Do not claim that you handed work off", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Equal("You triage requests and must hand them off to the most appropriate specialist.", instructions);
Assert.DoesNotContain("routing", instructions, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("actual handoff", instructions, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Compose_StrengthensHandoffSpecialistInstructions()
public void Compose_LeavesHandoffSpecialistPromptFocusedOnAgentInstructions()
{
PatternDefinitionDto pattern = new()
{
@@ -93,8 +92,9 @@ public sealed class AgentInstructionComposerTests
string instructions = AgentInstructionComposer.Compose(pattern, specialist, agentIndex: 1);
Assert.Contains("Once the triage agent hands work to you", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("own the substantive answer", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Equal("You focus on navigation, UX, and interaction details.", instructions);
Assert.DoesNotContain("triage agent", instructions, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("substantive answer", instructions, StringComparison.OrdinalIgnoreCase);
}
[Fact]
@@ -11,8 +11,12 @@ public sealed class HandoffWorkflowGuidanceTests
string instructions = HandoffWorkflowGuidance.CreateWorkflowInstructions();
Assert.Contains("explicit handoffs", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("routing or triage agent", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("best specialist", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Do not inspect files", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Do not claim that you delegated", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Do not narrate a handoff", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("own the substantive answer", instructions, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Specialists should complete the substantive work", instructions, StringComparison.OrdinalIgnoreCase);
}