aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
612 lines (540 loc) • 20.9 kB
text/typescript
/**
* @file agent-orchestrator.ts
* @description Multi-agent orchestration system for collaborative SDLC artifact generation
*
* Implements UC-004: Multi-Agent Documentation Generation
* - Coordinates multiple specialized agents (Primary Author + Reviewers)
* - Manages review cycles and feedback synthesis
* - Ensures comprehensive artifact quality through parallel review
* - Tracks orchestration state and agent progress
*
* @implements NFR-PERF-004: <30s artifact generation coordination
* @implements NFR-QUAL-001: 100% artifact completeness validation
*/
import { EventEmitter } from 'events';
import path from 'path';
// ============================================================================
// Types and Interfaces
// ============================================================================
export type AgentRole = 'primary-author' | 'reviewer' | 'synthesizer';
export type AgentType =
| 'architecture-designer'
| 'requirements-analyst'
| 'security-architect'
| 'test-architect'
| 'technical-writer'
| 'documentation-synthesizer'
| 'devops-engineer'
| 'performance-engineer';
export type OrchestrationPhase = 'draft' | 'review' | 'synthesis' | 'complete' | 'failed';
export type ReviewStatus = 'approved' | 'approved-with-changes' | 'conditional' | 'rejected';
export interface AgentTask {
agentType: AgentType;
role: AgentRole;
description: string;
inputPaths: string[];
outputPath: string;
dependsOn?: string[]; // Task IDs this task depends on
timeout?: number; // ms
}
export interface AgentResult {
taskId: string;
agentType: AgentType;
success: boolean;
outputPath: string;
duration: number; // ms
error?: string;
}
export interface ReviewResult extends AgentResult {
status: ReviewStatus;
comments: ReviewComment[];
suggestions: string[];
}
export interface ReviewComment {
severity: 'critical' | 'major' | 'minor' | 'info';
section: string;
comment: string;
suggestedFix?: string;
}
export interface OrchestrationPlan {
artifactType: string; // SAD, ADR, Test Plan, etc.
workingDir: string;
primaryAuthor: AgentTask;
reviewers: AgentTask[];
synthesizer: AgentTask;
validation?: AgentTask;
timeout: number; // Total orchestration timeout in ms
}
export interface OrchestrationResult {
plan: OrchestrationPlan;
phase: OrchestrationPhase;
draftResult?: AgentResult;
reviewResults: ReviewResult[];
synthesisResult?: AgentResult;
validationResult?: AgentResult;
finalArtifactPath?: string;
totalDuration: number; // ms
success: boolean;
error?: string;
}
export interface OrchestrationOptions {
workingDir: string;
parallelReviews?: boolean; // Default: true
requireAllApprovals?: boolean; // Default: false (allow approved-with-changes)
maxRetries?: number; // Default: 1
enableValidation?: boolean; // Default: true
}
// ============================================================================
// Agent Orchestrator Class
// ============================================================================
export class AgentOrchestrator extends EventEmitter {
private activeOrchestrations: Map<string, OrchestrationResult> = new Map();
private orchestrationCounter = 0;
constructor() {
super();
}
/**
* Generate a unique orchestration ID
*/
private generateOrchestrationId(): string {
return `orch-${Date.now()}-${++this.orchestrationCounter}`;
}
/**
* Create an orchestration plan for SAD generation
*/
public createSADPlan(options: OrchestrationOptions): OrchestrationPlan {
const workingDir = path.join(options.workingDir, 'architecture', 'sad');
return {
artifactType: 'Software Architecture Document',
workingDir,
primaryAuthor: {
agentType: 'architecture-designer',
role: 'primary-author',
description: 'Create initial SAD draft from requirements',
inputPaths: [
path.join(options.workingDir, 'requirements'),
'~/.local/share/ai-writing-guide/agentic/code/frameworks/sdlc-complete/templates/analysis-design/software-architecture-doc-template.md'
],
outputPath: path.join(workingDir, 'drafts', 'v0.1-primary-draft.md'),
timeout: 300000 // 5 minutes
},
reviewers: [
{
agentType: 'security-architect',
role: 'reviewer',
description: 'Security architecture validation',
inputPaths: [path.join(workingDir, 'drafts', 'v0.1-primary-draft.md')],
outputPath: path.join(workingDir, 'reviews', 'security-review.md'),
dependsOn: ['primary-author'],
timeout: 120000 // 2 minutes
},
{
agentType: 'test-architect',
role: 'reviewer',
description: 'Testability and quality attribute review',
inputPaths: [path.join(workingDir, 'drafts', 'v0.1-primary-draft.md')],
outputPath: path.join(workingDir, 'reviews', 'testability-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
},
{
agentType: 'requirements-analyst',
role: 'reviewer',
description: 'Requirements traceability validation',
inputPaths: [
path.join(workingDir, 'drafts', 'v0.1-primary-draft.md'),
path.join(options.workingDir, 'requirements')
],
outputPath: path.join(workingDir, 'reviews', 'requirements-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
},
{
agentType: 'technical-writer',
role: 'reviewer',
description: 'Clarity, consistency, and style review',
inputPaths: [path.join(workingDir, 'drafts', 'v0.1-primary-draft.md')],
outputPath: path.join(workingDir, 'reviews', 'style-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
}
],
synthesizer: {
agentType: 'documentation-synthesizer',
role: 'synthesizer',
description: 'Merge all review feedback into final SAD',
inputPaths: [
path.join(workingDir, 'drafts', 'v0.1-primary-draft.md'),
path.join(workingDir, 'reviews')
],
outputPath: path.join(options.workingDir, 'architecture', 'software-architecture-doc.md'),
dependsOn: ['reviewers'],
timeout: 180000 // 3 minutes
},
timeout: 900000 // 15 minutes total
};
}
/**
* Create an orchestration plan for ADR generation
*/
public createADRPlan(options: OrchestrationOptions, decision: string): OrchestrationPlan {
const workingDir = path.join(options.workingDir, 'architecture', 'adrs');
const adrNumber = this.getNextADRNumber(workingDir);
const filename = `ADR-${adrNumber.toString().padStart(3, '0')}-${this.slugify(decision)}.md`;
return {
artifactType: 'Architecture Decision Record',
workingDir,
primaryAuthor: {
agentType: 'architecture-designer',
role: 'primary-author',
description: `Create ADR for: ${decision}`,
inputPaths: [
path.join(options.workingDir, 'architecture', 'software-architecture-doc.md'),
'~/.local/share/ai-writing-guide/agentic/code/frameworks/sdlc-complete/templates/analysis-design/adr-template.md'
],
outputPath: path.join(workingDir, 'drafts', `draft-${filename}`),
timeout: 180000 // 3 minutes
},
reviewers: [
{
agentType: 'security-architect',
role: 'reviewer',
description: 'Security implications review',
inputPaths: [path.join(workingDir, 'drafts', `draft-${filename}`)],
outputPath: path.join(workingDir, 'reviews', `security-${filename}`),
dependsOn: ['primary-author'],
timeout: 60000 // 1 minute
},
{
agentType: 'technical-writer',
role: 'reviewer',
description: 'Clarity and completeness review',
inputPaths: [path.join(workingDir, 'drafts', `draft-${filename}`)],
outputPath: path.join(workingDir, 'reviews', `style-${filename}`),
dependsOn: ['primary-author'],
timeout: 60000
}
],
synthesizer: {
agentType: 'documentation-synthesizer',
role: 'synthesizer',
description: 'Finalize ADR with review feedback',
inputPaths: [
path.join(workingDir, 'drafts', `draft-${filename}`),
path.join(workingDir, 'reviews')
],
outputPath: path.join(workingDir, filename),
dependsOn: ['reviewers'],
timeout: 120000 // 2 minutes
},
timeout: 420000 // 7 minutes total
};
}
/**
* Create an orchestration plan for Master Test Plan generation
*/
public createTestPlanPlan(options: OrchestrationOptions): OrchestrationPlan {
const workingDir = path.join(options.workingDir, 'testing');
return {
artifactType: 'Master Test Plan',
workingDir,
primaryAuthor: {
agentType: 'test-architect',
role: 'primary-author',
description: 'Create Master Test Plan from requirements and SAD',
inputPaths: [
path.join(options.workingDir, 'requirements'),
path.join(options.workingDir, 'architecture', 'software-architecture-doc.md'),
'~/.local/share/ai-writing-guide/agentic/code/frameworks/sdlc-complete/templates/testing-qa/master-test-plan-template.md'
],
outputPath: path.join(workingDir, 'drafts', 'v0.1-master-test-plan.md'),
timeout: 300000 // 5 minutes
},
reviewers: [
{
agentType: 'requirements-analyst',
role: 'reviewer',
description: 'Requirements coverage validation',
inputPaths: [
path.join(workingDir, 'drafts', 'v0.1-master-test-plan.md'),
path.join(options.workingDir, 'requirements')
],
outputPath: path.join(workingDir, 'reviews', 'requirements-coverage-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
},
{
agentType: 'security-architect',
role: 'reviewer',
description: 'Security testing strategy review',
inputPaths: [path.join(workingDir, 'drafts', 'v0.1-master-test-plan.md')],
outputPath: path.join(workingDir, 'reviews', 'security-testing-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
},
{
agentType: 'performance-engineer',
role: 'reviewer',
description: 'Performance testing strategy review',
inputPaths: [path.join(workingDir, 'drafts', 'v0.1-master-test-plan.md')],
outputPath: path.join(workingDir, 'reviews', 'performance-testing-review.md'),
dependsOn: ['primary-author'],
timeout: 120000
}
],
synthesizer: {
agentType: 'documentation-synthesizer',
role: 'synthesizer',
description: 'Finalize Master Test Plan with all feedback',
inputPaths: [
path.join(workingDir, 'drafts', 'v0.1-master-test-plan.md'),
path.join(workingDir, 'reviews')
],
outputPath: path.join(workingDir, 'master-test-plan.md'),
dependsOn: ['reviewers'],
timeout: 180000
},
timeout: 840000 // 14 minutes total
};
}
/**
* Execute an orchestration plan
* This simulates agent execution - in production, this would dispatch to actual agent Task tool
*/
public async executeOrchestration(
plan: OrchestrationPlan,
options: OrchestrationOptions = { workingDir: '.' }
): Promise<OrchestrationResult> {
const orchestrationId = this.generateOrchestrationId();
const startTime = Date.now();
const result: OrchestrationResult = {
plan,
phase: 'draft',
reviewResults: [],
totalDuration: 0,
success: false
};
this.activeOrchestrations.set(orchestrationId, result);
this.emit('orchestration:start', { orchestrationId, plan });
try {
// Phase 1: Draft Generation (Primary Author)
result.phase = 'draft';
this.emit('phase:start', { orchestrationId, phase: 'draft', task: plan.primaryAuthor });
const draftResult = await this.executeAgentTask(plan.primaryAuthor);
result.draftResult = draftResult;
if (!draftResult.success) {
throw new Error(`Draft generation failed: ${draftResult.error}`);
}
this.emit('phase:complete', { orchestrationId, phase: 'draft', result: draftResult });
// Phase 2: Parallel Review
result.phase = 'review';
this.emit('phase:start', { orchestrationId, phase: 'review', tasks: plan.reviewers });
const reviewResults = options.parallelReviews !== false
? await Promise.all(plan.reviewers.map(reviewer => this.executeReviewTask(reviewer)))
: await this.executeReviewsSequentially(plan.reviewers);
result.reviewResults = reviewResults;
// Check review approvals
const rejectedReviews = reviewResults.filter(r => r.status === 'rejected');
if (rejectedReviews.length > 0 && options.requireAllApprovals) {
throw new Error(`Rejected by reviewers: ${rejectedReviews.map(r => r.agentType).join(', ')}`);
}
this.emit('phase:complete', { orchestrationId, phase: 'review', results: reviewResults });
// Phase 3: Synthesis
result.phase = 'synthesis';
this.emit('phase:start', { orchestrationId, phase: 'synthesis', task: plan.synthesizer });
const synthesisResult = await this.executeAgentTask(plan.synthesizer);
result.synthesisResult = synthesisResult;
if (!synthesisResult.success) {
throw new Error(`Synthesis failed: ${synthesisResult.error}`);
}
result.finalArtifactPath = synthesisResult.outputPath;
this.emit('phase:complete', { orchestrationId, phase: 'synthesis', result: synthesisResult });
// Phase 4: Validation (optional)
if (plan.validation && options.enableValidation !== false) {
this.emit('phase:start', { orchestrationId, phase: 'validation', task: plan.validation });
const validationResult = await this.executeAgentTask(plan.validation);
result.validationResult = validationResult;
if (!validationResult.success) {
throw new Error(`Validation failed: ${validationResult.error}`);
}
this.emit('phase:complete', { orchestrationId, phase: 'validation', result: validationResult });
}
// Complete
result.phase = 'complete';
result.success = true;
result.totalDuration = Date.now() - startTime;
this.emit('orchestration:complete', { orchestrationId, result });
return result;
} catch (error) {
result.phase = 'failed';
result.success = false;
result.error = error instanceof Error ? error.message : String(error);
result.totalDuration = Date.now() - startTime;
this.emit('orchestration:failed', { orchestrationId, result, error });
return result;
} finally {
this.activeOrchestrations.delete(orchestrationId);
}
}
/**
* Execute a single agent task (simulated)
* In production, this would call the Claude Code Task tool with appropriate subagent_type
*/
private async executeAgentTask(task: AgentTask): Promise<AgentResult> {
const startTime = Date.now();
const taskId = `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
try {
// Simulate agent execution time
await this.delay(Math.min(task.timeout || 60000, 1000)); // 1s for tests, would be longer in production
// Simulate success
return {
taskId,
agentType: task.agentType,
success: true,
outputPath: task.outputPath,
duration: Date.now() - startTime
};
} catch (error) {
return {
taskId,
agentType: task.agentType,
success: false,
outputPath: task.outputPath,
duration: Date.now() - startTime,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Execute a review task (simulated)
*/
private async executeReviewTask(task: AgentTask): Promise<ReviewResult> {
const baseResult = await this.executeAgentTask(task);
// Simulate review outcomes
const statuses: ReviewStatus[] = ['approved', 'approved-with-changes', 'conditional'];
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
return {
...baseResult,
status: randomStatus,
comments: this.generateMockComments(task.agentType, randomStatus),
suggestions: this.generateMockSuggestions(task.agentType)
};
}
/**
* Execute reviews sequentially
*/
private async executeReviewsSequentially(reviewers: AgentTask[]): Promise<ReviewResult[]> {
const results: ReviewResult[] = [];
for (const reviewer of reviewers) {
const result = await this.executeReviewTask(reviewer);
results.push(result);
}
return results;
}
/**
* Generate mock review comments (for testing/simulation)
*/
private generateMockComments(agentType: AgentType, status: ReviewStatus): ReviewComment[] {
if (status === 'approved') return [];
const comments: ReviewComment[] = [];
switch (agentType) {
case 'security-architect':
comments.push({
severity: 'major',
section: 'Security Architecture',
comment: 'Consider adding authentication details',
suggestedFix: 'Add section on JWT token handling and session management'
});
break;
case 'test-architect':
comments.push({
severity: 'minor',
section: 'Testing Strategy',
comment: 'Add performance test scenarios',
suggestedFix: 'Include load testing targets (e.g., 1000 concurrent users)'
});
break;
case 'requirements-analyst':
comments.push({
severity: 'major',
section: 'Requirements Traceability',
comment: 'Missing UC-003 mapping',
suggestedFix: 'Add traceability link to UC-003 in Component Design section'
});
break;
case 'technical-writer':
comments.push({
severity: 'minor',
section: 'Style and Clarity',
comment: 'Use consistent heading levels',
suggestedFix: 'Convert all subsections to use ### for consistency'
});
break;
}
return comments;
}
/**
* Generate mock suggestions (for testing/simulation)
*/
private generateMockSuggestions(agentType: AgentType): string[] {
const suggestions: Record<AgentType, string[]> = {
'security-architect': ['Add threat modeling section', 'Include security compliance matrix'],
'test-architect': ['Add regression test strategy', 'Include test automation framework details'],
'requirements-analyst': ['Add NFR traceability matrix', 'Link to user story mapping'],
'technical-writer': ['Add table of contents', 'Include glossary of terms'],
'architecture-designer': ['Add deployment view', 'Include technology stack rationale'],
'documentation-synthesizer': ['Consolidate duplicate sections', 'Add executive summary'],
'devops-engineer': ['Add CI/CD pipeline diagram', 'Include deployment runbook reference'],
'performance-engineer': ['Add performance targets', 'Include capacity planning details']
};
return suggestions[agentType] || [];
}
/**
* Get next ADR number from directory
*/
private getNextADRNumber(_adrDir: string): number {
try {
// This would scan the directory in production
// For now, return a mock number
return 1;
} catch {
return 1;
}
}
/**
* Convert decision text to URL-friendly slug
*/
private slugify(text: string): string {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
/**
* Delay helper for simulation
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get active orchestrations count
*/
public getActiveOrchestrations(): Map<string, OrchestrationResult> {
return new Map(this.activeOrchestrations);
}
/**
* Cancel an active orchestration
*/
public cancelOrchestration(orchestrationId: string): boolean {
const orchestration = this.activeOrchestrations.get(orchestrationId);
if (!orchestration) return false;
orchestration.phase = 'failed';
orchestration.error = 'Cancelled by user';
this.activeOrchestrations.delete(orchestrationId);
this.emit('orchestration:cancelled', { orchestrationId });
return true;
}
}
export default AgentOrchestrator;