Initial commit: Agent Governance System Phase 8
Phase 8 Production Hardening with complete governance infrastructure: - Vault integration with tiered policies (T0-T4) - DragonflyDB state management - SQLite audit ledger - Pipeline DSL and templates - Promotion/revocation engine - Checkpoint system for session persistence - Health manager and circuit breaker for fault tolerance - GitHub/Slack integrations - Architectural test pipeline with bug watcher, suggestion engine, council review - Multi-agent chaos testing framework Test Results: - Governance tests: 68/68 passing - E2E workflow: 16/16 passing - Phase 2 Vault: 14/14 passing - Integration tests: 27/27 passing Coverage: 57.6% average across 12 phases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Multi Agent
|
||||
|
||||
> Multi-agent coordination demos
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains multi-agent coordination demos.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `types.ts` | TypeScript module |
|
||||
| `coordination.ts` | TypeScript module |
|
||||
| `agents.ts` | TypeScript module |
|
||||
| `orchestrator.ts` | TypeScript module |
|
||||
| `package.json` | Data/Schema |
|
||||
|
||||
## Interfaces / APIs
|
||||
|
||||
*Document any APIs, CLI commands, or interfaces here.*
|
||||
|
||||
## Status
|
||||
|
||||
** Not Started**
|
||||
|
||||
See [STATUS.md](./STATUS.md) for detailed progress tracking.
|
||||
|
||||
## Architecture Reference
|
||||
|
||||
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
|
||||
|
||||
Parent: [Agents](..)
|
||||
|
||||
---
|
||||
*Last updated: 2026-01-23 23:25:09 UTC*
|
||||
@@ -0,0 +1,30 @@
|
||||
# Status: Multi Agent
|
||||
|
||||
## Current Phase
|
||||
|
||||
** NOT STARTED**
|
||||
|
||||
## Tasks
|
||||
|
||||
| Status | Task | Updated |
|
||||
|--------|------|---------|
|
||||
| ☐ | *No tasks defined* | - |
|
||||
|
||||
## Dependencies
|
||||
|
||||
*No external dependencies.*
|
||||
|
||||
## Issues / Blockers
|
||||
|
||||
*No current issues or blockers.*
|
||||
|
||||
## Activity Log
|
||||
|
||||
### 2026-01-23 23:25:09 UTC
|
||||
- **Phase**: NOT STARTED
|
||||
- **Action**: Initialized
|
||||
- **Details**: Status tracking initialized for this directory.
|
||||
|
||||
|
||||
---
|
||||
*Last updated: 2026-01-23 23:25:09 UTC*
|
||||
@@ -0,0 +1,893 @@
|
||||
/**
|
||||
* Multi-Agent Coordination System - Agent Implementations
|
||||
*/
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { $ } from "bun";
|
||||
import type {
|
||||
AgentRole,
|
||||
AgentState,
|
||||
AgentMessage,
|
||||
TaskDefinition,
|
||||
BlackboardEntry,
|
||||
ConsensusVote,
|
||||
} from "./types";
|
||||
import {
|
||||
Blackboard,
|
||||
MessageBus,
|
||||
AgentStateManager,
|
||||
SpawnController,
|
||||
MetricsCollector,
|
||||
} from "./coordination";
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function generateId(): string {
|
||||
return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
async function getVaultSecret(path: string): Promise<Record<string, any>> {
|
||||
const initKeys = await Bun.file("/opt/vault/init-keys.json").json();
|
||||
const token = initKeys.root_token;
|
||||
const result = await $`curl -sk -H "X-Vault-Token: ${token}" https://127.0.0.1:8200/v1/secret/data/${path}`.json();
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Base Agent Class
|
||||
// =============================================================================
|
||||
|
||||
abstract class BaseAgent {
|
||||
protected role: AgentRole;
|
||||
protected taskId: string;
|
||||
protected blackboard: Blackboard;
|
||||
protected messageBus: MessageBus;
|
||||
protected stateManager: AgentStateManager;
|
||||
protected metrics: MetricsCollector;
|
||||
protected llm!: OpenAI;
|
||||
protected model: string;
|
||||
protected state: AgentState;
|
||||
protected startTime: number;
|
||||
protected log: (msg: string) => void;
|
||||
|
||||
constructor(
|
||||
role: AgentRole,
|
||||
taskId: string,
|
||||
blackboard: Blackboard,
|
||||
messageBus: MessageBus,
|
||||
stateManager: AgentStateManager,
|
||||
metrics: MetricsCollector,
|
||||
model: string = "anthropic/claude-sonnet-4"
|
||||
) {
|
||||
this.role = role;
|
||||
this.taskId = taskId;
|
||||
this.blackboard = blackboard;
|
||||
this.messageBus = messageBus;
|
||||
this.stateManager = stateManager;
|
||||
this.metrics = metrics;
|
||||
this.model = model;
|
||||
this.startTime = Date.now();
|
||||
|
||||
this.state = {
|
||||
agent_id: `${role}-${taskId}`,
|
||||
role,
|
||||
status: "IDLE",
|
||||
current_task: "",
|
||||
progress: 0,
|
||||
confidence: 0,
|
||||
last_activity: now(),
|
||||
messages_sent: 0,
|
||||
messages_received: 0,
|
||||
proposals_made: 0,
|
||||
conflicts_detected: 0,
|
||||
};
|
||||
|
||||
this.log = (msg: string) => {
|
||||
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
||||
console.log(`[${elapsed}s] [${this.role}] ${msg}`);
|
||||
};
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
const secrets = await getVaultSecret("api-keys/openrouter");
|
||||
this.llm = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
apiKey: secrets.api_key,
|
||||
});
|
||||
|
||||
// Register message handler
|
||||
this.messageBus.onMessage(this.role, (msg) => this.handleMessage(msg));
|
||||
|
||||
await this.updateState({ status: "IDLE" });
|
||||
this.log("Initialized");
|
||||
}
|
||||
|
||||
protected async updateState(partial: Partial<AgentState>): Promise<void> {
|
||||
Object.assign(this.state, partial);
|
||||
this.state.last_activity = now();
|
||||
await this.stateManager.updateState(this.state);
|
||||
}
|
||||
|
||||
protected async handleMessage(msg: AgentMessage): Promise<void> {
|
||||
this.state.messages_received++;
|
||||
this.log(`Received ${msg.type} from ${msg.from}`);
|
||||
|
||||
// Override in subclasses for specific handling
|
||||
}
|
||||
|
||||
protected async sendMessage(to: AgentRole | "ALL", type: AgentMessage["type"], payload: any, correlationId?: string): Promise<void> {
|
||||
await this.messageBus.send(to, type, payload, correlationId);
|
||||
this.state.messages_sent++;
|
||||
await this.updateState({});
|
||||
}
|
||||
|
||||
protected async writeToBlackboard(section: Parameters<Blackboard["write"]>[0], key: string, value: any): Promise<void> {
|
||||
await this.blackboard.write(section, key, value, this.role);
|
||||
this.log(`Wrote to blackboard: ${section}/${key}`);
|
||||
}
|
||||
|
||||
protected async readFromBlackboard(section: Parameters<Blackboard["read"]>[0], key: string): Promise<any> {
|
||||
const entry = await this.blackboard.read(section, key);
|
||||
return entry?.value;
|
||||
}
|
||||
|
||||
protected async callLLM(systemPrompt: string, userPrompt: string, maxTokens: number = 2000): Promise<string> {
|
||||
const response = await this.llm.chat.completions.create({
|
||||
model: this.model,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: userPrompt },
|
||||
],
|
||||
max_tokens: maxTokens,
|
||||
temperature: 0.4,
|
||||
});
|
||||
return response.choices[0].message.content || "";
|
||||
}
|
||||
|
||||
abstract run(task: TaskDefinition): Promise<void>;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Agent ALPHA - Research & Analysis Specialist
|
||||
// =============================================================================
|
||||
|
||||
export class AgentAlpha extends BaseAgent {
|
||||
private proposals: Map<string, any> = new Map();
|
||||
|
||||
constructor(
|
||||
taskId: string,
|
||||
blackboard: Blackboard,
|
||||
messageBus: MessageBus,
|
||||
stateManager: AgentStateManager,
|
||||
metrics: MetricsCollector,
|
||||
model?: string
|
||||
) {
|
||||
super("ALPHA", taskId, blackboard, messageBus, stateManager, metrics, model);
|
||||
}
|
||||
|
||||
protected async handleMessage(msg: AgentMessage): Promise<void> {
|
||||
await super.handleMessage(msg);
|
||||
|
||||
switch (msg.type) {
|
||||
case "FEEDBACK":
|
||||
await this.handleFeedback(msg);
|
||||
break;
|
||||
case "QUERY":
|
||||
await this.handleQuery(msg);
|
||||
break;
|
||||
case "SYNC":
|
||||
await this.handleSync(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleFeedback(msg: AgentMessage): Promise<void> {
|
||||
const { proposal_id, accepted, reasoning, suggestions } = msg.payload;
|
||||
this.log(`Received feedback on proposal ${proposal_id}: ${accepted ? "ACCEPTED" : "REJECTED"}`);
|
||||
|
||||
if (!accepted && suggestions) {
|
||||
// Revise proposal based on feedback
|
||||
const original = this.proposals.get(proposal_id);
|
||||
if (original) {
|
||||
await this.reviseProposal(proposal_id, original, suggestions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleQuery(msg: AgentMessage): Promise<void> {
|
||||
const { question, context } = msg.payload;
|
||||
this.log(`Answering query from ${msg.from}: ${question.slice(0, 50)}...`);
|
||||
|
||||
const answer = await this.analyzeQuestion(question, context);
|
||||
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
|
||||
question,
|
||||
answer,
|
||||
confidence: answer.confidence,
|
||||
}, msg.id);
|
||||
}
|
||||
|
||||
private async handleSync(msg: AgentMessage): Promise<void> {
|
||||
// Share current state with other agents
|
||||
const currentProposals = Array.from(this.proposals.entries());
|
||||
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
|
||||
proposals: currentProposals,
|
||||
progress: this.state.progress,
|
||||
current_task: this.state.current_task,
|
||||
}, msg.id);
|
||||
}
|
||||
|
||||
private async analyzeQuestion(question: string, context: any): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent ALPHA, a research and analysis specialist. Answer questions thoroughly and provide confidence scores.
|
||||
Output JSON: { "answer": "...", "confidence": 0.0-1.0, "supporting_evidence": [...], "uncertainties": [...] }`,
|
||||
`Question: ${question}\nContext: ${JSON.stringify(context)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { answer: response, confidence: 0.5 };
|
||||
} catch {
|
||||
return { answer: response, confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private async reviseProposal(proposalId: string, original: any, suggestions: string[]): Promise<void> {
|
||||
this.log(`Revising proposal ${proposalId} based on feedback`);
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent ALPHA. Revise the proposal based on the feedback suggestions.
|
||||
Output JSON: { "revised_proposal": {...}, "changes_made": [...], "confidence": 0.0-1.0 }`,
|
||||
`Original proposal: ${JSON.stringify(original)}\nSuggestions: ${suggestions.join(", ")}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
if (match) {
|
||||
const revised = JSON.parse(match[0]);
|
||||
const newProposalId = proposalId + "-rev";
|
||||
this.proposals.set(newProposalId, revised.revised_proposal);
|
||||
await this.writeToBlackboard("solutions", newProposalId, revised);
|
||||
await this.sendMessage("BETA", "PROPOSAL", {
|
||||
proposal_id: newProposalId,
|
||||
proposal: revised.revised_proposal,
|
||||
is_revision: true,
|
||||
original_id: proposalId,
|
||||
});
|
||||
this.state.proposals_made++;
|
||||
}
|
||||
} catch (e) {
|
||||
this.log(`Failed to revise proposal: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
async run(task: TaskDefinition): Promise<void> {
|
||||
await this.updateState({ status: "WORKING", current_task: "Analyzing problem" });
|
||||
this.log(`Starting analysis of: ${task.objective}`);
|
||||
|
||||
// Phase 1: Problem Analysis
|
||||
await this.writeToBlackboard("problem", "objective", task.objective);
|
||||
await this.writeToBlackboard("problem", "constraints", task.constraints);
|
||||
|
||||
const analysis = await this.analyzeProblem(task);
|
||||
await this.writeToBlackboard("problem", "analysis", analysis);
|
||||
this.log(`Problem analysis complete. Complexity: ${analysis.complexity_score}`);
|
||||
|
||||
await this.updateState({ progress: 0.2 });
|
||||
|
||||
// Phase 2: Generate Initial Proposals
|
||||
await this.updateState({ current_task: "Generating solution proposals" });
|
||||
|
||||
const proposals = await this.generateProposals(task, analysis);
|
||||
for (const proposal of proposals) {
|
||||
const proposalId = generateId();
|
||||
this.proposals.set(proposalId, proposal);
|
||||
await this.writeToBlackboard("solutions", proposalId, proposal);
|
||||
|
||||
// Send proposal to BETA for evaluation
|
||||
await this.sendMessage("BETA", "PROPOSAL", {
|
||||
proposal_id: proposalId,
|
||||
proposal,
|
||||
phase: "initial",
|
||||
});
|
||||
this.state.proposals_made++;
|
||||
}
|
||||
|
||||
this.log(`Generated ${proposals.length} initial proposals`);
|
||||
await this.updateState({ progress: 0.5 });
|
||||
|
||||
// Phase 3: Iterative Refinement Loop
|
||||
await this.updateState({ current_task: "Refining solutions" });
|
||||
let iterations = 0;
|
||||
const maxIterations = 5;
|
||||
|
||||
while (iterations < maxIterations && this.state.progress < 0.9) {
|
||||
iterations++;
|
||||
this.log(`Refinement iteration ${iterations}`);
|
||||
|
||||
// Read feedback from blackboard
|
||||
const feedbackEntries = await this.blackboard.readSection("progress");
|
||||
const latestFeedback = feedbackEntries
|
||||
.filter(e => e.author === "BETA")
|
||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0];
|
||||
|
||||
if (latestFeedback?.value?.needs_revision) {
|
||||
// More work needed
|
||||
await this.refineBasedOnFeedback(latestFeedback.value);
|
||||
}
|
||||
|
||||
await this.updateState({ progress: Math.min(0.9, this.state.progress + 0.1) });
|
||||
await new Promise(r => setTimeout(r, 500)); // Brief pause between iterations
|
||||
}
|
||||
|
||||
await this.updateState({ status: "WAITING", current_task: "Awaiting consensus", progress: 0.9 });
|
||||
this.log("Analysis phase complete, awaiting consensus");
|
||||
}
|
||||
|
||||
private async analyzeProblem(task: TaskDefinition): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent ALPHA, a research and analysis specialist. Analyze the problem thoroughly.
|
||||
Output JSON: {
|
||||
"complexity_score": 0.0-1.0,
|
||||
"key_challenges": [...],
|
||||
"dependencies": [...],
|
||||
"risks": [...],
|
||||
"recommended_approach": "...",
|
||||
"subtask_breakdown": [...]
|
||||
}`,
|
||||
`Objective: ${task.objective}\nConstraints: ${task.constraints.join(", ")}\nSubtasks: ${task.subtasks.map(s => s.description).join(", ")}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { complexity_score: 0.5, key_challenges: [response] };
|
||||
} catch {
|
||||
return { complexity_score: 0.5, key_challenges: [response] };
|
||||
}
|
||||
}
|
||||
|
||||
private async generateProposals(task: TaskDefinition, analysis: any): Promise<any[]> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent ALPHA. Generate 2-3 distinct solution proposals based on the analysis.
|
||||
Output JSON array: [
|
||||
{ "name": "...", "approach": "...", "steps": [...], "pros": [...], "cons": [...], "confidence": 0.0-1.0 },
|
||||
...
|
||||
]`,
|
||||
`Task: ${task.objective}\nAnalysis: ${JSON.stringify(analysis)}\nConstraints: ${task.constraints.join(", ")}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\[[\s\S]*\]/);
|
||||
return match ? JSON.parse(match[0]) : [{ name: "Default", approach: response, steps: [], confidence: 0.5 }];
|
||||
} catch {
|
||||
return [{ name: "Default", approach: response, steps: [], confidence: 0.5 }];
|
||||
}
|
||||
}
|
||||
|
||||
private async refineBasedOnFeedback(feedback: any): Promise<void> {
|
||||
const { proposal_id, issues, suggestions } = feedback;
|
||||
if (proposal_id && this.proposals.has(proposal_id)) {
|
||||
await this.reviseProposal(proposal_id, this.proposals.get(proposal_id), suggestions || []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Agent BETA - Implementation & Synthesis Specialist
|
||||
// =============================================================================
|
||||
|
||||
export class AgentBeta extends BaseAgent {
|
||||
private evaluatedProposals: Map<string, any> = new Map();
|
||||
|
||||
constructor(
|
||||
taskId: string,
|
||||
blackboard: Blackboard,
|
||||
messageBus: MessageBus,
|
||||
stateManager: AgentStateManager,
|
||||
metrics: MetricsCollector,
|
||||
model?: string
|
||||
) {
|
||||
super("BETA", taskId, blackboard, messageBus, stateManager, metrics, model);
|
||||
}
|
||||
|
||||
protected async handleMessage(msg: AgentMessage): Promise<void> {
|
||||
await super.handleMessage(msg);
|
||||
|
||||
switch (msg.type) {
|
||||
case "PROPOSAL":
|
||||
await this.evaluateProposal(msg);
|
||||
break;
|
||||
case "QUERY":
|
||||
await this.handleQuery(msg);
|
||||
break;
|
||||
case "SYNC":
|
||||
await this.handleSync(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async evaluateProposal(msg: AgentMessage): Promise<void> {
|
||||
const { proposal_id, proposal, phase, is_revision } = msg.payload;
|
||||
this.log(`Evaluating proposal ${proposal_id} (${is_revision ? "revision" : phase})`);
|
||||
|
||||
await this.updateState({ current_task: `Evaluating proposal ${proposal_id}` });
|
||||
|
||||
const evaluation = await this.performEvaluation(proposal);
|
||||
this.evaluatedProposals.set(proposal_id, { proposal, evaluation });
|
||||
|
||||
// Write evaluation to blackboard
|
||||
await this.writeToBlackboard("progress", `eval_${proposal_id}`, {
|
||||
proposal_id,
|
||||
evaluation,
|
||||
evaluator: this.role,
|
||||
timestamp: now(),
|
||||
});
|
||||
|
||||
// Send feedback to ALPHA
|
||||
const accepted = evaluation.score >= 0.7 && evaluation.feasibility >= 0.6;
|
||||
await this.sendMessage("ALPHA", "FEEDBACK", {
|
||||
proposal_id,
|
||||
accepted,
|
||||
score: evaluation.score,
|
||||
reasoning: evaluation.reasoning,
|
||||
suggestions: evaluation.improvements,
|
||||
needs_revision: !accepted,
|
||||
}, msg.id);
|
||||
|
||||
if (accepted) {
|
||||
this.log(`Proposal ${proposal_id} accepted with score ${evaluation.score}`);
|
||||
// Cast vote for consensus
|
||||
const vote: ConsensusVote = {
|
||||
agent: this.role,
|
||||
proposal_id,
|
||||
vote: "ACCEPT",
|
||||
reasoning: evaluation.reasoning,
|
||||
timestamp: now(),
|
||||
};
|
||||
await this.blackboard.recordVote(vote);
|
||||
} else {
|
||||
this.log(`Proposal ${proposal_id} needs revision. Score: ${evaluation.score}`);
|
||||
await this.metrics.increment("conflicts_detected");
|
||||
this.state.conflicts_detected++;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleQuery(msg: AgentMessage): Promise<void> {
|
||||
const { question, context } = msg.payload;
|
||||
this.log(`Answering query from ${msg.from}`);
|
||||
|
||||
const answer = await this.synthesizeAnswer(question, context);
|
||||
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
|
||||
question,
|
||||
answer,
|
||||
}, msg.id);
|
||||
}
|
||||
|
||||
private async handleSync(msg: AgentMessage): Promise<void> {
|
||||
const evaluations = Array.from(this.evaluatedProposals.entries());
|
||||
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
|
||||
evaluations,
|
||||
progress: this.state.progress,
|
||||
}, msg.id);
|
||||
}
|
||||
|
||||
private async performEvaluation(proposal: any): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent BETA, an implementation and synthesis specialist. Evaluate this proposal critically.
|
||||
Output JSON: {
|
||||
"score": 0.0-1.0,
|
||||
"feasibility": 0.0-1.0,
|
||||
"completeness": 0.0-1.0,
|
||||
"reasoning": "...",
|
||||
"strengths": [...],
|
||||
"weaknesses": [...],
|
||||
"improvements": [...],
|
||||
"implementation_notes": "..."
|
||||
}`,
|
||||
`Proposal: ${JSON.stringify(proposal)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { score: 0.5, feasibility: 0.5, reasoning: response };
|
||||
} catch {
|
||||
return { score: 0.5, feasibility: 0.5, reasoning: response };
|
||||
}
|
||||
}
|
||||
|
||||
private async synthesizeAnswer(question: string, context: any): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent BETA. Provide a practical, implementation-focused answer.`,
|
||||
`Question: ${question}\nContext: ${JSON.stringify(context)}`
|
||||
);
|
||||
return { answer: response, source: this.role };
|
||||
}
|
||||
|
||||
async run(task: TaskDefinition): Promise<void> {
|
||||
await this.updateState({ status: "WORKING", current_task: "Preparing for evaluation" });
|
||||
this.log(`Starting evaluation mode for: ${task.objective}`);
|
||||
|
||||
// Phase 1: Read and understand the problem from blackboard
|
||||
await new Promise(r => setTimeout(r, 1000)); // Wait for ALPHA to write problem analysis
|
||||
|
||||
const problemAnalysis = await this.readFromBlackboard("problem", "analysis");
|
||||
if (problemAnalysis) {
|
||||
this.log(`Read problem analysis: complexity ${problemAnalysis.complexity_score}`);
|
||||
}
|
||||
|
||||
await this.updateState({ progress: 0.1 });
|
||||
|
||||
// Phase 2: Active evaluation loop - wait for proposals and evaluate
|
||||
await this.updateState({ current_task: "Evaluating proposals" });
|
||||
|
||||
let iterations = 0;
|
||||
const maxIterations = 10;
|
||||
|
||||
while (iterations < maxIterations && this.state.progress < 0.9) {
|
||||
iterations++;
|
||||
|
||||
// Check for new proposals on blackboard
|
||||
const solutions = await this.blackboard.readSection("solutions");
|
||||
const newProposals = solutions.filter(s =>
|
||||
!this.evaluatedProposals.has(s.key) && s.author === "ALPHA"
|
||||
);
|
||||
|
||||
for (const entry of newProposals) {
|
||||
// Evaluate via message handling (simulated direct read)
|
||||
if (!this.evaluatedProposals.has(entry.key)) {
|
||||
await this.evaluateProposal({
|
||||
id: generateId(),
|
||||
from: "ALPHA",
|
||||
to: "BETA",
|
||||
type: "PROPOSAL",
|
||||
payload: {
|
||||
proposal_id: entry.key,
|
||||
proposal: entry.value,
|
||||
phase: "blackboard_read",
|
||||
},
|
||||
timestamp: now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateState({ progress: Math.min(0.9, 0.1 + (iterations * 0.08)) });
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
|
||||
// Phase 3: Generate synthesis of best solutions
|
||||
await this.updateState({ current_task: "Synthesizing final solution" });
|
||||
|
||||
const bestProposals = Array.from(this.evaluatedProposals.entries())
|
||||
.filter(([_, v]) => v.evaluation.score >= 0.6)
|
||||
.sort((a, b) => b[1].evaluation.score - a[1].evaluation.score);
|
||||
|
||||
if (bestProposals.length > 0) {
|
||||
const synthesis = await this.synthesizeSolution(bestProposals.map(([_, v]) => v));
|
||||
await this.writeToBlackboard("solutions", "synthesis", synthesis);
|
||||
this.log(`Generated synthesis from ${bestProposals.length} proposals`);
|
||||
|
||||
// Vote for synthesis
|
||||
const vote: ConsensusVote = {
|
||||
agent: this.role,
|
||||
proposal_id: "synthesis",
|
||||
vote: "ACCEPT",
|
||||
reasoning: "Synthesized best elements from top proposals",
|
||||
timestamp: now(),
|
||||
};
|
||||
await this.blackboard.recordVote(vote);
|
||||
}
|
||||
|
||||
await this.updateState({ status: "WAITING", current_task: "Awaiting consensus", progress: 0.9 });
|
||||
this.log("Evaluation phase complete, awaiting consensus");
|
||||
}
|
||||
|
||||
private async synthesizeSolution(proposals: any[]): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent BETA. Synthesize the best elements from these evaluated proposals into a final solution.
|
||||
Output JSON: {
|
||||
"final_solution": { "name": "...", "approach": "...", "steps": [...] },
|
||||
"confidence": 0.0-1.0,
|
||||
"sources": [...],
|
||||
"trade_offs": [...]
|
||||
}`,
|
||||
`Proposals and evaluations: ${JSON.stringify(proposals)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { final_solution: { approach: response }, confidence: 0.5 };
|
||||
} catch {
|
||||
return { final_solution: { approach: response }, confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Agent GAMMA - Mediator & Resolver (Conditionally Spawned)
|
||||
// =============================================================================
|
||||
|
||||
export class AgentGamma extends BaseAgent {
|
||||
private spawnReason: string;
|
||||
|
||||
constructor(
|
||||
taskId: string,
|
||||
blackboard: Blackboard,
|
||||
messageBus: MessageBus,
|
||||
stateManager: AgentStateManager,
|
||||
metrics: MetricsCollector,
|
||||
spawnReason: string,
|
||||
model?: string
|
||||
) {
|
||||
super("GAMMA", taskId, blackboard, messageBus, stateManager, metrics, model);
|
||||
this.spawnReason = spawnReason;
|
||||
}
|
||||
|
||||
protected async handleMessage(msg: AgentMessage): Promise<void> {
|
||||
await super.handleMessage(msg);
|
||||
|
||||
switch (msg.type) {
|
||||
case "QUERY":
|
||||
await this.handleQuery(msg);
|
||||
break;
|
||||
case "HANDOFF":
|
||||
await this.handleHandoff(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleQuery(msg: AgentMessage): Promise<void> {
|
||||
const { question, context } = msg.payload;
|
||||
const answer = await this.mediateQuestion(question, context);
|
||||
await this.sendMessage(msg.from as AgentRole, "RESPONSE", answer, msg.id);
|
||||
}
|
||||
|
||||
private async handleHandoff(msg: AgentMessage): Promise<void> {
|
||||
this.log(`Received handoff from ${msg.from}: ${JSON.stringify(msg.payload).slice(0, 100)}...`);
|
||||
}
|
||||
|
||||
private async mediateQuestion(question: string, context: any): Promise<any> {
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA, a mediator and conflict resolver. Provide balanced, integrative answers.`,
|
||||
`Question: ${question}\nContext: ${JSON.stringify(context)}`
|
||||
);
|
||||
return { answer: response, mediator: true };
|
||||
}
|
||||
|
||||
async run(task: TaskDefinition): Promise<void> {
|
||||
await this.updateState({ status: "WORKING", current_task: `Resolving: ${this.spawnReason}` });
|
||||
this.log(`GAMMA spawned for: ${this.spawnReason}`);
|
||||
|
||||
// Announce presence
|
||||
await this.sendMessage("ALL", "SYNC", {
|
||||
event: "GAMMA_SPAWNED",
|
||||
reason: this.spawnReason,
|
||||
timestamp: now(),
|
||||
});
|
||||
|
||||
// Phase 1: Gather current state from all sources
|
||||
const problemState = await this.blackboard.readSection("problem");
|
||||
const solutionsState = await this.blackboard.readSection("solutions");
|
||||
const progressState = await this.blackboard.readSection("progress");
|
||||
const conflictsState = await this.blackboard.readSection("conflicts");
|
||||
|
||||
this.log(`Gathered state: ${problemState.length} problem entries, ${solutionsState.length} solutions`);
|
||||
|
||||
await this.updateState({ progress: 0.2 });
|
||||
|
||||
// Phase 2: Analyze situation based on spawn reason
|
||||
let resolution: any;
|
||||
|
||||
switch (this.spawnReason) {
|
||||
case "STUCK":
|
||||
resolution = await this.resolveStuck(task, problemState, solutionsState, progressState);
|
||||
break;
|
||||
case "CONFLICT":
|
||||
resolution = await this.resolveConflict(conflictsState, solutionsState);
|
||||
break;
|
||||
case "COMPLEXITY":
|
||||
resolution = await this.handleComplexity(task, problemState, solutionsState);
|
||||
break;
|
||||
case "SUCCESS":
|
||||
resolution = await this.validateSuccess(task, solutionsState);
|
||||
break;
|
||||
default:
|
||||
resolution = await this.generalMediation(task, problemState, solutionsState);
|
||||
}
|
||||
|
||||
await this.writeToBlackboard("progress", "gamma_resolution", resolution);
|
||||
await this.updateState({ progress: 0.7 });
|
||||
|
||||
// Phase 3: Drive to consensus
|
||||
await this.updateState({ current_task: "Driving consensus" });
|
||||
|
||||
const consensusResult = await this.driveConsensus(resolution);
|
||||
await this.writeToBlackboard("consensus", "final", consensusResult);
|
||||
|
||||
// Cast final vote
|
||||
const vote: ConsensusVote = {
|
||||
agent: this.role,
|
||||
proposal_id: "final_consensus",
|
||||
vote: consensusResult.achieved ? "ACCEPT" : "ABSTAIN",
|
||||
reasoning: consensusResult.reasoning,
|
||||
timestamp: now(),
|
||||
};
|
||||
await this.blackboard.recordVote(vote);
|
||||
|
||||
await this.updateState({ status: "COMPLETED", progress: 1.0, current_task: "Resolution complete" });
|
||||
this.log(`GAMMA resolution complete. Consensus: ${consensusResult.achieved}`);
|
||||
}
|
||||
|
||||
private async resolveStuck(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[], progress: BlackboardEntry[]): Promise<any> {
|
||||
this.log("Analyzing stuck condition...");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA, a mediator. The other agents appear stuck. Analyze the situation and provide direction.
|
||||
Output JSON: {
|
||||
"diagnosis": "why agents are stuck",
|
||||
"blockers": [...],
|
||||
"recommended_actions": [...],
|
||||
"new_approach": "...",
|
||||
"confidence": 0.0-1.0
|
||||
}`,
|
||||
`Task: ${task.objective}\nProblem analysis: ${JSON.stringify(problem)}\nSolutions so far: ${JSON.stringify(solutions)}\nProgress: ${JSON.stringify(progress)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
const result = match ? JSON.parse(match[0]) : { diagnosis: response, confidence: 0.5 };
|
||||
|
||||
// Broadcast new direction to other agents
|
||||
await this.sendMessage("ALL", "HANDOFF", {
|
||||
type: "NEW_DIRECTION",
|
||||
...result,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch {
|
||||
return { diagnosis: response, confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveConflict(conflicts: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
|
||||
this.log("Mediating conflicts...");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA, a conflict mediator. Resolve the conflicts between proposals.
|
||||
Output JSON: {
|
||||
"conflicts_analyzed": [...],
|
||||
"resolution": "...",
|
||||
"compromise_solution": {...},
|
||||
"reasoning": "...",
|
||||
"confidence": 0.0-1.0
|
||||
}`,
|
||||
`Conflicts: ${JSON.stringify(conflicts)}\nSolutions: ${JSON.stringify(solutions)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
const result = match ? JSON.parse(match[0]) : { resolution: response, confidence: 0.5 };
|
||||
|
||||
await this.metrics.increment("conflicts_resolved");
|
||||
return result;
|
||||
} catch {
|
||||
return { resolution: response, confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private async handleComplexity(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
|
||||
this.log("Breaking down complexity...");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA. The task is too complex. Break it into manageable pieces.
|
||||
Output JSON: {
|
||||
"complexity_analysis": "...",
|
||||
"decomposition": [...subtasks...],
|
||||
"priority_order": [...],
|
||||
"delegation": { "ALPHA": [...], "BETA": [...] },
|
||||
"confidence": 0.0-1.0
|
||||
}`,
|
||||
`Task: ${task.objective}\nProblem: ${JSON.stringify(problem)}\nCurrent solutions: ${JSON.stringify(solutions)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
const result = match ? JSON.parse(match[0]) : { decomposition: [response], confidence: 0.5 };
|
||||
|
||||
// Delegate subtasks
|
||||
if (result.delegation) {
|
||||
if (result.delegation.ALPHA) {
|
||||
await this.sendMessage("ALPHA", "HANDOFF", {
|
||||
type: "SUBTASK_ASSIGNMENT",
|
||||
tasks: result.delegation.ALPHA,
|
||||
});
|
||||
}
|
||||
if (result.delegation.BETA) {
|
||||
await this.sendMessage("BETA", "HANDOFF", {
|
||||
type: "SUBTASK_ASSIGNMENT",
|
||||
tasks: result.delegation.BETA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch {
|
||||
return { decomposition: [response], confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private async validateSuccess(task: TaskDefinition, solutions: BlackboardEntry[]): Promise<any> {
|
||||
this.log("Validating task success...");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA. Validate that the task has been successfully completed.
|
||||
Output JSON: {
|
||||
"success": true/false,
|
||||
"criteria_met": [...],
|
||||
"criteria_unmet": [...],
|
||||
"final_assessment": "...",
|
||||
"recommendations": [...],
|
||||
"confidence": 0.0-1.0
|
||||
}`,
|
||||
`Task: ${task.objective}\nSuccess criteria: ${task.success_criteria.join(", ")}\nSolutions: ${JSON.stringify(solutions)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { success: false, final_assessment: response };
|
||||
} catch {
|
||||
return { success: false, final_assessment: response };
|
||||
}
|
||||
}
|
||||
|
||||
private async generalMediation(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
|
||||
this.log("General mediation...");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA, a general mediator. Help coordinate the other agents toward a solution.
|
||||
Output JSON: {
|
||||
"assessment": "...",
|
||||
"recommendations": [...],
|
||||
"next_steps": [...],
|
||||
"confidence": 0.0-1.0
|
||||
}`,
|
||||
`Task: ${task.objective}\nProblem: ${JSON.stringify(problem)}\nSolutions: ${JSON.stringify(solutions)}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { assessment: response, confidence: 0.5 };
|
||||
} catch {
|
||||
return { assessment: response, confidence: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private async driveConsensus(resolution: any): Promise<any> {
|
||||
this.log("Driving to consensus...");
|
||||
|
||||
// Check existing votes
|
||||
const synthesisVotes = await this.blackboard.getVotes("synthesis");
|
||||
const acceptVotes = synthesisVotes.filter(v => v.vote === "ACCEPT");
|
||||
|
||||
const response = await this.callLLM(
|
||||
`You are Agent GAMMA. Based on the resolution and votes, determine if consensus is achieved.
|
||||
Output JSON: {
|
||||
"achieved": true/false,
|
||||
"reasoning": "...",
|
||||
"final_decision": "...",
|
||||
"dissenting_views": [...],
|
||||
"action_items": [...]
|
||||
}`,
|
||||
`Resolution: ${JSON.stringify(resolution)}\nVotes: ${JSON.stringify(synthesisVotes)}\nAccept count: ${acceptVotes.length}`
|
||||
);
|
||||
|
||||
try {
|
||||
const match = response.match(/\{[\s\S]*\}/);
|
||||
return match ? JSON.parse(match[0]) : { achieved: acceptVotes.length >= 2, reasoning: response };
|
||||
} catch {
|
||||
return { achieved: acceptVotes.length >= 2, reasoning: response };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseAgent };
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "multi-agent-coordination",
|
||||
"dependencies": {
|
||||
"openai": "^4.0.0",
|
||||
"redis": "^4.6.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="],
|
||||
|
||||
"@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="],
|
||||
|
||||
"@redis/graph": ["@redis/graph@1.1.1", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="],
|
||||
|
||||
"@redis/json": ["@redis/json@1.0.7", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="],
|
||||
|
||||
"@redis/search": ["@redis/search@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="],
|
||||
|
||||
"@redis/time-series": ["@redis/time-series@1.1.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="],
|
||||
|
||||
"@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||
|
||||
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
|
||||
|
||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="],
|
||||
|
||||
"redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
/**
|
||||
* Multi-Agent Coordination System - Blackboard & Messaging
|
||||
*/
|
||||
|
||||
import { createClient, RedisClientType } from "redis";
|
||||
import { $ } from "bun";
|
||||
import type {
|
||||
AgentRole,
|
||||
AgentMessage,
|
||||
BlackboardEntry,
|
||||
BlackboardSection,
|
||||
AgentState,
|
||||
CoordinationMetrics,
|
||||
SpawnCondition,
|
||||
ConsensusVote,
|
||||
} from "./types";
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function generateId(): string {
|
||||
return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
async function getVaultSecret(path: string): Promise<Record<string, any>> {
|
||||
const initKeys = await Bun.file("/opt/vault/init-keys.json").json();
|
||||
const token = initKeys.root_token;
|
||||
const result = await $`curl -sk -H "X-Vault-Token: ${token}" https://127.0.0.1:8200/v1/secret/data/${path}`.json();
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Blackboard System - Shared Memory for Agent Coordination
|
||||
// =============================================================================
|
||||
|
||||
export class Blackboard {
|
||||
private redis!: RedisClientType;
|
||||
private taskId: string;
|
||||
private versionCounters: Map<string, number> = new Map();
|
||||
|
||||
constructor(taskId: string) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const creds = await getVaultSecret("services/dragonfly");
|
||||
this.redis = createClient({
|
||||
url: "redis://" + creds.host + ":" + creds.port,
|
||||
password: creds.password,
|
||||
});
|
||||
await this.redis.connect();
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
await this.redis.quit();
|
||||
}
|
||||
|
||||
private key(section: BlackboardSection, entryKey: string): string {
|
||||
return `blackboard:${this.taskId}:${section}:${entryKey}`;
|
||||
}
|
||||
|
||||
private sectionKey(section: BlackboardSection): string {
|
||||
return `blackboard:${this.taskId}:${section}`;
|
||||
}
|
||||
|
||||
async write(section: BlackboardSection, entryKey: string, value: any, author: AgentRole): Promise<BlackboardEntry> {
|
||||
const fullKey = this.key(section, entryKey);
|
||||
const currentVersion = this.versionCounters.get(fullKey) || 0;
|
||||
const newVersion = currentVersion + 1;
|
||||
this.versionCounters.set(fullKey, newVersion);
|
||||
|
||||
const entry: BlackboardEntry = {
|
||||
section,
|
||||
key: entryKey,
|
||||
value,
|
||||
author,
|
||||
version: newVersion,
|
||||
timestamp: now(),
|
||||
};
|
||||
|
||||
await this.redis.hSet(this.sectionKey(section), entryKey, JSON.stringify(entry));
|
||||
await this.redis.rPush(`blackboard:${this.taskId}:history`, JSON.stringify({
|
||||
action: "WRITE",
|
||||
...entry,
|
||||
}));
|
||||
|
||||
// Increment metrics
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_writes", 1);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
async read(section: BlackboardSection, entryKey: string): Promise<BlackboardEntry | null> {
|
||||
const data = await this.redis.hGet(this.sectionKey(section), entryKey);
|
||||
if (!data) return null;
|
||||
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_reads", 1);
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
async readSection(section: BlackboardSection): Promise<BlackboardEntry[]> {
|
||||
const data = await this.redis.hGetAll(this.sectionKey(section));
|
||||
const entries: BlackboardEntry[] = [];
|
||||
for (const value of Object.values(data)) {
|
||||
entries.push(JSON.parse(value));
|
||||
}
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_reads", entries.length);
|
||||
return entries;
|
||||
}
|
||||
|
||||
async getHistory(limit: number = 100): Promise<any[]> {
|
||||
const data = await this.redis.lRange(`blackboard:${this.taskId}:history`, -limit, -1);
|
||||
return data.map(d => JSON.parse(d));
|
||||
}
|
||||
|
||||
// Consensus tracking
|
||||
async recordVote(vote: ConsensusVote): Promise<void> {
|
||||
await this.redis.rPush(`blackboard:${this.taskId}:votes:${vote.proposal_id}`, JSON.stringify(vote));
|
||||
await this.write("consensus", vote.proposal_id + ":" + vote.agent, vote, vote.agent);
|
||||
}
|
||||
|
||||
async getVotes(proposalId: string): Promise<ConsensusVote[]> {
|
||||
const data = await this.redis.lRange(`blackboard:${this.taskId}:votes:${proposalId}`, 0, -1);
|
||||
return data.map(d => JSON.parse(d));
|
||||
}
|
||||
|
||||
async checkConsensus(proposalId: string, requiredAgents: AgentRole[]): Promise<{ reached: boolean; votes: ConsensusVote[] }> {
|
||||
const votes = await this.getVotes(proposalId);
|
||||
const acceptVotes = votes.filter(v => v.vote === "ACCEPT");
|
||||
const rejectVotes = votes.filter(v => v.vote === "REJECT");
|
||||
|
||||
// Consensus requires majority accept and no rejects from required agents
|
||||
const hasAllRequired = requiredAgents.every(agent =>
|
||||
votes.some(v => v.agent === agent)
|
||||
);
|
||||
|
||||
const reached = hasAllRequired &&
|
||||
acceptVotes.length > rejectVotes.length &&
|
||||
rejectVotes.length === 0;
|
||||
|
||||
return { reached, votes };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Direct Messaging System - Point-to-Point Communication
|
||||
// =============================================================================
|
||||
|
||||
export class MessageBus {
|
||||
private redis!: RedisClientType;
|
||||
private subscriber!: RedisClientType;
|
||||
private taskId: string;
|
||||
private agentRole: AgentRole;
|
||||
private messageHandlers: Map<string, (msg: AgentMessage) => void> = new Map();
|
||||
|
||||
constructor(taskId: string, agentRole: AgentRole) {
|
||||
this.taskId = taskId;
|
||||
this.agentRole = agentRole;
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const creds = await getVaultSecret("services/dragonfly");
|
||||
const config = {
|
||||
url: "redis://" + creds.host + ":" + creds.port,
|
||||
password: creds.password,
|
||||
};
|
||||
|
||||
this.redis = createClient(config);
|
||||
this.subscriber = createClient(config);
|
||||
|
||||
await this.redis.connect();
|
||||
await this.subscriber.connect();
|
||||
|
||||
// Subscribe to direct messages and broadcast
|
||||
await this.subscriber.subscribe(`msg:${this.taskId}:${this.agentRole}`, (message) => {
|
||||
this.handleMessage(JSON.parse(message));
|
||||
});
|
||||
await this.subscriber.subscribe(`msg:${this.taskId}:ALL`, (message) => {
|
||||
this.handleMessage(JSON.parse(message));
|
||||
});
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
await this.subscriber.unsubscribe();
|
||||
await this.subscriber.quit();
|
||||
await this.redis.quit();
|
||||
}
|
||||
|
||||
private handleMessage(msg: AgentMessage): void {
|
||||
// Store in message log
|
||||
this.redis.rPush(`msg:${this.taskId}:log`, JSON.stringify(msg));
|
||||
this.redis.hIncrBy(`metrics:${this.taskId}`, "total_messages", 1);
|
||||
this.redis.hIncrBy(`metrics:${this.taskId}`, "direct_messages", 1);
|
||||
|
||||
// Call registered handlers
|
||||
for (const handler of this.messageHandlers.values()) {
|
||||
try {
|
||||
handler(msg);
|
||||
} catch (e) {
|
||||
console.error("Message handler error:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(handlerId: string, handler: (msg: AgentMessage) => void): void {
|
||||
this.messageHandlers.set(handlerId, handler);
|
||||
}
|
||||
|
||||
removeHandler(handlerId: string): void {
|
||||
this.messageHandlers.delete(handlerId);
|
||||
}
|
||||
|
||||
async send(to: AgentRole | "ALL", type: AgentMessage["type"], payload: any, correlationId?: string): Promise<AgentMessage> {
|
||||
const msg: AgentMessage = {
|
||||
id: generateId(),
|
||||
from: this.agentRole,
|
||||
to,
|
||||
type,
|
||||
payload,
|
||||
timestamp: now(),
|
||||
correlation_id: correlationId,
|
||||
};
|
||||
|
||||
await this.redis.publish(`msg:${this.taskId}:${to}`, JSON.stringify(msg));
|
||||
await this.redis.rPush(`msg:${this.taskId}:log`, JSON.stringify(msg));
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, "total_messages", 1);
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, "direct_messages", 1);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
async getMessageLog(limit: number = 100): Promise<AgentMessage[]> {
|
||||
const data = await this.redis.lRange(`msg:${this.taskId}:log`, -limit, -1);
|
||||
return data.map(d => JSON.parse(d));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Agent State Manager
|
||||
// =============================================================================
|
||||
|
||||
export class AgentStateManager {
|
||||
private redis!: RedisClientType;
|
||||
private taskId: string;
|
||||
|
||||
constructor(taskId: string) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const creds = await getVaultSecret("services/dragonfly");
|
||||
this.redis = createClient({
|
||||
url: "redis://" + creds.host + ":" + creds.port,
|
||||
password: creds.password,
|
||||
});
|
||||
await this.redis.connect();
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
await this.redis.quit();
|
||||
}
|
||||
|
||||
async updateState(state: AgentState): Promise<void> {
|
||||
state.last_activity = now();
|
||||
await this.redis.hSet(`agents:${this.taskId}`, state.role, JSON.stringify(state));
|
||||
}
|
||||
|
||||
async getState(role: AgentRole): Promise<AgentState | null> {
|
||||
const data = await this.redis.hGet(`agents:${this.taskId}`, role);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
async getAllStates(): Promise<AgentState[]> {
|
||||
const data = await this.redis.hGetAll(`agents:${this.taskId}`);
|
||||
return Object.values(data).map(d => JSON.parse(d));
|
||||
}
|
||||
|
||||
async isBlocked(role: AgentRole, thresholdSeconds: number): Promise<boolean> {
|
||||
const state = await this.getState(role);
|
||||
if (!state || !state.blocked_since) return false;
|
||||
|
||||
const blockedDuration = (Date.now() - new Date(state.blocked_since).getTime()) / 1000;
|
||||
return blockedDuration > thresholdSeconds;
|
||||
}
|
||||
|
||||
async detectStuckAgents(thresholdSeconds: number): Promise<AgentRole[]> {
|
||||
const states = await this.getAllStates();
|
||||
const stuckAgents: AgentRole[] = [];
|
||||
|
||||
for (const state of states) {
|
||||
if (state.status === "COMPLETED" || state.status === "FAILED") continue;
|
||||
|
||||
const lastActivity = new Date(state.last_activity).getTime();
|
||||
const inactivitySeconds = (Date.now() - lastActivity) / 1000;
|
||||
|
||||
if (inactivitySeconds > thresholdSeconds) {
|
||||
stuckAgents.push(state.role);
|
||||
}
|
||||
}
|
||||
|
||||
return stuckAgents;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Spawn Controller - Manages Conditional Agent Spawning
|
||||
// =============================================================================
|
||||
|
||||
export class SpawnController {
|
||||
private redis!: RedisClientType;
|
||||
private taskId: string;
|
||||
private conditions: SpawnCondition[] = [];
|
||||
private gammaSpawned: boolean = false;
|
||||
|
||||
constructor(taskId: string) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const creds = await getVaultSecret("services/dragonfly");
|
||||
this.redis = createClient({
|
||||
url: "redis://" + creds.host + ":" + creds.port,
|
||||
password: creds.password,
|
||||
});
|
||||
await this.redis.connect();
|
||||
|
||||
// Initialize default spawn conditions
|
||||
this.conditions = [
|
||||
{
|
||||
type: "STUCK",
|
||||
threshold: 30, // seconds
|
||||
current_value: 0,
|
||||
triggered: false,
|
||||
description: "Spawn GAMMA when agents are stuck for 30+ seconds",
|
||||
},
|
||||
{
|
||||
type: "CONFLICT",
|
||||
threshold: 3, // conflicts
|
||||
current_value: 0,
|
||||
triggered: false,
|
||||
description: "Spawn GAMMA when 3+ unresolved conflicts detected",
|
||||
},
|
||||
{
|
||||
type: "COMPLEXITY",
|
||||
threshold: 0.8, // complexity score
|
||||
current_value: 0,
|
||||
triggered: false,
|
||||
description: "Spawn GAMMA when task complexity exceeds 0.8",
|
||||
},
|
||||
{
|
||||
type: "SUCCESS",
|
||||
threshold: 1.0, // completion
|
||||
current_value: 0,
|
||||
triggered: false,
|
||||
description: "Spawn GAMMA to validate and finalize when task complete",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
await this.redis.quit();
|
||||
}
|
||||
|
||||
async updateCondition(type: SpawnCondition["type"], value: number): Promise<SpawnCondition | null> {
|
||||
const condition = this.conditions.find(c => c.type === type);
|
||||
if (!condition) return null;
|
||||
|
||||
condition.current_value = value;
|
||||
condition.triggered = value >= condition.threshold;
|
||||
|
||||
await this.redis.hSet(`spawn:${this.taskId}:conditions`, type, JSON.stringify(condition));
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
async checkSpawnConditions(): Promise<{ shouldSpawn: boolean; reason: SpawnCondition | null }> {
|
||||
if (this.gammaSpawned) {
|
||||
return { shouldSpawn: false, reason: null };
|
||||
}
|
||||
|
||||
for (const condition of this.conditions) {
|
||||
if (condition.triggered) {
|
||||
return { shouldSpawn: true, reason: condition };
|
||||
}
|
||||
}
|
||||
|
||||
return { shouldSpawn: false, reason: null };
|
||||
}
|
||||
|
||||
async markGammaSpawned(reason: SpawnCondition): Promise<void> {
|
||||
this.gammaSpawned = true;
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawned", "true");
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawn_reason", reason.type);
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawn_time", now());
|
||||
}
|
||||
|
||||
isGammaSpawned(): boolean {
|
||||
return this.gammaSpawned;
|
||||
}
|
||||
|
||||
getConditions(): SpawnCondition[] {
|
||||
return this.conditions;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Metrics Collector
|
||||
// =============================================================================
|
||||
|
||||
export class MetricsCollector {
|
||||
private redis!: RedisClientType;
|
||||
private taskId: string;
|
||||
private startTime: number;
|
||||
|
||||
constructor(taskId: string) {
|
||||
this.taskId = taskId;
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const creds = await getVaultSecret("services/dragonfly");
|
||||
this.redis = createClient({
|
||||
url: "redis://" + creds.host + ":" + creds.port,
|
||||
password: creds.password,
|
||||
});
|
||||
await this.redis.connect();
|
||||
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, {
|
||||
task_id: this.taskId,
|
||||
start_time: now(),
|
||||
total_messages: "0",
|
||||
direct_messages: "0",
|
||||
blackboard_writes: "0",
|
||||
blackboard_reads: "0",
|
||||
conflicts_detected: "0",
|
||||
conflicts_resolved: "0",
|
||||
gamma_spawned: "false",
|
||||
final_consensus: "false",
|
||||
performance_score: "0",
|
||||
});
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
await this.redis.quit();
|
||||
}
|
||||
|
||||
async increment(metric: string, by: number = 1): Promise<void> {
|
||||
await this.redis.hIncrBy(`metrics:${this.taskId}`, metric, by);
|
||||
}
|
||||
|
||||
async set(metric: string, value: string): Promise<void> {
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, metric, value);
|
||||
}
|
||||
|
||||
async getMetrics(): Promise<CoordinationMetrics> {
|
||||
const data = await this.redis.hGetAll(`metrics:${this.taskId}`);
|
||||
|
||||
return {
|
||||
task_id: this.taskId,
|
||||
start_time: data.start_time || now(),
|
||||
end_time: data.end_time,
|
||||
total_messages: parseInt(data.total_messages || "0"),
|
||||
direct_messages: parseInt(data.direct_messages || "0"),
|
||||
blackboard_writes: parseInt(data.blackboard_writes || "0"),
|
||||
blackboard_reads: parseInt(data.blackboard_reads || "0"),
|
||||
conflicts_detected: parseInt(data.conflicts_detected || "0"),
|
||||
conflicts_resolved: parseInt(data.conflicts_resolved || "0"),
|
||||
gamma_spawned: data.gamma_spawned === "true",
|
||||
gamma_spawn_reason: data.gamma_spawn_reason,
|
||||
gamma_spawn_time: data.gamma_spawn_time,
|
||||
final_consensus: data.final_consensus === "true",
|
||||
performance_score: parseFloat(data.performance_score || "0"),
|
||||
};
|
||||
}
|
||||
|
||||
async finalize(consensus: boolean): Promise<CoordinationMetrics> {
|
||||
const endTime = now();
|
||||
const elapsedMs = Date.now() - this.startTime;
|
||||
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "end_time", endTime);
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "final_consensus", consensus ? "true" : "false");
|
||||
|
||||
// Calculate performance score
|
||||
const metrics = await this.getMetrics();
|
||||
const messageEfficiency = Math.max(0, 1 - (metrics.total_messages / 100));
|
||||
const conflictPenalty = metrics.conflicts_detected * 0.1;
|
||||
const timePenalty = Math.min(0.5, elapsedMs / 120000);
|
||||
const consensusBonus = consensus ? 0.2 : 0;
|
||||
const gammaBonus = metrics.gamma_spawned && consensus ? 0.1 : (metrics.gamma_spawned ? -0.1 : 0);
|
||||
|
||||
const score = Math.max(0, Math.min(1,
|
||||
0.5 + messageEfficiency * 0.2 - conflictPenalty - timePenalty + consensusBonus + gammaBonus
|
||||
));
|
||||
|
||||
await this.redis.hSet(`metrics:${this.taskId}`, "performance_score", score.toFixed(3));
|
||||
|
||||
return this.getMetrics();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Multi-Agent Coordination System - Orchestrator
|
||||
* Manages parallel agent execution, spawn conditions, and metrics
|
||||
*/
|
||||
|
||||
import type { TaskDefinition, CoordinationMetrics, SpawnCondition, AgentRole } from "./types";
|
||||
import {
|
||||
Blackboard,
|
||||
MessageBus,
|
||||
AgentStateManager,
|
||||
SpawnController,
|
||||
MetricsCollector,
|
||||
} from "./coordination";
|
||||
import { AgentAlpha, AgentBeta, AgentGamma } from "./agents";
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function generateId(): string {
|
||||
return "task-" + Math.random().toString(36).slice(2, 8) + "-" + Date.now().toString(36);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Multi-Agent Orchestrator
|
||||
// =============================================================================
|
||||
|
||||
export class MultiAgentOrchestrator {
|
||||
private taskId: string;
|
||||
private blackboard!: Blackboard;
|
||||
private stateManager!: AgentStateManager;
|
||||
private spawnController!: SpawnController;
|
||||
private metrics!: MetricsCollector;
|
||||
|
||||
private alphaAgent!: AgentAlpha;
|
||||
private betaAgent!: AgentBeta;
|
||||
private gammaAgent?: AgentGamma;
|
||||
|
||||
private alphaBus!: MessageBus;
|
||||
private betaBus!: MessageBus;
|
||||
private gammaBus?: MessageBus;
|
||||
|
||||
private model: string;
|
||||
private startTime!: number;
|
||||
private monitorInterval?: ReturnType<typeof setInterval>;
|
||||
|
||||
constructor(model: string = "anthropic/claude-sonnet-4") {
|
||||
this.taskId = generateId();
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
private log(msg: string) {
|
||||
const elapsed = this.startTime ? ((Date.now() - this.startTime) / 1000).toFixed(1) : "0.0";
|
||||
console.log(`[${elapsed}s] [ORCHESTRATOR] ${msg}`);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
this.startTime = Date.now();
|
||||
|
||||
console.log("\n" + "=".repeat(70));
|
||||
console.log("MULTI-AGENT COORDINATION SYSTEM");
|
||||
console.log("Task ID: " + this.taskId);
|
||||
console.log("Model: " + this.model);
|
||||
console.log("=".repeat(70) + "\n");
|
||||
|
||||
this.log("Initializing coordination infrastructure...");
|
||||
|
||||
// Initialize shared infrastructure
|
||||
this.blackboard = new Blackboard(this.taskId);
|
||||
this.stateManager = new AgentStateManager(this.taskId);
|
||||
this.spawnController = new SpawnController(this.taskId);
|
||||
this.metrics = new MetricsCollector(this.taskId);
|
||||
|
||||
await Promise.all([
|
||||
this.blackboard.connect(),
|
||||
this.stateManager.connect(),
|
||||
this.spawnController.connect(),
|
||||
this.metrics.connect(),
|
||||
]);
|
||||
|
||||
this.log("Infrastructure connected");
|
||||
|
||||
// Initialize message buses for ALPHA and BETA
|
||||
this.alphaBus = new MessageBus(this.taskId, "ALPHA");
|
||||
this.betaBus = new MessageBus(this.taskId, "BETA");
|
||||
|
||||
await Promise.all([
|
||||
this.alphaBus.connect(),
|
||||
this.betaBus.connect(),
|
||||
]);
|
||||
|
||||
this.log("Message buses connected");
|
||||
|
||||
// Create initial agents
|
||||
this.alphaAgent = new AgentAlpha(
|
||||
this.taskId, this.blackboard, this.alphaBus, this.stateManager, this.metrics, this.model
|
||||
);
|
||||
this.betaAgent = new AgentBeta(
|
||||
this.taskId, this.blackboard, this.betaBus, this.stateManager, this.metrics, this.model
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
this.alphaAgent.init(),
|
||||
this.betaAgent.init(),
|
||||
]);
|
||||
|
||||
this.log("Agents ALPHA and BETA initialized");
|
||||
}
|
||||
|
||||
async spawnGamma(reason: SpawnCondition): Promise<void> {
|
||||
if (this.gammaAgent) {
|
||||
this.log("GAMMA already spawned, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`SPAWNING GAMMA - Reason: ${reason.type} (threshold: ${reason.threshold}, current: ${reason.current_value})`);
|
||||
|
||||
// Create message bus for GAMMA
|
||||
this.gammaBus = new MessageBus(this.taskId, "GAMMA");
|
||||
await this.gammaBus.connect();
|
||||
|
||||
// Create and initialize GAMMA agent
|
||||
this.gammaAgent = new AgentGamma(
|
||||
this.taskId, this.blackboard, this.gammaBus, this.stateManager, this.metrics,
|
||||
reason.type, this.model
|
||||
);
|
||||
await this.gammaAgent.init();
|
||||
|
||||
await this.spawnController.markGammaSpawned(reason);
|
||||
|
||||
this.log("GAMMA agent spawned and initialized");
|
||||
}
|
||||
|
||||
private async monitorConditions(): Promise<void> {
|
||||
// Check stuck condition
|
||||
const stuckAgents = await this.stateManager.detectStuckAgents(30);
|
||||
if (stuckAgents.length > 0) {
|
||||
this.log(`Stuck agents detected: ${stuckAgents.join(", ")}`);
|
||||
const condition = await this.spawnController.updateCondition("STUCK", stuckAgents.length);
|
||||
if (condition?.triggered) {
|
||||
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
|
||||
if (shouldSpawn && reason) {
|
||||
await this.spawnGamma(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check conflict condition
|
||||
const metricsData = await this.metrics.getMetrics();
|
||||
const unresolvedConflicts = metricsData.conflicts_detected - metricsData.conflicts_resolved;
|
||||
const conflictCondition = await this.spawnController.updateCondition("CONFLICT", unresolvedConflicts);
|
||||
if (conflictCondition?.triggered && !this.spawnController.isGammaSpawned()) {
|
||||
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
|
||||
if (shouldSpawn && reason) {
|
||||
await this.spawnGamma(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// Check complexity condition (from blackboard)
|
||||
const analysis = await this.blackboard.read("problem", "analysis");
|
||||
if (analysis?.value?.complexity_score) {
|
||||
const complexityCondition = await this.spawnController.updateCondition("COMPLEXITY", analysis.value.complexity_score);
|
||||
if (complexityCondition?.triggered && !this.spawnController.isGammaSpawned()) {
|
||||
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
|
||||
if (shouldSpawn && reason) {
|
||||
await this.spawnGamma(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log current state
|
||||
const states = await this.stateManager.getAllStates();
|
||||
const statesSummary = states.map(s => `${s.role}:${s.status}(${(s.progress * 100).toFixed(0)}%)`).join(", ");
|
||||
this.log(`Status: ${statesSummary} | Messages: ${metricsData.total_messages} | Conflicts: ${unresolvedConflicts}`);
|
||||
}
|
||||
|
||||
async runTask(task: TaskDefinition): Promise<CoordinationMetrics> {
|
||||
this.log(`Starting task: ${task.objective.slice(0, 60)}...`);
|
||||
|
||||
// Write task to blackboard
|
||||
await this.blackboard.write("problem", "task_definition", task, "ALPHA");
|
||||
|
||||
// Start monitoring
|
||||
this.monitorInterval = setInterval(() => this.monitorConditions(), 2000);
|
||||
|
||||
// Run agents in parallel
|
||||
this.log("Launching ALPHA and BETA in parallel...");
|
||||
|
||||
const alphaPromise = this.alphaAgent.run(task).catch(e => {
|
||||
this.log(`ALPHA error: ${e.message}`);
|
||||
});
|
||||
|
||||
const betaPromise = this.betaAgent.run(task).catch(e => {
|
||||
this.log(`BETA error: ${e.message}`);
|
||||
});
|
||||
|
||||
// Wait for initial agents to complete (or timeout)
|
||||
const timeout = task.timeout_seconds * 1000;
|
||||
const timeoutPromise = new Promise<void>(resolve => setTimeout(resolve, timeout));
|
||||
|
||||
await Promise.race([
|
||||
Promise.all([alphaPromise, betaPromise]),
|
||||
timeoutPromise,
|
||||
]);
|
||||
|
||||
this.log("Initial agents completed or timeout reached");
|
||||
|
||||
// Check if GAMMA needs to be spawned for success validation
|
||||
const states = await this.stateManager.getAllStates();
|
||||
const bothComplete = states.every(s => s.status === "WAITING" || s.status === "COMPLETED");
|
||||
|
||||
if (bothComplete && !this.spawnController.isGammaSpawned()) {
|
||||
await this.spawnController.updateCondition("SUCCESS", 1.0);
|
||||
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
|
||||
if (shouldSpawn && reason) {
|
||||
await this.spawnGamma(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// If GAMMA was spawned, run it
|
||||
if (this.gammaAgent) {
|
||||
this.log("Running GAMMA for resolution...");
|
||||
await this.gammaAgent.run(task).catch(e => {
|
||||
this.log(`GAMMA error: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Stop monitoring
|
||||
if (this.monitorInterval) {
|
||||
clearInterval(this.monitorInterval);
|
||||
}
|
||||
|
||||
// Check consensus
|
||||
const consensus = await this.blackboard.checkConsensus("synthesis", ["ALPHA", "BETA"]);
|
||||
const consensusAchieved = consensus.reached ||
|
||||
(await this.blackboard.read("consensus", "final"))?.value?.achieved === true;
|
||||
|
||||
this.log(`Consensus achieved: ${consensusAchieved}`);
|
||||
|
||||
// Finalize metrics
|
||||
const finalMetrics = await this.metrics.finalize(consensusAchieved);
|
||||
|
||||
return finalMetrics;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
this.log("Cleaning up...");
|
||||
|
||||
if (this.monitorInterval) {
|
||||
clearInterval(this.monitorInterval);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.alphaBus?.disconnect(),
|
||||
this.betaBus?.disconnect(),
|
||||
this.gammaBus?.disconnect(),
|
||||
this.blackboard?.disconnect(),
|
||||
this.stateManager?.disconnect(),
|
||||
this.spawnController?.disconnect(),
|
||||
this.metrics?.disconnect(),
|
||||
].filter(Boolean));
|
||||
|
||||
this.log("Cleanup complete");
|
||||
}
|
||||
|
||||
getTaskId(): string {
|
||||
return this.taskId;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Performance Analysis
|
||||
// =============================================================================
|
||||
|
||||
export function analyzePerformance(metrics: CoordinationMetrics): void {
|
||||
console.log("\n" + "=".repeat(70));
|
||||
console.log("PERFORMANCE ANALYSIS");
|
||||
console.log("=".repeat(70));
|
||||
|
||||
const duration = metrics.end_time
|
||||
? (new Date(metrics.end_time).getTime() - new Date(metrics.start_time).getTime()) / 1000
|
||||
: 0;
|
||||
|
||||
console.log("\nTiming:");
|
||||
console.log(` Duration: ${duration.toFixed(1)}s`);
|
||||
|
||||
console.log("\nCommunication:");
|
||||
console.log(` Total messages: ${metrics.total_messages}`);
|
||||
console.log(` Direct messages: ${metrics.direct_messages}`);
|
||||
console.log(` Blackboard writes: ${metrics.blackboard_writes}`);
|
||||
console.log(` Blackboard reads: ${metrics.blackboard_reads}`);
|
||||
console.log(` Communication overhead: ${((metrics.total_messages + metrics.blackboard_writes) / duration).toFixed(2)} ops/sec`);
|
||||
|
||||
console.log("\nCoordination:");
|
||||
console.log(` Conflicts detected: ${metrics.conflicts_detected}`);
|
||||
console.log(` Conflicts resolved: ${metrics.conflicts_resolved}`);
|
||||
console.log(` Conflict resolution rate: ${metrics.conflicts_detected > 0 ? ((metrics.conflicts_resolved / metrics.conflicts_detected) * 100).toFixed(1) : 100}%`);
|
||||
|
||||
console.log("\nGamma Agent:");
|
||||
console.log(` Spawned: ${metrics.gamma_spawned ? "Yes" : "No"}`);
|
||||
if (metrics.gamma_spawned) {
|
||||
console.log(` Spawn reason: ${metrics.gamma_spawn_reason}`);
|
||||
console.log(` Spawn time: ${metrics.gamma_spawn_time}`);
|
||||
}
|
||||
|
||||
console.log("\nOutcome:");
|
||||
console.log(` Consensus achieved: ${metrics.final_consensus ? "Yes" : "No"}`);
|
||||
console.log(` Performance score: ${(metrics.performance_score * 100).toFixed(1)}%`);
|
||||
|
||||
// Threshold analysis
|
||||
console.log("\nThreshold Effects:");
|
||||
const messageThreshold = 50;
|
||||
const conflictThreshold = 3;
|
||||
|
||||
if (metrics.total_messages > messageThreshold) {
|
||||
console.log(` ! High message volume (${metrics.total_messages} > ${messageThreshold}) - potential coordination overhead`);
|
||||
} else {
|
||||
console.log(` + Message volume within threshold (${metrics.total_messages} <= ${messageThreshold})`);
|
||||
}
|
||||
|
||||
if (metrics.conflicts_detected > conflictThreshold) {
|
||||
console.log(` ! High conflict rate (${metrics.conflicts_detected} > ${conflictThreshold}) - agents may have divergent strategies`);
|
||||
} else {
|
||||
console.log(` + Conflict rate within threshold (${metrics.conflicts_detected} <= ${conflictThreshold})`);
|
||||
}
|
||||
|
||||
if (metrics.gamma_spawned && metrics.gamma_spawn_reason === "STUCK") {
|
||||
console.log(` ! Gamma spawned due to stuck condition - consider adjusting agent strategies`);
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(70));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CLI Entry Point
|
||||
// =============================================================================
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Default complex task
|
||||
let objective = args[0] || `Design a distributed event-driven architecture for a real-time analytics platform that handles:
|
||||
1) High-throughput data ingestion from multiple sources
|
||||
2) Stream processing with exactly-once semantics
|
||||
3) Real-time aggregations and windowed computations
|
||||
4) Low-latency query serving for dashboards
|
||||
5) Horizontal scalability to handle 1M events/second
|
||||
The solution should consider fault tolerance, data consistency, and cost optimization.`;
|
||||
|
||||
let model = "anthropic/claude-sonnet-4";
|
||||
const modelIdx = args.indexOf("--model");
|
||||
if (modelIdx !== -1 && args[modelIdx + 1]) {
|
||||
model = args[modelIdx + 1];
|
||||
}
|
||||
|
||||
// Parse timeout
|
||||
let timeout = 120;
|
||||
const timeoutIdx = args.indexOf("--timeout");
|
||||
if (timeoutIdx !== -1 && args[timeoutIdx + 1]) {
|
||||
timeout = parseInt(args[timeoutIdx + 1]);
|
||||
}
|
||||
|
||||
const task: TaskDefinition = {
|
||||
task_id: generateId(),
|
||||
objective,
|
||||
complexity: "high",
|
||||
subtasks: [
|
||||
{ id: "s1", description: "Analyze data ingestion requirements", status: "pending", dependencies: [] },
|
||||
{ id: "s2", description: "Design stream processing pipeline", status: "pending", dependencies: ["s1"] },
|
||||
{ id: "s3", description: "Plan storage and query layer", status: "pending", dependencies: ["s1"] },
|
||||
{ id: "s4", description: "Define scalability strategy", status: "pending", dependencies: ["s2", "s3"] },
|
||||
{ id: "s5", description: "Integrate fault tolerance mechanisms", status: "pending", dependencies: ["s4"] },
|
||||
],
|
||||
constraints: [
|
||||
"Must use open-source technologies where possible",
|
||||
"Latency < 100ms for query responses",
|
||||
"Support for multiple data formats (JSON, Avro, Protobuf)",
|
||||
"Cost-effective for variable workloads",
|
||||
],
|
||||
success_criteria: [
|
||||
"Complete architecture design with component diagrams",
|
||||
"Data flow specifications",
|
||||
"Scalability analysis",
|
||||
"Fault tolerance mechanisms documented",
|
||||
"Cost estimation provided",
|
||||
],
|
||||
timeout_seconds: timeout,
|
||||
};
|
||||
|
||||
const orchestrator = new MultiAgentOrchestrator(model);
|
||||
|
||||
try {
|
||||
await orchestrator.initialize();
|
||||
const metrics = await orchestrator.runTask(task);
|
||||
|
||||
console.log("\n" + "=".repeat(70));
|
||||
console.log("FINAL METRICS");
|
||||
console.log("=".repeat(70));
|
||||
console.log(JSON.stringify(metrics, null, 2));
|
||||
|
||||
analyzePerformance(metrics);
|
||||
|
||||
} catch (e: any) {
|
||||
console.error("Orchestrator error:", e.message);
|
||||
} finally {
|
||||
await orchestrator.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "multi-agent-coordination",
|
||||
"version": "0.1.0",
|
||||
"description": "Multi-agent coordination system with parallel execution, shared blackboard, and conditional spawning",
|
||||
"main": "orchestrator.ts",
|
||||
"scripts": {
|
||||
"start": "bun run orchestrator.ts",
|
||||
"test": "bun run orchestrator.ts --timeout 60"
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^4.0.0",
|
||||
"redis": "^4.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Multi-Agent Coordination System - Type Definitions
|
||||
*/
|
||||
|
||||
export type AgentRole = "ALPHA" | "BETA" | "GAMMA";
|
||||
export type AgentStatus = "IDLE" | "WORKING" | "WAITING" | "BLOCKED" | "COMPLETED" | "FAILED";
|
||||
export type MessageType = "PROPOSAL" | "FEEDBACK" | "QUERY" | "RESPONSE" | "SYNC" | "HANDOFF" | "SPAWN_REQUEST";
|
||||
export type BlackboardSection = "problem" | "solutions" | "constraints" | "progress" | "conflicts" | "consensus";
|
||||
|
||||
export interface AgentMessage {
|
||||
id: string;
|
||||
from: AgentRole;
|
||||
to: AgentRole | "ALL" | "BLACKBOARD";
|
||||
type: MessageType;
|
||||
payload: any;
|
||||
timestamp: string;
|
||||
correlation_id?: string;
|
||||
}
|
||||
|
||||
export interface BlackboardEntry {
|
||||
section: BlackboardSection;
|
||||
key: string;
|
||||
value: any;
|
||||
author: AgentRole;
|
||||
version: number;
|
||||
timestamp: string;
|
||||
supersedes?: string;
|
||||
}
|
||||
|
||||
export interface AgentState {
|
||||
agent_id: string;
|
||||
role: AgentRole;
|
||||
status: AgentStatus;
|
||||
current_task: string;
|
||||
progress: number;
|
||||
confidence: number;
|
||||
last_activity: string;
|
||||
messages_sent: number;
|
||||
messages_received: number;
|
||||
proposals_made: number;
|
||||
conflicts_detected: number;
|
||||
blocked_since?: string;
|
||||
}
|
||||
|
||||
export interface CoordinationMetrics {
|
||||
task_id: string;
|
||||
start_time: string;
|
||||
end_time?: string;
|
||||
total_messages: number;
|
||||
direct_messages: number;
|
||||
blackboard_writes: number;
|
||||
blackboard_reads: number;
|
||||
conflicts_detected: number;
|
||||
conflicts_resolved: number;
|
||||
gamma_spawned: boolean;
|
||||
gamma_spawn_reason?: string;
|
||||
gamma_spawn_time?: string;
|
||||
final_consensus: boolean;
|
||||
performance_score: number;
|
||||
}
|
||||
|
||||
export interface SpawnCondition {
|
||||
type: "STUCK" | "CONFLICT" | "COMPLEXITY" | "SUCCESS" | "MANUAL";
|
||||
threshold: number;
|
||||
current_value: number;
|
||||
triggered: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface TaskDefinition {
|
||||
task_id: string;
|
||||
objective: string;
|
||||
complexity: "low" | "medium" | "high" | "extreme";
|
||||
subtasks: {
|
||||
id: string;
|
||||
description: string;
|
||||
assigned_to?: AgentRole;
|
||||
status: "pending" | "in_progress" | "completed" | "blocked";
|
||||
dependencies: string[];
|
||||
}[];
|
||||
constraints: string[];
|
||||
success_criteria: string[];
|
||||
timeout_seconds: number;
|
||||
}
|
||||
|
||||
export interface ConsensusVote {
|
||||
agent: AgentRole;
|
||||
proposal_id: string;
|
||||
vote: "ACCEPT" | "REJECT" | "ABSTAIN";
|
||||
reasoning: string;
|
||||
timestamp: string;
|
||||
}
|
||||
Reference in New Issue
Block a user