@equilateral_ai/mindmeld 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -10
  2. package/hooks/pre-compact.js +213 -25
  3. package/hooks/session-start.js +636 -42
  4. package/hooks/subagent-start.js +150 -0
  5. package/hooks/subagent-stop.js +184 -0
  6. package/package.json +8 -7
  7. package/scripts/init-project.js +74 -33
  8. package/scripts/mcp-bridge.js +220 -0
  9. package/src/core/CorrelationAnalyzer.js +157 -0
  10. package/src/core/LLMPatternDetector.js +198 -0
  11. package/src/core/RelevanceDetector.js +123 -36
  12. package/src/core/StandardsIngestion.js +119 -18
  13. package/src/handlers/activity/activityGetMe.js +1 -1
  14. package/src/handlers/activity/activityGetTeam.js +100 -55
  15. package/src/handlers/admin/adminSetup.js +216 -0
  16. package/src/handlers/alerts/alertsAcknowledge.js +6 -6
  17. package/src/handlers/alerts/alertsGet.js +11 -11
  18. package/src/handlers/analytics/activitySummaryGet.js +34 -35
  19. package/src/handlers/analytics/coachingGet.js +11 -11
  20. package/src/handlers/analytics/convergenceGet.js +236 -0
  21. package/src/handlers/analytics/developerScoreGet.js +41 -111
  22. package/src/handlers/collaborators/collaboratorInvite.js +1 -1
  23. package/src/handlers/company/companyUsersDelete.js +141 -0
  24. package/src/handlers/company/companyUsersGet.js +90 -0
  25. package/src/handlers/company/companyUsersPost.js +267 -0
  26. package/src/handlers/company/companyUsersPut.js +76 -0
  27. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
  28. package/src/handlers/correlations/correlationsGet.js +8 -8
  29. package/src/handlers/correlations/correlationsProjectGet.js +5 -5
  30. package/src/handlers/enterprise/controlTowerGet.js +224 -0
  31. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
  32. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
  33. package/src/handlers/github/githubConnectionStatus.js +1 -1
  34. package/src/handlers/github/githubDiscoverPatterns.js +4 -2
  35. package/src/handlers/github/githubPatternsReview.js +7 -36
  36. package/src/handlers/health/healthGet.js +55 -0
  37. package/src/handlers/helpers/checkSuperAdmin.js +13 -14
  38. package/src/handlers/helpers/subscriptionTiers.js +27 -27
  39. package/src/handlers/mcp/mcpHandler.js +569 -0
  40. package/src/handlers/mcp/mindmeldMcpHandler.js +689 -0
  41. package/src/handlers/notifications/sendNotification.js +18 -18
  42. package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
  43. package/src/handlers/projects/projectCreate.js +124 -10
  44. package/src/handlers/projects/projectDelete.js +4 -4
  45. package/src/handlers/projects/projectGet.js +8 -8
  46. package/src/handlers/projects/projectUpdate.js +4 -4
  47. package/src/handlers/reports/aiLeverage.js +34 -30
  48. package/src/handlers/reports/engineeringInvestment.js +16 -16
  49. package/src/handlers/reports/riskForecast.js +41 -21
  50. package/src/handlers/reports/standardsRoi.js +101 -9
  51. package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
  52. package/src/handlers/sessions/sessionStandardsPost.js +43 -7
  53. package/src/handlers/standards/discoveriesGet.js +93 -0
  54. package/src/handlers/standards/projectStandardsGet.js +2 -2
  55. package/src/handlers/standards/projectStandardsPut.js +2 -2
  56. package/src/handlers/standards/standardsRelevantPost.js +107 -12
  57. package/src/handlers/standards/standardsTransition.js +112 -15
  58. package/src/handlers/stripe/billingPortalPost.js +1 -1
  59. package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
  60. package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
  61. package/src/handlers/stripe/webhookPost.js +42 -14
  62. package/src/handlers/user/apiTokenCreate.js +71 -0
  63. package/src/handlers/user/apiTokenList.js +64 -0
  64. package/src/handlers/user/userSplashGet.js +90 -73
  65. package/src/handlers/users/cognitoPostConfirmation.js +37 -1
  66. package/src/handlers/users/cognitoPreSignUp.js +114 -0
  67. package/src/handlers/users/userGet.js +12 -8
  68. package/src/handlers/webhooks/githubWebhook.js +117 -125
  69. package/src/index.js +46 -51
@@ -0,0 +1,220 @@
1
+ /**
2
+ * MindMeld MCP Bridge - Stdio-to-HTTP proxy for MCP clients
3
+ *
4
+ * Bridges stdio MCP transport to the remote MindMeld MCP server.
5
+ * For clients that don't support URL transport natively
6
+ * (Cline, Cursor, Windsurf, etc.)
7
+ *
8
+ * Usage:
9
+ * MINDMELD_TOKEN=mm_live_xxx mindmeld mcp
10
+ * mindmeld mcp --token mm_live_xxx
11
+ */
12
+
13
+ const https = require('https');
14
+ const os = require('os');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const MCP_HOST = 'api.mindmeld.dev';
19
+ const MCP_PATH = '/api/mcp/mindmeld';
20
+ const REQUEST_TIMEOUT = 30000;
21
+
22
+ /**
23
+ * Resolve API token from env var, CLI arg, or stored file
24
+ */
25
+ function resolveToken(cliToken) {
26
+ if (process.env.MINDMELD_TOKEN) {
27
+ return process.env.MINDMELD_TOKEN;
28
+ }
29
+ if (cliToken) {
30
+ return cliToken;
31
+ }
32
+ try {
33
+ const tokenPath = path.join(os.homedir(), '.mindmeld', 'api-token');
34
+ return fs.readFileSync(tokenPath, 'utf-8').trim();
35
+ } catch (err) {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Forward a JSON-RPC message to the remote MCP server
42
+ */
43
+ function forwardToServer(message, token) {
44
+ return new Promise((resolve, reject) => {
45
+ const body = JSON.stringify(message);
46
+ const req = https.request({
47
+ method: 'POST',
48
+ hostname: MCP_HOST,
49
+ path: MCP_PATH,
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ 'Accept': 'application/json',
53
+ 'X-MindMeld-Token': token
54
+ },
55
+ timeout: REQUEST_TIMEOUT
56
+ }, (res) => {
57
+ let data = '';
58
+ res.on('data', chunk => data += chunk);
59
+ res.on('end', () => {
60
+ if (res.statusCode === 202 || !data.trim()) {
61
+ resolve(null);
62
+ return;
63
+ }
64
+ try {
65
+ resolve(JSON.parse(data));
66
+ } catch (e) {
67
+ reject(new Error(`Invalid JSON from server: ${data.substring(0, 200)}`));
68
+ }
69
+ });
70
+ });
71
+
72
+ req.on('timeout', () => {
73
+ req.destroy();
74
+ reject(new Error('Request timed out'));
75
+ });
76
+
77
+ req.on('error', (e) => {
78
+ reject(new Error(`Connection failed: ${e.message}`));
79
+ });
80
+
81
+ req.write(body);
82
+ req.end();
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Process a single JSON-RPC message from stdin
88
+ */
89
+ async function processMessage(line, token) {
90
+ let message;
91
+ try {
92
+ message = JSON.parse(line);
93
+ } catch (e) {
94
+ process.stdout.write(JSON.stringify({
95
+ jsonrpc: '2.0',
96
+ error: { code: -32700, message: 'Parse error: invalid JSON' },
97
+ id: null
98
+ }) + '\n');
99
+ return;
100
+ }
101
+
102
+ const isNotification = !('id' in message);
103
+
104
+ try {
105
+ const response = await forwardToServer(message, token);
106
+
107
+ if (response === null || isNotification) {
108
+ return;
109
+ }
110
+
111
+ process.stdout.write(JSON.stringify(response) + '\n');
112
+ } catch (error) {
113
+ process.stderr.write(`[MindMeld] ${error.message}\n`);
114
+
115
+ if (!isNotification && message.id !== undefined) {
116
+ process.stdout.write(JSON.stringify({
117
+ jsonrpc: '2.0',
118
+ error: { code: -32603, message: error.message },
119
+ id: message.id
120
+ }) + '\n');
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Start the stdio bridge — long-lived, runs until stdin closes
127
+ */
128
+ function startBridge(token) {
129
+ process.stderr.write(`[MindMeld] MCP bridge started (pid ${process.pid})\n`);
130
+ process.stderr.write(`[MindMeld] Proxying to https://${MCP_HOST}${MCP_PATH}\n`);
131
+
132
+ let buffer = '';
133
+ let pending = 0;
134
+ let stdinClosed = false;
135
+
136
+ function maybeExit() {
137
+ if (stdinClosed && pending === 0) {
138
+ process.stderr.write('[MindMeld] All requests complete, shutting down\n');
139
+ process.exit(0);
140
+ }
141
+ }
142
+
143
+ function trackMessage(line) {
144
+ pending++;
145
+ processMessage(line, token).finally(() => {
146
+ pending--;
147
+ maybeExit();
148
+ });
149
+ }
150
+
151
+ process.stdin.setEncoding('utf-8');
152
+
153
+ process.stdin.on('data', (chunk) => {
154
+ buffer += chunk;
155
+ const lines = buffer.split('\n');
156
+ buffer = lines.pop();
157
+
158
+ for (const line of lines) {
159
+ const trimmed = line.trim();
160
+ if (!trimmed) continue;
161
+ trackMessage(trimmed);
162
+ }
163
+ });
164
+
165
+ process.stdin.on('end', () => {
166
+ process.stderr.write('[MindMeld] stdin closed\n');
167
+ stdinClosed = true;
168
+ maybeExit();
169
+ });
170
+
171
+ process.stdin.on('error', (err) => {
172
+ process.stderr.write(`[MindMeld] stdin error: ${err.message}\n`);
173
+ process.exit(1);
174
+ });
175
+ }
176
+
177
+ function showMcpHelp() {
178
+ console.log(`
179
+ MindMeld MCP Bridge - Stdio-to-HTTP proxy for MCP clients
180
+
181
+ Usage:
182
+ mindmeld mcp [options]
183
+
184
+ Options:
185
+ --token <token> MindMeld API token (or set MINDMELD_TOKEN env var)
186
+ --help, -h Show this help
187
+
188
+ Token Resolution (in priority order):
189
+ 1. MINDMELD_TOKEN environment variable
190
+ 2. --token CLI argument
191
+ 3. ~/.mindmeld/api-token file
192
+
193
+ Create a token at: https://app.mindmeld.dev/api-tokens
194
+
195
+ Client Configuration:
196
+ Cline / Cursor / Windsurf:
197
+ {
198
+ "mcpServers": {
199
+ "mindmeld": {
200
+ "command": "mindmeld",
201
+ "args": ["mcp"],
202
+ "env": { "MINDMELD_TOKEN": "mm_live_xxx" }
203
+ }
204
+ }
205
+ }
206
+
207
+ Claude Code (use URL transport instead — no bridge needed):
208
+ {
209
+ "mcpServers": {
210
+ "mindmeld": {
211
+ "type": "url",
212
+ "url": "https://api.mindmeld.dev/api/mcp/mindmeld",
213
+ "headers": { "X-MindMeld-Token": "mm_live_xxx" }
214
+ }
215
+ }
216
+ }
217
+ `);
218
+ }
219
+
220
+ module.exports = { startBridge, resolveToken, showMcpHelp };
@@ -144,6 +144,18 @@ class CorrelationAnalyzer {
144
144
  // Calculate aggregate metrics
145
145
  await this.calculateAggregateMetrics(projectId, companyId);
146
146
 
147
+ // Update standards_patterns.correlation with observed effectiveness
148
+ try {
149
+ const updated = await this.updateStandardsCorrelation();
150
+ summary.standardsCorrelationUpdated = updated;
151
+ if (updated > 0) {
152
+ console.log(`[CorrelationAnalyzer] Updated correlation scores for ${updated} standards`);
153
+ }
154
+ } catch (error) {
155
+ console.error('[CorrelationAnalyzer] Could not update standards correlations:', error.message);
156
+ summary.errors.push({ step: 'updateStandardsCorrelation', error: error.message });
157
+ }
158
+
147
159
  summary.completedAt = new Date().toISOString();
148
160
  console.log(`[CorrelationAnalyzer] Analysis complete: ${summary.correlationsCreated} correlations, ${summary.uncorrelatedSessions} uncorrelated`);
149
161
 
@@ -763,6 +775,151 @@ class CorrelationAnalyzer {
763
775
  };
764
776
  }
765
777
 
778
+ /**
779
+ * Update standards_patterns.correlation with observed effectiveness
780
+ *
781
+ * Computes per-standard effectiveness by joining session_standards
782
+ * (which standards were shown) with session_correlations (which
783
+ * sessions produced commits). Standards shown in sessions that
784
+ * produce commits get higher correlation scores.
785
+ *
786
+ * This directly influences relevance scoring since
787
+ * standardsRelevantPost.js uses correlation * 40 as the base score.
788
+ *
789
+ * @returns {Promise<number>} Number of standards updated
790
+ */
791
+ async updateStandardsCorrelation() {
792
+ const query = `
793
+ UPDATE rapport.standards_patterns sp SET
794
+ correlation = LEAST(sub.effectiveness, 1.00),
795
+ last_updated = NOW()
796
+ FROM (
797
+ SELECT ss.standard_id,
798
+ COUNT(*) FILTER (WHERE sc.has_commits = true)::decimal / COUNT(*) as effectiveness
799
+ FROM rapport.session_standards ss
800
+ JOIN rapport.session_correlations sc ON sc.session_id = ss.session_id
801
+ WHERE sc.analyzed_at > NOW() - INTERVAL '30 days'
802
+ GROUP BY ss.standard_id
803
+ HAVING COUNT(*) >= 5
804
+ ) sub
805
+ WHERE sp.pattern_id = sub.standard_id
806
+ `;
807
+
808
+ const result = await executeQuery(query);
809
+ return result.rowCount || 0;
810
+ }
811
+
812
+ // ─── Agent Execution Correlation ──────────────────────────────
813
+
814
+ /**
815
+ * Analyze correlations for agent execution patterns.
816
+ * Instead of session → git commit, this correlates:
817
+ * - Agent decision → task outcome success rate
818
+ * - Recovery effectiveness (did failover agents succeed?)
819
+ * - Tier accuracy (tier matches actual consequence?)
820
+ * - Cross-workflow pattern reuse
821
+ *
822
+ * @param {Object} options - Analysis options
823
+ * @param {string} options.projectId - Project filter
824
+ * @param {number} options.lookbackDays - Days to analyze (default: 30)
825
+ * @returns {Promise<Object>} Agent execution correlation summary
826
+ */
827
+ async analyzeAgentExecutionCorrelations(options = {}) {
828
+ const lookbackDays = options.lookbackDays || 30;
829
+
830
+ const summary = {
831
+ startedAt: new Date().toISOString(),
832
+ agentsAnalyzed: 0,
833
+ correlationsCreated: 0,
834
+ patterns: [],
835
+ errors: []
836
+ };
837
+
838
+ try {
839
+ // Get agent execution patterns from pattern_usage
840
+ const agentPatterns = await executeQuery(`
841
+ SELECT
842
+ p.element,
843
+ p.category,
844
+ COUNT(pu.*) AS total_uses,
845
+ COUNT(pu.*) FILTER (WHERE pu.success = true) AS successful_uses,
846
+ COUNT(DISTINCT pu.email_address) AS unique_agents,
847
+ AVG(CASE WHEN pu.success THEN 1.0 ELSE 0.0 END) AS success_rate,
848
+ MAX(pu.used_at) AS last_used
849
+ FROM rapport.patterns p
850
+ JOIN rapport.pattern_usage pu ON pu.pattern_id = p.pattern_id
851
+ WHERE p.pattern_data->>'source' = 'agent_execution'
852
+ AND pu.used_at > NOW() - INTERVAL '${lookbackDays} days'
853
+ ${options.projectId ? `AND p.project_id = '${options.projectId}'` : ''}
854
+ GROUP BY p.element, p.category
855
+ HAVING COUNT(pu.*) >= 3
856
+ ORDER BY success_rate ASC
857
+ `);
858
+
859
+ if (agentPatterns?.rows) {
860
+ for (const pattern of agentPatterns.rows) {
861
+ summary.agentsAnalyzed++;
862
+ summary.patterns.push({
863
+ element: pattern.element,
864
+ category: pattern.category,
865
+ totalUses: parseInt(pattern.total_uses),
866
+ successRate: parseFloat(pattern.success_rate),
867
+ uniqueAgents: parseInt(pattern.unique_agents),
868
+ lastUsed: pattern.last_used
869
+ });
870
+ }
871
+ }
872
+
873
+ // Update pattern correlations based on agent execution data
874
+ const updated = await this.updateAgentPatternCorrelations();
875
+ summary.correlationsCreated = updated;
876
+
877
+ } catch (error) {
878
+ summary.errors.push(error.message);
879
+ }
880
+
881
+ summary.completedAt = new Date().toISOString();
882
+ return summary;
883
+ }
884
+
885
+ /**
886
+ * Update pattern correlations for agent execution patterns.
887
+ * Uses agent task success as the "proof" (instead of git commits for code sessions).
888
+ *
889
+ * @returns {Promise<number>} Number of patterns updated
890
+ */
891
+ async updateAgentPatternCorrelations() {
892
+ const query = `
893
+ UPDATE rapport.patterns p SET
894
+ handoff_count = sub.total_uses,
895
+ successful_handoffs = sub.successful_uses,
896
+ failed_handoffs = sub.failed_uses,
897
+ last_used = sub.last_used
898
+ FROM (
899
+ SELECT
900
+ pu.pattern_id,
901
+ COUNT(*) AS total_uses,
902
+ COUNT(*) FILTER (WHERE pu.success = true) AS successful_uses,
903
+ COUNT(*) FILTER (WHERE pu.success = false) AS failed_uses,
904
+ MAX(pu.used_at) AS last_used
905
+ FROM rapport.pattern_usage pu
906
+ JOIN rapport.patterns pat ON pat.pattern_id = pu.pattern_id
907
+ WHERE pat.pattern_data->>'source' = 'agent_execution'
908
+ AND pu.used_at > NOW() - INTERVAL '30 days'
909
+ GROUP BY pu.pattern_id
910
+ ) sub
911
+ WHERE p.pattern_id = sub.pattern_id
912
+ `;
913
+
914
+ try {
915
+ const result = await executeQuery(query);
916
+ return result.rowCount || 0;
917
+ } catch (error) {
918
+ console.error('[CorrelationAnalyzer] Agent correlation update failed:', error.message);
919
+ return 0;
920
+ }
921
+ }
922
+
766
923
  /**
767
924
  * Get configuration (for debugging/admin)
768
925
  */
@@ -103,6 +103,204 @@ class LLMPatternDetector {
103
103
  }
104
104
  }
105
105
 
106
+ /**
107
+ * Analyze agent execution logs for patterns in decision-making,
108
+ * routing, failure recovery, and consequence tier accuracy.
109
+ *
110
+ * This is the agent-execution counterpart to analyzeSessionTranscript().
111
+ * Called by AgentExecutionHarvester after workflow completion.
112
+ *
113
+ * @param {Object} executionLog - Structured execution data from EA
114
+ * @param {Object} executionLog.workflowName - Workflow that was executed
115
+ * @param {Object[]} executionLog.steps - Array of step results
116
+ * @param {number} executionLog.duration - Total workflow duration
117
+ * @param {string[]} executionLog.agentsUsed - List of agents involved
118
+ * @param {Object} context - Additional context
119
+ * @returns {Promise<Object>} Detected patterns and analysis
120
+ */
121
+ async analyzeAgentExecution(executionLog, context = {}) {
122
+ if (!this.isAvailable()) {
123
+ return this.fallbackAgentAnalysis(executionLog);
124
+ }
125
+
126
+ // Build execution-specific prompts
127
+ const systemPrompt = this.buildAgentExecutionSystemPrompt(context);
128
+ const userPrompt = this.buildAgentExecutionUserPrompt(executionLog, context);
129
+
130
+ try {
131
+ const requestBody = {
132
+ anthropic_version: 'bedrock-2023-05-31',
133
+ max_tokens: this.config.maxTokens,
134
+ system: systemPrompt,
135
+ messages: [
136
+ { role: 'user', content: userPrompt }
137
+ ]
138
+ };
139
+
140
+ const command = new InvokeModelCommand({
141
+ modelId: this.config.model,
142
+ contentType: 'application/json',
143
+ accept: 'application/json',
144
+ body: JSON.stringify(requestBody)
145
+ });
146
+
147
+ const response = await this.client.send(command);
148
+ const responseBody = JSON.parse(new TextDecoder().decode(response.body));
149
+ const content = responseBody.content?.[0]?.text || '';
150
+
151
+ return this.parseResponse(content);
152
+ } catch (error) {
153
+ console.error('[LLMPatternDetector] Agent execution analysis error:', error.message);
154
+ return this.fallbackAgentAnalysis(executionLog);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Build system prompt for agent execution analysis
160
+ */
161
+ buildAgentExecutionSystemPrompt(context) {
162
+ return `You are an expert multi-agent governance analyst for the Equilateral platform.
163
+
164
+ Your task is to analyze agent execution logs and extract:
165
+ 1. **Agent Selection Strategies**: Which agents were chosen for which tasks and why
166
+ 2. **Failure Recovery Patterns**: Which fallbacks worked, which didn't, and why
167
+ 3. **Consequence Tier Accuracy**: Was the assigned tier appropriate for what actually happened?
168
+ 4. **Standards Compliance Patterns**: Which standards consistently apply to which workflows
169
+ 5. **Routing Efficiency**: Could different agent selections have improved outcomes?
170
+
171
+ Key governance concepts:
172
+ - Consequence Tiers: T1 (read-only), T2 (reversible write), T3 (irreversible mutation), T4 (external side effect)
173
+ - Agent Classifications: REWARD (high performer), REMEDIATE (needs improvement), REPLACE (should be swapped)
174
+ - Agent Maturity: provisional (new) → validated (confirmed) → reinforced (proven)
175
+ - DecisionFrame: Carries selection reasoning, confidence, fallbacks, violations
176
+
177
+ Respond ONLY with valid JSON in this exact format:
178
+ {
179
+ "patterns": [
180
+ {
181
+ "element": "Pattern name",
182
+ "type": "agent_selection|failure_recovery|tier_accuracy|compliance|routing",
183
+ "intent": "What this pattern means for governance",
184
+ "evidence": "Data from the execution log",
185
+ "confidence": 0.85,
186
+ "severity": "info|warning|critical",
187
+ "category": "multi-agent-orchestration"
188
+ }
189
+ ],
190
+ "summary": "Brief summary of execution patterns detected",
191
+ "recommendations": ["Governance improvements"],
192
+ "violations": [
193
+ {
194
+ "standard": "Which governance principle was violated",
195
+ "description": "What happened",
196
+ "fix": "How to improve"
197
+ }
198
+ ]
199
+ }`;
200
+ }
201
+
202
+ /**
203
+ * Build user prompt for agent execution analysis
204
+ */
205
+ buildAgentExecutionUserPrompt(executionLog, context) {
206
+ const maxLength = 40000;
207
+ const logStr = JSON.stringify(executionLog, null, 2);
208
+ const truncated = logStr.length > maxLength
209
+ ? logStr.substring(0, maxLength) + '\n\n[... execution log truncated ...]'
210
+ : logStr;
211
+
212
+ const workflowName = executionLog.workflowName || 'Unknown';
213
+ const agentCount = executionLog.agentsUsed?.length || 0;
214
+
215
+ return `Analyze this multi-agent workflow execution for governance patterns:
216
+
217
+ ---
218
+ WORKFLOW: ${workflowName}
219
+ AGENTS USED: ${agentCount}
220
+ DURATION: ${executionLog.duration || 'Unknown'}ms
221
+ ---
222
+
223
+ EXECUTION LOG:
224
+ ${truncated}
225
+
226
+ ---
227
+
228
+ Extract all agent selection strategies, failure recovery patterns, consequence tier accuracy issues, and governance improvements. Focus on:
229
+ 1. Were agents selected optimally for each step?
230
+ 2. Did failovers work correctly when needed?
231
+ 3. Were consequence tiers accurate for the actual operations performed?
232
+ 4. Were any governance constraints violated?
233
+ 5. Are there patterns that should become standards?
234
+
235
+ Respond with JSON only.`;
236
+ }
237
+
238
+ /**
239
+ * Fallback analysis for agent execution when LLM unavailable
240
+ */
241
+ fallbackAgentAnalysis(executionLog) {
242
+ const patterns = [];
243
+ const steps = executionLog.steps || [];
244
+
245
+ // Detect basic patterns from step data
246
+ const failedSteps = steps.filter(s => !s.success);
247
+ const failoverSteps = steps.filter(s => s.failoverUsed);
248
+
249
+ if (failedSteps.length > 0) {
250
+ patterns.push({
251
+ element: `Workflow failure rate: ${failedSteps.length}/${steps.length}`,
252
+ type: 'failure_recovery',
253
+ intent: 'Track workflow reliability',
254
+ confidence: 1.0,
255
+ severity: failedSteps.length > steps.length / 2 ? 'critical' : 'warning',
256
+ category: 'multi-agent-orchestration'
257
+ });
258
+ }
259
+
260
+ if (failoverSteps.length > 0) {
261
+ patterns.push({
262
+ element: `Failover usage: ${failoverSteps.length} steps required failover`,
263
+ type: 'failure_recovery',
264
+ intent: 'Track agent reliability requiring fallback',
265
+ confidence: 1.0,
266
+ severity: 'info',
267
+ category: 'multi-agent-orchestration'
268
+ });
269
+ }
270
+
271
+ // Detect repeated agent usage (specialization pattern)
272
+ const agentCounts = {};
273
+ for (const step of steps) {
274
+ const agent = step.agent || step.event?.agent;
275
+ if (agent) {
276
+ agentCounts[agent] = (agentCounts[agent] || 0) + 1;
277
+ }
278
+ }
279
+
280
+ for (const [agent, count] of Object.entries(agentCounts)) {
281
+ if (count >= 3) {
282
+ patterns.push({
283
+ element: `Agent specialization: ${agent} used ${count} times`,
284
+ type: 'agent_selection',
285
+ intent: 'Detect heavily-used agents for optimization',
286
+ confidence: 0.8,
287
+ severity: 'info',
288
+ category: 'multi-agent-orchestration'
289
+ });
290
+ }
291
+ }
292
+
293
+ return {
294
+ success: true,
295
+ source: 'fallback',
296
+ timestamp: new Date().toISOString(),
297
+ patterns,
298
+ summary: `Fallback analysis: ${steps.length} steps, ${failedSteps.length} failures, ${failoverSteps.length} failovers`,
299
+ recommendations: [],
300
+ violations: []
301
+ };
302
+ }
303
+
106
304
  /**
107
305
  * Call AWS Bedrock (Claude) for pattern analysis
108
306
  */