@neuroverseos/nv-sim 0.1.4 → 0.1.7
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.
- package/README.md +260 -6
- package/dist/adapters/mirofish.js +461 -0
- package/dist/adapters/scienceclaw.js +750 -0
- package/dist/assets/index-CHmUN8s0.js +532 -0
- package/dist/assets/index-DWgMnB7I.css +1 -0
- package/dist/assets/{reportEngine-BfteK4MN.js → reportEngine-BVdQ2_nW.js} +1 -1
- package/dist/components/ConstraintsPanel.js +11 -0
- package/dist/components/StakeholderBuilder.js +32 -0
- package/dist/components/ui/badge.js +24 -0
- package/dist/components/ui/button.js +70 -0
- package/dist/components/ui/card.js +57 -0
- package/dist/components/ui/input.js +44 -0
- package/dist/components/ui/label.js +45 -0
- package/dist/components/ui/select.js +70 -0
- package/dist/engine/aiProvider.js +427 -2
- package/dist/engine/auditTrace.js +352 -0
- package/dist/engine/behavioralAnalysis.js +605 -0
- package/dist/engine/cli.js +1087 -13
- package/dist/engine/dynamicsGovernance.js +588 -0
- package/dist/engine/fullGovernedLoop.js +367 -0
- package/dist/engine/governedSimulation.js +77 -6
- package/dist/engine/index.js +41 -1
- package/dist/engine/liveVisualizer.js +1961 -197
- package/dist/engine/metrics/science.metrics.js +335 -0
- package/dist/engine/policyEnforcement.js +1611 -0
- package/dist/engine/policyEngine.js +799 -0
- package/dist/engine/primeRadiant.js +540 -0
- package/dist/engine/scenarioComparison.js +463 -0
- package/dist/engine/swarmSimulation.js +54 -1
- package/dist/engine/worldComparison.js +164 -0
- package/dist/engine/worldStorage.js +232 -0
- package/dist/index.html +2 -2
- package/dist/lib/reasoningEngine.js +290 -0
- package/dist/lib/simulationAdapter.js +686 -0
- package/dist/lib/swarmParser.js +291 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/utils.js +8 -0
- package/dist/runtime/govern.js +473 -0
- package/dist/runtime/index.js +75 -0
- package/dist/runtime/types.js +11 -0
- package/package.json +5 -2
- package/dist/assets/index-DHKd4rcV.js +0 -338
- package/dist/assets/index-SyyA3z3U.css +0 -1
- package/dist/assets/swarmSimulation-DHDqjfMa.js +0 -1
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Scenario Comparison Runner — N Scenarios × M Policies
|
|
4
|
+
*
|
|
5
|
+
* The decision intelligence engine. This is MODE 3 of the Prime Radiant.
|
|
6
|
+
*
|
|
7
|
+
* Instead of:
|
|
8
|
+
* "Here's what happened"
|
|
9
|
+
* We produce:
|
|
10
|
+
* "Here's what you should do"
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const result = await runScenarioMatrix({
|
|
14
|
+
* scenario: "University disciplinary crisis",
|
|
15
|
+
* stakeholders: [...],
|
|
16
|
+
* paths: [...],
|
|
17
|
+
* policyOptions: [
|
|
18
|
+
* { id: "revoke", label: "Revoke Decision", policyText: "..." },
|
|
19
|
+
* { id: "partial", label: "Partial Apology", policyText: "..." },
|
|
20
|
+
* { id: "maintain", label: "Maintain Decision", policyText: "..." },
|
|
21
|
+
* ],
|
|
22
|
+
* evaluationMetrics: [
|
|
23
|
+
* { id: "trust", label: "Public Trust", weight: 0.3 },
|
|
24
|
+
* { id: "polarization", label: "Polarization Index", weight: 0.25, lowerIsBetter: true },
|
|
25
|
+
* ...
|
|
26
|
+
* ],
|
|
27
|
+
* })
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.DEFAULT_METRICS = void 0;
|
|
31
|
+
exports.runScenarioMatrix = runScenarioMatrix;
|
|
32
|
+
const fullGovernedLoop_1 = require("./fullGovernedLoop");
|
|
33
|
+
// ============================================
|
|
34
|
+
// DEFAULT METRICS
|
|
35
|
+
// ============================================
|
|
36
|
+
exports.DEFAULT_METRICS = [
|
|
37
|
+
{
|
|
38
|
+
id: "trust",
|
|
39
|
+
label: "Public Trust",
|
|
40
|
+
weight: 0.25,
|
|
41
|
+
extractor: (r) => r.finalState.trust,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "polarization",
|
|
45
|
+
label: "Polarization Index",
|
|
46
|
+
weight: 0.20,
|
|
47
|
+
lowerIsBetter: true,
|
|
48
|
+
extractor: (r) => r.finalState.polarization,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "outrage",
|
|
52
|
+
label: "Outrage Level",
|
|
53
|
+
weight: 0.15,
|
|
54
|
+
lowerIsBetter: true,
|
|
55
|
+
extractor: (r) => r.finalState.outrage,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "cascade_risk",
|
|
59
|
+
label: "Cascade Risk",
|
|
60
|
+
weight: 0.15,
|
|
61
|
+
lowerIsBetter: true,
|
|
62
|
+
extractor: (r) => r.finalState.cascadeRisk,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "system_health",
|
|
66
|
+
label: "System Health",
|
|
67
|
+
weight: 0.15,
|
|
68
|
+
extractor: (r) => r.systemHealthScore / 100,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "governance_effectiveness",
|
|
72
|
+
label: "Governance Effectiveness",
|
|
73
|
+
weight: 0.10,
|
|
74
|
+
extractor: (r) => {
|
|
75
|
+
const blocked = r.actionGovernance.blocked;
|
|
76
|
+
const total = r.actionGovernance.totalEvaluations;
|
|
77
|
+
const blockRate = total > 0 ? blocked / total : 0;
|
|
78
|
+
// Sweet spot: some blocking is good, too much is bad
|
|
79
|
+
if (blockRate < 0.1)
|
|
80
|
+
return 0.3; // too lenient
|
|
81
|
+
if (blockRate > 0.7)
|
|
82
|
+
return 0.2; // over-constrained
|
|
83
|
+
return 0.7 + (1 - Math.abs(blockRate - 0.3) * 3) * 0.3;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
// ============================================
|
|
88
|
+
// METRIC EXTRACTION
|
|
89
|
+
// ============================================
|
|
90
|
+
function extractMetric(metric, result) {
|
|
91
|
+
if (metric.extractor) {
|
|
92
|
+
return metric.extractor(result);
|
|
93
|
+
}
|
|
94
|
+
// Default extractors based on metric ID
|
|
95
|
+
switch (metric.id) {
|
|
96
|
+
case "trust": return result.finalState.trust;
|
|
97
|
+
case "polarization": return result.finalState.polarization;
|
|
98
|
+
case "outrage": return result.finalState.outrage;
|
|
99
|
+
case "cascade_risk": return result.finalState.cascadeRisk;
|
|
100
|
+
case "system_health": return result.systemHealthScore / 100;
|
|
101
|
+
case "amplification": return result.finalState.amplification;
|
|
102
|
+
default: return 0.5;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeScore(value, lowerIsBetter) {
|
|
106
|
+
if (lowerIsBetter) {
|
|
107
|
+
return Math.max(0, Math.min(1, 1 - value));
|
|
108
|
+
}
|
|
109
|
+
return Math.max(0, Math.min(1, value));
|
|
110
|
+
}
|
|
111
|
+
function rateScore(normalized) {
|
|
112
|
+
if (normalized >= 0.8)
|
|
113
|
+
return "excellent";
|
|
114
|
+
if (normalized >= 0.6)
|
|
115
|
+
return "good";
|
|
116
|
+
if (normalized >= 0.4)
|
|
117
|
+
return "moderate";
|
|
118
|
+
if (normalized >= 0.2)
|
|
119
|
+
return "poor";
|
|
120
|
+
return "critical";
|
|
121
|
+
}
|
|
122
|
+
function formatMetricValue(value, metricId) {
|
|
123
|
+
if (metricId === "system_health")
|
|
124
|
+
return `${(value * 100).toFixed(0)}/100`;
|
|
125
|
+
if (metricId.includes("risk") || metricId.includes("probability"))
|
|
126
|
+
return `${(value * 100).toFixed(0)}%`;
|
|
127
|
+
return value.toFixed(2);
|
|
128
|
+
}
|
|
129
|
+
// ============================================
|
|
130
|
+
// MAIN: runScenarioMatrix()
|
|
131
|
+
// ============================================
|
|
132
|
+
async function runScenarioMatrix(config) {
|
|
133
|
+
const metrics = config.evaluationMetrics.length > 0
|
|
134
|
+
? config.evaluationMetrics
|
|
135
|
+
: exports.DEFAULT_METRICS;
|
|
136
|
+
const optionResults = [];
|
|
137
|
+
// --- Run simulation for each policy option ---
|
|
138
|
+
for (const option of config.policyOptions) {
|
|
139
|
+
config.onProgress?.(option.id, "simulating");
|
|
140
|
+
const worldDef = option.worldDef ?? config.defaultWorldDef;
|
|
141
|
+
const simulation = await (0, fullGovernedLoop_1.runFullGovernedSimulation)({
|
|
142
|
+
scenario: config.scenario,
|
|
143
|
+
stakeholders: config.stakeholders,
|
|
144
|
+
paths: config.paths,
|
|
145
|
+
swarmConfig: config.swarmConfig,
|
|
146
|
+
policyText: option.policyText,
|
|
147
|
+
worldDef,
|
|
148
|
+
narrativeEvents: config.narrativeEvents,
|
|
149
|
+
agentTypes: config.agentTypes,
|
|
150
|
+
});
|
|
151
|
+
config.onProgress?.(option.id, "evaluating");
|
|
152
|
+
// Score each metric
|
|
153
|
+
const metricScores = metrics.map((metric) => {
|
|
154
|
+
const value = extractMetric(metric, simulation);
|
|
155
|
+
const normalized = normalizeScore(value, metric.lowerIsBetter ?? false);
|
|
156
|
+
const weighted = normalized * metric.weight;
|
|
157
|
+
return {
|
|
158
|
+
metricId: metric.id,
|
|
159
|
+
metricLabel: metric.label,
|
|
160
|
+
value: Number(value.toFixed(3)),
|
|
161
|
+
normalizedScore: Number(normalized.toFixed(3)),
|
|
162
|
+
weightedScore: Number(weighted.toFixed(3)),
|
|
163
|
+
display: formatMetricValue(value, metric.id),
|
|
164
|
+
rating: rateScore(normalized),
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
const overallScore = metricScores.reduce((s, m) => s + m.weightedScore, 0);
|
|
168
|
+
// Extract outcomes
|
|
169
|
+
const outcomes = {
|
|
170
|
+
finalTrust: Number(simulation.finalState.trust.toFixed(3)),
|
|
171
|
+
finalPolarization: Number(simulation.finalState.polarization.toFixed(3)),
|
|
172
|
+
finalOutrage: Number(simulation.finalState.outrage.toFixed(3)),
|
|
173
|
+
finalCascadeRisk: Number(simulation.finalState.cascadeRisk.toFixed(3)),
|
|
174
|
+
peakOutrage: simulation.dynamicsGovernance.peakOutrage,
|
|
175
|
+
totalInterventions: simulation.dynamicsGovernance.totalInterventions,
|
|
176
|
+
actionsBlocked: simulation.actionGovernance.blocked,
|
|
177
|
+
systemHealth: simulation.systemHealthScore,
|
|
178
|
+
trajectory: simulation.trajectory,
|
|
179
|
+
cascadeBreakerFired: simulation.dynamicsGovernance.cascadeBreakers > 0,
|
|
180
|
+
coolingActivated: simulation.dynamicsGovernance.coolingPeriods > 0,
|
|
181
|
+
};
|
|
182
|
+
optionResults.push({
|
|
183
|
+
optionId: option.id,
|
|
184
|
+
optionLabel: option.label,
|
|
185
|
+
simulation,
|
|
186
|
+
metricScores,
|
|
187
|
+
overallScore: Number(overallScore.toFixed(3)),
|
|
188
|
+
outcomes,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// --- Build comparison matrix ---
|
|
192
|
+
const sortedResults = [...optionResults].sort((a, b) => b.overallScore - a.overallScore);
|
|
193
|
+
const comparison = buildComparisonMatrix(optionResults, metrics);
|
|
194
|
+
const rankings = buildRankings(sortedResults);
|
|
195
|
+
const riskAssessment = buildRiskAssessment(optionResults);
|
|
196
|
+
const recommendation = buildRecommendation(sortedResults, riskAssessment, config.policyOptions);
|
|
197
|
+
const decisionBrief = buildDecisionBrief(sortedResults, recommendation, riskAssessment);
|
|
198
|
+
return {
|
|
199
|
+
scenario: config.scenario,
|
|
200
|
+
optionResults,
|
|
201
|
+
comparison,
|
|
202
|
+
recommendation,
|
|
203
|
+
rankings,
|
|
204
|
+
riskAssessment,
|
|
205
|
+
decisionBrief,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// ============================================
|
|
209
|
+
// COMPARISON MATRIX BUILDER
|
|
210
|
+
// ============================================
|
|
211
|
+
function buildComparisonMatrix(results, metrics) {
|
|
212
|
+
const metricIds = metrics.map((m) => m.id);
|
|
213
|
+
// Find best/worst per metric
|
|
214
|
+
const bestPerMetric = {};
|
|
215
|
+
const worstPerMetric = {};
|
|
216
|
+
for (const metric of metrics) {
|
|
217
|
+
const values = results.map((r) => {
|
|
218
|
+
const score = r.metricScores.find((s) => s.metricId === metric.id);
|
|
219
|
+
return score?.normalizedScore ?? 0;
|
|
220
|
+
});
|
|
221
|
+
bestPerMetric[metric.id] = Math.max(...values);
|
|
222
|
+
worstPerMetric[metric.id] = Math.min(...values);
|
|
223
|
+
}
|
|
224
|
+
const sorted = [...results].sort((a, b) => b.overallScore - a.overallScore);
|
|
225
|
+
const rows = sorted.map((result, idx) => ({
|
|
226
|
+
optionId: result.optionId,
|
|
227
|
+
optionLabel: result.optionLabel,
|
|
228
|
+
values: metrics.map((metric) => {
|
|
229
|
+
const score = result.metricScores.find((s) => s.metricId === metric.id);
|
|
230
|
+
return {
|
|
231
|
+
metricId: metric.id,
|
|
232
|
+
value: score.value,
|
|
233
|
+
display: score.display,
|
|
234
|
+
isBest: score.normalizedScore === bestPerMetric[metric.id],
|
|
235
|
+
isWorst: score.normalizedScore === worstPerMetric[metric.id],
|
|
236
|
+
};
|
|
237
|
+
}),
|
|
238
|
+
overallScore: result.overallScore,
|
|
239
|
+
rank: idx + 1,
|
|
240
|
+
}));
|
|
241
|
+
return {
|
|
242
|
+
metrics: metricIds,
|
|
243
|
+
rows,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// ============================================
|
|
247
|
+
// RANKINGS
|
|
248
|
+
// ============================================
|
|
249
|
+
function buildRankings(sorted) {
|
|
250
|
+
return sorted.map((result, idx) => {
|
|
251
|
+
let verdict;
|
|
252
|
+
if (idx === 0)
|
|
253
|
+
verdict = "BEST";
|
|
254
|
+
else if (result.overallScore > 0.5)
|
|
255
|
+
verdict = "VIABLE";
|
|
256
|
+
else if (result.overallScore > 0.3)
|
|
257
|
+
verdict = "RISKY";
|
|
258
|
+
else
|
|
259
|
+
verdict = "AVOID";
|
|
260
|
+
const oneLiner = buildOneLiner(result, idx);
|
|
261
|
+
return {
|
|
262
|
+
rank: idx + 1,
|
|
263
|
+
optionId: result.optionId,
|
|
264
|
+
optionLabel: result.optionLabel,
|
|
265
|
+
overallScore: result.overallScore,
|
|
266
|
+
verdict,
|
|
267
|
+
oneLiner,
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function buildOneLiner(result, rank) {
|
|
272
|
+
const outcomes = result.outcomes;
|
|
273
|
+
if (outcomes.cascadeBreakerFired) {
|
|
274
|
+
return "Required emergency circuit breaker — high risk scenario.";
|
|
275
|
+
}
|
|
276
|
+
if (outcomes.finalTrust > 0.6 && outcomes.finalPolarization < 0.3) {
|
|
277
|
+
return "Maintains trust and minimizes polarization.";
|
|
278
|
+
}
|
|
279
|
+
if (outcomes.finalOutrage > 0.6) {
|
|
280
|
+
return "Outrage remains elevated — risk of sustained negative dynamics.";
|
|
281
|
+
}
|
|
282
|
+
if (outcomes.finalCascadeRisk > 0.5) {
|
|
283
|
+
return "Cascade risk remains dangerous — structural instability.";
|
|
284
|
+
}
|
|
285
|
+
if (outcomes.systemHealth > 70) {
|
|
286
|
+
return "System stabilizes with good overall health.";
|
|
287
|
+
}
|
|
288
|
+
if (outcomes.totalInterventions > 10) {
|
|
289
|
+
return "Requires heavy governance intervention to maintain stability.";
|
|
290
|
+
}
|
|
291
|
+
return `Overall score: ${result.overallScore.toFixed(2)} — ${outcomes.trajectory} trajectory.`;
|
|
292
|
+
}
|
|
293
|
+
// ============================================
|
|
294
|
+
// RISK ASSESSMENT
|
|
295
|
+
// ============================================
|
|
296
|
+
function buildRiskAssessment(results) {
|
|
297
|
+
return results.map((result) => {
|
|
298
|
+
const risks = [];
|
|
299
|
+
const outcomes = result.outcomes;
|
|
300
|
+
if (outcomes.finalCascadeRisk > 0.5) {
|
|
301
|
+
risks.push({
|
|
302
|
+
risk: "Cascading failure — actions compound into system-wide instability",
|
|
303
|
+
probability: outcomes.finalCascadeRisk,
|
|
304
|
+
severity: outcomes.finalCascadeRisk > 0.7 ? "critical" : "high",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (outcomes.finalPolarization > 0.5) {
|
|
308
|
+
risks.push({
|
|
309
|
+
risk: "Entrenched polarization — stakeholder base splits into opposing camps",
|
|
310
|
+
probability: outcomes.finalPolarization,
|
|
311
|
+
severity: outcomes.finalPolarization > 0.7 ? "high" : "moderate",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (outcomes.finalTrust < 0.3) {
|
|
315
|
+
risks.push({
|
|
316
|
+
risk: "Institutional trust collapse — credibility cannot be recovered",
|
|
317
|
+
probability: 1 - outcomes.finalTrust,
|
|
318
|
+
severity: outcomes.finalTrust < 0.15 ? "critical" : "high",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (outcomes.peakOutrage > 0.7) {
|
|
322
|
+
risks.push({
|
|
323
|
+
risk: "Outrage spike — may trigger media cascade or coordinated backlash",
|
|
324
|
+
probability: outcomes.peakOutrage,
|
|
325
|
+
severity: outcomes.peakOutrage > 0.85 ? "critical" : "high",
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (outcomes.actionsBlocked > outcomes.systemHealth * 0.3) {
|
|
329
|
+
risks.push({
|
|
330
|
+
risk: "Over-governance — too many blocked actions may freeze legitimate response",
|
|
331
|
+
probability: 0.5,
|
|
332
|
+
severity: "moderate",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
let overallRisk;
|
|
336
|
+
const criticalRisks = risks.filter((r) => r.severity === "critical").length;
|
|
337
|
+
const highRisks = risks.filter((r) => r.severity === "high").length;
|
|
338
|
+
if (criticalRisks > 0)
|
|
339
|
+
overallRisk = "CRITICAL";
|
|
340
|
+
else if (highRisks > 1)
|
|
341
|
+
overallRisk = "HIGH";
|
|
342
|
+
else if (highRisks > 0 || risks.length > 2)
|
|
343
|
+
overallRisk = "MEDIUM";
|
|
344
|
+
else
|
|
345
|
+
overallRisk = "LOW";
|
|
346
|
+
return {
|
|
347
|
+
optionId: result.optionId,
|
|
348
|
+
optionLabel: result.optionLabel,
|
|
349
|
+
overallRisk,
|
|
350
|
+
risks,
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// ============================================
|
|
355
|
+
// RECOMMENDATION ENGINE
|
|
356
|
+
// ============================================
|
|
357
|
+
function buildRecommendation(sorted, risks, options) {
|
|
358
|
+
const best = sorted[0];
|
|
359
|
+
const bestRisk = risks.find((r) => r.optionId === best.optionId);
|
|
360
|
+
const secondBest = sorted.length > 1 ? sorted[1] : null;
|
|
361
|
+
// Confidence: based on score gap and risk level
|
|
362
|
+
let confidence = 0.5;
|
|
363
|
+
if (secondBest) {
|
|
364
|
+
const gap = best.overallScore - secondBest.overallScore;
|
|
365
|
+
confidence += gap * 2; // bigger gap = more confidence
|
|
366
|
+
}
|
|
367
|
+
if (bestRisk?.overallRisk === "LOW")
|
|
368
|
+
confidence += 0.15;
|
|
369
|
+
else if (bestRisk?.overallRisk === "CRITICAL")
|
|
370
|
+
confidence -= 0.2;
|
|
371
|
+
confidence = Math.max(0.2, Math.min(0.95, confidence));
|
|
372
|
+
// Rationale
|
|
373
|
+
const parts = [];
|
|
374
|
+
parts.push(`"${best.optionLabel}" scores highest across the evaluation metrics (${best.overallScore.toFixed(2)}).`);
|
|
375
|
+
if (best.outcomes.finalTrust > 0.5) {
|
|
376
|
+
parts.push(`Preserves institutional trust at ${(best.outcomes.finalTrust * 100).toFixed(0)}%.`);
|
|
377
|
+
}
|
|
378
|
+
if (best.outcomes.finalPolarization < 0.3) {
|
|
379
|
+
parts.push(`Keeps polarization contained at ${(best.outcomes.finalPolarization * 100).toFixed(0)}%.`);
|
|
380
|
+
}
|
|
381
|
+
if (secondBest) {
|
|
382
|
+
parts.push(`Outperforms "${secondBest.optionLabel}" by ${((best.overallScore - secondBest.overallScore) * 100).toFixed(0)} points.`);
|
|
383
|
+
}
|
|
384
|
+
// Caveats
|
|
385
|
+
const caveats = [];
|
|
386
|
+
if (bestRisk?.overallRisk === "HIGH" || bestRisk?.overallRisk === "CRITICAL") {
|
|
387
|
+
caveats.push(`This option still carries ${bestRisk.overallRisk} risk — ${bestRisk.risks[0]?.risk}`);
|
|
388
|
+
}
|
|
389
|
+
if (best.outcomes.coolingActivated) {
|
|
390
|
+
caveats.push("Required a cooling period — expect delayed response dynamics.");
|
|
391
|
+
}
|
|
392
|
+
if (best.outcomes.cascadeBreakerFired) {
|
|
393
|
+
caveats.push("Circuit breaker was triggered — the system reached critical thresholds before stabilizing.");
|
|
394
|
+
}
|
|
395
|
+
if (confidence < 0.5) {
|
|
396
|
+
caveats.push("Score differences between options are small — the choice is not clear-cut.");
|
|
397
|
+
}
|
|
398
|
+
// Next steps
|
|
399
|
+
const nextSteps = [
|
|
400
|
+
`Implement the "${best.optionLabel}" policy as the primary approach.`,
|
|
401
|
+
];
|
|
402
|
+
if (best.outcomes.totalInterventions > 5) {
|
|
403
|
+
nextSteps.push("Prepare governance mechanisms for active intervention — this option requires ongoing management.");
|
|
404
|
+
}
|
|
405
|
+
if (secondBest && secondBest.overallScore > best.overallScore - 0.1) {
|
|
406
|
+
nextSteps.push(`Keep "${secondBest.optionLabel}" as a fallback — score difference is marginal.`);
|
|
407
|
+
}
|
|
408
|
+
nextSteps.push("Monitor the system state metrics (trust, polarization, outrage) in real-time.");
|
|
409
|
+
return {
|
|
410
|
+
optionId: best.optionId,
|
|
411
|
+
optionLabel: best.optionLabel,
|
|
412
|
+
confidence: Number(confidence.toFixed(2)),
|
|
413
|
+
rationale: parts.join(" "),
|
|
414
|
+
caveats,
|
|
415
|
+
nextSteps,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// ============================================
|
|
419
|
+
// DECISION BRIEF
|
|
420
|
+
// ============================================
|
|
421
|
+
function buildDecisionBrief(sorted, recommendation, risks) {
|
|
422
|
+
const lines = [];
|
|
423
|
+
lines.push("DECISION INTELLIGENCE BRIEF");
|
|
424
|
+
lines.push("=".repeat(50));
|
|
425
|
+
lines.push("");
|
|
426
|
+
// Rankings table
|
|
427
|
+
lines.push("SCENARIO COMPARISON:");
|
|
428
|
+
lines.push("-".repeat(50));
|
|
429
|
+
const headers = ["Option", "Score", "Trust", "Polar.", "Risk", "Verdict"];
|
|
430
|
+
lines.push(` ${headers.map((h) => h.padEnd(12)).join("")}`);
|
|
431
|
+
lines.push(` ${"-".repeat(72)}`);
|
|
432
|
+
for (const result of sorted) {
|
|
433
|
+
const risk = risks.find((r) => r.optionId === result.optionId);
|
|
434
|
+
const rank = sorted.indexOf(result);
|
|
435
|
+
const verdict = rank === 0 ? "BEST" : result.overallScore > 0.5 ? "VIABLE" : result.overallScore > 0.3 ? "RISKY" : "AVOID";
|
|
436
|
+
const prefix = rank === 0 ? ">>> " : " ";
|
|
437
|
+
const row = [
|
|
438
|
+
result.optionLabel.slice(0, 11),
|
|
439
|
+
result.overallScore.toFixed(2),
|
|
440
|
+
`${(result.outcomes.finalTrust * 100).toFixed(0)}%`,
|
|
441
|
+
`${(result.outcomes.finalPolarization * 100).toFixed(0)}%`,
|
|
442
|
+
risk?.overallRisk ?? "?",
|
|
443
|
+
verdict,
|
|
444
|
+
];
|
|
445
|
+
lines.push(`${prefix}${row.map((v) => v.padEnd(12)).join("")}`);
|
|
446
|
+
}
|
|
447
|
+
lines.push("");
|
|
448
|
+
lines.push("RECOMMENDATION:");
|
|
449
|
+
lines.push(` ${recommendation.rationale}`);
|
|
450
|
+
if (recommendation.caveats.length > 0) {
|
|
451
|
+
lines.push("");
|
|
452
|
+
lines.push("CAVEATS:");
|
|
453
|
+
for (const caveat of recommendation.caveats) {
|
|
454
|
+
lines.push(` - ${caveat}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
lines.push("");
|
|
458
|
+
lines.push("NEXT STEPS:");
|
|
459
|
+
for (const step of recommendation.nextSteps) {
|
|
460
|
+
lines.push(` ${sorted.indexOf(sorted[0]) + 1}. ${step}`);
|
|
461
|
+
}
|
|
462
|
+
return lines.join("\n");
|
|
463
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.runSwarmSimulation = runSwarmSimulation;
|
|
22
|
+
const aiProvider_1 = require("./aiProvider");
|
|
22
23
|
// ============================================
|
|
23
24
|
// REACTION MODELS
|
|
24
25
|
// ============================================
|
|
@@ -205,10 +206,16 @@ function findInflectionPoints(rounds) {
|
|
|
205
206
|
* This is Echelon's native reaction model — NOT MiroFish.
|
|
206
207
|
* It simulates how stakeholders react to different reasoning paths
|
|
207
208
|
* over multiple rounds, detecting emergent dynamics.
|
|
209
|
+
*
|
|
210
|
+
* When ANTHROPIC_API_KEY is set, agents use Claude for real reasoning.
|
|
211
|
+
* Otherwise, falls back to the deterministic Math.random() model.
|
|
208
212
|
*/
|
|
209
213
|
async function runSwarmSimulation(scenario, stakeholders, paths, config) {
|
|
210
214
|
const roundCount = config.rounds ?? 3;
|
|
211
215
|
const model = config.reaction_model ?? "mixed";
|
|
216
|
+
// Detect if AI reasoning is available and requested
|
|
217
|
+
const defaultProvider = (0, aiProvider_1.getDefaultProviderName)();
|
|
218
|
+
const useAI = config.ai_reasoning !== false && defaultProvider !== "deterministic";
|
|
212
219
|
// Filter stakeholders if specific ones requested
|
|
213
220
|
const activeStakeholders = config.simulate_stakeholders
|
|
214
221
|
? stakeholders.filter((s) => config.simulate_stakeholders.includes(s.id))
|
|
@@ -224,8 +231,54 @@ async function runSwarmSimulation(scenario, stakeholders, paths, config) {
|
|
|
224
231
|
}
|
|
225
232
|
// Run simulation rounds
|
|
226
233
|
const rounds = [];
|
|
234
|
+
let aiProvider;
|
|
235
|
+
if (useAI) {
|
|
236
|
+
try {
|
|
237
|
+
aiProvider = (0, aiProvider_1.getAIProvider)(defaultProvider);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// No API key or provider unavailable — fall back silently
|
|
241
|
+
}
|
|
242
|
+
}
|
|
227
243
|
for (let round = 0; round < roundCount; round++) {
|
|
228
|
-
|
|
244
|
+
let reactions;
|
|
245
|
+
if (aiProvider) {
|
|
246
|
+
// AI-POWERED REASONING: Each agent gets a Claude call
|
|
247
|
+
const previousReactions = round > 0
|
|
248
|
+
? rounds[round - 1].reactions.map(r => `${r.stakeholder_id}: ${r.reaction}`)
|
|
249
|
+
: [];
|
|
250
|
+
reactions = await Promise.all(activeStakeholders.map(async (stakeholder) => {
|
|
251
|
+
try {
|
|
252
|
+
const aiResult = await (0, aiProvider_1.generateAIReaction)({
|
|
253
|
+
stakeholderId: stakeholder.id,
|
|
254
|
+
stakeholderDescription: stakeholder.description,
|
|
255
|
+
stakeholderDisposition: stakeholder.disposition,
|
|
256
|
+
stakeholderPriorities: stakeholder.priorities,
|
|
257
|
+
scenario,
|
|
258
|
+
pathDescription: primaryPath.description,
|
|
259
|
+
pathRisk: primaryPath.risk,
|
|
260
|
+
round,
|
|
261
|
+
previousReactions,
|
|
262
|
+
provider: aiProvider,
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
stakeholder_id: stakeholder.id,
|
|
266
|
+
reaction: aiResult.reaction,
|
|
267
|
+
confidence: aiResult.confidence,
|
|
268
|
+
impact: aiResult.impact,
|
|
269
|
+
trigger: aiResult.trigger,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Individual agent fallback to deterministic
|
|
274
|
+
return modelReaction(stakeholder, primaryPath, model, round);
|
|
275
|
+
}
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// DETERMINISTIC: Original Math.random() model
|
|
280
|
+
reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
|
|
281
|
+
}
|
|
229
282
|
const emergentDynamics = detectEmergentDynamics(reactions);
|
|
230
283
|
rounds.push({
|
|
231
284
|
round,
|