@neuroverseos/nv-sim 0.1.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.
@@ -0,0 +1,651 @@
1
+ "use strict";
2
+ /**
3
+ * MiroFish Simulation Analyzer
4
+ *
5
+ * THE VIRAL WEDGE: "Explain My MiroFish Simulation"
6
+ *
7
+ * Users paste MiroFish simulation output → Mirotir explains it.
8
+ * This is the feature that gets Mirotir adopted in the MiroFish community.
9
+ *
10
+ * The insight:
11
+ * People run MiroFish simulations and post screenshots saying
12
+ * "Look what the swarm did!" — but they don't know WHY.
13
+ * Mirotir turns spectacle into meaning.
14
+ *
15
+ * Input: MiroFish simulation JSON, logs, or raw output
16
+ * Output: Structured analysis — emergent patterns, dominant strategies,
17
+ * unstable assumptions, coalition detection, projected futures
18
+ *
19
+ * Tagline: "Run MiroFish. Then ask Mirotir what it means."
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.analyzeMiroFishSimulation = analyzeMiroFishSimulation;
23
+ const governance_1 = require("./governance");
24
+ // ============================================
25
+ // SIMULATION PARSER
26
+ // ============================================
27
+ /**
28
+ * Parse raw MiroFish output into structured format.
29
+ * Handles JSON strings, objects, and partial data.
30
+ */
31
+ function parseSimulation(raw) {
32
+ if (typeof raw === "string") {
33
+ try {
34
+ return JSON.parse(raw);
35
+ }
36
+ catch {
37
+ // Try to extract JSON from mixed content (e.g., pasted log output)
38
+ const jsonMatch = raw.match(/\{[\s\S]*\}/);
39
+ if (jsonMatch) {
40
+ try {
41
+ return JSON.parse(jsonMatch[0]);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ }
50
+ return raw;
51
+ }
52
+ // ============================================
53
+ // PATTERN DETECTION
54
+ // ============================================
55
+ /**
56
+ * Detect emergent patterns from simulation steps.
57
+ */
58
+ function detectEmergentPatterns(steps) {
59
+ const patterns = [];
60
+ // Track sentiment trajectories per agent
61
+ const agentSentiments = {};
62
+ for (const step of steps) {
63
+ for (const action of step.agent_actions) {
64
+ if (!agentSentiments[action.agent_id]) {
65
+ agentSentiments[action.agent_id] = [];
66
+ }
67
+ agentSentiments[action.agent_id].push(action.sentiment);
68
+ }
69
+ }
70
+ // Detect herd behavior (agents converging on same sentiment)
71
+ for (let i = 1; i < steps.length; i++) {
72
+ const sentiments = steps[i].agent_actions.map((a) => a.sentiment);
73
+ const avg = sentiments.reduce((s, v) => s + v, 0) / sentiments.length;
74
+ const variance = sentiments.reduce((s, v) => s + (v - avg) ** 2, 0) / sentiments.length;
75
+ if (variance < 0.05 && sentiments.length > 1) {
76
+ patterns.push({
77
+ pattern: "Herd behavior — agents converging on shared sentiment",
78
+ first_observed_step: i,
79
+ strength: variance < 0.02 ? "strong" : "moderate",
80
+ agents_involved: steps[i].agent_actions.map((a) => a.agent_id),
81
+ significance: "When agents stop disagreeing, the system may be approaching consensus or groupthink",
82
+ });
83
+ break; // Only report first occurrence
84
+ }
85
+ }
86
+ // Detect polarization (agents splitting into camps)
87
+ for (let i = 1; i < steps.length; i++) {
88
+ const sentiments = steps[i].agent_actions.map((a) => a.sentiment);
89
+ const positive = sentiments.filter((s) => s > 0.3);
90
+ const negative = sentiments.filter((s) => s < -0.3);
91
+ if (positive.length >= 2 && negative.length >= 2) {
92
+ patterns.push({
93
+ pattern: "Polarization — agents splitting into opposing camps",
94
+ first_observed_step: i,
95
+ strength: "strong",
96
+ agents_involved: steps[i].agent_actions.map((a) => a.agent_id),
97
+ significance: "Polarized systems are harder to steer and more likely to produce extreme outcomes",
98
+ });
99
+ break;
100
+ }
101
+ }
102
+ // Detect sentiment reversal (agent flipping position)
103
+ for (const [agentId, sentiments] of Object.entries(agentSentiments)) {
104
+ for (let i = 1; i < sentiments.length; i++) {
105
+ if (Math.abs(sentiments[i] - sentiments[i - 1]) > 0.6 &&
106
+ Math.sign(sentiments[i]) !== Math.sign(sentiments[i - 1])) {
107
+ patterns.push({
108
+ pattern: `Sentiment reversal — ${agentId} flipped position`,
109
+ first_observed_step: i,
110
+ strength: "strong",
111
+ agents_involved: [agentId],
112
+ significance: "Position reversals indicate either new information or breaking of prior commitments",
113
+ });
114
+ }
115
+ }
116
+ }
117
+ // Detect escalation (overall sentiment getting more extreme)
118
+ if (steps.length >= 3) {
119
+ const earlyAvg = steps[0].agent_actions.reduce((s, a) => s + Math.abs(a.sentiment), 0) /
120
+ steps[0].agent_actions.length;
121
+ const lateAvg = steps[steps.length - 1].agent_actions.reduce((s, a) => s + Math.abs(a.sentiment), 0) / steps[steps.length - 1].agent_actions.length;
122
+ if (lateAvg > earlyAvg * 1.5) {
123
+ patterns.push({
124
+ pattern: "Escalation — positions becoming more extreme over time",
125
+ first_observed_step: Math.floor(steps.length / 2),
126
+ strength: lateAvg > earlyAvg * 2 ? "strong" : "moderate",
127
+ agents_involved: steps[steps.length - 1].agent_actions.map((a) => a.agent_id),
128
+ significance: "Escalating systems are harder to de-escalate and more likely to reach crisis points",
129
+ });
130
+ }
131
+ }
132
+ return patterns;
133
+ }
134
+ /**
135
+ * Identify dominant strategies from agent actions.
136
+ */
137
+ function identifyDominantStrategies(steps) {
138
+ const strategies = [];
139
+ // Aggregate each agent's behavior across all steps
140
+ const agentBehaviors = {};
141
+ for (const step of steps) {
142
+ for (const action of step.agent_actions) {
143
+ if (!agentBehaviors[action.agent_id]) {
144
+ agentBehaviors[action.agent_id] = {
145
+ actions: [],
146
+ avgSentiment: 0,
147
+ avgConfidence: 0,
148
+ };
149
+ }
150
+ agentBehaviors[action.agent_id].actions.push(action.action);
151
+ }
152
+ }
153
+ // Calculate averages
154
+ for (const [agentId, behavior] of Object.entries(agentBehaviors)) {
155
+ const agentActions = steps.flatMap((s) => s.agent_actions.filter((a) => a.agent_id === agentId));
156
+ behavior.avgSentiment =
157
+ agentActions.reduce((s, a) => s + a.sentiment, 0) / agentActions.length;
158
+ behavior.avgConfidence =
159
+ agentActions.reduce((s, a) => s + a.confidence, 0) / agentActions.length;
160
+ // Determine dominant strategy description
161
+ const sentiment = behavior.avgSentiment;
162
+ const confidence = behavior.avgConfidence;
163
+ let strategy;
164
+ let whyItWorks;
165
+ if (confidence > 0.7 && sentiment > 0.3) {
166
+ strategy = "Assertive positive — consistently pushing for favorable outcomes";
167
+ whyItWorks = "High confidence + positive positioning creates momentum and attracts allies";
168
+ }
169
+ else if (confidence > 0.7 && sentiment < -0.3) {
170
+ strategy = "Assertive opposition — consistently blocking unfavorable outcomes";
171
+ whyItWorks = "Strong opposition from a confident actor forces others to negotiate or adapt";
172
+ }
173
+ else if (confidence < 0.4) {
174
+ strategy = "Cautious observation — waiting for clearer signals before committing";
175
+ whyItWorks = "In uncertain environments, preserving optionality is often optimal";
176
+ }
177
+ else if (Math.abs(sentiment) < 0.2) {
178
+ strategy = "Neutral balancing — maintaining equilibrium between factions";
179
+ whyItWorks = "Neutral actors become kingmakers when factions polarize";
180
+ }
181
+ else {
182
+ strategy = "Adaptive positioning — adjusting stance based on evolving dynamics";
183
+ whyItWorks = "Flexibility allows exploitation of emerging opportunities";
184
+ }
185
+ strategies.push({
186
+ agent_id: agentId,
187
+ strategy,
188
+ effectiveness: Number(((Math.abs(sentiment) + confidence) / 2).toFixed(2)),
189
+ why_it_works: whyItWorks,
190
+ });
191
+ }
192
+ // Sort by effectiveness
193
+ return strategies.sort((a, b) => b.effectiveness - a.effectiveness);
194
+ }
195
+ /**
196
+ * Detect coalitions — groups of agents acting in concert.
197
+ */
198
+ function detectCoalitions(steps) {
199
+ const coalitions = [];
200
+ if (steps.length < 2)
201
+ return coalitions;
202
+ // Get final step sentiment per agent
203
+ const finalStep = steps[steps.length - 1];
204
+ const positiveAgents = finalStep.agent_actions
205
+ .filter((a) => a.sentiment > 0.2)
206
+ .map((a) => a.agent_id);
207
+ const negativeAgents = finalStep.agent_actions
208
+ .filter((a) => a.sentiment < -0.2)
209
+ .map((a) => a.agent_id);
210
+ if (positiveAgents.length >= 2) {
211
+ coalitions.push({
212
+ members: positiveAgents,
213
+ basis: "Shared positive sentiment toward the scenario outcome",
214
+ strength: Number((finalStep.agent_actions
215
+ .filter((a) => positiveAgents.includes(a.agent_id))
216
+ .reduce((s, a) => s + a.confidence, 0) / positiveAgents.length).toFixed(2)),
217
+ threat_to: negativeAgents,
218
+ });
219
+ }
220
+ if (negativeAgents.length >= 2) {
221
+ coalitions.push({
222
+ members: negativeAgents,
223
+ basis: "Shared opposition to the scenario outcome",
224
+ strength: Number((finalStep.agent_actions
225
+ .filter((a) => negativeAgents.includes(a.agent_id))
226
+ .reduce((s, a) => s + a.confidence, 0) / negativeAgents.length).toFixed(2)),
227
+ threat_to: positiveAgents,
228
+ });
229
+ }
230
+ return coalitions;
231
+ }
232
+ /**
233
+ * Identify fragile points in the simulation.
234
+ */
235
+ function identifyFragilePoints(steps) {
236
+ const fragile = [];
237
+ // Look for steps where small changes produced large effects
238
+ for (let i = 1; i < steps.length; i++) {
239
+ const prev = steps[i - 1];
240
+ const curr = steps[i];
241
+ for (const action of curr.agent_actions) {
242
+ const prevAction = prev.agent_actions.find((a) => a.agent_id === action.agent_id);
243
+ if (prevAction) {
244
+ const sentimentShift = Math.abs(action.sentiment - prevAction.sentiment);
245
+ if (sentimentShift > 0.5) {
246
+ fragile.push({
247
+ assumption: `${action.agent_id} would maintain their position`,
248
+ fragility: sentimentShift > 0.7 ? "critical" : "high",
249
+ what_breaks: `${action.agent_id} reversed position in step ${i}, suggesting their commitment was conditional on unstated factors`,
250
+ step_observed: i,
251
+ });
252
+ }
253
+ }
254
+ }
255
+ }
256
+ // Check for narrow margins (many agents near 0 sentiment = unstable equilibrium)
257
+ for (let i = 0; i < steps.length; i++) {
258
+ const neutralAgents = steps[i].agent_actions.filter((a) => Math.abs(a.sentiment) < 0.15);
259
+ if (neutralAgents.length > steps[i].agent_actions.length / 2) {
260
+ fragile.push({
261
+ assumption: "System is in stable equilibrium",
262
+ fragility: "high",
263
+ what_breaks: `Step ${i}: ${neutralAgents.length} of ${steps[i].agent_actions.length} agents are near-neutral — a small push in either direction could cascade`,
264
+ step_observed: i,
265
+ });
266
+ break;
267
+ }
268
+ }
269
+ return fragile;
270
+ }
271
+ /**
272
+ * Analyze equilibrium state.
273
+ */
274
+ function analyzeEquilibrium(steps, finalState) {
275
+ if (steps.length < 2) {
276
+ return {
277
+ reached: false,
278
+ stability: 0,
279
+ type: "none",
280
+ description: "Insufficient simulation data to determine equilibrium",
281
+ };
282
+ }
283
+ // Calculate sentiment variance trend
284
+ const variances = steps.map((step) => {
285
+ const sentiments = step.agent_actions.map((a) => a.sentiment);
286
+ const avg = sentiments.reduce((s, v) => s + v, 0) / sentiments.length;
287
+ return (sentiments.reduce((s, v) => s + (v - avg) ** 2, 0) / sentiments.length);
288
+ });
289
+ const varianceTrend = variances[variances.length - 1] - variances[0];
290
+ const lastVariance = variances[variances.length - 1];
291
+ if (finalState.convergence && finalState.stability > 0.7) {
292
+ return {
293
+ reached: true,
294
+ step_reached: steps.length - 1,
295
+ stability: finalState.stability,
296
+ type: "stable",
297
+ description: "System reached stable equilibrium — agents converged on compatible positions. This outcome is likely to persist unless new external shocks are introduced.",
298
+ };
299
+ }
300
+ if (lastVariance < 0.05 && varianceTrend < 0) {
301
+ return {
302
+ reached: true,
303
+ step_reached: steps.length - 1,
304
+ stability: Number((1 - lastVariance * 10).toFixed(2)),
305
+ type: "stable",
306
+ description: "System is converging toward equilibrium — variance is decreasing. Additional steps would likely solidify this state.",
307
+ };
308
+ }
309
+ // Check for oscillation
310
+ if (variances.length >= 4) {
311
+ let oscillations = 0;
312
+ for (let i = 2; i < variances.length; i++) {
313
+ if ((variances[i] - variances[i - 1]) *
314
+ (variances[i - 1] - variances[i - 2]) <
315
+ 0) {
316
+ oscillations++;
317
+ }
318
+ }
319
+ if (oscillations >= variances.length / 2 - 1) {
320
+ return {
321
+ reached: false,
322
+ stability: Number((0.5 - lastVariance).toFixed(2)),
323
+ type: "oscillating",
324
+ description: "System is oscillating — agents are cycling between positions without settling. This suggests competing feedback loops with similar strength.",
325
+ };
326
+ }
327
+ }
328
+ if (varianceTrend > 0.1) {
329
+ return {
330
+ reached: false,
331
+ stability: Number(Math.max(0, 1 - varianceTrend * 5).toFixed(2)),
332
+ type: "diverging",
333
+ description: "System is diverging — positions are becoming more extreme over time. Without intervention, this trajectory leads to polarization or breakdown.",
334
+ };
335
+ }
336
+ return {
337
+ reached: false,
338
+ stability: finalState.stability,
339
+ type: "unstable",
340
+ description: "System has not reached equilibrium. Agent positions remain contested. The outcome is sensitive to initial conditions and external factors.",
341
+ };
342
+ }
343
+ /**
344
+ * Analyze power dynamics — who's driving outcomes.
345
+ */
346
+ function analyzePowerDynamics(steps) {
347
+ const dynamics = [];
348
+ const agentIds = new Set();
349
+ for (const step of steps) {
350
+ for (const action of step.agent_actions) {
351
+ agentIds.add(action.agent_id);
352
+ }
353
+ }
354
+ for (const agentId of agentIds) {
355
+ const actions = steps.flatMap((s) => s.agent_actions.filter((a) => a.agent_id === agentId));
356
+ const avgConfidence = actions.reduce((s, a) => s + a.confidence, 0) / actions.length;
357
+ const avgAbsSentiment = actions.reduce((s, a) => s + Math.abs(a.sentiment), 0) / actions.length;
358
+ const sentimentVariance = (() => {
359
+ const avg = actions.reduce((s, a) => s + a.sentiment, 0) / actions.length;
360
+ return (actions.reduce((s, a) => s + (a.sentiment - avg) ** 2, 0) /
361
+ actions.length);
362
+ })();
363
+ // Determine role
364
+ let role;
365
+ const influence = Number(((avgConfidence * 0.6 + avgAbsSentiment * 0.4)).toFixed(2));
366
+ if (avgConfidence > 0.7 && avgAbsSentiment > 0.5) {
367
+ role = "driver";
368
+ }
369
+ else if (sentimentVariance > 0.2) {
370
+ role = "reactor";
371
+ }
372
+ else if (avgAbsSentiment < 0.15) {
373
+ role = "stabilizer";
374
+ }
375
+ else if (avgConfidence > 0.6 && actions.some((a) => a.sentiment < -0.5)) {
376
+ role = "disruptor";
377
+ }
378
+ else {
379
+ role = "bystander";
380
+ }
381
+ dynamics.push({
382
+ agent_id: agentId,
383
+ influence: Math.min(1, influence),
384
+ role,
385
+ key_actions: actions.slice(-3).map((a) => a.action),
386
+ });
387
+ }
388
+ return dynamics.sort((a, b) => b.influence - a.influence);
389
+ }
390
+ /**
391
+ * Generate a human-readable narrative of what happened in the simulation.
392
+ */
393
+ function generateNarrative(steps, patterns, coalitions, equilibrium, dynamics) {
394
+ const parts = [];
395
+ // Opening
396
+ parts.push(`Over ${steps.length} simulation rounds, ${new Set(steps.flatMap((s) => s.agent_actions.map((a) => a.agent_id))).size} agents interacted.`);
397
+ // Dominant actors
398
+ const drivers = dynamics.filter((d) => d.role === "driver");
399
+ if (drivers.length > 0) {
400
+ parts.push(`${drivers.map((d) => d.agent_id).join(" and ")} emerged as the primary ${drivers.length > 1 ? "drivers" : "driver"} of outcomes.`);
401
+ }
402
+ // Coalitions
403
+ if (coalitions.length > 0) {
404
+ parts.push(`${coalitions.length} coalition${coalitions.length > 1 ? "s" : ""} formed: ${coalitions.map((c) => c.members.join(" + ")).join("; ")}.`);
405
+ }
406
+ // Key patterns
407
+ const strongPatterns = patterns.filter((p) => p.strength === "strong");
408
+ if (strongPatterns.length > 0) {
409
+ parts.push(`Notable dynamics: ${strongPatterns.map((p) => p.pattern.toLowerCase()).join("; ")}.`);
410
+ }
411
+ // Equilibrium
412
+ parts.push(equilibrium.description);
413
+ return parts.join(" ");
414
+ }
415
+ // ============================================
416
+ // MAIN ANALYZER
417
+ // ============================================
418
+ /**
419
+ * Analyze a MiroFish simulation and produce structured insights.
420
+ *
421
+ * This is THE viral feature:
422
+ * Paste simulation → Get explanation
423
+ * "Run MiroFish. Then ask Mirotir what it means."
424
+ */
425
+ async function analyzeMiroFishSimulation(input) {
426
+ const startTime = Date.now();
427
+ const reasoningId = `rsn_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
428
+ // Parse the simulation data
429
+ const simulation = parseSimulation(input.simulation);
430
+ if (!simulation) {
431
+ return {
432
+ status: "error",
433
+ error: {
434
+ code: "INVALID_REQUEST",
435
+ message: "Could not parse MiroFish simulation data. Paste the JSON output from your simulation.",
436
+ detail: "Expected a MiroFish simulation response object with steps and agent_actions",
437
+ },
438
+ reasoning_id: reasoningId,
439
+ timestamp: new Date().toISOString(),
440
+ };
441
+ }
442
+ if (!simulation.steps || simulation.steps.length === 0) {
443
+ return {
444
+ status: "error",
445
+ error: {
446
+ code: "INVALID_REQUEST",
447
+ message: "Simulation has no steps. Run your MiroFish simulation for at least 2 steps before analyzing.",
448
+ },
449
+ reasoning_id: reasoningId,
450
+ timestamp: new Date().toISOString(),
451
+ };
452
+ }
453
+ // Extract all unique agents as stakeholders
454
+ const agentIds = new Set();
455
+ for (const step of simulation.steps) {
456
+ for (const action of step.agent_actions) {
457
+ agentIds.add(action.agent_id);
458
+ }
459
+ }
460
+ const stakeholders = Array.from(agentIds).map((id) => ({
461
+ id,
462
+ disposition: "unknown",
463
+ }));
464
+ // Run all analyses
465
+ const patterns = detectEmergentPatterns(simulation.steps);
466
+ const dominantStrategies = identifyDominantStrategies(simulation.steps);
467
+ const coalitions = detectCoalitions(simulation.steps);
468
+ const fragilePoints = identifyFragilePoints(simulation.steps);
469
+ const equilibrium = analyzeEquilibrium(simulation.steps, simulation.final_state ?? { convergence: false, stability: 0.5 });
470
+ const powerDynamics = analyzePowerDynamics(simulation.steps);
471
+ const narrative = generateNarrative(simulation.steps, patterns, coalitions, equilibrium, powerDynamics);
472
+ // Build simulation insights
473
+ const simulationInsights = {
474
+ emergent_patterns: patterns,
475
+ dominant_strategies: dominantStrategies,
476
+ coalitions,
477
+ fragile_points: fragilePoints,
478
+ equilibrium,
479
+ power_dynamics: powerDynamics,
480
+ narrative,
481
+ };
482
+ // Build reasoning paths from the analysis
483
+ const paths = [];
484
+ // Path based on equilibrium
485
+ if (equilibrium.reached) {
486
+ paths.push({
487
+ id: "path_accept_equilibrium",
488
+ label: "Accept Equilibrium",
489
+ description: "The system has reached a stable state. Work within this new equilibrium rather than fighting it.",
490
+ projected_outcome: "Predictable dynamics but potentially locked into suboptimal arrangements",
491
+ probability: 0.7,
492
+ risk: "low",
493
+ tradeoffs: [
494
+ "Stability vs. potentially better outcomes through disruption",
495
+ "Known dynamics vs. unknown alternatives",
496
+ ],
497
+ });
498
+ }
499
+ else {
500
+ paths.push({
501
+ id: "path_intervene_early",
502
+ label: "Intervene Before Equilibrium",
503
+ description: "The system hasn't settled. This is the window to influence outcomes before positions harden.",
504
+ projected_outcome: "Opportunity to shape the outcome, but intervention may trigger unintended cascades",
505
+ probability: 0.5,
506
+ risk: "moderate",
507
+ tradeoffs: [
508
+ "Influence opportunity vs. cascade risk",
509
+ "Speed vs. understanding",
510
+ ],
511
+ });
512
+ }
513
+ // Path based on coalition dynamics
514
+ if (coalitions.length > 0) {
515
+ const dominantCoalition = [...coalitions].sort((a, b) => b.strength - a.strength)[0];
516
+ paths.push({
517
+ id: "path_align_with_coalition",
518
+ label: `Align With ${dominantCoalition.members.join(" + ")}`,
519
+ description: `The strongest coalition (${dominantCoalition.members.join(", ")}) is forming around: ${dominantCoalition.basis}`,
520
+ projected_outcome: "Shared influence through coalition membership, but constrained by coalition consensus",
521
+ probability: 0.6,
522
+ risk: "moderate",
523
+ tradeoffs: [
524
+ "Coalition power vs. individual autonomy",
525
+ "Shared upside vs. shared downside",
526
+ ],
527
+ benefits_stakeholders: dominantCoalition.members,
528
+ harms_stakeholders: dominantCoalition.threat_to,
529
+ });
530
+ }
531
+ // Path based on disruptors
532
+ const disruptors = powerDynamics.filter((d) => d.role === "disruptor");
533
+ if (disruptors.length > 0) {
534
+ paths.push({
535
+ id: "path_neutralize_disruptors",
536
+ label: "Address Disruptive Agents",
537
+ description: `${disruptors.map((d) => d.agent_id).join(", ")} ${disruptors.length > 1 ? "are" : "is"} destabilizing the system. Addressing ${disruptors.length > 1 ? "them" : "this agent"} could change the trajectory.`,
538
+ projected_outcome: "Reduced volatility but risk of driving disruption underground",
539
+ probability: 0.45,
540
+ risk: "high",
541
+ tradeoffs: [
542
+ "Stability vs. innovation that disruption sometimes enables",
543
+ "Short-term relief vs. addressing root causes",
544
+ ],
545
+ });
546
+ }
547
+ // Build assumption challenges from fragile points
548
+ const challenges = fragilePoints.map((fp) => ({
549
+ assumption: fp.assumption,
550
+ challenge: `This assumption broke at step ${fp.step_observed}: ${fp.what_breaks}`,
551
+ impact_if_wrong: "The simulation trajectory would be fundamentally different under alternative assumptions",
552
+ severity: fp.fragility === "critical" ? "critical" : "high",
553
+ }));
554
+ // Build projected outcomes
555
+ const outcomes = [
556
+ {
557
+ id: "outcome_current_trajectory",
558
+ label: "Current Trajectory",
559
+ conditions: "If the dynamics observed in the simulation continue",
560
+ outcome: equilibrium.description,
561
+ likelihood: 0.5,
562
+ impact: equilibrium.type === "diverging" ? "negative" : "neutral",
563
+ },
564
+ {
565
+ id: "outcome_intervention",
566
+ label: "With Strategic Intervention",
567
+ conditions: "If the key fragile points are addressed proactively",
568
+ outcome: "System could be steered toward a more favorable equilibrium, but requires careful timing",
569
+ likelihood: 0.3,
570
+ impact: "positive",
571
+ },
572
+ ];
573
+ // Build recommendations
574
+ const driverAgents = powerDynamics.filter((d) => d.role === "driver");
575
+ const recommendations = [
576
+ {
577
+ priority: 1,
578
+ action: "Validate the simulation's key assumptions — the fragile points identified suggest the outcome is sensitive to conditions that may not hold",
579
+ rationale: `${fragilePoints.length} fragile points detected. The simulation may be overconfident in its trajectory.`,
580
+ timeframe: "Before acting on these results",
581
+ },
582
+ {
583
+ priority: 2,
584
+ action: `Monitor ${driverAgents.length > 0 ? driverAgents.map((d) => d.agent_id).join(" and ") : "the most influential agents"} — their next moves will determine the trajectory`,
585
+ rationale: "Power dynamics analysis shows these actors are driving outcomes",
586
+ timeframe: "Immediately",
587
+ },
588
+ ];
589
+ if (coalitions.length > 0) {
590
+ recommendations.push({
591
+ priority: 3,
592
+ action: "Map your position relative to the emerging coalitions — are you inside or outside?",
593
+ rationale: "Coalition dynamics will increasingly determine who benefits and who doesn't",
594
+ timeframe: "Within 24 hours",
595
+ });
596
+ }
597
+ // Build the analysis
598
+ const analysis = {
599
+ summary: narrative,
600
+ paths,
601
+ projected_outcomes: outcomes,
602
+ assumption_challenges: challenges,
603
+ recommendations,
604
+ key_uncertainties: [
605
+ "Whether the simulation accurately models real agent incentives",
606
+ "Whether external shocks not modeled could change the trajectory",
607
+ `Whether ${equilibrium.type === "stable" ? "the equilibrium will hold under stress" : "equilibrium will be reached at all"}`,
608
+ ],
609
+ missing_questions: input.question
610
+ ? undefined
611
+ : [
612
+ "What specific decision are you trying to make based on this simulation?",
613
+ "Which agent's perspective are you most interested in?",
614
+ "What would you change if you could re-run the simulation?",
615
+ ],
616
+ };
617
+ // Governance trace
618
+ const governance = {
619
+ trace_id: `gov_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
620
+ constitutional_checks: (0, governance_1.runGovernanceChecks)({
621
+ scenario: `MiroFish simulation analysis: ${simulation.simulation_id ?? "unknown"}`,
622
+ }),
623
+ authority_level: "ANALYSIS_ONLY",
624
+ enforced_constraints: [
625
+ "ANALYSIS_ONLY: Interpreting simulation results, no execution authority",
626
+ "STATELESS: No simulation data stored after response",
627
+ "GOVERNANCE_TRACED: Analysis provenance auditable",
628
+ ],
629
+ timestamp: new Date().toISOString(),
630
+ engine_version: "0.1.0",
631
+ };
632
+ const processingTime = Date.now() - startTime;
633
+ const meta = {
634
+ processing_time_ms: processingTime,
635
+ depth_used: "full",
636
+ paths_explored: paths.length,
637
+ assumptions_challenged: challenges.length,
638
+ timestamp: new Date().toISOString(),
639
+ };
640
+ const reasoning = {
641
+ reasoning_id: reasoningId,
642
+ status: "completed",
643
+ analysis,
644
+ governance,
645
+ meta,
646
+ };
647
+ return {
648
+ reasoning,
649
+ simulation_insights: simulationInsights,
650
+ };
651
+ }