UNPKG

coaian

Version:

Creative Orientation AI Agentic Memories - Narrative Beat Extension with IAIP relational integration

1,070 lines (996 loc) • 102 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { promises as fs } from 'fs'; import path from 'path'; import { LLM_GUIDANCE } from "./generated-llm-guidance.js"; import { fileURLToPath } from 'url'; import minimist from 'minimist'; import { isAbsolute } from 'path'; import { validate, ValidationSchemas } from './validation.js'; // Parse args and handle paths safely const argv = minimist(process.argv.slice(2)); // Handle help command if (argv.help || argv.h) { console.log(` 🧠 COAIA Memory - Creative-Oriented AI Assistant Memory System v2.1.0 Based on Robert Fritz's Structural Tension methodology DESCRIPTION: MCP server that extends knowledge graphs with structural tension charts for creative-oriented memory management. Supports advancing patterns, telescoping charts, and natural language interaction for AI assistants. USAGE: coaia-memory [OPTIONS] npx coaia-memory [OPTIONS] OPTIONS: --memory-path PATH Custom path for memory storage (default: ./memory.jsonl) --help, -h Show this help message ENVIRONMENT VARIABLES: COAIA_TOOLS Comma or space separated list of tool groups and/or individual tools to enable (default: "STC_TOOLS,init_llm_guidance") COAIA_DISABLED_TOOLS Comma or space separated list of tools to disable (useful for selectively removing tools from a group) TOOL GROUPS: STC_TOOLS All structural tension chart tools (11 tools) - recommended for creative work KG_TOOLS All knowledge graph tools (9 tools) - for traditional entity/relation work CORE_TOOLS Essential tools only (4 tools) - minimal viable set EXAMPLES: # Use only STC tools (default) coaia-memory --memory-path ./memory.jsonl # Enable both STC and KG tools COAIA_TOOLS="STC_TOOLS KG_TOOLS" coaia-memory --memory-path ./memory.jsonl # Use only core tools COAIA_TOOLS="CORE_TOOLS" coaia-memory --memory-path ./memory.jsonl # Enable STC tools but disable specific tools COAIA_TOOLS="STC_TOOLS" COAIA_DISABLED_TOOLS="delete_entities,delete_relations" coaia-memory # Enable specific individual tools COAIA_TOOLS="create_structural_tension_chart add_action_step list_active_charts" coaia-memory CORE FEATURES: šŸ“Š Structural Tension Charts • Create charts with desired outcomes, current reality, and action steps • Automatic due date distribution for strategic timing • Progress tracking and completion monitoring šŸ”­ Telescoping Support • Break down action steps into detailed sub-charts • Proper due date inheritance from parent steps • Navigate between overview and details seamlessly šŸ“ˆ Advancing Patterns • Completed actions flow into current reality automatically • Success builds momentum for continued advancement • Prevents oscillating patterns through structural awareness MCP TOOLS AVAILABLE: Chart Management (Common Workflow): • list_active_charts - START HERE: See all charts and their progress • add_action_step - Add strategic actions to existing charts • telescope_action_step - Break down action steps into detailed sub-charts • update_action_progress - Track progress without completing actions • mark_action_complete - Complete actions & update reality • update_current_reality - Add observations directly to current reality • create_structural_tension_chart - Create new chart with outcome & reality Chart Analysis (Advanced): • get_chart_progress - Detailed progress (redundant after list_active_charts) • open_nodes - Inspect specific chart components by exact name • read_graph - Dump all data (rarely needed) Knowledge Graph (Traditional): • create_entities - Add entities (people, concepts, events) • create_relations - Connect entities with relationships • add_observations - Record information about entities • search_nodes - Search across all stored information • read_graph - Export complete graph structure EXAMPLE USAGE: # Start with custom memory path coaia-memory --memory-path /path/to/my-charts.jsonl # Use in Claude Desktop (add to claude_desktop_config.json): { "mcpServers": { "coaia-memory": { "command": "npx", "args": ["-y", "coaia-memory", "--memory-path", "./charts.jsonl"] } } } NATURAL LANGUAGE PATTERNS: Creating Charts: "I want to create a mobile app in 3 months" "My desired outcome is to establish a morning routine" Progress Tracking: "I completed the research phase yesterday" "Show me progress on my Python learning goal" Telescoping: "Break down the Django tutorial step further" "I need more detail on the deployment action" CREATIVE ORIENTATION PRINCIPLES: āœ… Focus on Creation (not problem-solving): • "I want to create..." vs "I need to fix..." • "My desired outcome..." vs "The problem is..." āœ… Structural Tension Awareness: • Always pair desired outcomes with current reality • Honest assessment creates productive tension • Action steps are strategic secondary action we choose todo to achive the primary goal āœ… Advancing Patterns: • Success builds on success • Completed actions become part of current reality • Momentum creates natural progression toward goals PHILOSOPHY: COAIA Memory recognizes that structure determines behavior. By organizing memory around structural tension rather than problem-solving patterns, it naturally forms a structure that advances and helps build, not just the life you want, but the technologies to supports it's manifestation (hopefully!). CREDITS: • Author: J.Guillaume D.-Isabelle <jgi@jgwill.com> • Methodology: Robert Fritz - https://robertfritz.com • Foundation: Shane Holloman (original mcp-knowledge-graph) • License: MIT For more information, see: CLAUDE.md in the package directory `); process.exit(0); } let memoryPath = argv['memory-path']; // If a custom path is provided, ensure it's absolute if (memoryPath && !isAbsolute(memoryPath)) { memoryPath = path.resolve(process.cwd(), memoryPath); } // Tool filtering configuration const TOOL_GROUPS = { STC_TOOLS: [ 'create_structural_tension_chart', 'telescope_action_step', 'add_action_step', 'remove_action_step', 'mark_action_complete', 'get_chart_progress', 'list_active_charts', 'update_action_progress', 'update_current_reality', 'update_desired_outcome', 'creator_moment_of_truth' ], NARRATIVE_TOOLS: [ 'create_narrative_beat', 'telescope_narrative_beat', 'list_narrative_beats' ], KG_TOOLS: [ 'create_entities', 'create_relations', 'add_observations', 'delete_entities', 'delete_observations', 'delete_relations', 'search_nodes', 'open_nodes', 'read_graph' ], CORE_TOOLS: [ 'list_active_charts', 'create_structural_tension_chart', 'add_action_step', 'mark_action_complete' ] }; function getEnabledTools() { const enabledTools = new Set(); // Check for COAIA_DISABLED_TOOLS env var (comma or space separated) const disabledStr = process.env.COAIA_DISABLED_TOOLS || ''; const disabledTools = new Set(disabledStr.split(/[,\s]+/).filter(t => t.trim())); // Determine which tools to enable const enabledGroupsStr = process.env.COAIA_TOOLS || 'STC_TOOLS,NARRATIVE_TOOLS,init_llm_guidance'; const enabledGroups = enabledGroupsStr.split(/[,\s]+/).filter(t => t.trim()); enabledGroups.forEach(group => { const groupTools = TOOL_GROUPS[group]; if (groupTools) { groupTools.forEach(tool => enabledTools.add(tool)); } else { // Assume it's an individual tool name enabledTools.add(group); } }); // Remove disabled tools disabledTools.forEach(tool => enabledTools.delete(tool)); return enabledTools; } // Define the path to the JSONL file const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Use the custom path or default to the installation directory const MEMORY_FILE_PATH = memoryPath || path.join(__dirname, 'memory.jsonl'); // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph class KnowledgeGraphManager { async loadGraph() { try { const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); return lines.reduce((graph, line) => { const item = JSON.parse(line); if (item.type === "entity") graph.entities.push(item); if (item.type === "relation") graph.relations.push(item); // Support narrative_beat entities (convert to entity format) if (item.type === "narrative_beat") { const narrativeBeat = { name: item.name, entityType: 'narrative_beat', observations: item.observations || [], metadata: { ...item.metadata, narrative: item.narrative, relationalAlignment: item.relational_alignment, fourDirections: item.four_directions } }; graph.entities.push(narrativeBeat); } return graph; }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && error.code === "ENOENT") { return { entities: [], relations: [] }; } throw error; } } // Helper function to extract current reality from user context // Maintains structural tension by requiring explicit assessment extractCurrentRealityFromContext(userInput, actionStepTitle) { // Common patterns that indicate current reality assessment const realityPatterns = [ /(?:currently|right now|at present|today)\s+(.{10,})/i, /(?:i am|we are|the situation is)\s+(.{10,})/i, /(?:i have|we have|there is|there are)\s+(.{10,})/i, /(?:my current|our current|the current)\s+(.{10,})/i ]; for (const pattern of realityPatterns) { const match = userInput.match(pattern); if (match && match[1]) { return match[1].trim(); } } // If no explicit current reality found, require assessment return null; } async saveGraph(graph) { const lines = [ ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n")); } async createEntities(entities) { const graph = await this.loadGraph(); const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)); graph.entities.push(...newEntities); await this.saveGraph(graph); return newEntities; } async createRelations(relations) { const graph = await this.loadGraph(); const newRelations = relations.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from && existingRelation.to === r.to && existingRelation.relationType === r.relationType)); graph.relations.push(...newRelations); await this.saveGraph(graph); return newRelations; } async addObservations(observations) { const graph = await this.loadGraph(); const results = observations.map(o => { const entity = graph.entities.find(e => e.name === o.entityName); if (!entity) { throw new Error(`Entity with name ${o.entityName} not found`); } const newObservations = o.contents.filter(content => !entity.observations.includes(content)); entity.observations.push(...newObservations); return { entityName: o.entityName, addedObservations: newObservations }; }); await this.saveGraph(graph); return results; } async deleteEntities(entityNames) { const graph = await this.loadGraph(); graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); await this.saveGraph(graph); } async deleteObservations(deletions) { const graph = await this.loadGraph(); deletions.forEach(d => { const entity = graph.entities.find(e => e.name === d.entityName); if (entity) { entity.observations = entity.observations.filter(o => !d.observations.includes(o)); } }); await this.saveGraph(graph); } async deleteRelations(relations) { const graph = await this.loadGraph(); graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from && r.to === delRelation.to && r.relationType === delRelation.relationType)); await this.saveGraph(graph); } async readGraph() { return this.loadGraph(); } // Very basic search function async searchNodes(query) { const graph = await this.loadGraph(); // Filter entities const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) || e.entityType.toLowerCase().includes(query.toLowerCase()) || e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); const filteredGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async openNodes(names) { const graph = await this.loadGraph(); // Filter entities const filteredEntities = graph.entities.filter(e => names.includes(e.name)); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); const filteredGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } // COAIA-specific methods for structural tension charts and creative processes async createStructuralTensionChart(desiredOutcome, currentReality, dueDate, actionSteps) { // Educational validation for creative orientation const problemSolvingWords = ['fix', 'solve', 'eliminate', 'prevent', 'stop', 'avoid', 'reduce', 'remove']; const detectedProblemWords = problemSolvingWords.filter(word => desiredOutcome.toLowerCase().includes(word)); if (detectedProblemWords.length > 0) { throw new Error(`🌊 CREATIVE ORIENTATION REQUIRED Desired Outcome: "${desiredOutcome}" āŒ **Problem**: Contains problem-solving language: "${detectedProblemWords.join(', ')}" šŸ“š **Principle**: Structural Tension Charts use creative orientation - focus on what you want to CREATE, not what you want to eliminate. šŸŽÆ **Reframe Your Outcome**: Instead of elimination → Creation focus āœ… **Examples**: - Instead of: "Fix communication problems" - Use: "Establish clear, effective communication practices" - Instead of: "Reduce website loading time" - Use: "Achieve fast, responsive website performance" **Why This Matters**: Problem-solving creates oscillating patterns. Creative orientation creates advancing patterns toward desired outcomes. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } // Educational validation for current reality const readinessWords = ['ready to', 'prepared to', 'all set', 'ready for', 'set to']; const detectedReadinessWords = readinessWords.filter(phrase => currentReality.toLowerCase().includes(phrase)); if (detectedReadinessWords.length > 0) { throw new Error(`🌊 DELAYED RESOLUTION PRINCIPLE VIOLATION Current Reality: "${currentReality}" āŒ **Problem**: Contains readiness assumptions: "${detectedReadinessWords.join(', ')}" šŸ“š **Principle**: "Tolerate discrepancy, tension, and delayed resolution" - Robert Fritz šŸŽÆ **What's Needed**: Factual assessment of your actual current state (not readiness or preparation). āœ… **Examples**: - Instead of: "Ready to learn Python" - Use: "Never programmed before, interested in web development" - Instead of: "Prepared to start the project" - Use: "Have project requirements, no code written yet" **Why This Matters**: Readiness assumptions prematurely resolve the structural tension needed for creative advancement. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } const chartId = `chart_${Date.now()}`; const timestamp = new Date().toISOString(); // Create chart, desired outcome, and current reality entities const entities = [ { name: `${chartId}_chart`, entityType: 'structural_tension_chart', observations: [`Chart created on ${timestamp}`], metadata: { chartId, dueDate, level: 0, createdAt: timestamp, updatedAt: timestamp } }, { name: `${chartId}_desired_outcome`, entityType: 'desired_outcome', observations: [desiredOutcome], metadata: { chartId, dueDate, createdAt: timestamp, updatedAt: timestamp } }, { name: `${chartId}_current_reality`, entityType: 'current_reality', observations: [currentReality], metadata: { chartId, createdAt: timestamp, updatedAt: timestamp } } ]; // Add action steps if provided if (actionSteps && actionSteps.length > 0) { const stepDueDates = this.distributeActionStepDates(new Date(), new Date(dueDate), actionSteps.length); actionSteps.forEach((step, index) => { entities.push({ name: `${chartId}_action_${index + 1}`, entityType: 'action_step', observations: [step], metadata: { chartId, dueDate: stepDueDates[index].toISOString(), completionStatus: false, createdAt: timestamp, updatedAt: timestamp } }); }); } // Create relations const relations = [ { from: `${chartId}_chart`, to: `${chartId}_desired_outcome`, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: `${chartId}_chart`, to: `${chartId}_current_reality`, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: `${chartId}_current_reality`, to: `${chartId}_desired_outcome`, relationType: 'creates_tension_with', metadata: { createdAt: timestamp } } ]; // Add action step relations if (actionSteps && actionSteps.length > 0) { actionSteps.forEach((_, index) => { const actionName = `${chartId}_action_${index + 1}`; relations.push({ from: `${chartId}_chart`, to: actionName, relationType: 'contains', metadata: { createdAt: timestamp } }, { from: actionName, to: `${chartId}_desired_outcome`, relationType: 'advances_toward', metadata: { createdAt: timestamp } }); }); } // Save to graph await this.createEntities(entities); await this.createRelations(relations); return { chartId, entities, relations }; } async telescopeActionStep(actionStepName, newCurrentReality, initialActionSteps) { const graph = await this.loadGraph(); const actionStep = graph.entities.find(e => e.name === actionStepName && e.entityType === 'action_step'); if (!actionStep || !actionStep.metadata?.chartId) { throw new Error(`Action step ${actionStepName} not found or not properly configured`); } const parentChartId = actionStep.metadata.chartId; const inheritedDueDate = actionStep.metadata.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); const desiredOutcome = actionStep.observations[0]; // Use the action step description as the new desired outcome const result = await this.createStructuralTensionChart(desiredOutcome, newCurrentReality, inheritedDueDate, initialActionSteps); // Update the new chart's metadata to reflect telescoping relationship const newChart = await this.loadGraph(); const chartEntity = newChart.entities.find(e => e.name === `${result.chartId}_chart`); if (chartEntity && chartEntity.metadata) { chartEntity.metadata.parentChart = parentChartId; chartEntity.metadata.parentActionStep = actionStepName; chartEntity.metadata.level = (actionStep.metadata.level || 0) + 1; chartEntity.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(newChart); return { chartId: result.chartId, parentChart: parentChartId }; } async markActionStepComplete(actionStepName) { const graph = await this.loadGraph(); // An "action step" can be a 'desired_outcome' of a sub-chart, or a simple 'action_step' entity. const actionStep = graph.entities.find(e => e.name === actionStepName && (e.entityType === 'action_step' || e.entityType === 'desired_outcome')); if (!actionStep) { throw new Error(`Action step ${actionStepName} not found`); } const chartId = actionStep.metadata?.chartId; if (!chartId) { throw new Error(`Chart ID not found for action step ${actionStepName}`); } // Mark the action step itself as complete if (actionStep.metadata) { actionStep.metadata.completionStatus = true; actionStep.metadata.updatedAt = new Date().toISOString(); } // Also mark the parent chart entity as complete const chartEntity = graph.entities.find(e => e.name === `${chartId}_chart`); if (chartEntity && chartEntity.metadata) { chartEntity.metadata.completionStatus = true; chartEntity.metadata.updatedAt = new Date().toISOString(); } // Structural tension principle: completed action steps flow into the CURRENT REALITY // of the PARENT chart, advancing the overall structure. const parentChartId = chartEntity?.metadata?.parentChart; if (parentChartId) { const parentCurrentReality = graph.entities.find(e => e.name === `${parentChartId}_current_reality` && e.entityType === 'current_reality'); if (parentCurrentReality) { const completionMessage = `Completed: ${actionStep.observations[0]}`; if (!parentCurrentReality.observations.includes(completionMessage)) { parentCurrentReality.observations.push(completionMessage); if (parentCurrentReality.metadata) { parentCurrentReality.metadata.updatedAt = new Date().toISOString(); } } } } await this.saveGraph(graph); } async getChartProgress(chartId) { const graph = await this.loadGraph(); const actionSteps = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId); const completedActions = actionSteps.filter(e => e.metadata?.completionStatus === true).length; const totalActions = actionSteps.length; const progress = totalActions > 0 ? completedActions / totalActions : 0; // Find next incomplete action step with earliest due date const incompleteActions = actionSteps .filter(e => e.metadata?.completionStatus !== true) .sort((a, b) => { const dateA = new Date(a.metadata?.dueDate || '').getTime(); const dateB = new Date(b.metadata?.dueDate || '').getTime(); return dateA - dateB; }); const chart = graph.entities.find(e => e.name === `${chartId}_chart`); return { chartId, progress, completedActions, totalActions, nextAction: incompleteActions[0]?.name, dueDate: chart?.metadata?.dueDate }; } distributeActionStepDates(startDate, endDate, stepCount) { const totalTime = endDate.getTime() - startDate.getTime(); const stepInterval = totalTime / (stepCount + 1); // +1 to leave space before final due date const dates = []; for (let i = 1; i <= stepCount; i++) { dates.push(new Date(startDate.getTime() + (stepInterval * i))); } return dates; } async listActiveCharts() { const graph = await this.loadGraph(); const charts = graph.entities.filter(e => e.entityType === 'structural_tension_chart'); const chartSummaries = await Promise.all(charts.map(async (chart) => { const chartId = chart.metadata?.chartId || chart.name.replace('_chart', ''); const progress = await this.getChartProgress(chartId); // Get desired outcome const desiredOutcome = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome'); return { chartId, desiredOutcome: desiredOutcome?.observations[0] || 'Unknown outcome', dueDate: chart.metadata?.dueDate, progress: progress.progress, completedActions: progress.completedActions, totalActions: progress.totalActions, level: chart.metadata?.level || 0, parentChart: chart.metadata?.parentChart }; })); return chartSummaries.sort((a, b) => { // Sort by level first (master charts first), then by due date if (a.level !== b.level) return a.level - b.level; const dateA = new Date(a.dueDate || '').getTime(); const dateB = new Date(b.dueDate || '').getTime(); return dateA - dateB; }); } async updateActionProgress(actionStepName, progressObservation, updateCurrentReality) { const graph = await this.loadGraph(); const actionStep = graph.entities.find(e => e.name === actionStepName && (e.entityType === 'action_step' || e.entityType === 'desired_outcome')); if (!actionStep) { throw new Error(`Action step ${actionStepName} not found`); } // Add progress observation to action step actionStep.observations.push(progressObservation); if (actionStep.metadata) { actionStep.metadata.updatedAt = new Date().toISOString(); } // Optionally update current reality with progress if (updateCurrentReality && actionStep.metadata?.chartId) { const chartEntity = graph.entities.find(e => e.name === `${actionStep.metadata.chartId}_chart`); const parentChartId = chartEntity?.metadata?.parentChart; const targetChartId = parentChartId || actionStep.metadata.chartId; const currentReality = graph.entities.find(e => e.name === `${targetChartId}_current_reality` && e.entityType === 'current_reality'); if (currentReality) { // Progress observations flow into current reality, changing the structural dynamic const progressMessage = `Progress on ${actionStep.observations[0]}: ${progressObservation}`; if (!currentReality.observations.includes(progressMessage)) { currentReality.observations.push(progressMessage); if (currentReality.metadata) { currentReality.metadata.updatedAt = new Date().toISOString(); } } } } await this.saveGraph(graph); } async updateCurrentReality(chartId, newObservations) { const graph = await this.loadGraph(); const currentReality = graph.entities.find(e => e.name === `${chartId}_current_reality` && e.entityType === 'current_reality'); if (!currentReality) { throw new Error(`Chart ${chartId} not found or missing current reality`); } // Add new observations to current reality const uniqueObservations = newObservations.filter(obs => !currentReality.observations.includes(obs)); currentReality.observations.push(...uniqueObservations); if (currentReality.metadata) { currentReality.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(graph); } async updateDesiredOutcome(chartId, newDesiredOutcome) { const graph = await this.loadGraph(); const desiredOutcomeEntity = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome'); if (!desiredOutcomeEntity) { throw new Error(`Chart ${chartId} desired outcome not found`); } // Replace the first observation (which is the desired outcome text) desiredOutcomeEntity.observations[0] = newDesiredOutcome; if (desiredOutcomeEntity.metadata) { desiredOutcomeEntity.metadata.updatedAt = new Date().toISOString(); } await this.saveGraph(graph); } // Narrative beat creation functionality async createNarrativeBeat(parentChartId, title, act, type_dramatic, universes, description, prose, lessons, assessRelationalAlignment = false, initiateFourDirectionsInquiry = false, filePath) { const timestamp = Date.now(); const beatName = `${parentChartId}_beat_${timestamp}`; // Create narrative beat entity const entity = { name: beatName, entityType: 'narrative_beat', observations: [ `Act ${act} ${type_dramatic}`, `Timestamp: ${new Date().toISOString()}`, `Universe: ${universes.join(', ')}` ], metadata: { chartId: parentChartId, act, type_dramatic, universes, timestamp: new Date().toISOString(), createdAt: new Date().toISOString(), narrative: { description, prose, lessons }, relationalAlignment: { assessed: false, score: null, principles: [] }, fourDirections: { north_vision: null, east_intention: null, south_emotion: null, west_introspection: null } } }; // Add to graph await this.createEntities([entity]); // Create relation to parent chart if it exists const graph = await this.loadGraph(); const parentChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === parentChartId); if (parentChart) { await this.createRelations([{ from: beatName, to: `${parentChartId}_chart`, relationType: 'documents', metadata: { createdAt: new Date().toISOString(), description: 'Narrative beat documents chart progress' } }]); } // TODO: IAIP integration would go here if (assessRelationalAlignment) { console.log('šŸ”® Relational alignment assessment requested (iaip-mcp integration pending)'); } if (initiateFourDirectionsInquiry) { console.log('🧭 Four Directions inquiry requested (iaip-mcp integration pending)'); } return { entity, beatName }; } async telescopeNarrativeBeat(parentBeatName, newCurrentReality, initialSubBeats) { const graph = await this.loadGraph(); const parentBeat = graph.entities.find(e => e.name === parentBeatName && e.entityType === 'narrative_beat'); if (!parentBeat) { throw new Error(`Parent narrative beat not found: ${parentBeatName}`); } // Update parent beat's current reality (add to observations) parentBeat.observations.push(`Telescoped: ${newCurrentReality}`); if (parentBeat.metadata) { parentBeat.metadata.updatedAt = new Date().toISOString(); } const subBeats = []; // Create sub-beats if provided if (initialSubBeats && initialSubBeats.length > 0) { for (let i = 0; i < initialSubBeats.length; i++) { const subBeat = initialSubBeats[i]; const result = await this.createNarrativeBeat(parentBeatName, // Use parent beat as chart ID subBeat.title, i + 1, // Sequential act numbers subBeat.type_dramatic, parentBeat.metadata?.universes || ['engineer-world'], subBeat.description, subBeat.prose, subBeat.lessons); subBeats.push(result.entity); } } await this.saveGraph(graph); return { parentBeat, subBeats }; } async listNarrativeBeats(parentChartId) { const graph = await this.loadGraph(); const beats = graph.entities.filter(e => e.entityType === 'narrative_beat'); if (parentChartId) { return beats.filter(beat => beat.metadata?.chartId === parentChartId); } return beats; } async addActionStep(parentChartId, actionStepTitle, dueDate, currentReality) { const graph = await this.loadGraph(); const parentChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === parentChartId); if (!parentChart) { throw new Error(`Parent chart ${parentChartId} not found`); } // Get parent chart's due date for auto-distribution const parentDueDate = parentChart.metadata?.dueDate; if (!parentDueDate) { throw new Error(`Parent chart ${parentChartId} has no due date`); } // Calculate due date for action step if not provided let actionStepDueDate = dueDate; if (!actionStepDueDate) { // Distribute between now and parent due date (simple midpoint for now) const now = new Date(); const parentEnd = new Date(parentDueDate); const midpoint = new Date(now.getTime() + (parentEnd.getTime() - now.getTime()) / 2); actionStepDueDate = midpoint.toISOString(); } // Require current reality assessment - no defaults that prematurely resolve tension if (!currentReality) { throw new Error(`🌊 DELAYED RESOLUTION PRINCIPLE VIOLATION Action step: "${actionStepTitle}" āŒ **Problem**: Current reality assessment missing šŸ“š **Principle**: "Tolerate discrepancy, tension, and delayed resolution" - Robert Fritz šŸŽÆ **What's Needed**: Honest assessment of your actual current state relative to this action step. āœ… **Examples**: - "Never used Django, completed Python basics" - "Built one API, struggling with authentication" - "Read 3 chapters, concepts still unclear" āŒ **Avoid**: "Ready to begin", "Prepared to start", "All set to..." **Why This Matters**: Premature resolution destroys the structural tension that generates creative advancement. The system NEEDS honest current reality to create productive tension. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } const actionCurrentReality = currentReality; // Create telescoped structural tension chart const telescopedChart = await this.createStructuralTensionChart(actionStepTitle, actionCurrentReality, actionStepDueDate); // Update the telescoped chart's metadata to show parent relationship const updatedGraph = await this.loadGraph(); const telescopedChartEntity = updatedGraph.entities.find(e => e.name === `${telescopedChart.chartId}_chart`); if (telescopedChartEntity && telescopedChartEntity.metadata) { telescopedChartEntity.metadata.parentChart = parentChartId; telescopedChartEntity.metadata.level = (parentChart.metadata?.level || 0) + 1; telescopedChartEntity.metadata.updatedAt = new Date().toISOString(); } // Create relationship: telescoped chart advances toward parent's desired outcome const parentDesiredOutcome = updatedGraph.entities.find(e => e.name === `${parentChartId}_desired_outcome` && e.entityType === 'desired_outcome'); if (parentDesiredOutcome) { const timestamp = new Date().toISOString(); await this.createRelations([{ from: `${telescopedChart.chartId}_desired_outcome`, to: parentDesiredOutcome.name, relationType: 'advances_toward', metadata: { createdAt: timestamp } }]); } await this.saveGraph(updatedGraph); return { chartId: telescopedChart.chartId, actionStepName: `${telescopedChart.chartId}_desired_outcome` }; } // Enhanced method for LLMs to telescope with intelligent current reality extraction async telescopeActionStepWithContext(parentChartId, actionStepTitle, userContext, currentReality, dueDate) { // If current reality not provided, try to extract from context let finalCurrentReality = currentReality; if (!finalCurrentReality) { finalCurrentReality = this.extractCurrentRealityFromContext(userContext, actionStepTitle) ?? undefined; } // If still no current reality, provide guidance while maintaining tension if (!finalCurrentReality) { throw new Error(`Current reality assessment needed for "${actionStepTitle}". ` + `Please assess your actual current state relative to this action step. ` + `Example: "I have never used Django before" or "I completed the basics but haven't built a real project" ` + `rather than assuming readiness. Structural tension requires honest current reality assessment.`); } // Proceed with telescoping using the assessed current reality return this.addActionStep(parentChartId, actionStepTitle, dueDate, finalCurrentReality); } // Unified interface for managing action steps - handles both creation and expansion async manageActionStep(parentReference, actionDescription, currentReality, initialActionSteps, dueDate) { const graph = await this.loadGraph(); // Pattern detection: Determine if parentReference is entity name or chart ID const actionStepPattern = /^chart_\d+_action_\d+$/; const desiredOutcomePattern = /^chart_\d+_desired_outcome$/; const chartIdPattern = /^chart_\d+$/; const isActionStepEntity = actionStepPattern.test(parentReference); const isDesiredOutcomeEntity = desiredOutcomePattern.test(parentReference); const isChartId = chartIdPattern.test(parentReference); // Route 1: Expanding existing action_step entity (legacy pattern) if (isActionStepEntity) { const actionStep = graph.entities.find(e => e.name === parentReference && e.entityType === 'action_step'); if (!actionStep) { // Provide helpful error with available actions const allActionSteps = graph.entities .filter(e => e.entityType === 'action_step') .map(e => `- ${e.name}: "${e.observations[0]}"`); throw new Error(`šŸ” ACTION STEP ENTITY NOT FOUND Received: "${parentReference}" Expected: Valid action_step entity name (e.g., "chart_123_action_1") Available action steps in memory: ${allActionSteps.length > 0 ? allActionSteps.join('\n') : '(none found)'} Tip: If creating a new action step, use the parent chart ID instead.`); } // Use telescoping logic for legacy action_step entities const currentRealityToUse = currentReality || "Expanding action step into detailed sub-chart"; const telescopedResult = await this.telescopeActionStep(parentReference, currentRealityToUse, initialActionSteps); // Transform result to include actionStepName return { chartId: telescopedResult.chartId, actionStepName: `${telescopedResult.chartId}_desired_outcome` }; } // Route 2: Expanding existing desired_outcome entity (modern pattern) if (isDesiredOutcomeEntity) { const desiredOutcome = graph.entities.find(e => e.name === parentReference && e.entityType === 'desired_outcome'); if (!desiredOutcome || !desiredOutcome.metadata?.chartId) { throw new Error(`šŸ” DESIRED OUTCOME ENTITY NOT FOUND Received: "${parentReference}" Expected: Valid desired_outcome entity name (e.g., "chart_123_desired_outcome") Tip: If creating a new action step, use the parent chart ID instead.`); } // Use telescoping logic for desired_outcome entities const currentRealityToUse = currentReality || "Expanding desired outcome into detailed sub-chart"; const telescopedResult = await this.telescopeActionStep(parentReference, currentRealityToUse, initialActionSteps); // Transform result to include actionStepName return { chartId: telescopedResult.chartId, actionStepName: `${telescopedResult.chartId}_desired_outcome` }; } // Route 3: Creating new action step under parent chart (modern pattern) if (isChartId) { // Validate parent chart exists const parentChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === parentReference); if (!parentChart) { // Provide helpful error with available charts const allCharts = graph.entities .filter(e => e.entityType === 'structural_tension_chart') .map(e => { const outcome = graph.entities.find(o => o.name === `${e.metadata?.chartId}_desired_outcome`); return `- ${e.metadata?.chartId}: "${outcome?.observations[0] || 'Unknown'}"`; }); throw new Error(`šŸ” PARENT CHART NOT FOUND Received: "${parentReference}" Expected: Valid chart ID (e.g., "chart_123") Available charts in memory: ${allCharts.length > 0 ? allCharts.join('\n') : '(none found)'} Tip: Use 'list_active_charts' to see all available charts.`); } // Enforce delayed resolution principle for new action creation if (!currentReality) { throw new Error(`🌊 DELAYED RESOLUTION PRINCIPLE VIOLATION Action step: "${actionDescription}" Parent chart: "${parentReference}" āŒ **Problem**: Current reality assessment missing šŸ“š **Principle**: "Tolerate discrepancy, tension, and delayed resolution" - Robert Fritz šŸŽÆ **What's Needed**: Honest assessment of actual current state relative to this action step. āœ… **Examples**: - "Never used Django, completed Python basics" - "Built one API, struggling with authentication" - "Read 3 chapters, concepts still unclear" āŒ **Avoid**: "Ready to begin", "Prepared to start", "All set to..." **Why This Matters**: Premature resolution destroys structural tension essential for creative advancement. šŸ’” **Tip**: Run 'init_llm_guidance' for complete methodology overview.`); } // Create new action step as telescoped chart return await this.addActionStep(parentReference, actionDescription, dueDate, currentReality); } // Route 4: Invalid format - provide comprehensive guidance throw new Error(`🚨 INVALID PARENT REFERENCE FORMAT Received: "${parentReference}" Valid formats: 1. Chart ID: "chart_123" → Creates new action step 2. Action entity: "chart_123_action_1" → Expands existing legacy action step 3. Desired outcome: "chart_123_desired_outcome" → Expands existing modern action step Examples: - Create new action: manageActionStep("chart_123", "Complete tutorial", "Never used Django") - Expand existing: manageActionStep("chart_123_action_1", "Complete tutorial", undefined, ["Step 1", "Step 2"]) šŸ’” **Tip**: Use 'list_active_charts' to see available charts and their IDs.`); } async removeActionStep(parentChartId, actionStepName) { const graph = await this.loadGraph(); // Find the action step (which is actually a telescoped chart's desired outcome) const actionStepEntity = graph.entities.find(e => e.name === actionStepName); if (!actionStepEntity || !actionStepEntity.metadata?.chartId) { throw new Error(`Action step ${actionStepName} not found`); } const telescopedChartId = actionStepEntity.metadata.chartId; // Verify it belongs to the parent chart const telescopedChart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === telescopedChartId && e.metadata?.parentChart === parentChartId); if (!telescopedChart) { throw new Error(`Action step ${actionStepName} does not belong to chart ${parentChartId}`); } // Remove all entities belonging to the telescoped chart const entitiesToRemove = graph.entities .filter(e => e.metadata?.chartId === telescopedChartId) .map(e => e.name); await this.deleteEntities(entitiesToRemove); } } const knowledgeGraphManager = new KnowledgeGraphManager(); // The server instance and tools exposed to AI models const server = new Server({ name: "coaia-narrative", version: "0.1.0", description: "COAIA Narrative - Structural Tension Charts with Narrative Beat Extension for multi-universe story capture. Extends coaia-memory with relational and ceremonial integration. 🚨 NEW LLM? Run 'init_llm_guidance' first." }, { capabilities: { tools: {}, }, }); server.setRequestHandler(ListToolsRequestSchema, async () => { const enabledTools = getEnabledTools(); const allTools = [ { name: "create_entities", description: "ADVANCED: Create traditional knowledge graph entities. For structural tension charts, use create_structural_tension_chart or add_action_step instead.", inputSchema: { type: "object", properties: { entities: { type: "array", items: { type: "object", properties: { name: { type: "string", description: "The name of the entity" }, entityType: { type: "string", description: "The type of the entity" }, observations: { type: "array", items: { type: "string" },