import { UnifiedGraphRAG, unifiedGraphRAG } from './UnifiedGraphRAG.js'; import { UnifiedMemorySystem, unifiedMemorySystem } from './UnifiedMemorySystem.js'; export type AgentNodeType = 'router' | 'planner' | 'researcher' | 'coder' | 'reviewer' | 'end'; export interface AgentState { id: string; messages: { role: string; content: string }[]; context: any; currentNode: AgentNodeType; history: AgentNodeType[]; scratchpad: any; // Shared working memory for agents status: 'active' | 'completed' | 'failed' | 'waiting'; } interface Checkpoint { id: string; state: AgentState; timestamp: Date; metadata?: any; } export class StateGraphRouter { private graphRag: UnifiedGraphRAG; private memory: UnifiedMemorySystem; private checkpoints: Map = new Map(); constructor() { this.graphRag = unifiedGraphRAG; this.memory = unifiedMemorySystem; } /** * Initialize a new state for a task */ public initState(taskId: string, initialInput: string): AgentState { return { id: taskId, messages: [{ role: 'user', content: initialInput }], context: {}, currentNode: 'router', history: [], scratchpad: {}, status: 'active' }; } /** * Save checkpoint for time-travel debugging */ private saveCheckpoint(state: AgentState, metadata?: any): string { const checkpointId = `${state.id}-${Date.now()}`; this.checkpoints.set(checkpointId, { id: checkpointId, state: JSON.parse(JSON.stringify(state)), // Deep clone timestamp: new Date(), metadata }); // Keep only last 50 checkpoints per task const taskCheckpoints = Array.from(this.checkpoints.entries()) .filter(([_, cp]) => cp.state.id === state.id) .sort((a, b) => b[1].timestamp.getTime() - a[1].timestamp.getTime()) .slice(50); this.checkpoints.clear(); taskCheckpoints.forEach(([id, cp]) => this.checkpoints.set(id, cp)); return checkpointId; } /** * Time-travel: restore to previous checkpoint */ public async timeTravel(checkpointId: string): Promise { const checkpoint = this.checkpoints.get(checkpointId); if (!checkpoint) { return null; } console.log(`⏪ [StateGraph] Time-traveling to checkpoint: ${checkpointId}`); return JSON.parse(JSON.stringify(checkpoint.state)); // Deep clone } /** * Get all checkpoints for a task */ public getCheckpoints(taskId: string): Checkpoint[] { return Array.from(this.checkpoints.values()) .filter(cp => cp.state.id === taskId) .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } /** * Route the state to the next node */ public async route(state: AgentState): Promise { console.log(`🔄 [StateGraph] Routing from ${state.currentNode}...`); // Save checkpoint before routing this.saveCheckpoint(state, { action: 'before_routing', node: state.currentNode }); // Update history if (state.history[state.history.length - 1] !== state.currentNode) { state.history.push(state.currentNode); } let newState: AgentState; switch (state.currentNode) { case 'router': newState = await this.handleRouterNode(state); break; case 'planner': newState = await this.handlePlannerNode(state); break; case 'researcher': newState = await this.handleResearcherNode(state); break; case 'reviewer': newState = await this.handleReviewerNode(state); break; case 'end': newState = state; break; default: console.error(`Unknown node: ${state.currentNode}`); state.status = 'failed'; newState = state; } // Save checkpoint after routing this.saveCheckpoint(newState, { action: 'after_routing', node: newState.currentNode }); return newState; } /** * Router Logic: Decides the initial path based on query complexity */ private async handleRouterNode(state: AgentState): Promise { const lastMessage = state.messages[state.messages.length - 1].content; // 1. Use GraphRAG to understand context const ragResult = await this.graphRag.query(lastMessage, { userId: 'system', orgId: 'default' }); state.context.ragReasoning = ragResult; // 2. Heuristic routing (Will be replaced by LLM classifier later) if (ragResult.confidence < 0.3 || lastMessage.length > 100) { // Low confidence or complex query -> Needs Planning console.log(' -> Routing to Planner (Complex/Unknown)'); state.currentNode = 'planner'; } else { // High confidence -> Direct Research or Execution (Simplified) console.log(' -> Routing to Researcher (Simple)'); state.currentNode = 'researcher'; } return state; } /** * Planner Node: Break down complex tasks */ private async handlePlannerNode(state: AgentState): Promise { console.log(' -> Planner: Analyzing task...'); const lastMessage = state.messages[state.messages.length - 1].content; // Use GraphRAG to understand context and dependencies const ragResult = await this.graphRag.query(`Plan: ${lastMessage}`, { userId: 'system', orgId: 'default' }); // Simple planning logic (can be enhanced with LLM) const plan = [ `Step 1: Analyze requirements (confidence: ${ragResult.confidence.toFixed(2)})`, `Step 2: Identify dependencies (${ragResult.nodes.length} related concepts found)`, 'Step 3: Execute plan', 'Step 4: Review results' ]; state.scratchpad.plan = plan; state.scratchpad.ragContext = ragResult; state.currentNode = 'researcher'; return state; } /** * Researcher Node: Gather information */ private async handleResearcherNode(state: AgentState): Promise { console.log(' -> Researcher: Gathering information...'); const lastMessage = state.messages[state.messages.length - 1].content; // Use UnifiedMemorySystem to search for relevant information const searchResults = await this.memory.getWorkingMemory({ userId: 'system', orgId: 'default' }); state.scratchpad.research = { query: lastMessage, foundContext: searchResults.recentEvents?.slice(0, 5) || [], foundFeatures: searchResults.recentFeatures?.slice(0, 3) || [] }; state.currentNode = 'reviewer'; return state; } /** * Reviewer Node: Validate and finalize */ private async handleReviewerNode(state: AgentState): Promise { console.log(' -> Reviewer: Validating results...'); // Simple validation (can be enhanced) const hasPlan = state.scratchpad.plan && state.scratchpad.plan.length > 0; const hasResearch = state.scratchpad.research; if (hasPlan && hasResearch) { state.status = 'completed'; state.currentNode = 'end'; } else { // If missing info, go back to researcher state.currentNode = 'researcher'; } return state; } } export const stateGraphRouter = new StateGraphRouter();