@cognitive-swarm/evolution
Self-evolving swarm: agents detect expertise gaps, propose new specialists, and dissolve underperformers.
Install
bash
npm install @cognitive-swarm/evolutionSwarmEvolver
typescript
import { SwarmEvolver } from '@cognitive-swarm/evolution'
const evolver = new SwarmEvolver(llmProvider, {
minVotesForSpawn: 2,
approvalThreshold: 0.6,
minValueForKeep: 0.3,
evaluationWindow: 3,
})The evolver manages the full lifecycle:
- Agents detect gaps in collective expertise →
reportGap() - Other agents confirm the gap →
confirmGap() - When enough confirmations, spawn a new specialist →
proposeSpawn() - Track spawned agent contribution →
evaluate() - Dissolve agents that don't provide value →
suggestPrune()
Gap Detection
typescript
// Agent detects a gap
evolver.reportGap({
id: 'g1',
detectedBy: 'agent-1',
domain: 'reverse-engineering',
reason: 'Found obfuscated code that needs binary analysis',
urgency: 0.8,
timestamp: Date.now(),
})
// Another agent confirms the gap
evolver.confirmGap('g1', 'agent-2')
// Check confirmation count
evolver.getConfirmationCount('g1') // 2
// Dismiss a gap
evolver.dismissGap('g1', 'agent-3')typescript
interface GapSignal {
readonly id: string
readonly detectedBy: string
readonly domain: string
readonly reason: string
readonly suggestedRole?: string
readonly urgency: number // 0..1
readonly timestamp: number
}Spawning Agents
When enough agents confirm a gap, propose a new specialist:
typescript
const proposal = await evolver.proposeSpawn('g1', ['analyst', 'critic'])
// proposal is null if confirmations < minVotesForSpawn
if (proposal) {
console.log(proposal.role) // 'RE Specialist' (LLM-generated)
console.log(proposal.roleDescription) // generated description
console.log(proposal.personality) // generated personality vector
console.log(proposal.temporary) // true if urgency < 0.5
}The role description and personality are generated by the LLM based on the gap context.
typescript
interface SpawnProposal {
readonly id: string
readonly gapId: string
readonly role: string
readonly roleDescription: string
readonly personality: PersonalityVector
readonly listens: readonly SignalType[]
readonly canEmit: readonly SignalType[]
readonly temporary: boolean
readonly proposedBy: readonly string[]
readonly votes: readonly VoteRecord[]
readonly status: 'pending' | 'approved' | 'rejected'
}Evaluating Spawned Agents
Track whether spawned agents provide value:
typescript
const result = evolver.evaluate(
'spawned-agent-1', // agentId
12, // signalsSent
3, // proposalsMade
5, // roundsActive
)
console.log(result.valueScore) // 0..1
console.log(result.recommendation) // 'keep' | 'dissolve'
console.log(result.reason)Value score: 0.4 * signalScore + 0.6 * proposalScore. Agents evaluated before evaluationWindow rounds always get "keep".
typescript
interface EvaluationResult {
readonly agentId: string
readonly valueScore: number
readonly roundsActive: number
readonly recommendation: 'keep' | 'dissolve'
readonly reason: string
}Pruning
Suggest agents to remove based on value scores and redundancy:
typescript
const redundancyScores = new Map([
['spawned-agent-1', 0.85], // highly similar to another agent
['spawned-agent-2', 0.3],
])
const report = evolver.suggestPrune(redundancyScores)
for (const candidate of report.candidates) {
console.log(`${candidate.agentId}: ${candidate.reason} (redundancy: ${candidate.redundancyScore})`)
}typescript
interface PruneReport {
readonly candidates: readonly PruneCandidate[]
readonly pruneCount: number
}
interface PruneCandidate {
readonly agentId: string
readonly reason: string
readonly redundancyScore: number
}EvolverConfig
typescript
interface EvolverConfig {
readonly minVotesForSpawn?: number // default: 2
readonly approvalThreshold?: number // default: 0.6
readonly minValueForKeep?: number // default: 0.3
readonly evaluationWindow?: number // rounds before evaluation, default: 3
}Usage via Orchestrator
The orchestrator uses evolution internally when configured:
typescript
const swarm = new SwarmOrchestrator({
agents,
evolution: {
enabled: true,
maxEvolvedAgents: 3,
evaluationWindow: 5,
minValueForKeep: 0.5,
cooldownRounds: 3,
nmiPruneThreshold: 0.8,
},
})
const result = await swarm.solve('complex multi-domain task')
console.log(result.evolutionReport?.spawned)Stream Events
typescript
for await (const event of swarm.solveWithStream('task')) {
if (event.type === 'evolution:spawned') {
// event.agentId, event.domain, event.reason
}
if (event.type === 'evolution:dissolved') {
// event.agentId, event.reason
}
}