Kraft102's picture
Initial deployment - WidgeTDC Cortex Backend v2.1.0
529090e
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<string, Checkpoint> = 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<AgentState | null> {
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<AgentState> {
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<AgentState> {
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<AgentState> {
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<AgentState> {
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<AgentState> {
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();