mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-25 05:58:39 +02:00
Audit confirmed that single, sequential, concurrent, and handoff are mapped to distinct workflow builders as intended. Group-chat was also using the correct round-robin manager, but the agents only had one-shot role prompts, so the reviewer could behave like a fresh assistant instead of refining the draft in progress. Add explicit runtime guidance for group-chat participants plus clearer built-in Writer/Reviewer instructions and regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
329 lines
11 KiB
TypeScript
329 lines
11 KiB
TypeScript
import type { ChatMessageRecord } from '@shared/domain/session';
|
|
|
|
export type OrchestrationMode =
|
|
| 'single'
|
|
| 'sequential'
|
|
| 'concurrent'
|
|
| 'handoff'
|
|
| 'group-chat'
|
|
| 'magentic';
|
|
|
|
export type PatternAvailability = 'available' | 'preview' | 'unavailable';
|
|
export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh';
|
|
|
|
export const reasoningEffortOptions: ReadonlyArray<{ value: ReasoningEffort; label: string }> = [
|
|
{ value: 'low', label: 'Low' },
|
|
{ value: 'medium', label: 'Medium' },
|
|
{ value: 'high', label: 'High' },
|
|
{ value: 'xhigh', label: 'Maximum' },
|
|
];
|
|
|
|
export interface PatternAgentDefinition {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
instructions: string;
|
|
model: string;
|
|
reasoningEffort?: ReasoningEffort;
|
|
}
|
|
|
|
export interface PatternDefinition {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
isFavorite?: boolean;
|
|
mode: OrchestrationMode;
|
|
availability: PatternAvailability;
|
|
unavailabilityReason?: string;
|
|
maxIterations: number;
|
|
agents: PatternAgentDefinition[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PatternValidationIssue {
|
|
level: 'error' | 'warning';
|
|
field?: string;
|
|
message: string;
|
|
}
|
|
|
|
const defaultModels = {
|
|
claude: 'claude-opus-4.5',
|
|
gpt54: 'gpt-5.4',
|
|
gpt53: 'gpt-5.3-codex',
|
|
} as const;
|
|
|
|
const reasoningEffortSet = new Set<ReasoningEffort>(reasoningEffortOptions.map((option) => option.value));
|
|
|
|
export function isReasoningEffort(value: string | undefined): value is ReasoningEffort {
|
|
return value !== undefined && reasoningEffortSet.has(value as ReasoningEffort);
|
|
}
|
|
|
|
export function createBuiltinPatterns(timestamp: string): PatternDefinition[] {
|
|
return [
|
|
{
|
|
id: 'pattern-single-chat',
|
|
name: '1-on-1 Copilot Chat',
|
|
description: 'Direct human-agent conversation for a selected project.',
|
|
mode: 'single',
|
|
availability: 'available',
|
|
maxIterations: 1,
|
|
agents: [
|
|
{
|
|
id: 'agent-single-primary',
|
|
name: 'Primary Agent',
|
|
description: 'General-purpose project assistant.',
|
|
instructions: 'You are a helpful coding assistant working inside the selected project.',
|
|
model: defaultModels.gpt54,
|
|
reasoningEffort: 'high',
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
{
|
|
id: 'pattern-sequential-review',
|
|
name: 'Sequential Trio Review',
|
|
description: 'Three Copilot-backed agents execute in order, refining the answer each step.',
|
|
mode: 'sequential',
|
|
availability: 'available',
|
|
maxIterations: 1,
|
|
agents: [
|
|
{
|
|
id: 'agent-sequential-analyst',
|
|
name: 'Analyst',
|
|
description: 'Breaks the task down and captures risks.',
|
|
instructions: 'Analyze the request, identify constraints, and produce a short working plan.',
|
|
model: defaultModels.gpt54,
|
|
reasoningEffort: 'high',
|
|
},
|
|
{
|
|
id: 'agent-sequential-builder',
|
|
name: 'Builder',
|
|
description: 'Translates the plan into a practical implementation.',
|
|
instructions: 'Use the prior context to propose a concrete implementation.',
|
|
model: defaultModels.gpt53,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
{
|
|
id: 'agent-sequential-reviewer',
|
|
name: 'Reviewer',
|
|
description: 'Checks the proposal for gaps and edge cases.',
|
|
instructions: 'Review the previous answer, tighten it, and call out any missing edge cases.',
|
|
model: defaultModels.claude,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
{
|
|
id: 'pattern-concurrent-brainstorm',
|
|
name: 'Concurrent Brainstorm',
|
|
description: 'Multiple agents respond in parallel for comparison or voting.',
|
|
mode: 'concurrent',
|
|
availability: 'available',
|
|
maxIterations: 1,
|
|
agents: [
|
|
{
|
|
id: 'agent-concurrent-architect',
|
|
name: 'Architect',
|
|
description: 'Focuses on architecture and boundaries.',
|
|
instructions: 'Answer from an architecture-first perspective.',
|
|
model: defaultModels.gpt54,
|
|
reasoningEffort: 'high',
|
|
},
|
|
{
|
|
id: 'agent-concurrent-product',
|
|
name: 'Product',
|
|
description: 'Focuses on UX and scope.',
|
|
instructions: 'Answer from a product and UX perspective.',
|
|
model: defaultModels.claude,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
{
|
|
id: 'agent-concurrent-implementer',
|
|
name: 'Implementer',
|
|
description: 'Focuses on practical delivery.',
|
|
instructions: 'Answer from an implementation and testing perspective.',
|
|
model: defaultModels.gpt53,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
{
|
|
id: 'pattern-handoff-support',
|
|
name: 'Handoff Support Flow',
|
|
description: 'A triage agent routes the task to specialists and can reclaim control.',
|
|
mode: 'handoff',
|
|
availability: 'available',
|
|
maxIterations: 4,
|
|
agents: [
|
|
{
|
|
id: 'agent-handoff-triage',
|
|
name: 'Triage',
|
|
description: 'Routes the request to the right specialist.',
|
|
instructions:
|
|
'You triage requests and must hand them off to the most appropriate specialist. Do not do the specialist work yourself once the right owner is clear.',
|
|
model: defaultModels.gpt54,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
{
|
|
id: 'agent-handoff-ux',
|
|
name: 'UX Specialist',
|
|
description: 'Handles user experience questions.',
|
|
instructions:
|
|
'You focus on navigation, UX, and interaction details. Once triage hands work to you, you own the substantive answer.',
|
|
model: defaultModels.claude,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
{
|
|
id: 'agent-handoff-runtime',
|
|
name: 'Runtime Specialist',
|
|
description: 'Handles backend and execution details.',
|
|
instructions:
|
|
'You focus on runtime, orchestration, and backend integration details. Once triage hands work to you, you own the substantive answer.',
|
|
model: defaultModels.gpt53,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
{
|
|
id: 'pattern-group-chat',
|
|
name: 'Collaborative Group Chat',
|
|
description: 'Two or more agents iterate together under a round-robin manager.',
|
|
mode: 'group-chat',
|
|
availability: 'available',
|
|
maxIterations: 5,
|
|
agents: [
|
|
{
|
|
id: 'agent-group-writer',
|
|
name: 'Writer',
|
|
description: 'Produces candidate answers.',
|
|
instructions:
|
|
'You draft a concise, useful answer for the task. On later turns, refine your earlier draft based on peer feedback rather than restarting.',
|
|
model: defaultModels.gpt54,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
{
|
|
id: 'agent-group-reviewer',
|
|
name: 'Reviewer',
|
|
description: 'Critiques and refines the answer.',
|
|
instructions:
|
|
'You review the latest draft and offer specific improvements. Focus on critique and refinement instead of restarting the conversation.',
|
|
model: defaultModels.claude,
|
|
reasoningEffort: 'medium',
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
{
|
|
id: 'pattern-magentic',
|
|
name: 'Magentic Planning',
|
|
description: 'Reserved for future .NET support when Magentic becomes available in C#.',
|
|
mode: 'magentic',
|
|
availability: 'unavailable',
|
|
unavailabilityReason: 'Microsoft Agent Framework currently documents Magentic orchestration as unsupported in C#.',
|
|
maxIterations: 0,
|
|
agents: [
|
|
{
|
|
id: 'agent-magentic-manager',
|
|
name: 'Manager',
|
|
description: 'Future manager agent.',
|
|
instructions: 'Reserved until the .NET runtime supports Magentic orchestration.',
|
|
model: defaultModels.gpt54,
|
|
},
|
|
{
|
|
id: 'agent-magentic-specialist',
|
|
name: 'Specialist',
|
|
description: 'Future specialist agent.',
|
|
instructions: 'Reserved until the .NET runtime supports Magentic orchestration.',
|
|
model: defaultModels.claude,
|
|
},
|
|
],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
];
|
|
}
|
|
|
|
export function validatePatternDefinition(pattern: PatternDefinition): PatternValidationIssue[] {
|
|
const issues: PatternValidationIssue[] = [];
|
|
|
|
if (!pattern.name.trim()) {
|
|
issues.push({ level: 'error', field: 'name', message: 'Pattern name is required.' });
|
|
}
|
|
|
|
if (pattern.availability === 'unavailable') {
|
|
issues.push({
|
|
level: 'error',
|
|
field: 'availability',
|
|
message: pattern.unavailabilityReason ?? 'This orchestration mode is currently unavailable.',
|
|
});
|
|
}
|
|
|
|
if (pattern.agents.length === 0) {
|
|
issues.push({ level: 'error', field: 'agents', message: 'At least one agent is required.' });
|
|
}
|
|
|
|
if (pattern.mode === 'single' && pattern.agents.length !== 1) {
|
|
issues.push({ level: 'error', field: 'agents', message: 'Single-agent chat requires exactly one agent.' });
|
|
}
|
|
|
|
if (pattern.mode === 'handoff' && pattern.agents.length < 2) {
|
|
issues.push({ level: 'error', field: 'agents', message: 'Handoff orchestration requires at least two agents.' });
|
|
}
|
|
|
|
if (pattern.mode === 'group-chat' && pattern.agents.length < 2) {
|
|
issues.push({ level: 'error', field: 'agents', message: 'Group chat requires at least two agents.' });
|
|
}
|
|
|
|
if (pattern.mode === 'magentic') {
|
|
issues.push({
|
|
level: 'error',
|
|
field: 'mode',
|
|
message:
|
|
pattern.unavailabilityReason ??
|
|
'Magentic orchestration is currently documented as unsupported in the .NET Agent Framework.',
|
|
});
|
|
}
|
|
|
|
for (const agent of pattern.agents) {
|
|
if (!agent.name.trim()) {
|
|
issues.push({ level: 'error', field: 'agents.name', message: 'Every agent needs a name.' });
|
|
}
|
|
|
|
if (!agent.instructions.trim()) {
|
|
issues.push({
|
|
level: 'warning',
|
|
field: 'agents.instructions',
|
|
message: `Agent "${agent.name || agent.id}" should have instructions.`,
|
|
});
|
|
}
|
|
|
|
if (!agent.model.trim()) {
|
|
issues.push({
|
|
level: 'error',
|
|
field: 'agents.model',
|
|
message: `Agent "${agent.name || agent.id}" requires a model identifier.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return issues;
|
|
}
|
|
|
|
export function buildSessionTitle(pattern: PatternDefinition, messages: ChatMessageRecord[]): string {
|
|
const firstUserMessage = messages.find((message) => message.role === 'user');
|
|
if (!firstUserMessage) {
|
|
return pattern.name;
|
|
}
|
|
|
|
return firstUserMessage.content.slice(0, 48).trim() || pattern.name;
|
|
}
|