Files
proffesor-for-testing__agen…/tests/voting/orchestrator.test.ts
Profa 108676e602 feat: Phase 2 complete - Instrumentation, evaluation, voting, and CLI
Phase 2 Implementation (95% complete):
- All 12 planned deliverables implemented
- 5/5 validation criteria passing (VC1-VC5)
- 0 TypeScript errors, 0 critical bugs
- Comprehensive documentation (14 files)

Core Infrastructure:
- Agent lifecycle instrumentation (agent.ts, task.ts)
- Memory operation instrumentation (memory.ts) with SwarmMemoryManager integration
- Token tracking and cost calculation (CostTracker)
- 4 clause evaluators (AST, metric, pattern, semantic)
- Multi-agent voting protocol with consensus algorithms
- Distributed trace propagation with OTEL

CLI Commands (Phase 3/4 completed early):
- Telemetry: status, metrics, trace, export-prometheus
- Constitution: validate, list, show, evaluate

Validation:
- VC1: Agent trace retrieval ✅
- VC2: Token tracking ✅
- VC3: Clause evaluation ✅
- VC4: Multi-agent voting ✅
- VC5: Memory operation instrumentation ✅

Tests:
- 5 validation scripts (790+ LOC)
- 13+ unit tests
- Integration tests for SwarmMemoryManager
- All tests passing

Documentation:
- MEMORY-TS-ANALYSIS.md (10+ pages deep analysis)
- PHASE2-FINAL-STATUS.md (comprehensive status)
- BRUTAL-HONESTY-REVIEW-PHASE2.md (technical review)
- SESSION-SUMMARY.md (achievements summary)
- CLI documentation (telemetry + constitution)
- Memory instrumentation guides (integration + examples)

Files Created (60+):
- Instrumentation: 4 files (agent, task, memory, index)
- Evaluators: 6 files (AST, metric, pattern, semantic, base, index)
- Voting: 6 files (protocol, panel, consensus, orchestrator, types, index)
- Validation: 5 scripts
- Documentation: 14+ files
- Tests: 10+ test files
- CLI: 2 command files

What's Left (5%):
- CLI integration testing against real OTEL collector
- Real-world validation with production agent workflows

Ready for Phase 3: ✅ Dashboards & Visualization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 07:52:51 +00:00

406 lines
12 KiB
TypeScript

/**
* Tests for voting orchestrator
* Validates parallel coordination, timeout handling, and result aggregation
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
VotingOrchestrator,
DefaultAgentPool,
DefaultVotingStrategy,
ConsensusFactory
} from '../../src/voting/index.js';
import type {
VotingAgent,
VotingTask,
Vote,
VotingPanelConfig
} from '../../src/voting/types.js';
describe('VotingOrchestrator', () => {
let orchestrator: VotingOrchestrator;
let pool: DefaultAgentPool;
let strategy: DefaultVotingStrategy;
let mockVoteExecutor: ReturnType<typeof vi.fn>;
const createMockAgents = (): VotingAgent[] => [
{
id: 'agent-1',
type: 'test-generator',
expertise: ['unit-testing', 'integration-testing'],
weight: 1.0
},
{
id: 'agent-2',
type: 'coverage-analyzer',
expertise: ['coverage-analysis', 'gap-detection'],
weight: 1.2
},
{
id: 'agent-3',
type: 'quality-gate',
expertise: ['quality-metrics', 'policy-enforcement'],
weight: 1.5
},
{
id: 'agent-4',
type: 'performance-tester',
expertise: ['performance', 'benchmarking'],
weight: 0.9
},
{
id: 'agent-5',
type: 'security-scanner',
expertise: ['security', 'vulnerability-detection'],
weight: 1.3
}
];
const createMockTask = (): VotingTask => ({
id: 'task-1',
type: 'test-generation',
description: 'Generate comprehensive test suite',
context: { sourceFile: 'user-service.ts' },
priority: 'high',
requiredExpertise: ['unit-testing', 'coverage-analysis']
});
beforeEach(() => {
const agents = createMockAgents();
pool = new DefaultAgentPool(agents);
strategy = new DefaultVotingStrategy();
mockVoteExecutor = vi.fn().mockImplementation(
async (agent: VotingAgent, task: VotingTask): Promise<Vote> => {
// Simulate voting delay
await new Promise(resolve => setTimeout(resolve, 10));
return {
agentId: agent.id,
taskId: task.id,
score: 0.8 + Math.random() * 0.2,
confidence: 0.85 + Math.random() * 0.15,
reasoning: `Agent ${agent.id} evaluated task ${task.id}`,
timestamp: new Date()
};
}
);
orchestrator = new VotingOrchestrator(pool, strategy, mockVoteExecutor);
});
describe('Panel Assembly', () => {
it('should assemble panel with required size', async () => {
const config: VotingPanelConfig = {
minPanelSize: 3,
maxPanelSize: 5,
consensusMethod: 'weighted-average',
timeoutMs: 5000,
maxRetries: 3,
retryDelayMs: 100,
parallelExecution: true
};
const result = await orchestrator.assemblePanel(config);
expect(result.panel.length).toBeGreaterThanOrEqual(config.minPanelSize);
expect(result.panel.length).toBeLessThanOrEqual(config.maxPanelSize);
expect(result.assemblyTime).toBeGreaterThan(0);
expect(result.coverage.expertise.length).toBeGreaterThan(0);
});
it('should select agents with matching expertise', async () => {
const config: VotingPanelConfig = {
minPanelSize: 2,
maxPanelSize: 4,
requiredExpertise: ['unit-testing', 'coverage-analysis'],
consensusMethod: 'quorum',
quorumThreshold: 0.67,
timeoutMs: 5000,
maxRetries: 3,
retryDelayMs: 100,
parallelExecution: true
};
const result = await orchestrator.assemblePanel(config);
const hasRequiredExpertise = result.panel.every(agent =>
config.requiredExpertise?.some(e => agent.expertise.includes(e))
);
expect(hasRequiredExpertise).toBe(true);
});
});
describe('Parallel Voting', () => {
it('should distribute task and collect votes in parallel', async () => {
const task = createMockTask();
const config: VotingPanelConfig = {
minPanelSize: 3,
maxPanelSize: 5,
consensusMethod: 'weighted-average',
timeoutMs: 5000,
maxRetries: 3,
retryDelayMs: 100,
parallelExecution: true
};
const panelResult = await orchestrator.assemblePanel(config);
await orchestrator.distributeTask(task, panelResult.panel);
const votes = await orchestrator.collectVotes(task.id, 5000);
expect(votes.length).toBeGreaterThan(0);
expect(votes.length).toBeLessThanOrEqual(panelResult.panel.length);
expect(mockVoteExecutor).toHaveBeenCalledTimes(panelResult.panel.length);
});
it('should handle concurrent voting for multiple agents', async () => {
const task = createMockTask();
const agents = createMockAgents();
await orchestrator.distributeTask(task, agents);
const votes = await orchestrator.collectVotes(task.id, 5000);
// Verify all agents voted
const agentIds = votes.map(v => v.agentId);
expect(agentIds.length).toBe(agents.length);
expect(new Set(agentIds).size).toBe(agents.length); // All unique
});
});
describe('Timeout Handling', () => {
it('should handle vote timeout gracefully', async () => {
// Create slow vote executor
const slowExecutor = vi.fn().mockImplementation(
async (agent: VotingAgent, task: VotingTask): Promise<Vote> => {
if (agent.id === 'agent-3') {
// Simulate timeout for one agent
await new Promise(resolve => setTimeout(resolve, 6000));
}
await new Promise(resolve => setTimeout(resolve, 10));
return {
agentId: agent.id,
taskId: task.id,
score: 0.8,
confidence: 0.85,
reasoning: `Vote from ${agent.id}`,
timestamp: new Date()
};
}
);
const slowOrchestrator = new VotingOrchestrator(
pool,
strategy,
slowExecutor
);
const task = createMockTask();
const agents = createMockAgents().slice(0, 3); // Use 3 agents
await slowOrchestrator.distributeTask(task, agents);
const votes = await slowOrchestrator.collectVotes(task.id, 1000); // Short timeout
// Should collect votes from agents that completed in time
expect(votes.length).toBeGreaterThan(0);
expect(votes.length).toBeLessThan(agents.length);
const metrics = slowOrchestrator.getMetrics();
expect(metrics.timeoutVotes).toBeGreaterThan(0);
});
});
describe('Retry Logic', () => {
it('should retry failed votes', async () => {
let attemptCount = 0;
const flakyExecutor = vi.fn().mockImplementation(
async (agent: VotingAgent, task: VotingTask): Promise<Vote> => {
attemptCount++;
// Fail first attempt, succeed on retry
if (agent.id === 'agent-2' && attemptCount === 1) {
throw new Error('ECONNRESET: Connection reset');
}
await new Promise(resolve => setTimeout(resolve, 10));
return {
agentId: agent.id,
taskId: task.id,
score: 0.8,
confidence: 0.85,
reasoning: `Vote from ${agent.id}`,
timestamp: new Date()
};
}
);
const retryOrchestrator = new VotingOrchestrator(
pool,
strategy,
flakyExecutor
);
const task = createMockTask();
const agent = createMockAgents()[1]; // agent-2
const vote = await retryOrchestrator.retry(agent.id, task.id, 1);
expect(vote).not.toBeNull();
expect(vote?.agentId).toBe(agent.id);
const metrics = retryOrchestrator.getMetrics();
expect(metrics.retryRate).toBeGreaterThan(0);
});
it('should not retry after max attempts', async () => {
const failingExecutor = vi.fn().mockRejectedValue(
new Error('Persistent failure')
);
const retryOrchestrator = new VotingOrchestrator(
pool,
strategy,
failingExecutor
);
const task = createMockTask();
const agent = createMockAgents()[0];
const vote = await retryOrchestrator.retry(agent.id, task.id, 3);
expect(vote).toBeNull();
});
});
describe('Result Aggregation', () => {
it('should aggregate votes using weighted average', async () => {
const votes: Vote[] = [
{
agentId: 'agent-1',
taskId: 'task-1',
score: 0.8,
confidence: 0.9,
reasoning: 'Good coverage',
timestamp: new Date()
},
{
agentId: 'agent-2',
taskId: 'task-1',
score: 0.85,
confidence: 0.95,
reasoning: 'Excellent patterns',
timestamp: new Date()
},
{
agentId: 'agent-3',
taskId: 'task-1',
score: 0.9,
confidence: 0.85,
reasoning: 'Strong quality',
timestamp: new Date()
}
];
const result = orchestrator.aggregateResults(votes, 'weighted-average');
expect(result.consensusReached).toBe(true);
expect(result.finalScore).toBeGreaterThan(0.8);
expect(result.finalScore).toBeLessThan(0.9);
expect(result.votes.length).toBe(3);
expect(result.aggregationMethod).toBe('weighted-average');
});
it('should detect consensus with majority method', async () => {
const votes: Vote[] = [
{
agentId: 'agent-1',
taskId: 'task-1',
score: 0.9,
confidence: 0.9,
reasoning: 'Excellent',
timestamp: new Date()
},
{
agentId: 'agent-2',
taskId: 'task-1',
score: 0.9,
confidence: 0.95,
reasoning: 'Excellent',
timestamp: new Date()
},
{
agentId: 'agent-3',
taskId: 'task-1',
score: 0.5,
confidence: 0.8,
reasoning: 'Needs work',
timestamp: new Date()
}
];
const result = orchestrator.aggregateResults(votes, 'majority');
expect(result.finalScore).toBe(0.9);
expect(result.consensusReached).toBe(true);
});
});
describe('Orchestration Metrics', () => {
it('should track orchestration metrics', async () => {
const task = createMockTask();
const agents = createMockAgents().slice(0, 3);
await orchestrator.distributeTask(task, agents);
await orchestrator.collectVotes(task.id, 5000);
const metrics = orchestrator.getMetrics();
expect(metrics.totalTasks).toBe(1);
expect(metrics.successfulVotes).toBeGreaterThan(0);
expect(metrics.averageExecutionTime).toBeGreaterThan(0);
});
});
describe('Orchestration Logging', () => {
it('should log orchestration events', async () => {
const task = createMockTask();
const config: VotingPanelConfig = {
minPanelSize: 2,
maxPanelSize: 3,
consensusMethod: 'quorum',
quorumThreshold: 0.67,
timeoutMs: 5000,
maxRetries: 3,
retryDelayMs: 100,
parallelExecution: true
};
await orchestrator.assemblePanel(config);
const logs = orchestrator.getLogs();
expect(logs.length).toBeGreaterThan(0);
expect(logs[0].event).toBe('panel-assembled');
expect(logs[0].taskId).toBeDefined();
});
it('should filter logs by task ID', async () => {
const task1 = { ...createMockTask(), id: 'task-1' };
const task2 = { ...createMockTask(), id: 'task-2' };
const agents = createMockAgents().slice(0, 2);
await orchestrator.distributeTask(task1, agents);
await orchestrator.distributeTask(task2, agents);
const task1Logs = orchestrator.getLogs('task-1');
const task2Logs = orchestrator.getLogs('task-2');
expect(task1Logs.every(log => log.taskId === 'task-1')).toBe(true);
expect(task2Logs.every(log => log.taskId === 'task-2')).toBe(true);
});
});
});