@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
509 lines (508 loc) • 16.3 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { v4 as uuidv4 } from "uuid";
import { logger } from "../../../core/monitoring/logger.js";
import { FrameManager } from "../../../core/context/index.js";
import { sessionManager } from "../../../core/session/index.js";
import { RalphStackMemoryBridge } from "../bridge/ralph-stackmemory-bridge.js";
class MultiLoopOrchestrator {
frameManager;
activeTasks = /* @__PURE__ */ new Map();
activeLoops = /* @__PURE__ */ new Map();
config;
constructor(config) {
this.config = {
maxConcurrentLoops: 3,
dependencyResolutionTimeout: 3e4,
// 30 seconds
enableAdaptivePlanning: true,
sharedContextEnabled: true,
fallbackStrategy: "sequential",
...config
};
logger.info("Multi-loop orchestrator initialized", this.config);
}
async initialize() {
try {
await sessionManager.initialize();
const session = await sessionManager.getOrCreateSession({});
if (session.database) {
this.frameManager = new FrameManager(
session.database,
session.projectId
);
}
logger.info("Orchestrator initialized successfully");
} catch (error) {
logger.error("Failed to initialize orchestrator", error);
throw error;
}
}
/**
* Break down complex task into manageable loops
*/
async orchestrateComplexTask(description, criteria, options) {
logger.info("Orchestrating complex task", {
task: description.substring(0, 100),
criteriaCount: criteria.length,
maxLoops: options?.maxLoops || this.config.maxConcurrentLoops
});
const orchestrationId = uuidv4();
try {
const breakdown = options?.customBreakdown || await this.analyzeAndBreakdownTask(description, criteria);
const executionPlan = await this.createExecutionPlan(breakdown, options);
const dependencyErrors = this.validateDependencies(executionPlan);
if (dependencyErrors.length > 0) {
throw new Error(`Dependency errors: ${dependencyErrors.join(", ")}`);
}
const orchestratedTask = {
id: orchestrationId,
description,
breakdown,
executionPlan,
status: "planning",
startTime: Date.now(),
loops: /* @__PURE__ */ new Map(),
sharedContext: {}
};
this.activeTasks.set(orchestrationId, orchestratedTask);
const result = await this.executeOrchestration(orchestratedTask);
logger.info("Complex task orchestration completed", {
orchestrationId,
status: result.success ? "success" : "failure",
loopsExecuted: result.completedLoops.length,
duration: Date.now() - orchestratedTask.startTime
});
return result;
} catch (error) {
logger.error("Orchestration failed", error);
throw error;
} finally {
this.activeTasks.delete(orchestrationId);
}
}
/**
* Execute coordinated parallel loops
*/
async executeParallelLoops(tasks, coordination) {
logger.info(`Executing ${tasks.length} parallel loops`);
const execution = {
id: uuidv4(),
tasks,
startTime: Date.now(),
results: /* @__PURE__ */ new Map(),
sharedState: coordination?.sharedState || {}
};
const promises = tasks.map(
(task) => this.executeParallelTask(task, execution)
);
try {
await Promise.allSettled(promises);
execution.endTime = Date.now();
execution.status = Array.from(execution.results.values()).every(
(r) => r.success
) ? "success" : "partial";
return execution;
} catch (error) {
logger.error("Parallel execution failed", error);
execution.status = "failed";
execution.error = error.message;
return execution;
}
}
/**
* Analyze and break down complex task
*/
async analyzeAndBreakdownTask(description, criteria) {
const complexity = this.assessTaskComplexity(description);
if (complexity.score < 5) {
return [
{
id: uuidv4(),
title: description,
description,
criteria,
priority: 1,
estimatedIterations: 3,
dependencies: [],
type: "single"
}
];
}
const subtasks = [];
if (this.needsSetup(description)) {
subtasks.push({
id: uuidv4(),
title: "Project Setup",
description: "Set up project structure and dependencies",
criteria: ["Project structure created", "Dependencies installed"],
priority: 1,
estimatedIterations: 2,
dependencies: [],
type: "setup"
});
}
const coreTask = this.extractCoreTask(description);
if (coreTask) {
subtasks.push({
id: uuidv4(),
title: "Core Implementation",
description: coreTask,
criteria: criteria.filter(
(c) => c.toLowerCase().includes("function") || c.toLowerCase().includes("implement")
),
priority: 2,
estimatedIterations: 5,
dependencies: subtasks.length > 0 ? [subtasks[0].id] : [],
type: "implementation"
});
}
if (this.needsTesting(criteria)) {
subtasks.push({
id: uuidv4(),
title: "Testing Implementation",
description: "Create comprehensive tests",
criteria: criteria.filter((c) => c.toLowerCase().includes("test")),
priority: 3,
estimatedIterations: 3,
dependencies: subtasks.length > 0 ? [subtasks[subtasks.length - 1].id] : [],
type: "testing"
});
}
if (this.needsDocumentation(criteria)) {
subtasks.push({
id: uuidv4(),
title: "Documentation",
description: "Create documentation and examples",
criteria: criteria.filter((c) => c.toLowerCase().includes("doc")),
priority: 4,
estimatedIterations: 2,
dependencies: [],
type: "documentation"
});
}
return subtasks.length > 0 ? subtasks : [
{
id: uuidv4(),
title: description,
description,
criteria,
priority: 1,
estimatedIterations: Math.min(8, Math.max(3, complexity.score)),
dependencies: [],
type: "single"
}
];
}
/**
* Create execution plan from breakdown
*/
async createExecutionPlan(breakdown, options) {
const plan = {
phases: [],
totalEstimatedTime: 0,
parallelizable: !options?.forceSequential && breakdown.length > 1
};
if (options?.forceSequential || !this.canExecuteInParallel(breakdown)) {
plan.phases = breakdown.map((task, index) => ({
id: `phase-${index + 1}`,
tasks: [task],
dependencies: index > 0 ? [`phase-${index}`] : [],
parallelExecution: false
}));
} else {
const phases = this.groupTasksByDependencies(breakdown);
plan.phases = phases;
}
plan.totalEstimatedTime = plan.phases.reduce(
(sum, phase) => sum + Math.max(...phase.tasks.map((t) => t.estimatedIterations)) * 3e4,
// 30s per iteration
0
);
return plan;
}
/**
* Execute the orchestration plan
*/
async executeOrchestration(task) {
const result = {
orchestrationId: task.id,
success: false,
completedLoops: [],
failedLoops: [],
totalDuration: 0,
insights: []
};
try {
task.status = "executing";
for (const phase of task.executionPlan.phases) {
logger.info(
`Executing phase ${phase.id} with ${phase.tasks.length} tasks`
);
if (phase.parallelExecution && phase.tasks.length > 1) {
const parallelResult = await this.executeParallelLoops(phase.tasks);
for (const [_taskId, taskResult] of parallelResult.results) {
if (taskResult.success) {
result.completedLoops.push(taskResult.loopId);
} else {
result.failedLoops.push({
loopId: taskResult.loopId,
error: taskResult.error || "Unknown error"
});
}
}
} else {
for (const phaseTask of phase.tasks) {
const loopResult = await this.executeTaskLoop(phaseTask, task);
if (loopResult.success) {
result.completedLoops.push(loopResult.loopId);
if (this.config.sharedContextEnabled) {
await this.updateSharedContext(task, loopResult);
}
} else {
result.failedLoops.push({
loopId: loopResult.loopId,
error: loopResult.error || "Unknown error"
});
if (this.config.fallbackStrategy === "abort") {
throw new Error(`Task failed: ${loopResult.error}`);
}
}
}
}
}
task.status = "completed";
result.success = result.failedLoops.length === 0;
result.totalDuration = Date.now() - task.startTime;
result.insights = this.generateOrchestrationInsights(task, result);
return result;
} catch (error) {
task.status = "failed";
result.success = false;
result.error = error.message;
return result;
}
}
/**
* Execute a single task as a Ralph loop
*/
async executeTaskLoop(taskBreakdown, orchestratedTask) {
try {
const bridge = new RalphStackMemoryBridge({
baseDir: `.ralph-${taskBreakdown.id}`,
maxIterations: taskBreakdown.estimatedIterations * 2,
// Allow extra iterations
useStackMemory: true
});
await bridge.initialize({
task: taskBreakdown.description,
criteria: taskBreakdown.criteria.join("\n")
});
this.activeLoops.set(taskBreakdown.id, bridge);
orchestratedTask.loops.set(taskBreakdown.id, {
bridge,
status: "running",
startTime: Date.now()
});
await bridge.run();
const loopInfo = orchestratedTask.loops.get(taskBreakdown.id);
if (loopInfo) {
loopInfo.status = "completed";
loopInfo.endTime = Date.now();
}
this.activeLoops.delete(taskBreakdown.id);
return { success: true, loopId: taskBreakdown.id };
} catch (error) {
logger.error(`Task loop failed: ${taskBreakdown.title}`, error);
const loopInfo = orchestratedTask.loops.get(taskBreakdown.id);
if (loopInfo) {
loopInfo.status = "failed";
loopInfo.error = error.message;
loopInfo.endTime = Date.now();
}
this.activeLoops.delete(taskBreakdown.id);
return {
success: false,
loopId: taskBreakdown.id,
error: error.message
};
}
}
/**
* Execute a task in parallel context
*/
async executeParallelTask(task, execution) {
try {
const result = await this.executeTaskLoop(task, {
id: execution.id,
description: `Parallel task: ${task.title}`,
breakdown: [task],
executionPlan: {
phases: [],
totalEstimatedTime: 0,
parallelizable: false
},
status: "executing",
startTime: execution.startTime,
loops: /* @__PURE__ */ new Map(),
sharedContext: execution.sharedState
});
execution.results.set(task.id, result);
} catch (error) {
execution.results.set(task.id, {
success: false,
loopId: task.id,
error: error.message
});
}
}
/**
* Update shared context between tasks
*/
async updateSharedContext(orchestratedTask, loopResult) {
logger.debug("Updating shared context", {
orchestrationId: orchestratedTask.id,
loopId: loopResult.loopId
});
}
/**
* Generate insights from orchestration
*/
generateOrchestrationInsights(task, result) {
const insights = [];
const avgLoopDuration = Array.from(task.loops.values()).filter((l) => l.endTime && l.startTime).map((l) => l.endTime - l.startTime).reduce((sum, duration) => sum + duration, 0) / task.loops.size;
if (avgLoopDuration > 0) {
insights.push(
`Average loop duration: ${Math.round(avgLoopDuration / 1e3)}s`
);
}
const successRate = result.completedLoops.length / (result.completedLoops.length + result.failedLoops.length);
insights.push(`Success rate: ${Math.round(successRate * 100)}%`);
if (task.breakdown.length > 3) {
insights.push(
"Complex task benefited from breakdown into multiple loops"
);
}
return insights;
}
// Helper methods for task analysis
assessTaskComplexity(description) {
const factors = [];
let score = 1;
if (description.length > 200) {
score += 2;
factors.push("long description");
}
if (description.includes("and")) {
score += 1;
factors.push("multiple requirements");
}
if (description.toLowerCase().includes("test")) {
score += 2;
factors.push("testing required");
}
if (description.toLowerCase().includes("document")) {
score += 1;
factors.push("documentation needed");
}
if (description.toLowerCase().includes("refactor")) {
score += 3;
factors.push("refactoring complexity");
}
return { score, factors };
}
needsSetup(description) {
const setupKeywords = [
"project",
"initialize",
"setup",
"scaffold",
"create structure"
];
return setupKeywords.some(
(keyword) => description.toLowerCase().includes(keyword)
);
}
needsTesting(criteria) {
return criteria.some((c) => c.toLowerCase().includes("test"));
}
needsDocumentation(criteria) {
return criteria.some((c) => c.toLowerCase().includes("doc"));
}
extractCoreTask(description) {
const sentences = description.split(".");
return sentences.find(
(s) => s.toLowerCase().includes("implement") || s.toLowerCase().includes("create") || s.toLowerCase().includes("add")
) || null;
}
canExecuteInParallel(breakdown) {
return breakdown.some((task) => task.dependencies.length === 0);
}
groupTasksByDependencies(breakdown) {
const phases = [];
const processed = /* @__PURE__ */ new Set();
while (processed.size < breakdown.length) {
const readyTasks = breakdown.filter(
(task) => !processed.has(task.id) && task.dependencies.every((dep) => processed.has(dep))
);
if (readyTasks.length === 0) break;
phases.push({
id: `phase-${phases.length + 1}`,
tasks: readyTasks,
dependencies: phases.length > 0 ? [`phase-${phases.length}`] : [],
parallelExecution: readyTasks.length > 1
});
readyTasks.forEach((task) => processed.add(task.id));
}
return phases;
}
validateDependencies(plan) {
const errors = [];
const allTaskIds = new Set(
plan.phases.flatMap((phase) => phase.tasks.map((task) => task.id))
);
for (const phase of plan.phases) {
for (const task of phase.tasks) {
for (const dep of task.dependencies) {
if (!allTaskIds.has(dep)) {
errors.push(`Task ${task.id} depends on non-existent task ${dep}`);
}
}
}
}
return errors;
}
/**
* Monitor orchestration progress
*/
getOrchestrationStatus(orchestrationId) {
return this.activeTasks.get(orchestrationId) || null;
}
/**
* Stop orchestration
*/
async stopOrchestration(orchestrationId) {
const task = this.activeTasks.get(orchestrationId);
if (!task) return;
for (const [loopId, loopInfo] of task.loops) {
if (loopInfo.status === "running") {
try {
loopInfo.status = "stopped";
this.activeLoops.delete(loopId);
} catch (error) {
logger.error(`Failed to stop loop ${loopId}`, error);
}
}
}
task.status = "stopped";
this.activeTasks.delete(orchestrationId);
logger.info("Orchestration stopped", { orchestrationId });
}
}
const multiLoopOrchestrator = new MultiLoopOrchestrator();
export {
MultiLoopOrchestrator,
multiLoopOrchestrator
};