Add Delegation System with 3rd Party AI Tool Integration
NEW: Delegation System (v1.2.0) - Request Classifier for fast request analysis (<50ms) - Agent Pool Manager with auto-scaling (8 agent types) - Delegation Engine with 4 strategies (full, parallel, hierarchical, hybrid) - Progress Streamer for real-time updates - Context Handoff Protocol for inter-agent communication - Quality Gate with confidence thresholds and auto-escalation NEW: 3rd Party Integration Adapters - OpenClaw adapter with parallel execution support - Claude Code CLI adapter with tool registration - Generic adapter for custom integrations - Standardized IntegrationAdapter interface Agent Types Added: - fast-responder (quick answers < 2s) - explorer (code navigation) - researcher (deep analysis) - coder (implementation) - reviewer (quality checks) - planner (architecture) - executor (commands) - analyzer (debugging) Tests: All 6 tests passing This project was 100% autonomously built by Z.AI GLM-5
This commit is contained in:
370
delegation-system/core/context-handoff.ts
Normal file
370
delegation-system/core/context-handoff.ts
Normal file
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* Context Handoff Protocol
|
||||
*
|
||||
* Manages context transfer between agents for seamless delegation.
|
||||
* Ensures proper serialization, validation, and cleanup of handoffs.
|
||||
*/
|
||||
|
||||
import {
|
||||
ContextHandoff,
|
||||
HandoffResult,
|
||||
ConversationTurn,
|
||||
DelegationContext
|
||||
} from '../core/types';
|
||||
|
||||
// ============================================================================
|
||||
// Handoff Configuration
|
||||
// ============================================================================
|
||||
|
||||
interface HandoffConfig {
|
||||
defaultTimeout: number;
|
||||
defaultTimeLimit: number;
|
||||
maxHandoffs: number;
|
||||
cleanupInterval: number;
|
||||
handoffTTL: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: HandoffConfig = {
|
||||
defaultTimeout: 30000,
|
||||
defaultTimeLimit: 60000,
|
||||
maxHandoffs: 100,
|
||||
cleanupInterval: 60000,
|
||||
handoffTTL: 300000 // 5 minutes
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Context Handoff Manager Class
|
||||
// ============================================================================
|
||||
|
||||
export class ContextHandoffManager {
|
||||
private config: HandoffConfig;
|
||||
private activeHandoffs: Map<string, ContextHandoff> = new Map();
|
||||
private completedHandoffs: Map<string, HandoffResult> = new Map();
|
||||
private cleanupTimer?: NodeJS.Timeout;
|
||||
private handoffCounter: number = 0;
|
||||
|
||||
constructor(config: Partial<HandoffConfig> = {}) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
this.startCleanupTimer();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Handoff Creation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a new context handoff
|
||||
*/
|
||||
createHandoff(options: {
|
||||
sourceAgent: string;
|
||||
targetAgent: string;
|
||||
request: any;
|
||||
files?: Record<string, string>;
|
||||
conversationHistory?: ConversationTurn[];
|
||||
workspace?: string;
|
||||
metadata?: Record<string, any>;
|
||||
timeLimit?: number;
|
||||
scope?: 'narrow' | 'medium' | 'broad';
|
||||
qualityLevel?: 'fast' | 'balanced' | 'thorough';
|
||||
callbackEndpoint?: string;
|
||||
callbackTimeout?: number;
|
||||
}): ContextHandoff {
|
||||
const id = this.generateHandoffId();
|
||||
|
||||
const handoff: ContextHandoff = {
|
||||
id,
|
||||
sourceAgent: options.sourceAgent,
|
||||
targetAgent: options.targetAgent,
|
||||
request: options.request,
|
||||
context: {
|
||||
files: options.files || {},
|
||||
conversationHistory: options.conversationHistory || [],
|
||||
workspace: options.workspace || '',
|
||||
metadata: options.metadata || {}
|
||||
},
|
||||
constraints: {
|
||||
timeLimit: options.timeLimit || this.config.defaultTimeLimit,
|
||||
scope: options.scope || 'medium',
|
||||
qualityLevel: options.qualityLevel || 'balanced'
|
||||
},
|
||||
callback: {
|
||||
endpoint: options.callbackEndpoint || '',
|
||||
timeout: options.callbackTimeout || this.config.defaultTimeout,
|
||||
retries: 3
|
||||
},
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + this.config.handoffTTL)
|
||||
};
|
||||
|
||||
this.activeHandoffs.set(id, handoff);
|
||||
this.handoffCounter++;
|
||||
|
||||
return handoff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create handoff from delegation context
|
||||
*/
|
||||
createFromDelegation(
|
||||
delegationContext: DelegationContext,
|
||||
sourceAgent: string,
|
||||
targetAgent: string
|
||||
): ContextHandoff {
|
||||
return this.createHandoff({
|
||||
sourceAgent,
|
||||
targetAgent,
|
||||
request: delegationContext.originalRequest,
|
||||
metadata: {
|
||||
requestId: delegationContext.requestId,
|
||||
classification: delegationContext.classification,
|
||||
strategy: delegationContext.delegationDecision.strategy
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Handoff Execution
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get a handoff by ID
|
||||
*/
|
||||
getHandoff(id: string): ContextHandoff | undefined {
|
||||
return this.activeHandoffs.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a handoff with result
|
||||
*/
|
||||
completeHandoff(
|
||||
handoffId: string,
|
||||
result: {
|
||||
success: boolean;
|
||||
output?: any;
|
||||
error?: string;
|
||||
processingTime: number;
|
||||
}
|
||||
): HandoffResult {
|
||||
const handoff = this.activeHandoffs.get(handoffId);
|
||||
|
||||
const handoffResult: HandoffResult = {
|
||||
handoffId,
|
||||
success: result.success,
|
||||
result: result.output,
|
||||
error: result.error,
|
||||
processingTime: result.processingTime
|
||||
};
|
||||
|
||||
// Store result
|
||||
this.completedHandoffs.set(handoffId, handoffResult);
|
||||
|
||||
// Remove from active
|
||||
if (handoff) {
|
||||
this.activeHandoffs.delete(handoffId);
|
||||
|
||||
// Execute callback if configured
|
||||
if (handoff.callback.endpoint) {
|
||||
this.executeCallback(handoff, handoffResult);
|
||||
}
|
||||
}
|
||||
|
||||
return handoffResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a handoff
|
||||
*/
|
||||
cancelHandoff(handoffId: string, reason: string): void {
|
||||
const handoff = this.activeHandoffs.get(handoffId);
|
||||
|
||||
if (handoff) {
|
||||
this.completeHandoff(handoffId, {
|
||||
success: false,
|
||||
error: `Cancelled: ${reason}`,
|
||||
processingTime: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callback Execution
|
||||
// ============================================================================
|
||||
|
||||
private async executeCallback(
|
||||
handoff: ContextHandoff,
|
||||
result: HandoffResult
|
||||
): Promise<void> {
|
||||
const { endpoint, timeout, retries } = handoff.callback;
|
||||
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
handoffId: handoff.id,
|
||||
sourceAgent: handoff.sourceAgent,
|
||||
targetAgent: handoff.targetAgent,
|
||||
result
|
||||
}),
|
||||
signal: AbortSignal.timeout(timeout)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Callback attempt ${attempt + 1} failed:`, error);
|
||||
|
||||
// Wait before retry
|
||||
if (attempt < retries - 1) {
|
||||
await this.sleep(1000 * (attempt + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Context Serialization
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Serialize handoff for transmission
|
||||
*/
|
||||
serialize(handoff: ContextHandoff): string {
|
||||
return JSON.stringify({
|
||||
id: handoff.id,
|
||||
sourceAgent: handoff.sourceAgent,
|
||||
targetAgent: handoff.targetAgent,
|
||||
request: handoff.request,
|
||||
context: handoff.context,
|
||||
constraints: handoff.constraints,
|
||||
callback: handoff.callback,
|
||||
createdAt: handoff.createdAt.toISOString(),
|
||||
expiresAt: handoff.expiresAt.toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize handoff from transmission
|
||||
*/
|
||||
deserialize(data: string): ContextHandoff {
|
||||
const parsed = JSON.parse(data);
|
||||
return {
|
||||
...parsed,
|
||||
createdAt: new Date(parsed.createdAt),
|
||||
expiresAt: new Date(parsed.expiresAt)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate handoff integrity
|
||||
*/
|
||||
validate(handoff: ContextHandoff): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!handoff.id) errors.push('Missing handoff ID');
|
||||
if (!handoff.sourceAgent) errors.push('Missing source agent');
|
||||
if (!handoff.targetAgent) errors.push('Missing target agent');
|
||||
if (!handoff.request) errors.push('Missing request');
|
||||
|
||||
if (handoff.constraints.timeLimit <= 0) {
|
||||
errors.push('Invalid time limit');
|
||||
}
|
||||
|
||||
if (handoff.expiresAt <= new Date()) {
|
||||
errors.push('Handoff has expired');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cleanup
|
||||
// ============================================================================
|
||||
|
||||
private startCleanupTimer(): void {
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.cleanup();
|
||||
}, this.config.cleanupInterval);
|
||||
}
|
||||
|
||||
private cleanup(): void {
|
||||
const now = new Date();
|
||||
|
||||
// Remove expired handoffs
|
||||
for (const [id, handoff] of this.activeHandoffs) {
|
||||
if (handoff.expiresAt <= now) {
|
||||
this.cancelHandoff(id, 'Expired');
|
||||
}
|
||||
}
|
||||
|
||||
// Limit completed handoffs
|
||||
if (this.completedHandoffs.size > this.config.maxHandoffs) {
|
||||
const keys = Array.from(this.completedHandoffs.keys());
|
||||
const toRemove = keys.slice(0, this.completedHandoffs.size - this.config.maxHandoffs);
|
||||
toRemove.forEach(key => this.completedHandoffs.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop cleanup timer and clear all
|
||||
*/
|
||||
shutdown(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
}
|
||||
this.activeHandoffs.clear();
|
||||
this.completedHandoffs.clear();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Statistics
|
||||
// ============================================================================
|
||||
|
||||
getStats(): {
|
||||
active: number;
|
||||
completed: number;
|
||||
totalCreated: number;
|
||||
successRate: number;
|
||||
averageProcessingTime: number;
|
||||
} {
|
||||
const completed = Array.from(this.completedHandoffs.values());
|
||||
const successful = completed.filter(r => r.success);
|
||||
|
||||
return {
|
||||
active: this.activeHandoffs.size,
|
||||
completed: completed.length,
|
||||
totalCreated: this.handoffCounter,
|
||||
successRate: completed.length > 0 ? successful.length / completed.length : 0,
|
||||
averageProcessingTime: completed.length > 0
|
||||
? completed.reduce((sum, r) => sum + r.processingTime, 0) / completed.length
|
||||
: 0
|
||||
};
|
||||
}
|
||||
|
||||
private generateHandoffId(): string {
|
||||
return `handoff-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Factory Function
|
||||
// ============================================================================
|
||||
|
||||
export function createContextHandoffManager(config?: Partial<HandoffConfig>): ContextHandoffManager {
|
||||
return new ContextHandoffManager(config);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export
|
||||
// ============================================================================
|
||||
|
||||
export default ContextHandoffManager;
|
||||
642
delegation-system/core/delegation-engine.ts
Normal file
642
delegation-system/core/delegation-engine.ts
Normal file
@@ -0,0 +1,642 @@
|
||||
/**
|
||||
* Delegation Engine
|
||||
*
|
||||
* Makes intelligent decisions about when and how to delegate requests.
|
||||
* Routes requests to appropriate agents based on classification and pool state.
|
||||
*/
|
||||
|
||||
import {
|
||||
DelegationDecision,
|
||||
DelegationStrategy,
|
||||
DelegationContext,
|
||||
DelegationResult,
|
||||
RequestClassification,
|
||||
ClassifiableRequest,
|
||||
AgentType,
|
||||
PoolAgent
|
||||
} from '../core/types';
|
||||
import { RequestClassifier } from '../core/request-classifier';
|
||||
import { AgentPoolManager } from '../pool/agent-pool-manager';
|
||||
|
||||
// ============================================================================
|
||||
// Delegation Configuration
|
||||
// ============================================================================
|
||||
|
||||
interface DelegationConfig {
|
||||
// Main agent load thresholds
|
||||
mainAgentBusyThreshold: number; // 0-1, above this = delegate
|
||||
mainAgentQueueThreshold: number; // max queue before delegation
|
||||
|
||||
// Delegation preferences
|
||||
preferDelegation: boolean;
|
||||
maxDelegationTime: number; // ms before fallback
|
||||
parallelThreshold: number; // complexity score for parallel delegation
|
||||
|
||||
// Fallback settings
|
||||
fallbackToMain: boolean;
|
||||
escalationEnabled: boolean;
|
||||
escalationThreshold: number; // confidence below this triggers escalation
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: DelegationConfig = {
|
||||
mainAgentBusyThreshold: 0.7,
|
||||
mainAgentQueueThreshold: 5,
|
||||
preferDelegation: true,
|
||||
maxDelegationTime: 60000,
|
||||
parallelThreshold: 0.6,
|
||||
fallbackToMain: true,
|
||||
escalationEnabled: true,
|
||||
escalationThreshold: 0.7
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Main Agent State (simulated)
|
||||
// ============================================================================
|
||||
|
||||
interface MainAgentState {
|
||||
load: number; // 0-1
|
||||
queueLength: number;
|
||||
currentTask?: string;
|
||||
averageResponseTime: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Delegation Engine Class
|
||||
// ============================================================================
|
||||
|
||||
export class DelegationEngine {
|
||||
private config: DelegationConfig;
|
||||
private classifier: RequestClassifier;
|
||||
private poolManager: AgentPoolManager;
|
||||
private mainAgentState: MainAgentState;
|
||||
private activeDelegations: Map<string, DelegationContext> = new Map();
|
||||
private delegationHistory: DelegationContext[] = [];
|
||||
private maxHistorySize: number = 1000;
|
||||
|
||||
constructor(
|
||||
classifier: RequestClassifier,
|
||||
poolManager: AgentPoolManager,
|
||||
config: Partial<DelegationConfig> = {}
|
||||
) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
this.classifier = classifier;
|
||||
this.poolManager = poolManager;
|
||||
this.mainAgentState = {
|
||||
load: 0,
|
||||
queueLength: 0,
|
||||
averageResponseTime: 5000
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Delegation Decision
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Make a delegation decision for a request
|
||||
*/
|
||||
async makeDecision(
|
||||
request: ClassifiableRequest,
|
||||
classification?: RequestClassification
|
||||
): Promise<DelegationDecision> {
|
||||
// Classify if not provided
|
||||
if (!classification) {
|
||||
classification = await this.classifier.classify(request);
|
||||
}
|
||||
|
||||
// Get current state
|
||||
const poolStats = this.poolManager.getPoolStats();
|
||||
|
||||
// Decision tree
|
||||
const decision = this.evaluateDelegation(request, classification, poolStats);
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
private evaluateDelegation(
|
||||
request: ClassifiableRequest,
|
||||
classification: RequestClassification,
|
||||
poolStats: ReturnType<AgentPoolManager['getPoolStats']>
|
||||
): DelegationDecision {
|
||||
const mainAgentLoad = this.mainAgentState.load;
|
||||
const mainAgentQueue = this.mainAgentState.queueLength;
|
||||
|
||||
// Check if can delegate
|
||||
if (!classification.canDelegate) {
|
||||
return this.noDelegation(classification, 'Request cannot be delegated');
|
||||
}
|
||||
|
||||
// Check if pool has available agents
|
||||
if (poolStats.idleCount === 0 && poolStats.coolingDownCount === 0) {
|
||||
if (this.config.fallbackToMain) {
|
||||
return this.noDelegation(classification, 'No agents available in pool');
|
||||
}
|
||||
return this.queueDelegation(classification, 'Waiting for agent availability');
|
||||
}
|
||||
|
||||
// Main agent is lightly loaded - process directly
|
||||
if (mainAgentLoad < 0.5 && mainAgentQueue < 3) {
|
||||
if (!this.config.preferDelegation || classification.complexity === 'complex') {
|
||||
return this.noDelegation(classification, 'Main agent available');
|
||||
}
|
||||
}
|
||||
|
||||
// Determine delegation strategy
|
||||
const strategy = this.determineStrategy(classification, poolStats);
|
||||
const targetAgents = this.selectAgents(classification, strategy, poolStats);
|
||||
const estimatedCompletion = this.estimateCompletion(classification, strategy, poolStats);
|
||||
|
||||
return {
|
||||
shouldDelegate: true,
|
||||
strategy,
|
||||
targetAgents,
|
||||
estimatedCompletion,
|
||||
reason: this.getDelegationReason(mainAgentLoad, poolStats),
|
||||
fallbackPlan: this.config.fallbackToMain ? 'main-agent' : 'queue',
|
||||
requiresCallback: strategy === 'hierarchical' || classification.complexity === 'complex'
|
||||
};
|
||||
}
|
||||
|
||||
private determineStrategy(
|
||||
classification: RequestClassification,
|
||||
poolStats: ReturnType<AgentPoolManager['getPoolStats']>
|
||||
): DelegationStrategy {
|
||||
// Complex requests with high confidence -> hierarchical
|
||||
if (classification.complexity === 'complex' && classification.confidence > 0.8) {
|
||||
return 'hierarchical';
|
||||
}
|
||||
|
||||
// Multiple files or high complexity score -> parallel
|
||||
if (classification.contextRequirements.files > 2 ||
|
||||
classification.score > this.config.parallelThreshold) {
|
||||
if (poolStats.idleCount >= 2) {
|
||||
return 'parallel';
|
||||
}
|
||||
}
|
||||
|
||||
// Quick requests -> full delegation
|
||||
if (classification.complexity === 'quick') {
|
||||
return 'full';
|
||||
}
|
||||
|
||||
// Streaming requests with progress -> hybrid
|
||||
if (classification.complexity === 'streaming') {
|
||||
return 'hybrid';
|
||||
}
|
||||
|
||||
// Default to full delegation
|
||||
return 'full';
|
||||
}
|
||||
|
||||
private selectAgents(
|
||||
classification: RequestClassification,
|
||||
strategy: DelegationStrategy,
|
||||
poolStats: ReturnType<AgentPoolManager['getPoolStats']>
|
||||
): AgentType[] {
|
||||
const primary = classification.recommendedAgent;
|
||||
const agents: AgentType[] = [];
|
||||
|
||||
switch (strategy) {
|
||||
case 'full':
|
||||
agents.push(primary);
|
||||
break;
|
||||
|
||||
case 'parallel':
|
||||
// Add primary agent and complementary agents
|
||||
agents.push(primary);
|
||||
if (classification.requiredCapabilities.includes('security')) {
|
||||
agents.push('reviewer');
|
||||
}
|
||||
if (classification.contextRequirements.files > 3) {
|
||||
agents.push('explorer');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'hierarchical':
|
||||
// Planner oversees execution
|
||||
agents.push('planner', primary);
|
||||
if (classification.requiredCapabilities.includes('security')) {
|
||||
agents.push('reviewer');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'hybrid':
|
||||
agents.push(primary);
|
||||
// Add support agents if available
|
||||
if (poolStats.idleCount > 2) {
|
||||
agents.push('fast-responder');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return agents;
|
||||
}
|
||||
|
||||
private estimateCompletion(
|
||||
classification: RequestClassification,
|
||||
strategy: DelegationStrategy,
|
||||
poolStats: ReturnType<AgentPoolManager['getPoolStats']>
|
||||
): number {
|
||||
let baseTime = classification.estimatedTime;
|
||||
|
||||
// Adjust for strategy
|
||||
switch (strategy) {
|
||||
case 'parallel':
|
||||
// Parallel is faster but has coordination overhead
|
||||
baseTime = baseTime * 0.6 + 500;
|
||||
break;
|
||||
case 'hierarchical':
|
||||
// Hierarchical has communication overhead
|
||||
baseTime = baseTime * 1.3;
|
||||
break;
|
||||
case 'hybrid':
|
||||
baseTime = baseTime * 0.8;
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust for pool availability
|
||||
const availabilityFactor = 1 + (1 - poolStats.availablePercentage) * 0.5;
|
||||
|
||||
return Math.round(baseTime * availabilityFactor);
|
||||
}
|
||||
|
||||
private getDelegationReason(
|
||||
mainAgentLoad: number,
|
||||
poolStats: ReturnType<AgentPoolManager['getPoolStats']>
|
||||
): string {
|
||||
if (mainAgentLoad > 0.8) {
|
||||
return 'Main agent at high capacity';
|
||||
}
|
||||
if (poolStats.busyPercentage > 0.7) {
|
||||
return 'Optimal for parallel processing';
|
||||
}
|
||||
return 'Fast delegation path available';
|
||||
}
|
||||
|
||||
private noDelegation(classification: RequestClassification, reason: string): DelegationDecision {
|
||||
return {
|
||||
shouldDelegate: false,
|
||||
strategy: 'full',
|
||||
targetAgents: [classification.recommendedAgent],
|
||||
estimatedCompletion: classification.estimatedTime,
|
||||
reason,
|
||||
requiresCallback: false
|
||||
};
|
||||
}
|
||||
|
||||
private queueDelegation(classification: RequestClassification, reason: string): DelegationDecision {
|
||||
return {
|
||||
shouldDelegate: true,
|
||||
strategy: 'full',
|
||||
targetAgents: [classification.recommendedAgent],
|
||||
estimatedCompletion: classification.estimatedTime + 10000, // Add queue time
|
||||
reason,
|
||||
fallbackPlan: 'queue',
|
||||
requiresCallback: true
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Delegation Execution
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Execute a delegation
|
||||
*/
|
||||
async executeDelegation(
|
||||
request: ClassifiableRequest,
|
||||
decision: DelegationDecision,
|
||||
executor: (agentType: AgentType, agentId: string, request: ClassifiableRequest) => Promise<any>
|
||||
): Promise<DelegationResult> {
|
||||
const requestId = this.generateRequestId();
|
||||
const startTime = Date.now();
|
||||
|
||||
// Create delegation context
|
||||
const context: DelegationContext = {
|
||||
requestId,
|
||||
originalRequest: request,
|
||||
classification: await this.classifier.classify(request),
|
||||
delegationDecision: decision,
|
||||
assignedAgents: [],
|
||||
status: 'in-progress',
|
||||
startTime: new Date()
|
||||
};
|
||||
|
||||
this.activeDelegations.set(requestId, context);
|
||||
|
||||
try {
|
||||
let result: any;
|
||||
const agentsUsed: string[] = [];
|
||||
|
||||
switch (decision.strategy) {
|
||||
case 'full':
|
||||
result = await this.executeFull(request, decision, executor, agentsUsed);
|
||||
break;
|
||||
case 'parallel':
|
||||
result = await this.executeParallel(request, decision, executor, agentsUsed);
|
||||
break;
|
||||
case 'hierarchical':
|
||||
result = await this.executeHierarchical(request, decision, executor, agentsUsed);
|
||||
break;
|
||||
case 'hybrid':
|
||||
result = await this.executeHybrid(request, decision, executor, agentsUsed);
|
||||
break;
|
||||
}
|
||||
|
||||
const delegationResult: DelegationResult = {
|
||||
success: true,
|
||||
output: result.output || result,
|
||||
confidence: result.confidence || 0.9,
|
||||
tokensUsed: result.tokensUsed || 0,
|
||||
duration: Date.now() - startTime,
|
||||
agentsUsed,
|
||||
needsReview: context.classification.complexity === 'complex'
|
||||
};
|
||||
|
||||
context.status = 'completed';
|
||||
context.endTime = new Date();
|
||||
context.result = delegationResult;
|
||||
|
||||
this.addToHistory(context);
|
||||
return delegationResult;
|
||||
|
||||
} catch (error) {
|
||||
const delegationResult: DelegationResult = {
|
||||
success: false,
|
||||
output: `Delegation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
confidence: 0,
|
||||
tokensUsed: 0,
|
||||
duration: Date.now() - startTime,
|
||||
agentsUsed: [],
|
||||
needsReview: true,
|
||||
escalationReason: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
|
||||
context.status = 'failed';
|
||||
context.endTime = new Date();
|
||||
context.result = delegationResult;
|
||||
|
||||
this.addToHistory(context);
|
||||
return delegationResult;
|
||||
} finally {
|
||||
this.activeDelegations.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private async executeFull(
|
||||
request: ClassifiableRequest,
|
||||
decision: DelegationDecision,
|
||||
executor: (agentType: AgentType, agentId: string, request: ClassifiableRequest) => Promise<any>,
|
||||
agentsUsed: string[]
|
||||
): Promise<any> {
|
||||
const agentType = decision.targetAgents[0];
|
||||
const agent = this.poolManager.acquireAgent(agentType, undefined, request.content.slice(0, 50));
|
||||
|
||||
if (!agent) {
|
||||
throw new Error(`No ${agentType} agent available`);
|
||||
}
|
||||
|
||||
agentsUsed.push(agent.id);
|
||||
|
||||
try {
|
||||
const result = await executor(agentType, agent.id, request);
|
||||
return result;
|
||||
} finally {
|
||||
this.poolManager.releaseAgent(agent.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async executeParallel(
|
||||
request: ClassifiableRequest,
|
||||
decision: DelegationDecision,
|
||||
executor: (agentType: AgentType, agentId: string, request: ClassifiableRequest) => Promise<any>,
|
||||
agentsUsed: string[]
|
||||
): Promise<any> {
|
||||
const agents = this.poolManager.acquireAgents(decision.targetAgents, request.content.slice(0, 50));
|
||||
|
||||
if (agents.length === 0) {
|
||||
throw new Error('No agents available for parallel execution');
|
||||
}
|
||||
|
||||
agents.forEach(a => agentsUsed.push(a.id));
|
||||
|
||||
// Split request for parallel execution
|
||||
const subRequests = this.splitRequest(request, agents.length);
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
agents.map((agent, i) => executor(agent.type, agent.id, subRequests[i] || request))
|
||||
);
|
||||
|
||||
// Merge results
|
||||
return this.mergeResults(results);
|
||||
} finally {
|
||||
agents.forEach(a => this.poolManager.releaseAgent(a.id));
|
||||
}
|
||||
}
|
||||
|
||||
private async executeHierarchical(
|
||||
request: ClassifiableRequest,
|
||||
decision: DelegationDecision,
|
||||
executor: (agentType: AgentType, agentId: string, request: ClassifiableRequest) => Promise<any>,
|
||||
agentsUsed: string[]
|
||||
): Promise<any> {
|
||||
// First, planner creates plan
|
||||
const plannerAgent = this.poolManager.acquireAgent('planner', undefined, request.content.slice(0, 50));
|
||||
|
||||
if (!plannerAgent) {
|
||||
return this.executeFull(request, decision, executor, agentsUsed);
|
||||
}
|
||||
|
||||
agentsUsed.push(plannerAgent.id);
|
||||
|
||||
try {
|
||||
// Get plan from planner
|
||||
const plan = await executor('planner', plannerAgent.id, {
|
||||
...request,
|
||||
content: `Create execution plan for: ${request.content}`
|
||||
});
|
||||
|
||||
// Execute plan steps with primary agent
|
||||
const primaryType = decision.targetAgents[decision.targetAgents.indexOf('planner') + 1] || 'coder';
|
||||
const primaryAgent = this.poolManager.acquireAgent(primaryType);
|
||||
|
||||
if (primaryAgent) {
|
||||
agentsUsed.push(primaryAgent.id);
|
||||
const result = await executor(primaryType, primaryAgent.id, {
|
||||
...request,
|
||||
content: `Execute this plan:\n${plan.output}\n\nOriginal request: ${request.content}`
|
||||
});
|
||||
|
||||
this.poolManager.releaseAgent(primaryAgent.id);
|
||||
return result;
|
||||
}
|
||||
|
||||
return plan;
|
||||
} finally {
|
||||
this.poolManager.releaseAgent(plannerAgent.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async executeHybrid(
|
||||
request: ClassifiableRequest,
|
||||
decision: DelegationDecision,
|
||||
executor: (agentType: AgentType, agentId: string, request: ClassifiableRequest) => Promise<any>,
|
||||
agentsUsed: string[]
|
||||
): Promise<any> {
|
||||
// Start with fast response, then enhance
|
||||
const fastAgent = this.poolManager.acquireAgent('fast-responder');
|
||||
|
||||
if (fastAgent) {
|
||||
agentsUsed.push(fastAgent.id);
|
||||
|
||||
try {
|
||||
// Quick initial response
|
||||
const quickResult = await executor('fast-responder', fastAgent.id, request);
|
||||
|
||||
// Enhanced processing with primary agent
|
||||
const primaryType = decision.targetAgents[0];
|
||||
const primaryAgent = this.poolManager.acquireAgent(primaryType);
|
||||
|
||||
if (primaryAgent) {
|
||||
agentsUsed.push(primaryAgent.id);
|
||||
|
||||
const enhancedResult = await executor(primaryType, primaryAgent.id, {
|
||||
...request,
|
||||
content: `Enhance this response:\n${quickResult.output}\n\nOriginal: ${request.content}`
|
||||
});
|
||||
|
||||
this.poolManager.releaseAgent(primaryAgent.id);
|
||||
return enhancedResult;
|
||||
}
|
||||
|
||||
return quickResult;
|
||||
} finally {
|
||||
this.poolManager.releaseAgent(fastAgent.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to full delegation
|
||||
return this.executeFull(request, decision, executor, agentsUsed);
|
||||
}
|
||||
|
||||
private splitRequest(request: ClassifiableRequest, parts: number): ClassifiableRequest[] {
|
||||
// Simple splitting by files if available
|
||||
if (request.files && request.files.length >= parts) {
|
||||
const filesPerPart = Math.ceil(request.files.length / parts);
|
||||
return Array.from({ length: parts }, (_, i) => ({
|
||||
...request,
|
||||
files: request.files?.slice(i * filesPerPart, (i + 1) * filesPerPart)
|
||||
}));
|
||||
}
|
||||
|
||||
// Otherwise return the same request for each agent
|
||||
return Array(parts).fill(request);
|
||||
}
|
||||
|
||||
private mergeResults(results: any[]): any {
|
||||
if (results.length === 1) return results[0];
|
||||
|
||||
return {
|
||||
output: results.map(r => r.output || r).join('\n\n---\n\n'),
|
||||
confidence: results.reduce((sum, r) => sum + (r.confidence || 0.9), 0) / results.length,
|
||||
tokensUsed: results.reduce((sum, r) => sum + (r.tokensUsed || 0), 0)
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Agent State Management
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Update main agent state (called by integration layer)
|
||||
*/
|
||||
updateMainAgentState(state: Partial<MainAgentState>): void {
|
||||
this.mainAgentState = { ...this.mainAgentState, ...state };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current main agent state
|
||||
*/
|
||||
getMainAgentState(): MainAgentState {
|
||||
return { ...this.mainAgentState };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// History and Monitoring
|
||||
// ============================================================================
|
||||
|
||||
private addToHistory(context: DelegationContext): void {
|
||||
this.delegationHistory.push(context);
|
||||
|
||||
if (this.delegationHistory.length > this.maxHistorySize) {
|
||||
this.delegationHistory.shift();
|
||||
}
|
||||
}
|
||||
|
||||
getActiveDelegations(): DelegationContext[] {
|
||||
return Array.from(this.activeDelegations.values());
|
||||
}
|
||||
|
||||
getDelegationHistory(limit: number = 100): DelegationContext[] {
|
||||
return this.delegationHistory.slice(-limit);
|
||||
}
|
||||
|
||||
getStats(): {
|
||||
totalDelegations: number;
|
||||
successfulDelegations: number;
|
||||
failedDelegations: number;
|
||||
averageDuration: number;
|
||||
strategyUsage: Record<DelegationStrategy, number>;
|
||||
} {
|
||||
const history = this.delegationHistory;
|
||||
|
||||
const stats = {
|
||||
totalDelegations: history.length,
|
||||
successfulDelegations: history.filter(h => h.status === 'completed').length,
|
||||
failedDelegations: history.filter(h => h.status === 'failed').length,
|
||||
averageDuration: 0,
|
||||
strategyUsage: {
|
||||
full: 0,
|
||||
parallel: 0,
|
||||
hierarchical: 0,
|
||||
hybrid: 0
|
||||
} as Record<DelegationStrategy, number>
|
||||
};
|
||||
|
||||
if (history.length > 0) {
|
||||
const durations = history
|
||||
.filter(h => h.result)
|
||||
.map(h => h.result!.duration);
|
||||
stats.averageDuration = durations.reduce((a, b) => a + b, 0) / durations.length || 0;
|
||||
|
||||
history.forEach(h => {
|
||||
stats.strategyUsage[h.delegationDecision.strategy]++;
|
||||
});
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private generateRequestId(): string {
|
||||
return `del-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Factory Function
|
||||
// ============================================================================
|
||||
|
||||
export function createDelegationEngine(
|
||||
classifier: RequestClassifier,
|
||||
poolManager: AgentPoolManager,
|
||||
config?: Partial<DelegationConfig>
|
||||
): DelegationEngine {
|
||||
return new DelegationEngine(classifier, poolManager, config);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export
|
||||
// ============================================================================
|
||||
|
||||
export default DelegationEngine;
|
||||
379
delegation-system/core/request-classifier.ts
Normal file
379
delegation-system/core/request-classifier.ts
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Request Classifier
|
||||
*
|
||||
* Fast request analysis for delegation decisions.
|
||||
* Classifies requests by complexity and determines optimal routing.
|
||||
*/
|
||||
|
||||
import {
|
||||
RequestClassification,
|
||||
RequestComplexity,
|
||||
ClassifiableRequest,
|
||||
AgentType
|
||||
} from './types';
|
||||
|
||||
// ============================================================================
|
||||
// Classification Rules
|
||||
// ============================================================================
|
||||
|
||||
interface ClassificationRule {
|
||||
pattern: RegExp | string;
|
||||
complexity: RequestComplexity;
|
||||
agentType: AgentType;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
const CLASSIFICATION_RULES: ClassificationRule[] = [
|
||||
// Quick patterns - instant response
|
||||
{ pattern: /^(what is|explain|show me|list|get|find)\s/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.9 },
|
||||
{ pattern: /status|help|version|info$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.95 },
|
||||
{ pattern: /^(yes|no|ok|thanks|done)$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.99 },
|
||||
|
||||
// Code patterns - moderate complexity
|
||||
{ pattern: /(review|check|analyze|inspect)\s+(this|the|my)?\s*(code|file|function)/i, complexity: 'moderate', agentType: 'reviewer', weight: 0.85 },
|
||||
{ pattern: /(fix|solve|debug|resolve)\s+/i, complexity: 'moderate', agentType: 'analyzer', weight: 0.8 },
|
||||
{ pattern: /(add|update|modify|change)\s+(a|the|this)?\s*(function|method|class)/i, complexity: 'moderate', agentType: 'coder', weight: 0.85 },
|
||||
|
||||
// Research patterns - streaming
|
||||
{ pattern: /(research|investigate|explore|search)\s+/i, complexity: 'streaming', agentType: 'researcher', weight: 0.8 },
|
||||
{ pattern: /(analyze|audit|examine)\s+(the|entire|whole)?\s*(codebase|project|repo)/i, complexity: 'streaming', agentType: 'explorer', weight: 0.85 },
|
||||
|
||||
// Complex patterns - need main agent or parallel delegation
|
||||
{ pattern: /(refactor|rewrite|restructure|redesign)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 },
|
||||
{ pattern: /(implement|build|create|develop)\s+(a|an|the)?\s*(new|feature|system)/i, complexity: 'complex', agentType: 'planner', weight: 0.85 },
|
||||
{ pattern: /(architect|design|plan)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 },
|
||||
{ pattern: /migrate|upgrade|port\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.85 },
|
||||
];
|
||||
|
||||
// Keywords that indicate complexity
|
||||
const COMPLEXITY_INDICATORS = {
|
||||
high: ['architecture', 'system', 'multiple', 'integrate', 'refactor', 'migrate', 'redesign'],
|
||||
medium: ['implement', 'feature', 'function', 'module', 'component', 'service', 'api'],
|
||||
low: ['fix', 'update', 'change', 'add', 'remove', 'rename', 'comment']
|
||||
};
|
||||
|
||||
// Time estimates by complexity (ms)
|
||||
const TIME_ESTIMATES: Record<RequestComplexity, { min: number; max: number }> = {
|
||||
quick: { min: 100, max: 2000 },
|
||||
moderate: { min: 2000, max: 15000 },
|
||||
complex: { min: 15000, max: 120000 },
|
||||
streaming: { min: 5000, max: 60000 }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Request Classifier Class
|
||||
// ============================================================================
|
||||
|
||||
export class RequestClassifier {
|
||||
private cachedClassifications: Map<string, RequestClassification> = new Map();
|
||||
private readonly cacheMaxSize: number = 1000;
|
||||
|
||||
// Capability mapping by request type
|
||||
private readonly typeCapabilities: Record<ClassifiableRequest['type'], string[]> = {
|
||||
code: ['syntax', 'semantics', 'patterns', 'best-practices'],
|
||||
question: ['knowledge', 'explanation', 'examples'],
|
||||
task: ['execution', 'planning', 'coordination'],
|
||||
analysis: ['inspection', 'metrics', 'patterns', 'security'],
|
||||
review: ['quality', 'standards', 'best-practices', 'security'],
|
||||
refactor: ['patterns', 'optimization', 'clean-code'],
|
||||
debug: ['tracing', 'analysis', 'diagnosis']
|
||||
};
|
||||
|
||||
/**
|
||||
* Classify a request for delegation decisions
|
||||
*/
|
||||
async classify(request: ClassifiableRequest): Promise<RequestClassification> {
|
||||
// Check cache first
|
||||
const cacheKey = this.getCacheKey(request);
|
||||
const cached = this.cachedClassifications.get(cacheKey);
|
||||
if (cached) {
|
||||
return { ...cached, confidence: Math.max(0, cached.confidence - 0.1) }; // Slightly lower confidence for cached
|
||||
}
|
||||
|
||||
// Analyze request
|
||||
const analysis = await this.analyzeRequest(request);
|
||||
|
||||
// Determine complexity
|
||||
const complexity = this.determineComplexity(request, analysis);
|
||||
|
||||
// Get recommended agent
|
||||
const recommendedAgent = this.getRecommendedAgent(request, complexity, analysis);
|
||||
|
||||
// Calculate estimated time
|
||||
const estimatedTime = this.estimateTime(complexity, analysis);
|
||||
|
||||
// Determine capabilities needed
|
||||
const requiredCapabilities = this.getRequiredCapabilities(request, analysis);
|
||||
|
||||
// Build classification
|
||||
const classification: RequestClassification = {
|
||||
complexity,
|
||||
score: analysis.complexityScore,
|
||||
confidence: analysis.confidence,
|
||||
recommendedAgent,
|
||||
estimatedTime,
|
||||
canDelegate: this.canDelegate(complexity, analysis),
|
||||
delegationPriority: this.getPriority(request, analysis),
|
||||
requiredCapabilities,
|
||||
contextRequirements: {
|
||||
files: analysis.fileCount,
|
||||
depth: analysis.contextDepth,
|
||||
history: analysis.needsHistory
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
this.cacheClassification(cacheKey, classification);
|
||||
|
||||
return classification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick classification for fast-path decisions (< 50ms target)
|
||||
*/
|
||||
quickClassify(content: string): { complexity: RequestComplexity; agent: AgentType } {
|
||||
// Fast pattern matching
|
||||
for (const rule of CLASSIFICATION_RULES) {
|
||||
if (typeof rule.pattern === 'string') {
|
||||
if (content.toLowerCase().includes(rule.pattern.toLowerCase())) {
|
||||
return { complexity: rule.complexity, agent: rule.agentType };
|
||||
}
|
||||
} else {
|
||||
if (rule.pattern.test(content)) {
|
||||
return { complexity: rule.complexity, agent: rule.agentType };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default based on length
|
||||
const length = content.length;
|
||||
if (length < 100) return { complexity: 'quick', agent: 'fast-responder' };
|
||||
if (length < 500) return { complexity: 'moderate', agent: 'coder' };
|
||||
return { complexity: 'complex', agent: 'planner' };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Private Methods
|
||||
// ============================================================================
|
||||
|
||||
private async analyzeRequest(request: ClassifiableRequest): Promise<{
|
||||
complexityScore: number;
|
||||
confidence: number;
|
||||
fileCount: number;
|
||||
contextDepth: 'shallow' | 'medium' | 'deep';
|
||||
needsHistory: boolean;
|
||||
matchedRules: ClassificationRule[];
|
||||
keywordDensity: Record<string, number>;
|
||||
}> {
|
||||
const content = request.content.toLowerCase();
|
||||
|
||||
// Match rules
|
||||
const matchedRules: ClassificationRule[] = [];
|
||||
for (const rule of CLASSIFICATION_RULES) {
|
||||
const pattern = typeof rule.pattern === 'string'
|
||||
? new RegExp(rule.pattern, 'i')
|
||||
: rule.pattern;
|
||||
if (pattern.test(content)) {
|
||||
matchedRules.push(rule);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate keyword density
|
||||
const keywordDensity: Record<string, number> = {};
|
||||
for (const [level, keywords] of Object.entries(COMPLEXITY_INDICATORS)) {
|
||||
keywordDensity[level] = keywords.reduce((count, kw) => {
|
||||
return count + (content.includes(kw) ? 1 : 0);
|
||||
}, 0) / keywords.length;
|
||||
}
|
||||
|
||||
// Calculate complexity score (0-1)
|
||||
let complexityScore = 0;
|
||||
|
||||
// Length factor
|
||||
complexityScore += Math.min(request.content.length / 2000, 0.3);
|
||||
|
||||
// Rule matching factor
|
||||
if (matchedRules.length > 0) {
|
||||
const avgWeight = matchedRules.reduce((sum, r) => sum + r.weight, 0) / matchedRules.length;
|
||||
complexityScore += avgWeight * 0.4;
|
||||
}
|
||||
|
||||
// Keyword density factor
|
||||
complexityScore += keywordDensity.high * 0.2;
|
||||
complexityScore += keywordDensity.medium * 0.1;
|
||||
|
||||
// File count factor
|
||||
const fileCount = request.files?.length || 0;
|
||||
complexityScore += Math.min(fileCount / 10, 0.1);
|
||||
|
||||
// Normalize
|
||||
complexityScore = Math.min(complexityScore, 1);
|
||||
|
||||
// Determine context depth
|
||||
let contextDepth: 'shallow' | 'medium' | 'deep' = 'shallow';
|
||||
if (complexityScore > 0.6 || fileCount > 3) contextDepth = 'deep';
|
||||
else if (complexityScore > 0.3 || fileCount > 1) contextDepth = 'medium';
|
||||
|
||||
// Check if history is needed
|
||||
const needsHistory = /context|previous|earlier|before|last|history/i.test(content);
|
||||
|
||||
// Calculate confidence
|
||||
let confidence = 0.5;
|
||||
if (matchedRules.length > 0) confidence += 0.3;
|
||||
if (keywordDensity.high > 0 || keywordDensity.medium > 0) confidence += 0.1;
|
||||
confidence = Math.min(confidence, 0.95);
|
||||
|
||||
return {
|
||||
complexityScore,
|
||||
confidence,
|
||||
fileCount,
|
||||
contextDepth,
|
||||
needsHistory,
|
||||
matchedRules,
|
||||
keywordDensity
|
||||
};
|
||||
}
|
||||
|
||||
private determineComplexity(
|
||||
request: ClassifiableRequest,
|
||||
analysis: { complexityScore: number; matchedRules: ClassificationRule[] }
|
||||
): RequestComplexity {
|
||||
// Check matched rules first
|
||||
if (analysis.matchedRules.length > 0) {
|
||||
// Get the highest weight rule's complexity
|
||||
const topRule = analysis.matchedRules.reduce((best, rule) =>
|
||||
rule.weight > best.weight ? rule : best
|
||||
);
|
||||
return topRule.complexity;
|
||||
}
|
||||
|
||||
// Fall back to score-based classification
|
||||
if (analysis.complexityScore < 0.25) return 'quick';
|
||||
if (analysis.complexityScore < 0.5) return 'moderate';
|
||||
if (analysis.complexityScore < 0.75) return 'streaming';
|
||||
return 'complex';
|
||||
}
|
||||
|
||||
private getRecommendedAgent(
|
||||
request: ClassifiableRequest,
|
||||
complexity: RequestComplexity,
|
||||
analysis: { matchedRules: ClassificationRule[] }
|
||||
): AgentType {
|
||||
// Check matched rules
|
||||
if (analysis.matchedRules.length > 0) {
|
||||
const topRule = analysis.matchedRules.reduce((best, rule) =>
|
||||
rule.weight > best.weight ? rule : best
|
||||
);
|
||||
return topRule.agentType;
|
||||
}
|
||||
|
||||
// Map request type to agent
|
||||
const typeToAgent: Record<ClassifiableRequest['type'], AgentType> = {
|
||||
code: 'coder',
|
||||
question: 'fast-responder',
|
||||
task: 'executor',
|
||||
analysis: 'analyzer',
|
||||
review: 'reviewer',
|
||||
refactor: 'coder',
|
||||
debug: 'analyzer'
|
||||
};
|
||||
|
||||
return typeToAgent[request.type] || 'fast-responder';
|
||||
}
|
||||
|
||||
private estimateTime(
|
||||
complexity: RequestComplexity,
|
||||
analysis: { complexityScore: number; fileCount: number }
|
||||
): number {
|
||||
const base = TIME_ESTIMATES[complexity];
|
||||
let estimate = base.min + (base.max - base.min) * analysis.complexityScore;
|
||||
|
||||
// Add time for files
|
||||
estimate += analysis.fileCount * 500;
|
||||
|
||||
return Math.round(estimate);
|
||||
}
|
||||
|
||||
private canDelegate(complexity: RequestComplexity, analysis: any): boolean {
|
||||
// Quick and moderate can always be delegated
|
||||
if (complexity === 'quick' || complexity === 'moderate') return true;
|
||||
|
||||
// Streaming can be delegated with progress
|
||||
if (complexity === 'streaming') return true;
|
||||
|
||||
// Complex depends on confidence
|
||||
return analysis.confidence > 0.7;
|
||||
}
|
||||
|
||||
private getPriority(
|
||||
request: ClassifiableRequest,
|
||||
analysis: any
|
||||
): 'low' | 'medium' | 'high' | 'critical' {
|
||||
// Check metadata for explicit priority
|
||||
if (request.metadata?.priority) {
|
||||
return request.metadata.priority;
|
||||
}
|
||||
|
||||
// Determine from analysis
|
||||
if (analysis.keywordDensity?.high > 0.5) return 'high';
|
||||
if (analysis.complexityScore > 0.7) return 'high';
|
||||
if (analysis.complexityScore > 0.4) return 'medium';
|
||||
return 'low';
|
||||
}
|
||||
|
||||
private getRequiredCapabilities(
|
||||
request: ClassifiableRequest,
|
||||
analysis: any
|
||||
): string[] {
|
||||
const capabilities = new Set<string>();
|
||||
|
||||
// Add type-based capabilities
|
||||
const typeCaps = this.typeCapabilities[request.type] || [];
|
||||
typeCaps.forEach(c => capabilities.add(c));
|
||||
|
||||
// Add based on content analysis
|
||||
if (/security|auth|encrypt/i.test(request.content)) capabilities.add('security');
|
||||
if (/test|spec|coverage/i.test(request.content)) capabilities.add('testing');
|
||||
if (/performance|optim|speed/i.test(request.content)) capabilities.add('performance');
|
||||
if (/doc|comment|readme/i.test(request.content)) capabilities.add('documentation');
|
||||
|
||||
return Array.from(capabilities);
|
||||
}
|
||||
|
||||
private getCacheKey(request: ClassifiableRequest): string {
|
||||
return `${request.type}:${request.content.slice(0, 100)}:${request.files?.length || 0}`;
|
||||
}
|
||||
|
||||
private cacheClassification(key: string, classification: RequestClassification): void {
|
||||
// Enforce max size
|
||||
if (this.cachedClassifications.size >= this.cacheMaxSize) {
|
||||
// Remove oldest entry
|
||||
const firstKey = this.cachedClassifications.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.cachedClassifications.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cachedClassifications.set(key, classification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear classification cache
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.cachedClassifications.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Factory Function
|
||||
// ============================================================================
|
||||
|
||||
export function createRequestClassifier(): RequestClassifier {
|
||||
return new RequestClassifier();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export
|
||||
// ============================================================================
|
||||
|
||||
export default RequestClassifier;
|
||||
334
delegation-system/core/types.ts
Normal file
334
delegation-system/core/types.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* Delegation System Types
|
||||
*
|
||||
* Core types for the delegation system that enables fast response
|
||||
* by delegating requests to subagents when the main agent is busy.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Request Classification
|
||||
// ============================================================================
|
||||
|
||||
export type RequestComplexity = 'quick' | 'moderate' | 'complex' | 'streaming';
|
||||
|
||||
export interface RequestClassification {
|
||||
complexity: RequestComplexity;
|
||||
score: number; // 0-1
|
||||
confidence: number; // 0-1
|
||||
recommendedAgent: AgentType;
|
||||
estimatedTime: number; // milliseconds
|
||||
canDelegate: boolean;
|
||||
delegationPriority: 'low' | 'medium' | 'high' | 'critical';
|
||||
requiredCapabilities: string[];
|
||||
contextRequirements: {
|
||||
files: number;
|
||||
depth: 'shallow' | 'medium' | 'deep';
|
||||
history: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClassifiableRequest {
|
||||
content: string;
|
||||
type: 'code' | 'question' | 'task' | 'analysis' | 'review' | 'refactor' | 'debug';
|
||||
files?: string[];
|
||||
metadata?: Record<string, any>;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Pool Types
|
||||
// ============================================================================
|
||||
|
||||
export type AgentType =
|
||||
| 'explorer'
|
||||
| 'researcher'
|
||||
| 'coder'
|
||||
| 'reviewer'
|
||||
| 'planner'
|
||||
| 'executor'
|
||||
| 'analyzer'
|
||||
| 'fast-responder';
|
||||
|
||||
export type AgentStatus = 'idle' | 'warming-up' | 'busy' | 'cooling-down' | 'error';
|
||||
|
||||
export interface PoolAgent {
|
||||
id: string;
|
||||
type: AgentType;
|
||||
status: AgentStatus;
|
||||
capabilities: string[];
|
||||
currentTask?: string;
|
||||
completedTasks: number;
|
||||
averageResponseTime: number;
|
||||
successRate: number;
|
||||
lastUsed?: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AgentPoolConfig {
|
||||
maxSize: number;
|
||||
minIdle: number;
|
||||
warmUpTimeout: number;
|
||||
coolDownPeriod: number;
|
||||
scaleUpThreshold: number; // % busy triggers scale up
|
||||
scaleDownThreshold: number; // % idle triggers scale down
|
||||
agentConfigs: Record<AgentType, AgentSpawnConfig>;
|
||||
}
|
||||
|
||||
export interface AgentSpawnConfig {
|
||||
type: AgentType;
|
||||
capabilities: string[];
|
||||
maxConcurrent: number;
|
||||
timeout: number;
|
||||
retryAttempts: number;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Delegation Types
|
||||
// ============================================================================
|
||||
|
||||
export type DelegationStrategy =
|
||||
| 'full' // Complete delegation to subagent
|
||||
| 'parallel' // Multiple subagents work together
|
||||
| 'hierarchical' // Main agent + subagent collaboration
|
||||
| 'hybrid'; // Combination based on context
|
||||
|
||||
export interface DelegationDecision {
|
||||
shouldDelegate: boolean;
|
||||
strategy: DelegationStrategy;
|
||||
targetAgents: AgentType[];
|
||||
estimatedCompletion: number;
|
||||
reason: string;
|
||||
fallbackPlan?: string;
|
||||
requiresCallback: boolean;
|
||||
}
|
||||
|
||||
export interface DelegationContext {
|
||||
requestId: string;
|
||||
originalRequest: ClassifiableRequest;
|
||||
classification: RequestClassification;
|
||||
delegationDecision: DelegationDecision;
|
||||
assignedAgents: string[];
|
||||
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'escalated';
|
||||
startTime: Date;
|
||||
endTime?: Date;
|
||||
result?: DelegationResult;
|
||||
}
|
||||
|
||||
export interface DelegationResult {
|
||||
success: boolean;
|
||||
output: string;
|
||||
confidence: number;
|
||||
tokensUsed: number;
|
||||
duration: number;
|
||||
agentsUsed: string[];
|
||||
needsReview: boolean;
|
||||
escalationReason?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Progress Streaming Types
|
||||
// ============================================================================
|
||||
|
||||
export type ProgressEventType =
|
||||
| 'acknowledgment'
|
||||
| 'delegation'
|
||||
| 'progress'
|
||||
| 'partial-result'
|
||||
| 'completion'
|
||||
| 'error'
|
||||
| 'escalation';
|
||||
|
||||
export interface ProgressEvent {
|
||||
type: ProgressEventType;
|
||||
requestId: string;
|
||||
message: string;
|
||||
progress: number; // 0-100
|
||||
timestamp: Date;
|
||||
data?: {
|
||||
agentType?: AgentType;
|
||||
agentId?: string;
|
||||
currentStep?: string;
|
||||
totalSteps?: number;
|
||||
completedSteps?: number;
|
||||
partialResults?: any[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProgressConfig {
|
||||
enabled: boolean;
|
||||
updateInterval: number; // milliseconds
|
||||
includePartialResults: boolean;
|
||||
maxQueueSize: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Context Handoff Types
|
||||
// ============================================================================
|
||||
|
||||
export interface ContextHandoff {
|
||||
id: string;
|
||||
sourceAgent: string;
|
||||
targetAgent: string;
|
||||
request: ClassifiableRequest;
|
||||
context: {
|
||||
files: Record<string, string>;
|
||||
conversationHistory: ConversationTurn[];
|
||||
workspace: string;
|
||||
metadata: Record<string, any>;
|
||||
};
|
||||
constraints: {
|
||||
timeLimit: number;
|
||||
scope: 'narrow' | 'medium' | 'broad';
|
||||
qualityLevel: 'fast' | 'balanced' | 'thorough';
|
||||
};
|
||||
callback: {
|
||||
endpoint: string;
|
||||
timeout: number;
|
||||
retries: number;
|
||||
};
|
||||
createdAt: Date;
|
||||
expiresAt: Date;
|
||||
}
|
||||
|
||||
export interface ConversationTurn {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface HandoffResult {
|
||||
handoffId: string;
|
||||
success: boolean;
|
||||
result?: any;
|
||||
error?: string;
|
||||
processingTime: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Quality Gate Types
|
||||
// ============================================================================
|
||||
|
||||
export interface QualityCheck {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
check: (result: DelegationResult, context: DelegationContext) => Promise<QualityCheckResult>;
|
||||
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||
autoFix: boolean;
|
||||
}
|
||||
|
||||
export interface QualityCheckResult {
|
||||
passed: boolean;
|
||||
score: number; // 0-1
|
||||
issues: QualityIssue[];
|
||||
recommendations: string[];
|
||||
}
|
||||
|
||||
export interface QualityIssue {
|
||||
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||
message: string;
|
||||
location?: string;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
export interface QualityGateConfig {
|
||||
enabled: boolean;
|
||||
minConfidence: number; // 0-1
|
||||
checks: QualityCheck[];
|
||||
escalationThreshold: number;
|
||||
autoEscalate: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Integration Types
|
||||
// ============================================================================
|
||||
|
||||
export type IntegrationType =
|
||||
| 'openclaw'
|
||||
| 'claude-code'
|
||||
| 'cursor'
|
||||
| 'aider'
|
||||
| 'copilot'
|
||||
| 'generic';
|
||||
|
||||
export interface IntegrationAdapter {
|
||||
type: IntegrationType;
|
||||
name: string;
|
||||
version: string;
|
||||
|
||||
// Lifecycle
|
||||
initialize(config: any): Promise<void>;
|
||||
shutdown(): Promise<void>;
|
||||
|
||||
// Core operations
|
||||
classifyRequest(request: any): Promise<RequestClassification>;
|
||||
delegateRequest(request: any, decision: DelegationDecision): Promise<DelegationResult>;
|
||||
streamProgress(requestId: string, callback: (event: ProgressEvent) => void): void;
|
||||
|
||||
// Status
|
||||
getStatus(): IntegrationStatus;
|
||||
getCapabilities(): string[];
|
||||
}
|
||||
|
||||
export interface IntegrationStatus {
|
||||
connected: boolean;
|
||||
ready: boolean;
|
||||
agentPoolAvailable: boolean;
|
||||
currentLoad: number; // 0-1
|
||||
queueLength: number;
|
||||
averageResponseTime: number;
|
||||
}
|
||||
|
||||
export interface IntegrationConfig {
|
||||
type: IntegrationType;
|
||||
delegation: {
|
||||
enabled: boolean;
|
||||
autoDelegate: boolean;
|
||||
strategy: DelegationStrategy;
|
||||
fallbackToMain: boolean;
|
||||
};
|
||||
pool: AgentPoolConfig;
|
||||
quality: QualityGateConfig;
|
||||
progress: ProgressConfig;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unified API Types
|
||||
// ============================================================================
|
||||
|
||||
export interface DelegationAPIConfig {
|
||||
integrations: IntegrationConfig[];
|
||||
defaultStrategy: DelegationStrategy;
|
||||
maxConcurrentDelegations: number;
|
||||
requestTimeout: number;
|
||||
enableCaching: boolean;
|
||||
cacheConfig?: {
|
||||
maxSize: number;
|
||||
ttl: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DelegationRequest {
|
||||
id: string;
|
||||
content: string;
|
||||
type: ClassifiableRequest['type'];
|
||||
files?: string[];
|
||||
metadata?: Record<string, any>;
|
||||
priority?: 'low' | 'medium' | 'high' | 'critical';
|
||||
preferredAgent?: AgentType;
|
||||
timeout?: number;
|
||||
streamProgress?: boolean;
|
||||
}
|
||||
|
||||
export interface DelegationResponse {
|
||||
requestId: string;
|
||||
success: boolean;
|
||||
result?: string;
|
||||
classification?: RequestClassification;
|
||||
delegation?: DelegationDecision;
|
||||
confidence: number;
|
||||
processingTime: number;
|
||||
agentsUsed: string[];
|
||||
progress?: ProgressEvent[];
|
||||
}
|
||||
Reference in New Issue
Block a user