432 lines
17 KiB
JavaScript
432 lines
17 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Federation Hub CLI - Manage ephemeral agent federation
|
||
* Supports hub server, agent lifecycle, stats, and monitoring
|
||
*/
|
||
import { existsSync } from 'fs';
|
||
import { resolve, dirname } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
import { spawn } from 'child_process';
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
/**
|
||
* Federation Hub CLI Manager
|
||
*/
|
||
export class FederationCLI {
|
||
hubProcess = null;
|
||
/**
|
||
* Start federation hub server
|
||
*/
|
||
async startHub(config = {}) {
|
||
const port = config.port || parseInt(process.env.FEDERATION_HUB_PORT || '8443');
|
||
const dbPath = config.dbPath || process.env.FEDERATION_DB_PATH || ':memory:';
|
||
const maxAgents = config.maxAgents || parseInt(process.env.FEDERATION_MAX_AGENTS || '1000');
|
||
console.log('\n🌐 Starting Federation Hub Server...');
|
||
console.log('═'.repeat(60));
|
||
console.log(`📍 Port: ${port}`);
|
||
console.log(`💾 Database: ${dbPath === ':memory:' ? 'In-Memory' : dbPath}`);
|
||
console.log(`👥 Max Agents: ${maxAgents}`);
|
||
console.log(`🔒 Protocol: WebSocket (QUIC support planned)`);
|
||
console.log('');
|
||
// Start hub server
|
||
const hubPath = resolve(__dirname, '../federation/run-hub.js');
|
||
// Check if compiled hub exists
|
||
if (!existsSync(hubPath)) {
|
||
console.error(`❌ Error: Hub server not found at ${hubPath}`);
|
||
console.error(' Please build the project first: npm run build');
|
||
process.exit(1);
|
||
}
|
||
this.hubProcess = spawn('node', [hubPath], {
|
||
stdio: 'inherit',
|
||
env: {
|
||
...process.env,
|
||
FEDERATION_HUB_PORT: port.toString(),
|
||
FEDERATION_DB_PATH: dbPath,
|
||
FEDERATION_MAX_AGENTS: maxAgents.toString(),
|
||
DEBUG: config.verbose ? 'federation:*' : undefined
|
||
}
|
||
});
|
||
this.hubProcess.on('exit', (code) => {
|
||
console.log(`\n👋 Hub server stopped (exit code: ${code})`);
|
||
process.exit(code || 0);
|
||
});
|
||
// Handle signals
|
||
process.on('SIGINT', () => {
|
||
console.log('\n\n⏸️ Shutting down hub server...');
|
||
if (this.hubProcess) {
|
||
this.hubProcess.kill('SIGINT');
|
||
}
|
||
});
|
||
process.on('SIGTERM', () => {
|
||
if (this.hubProcess) {
|
||
this.hubProcess.kill('SIGTERM');
|
||
}
|
||
});
|
||
console.log('✅ Hub server started successfully!');
|
||
console.log('\nAgent Connection:');
|
||
console.log(` Endpoint: ws://localhost:${port}`);
|
||
console.log(` Protocol: WebSocket (HTTP/2 upgrade)`);
|
||
console.log('\nUse Ctrl+C to stop the server\n');
|
||
// Keep alive
|
||
await new Promise(() => { });
|
||
}
|
||
/**
|
||
* Spawn ephemeral agent
|
||
*/
|
||
async spawnAgent(config = {}) {
|
||
const agentId = config.agentId || `agent-${Date.now()}`;
|
||
const tenantId = config.tenantId || process.env.FEDERATION_TENANT_ID || 'default';
|
||
const lifetime = config.lifetime || parseInt(process.env.AGENT_LIFETIME || '300'); // 5 minutes default
|
||
const hubEndpoint = config.hubEndpoint || process.env.FEDERATION_HUB_ENDPOINT || 'ws://localhost:8443';
|
||
const agentType = config.agentType || 'worker';
|
||
console.log('\n🤖 Spawning Ephemeral Agent...');
|
||
console.log('═'.repeat(60));
|
||
console.log(`🆔 Agent ID: ${agentId}`);
|
||
console.log(`🏢 Tenant: ${tenantId}`);
|
||
console.log(`⏱️ Lifetime: ${lifetime}s`);
|
||
console.log(`🔗 Hub: ${hubEndpoint}`);
|
||
console.log(`📋 Type: ${agentType}`);
|
||
console.log('');
|
||
// Spawn agent process
|
||
const agentPath = resolve(__dirname, '../federation/run-agent.js');
|
||
if (!existsSync(agentPath)) {
|
||
console.error(`❌ Error: Agent runtime not found at ${agentPath}`);
|
||
console.error(' Please build the project first: npm run build');
|
||
process.exit(1);
|
||
}
|
||
const agentProcess = spawn('node', [agentPath], {
|
||
stdio: 'inherit',
|
||
env: {
|
||
...process.env,
|
||
AGENT_ID: agentId,
|
||
TENANT_ID: tenantId,
|
||
AGENT_LIFETIME: lifetime.toString(),
|
||
HUB_ENDPOINT: hubEndpoint,
|
||
AGENT_TYPE: agentType
|
||
}
|
||
});
|
||
agentProcess.on('exit', (code) => {
|
||
console.log(`\n📊 Agent lifecycle complete (exit code: ${code})`);
|
||
process.exit(code || 0);
|
||
});
|
||
process.on('SIGINT', () => {
|
||
console.log('\n\n⏸️ Terminating agent...');
|
||
agentProcess.kill('SIGINT');
|
||
});
|
||
process.on('SIGTERM', () => {
|
||
agentProcess.kill('SIGTERM');
|
||
});
|
||
}
|
||
/**
|
||
* Show hub statistics
|
||
*/
|
||
async stats(hubEndpoint) {
|
||
const endpoint = hubEndpoint || process.env.FEDERATION_HUB_ENDPOINT || 'ws://localhost:8443';
|
||
console.log('\n📊 Federation Hub Statistics');
|
||
console.log('═'.repeat(60));
|
||
console.log(`🔗 Hub: ${endpoint}`);
|
||
console.log('');
|
||
try {
|
||
// TODO: Implement WebSocket stats query
|
||
// For now, show placeholder
|
||
console.log('⏳ Querying hub statistics...\n');
|
||
console.log('Note: Stats API not yet implemented.');
|
||
console.log('The hub server logs real-time statistics to stdout.');
|
||
console.log('\nExpected stats:');
|
||
console.log(' • Connected agents count');
|
||
console.log(' • Total episodes stored');
|
||
console.log(' • Active tenants');
|
||
console.log(' • Uptime and performance metrics');
|
||
console.log('');
|
||
}
|
||
catch (error) {
|
||
console.error(`❌ Failed to query stats: ${error.message}`);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
/**
|
||
* Show federation status
|
||
*/
|
||
async status() {
|
||
console.log('\n🔍 Federation System Status');
|
||
console.log('═'.repeat(60));
|
||
console.log('');
|
||
console.log('Components:');
|
||
console.log(' ✅ FederationHubServer - WebSocket hub for agent sync');
|
||
console.log(' ✅ FederationHubClient - WebSocket client for agents');
|
||
console.log(' ✅ EphemeralAgent - Short-lived agent lifecycle');
|
||
console.log(' ✅ SecurityManager - JWT authentication & encryption');
|
||
console.log(' ✅ AgentDB Integration - Vector memory storage (150x faster)');
|
||
console.log('');
|
||
console.log('Features:');
|
||
console.log(' ✅ Tenant Isolation - Multi-tenant memory separation');
|
||
console.log(' ✅ Persistent Hub - SQLite + AgentDB storage');
|
||
console.log(' ✅ Ephemeral Agents - :memory: databases (5s-15min lifetime)');
|
||
console.log(' ✅ Semantic Search - HNSW vector indexing');
|
||
console.log(' ✅ Multi-Generation - Agents learn from past agents');
|
||
console.log(' ⏳ QUIC Transport - Native QUIC planned (WebSocket fallback)');
|
||
console.log('');
|
||
console.log('Architecture:');
|
||
console.log(' • Hub: Persistent central database (disk)');
|
||
console.log(' • Agents: Ephemeral local databases (RAM)');
|
||
console.log(' • Sync: Real-time via WebSocket');
|
||
console.log(' • Memory: Outlives agent lifecycle');
|
||
console.log('');
|
||
console.log('Documentation:');
|
||
console.log(' 📖 Architecture: docs/architecture/FEDERATED-AGENTDB-EPHEMERAL-AGENTS.md');
|
||
console.log(' 📖 Data Lifecycle: docs/architecture/FEDERATION-DATA-LIFECYCLE.md');
|
||
console.log(' 📖 Test Report: docs/architecture/FEDERATION-TEST-REPORT.md');
|
||
console.log(' 📖 Integration: docs/architecture/AGENTDB-INTEGRATION-COMPLETE.md');
|
||
console.log('');
|
||
}
|
||
/**
|
||
* Run multi-agent collaboration test
|
||
*/
|
||
async testCollaboration() {
|
||
console.log('\n🧪 Running Multi-Agent Collaboration Test...');
|
||
console.log('═'.repeat(60));
|
||
console.log('');
|
||
const testPath = resolve(__dirname, '../../tests/federation/test-agentdb-collaboration.js');
|
||
if (!existsSync(testPath)) {
|
||
console.error(`❌ Error: Test not found at ${testPath}`);
|
||
console.error(' Please build the project first: npm run build');
|
||
process.exit(1);
|
||
}
|
||
console.log('📋 Test Scenario:');
|
||
console.log(' • 5 collaborative agents (researcher, coder, tester, reviewer, isolated)');
|
||
console.log(' • Real AgentDB integration');
|
||
console.log(' • Cross-agent memory sharing');
|
||
console.log(' • Tenant isolation validation');
|
||
console.log('');
|
||
const testProcess = spawn('node', [testPath], {
|
||
stdio: 'inherit'
|
||
});
|
||
testProcess.on('exit', (code) => {
|
||
if (code === 0) {
|
||
console.log('\n✅ Collaboration test passed!');
|
||
}
|
||
else {
|
||
console.log(`\n❌ Collaboration test failed (exit code: ${code})`);
|
||
}
|
||
process.exit(code || 0);
|
||
});
|
||
process.on('SIGINT', () => {
|
||
console.log('\n\n⏸️ Terminating test...');
|
||
testProcess.kill('SIGINT');
|
||
});
|
||
process.on('SIGTERM', () => {
|
||
testProcess.kill('SIGTERM');
|
||
});
|
||
}
|
||
/**
|
||
* Print help message
|
||
*/
|
||
printHelp() {
|
||
console.log(`
|
||
🌐 Federation Hub CLI - Ephemeral Agent Management
|
||
|
||
USAGE:
|
||
npx agentic-flow federation <command> [options]
|
||
|
||
COMMANDS:
|
||
start Start federation hub server
|
||
spawn Spawn ephemeral agent
|
||
stats Show hub statistics
|
||
status Show federation system status
|
||
test Run multi-agent collaboration test
|
||
help Show this help message
|
||
|
||
HUB SERVER OPTIONS:
|
||
--port, -p <port> Hub server port [default: 8443]
|
||
--db-path <path> Database path [default: :memory:]
|
||
--max-agents <number> Maximum concurrent agents [default: 1000]
|
||
--verbose, -v Enable verbose logging
|
||
|
||
AGENT OPTIONS:
|
||
--agent-id <id> Custom agent ID [default: auto-generated]
|
||
--tenant <id> Tenant ID [default: 'default']
|
||
--lifetime <seconds> Agent lifetime [default: 300]
|
||
--hub <endpoint> Hub WebSocket endpoint [default: ws://localhost:8443]
|
||
--type <type> Agent type [default: 'worker']
|
||
|
||
ENVIRONMENT VARIABLES:
|
||
FEDERATION_HUB_PORT Hub server port (default: 8443)
|
||
FEDERATION_DB_PATH Database path (default: :memory:)
|
||
FEDERATION_MAX_AGENTS Max concurrent agents (default: 1000)
|
||
FEDERATION_TENANT_ID Default tenant ID
|
||
FEDERATION_HUB_ENDPOINT Hub WebSocket endpoint
|
||
AGENT_LIFETIME Agent lifetime in seconds (default: 300)
|
||
|
||
DEBUG OPTIONS (for detailed operation visibility):
|
||
DEBUG_LEVEL Debug verbosity level
|
||
• SILENT (0) - No output
|
||
• BASIC (1) - Major events only [default]
|
||
• DETAILED (2) - Include all operations with timing
|
||
• VERBOSE (3) - All events + realtime + tasks
|
||
• TRACE (4) - Everything + internal state changes
|
||
DEBUG_FORMAT Output format (human | json | compact) [default: human]
|
||
DEBUG_OUTPUT Output destination (console | file | both) [default: console]
|
||
DEBUG_OUTPUT_FILE File path for debug output [default: none]
|
||
|
||
DEBUG EXAMPLES:
|
||
# Enable detailed debug with timing
|
||
DEBUG_LEVEL=DETAILED npx agentic-flow federation start
|
||
|
||
# Maximum verbosity for troubleshooting
|
||
DEBUG_LEVEL=TRACE DEBUG_FORMAT=human npx agentic-flow federation spawn
|
||
|
||
# Production monitoring with JSON output to file
|
||
DEBUG_LEVEL=BASIC DEBUG_FORMAT=json DEBUG_OUTPUT=file \\
|
||
DEBUG_OUTPUT_FILE=/var/log/federation.log npx agentic-flow federation start
|
||
|
||
# Compact format for log aggregation
|
||
DEBUG_LEVEL=DETAILED DEBUG_FORMAT=compact DEBUG_OUTPUT=both \\
|
||
DEBUG_OUTPUT_FILE=debug.log npx agentic-flow federation start
|
||
|
||
EXAMPLES:
|
||
# Start hub server (in-memory)
|
||
npx agentic-flow federation start
|
||
|
||
# Start hub with persistent storage
|
||
npx agentic-flow federation start --db-path ./data/hub.db
|
||
|
||
# Start hub on custom port
|
||
npx agentic-flow federation start --port 9443 --verbose
|
||
|
||
# Spawn ephemeral agent (5 minute lifetime)
|
||
npx agentic-flow federation spawn --tenant acme-corp
|
||
|
||
# Spawn agent with custom lifetime
|
||
npx agentic-flow federation spawn --tenant acme-corp --lifetime 600 --type researcher
|
||
|
||
# Show hub statistics
|
||
npx agentic-flow federation stats
|
||
|
||
# Show system status
|
||
npx agentic-flow federation status
|
||
|
||
# Run collaboration test
|
||
npx agentic-flow federation test
|
||
|
||
ARCHITECTURE:
|
||
Hub: Persistent central database (SQLite + AgentDB)
|
||
• Episode metadata storage
|
||
• Vector memory with HNSW indexing (150x faster)
|
||
• Tenant isolation via sessionId prefixes
|
||
• Change log for synchronization
|
||
|
||
Agents: Ephemeral local databases (:memory:)
|
||
• Short-lived (5 seconds to 15 minutes)
|
||
• Pull memories from hub on spawn
|
||
• Push new memories to hub during work
|
||
• Destroyed on cleanup (memory persists in hub)
|
||
|
||
Memory Persistence:
|
||
• Hub database outlives all agents
|
||
• New agents can access memories from dead agents
|
||
• Multi-generation learning enabled
|
||
• Semantic search for pattern discovery
|
||
|
||
BENEFITS:
|
||
✅ Memory outlives agents - Persistent learning across generations
|
||
✅ Tenant isolation - Multi-tenant with zero data leakage
|
||
✅ Semantic search - Find similar patterns via vector similarity
|
||
✅ 150x faster search - HNSW indexing vs brute force
|
||
✅ Scalable architecture - Ready for 100+ concurrent agents
|
||
✅ WebSocket protocol - Real-time synchronization
|
||
✅ Zero-trust security - JWT auth + AES-256 encryption
|
||
|
||
DOCUMENTATION:
|
||
📖 Complete Architecture:
|
||
docs/architecture/FEDERATED-AGENTDB-EPHEMERAL-AGENTS.md
|
||
|
||
📖 Data Lifecycle Explanation:
|
||
docs/architecture/FEDERATION-DATA-LIFECYCLE.md
|
||
|
||
📖 Multi-Agent Test Report:
|
||
docs/architecture/FEDERATION-TEST-REPORT.md
|
||
|
||
📖 AgentDB Integration:
|
||
docs/architecture/AGENTDB-INTEGRATION-COMPLETE.md
|
||
|
||
📖 GitHub: https://github.com/ruvnet/agentic-flow
|
||
`);
|
||
}
|
||
}
|
||
/**
|
||
* CLI command handler
|
||
*/
|
||
export async function handleFederationCommand(args) {
|
||
const command = args[0];
|
||
const cli = new FederationCLI();
|
||
// Parse options
|
||
const parseOptions = (args) => {
|
||
const options = {};
|
||
for (let i = 0; i < args.length; i++) {
|
||
if ((args[i] === '--port' || args[i] === '-p') && args[i + 1]) {
|
||
options.port = parseInt(args[++i]);
|
||
}
|
||
else if (args[i] === '--db-path' && args[i + 1]) {
|
||
options.dbPath = args[++i];
|
||
}
|
||
else if (args[i] === '--max-agents' && args[i + 1]) {
|
||
options.maxAgents = parseInt(args[++i]);
|
||
}
|
||
else if (args[i] === '--verbose' || args[i] === '-v') {
|
||
options.verbose = true;
|
||
}
|
||
else if (args[i] === '--agent-id' && args[i + 1]) {
|
||
options.agentId = args[++i];
|
||
}
|
||
else if (args[i] === '--tenant' && args[i + 1]) {
|
||
options.tenantId = args[++i];
|
||
}
|
||
else if (args[i] === '--lifetime' && args[i + 1]) {
|
||
options.lifetime = parseInt(args[++i]);
|
||
}
|
||
else if (args[i] === '--hub' && args[i + 1]) {
|
||
options.hubEndpoint = args[++i];
|
||
}
|
||
else if (args[i] === '--type' && args[i + 1]) {
|
||
options.agentType = args[++i];
|
||
}
|
||
}
|
||
return options;
|
||
};
|
||
const options = parseOptions(args.slice(1));
|
||
switch (command) {
|
||
case undefined:
|
||
case 'help':
|
||
cli.printHelp();
|
||
break;
|
||
case 'start':
|
||
await cli.startHub(options);
|
||
break;
|
||
case 'spawn':
|
||
await cli.spawnAgent(options);
|
||
break;
|
||
case 'stats':
|
||
await cli.stats(options.hubEndpoint);
|
||
break;
|
||
case 'status':
|
||
await cli.status();
|
||
break;
|
||
case 'test':
|
||
await cli.testCollaboration();
|
||
break;
|
||
default:
|
||
console.log(`\n❌ Unknown command: ${command}\n`);
|
||
console.log('Use "npx agentic-flow federation help" for usage information\n');
|
||
process.exit(1);
|
||
}
|
||
}
|
||
// If run directly
|
||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||
const args = process.argv.slice(2);
|
||
handleFederationCommand(args).catch((error) => {
|
||
console.error('\n❌ Error:', error.message);
|
||
console.error(error.stack);
|
||
process.exit(1);
|
||
});
|
||
}
|
||
//# sourceMappingURL=federation-cli.js.map
|