@sparkleideas/swarm 3.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +472 -0
- package/README.md +634 -0
- package/__tests__/consensus.test.ts +577 -0
- package/__tests__/coordinator.test.ts +501 -0
- package/__tests__/queen-coordinator.test.ts +1335 -0
- package/__tests__/topology.test.ts +621 -0
- package/package.json +32 -0
- package/src/agent-pool.ts +476 -0
- package/src/application/commands/create-task.command.ts +124 -0
- package/src/application/commands/spawn-agent.command.ts +122 -0
- package/src/application/index.ts +30 -0
- package/src/application/services/swarm-application-service.ts +200 -0
- package/src/attention-coordinator.ts +1000 -0
- package/src/consensus/byzantine.ts +431 -0
- package/src/consensus/gossip.ts +513 -0
- package/src/consensus/index.ts +267 -0
- package/src/consensus/raft.ts +443 -0
- package/src/coordination/agent-registry.ts +544 -0
- package/src/coordination/index.ts +23 -0
- package/src/coordination/swarm-hub.ts +776 -0
- package/src/coordination/task-orchestrator.ts +605 -0
- package/src/domain/entities/agent.d.ts +151 -0
- package/src/domain/entities/agent.d.ts.map +1 -0
- package/src/domain/entities/agent.js +280 -0
- package/src/domain/entities/agent.js.map +1 -0
- package/src/domain/entities/agent.ts +370 -0
- package/src/domain/entities/task.d.ts +133 -0
- package/src/domain/entities/task.d.ts.map +1 -0
- package/src/domain/entities/task.js +261 -0
- package/src/domain/entities/task.js.map +1 -0
- package/src/domain/entities/task.ts +319 -0
- package/src/domain/index.ts +41 -0
- package/src/domain/repositories/agent-repository.interface.d.ts +57 -0
- package/src/domain/repositories/agent-repository.interface.d.ts.map +1 -0
- package/src/domain/repositories/agent-repository.interface.js +9 -0
- package/src/domain/repositories/agent-repository.interface.js.map +1 -0
- package/src/domain/repositories/agent-repository.interface.ts +69 -0
- package/src/domain/repositories/task-repository.interface.d.ts +61 -0
- package/src/domain/repositories/task-repository.interface.d.ts.map +1 -0
- package/src/domain/repositories/task-repository.interface.js +9 -0
- package/src/domain/repositories/task-repository.interface.js.map +1 -0
- package/src/domain/repositories/task-repository.interface.ts +75 -0
- package/src/domain/services/coordination-service.ts +320 -0
- package/src/federation-hub.d.ts +284 -0
- package/src/federation-hub.d.ts.map +1 -0
- package/src/federation-hub.js +692 -0
- package/src/federation-hub.js.map +1 -0
- package/src/federation-hub.ts +979 -0
- package/src/index.ts +348 -0
- package/src/message-bus.ts +607 -0
- package/src/queen-coordinator.ts +2025 -0
- package/src/shared/events.ts +285 -0
- package/src/shared/types.ts +389 -0
- package/src/topology-manager.ts +656 -0
- package/src/types.ts +545 -0
- package/src/unified-coordinator.ts +1844 -0
- package/src/workers/index.ts +65 -0
- package/src/workers/worker-dispatch.d.ts +234 -0
- package/src/workers/worker-dispatch.d.ts.map +1 -0
- package/src/workers/worker-dispatch.js +842 -0
- package/src/workers/worker-dispatch.js.map +1 -0
- package/src/workers/worker-dispatch.ts +1076 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-repository.interface.js","sourceRoot":"","sources":["agent-repository.interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Repository Interface - Domain Layer
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for agent persistence.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/swarm/domain/repositories
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Agent, AgentStatus, AgentRole } from '../entities/agent.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Agent query options
|
|
13
|
+
*/
|
|
14
|
+
export interface AgentQueryOptions {
|
|
15
|
+
status?: AgentStatus;
|
|
16
|
+
role?: AgentRole;
|
|
17
|
+
domain?: string;
|
|
18
|
+
parentId?: string;
|
|
19
|
+
capability?: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
offset?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Agent statistics
|
|
26
|
+
*/
|
|
27
|
+
export interface AgentStatistics {
|
|
28
|
+
total: number;
|
|
29
|
+
byStatus: Record<AgentStatus, number>;
|
|
30
|
+
byRole: Record<string, number>;
|
|
31
|
+
byDomain: Record<string, number>;
|
|
32
|
+
totalTasksCompleted: number;
|
|
33
|
+
averageUtilization: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Agent Repository Interface
|
|
38
|
+
*/
|
|
39
|
+
export interface IAgentRepository {
|
|
40
|
+
// CRUD
|
|
41
|
+
save(agent: Agent): Promise<void>;
|
|
42
|
+
findById(id: string): Promise<Agent | null>;
|
|
43
|
+
findByName(name: string): Promise<Agent | null>;
|
|
44
|
+
delete(id: string): Promise<boolean>;
|
|
45
|
+
exists(id: string): Promise<boolean>;
|
|
46
|
+
|
|
47
|
+
// Bulk operations
|
|
48
|
+
saveMany(agents: Agent[]): Promise<void>;
|
|
49
|
+
findByIds(ids: string[]): Promise<Agent[]>;
|
|
50
|
+
deleteMany(ids: string[]): Promise<number>;
|
|
51
|
+
|
|
52
|
+
// Query operations
|
|
53
|
+
findAll(options?: AgentQueryOptions): Promise<Agent[]>;
|
|
54
|
+
findByStatus(status: AgentStatus): Promise<Agent[]>;
|
|
55
|
+
findByRole(role: AgentRole): Promise<Agent[]>;
|
|
56
|
+
findByDomain(domain: string): Promise<Agent[]>;
|
|
57
|
+
findByParent(parentId: string): Promise<Agent[]>;
|
|
58
|
+
findByCapability(capability: string): Promise<Agent[]>;
|
|
59
|
+
findAvailable(): Promise<Agent[]>;
|
|
60
|
+
|
|
61
|
+
// Statistics
|
|
62
|
+
getStatistics(): Promise<AgentStatistics>;
|
|
63
|
+
count(options?: AgentQueryOptions): Promise<number>;
|
|
64
|
+
|
|
65
|
+
// Lifecycle
|
|
66
|
+
initialize(): Promise<void>;
|
|
67
|
+
shutdown(): Promise<void>;
|
|
68
|
+
clear(): Promise<void>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Repository Interface - Domain Layer
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for task persistence.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/swarm/domain/repositories
|
|
7
|
+
*/
|
|
8
|
+
import { Task, TaskStatus, TaskPriority } from '../entities/task.js';
|
|
9
|
+
/**
|
|
10
|
+
* Task query options
|
|
11
|
+
*/
|
|
12
|
+
export interface TaskQueryOptions {
|
|
13
|
+
status?: TaskStatus;
|
|
14
|
+
priority?: TaskPriority;
|
|
15
|
+
type?: string;
|
|
16
|
+
assignedAgentId?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
offset?: number;
|
|
19
|
+
orderBy?: 'createdAt' | 'priority' | 'startedAt';
|
|
20
|
+
orderDirection?: 'asc' | 'desc';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Task statistics
|
|
24
|
+
*/
|
|
25
|
+
export interface TaskStatistics {
|
|
26
|
+
total: number;
|
|
27
|
+
byStatus: Record<TaskStatus, number>;
|
|
28
|
+
byPriority: Record<TaskPriority, number>;
|
|
29
|
+
byType: Record<string, number>;
|
|
30
|
+
averageExecutionTime: number;
|
|
31
|
+
successRate: number;
|
|
32
|
+
retryRate: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Task Repository Interface
|
|
36
|
+
*/
|
|
37
|
+
export interface ITaskRepository {
|
|
38
|
+
save(task: Task): Promise<void>;
|
|
39
|
+
findById(id: string): Promise<Task | null>;
|
|
40
|
+
delete(id: string): Promise<boolean>;
|
|
41
|
+
exists(id: string): Promise<boolean>;
|
|
42
|
+
saveMany(tasks: Task[]): Promise<void>;
|
|
43
|
+
findByIds(ids: string[]): Promise<Task[]>;
|
|
44
|
+
deleteMany(ids: string[]): Promise<number>;
|
|
45
|
+
findAll(options?: TaskQueryOptions): Promise<Task[]>;
|
|
46
|
+
findByStatus(status: TaskStatus): Promise<Task[]>;
|
|
47
|
+
findByPriority(priority: TaskPriority): Promise<Task[]>;
|
|
48
|
+
findByAgent(agentId: string): Promise<Task[]>;
|
|
49
|
+
findPending(): Promise<Task[]>;
|
|
50
|
+
findQueued(): Promise<Task[]>;
|
|
51
|
+
findRunning(): Promise<Task[]>;
|
|
52
|
+
findTimedOut(): Promise<Task[]>;
|
|
53
|
+
getNextTask(agentCapabilities?: string[]): Promise<Task | null>;
|
|
54
|
+
getTaskQueue(limit?: number): Promise<Task[]>;
|
|
55
|
+
getStatistics(): Promise<TaskStatistics>;
|
|
56
|
+
count(options?: TaskQueryOptions): Promise<number>;
|
|
57
|
+
initialize(): Promise<void>;
|
|
58
|
+
shutdown(): Promise<void>;
|
|
59
|
+
clear(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=task-repository.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-repository.interface.d.ts","sourceRoot":"","sources":["task-repository.interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC;IACjD,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAE9B,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAGrC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAG3C,OAAO,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACxD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,WAAW,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/B,UAAU,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,WAAW,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/B,YAAY,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAGhC,WAAW,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAChE,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAG9C,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,KAAK,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAGnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-repository.interface.js","sourceRoot":"","sources":["task-repository.interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Repository Interface - Domain Layer
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for task persistence.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/swarm/domain/repositories
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Task, TaskStatus, TaskPriority } from '../entities/task.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Task query options
|
|
13
|
+
*/
|
|
14
|
+
export interface TaskQueryOptions {
|
|
15
|
+
status?: TaskStatus;
|
|
16
|
+
priority?: TaskPriority;
|
|
17
|
+
type?: string;
|
|
18
|
+
assignedAgentId?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
orderBy?: 'createdAt' | 'priority' | 'startedAt';
|
|
22
|
+
orderDirection?: 'asc' | 'desc';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Task statistics
|
|
27
|
+
*/
|
|
28
|
+
export interface TaskStatistics {
|
|
29
|
+
total: number;
|
|
30
|
+
byStatus: Record<TaskStatus, number>;
|
|
31
|
+
byPriority: Record<TaskPriority, number>;
|
|
32
|
+
byType: Record<string, number>;
|
|
33
|
+
averageExecutionTime: number;
|
|
34
|
+
successRate: number;
|
|
35
|
+
retryRate: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Task Repository Interface
|
|
40
|
+
*/
|
|
41
|
+
export interface ITaskRepository {
|
|
42
|
+
// CRUD
|
|
43
|
+
save(task: Task): Promise<void>;
|
|
44
|
+
findById(id: string): Promise<Task | null>;
|
|
45
|
+
delete(id: string): Promise<boolean>;
|
|
46
|
+
exists(id: string): Promise<boolean>;
|
|
47
|
+
|
|
48
|
+
// Bulk operations
|
|
49
|
+
saveMany(tasks: Task[]): Promise<void>;
|
|
50
|
+
findByIds(ids: string[]): Promise<Task[]>;
|
|
51
|
+
deleteMany(ids: string[]): Promise<number>;
|
|
52
|
+
|
|
53
|
+
// Query operations
|
|
54
|
+
findAll(options?: TaskQueryOptions): Promise<Task[]>;
|
|
55
|
+
findByStatus(status: TaskStatus): Promise<Task[]>;
|
|
56
|
+
findByPriority(priority: TaskPriority): Promise<Task[]>;
|
|
57
|
+
findByAgent(agentId: string): Promise<Task[]>;
|
|
58
|
+
findPending(): Promise<Task[]>;
|
|
59
|
+
findQueued(): Promise<Task[]>;
|
|
60
|
+
findRunning(): Promise<Task[]>;
|
|
61
|
+
findTimedOut(): Promise<Task[]>;
|
|
62
|
+
|
|
63
|
+
// Queue operations
|
|
64
|
+
getNextTask(agentCapabilities?: string[]): Promise<Task | null>;
|
|
65
|
+
getTaskQueue(limit?: number): Promise<Task[]>;
|
|
66
|
+
|
|
67
|
+
// Statistics
|
|
68
|
+
getStatistics(): Promise<TaskStatistics>;
|
|
69
|
+
count(options?: TaskQueryOptions): Promise<number>;
|
|
70
|
+
|
|
71
|
+
// Lifecycle
|
|
72
|
+
initialize(): Promise<void>;
|
|
73
|
+
shutdown(): Promise<void>;
|
|
74
|
+
clear(): Promise<void>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordination Domain Service - Domain Layer
|
|
3
|
+
*
|
|
4
|
+
* Contains coordination logic that spans multiple entities.
|
|
5
|
+
* Handles task assignment, load balancing, and swarm orchestration.
|
|
6
|
+
*
|
|
7
|
+
* @module v3/swarm/domain/services
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Agent, AgentRole } from '../entities/agent.js';
|
|
11
|
+
import { Task, TaskPriority } from '../entities/task.js';
|
|
12
|
+
import { IAgentRepository } from '../repositories/agent-repository.interface.js';
|
|
13
|
+
import { ITaskRepository } from '../repositories/task-repository.interface.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Load balancing strategy
|
|
17
|
+
*/
|
|
18
|
+
export type LoadBalancingStrategy = 'round-robin' | 'least-loaded' | 'capability-match' | 'random';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Task assignment result
|
|
22
|
+
*/
|
|
23
|
+
export interface TaskAssignmentResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
taskId: string;
|
|
26
|
+
agentId?: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Swarm health status
|
|
32
|
+
*/
|
|
33
|
+
export interface SwarmHealth {
|
|
34
|
+
healthy: boolean;
|
|
35
|
+
totalAgents: number;
|
|
36
|
+
activeAgents: number;
|
|
37
|
+
errorAgents: number;
|
|
38
|
+
pendingTasks: number;
|
|
39
|
+
runningTasks: number;
|
|
40
|
+
queueDepth: number;
|
|
41
|
+
averageUtilization: number;
|
|
42
|
+
issues: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Coordination Domain Service
|
|
47
|
+
*
|
|
48
|
+
* Provides domain-level coordination operations.
|
|
49
|
+
*/
|
|
50
|
+
export class CoordinationService {
|
|
51
|
+
constructor(
|
|
52
|
+
private readonly agentRepository: IAgentRepository,
|
|
53
|
+
private readonly taskRepository: ITaskRepository
|
|
54
|
+
) {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Assign a task to the best available agent
|
|
58
|
+
*/
|
|
59
|
+
async assignTask(
|
|
60
|
+
taskId: string,
|
|
61
|
+
strategy: LoadBalancingStrategy = 'capability-match'
|
|
62
|
+
): Promise<TaskAssignmentResult> {
|
|
63
|
+
const task = await this.taskRepository.findById(taskId);
|
|
64
|
+
if (!task) {
|
|
65
|
+
return { success: false, taskId, reason: 'Task not found' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check dependencies
|
|
69
|
+
const completedTasks = await this.taskRepository.findByStatus('completed');
|
|
70
|
+
const completedIds = new Set(completedTasks.map((t) => t.id));
|
|
71
|
+
|
|
72
|
+
if (!task.areDependenciesSatisfied(completedIds)) {
|
|
73
|
+
return { success: false, taskId, reason: 'Dependencies not satisfied' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Find best agent
|
|
77
|
+
const agent = await this.findBestAgent(task, strategy);
|
|
78
|
+
if (!agent) {
|
|
79
|
+
return { success: false, taskId, reason: 'No available agents' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Assign task
|
|
83
|
+
task.assign(agent.id);
|
|
84
|
+
agent.assignTask(taskId);
|
|
85
|
+
|
|
86
|
+
await this.taskRepository.save(task);
|
|
87
|
+
await this.agentRepository.save(agent);
|
|
88
|
+
|
|
89
|
+
return { success: true, taskId, agentId: agent.id };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find the best agent for a task based on strategy
|
|
94
|
+
*/
|
|
95
|
+
private async findBestAgent(
|
|
96
|
+
task: Task,
|
|
97
|
+
strategy: LoadBalancingStrategy
|
|
98
|
+
): Promise<Agent | null> {
|
|
99
|
+
const availableAgents = await this.agentRepository.findAvailable();
|
|
100
|
+
|
|
101
|
+
if (availableAgents.length === 0) return null;
|
|
102
|
+
|
|
103
|
+
switch (strategy) {
|
|
104
|
+
case 'round-robin':
|
|
105
|
+
return this.roundRobinSelection(availableAgents);
|
|
106
|
+
|
|
107
|
+
case 'least-loaded':
|
|
108
|
+
return this.leastLoadedSelection(availableAgents);
|
|
109
|
+
|
|
110
|
+
case 'capability-match':
|
|
111
|
+
return this.capabilityMatchSelection(availableAgents, task);
|
|
112
|
+
|
|
113
|
+
case 'random':
|
|
114
|
+
return availableAgents[Math.floor(Math.random() * availableAgents.length)];
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
return availableAgents[0];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private roundRobinSelection(agents: Agent[]): Agent {
|
|
122
|
+
// Sort by last active time and pick least recently used
|
|
123
|
+
return [...agents].sort(
|
|
124
|
+
(a, b) => a.lastActiveAt.getTime() - b.lastActiveAt.getTime()
|
|
125
|
+
)[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private leastLoadedSelection(agents: Agent[]): Agent {
|
|
129
|
+
return [...agents].sort((a, b) => a.getUtilization() - b.getUtilization())[0];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private capabilityMatchSelection(agents: Agent[], task: Task): Agent | null {
|
|
133
|
+
// Extract required capabilities from task type and metadata
|
|
134
|
+
const requiredCapabilities = this.extractRequiredCapabilities(task);
|
|
135
|
+
|
|
136
|
+
// Score agents by capability match
|
|
137
|
+
const scored = agents.map((agent) => {
|
|
138
|
+
let score = 0;
|
|
139
|
+
for (const cap of requiredCapabilities) {
|
|
140
|
+
if (agent.hasCapability(cap)) score++;
|
|
141
|
+
}
|
|
142
|
+
// Factor in utilization (prefer less loaded)
|
|
143
|
+
score -= agent.getUtilization() * 0.5;
|
|
144
|
+
return { agent, score };
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
scored.sort((a, b) => b.score - a.score);
|
|
148
|
+
|
|
149
|
+
// Return best match if any capabilities matched
|
|
150
|
+
if (scored.length > 0 && scored[0].score > 0) {
|
|
151
|
+
return scored[0].agent;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fallback to least loaded
|
|
155
|
+
return this.leastLoadedSelection(agents);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private extractRequiredCapabilities(task: Task): string[] {
|
|
159
|
+
const capabilities: string[] = [task.type];
|
|
160
|
+
|
|
161
|
+
// Map task types to required capabilities
|
|
162
|
+
const capabilityMap: Record<string, string[]> = {
|
|
163
|
+
implementation: ['coding', 'testing'],
|
|
164
|
+
review: ['review', 'analysis'],
|
|
165
|
+
testing: ['testing', 'qa'],
|
|
166
|
+
documentation: ['documentation', 'writing'],
|
|
167
|
+
security: ['security', 'audit'],
|
|
168
|
+
performance: ['performance', 'optimization'],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (capabilityMap[task.type]) {
|
|
172
|
+
capabilities.push(...capabilityMap[task.type]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return capabilities;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Process completed tasks and release agents
|
|
180
|
+
*/
|
|
181
|
+
async processTaskCompletion(taskId: string, output?: unknown): Promise<void> {
|
|
182
|
+
const task = await this.taskRepository.findById(taskId);
|
|
183
|
+
if (!task || !task.assignedAgentId) return;
|
|
184
|
+
|
|
185
|
+
const agent = await this.agentRepository.findById(task.assignedAgentId);
|
|
186
|
+
|
|
187
|
+
task.complete(output);
|
|
188
|
+
await this.taskRepository.save(task);
|
|
189
|
+
|
|
190
|
+
if (agent) {
|
|
191
|
+
agent.completeTask(taskId);
|
|
192
|
+
await this.agentRepository.save(agent);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for dependent tasks that can now be queued
|
|
196
|
+
await this.queueDependentTasks(taskId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Queue tasks whose dependencies are now satisfied
|
|
201
|
+
*/
|
|
202
|
+
private async queueDependentTasks(completedTaskId: string): Promise<void> {
|
|
203
|
+
const pendingTasks = await this.taskRepository.findPending();
|
|
204
|
+
const completedTasks = await this.taskRepository.findByStatus('completed');
|
|
205
|
+
const completedIds = new Set(completedTasks.map((t) => t.id));
|
|
206
|
+
|
|
207
|
+
for (const task of pendingTasks) {
|
|
208
|
+
if (
|
|
209
|
+
task.dependencies.includes(completedTaskId) &&
|
|
210
|
+
task.areDependenciesSatisfied(completedIds)
|
|
211
|
+
) {
|
|
212
|
+
task.queue();
|
|
213
|
+
await this.taskRepository.save(task);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Handle task failure with retry logic
|
|
220
|
+
*/
|
|
221
|
+
async processTaskFailure(taskId: string, error: string): Promise<boolean> {
|
|
222
|
+
const task = await this.taskRepository.findById(taskId);
|
|
223
|
+
if (!task) return false;
|
|
224
|
+
|
|
225
|
+
const agentId = task.assignedAgentId;
|
|
226
|
+
|
|
227
|
+
task.fail(error);
|
|
228
|
+
await this.taskRepository.save(task);
|
|
229
|
+
|
|
230
|
+
if (agentId) {
|
|
231
|
+
const agent = await this.agentRepository.findById(agentId);
|
|
232
|
+
if (agent) {
|
|
233
|
+
agent.completeTask(taskId);
|
|
234
|
+
await this.agentRepository.save(agent);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Return true if task was requeued for retry
|
|
239
|
+
return task.status === 'queued';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get overall swarm health
|
|
244
|
+
*/
|
|
245
|
+
async getSwarmHealth(): Promise<SwarmHealth> {
|
|
246
|
+
const agentStats = await this.agentRepository.getStatistics();
|
|
247
|
+
const taskStats = await this.taskRepository.getStatistics();
|
|
248
|
+
const queuedTasks = await this.taskRepository.findQueued();
|
|
249
|
+
|
|
250
|
+
const issues: string[] = [];
|
|
251
|
+
|
|
252
|
+
// Check for issues
|
|
253
|
+
if (agentStats.byStatus.error > 0) {
|
|
254
|
+
issues.push(`${agentStats.byStatus.error} agents in error state`);
|
|
255
|
+
}
|
|
256
|
+
if (queuedTasks.length > agentStats.byStatus.active * 10) {
|
|
257
|
+
issues.push('Task queue backlog detected');
|
|
258
|
+
}
|
|
259
|
+
if (agentStats.averageUtilization > 0.9) {
|
|
260
|
+
issues.push('High agent utilization');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const timedOutTasks = await this.taskRepository.findTimedOut();
|
|
264
|
+
if (timedOutTasks.length > 0) {
|
|
265
|
+
issues.push(`${timedOutTasks.length} timed out tasks`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
healthy: issues.length === 0,
|
|
270
|
+
totalAgents: agentStats.total,
|
|
271
|
+
activeAgents: agentStats.byStatus.active + agentStats.byStatus.busy,
|
|
272
|
+
errorAgents: agentStats.byStatus.error,
|
|
273
|
+
pendingTasks: taskStats.byStatus.pending,
|
|
274
|
+
runningTasks: taskStats.byStatus.running,
|
|
275
|
+
queueDepth: queuedTasks.length,
|
|
276
|
+
averageUtilization: agentStats.averageUtilization,
|
|
277
|
+
issues,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Scale agents based on workload
|
|
283
|
+
*/
|
|
284
|
+
async calculateScalingRecommendation(): Promise<{
|
|
285
|
+
action: 'scale-up' | 'scale-down' | 'none';
|
|
286
|
+
count: number;
|
|
287
|
+
reason: string;
|
|
288
|
+
}> {
|
|
289
|
+
const health = await this.getSwarmHealth();
|
|
290
|
+
const queuedTasks = await this.taskRepository.findQueued();
|
|
291
|
+
|
|
292
|
+
// Scale up if queue is deep and agents are highly utilized
|
|
293
|
+
if (queuedTasks.length > 10 && health.averageUtilization > 0.8) {
|
|
294
|
+
const additionalAgents = Math.ceil(queuedTasks.length / 5);
|
|
295
|
+
return {
|
|
296
|
+
action: 'scale-up',
|
|
297
|
+
count: additionalAgents,
|
|
298
|
+
reason: 'High queue depth with high agent utilization',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Scale down if queue is empty and many agents are idle
|
|
303
|
+
if (queuedTasks.length === 0 && health.averageUtilization < 0.2) {
|
|
304
|
+
const excessAgents = Math.floor(health.totalAgents * 0.3);
|
|
305
|
+
if (excessAgents > 0) {
|
|
306
|
+
return {
|
|
307
|
+
action: 'scale-down',
|
|
308
|
+
count: excessAgents,
|
|
309
|
+
reason: 'Low utilization with empty queue',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
action: 'none',
|
|
316
|
+
count: 0,
|
|
317
|
+
reason: 'Current scaling is appropriate',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|