mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-27 21:33:58 +02:00
fix: strengthen handoff workflow guidance
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -500,11 +500,30 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
private Workflow BuildHandoffWorkflow(PatternDefinitionDto pattern)
|
private Workflow BuildHandoffWorkflow(PatternDefinitionDto pattern)
|
||||||
{
|
{
|
||||||
AIAgent firstAgent = Agents[0];
|
AIAgent firstAgent = Agents[0];
|
||||||
IReadOnlyList<AIAgent> specialists = Agents.Skip(1).ToList();
|
PatternAgentDefinitionDto triageDefinition = pattern.Agents[0];
|
||||||
|
IReadOnlyList<(AIAgent Agent, PatternAgentDefinitionDto Definition)> specialists =
|
||||||
|
Agents.Skip(1)
|
||||||
|
.Zip(pattern.Agents.Skip(1), (agent, definition) => (agent, definition))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
HandoffsWorkflowBuilder builder = AgentWorkflowBuilder.CreateHandoffBuilderWith(firstAgent)
|
HandoffsWorkflowBuilder builder = AgentWorkflowBuilder.CreateHandoffBuilderWith(firstAgent)
|
||||||
.WithHandoffs(firstAgent, specialists)
|
.WithHandoffInstructions(HandoffWorkflowGuidance.CreateWorkflowInstructions());
|
||||||
.WithHandoffs(specialists, firstAgent);
|
|
||||||
|
foreach ((AIAgent specialist, PatternAgentDefinitionDto definition) in specialists)
|
||||||
|
{
|
||||||
|
builder = builder.WithHandoff(
|
||||||
|
firstAgent,
|
||||||
|
specialist,
|
||||||
|
HandoffWorkflowGuidance.CreateForwardReason(definition));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((AIAgent specialist, _) in specialists)
|
||||||
|
{
|
||||||
|
builder = builder.WithHandoff(
|
||||||
|
specialist,
|
||||||
|
firstAgent,
|
||||||
|
HandoffWorkflowGuidance.CreateReturnReason(triageDefinition));
|
||||||
|
}
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Kopaya.AgentHost.Contracts;
|
||||||
|
|
||||||
|
namespace Kopaya.AgentHost.Services;
|
||||||
|
|
||||||
|
internal static class HandoffWorkflowGuidance
|
||||||
|
{
|
||||||
|
public static string CreateWorkflowInstructions()
|
||||||
|
{
|
||||||
|
return """
|
||||||
|
This workflow uses explicit handoffs to transfer ownership between agents.
|
||||||
|
If another agent should do the substantive work, perform an actual handoff instead of answering as though the handoff already happened.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateForwardReason(PatternAgentDefinitionDto target)
|
||||||
|
{
|
||||||
|
string specialty = string.IsNullOrWhiteSpace(target.Description)
|
||||||
|
? target.Name
|
||||||
|
: target.Description.TrimEnd('.');
|
||||||
|
|
||||||
|
return $"Hand off when the request primarily concerns {specialty}. Once handed off, let {target.Name} own the substantive response.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateReturnReason(PatternAgentDefinitionDto triageAgent)
|
||||||
|
{
|
||||||
|
return $"Hand off back to {triageAgent.Name} only when the task needs re-routing, cross-specialist coordination, or is outside your specialty.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using Kopaya.AgentHost.Contracts;
|
||||||
|
using Kopaya.AgentHost.Services;
|
||||||
|
|
||||||
|
namespace Kopaya.AgentHost.Tests;
|
||||||
|
|
||||||
|
public sealed class HandoffWorkflowGuidanceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateWorkflowInstructions_RequiresRealHandoffs()
|
||||||
|
{
|
||||||
|
string instructions = HandoffWorkflowGuidance.CreateWorkflowInstructions();
|
||||||
|
|
||||||
|
Assert.Contains("explicit handoffs", instructions, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("Do not claim that you delegated", instructions, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("Specialists should complete the substantive work", instructions, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateForwardReason_UsesTargetSpecialtyAndOwnership()
|
||||||
|
{
|
||||||
|
PatternAgentDefinitionDto specialist = new()
|
||||||
|
{
|
||||||
|
Id = "agent-handoff-ux",
|
||||||
|
Name = "UX Specialist",
|
||||||
|
Description = "Handles user experience questions.",
|
||||||
|
Instructions = "Focus on UX.",
|
||||||
|
Model = "claude-opus-4.5",
|
||||||
|
};
|
||||||
|
|
||||||
|
string reason = HandoffWorkflowGuidance.CreateForwardReason(specialist);
|
||||||
|
|
||||||
|
Assert.Contains("Handles user experience questions", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("UX Specialist", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("substantive response", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateReturnReason_RestrictsReturnToReroutingCases()
|
||||||
|
{
|
||||||
|
PatternAgentDefinitionDto triage = new()
|
||||||
|
{
|
||||||
|
Id = "agent-handoff-triage",
|
||||||
|
Name = "Triage",
|
||||||
|
Description = "Routes the request to the right specialist.",
|
||||||
|
Instructions = "Triages requests.",
|
||||||
|
Model = "gpt-5.4",
|
||||||
|
};
|
||||||
|
|
||||||
|
string reason = HandoffWorkflowGuidance.CreateReturnReason(triage);
|
||||||
|
|
||||||
|
Assert.Contains("Triage", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("re-routing", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("outside your specialty", reason, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user