UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

562 lines (485 loc) 16.4 kB
/** * SYNAPSE Output Formatter * * Converts layer processor results into the <synapse-rules> XML block * that is injected into each user prompt. Enforces section ordering * per DESIGN doc section 14 and applies token budget truncation. * * @module core/synapse/output/formatter * @version 1.0.0 * @created Story SYN-6 - SynapseEngine Orchestrator + Output Formatter */ const { estimateTokens } = require('../utils/tokens'); // --------------------------------------------------------------------------- // Section ordering (DESIGN doc section 14) // --------------------------------------------------------------------------- /** * Ordered list of section identifiers matching layer outputs. * Sections are rendered in this order; null results are omitted. * * Truncation removes from the END of this array first (SUMMARY, then * KEYWORD, then SQUAD, etc.) to preserve highest-priority sections. */ const SECTION_ORDER = [ 'CONTEXT_BRACKET', 'CONSTITUTION', 'AGENT', 'WORKFLOW', 'TASK', 'SQUAD', 'KEYWORD', 'MEMORY_HINTS', 'STAR_COMMANDS', 'DEVMODE', 'SUMMARY', ]; /** * Map layer names to section identifiers. */ const LAYER_TO_SECTION = { constitution: 'CONSTITUTION', global: 'CONTEXT_BRACKET', // global rules go into bracket section agent: 'AGENT', workflow: 'WORKFLOW', task: 'TASK', squad: 'SQUAD', keyword: 'KEYWORD', memory: 'MEMORY_HINTS', 'star-command': 'STAR_COMMANDS', }; // --------------------------------------------------------------------------- // Section Formatters // --------------------------------------------------------------------------- /** * Format the CONTEXT BRACKET header section. * * @param {string} bracket - Current bracket name * @param {number} contextPercent - Remaining context % * @param {object[]} globalResults - Results from global/context layers * @returns {string} */ function formatContextBracket(bracket, contextPercent, globalResults) { const lines = [`[CONTEXT BRACKET]\nCONTEXT BRACKET: [${bracket}] (${contextPercent.toFixed(1)}% remaining)`]; // Include global/context rules if present for (const result of globalResults) { if (result && result.rules && result.rules.length > 0) { lines.push(`[${bracket}] CONTEXT RULES:`); result.rules.forEach((rule, i) => { lines.push(` ${i + 1}. ${rule}`); }); } } return lines.join('\n'); } /** * Format the CONSTITUTION section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatConstitution(result) { const lines = ['[CONSTITUTION] (NON-NEGOTIABLE)']; for (const rule of result.rules) { lines.push(` ${rule}`); } return lines.join('\n'); } /** * Format the AGENT section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatAgent(result) { const meta = result.metadata || {}; const agentId = meta.agentId || meta.source || 'unknown'; const domain = meta.domain || meta.source || ''; const lines = [`[ACTIVE AGENT: @${agentId}]`]; if (domain) { lines.push(` DOMAIN: ${domain}`); } if (meta.authority && Array.isArray(meta.authority)) { lines.push(' AUTHORITY BOUNDARIES:'); for (const auth of meta.authority) { lines.push(` - ${auth}`); } } if (result.rules.length > 0) { lines.push(' RULES:'); result.rules.forEach((rule, i) => { lines.push(` ${i}. ${rule}`); }); } return lines.join('\n'); } /** * Format the WORKFLOW section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatWorkflow(result) { const meta = result.metadata || {}; const workflowId = meta.workflowId || meta.source || 'unknown'; const phase = meta.phase || ''; const lines = [`[ACTIVE WORKFLOW: ${workflowId}]`]; if (phase) { lines.push(` PHASE: ${phase}`); } if (result.rules.length > 0) { lines.push(' RULES:'); result.rules.forEach((rule, i) => { lines.push(` ${i}. ${rule}`); }); } return lines.join('\n'); } /** * Format the TASK section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatTask(result) { const meta = result.metadata || {}; const lines = ['[TASK CONTEXT]']; if (meta.taskId) { lines.push(` Active Task: ${meta.taskId}`); } if (meta.storyId) { lines.push(` Story: ${meta.storyId}`); } for (const rule of result.rules) { lines.push(` ${rule}`); } return lines.join('\n'); } /** * Format the SQUAD section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatSquad(result) { const meta = result.metadata || {}; const squadName = meta.squadName || meta.source || 'unknown'; const lines = [`[SQUAD: ${squadName}]`]; for (const rule of result.rules) { lines.push(` ${rule}`); } return lines.join('\n'); } /** * Format the KEYWORD section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatKeyword(result) { const meta = result.metadata || {}; const lines = ['[KEYWORD MATCHES]']; if (meta.matches && Array.isArray(meta.matches)) { for (const match of meta.matches) { lines.push(` "${match.keyword}" matched ${match.domain} (${match.reason || 'keyword match'})`); } } for (const rule of result.rules) { lines.push(` ${rule}`); } return lines.join('\n'); } /** * Format the STAR-COMMANDS section. * * @param {object} result - Layer result { rules, metadata } * @returns {string} */ function formatStarCommands(result) { const meta = result.metadata || {}; const command = meta.command || meta.source || 'unknown'; const lines = [ '[STAR-COMMANDS]', '============================================================', `[*${command}] COMMAND:`, ]; result.rules.forEach((rule, i) => { lines.push(` ${i}. ${rule}`); }); lines.push('============================================================'); return lines.join('\n'); } // --------------------------------------------------------------------------- // Memory Hints Section (SYN-10) // --------------------------------------------------------------------------- /** * Format the MEMORY HINTS section. * * Only included when hints array is non-empty. * Each hint displays source, relevance, and content. * * @param {object} result - Layer result { rules (hint objects), metadata } * @returns {string} */ function formatMemoryHints(result) { const lines = ['[MEMORY HINTS]']; for (const hint of result.rules) { const source = hint.source || 'memory'; const relevance = typeof hint.relevance === 'number' ? `${(hint.relevance * 100).toFixed(0)}%` : '?%'; const content = hint.content || ''; lines.push(` [${source}] (relevance: ${relevance}) ${content}`); } return lines.join('\n'); } // --------------------------------------------------------------------------- // DEVMODE Section (DESIGN doc section 13) // --------------------------------------------------------------------------- /** * Format the DEVMODE debug section. * * @param {string} bracket - Current bracket name * @param {number} contextPercent - Remaining context % * @param {object} session - Session state * @param {object} metrics - Pipeline metrics summary * @param {object[]} results - Layer results for domain analysis * @returns {string} */ function formatDevmode(bracket, contextPercent, session, metrics, results) { const lines = [ '[DEVMODE STATUS]', '---', 'SYNAPSE DEVMODE', '', `Bracket: [${bracket}] (${contextPercent.toFixed(1)}% remaining)`, '', ]; // Layers loaded const loaded = Object.entries(metrics.per_layer || {}) .filter(([, v]) => v.status === 'ok'); if (loaded.length > 0) { lines.push('Layers Loaded:'); for (const [name, info] of loaded) { const duration = info.duration != null ? `${info.duration}ms` : '?ms'; lines.push(` [L${info.layer != null ? info.layer : '?'}] ${name.toUpperCase()}: ${info.rules || 0} rules (${duration})`); } lines.push(''); } // Layers skipped const skipped = Object.entries(metrics.per_layer || {}) .filter(([, v]) => v.status === 'skipped'); if (skipped.length > 0) { lines.push('Layers Skipped:'); for (const [name, info] of skipped) { lines.push(` [${name.toUpperCase()}] ${info.reason || 'Unknown reason'}`); } lines.push(''); } // Session info if (session) { lines.push('Session:'); if (session.id || session.sessionId) { lines.push(` UUID: ${session.id || session.sessionId || 'unknown'}`); } if (session.active_agent) { lines.push(` Agent: @${session.active_agent}`); } lines.push(` Prompts: ${session.prompt_count || 0} | Last bracket: ${bracket}`); lines.push(''); } // Pipeline metrics lines.push('Pipeline Metrics:'); lines.push(` Total: ${metrics.total_ms}ms | Layers: ${metrics.layers_loaded}/${metrics.layers_loaded + metrics.layers_skipped + metrics.layers_errored} | Rules: ${metrics.total_rules}`); // Available domains (not loaded) — based on results metadata const loadedDomains = new Set(results.map(r => (r.metadata && r.metadata.source) || '').filter(Boolean)); if (loadedDomains.size > 0) { lines.push(''); lines.push('Loaded Domains:'); for (const domain of loadedDomains) { const result = results.find(r => r.metadata && r.metadata.source === domain); const ruleCount = result ? result.rules.length : 0; lines.push(` [${domain.toUpperCase()}] (${ruleCount} rules)`); } } lines.push('---'); return lines.join('\n'); } // --------------------------------------------------------------------------- // Summary Section // --------------------------------------------------------------------------- /** * Format the LOADED DOMAINS SUMMARY section. * * @param {object[]} results - All layer results * @param {object} metrics - Pipeline metrics summary * @returns {string} */ function formatSummary(results, _metrics) { const lines = ['[LOADED DOMAINS SUMMARY]', ' LOADED DOMAINS:']; for (const result of results) { if (!result || !result.rules || result.rules.length === 0) continue; const meta = result.metadata || {}; const source = (meta.source || meta.domain || 'unknown').toUpperCase(); const reason = meta.activationReason || meta.reason || 'active layer'; lines.push(` [${source}] ${reason} (${result.rules.length} rules)`); } return lines.join('\n'); } // --------------------------------------------------------------------------- // Token Budget Enforcement // --------------------------------------------------------------------------- /** * Enforce a token budget by removing sections from the end. * * Truncation order (last removed first): SUMMARY, KEYWORD, SQUAD, * STAR_COMMANDS, TASK, WORKFLOW. CONSTITUTION and AGENT are never removed. * * @param {string[]} sections - Ordered section strings * @param {string[]} sectionIds - Corresponding section identifiers * @param {number} tokenBudget - Max tokens allowed * @returns {string[]} Filtered sections within budget */ function enforceTokenBudget(sections, sectionIds, tokenBudget) { if (!tokenBudget || tokenBudget <= 0) { return sections; } // Sections that should never be removed const PROTECTED = new Set(['CONTEXT_BRACKET', 'CONSTITUTION', 'AGENT']); // Truncation priority: remove from end first const TRUNCATION_ORDER = [ 'SUMMARY', 'KEYWORD', 'MEMORY_HINTS', 'SQUAD', 'STAR_COMMANDS', 'DEVMODE', 'TASK', 'WORKFLOW', ]; const result = [...sections]; const ids = [...sectionIds]; let totalTokens = estimateTokens(result.join('\n\n')); if (totalTokens <= tokenBudget) { return result; } // Remove sections in truncation order for (const sectionToRemove of TRUNCATION_ORDER) { if (totalTokens <= tokenBudget) { break; } const idx = ids.indexOf(sectionToRemove); if (idx !== -1 && !PROTECTED.has(sectionToRemove)) { result.splice(idx, 1); ids.splice(idx, 1); totalTokens = estimateTokens(result.join('\n\n')); } } return result; } // --------------------------------------------------------------------------- // Main Formatter // --------------------------------------------------------------------------- /** * Section formatter dispatch table. */ const SECTION_FORMATTERS = { CONSTITUTION: formatConstitution, AGENT: formatAgent, WORKFLOW: formatWorkflow, TASK: formatTask, SQUAD: formatSquad, KEYWORD: formatKeyword, MEMORY_HINTS: formatMemoryHints, STAR_COMMANDS: formatStarCommands, }; /** * Format all layer results into the <synapse-rules> XML output. * * @param {object[]} results - Layer results array * @param {string} bracket - Current bracket name * @param {number} contextPercent - Remaining context percentage * @param {object} session - Session state * @param {boolean} devmode - Whether DEVMODE is enabled * @param {object} metrics - Pipeline metrics summary from PipelineMetrics.getSummary() * @param {number} tokenBudget - Max tokens for this bracket * @param {boolean} showHandoffWarning - Whether to include handoff warning * @returns {string} Formatted <synapse-rules> XML string */ function formatSynapseRules(results, bracket, contextPercent, session, devmode, metrics, tokenBudget, showHandoffWarning) { if (!results || results.length === 0) { return ''; } // Categorize results by section const sectionResults = {}; const globalResults = []; for (const result of results) { if (!result || !result.rules || result.rules.length === 0) { continue; } const meta = result.metadata || {}; const layerName = meta.source || ''; const section = LAYER_TO_SECTION[layerName]; if (section === 'CONTEXT_BRACKET') { globalResults.push(result); } else if (section) { sectionResults[section] = result; } else { // Fallback: try to match by layer number const layerNum = meta.layer; if (layerNum === 0) sectionResults['CONSTITUTION'] = result; else if (layerNum === 1) globalResults.push(result); else if (layerNum === 2) sectionResults['AGENT'] = result; else if (layerNum === 3) sectionResults['WORKFLOW'] = result; else if (layerNum === 4) sectionResults['TASK'] = result; else if (layerNum === 5) sectionResults['SQUAD'] = result; else if (layerNum === 6) sectionResults['KEYWORD'] = result; else if (layerNum === 7) sectionResults['STAR_COMMANDS'] = result; } } // Build sections in order const sections = []; const sectionIds = []; // CONTEXT BRACKET — always first sections.push(formatContextBracket(bracket, contextPercent, globalResults)); sectionIds.push('CONTEXT_BRACKET'); // Remaining sections in order (skip CONTEXT_BRACKET since already added) for (const sectionId of SECTION_ORDER) { if (sectionId === 'CONTEXT_BRACKET') continue; if (sectionId === 'DEVMODE') continue; if (sectionId === 'SUMMARY') continue; const result = sectionResults[sectionId]; if (!result) continue; const formatter = SECTION_FORMATTERS[sectionId]; if (formatter) { sections.push(formatter(result)); sectionIds.push(sectionId); } } // Handoff warning (CRITICAL bracket) if (showHandoffWarning) { sections.push('[HANDOFF WARNING]\n Context is nearly exhausted. Consider starting a new session to preserve quality.'); sectionIds.push('HANDOFF_WARNING'); } // DEVMODE section (conditional) if (devmode && metrics) { sections.push(formatDevmode(bracket, contextPercent, session, metrics, results)); sectionIds.push('DEVMODE'); } // SUMMARY — always last if (results.length > 0) { sections.push(formatSummary(results, metrics || {})); sectionIds.push('SUMMARY'); } // Token budget enforcement const finalSections = enforceTokenBudget(sections, sectionIds, tokenBudget); // Wrap in <synapse-rules> tags const body = finalSections.join('\n\n'); return `<synapse-rules>\n\n${body}\n\n</synapse-rules>`; } module.exports = { formatSynapseRules, enforceTokenBudget, estimateTokens, SECTION_ORDER, LAYER_TO_SECTION, };