tasq/node_modules/agentic-flow/dist/federation/EphemeralAgent.js

259 lines
7.9 KiB
JavaScript

/**
* Ephemeral Agent - Short-lived agent with federated memory access
*
* Features:
* - Automatic lifecycle management (spawn → execute → learn → destroy)
* - Federated memory sync via QUIC
* - Tenant isolation with JWT authentication
* - Memory persistence after agent destruction
*/
import Database from 'better-sqlite3';
import { FederationHub } from './FederationHub.js';
import { SecurityManager } from './SecurityManager.js';
import { logger } from '../utils/logger.js';
export class EphemeralAgent {
config;
context;
hub;
security;
cleanupTimer;
syncTimer;
constructor(config) {
this.config = {
lifetime: 300, // 5 minutes default
syncInterval: 5000, // 5 seconds default
enableEncryption: true,
...config
};
this.security = new SecurityManager();
}
/**
* Spawn a new ephemeral agent with federated memory access
*/
static async spawn(config) {
const agent = new EphemeralAgent(config);
await agent.initialize();
return agent;
}
/**
* Initialize agent: setup DB, connect to hub, start lifecycle timers
*/
async initialize() {
const agentId = `eph-${this.config.tenantId}-${Date.now()}`;
const spawnTime = Date.now();
const expiresAt = spawnTime + ((this.config.lifetime || 300) * 1000);
logger.info('Spawning ephemeral agent', {
agentId,
tenantId: this.config.tenantId,
lifetime: this.config.lifetime,
expiresAt: new Date(expiresAt).toISOString()
});
// Initialize local database instance
const memoryPath = this.config.memoryPath || `:memory:`;
// Use better-sqlite3 for now (AgentDB integration planned)
const db = new Database(memoryPath);
// Create JWT token for authentication
const token = await this.security.createAgentToken({
agentId,
tenantId: this.config.tenantId,
expiresAt
});
// Connect to federation hub if endpoint provided
if (this.config.hubEndpoint) {
this.hub = new FederationHub({
endpoint: this.config.hubEndpoint,
agentId,
tenantId: this.config.tenantId,
token
});
await this.hub.connect();
// Start periodic sync
if (this.config.syncInterval) {
this.syncTimer = setInterval(async () => {
await this.syncWithHub();
}, this.config.syncInterval);
}
}
// Store context
this.context = {
agentId,
tenantId: this.config.tenantId,
db,
spawnTime,
expiresAt
};
// Schedule automatic cleanup at expiration
const timeUntilExpiry = expiresAt - Date.now();
this.cleanupTimer = setTimeout(async () => {
logger.warn('Agent lifetime expired, auto-destroying', { agentId });
await this.destroy();
}, timeUntilExpiry);
logger.info('Ephemeral agent spawned successfully', {
agentId,
hubConnected: !!this.hub,
timeUntilExpiry
});
}
/**
* Execute a task within the agent context
* Automatically syncs memory before and after execution
*/
async execute(task) {
if (!this.context) {
throw new Error('Agent not initialized. Call spawn() first.');
}
const { agentId, db } = this.context;
// Check if agent has expired
if (Date.now() >= this.context.expiresAt) {
throw new Error(`Agent ${agentId} has expired and cannot execute tasks`);
}
logger.info('Executing task', { agentId });
try {
// Pre-execution sync: pull latest memories from hub
if (this.hub) {
await this.syncWithHub();
}
// Execute user task
const result = await task(db, this.context);
// Post-execution sync: push new memories to hub
if (this.hub) {
await this.syncWithHub();
}
logger.info('Task execution completed', { agentId });
return result;
}
catch (error) {
logger.error('Task execution failed', {
agentId,
error: error.message
});
throw error;
}
}
/**
* Query memories from federated database
*/
async queryMemories(task, k = 5) {
if (!this.context) {
throw new Error('Agent not initialized');
}
const { db, tenantId } = this.context;
// Query using ReasoningBank pattern search
const patterns = await db.patternSearch({
task,
k,
tenantId // Apply tenant isolation
});
return patterns || [];
}
/**
* Store a learning episode to persistent memory
*/
async storeEpisode(episode) {
if (!this.context) {
throw new Error('Agent not initialized');
}
const { db, agentId, tenantId } = this.context;
// Store episode with tenant isolation
await db.patternStore({
sessionId: agentId,
task: episode.task,
input: episode.input,
output: episode.output,
reward: episode.reward,
critique: episode.critique || '',
success: episode.reward >= 0.7,
tokensUsed: 0,
latencyMs: 0,
tenantId // Ensure tenant isolation
});
logger.info('Episode stored', {
agentId,
task: episode.task,
reward: episode.reward
});
}
/**
* Sync local memory with federation hub
*/
async syncWithHub() {
if (!this.hub || !this.context) {
return;
}
try {
await this.hub.sync(this.context.db);
}
catch (error) {
logger.error('Federation sync failed', {
agentId: this.context.agentId,
error: error.message
});
}
}
/**
* Get remaining lifetime in seconds
*/
getRemainingLifetime() {
if (!this.context) {
return 0;
}
return Math.max(0, Math.floor((this.context.expiresAt - Date.now()) / 1000));
}
/**
* Destroy agent and cleanup resources
* Memory persists in federation hub
*/
async destroy() {
if (!this.context) {
return;
}
const { agentId, db } = this.context;
logger.info('Destroying ephemeral agent', { agentId });
// Clear timers
if (this.cleanupTimer) {
clearTimeout(this.cleanupTimer);
}
if (this.syncTimer) {
clearInterval(this.syncTimer);
}
// Final sync to persist any pending changes
if (this.hub) {
try {
await this.syncWithHub();
await this.hub.disconnect();
}
catch (error) {
logger.error('Final sync failed', {
agentId,
error: error.message
});
}
}
// Close local database
try {
await db.close?.();
}
catch (error) {
// Ignore close errors for in-memory databases
}
// Clear context
this.context = undefined;
logger.info('Ephemeral agent destroyed', { agentId });
}
/**
* Check if agent is still alive
*/
isAlive() {
if (!this.context) {
return false;
}
return Date.now() < this.context.expiresAt;
}
/**
* Get agent info
*/
getInfo() {
return this.context || null;
}
}
//# sourceMappingURL=EphemeralAgent.js.map