From 1dd13588a0e94eb288533f0b0a459c8387497345 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Wed, 1 Apr 2026 19:05:01 +0200 Subject: [PATCH] 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> --- .../Services/AgentInstructionComposer.cs | 22 +------------------ .../Services/HandoffWorkflowGuidance.cs | 8 +++++-- .../AgentInstructionComposerTests.cs | 16 +++++++------- .../HandoffWorkflowGuidanceTests.cs | 4 ++++ 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/sidecar/src/Aryx.AgentHost/Services/AgentInstructionComposer.cs b/sidecar/src/Aryx.AgentHost/Services/AgentInstructionComposer.cs index 35d08d7..7c7b962 100644 --- a/sidecar/src/Aryx.AgentHost/Services/AgentInstructionComposer.cs +++ b/sidecar/src/Aryx.AgentHost/Services/AgentInstructionComposer.cs @@ -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) diff --git a/sidecar/src/Aryx.AgentHost/Services/HandoffWorkflowGuidance.cs b/sidecar/src/Aryx.AgentHost/Services/HandoffWorkflowGuidance.cs index 399d39f..676aa15 100644 --- a/sidecar/src/Aryx.AgentHost/Services/HandoffWorkflowGuidance.cs +++ b/sidecar/src/Aryx.AgentHost/Services/HandoffWorkflowGuidance.cs @@ -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. """; } diff --git a/sidecar/tests/Aryx.AgentHost.Tests/AgentInstructionComposerTests.cs b/sidecar/tests/Aryx.AgentHost.Tests/AgentInstructionComposerTests.cs index 1a805e2..335afcc 100644 --- a/sidecar/tests/Aryx.AgentHost.Tests/AgentInstructionComposerTests.cs +++ b/sidecar/tests/Aryx.AgentHost.Tests/AgentInstructionComposerTests.cs @@ -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] diff --git a/sidecar/tests/Aryx.AgentHost.Tests/HandoffWorkflowGuidanceTests.cs b/sidecar/tests/Aryx.AgentHost.Tests/HandoffWorkflowGuidanceTests.cs index 1615af8..ca08a61 100644 --- a/sidecar/tests/Aryx.AgentHost.Tests/HandoffWorkflowGuidanceTests.cs +++ b/sidecar/tests/Aryx.AgentHost.Tests/HandoffWorkflowGuidanceTests.cs @@ -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); }