@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.
- package/LICENSE +201 -0
- package/README.md +73 -0
- package/dist/engine/analyzer.js +651 -0
- package/dist/engine/api.js +208 -0
- package/dist/engine/chaosEngine.js +292 -0
- package/dist/engine/cli.js +803 -0
- package/dist/engine/goalEngine.js +559 -0
- package/dist/engine/governance.js +210 -0
- package/dist/engine/governedSimulation.js +529 -0
- package/dist/engine/index.js +82 -0
- package/dist/engine/mirofish.js +295 -0
- package/dist/engine/reasoningEngine.js +548 -0
- package/dist/engine/scenarioCapsule.js +351 -0
- package/dist/engine/swarmSimulation.js +244 -0
- package/dist/engine/types.js +15 -0
- package/dist/engine/worldBridge.js +481 -0
- package/dist/package.json +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scenario Capsules — Shareable Decision Worlds
|
|
4
|
+
*
|
|
5
|
+
* A scenario capsule is a self-contained, portable simulation seed.
|
|
6
|
+
* It encodes everything needed to reproduce a reasoning session:
|
|
7
|
+
* - scenario description
|
|
8
|
+
* - assumption sliders
|
|
9
|
+
* - stakeholders
|
|
10
|
+
* - constraints
|
|
11
|
+
* - initial evidence signals
|
|
12
|
+
*
|
|
13
|
+
* Capsules can be:
|
|
14
|
+
* 1. Encoded in a URL: mirotir.com/run#capsule=<encoded>
|
|
15
|
+
* 2. Downloaded as JSON: scenario.capsule.json
|
|
16
|
+
* 3. Generated via API: POST /api/v1/scenario → capsule
|
|
17
|
+
* 4. Created by agents: autonomous agents generate decision worlds
|
|
18
|
+
*
|
|
19
|
+
* CRITICAL: No server-side storage. The capsule IS the data.
|
|
20
|
+
* This preserves the stateless, no-storage architecture.
|
|
21
|
+
*
|
|
22
|
+
* The sharing loop:
|
|
23
|
+
* watch scenario → change assumptions → new reasoning → share capsule
|
|
24
|
+
* The platform spreads through ideas, not accounts.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.SCENARIO_TEMPLATES = void 0;
|
|
28
|
+
exports.createCapsule = createCapsule;
|
|
29
|
+
exports.capsuleToReasonRequest = capsuleToReasonRequest;
|
|
30
|
+
exports.encodeCapsule = encodeCapsule;
|
|
31
|
+
exports.decodeCapsule = decodeCapsule;
|
|
32
|
+
exports.buildShareableUrl = buildShareableUrl;
|
|
33
|
+
exports.extractCapsuleFromUrl = extractCapsuleFromUrl;
|
|
34
|
+
exports.getPresetCapsule = getPresetCapsule;
|
|
35
|
+
// ============================================
|
|
36
|
+
// CAPSULE CREATION
|
|
37
|
+
// ============================================
|
|
38
|
+
/**
|
|
39
|
+
* Create a scenario capsule from a reason request.
|
|
40
|
+
*/
|
|
41
|
+
function createCapsule(title, request, options) {
|
|
42
|
+
const normalizedStakeholders = (request.stakeholders ?? []).map((s) => typeof s === "string" ? { id: s, disposition: "unknown" } : s);
|
|
43
|
+
const capsule = {
|
|
44
|
+
version: "1.0",
|
|
45
|
+
created_at: new Date().toISOString(),
|
|
46
|
+
title,
|
|
47
|
+
scenario: request.scenario,
|
|
48
|
+
stakeholders: normalizedStakeholders,
|
|
49
|
+
assumptions: request.assumptions ?? {},
|
|
50
|
+
constraints: request.constraints ?? {},
|
|
51
|
+
signals: options?.signals,
|
|
52
|
+
depth: request.depth ?? "standard",
|
|
53
|
+
perspective: request.perspective ?? "strategic_advisor",
|
|
54
|
+
swarm: request.swarm,
|
|
55
|
+
tags: options?.tags,
|
|
56
|
+
author: options?.author,
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...capsule,
|
|
60
|
+
capsule_hash: computeCapsuleHash(capsule),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convert a capsule back to a ReasonRequest for processing.
|
|
65
|
+
*/
|
|
66
|
+
function capsuleToReasonRequest(capsule) {
|
|
67
|
+
return {
|
|
68
|
+
scenario: capsule.scenario,
|
|
69
|
+
stakeholders: capsule.stakeholders,
|
|
70
|
+
assumptions: capsule.assumptions,
|
|
71
|
+
constraints: capsule.constraints,
|
|
72
|
+
depth: capsule.depth,
|
|
73
|
+
perspective: capsule.perspective,
|
|
74
|
+
swarm: capsule.swarm,
|
|
75
|
+
goal: capsule.goal,
|
|
76
|
+
mode: capsule.goal ? "goal" : "explore",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// ============================================
|
|
80
|
+
// ENCODING / DECODING (URL-safe)
|
|
81
|
+
// ============================================
|
|
82
|
+
/**
|
|
83
|
+
* Encode a capsule for URL embedding.
|
|
84
|
+
* Result can be used as: mirotir.com/run#capsule=<encoded>
|
|
85
|
+
*/
|
|
86
|
+
function encodeCapsule(capsule) {
|
|
87
|
+
const json = JSON.stringify(capsule);
|
|
88
|
+
// Use base64url encoding (URL-safe)
|
|
89
|
+
if (typeof btoa === "function") {
|
|
90
|
+
return btoa(unescape(encodeURIComponent(json)))
|
|
91
|
+
.replace(/\+/g, "-")
|
|
92
|
+
.replace(/\//g, "_")
|
|
93
|
+
.replace(/=+$/, "");
|
|
94
|
+
}
|
|
95
|
+
// Node.js fallback
|
|
96
|
+
return Buffer.from(json, "utf-8").toString("base64url");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decode a capsule from URL encoding.
|
|
100
|
+
*/
|
|
101
|
+
function decodeCapsule(encoded) {
|
|
102
|
+
// Restore base64 padding
|
|
103
|
+
let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
104
|
+
while (base64.length % 4) {
|
|
105
|
+
base64 += "=";
|
|
106
|
+
}
|
|
107
|
+
let json;
|
|
108
|
+
if (typeof atob === "function") {
|
|
109
|
+
json = decodeURIComponent(escape(atob(base64)));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
json = Buffer.from(base64, "base64").toString("utf-8");
|
|
113
|
+
}
|
|
114
|
+
const capsule = JSON.parse(json);
|
|
115
|
+
// Verify integrity
|
|
116
|
+
const { capsule_hash, ...rest } = capsule;
|
|
117
|
+
const expectedHash = computeCapsuleHash(rest);
|
|
118
|
+
if (expectedHash !== capsule_hash) {
|
|
119
|
+
throw new Error("Capsule integrity check failed — data may have been tampered with");
|
|
120
|
+
}
|
|
121
|
+
return capsule;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Build a shareable URL from a capsule.
|
|
125
|
+
*/
|
|
126
|
+
function buildShareableUrl(capsule, baseUrl = "https://mirotir.com") {
|
|
127
|
+
const encoded = encodeCapsule(capsule);
|
|
128
|
+
return `${baseUrl}/run#capsule=${encoded}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract a capsule from a shareable URL.
|
|
132
|
+
*/
|
|
133
|
+
function extractCapsuleFromUrl(url) {
|
|
134
|
+
const hashIndex = url.indexOf("#capsule=");
|
|
135
|
+
if (hashIndex === -1)
|
|
136
|
+
return null;
|
|
137
|
+
const encoded = url.slice(hashIndex + "#capsule=".length);
|
|
138
|
+
return decodeCapsule(encoded);
|
|
139
|
+
}
|
|
140
|
+
// ============================================
|
|
141
|
+
// CAPSULE HASH
|
|
142
|
+
// ============================================
|
|
143
|
+
/**
|
|
144
|
+
* Compute a hash for capsule integrity verification.
|
|
145
|
+
* Simple hash — not cryptographic. Just for tampering detection.
|
|
146
|
+
*/
|
|
147
|
+
function computeCapsuleHash(data) {
|
|
148
|
+
const json = JSON.stringify(data);
|
|
149
|
+
let hash = 0;
|
|
150
|
+
for (let i = 0; i < json.length; i++) {
|
|
151
|
+
const char = json.charCodeAt(i);
|
|
152
|
+
hash = (hash << 5) - hash + char;
|
|
153
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
154
|
+
}
|
|
155
|
+
return `cap_${Math.abs(hash).toString(36)}`;
|
|
156
|
+
}
|
|
157
|
+
// ============================================
|
|
158
|
+
// PRESET SCENARIO TEMPLATES
|
|
159
|
+
// ============================================
|
|
160
|
+
/**
|
|
161
|
+
* Built-in scenario templates that demonstrate Mirotir's capabilities.
|
|
162
|
+
* These are the "instant insight" scenarios that make people share.
|
|
163
|
+
*/
|
|
164
|
+
exports.SCENARIO_TEMPLATES = {
|
|
165
|
+
strait_of_hormuz: {
|
|
166
|
+
title: "Strait of Hormuz Closes",
|
|
167
|
+
scenario: "The Strait of Hormuz is closed due to military conflict. " +
|
|
168
|
+
"20% of global oil supply is disrupted. Oil prices spike 40% in 48 hours. " +
|
|
169
|
+
"Global supply chains begin to fracture.",
|
|
170
|
+
stakeholders: [
|
|
171
|
+
{ id: "Energy Companies", disposition: "supportive", priorities: ["supply security", "price stability"] },
|
|
172
|
+
{ id: "Consumers", disposition: "hostile", priorities: ["affordable fuel", "economic stability"] },
|
|
173
|
+
{ id: "Government", disposition: "neutral", priorities: ["national security", "economic stability", "election impact"] },
|
|
174
|
+
{ id: "Military", disposition: "neutral", priorities: ["strategic positioning", "force projection"] },
|
|
175
|
+
{ id: "Financial Markets", disposition: "hostile", priorities: ["stability", "predictability"] },
|
|
176
|
+
{ id: "OPEC", disposition: "unknown", priorities: ["market share", "price control"] },
|
|
177
|
+
],
|
|
178
|
+
assumptions: {
|
|
179
|
+
severity: "critical",
|
|
180
|
+
environmental_hostility: "high",
|
|
181
|
+
time_pressure: "immediate",
|
|
182
|
+
regulatory_climate: "strict",
|
|
183
|
+
},
|
|
184
|
+
constraints: {
|
|
185
|
+
time_horizon: "30 days",
|
|
186
|
+
regulatory: ["international maritime law", "sanctions framework"],
|
|
187
|
+
risk_tolerance: "conservative",
|
|
188
|
+
},
|
|
189
|
+
signals: {
|
|
190
|
+
oil_price_spike: "40%",
|
|
191
|
+
shipping_disruption: true,
|
|
192
|
+
military_presence: "elevated",
|
|
193
|
+
diplomatic_channels: "active",
|
|
194
|
+
},
|
|
195
|
+
depth: "full",
|
|
196
|
+
perspective: "strategic_advisor",
|
|
197
|
+
swarm: { enabled: true, rounds: 5, reaction_model: "mixed" },
|
|
198
|
+
tags: ["geopolitical", "energy", "crisis", "supply-chain"],
|
|
199
|
+
world: {
|
|
200
|
+
world_id: "geopolitics_energy",
|
|
201
|
+
name: "Geopolitics & Energy Crisis",
|
|
202
|
+
inline_definition: {
|
|
203
|
+
thesis: "Geopolitical disruptions to energy supply chains create cascading economic and political effects",
|
|
204
|
+
state_variables: [
|
|
205
|
+
{ id: "oil_supply_disruption", label: "Oil Supply Disruption %", type: "number", range: { min: 0, max: 100 }, default_value: 20 },
|
|
206
|
+
{ id: "military_escalation", label: "Military Escalation Level", type: "enum", enum_values: ["posturing", "limited_engagement", "full_conflict"], default_value: "limited_engagement" },
|
|
207
|
+
{ id: "diplomatic_channels_open", label: "Diplomatic Channels Open", type: "boolean", default_value: true },
|
|
208
|
+
{ id: "strategic_reserve_release", label: "Strategic Reserve Release %", type: "number", range: { min: 0, max: 50 }, default_value: 0 },
|
|
209
|
+
{ id: "media_hostility", label: "Media Hostility", type: "number", range: { min: 0, max: 1 }, default_value: 0.7 },
|
|
210
|
+
],
|
|
211
|
+
invariants: [
|
|
212
|
+
{ id: "INV-001", description: "Energy supply disruption > 30% triggers emergency pricing mechanisms", enforceable: true },
|
|
213
|
+
{ id: "INV-002", description: "Military escalation restricts diplomatic solution space", enforceable: true },
|
|
214
|
+
{ id: "INV-003", description: "Consumer sentiment inversely correlates with price spikes", enforceable: true },
|
|
215
|
+
],
|
|
216
|
+
gates: [
|
|
217
|
+
{ id: "GATE-001", label: "Market Panic", condition: "oil_supply_disruption > 40", severity: "critical" },
|
|
218
|
+
{ id: "GATE-002", label: "Diplomatic Window Closing", condition: "military_escalation == full_conflict", severity: "critical" },
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
gas_price_spike: {
|
|
224
|
+
title: "Gas Prices Hit $5/Gallon",
|
|
225
|
+
scenario: "US gas prices spike to $5/gallon driven by supply disruption and refinery constraints. " +
|
|
226
|
+
"Consumer sentiment craters. Midterm elections are 6 months away. " +
|
|
227
|
+
"EV adoption accelerates but grid infrastructure can't keep up.",
|
|
228
|
+
stakeholders: [
|
|
229
|
+
{ id: "Consumers", disposition: "hostile", priorities: ["affordability", "commute costs"] },
|
|
230
|
+
{ id: "Oil Companies", disposition: "supportive", priorities: ["margins", "market position"] },
|
|
231
|
+
{ id: "Politicians", disposition: "hostile", priorities: ["re-election", "constituent pressure"] },
|
|
232
|
+
{ id: "EV Manufacturers", disposition: "supportive", priorities: ["market share", "adoption"] },
|
|
233
|
+
{ id: "Grid Operators", disposition: "neutral", priorities: ["reliability", "capacity"] },
|
|
234
|
+
{ id: "Trucking Industry", disposition: "hostile", priorities: ["operating costs", "contracts"] },
|
|
235
|
+
],
|
|
236
|
+
assumptions: {
|
|
237
|
+
severity: "high",
|
|
238
|
+
environmental_hostility: "high",
|
|
239
|
+
time_pressure: "urgent",
|
|
240
|
+
regulatory_climate: "strict",
|
|
241
|
+
},
|
|
242
|
+
constraints: {
|
|
243
|
+
time_horizon: "6 months",
|
|
244
|
+
regulatory: ["EPA", "DOE", "state fuel regulations"],
|
|
245
|
+
risk_tolerance: "moderate",
|
|
246
|
+
},
|
|
247
|
+
signals: {
|
|
248
|
+
gas_price: "$5.00/gal",
|
|
249
|
+
consumer_sentiment: "very_low",
|
|
250
|
+
ev_demand_spike: "35%",
|
|
251
|
+
grid_capacity_surplus: "-12%",
|
|
252
|
+
},
|
|
253
|
+
depth: "full",
|
|
254
|
+
perspective: "strategic_advisor",
|
|
255
|
+
swarm: { enabled: true, rounds: 4, reaction_model: "mixed" },
|
|
256
|
+
tags: ["economic", "energy", "political", "consumer"],
|
|
257
|
+
world: {
|
|
258
|
+
world_id: "economic_energy",
|
|
259
|
+
name: "Economic Energy Shock",
|
|
260
|
+
inline_definition: {
|
|
261
|
+
thesis: "Energy price spikes create political, economic, and social cascading effects with EV transition implications",
|
|
262
|
+
state_variables: [
|
|
263
|
+
{ id: "gas_price", label: "Gas Price ($/gal)", type: "number", range: { min: 2, max: 10 }, default_value: 5 },
|
|
264
|
+
{ id: "consumer_sentiment", label: "Consumer Sentiment", type: "number", range: { min: 0, max: 1 }, default_value: 0.2 },
|
|
265
|
+
{ id: "ev_demand_surge", label: "EV Demand Surge %", type: "number", range: { min: 0, max: 100 }, default_value: 35 },
|
|
266
|
+
{ id: "election_proximity_months", label: "Months to Election", type: "number", range: { min: 0, max: 24 }, default_value: 6 },
|
|
267
|
+
{ id: "grid_capacity", label: "Grid Spare Capacity %", type: "number", range: { min: -30, max: 30 }, default_value: -12 },
|
|
268
|
+
],
|
|
269
|
+
invariants: [
|
|
270
|
+
{ id: "INV-001", description: "Gas price > $4.50 triggers political crisis mode", enforceable: true },
|
|
271
|
+
{ id: "INV-002", description: "EV demand surge without grid capacity creates secondary crisis", enforceable: true },
|
|
272
|
+
{ id: "INV-003", description: "Election proximity amplifies political response intensity", enforceable: true },
|
|
273
|
+
],
|
|
274
|
+
gates: [
|
|
275
|
+
{ id: "GATE-001", label: "Political Crisis", condition: "gas_price > 4.5 && election_proximity_months < 12", severity: "critical" },
|
|
276
|
+
{ id: "GATE-002", label: "Grid Failure Risk", condition: "ev_demand_surge > 30 && grid_capacity < 0", severity: "warning" },
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
ai_regulation_crisis: {
|
|
282
|
+
title: "Major AI Regulation Passes",
|
|
283
|
+
scenario: "The EU passes sweeping AI regulation requiring full model transparency, " +
|
|
284
|
+
"mandatory audits, and liability for AI-generated outputs. " +
|
|
285
|
+
"US lawmakers signal similar legislation within 12 months. " +
|
|
286
|
+
"Open-source AI communities face existential compliance questions.",
|
|
287
|
+
stakeholders: [
|
|
288
|
+
{ id: "AI Companies", disposition: "hostile", priorities: ["innovation speed", "competitive advantage", "IP protection"] },
|
|
289
|
+
{ id: "Regulators", disposition: "supportive", priorities: ["public safety", "accountability", "precedent"] },
|
|
290
|
+
{ id: "Open Source Community", disposition: "hostile", priorities: ["freedom", "access", "collaboration"] },
|
|
291
|
+
{ id: "Enterprise Customers", disposition: "neutral", priorities: ["compliance", "reliability", "cost"] },
|
|
292
|
+
{ id: "Civil Society", disposition: "supportive", priorities: ["transparency", "fairness", "rights"] },
|
|
293
|
+
],
|
|
294
|
+
assumptions: {
|
|
295
|
+
severity: "high",
|
|
296
|
+
environmental_hostility: "moderate",
|
|
297
|
+
time_pressure: "low",
|
|
298
|
+
regulatory_climate: "adversarial",
|
|
299
|
+
},
|
|
300
|
+
constraints: {
|
|
301
|
+
time_horizon: "18 months",
|
|
302
|
+
regulatory: ["EU AI Act", "proposed US AI legislation"],
|
|
303
|
+
risk_tolerance: "moderate",
|
|
304
|
+
},
|
|
305
|
+
depth: "full",
|
|
306
|
+
perspective: "strategic_advisor",
|
|
307
|
+
swarm: { enabled: true, rounds: 4, reaction_model: "rational" },
|
|
308
|
+
tags: ["ai", "regulation", "technology", "policy"],
|
|
309
|
+
world: {
|
|
310
|
+
world_id: "ai_regulation",
|
|
311
|
+
name: "AI Regulation Landscape",
|
|
312
|
+
inline_definition: {
|
|
313
|
+
thesis: "AI regulation creates compliance burdens that reshape competitive dynamics between open and closed AI ecosystems",
|
|
314
|
+
state_variables: [
|
|
315
|
+
{ id: "regulation_strictness", label: "Regulation Strictness", type: "number", range: { min: 0, max: 1 }, default_value: 0.8 },
|
|
316
|
+
{ id: "enforcement_timeline_months", label: "Enforcement Timeline (months)", type: "number", range: { min: 3, max: 36 }, default_value: 18 },
|
|
317
|
+
{ id: "open_source_exemption", label: "Open Source Exemption", type: "boolean", default_value: false },
|
|
318
|
+
{ id: "us_follows_eu", label: "US Follows EU", type: "boolean", default_value: true },
|
|
319
|
+
{ id: "compliance_cost_multiplier", label: "Compliance Cost Multiplier", type: "number", range: { min: 1, max: 10 }, default_value: 3 },
|
|
320
|
+
],
|
|
321
|
+
invariants: [
|
|
322
|
+
{ id: "INV-001", description: "Strict regulation without open-source exemption eliminates small AI labs", enforceable: true },
|
|
323
|
+
{ id: "INV-002", description: "Cross-jurisdiction regulation creates compliance arbitrage opportunities", enforceable: true },
|
|
324
|
+
{ id: "INV-003", description: "Enterprise customers require compliance before adoption", enforceable: true },
|
|
325
|
+
],
|
|
326
|
+
gates: [
|
|
327
|
+
{ id: "GATE-001", label: "Market Consolidation", condition: "compliance_cost_multiplier > 5 && !open_source_exemption", severity: "critical" },
|
|
328
|
+
{ id: "GATE-002", label: "Innovation Flight", condition: "regulation_strictness > 0.7 && !us_follows_eu", severity: "warning" },
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Get a preset scenario as a full capsule.
|
|
336
|
+
*/
|
|
337
|
+
function getPresetCapsule(templateId) {
|
|
338
|
+
const template = exports.SCENARIO_TEMPLATES[templateId];
|
|
339
|
+
if (!template) {
|
|
340
|
+
throw new Error(`Unknown scenario template: ${String(templateId)}`);
|
|
341
|
+
}
|
|
342
|
+
const capsule = {
|
|
343
|
+
version: "1.0",
|
|
344
|
+
created_at: new Date().toISOString(),
|
|
345
|
+
...template,
|
|
346
|
+
};
|
|
347
|
+
return {
|
|
348
|
+
...capsule,
|
|
349
|
+
capsule_hash: computeCapsuleHash(capsule),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Swarm Simulation Layer
|
|
4
|
+
*
|
|
5
|
+
* Lightweight stakeholder reaction simulation for POST /reason.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT — MiroFish License Compliance:
|
|
8
|
+
* This is NOT MiroFish. This is Echelon's own lightweight reaction model.
|
|
9
|
+
* MiroFish is a separate product with its own license.
|
|
10
|
+
* If/when MiroFish integration is desired, it would be called as an
|
|
11
|
+
* external service via its REST API, respecting its license terms.
|
|
12
|
+
*
|
|
13
|
+
* This simulation layer uses:
|
|
14
|
+
* - Echelon-native stakeholder modeling
|
|
15
|
+
* - Simplified reaction dynamics (rational, emotional, mixed)
|
|
16
|
+
* - Projected reactions based on scenario decomposition
|
|
17
|
+
*
|
|
18
|
+
* It does NOT use MiroFish internals, algorithms, or data structures.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.runSwarmSimulation = runSwarmSimulation;
|
|
22
|
+
// ============================================
|
|
23
|
+
// REACTION MODELS
|
|
24
|
+
// ============================================
|
|
25
|
+
/**
|
|
26
|
+
* Model how a stakeholder reacts based on their disposition
|
|
27
|
+
* and the reaction model selected.
|
|
28
|
+
*/
|
|
29
|
+
function modelReaction(stakeholder, path, model, round) {
|
|
30
|
+
const disposition = stakeholder.disposition ?? "unknown";
|
|
31
|
+
const pathRisk = path.risk;
|
|
32
|
+
// Base confidence decreases with rounds (more uncertainty over time)
|
|
33
|
+
const baseConfidence = Math.max(0.3, 0.9 - round * 0.15);
|
|
34
|
+
// Impact calculation based on disposition + path risk
|
|
35
|
+
let impact = 0;
|
|
36
|
+
let reaction = "";
|
|
37
|
+
let trigger = "";
|
|
38
|
+
switch (model) {
|
|
39
|
+
case "rational": {
|
|
40
|
+
// Rational agents evaluate based on self-interest
|
|
41
|
+
const benefited = path.benefits_stakeholders?.includes(stakeholder.id);
|
|
42
|
+
const harmed = path.harms_stakeholders?.includes(stakeholder.id);
|
|
43
|
+
if (benefited) {
|
|
44
|
+
impact = 0.5 + Math.random() * 0.3;
|
|
45
|
+
reaction = `${stakeholder.id} supports this path — aligns with their priorities`;
|
|
46
|
+
trigger = "Self-interest alignment";
|
|
47
|
+
}
|
|
48
|
+
else if (harmed) {
|
|
49
|
+
impact = -(0.5 + Math.random() * 0.3);
|
|
50
|
+
reaction = `${stakeholder.id} opposes this path — threatens their position`;
|
|
51
|
+
trigger = "Self-interest conflict";
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
impact = (Math.random() - 0.5) * 0.4;
|
|
55
|
+
reaction = `${stakeholder.id} monitors the situation — no strong position yet`;
|
|
56
|
+
trigger = "Neutral assessment";
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "emotional": {
|
|
61
|
+
// Emotional agents react more strongly and less predictably
|
|
62
|
+
const volatility = disposition === "hostile" ? 0.8 : disposition === "supportive" ? 0.3 : 0.5;
|
|
63
|
+
impact = (Math.random() - 0.5) * 2 * volatility;
|
|
64
|
+
if (pathRisk === "high" || pathRisk === "critical") {
|
|
65
|
+
impact -= 0.3; // Fear response to high-risk scenarios
|
|
66
|
+
reaction = `${stakeholder.id} reacts with concern — elevated anxiety about ${pathRisk} risk`;
|
|
67
|
+
trigger = "Fear of high-risk outcome";
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
reaction = `${stakeholder.id} responds with ${impact > 0 ? "cautious optimism" : "skepticism"}`;
|
|
71
|
+
trigger = "Emotional assessment of path viability";
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "mixed":
|
|
76
|
+
default: {
|
|
77
|
+
// Mixed model: 60% rational, 40% emotional
|
|
78
|
+
const rationalComponent = (() => {
|
|
79
|
+
const benefited = path.benefits_stakeholders?.includes(stakeholder.id);
|
|
80
|
+
const harmed = path.harms_stakeholders?.includes(stakeholder.id);
|
|
81
|
+
if (benefited)
|
|
82
|
+
return 0.4;
|
|
83
|
+
if (harmed)
|
|
84
|
+
return -0.4;
|
|
85
|
+
return 0;
|
|
86
|
+
})();
|
|
87
|
+
const emotionalComponent = (Math.random() - 0.5) * 0.6;
|
|
88
|
+
impact = rationalComponent * 0.6 + emotionalComponent * 0.4;
|
|
89
|
+
if (disposition === "hostile") {
|
|
90
|
+
impact -= 0.2;
|
|
91
|
+
reaction = `${stakeholder.id} pushes back — predisposed against this approach`;
|
|
92
|
+
trigger = "Pre-existing opposition + rational assessment";
|
|
93
|
+
}
|
|
94
|
+
else if (disposition === "supportive") {
|
|
95
|
+
impact += 0.15;
|
|
96
|
+
reaction = `${stakeholder.id} provides conditional support — reserves judgment on execution`;
|
|
97
|
+
trigger = "Pre-existing alignment + cautious evaluation";
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
reaction = `${stakeholder.id} weighs options — ${impact > 0 ? "leaning toward engagement" : "leaning toward caution"}`;
|
|
101
|
+
trigger = "Mixed rational-emotional evaluation";
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Clamp impact to [-1, 1]
|
|
107
|
+
impact = Math.max(-1, Math.min(1, impact));
|
|
108
|
+
return {
|
|
109
|
+
stakeholder_id: stakeholder.id,
|
|
110
|
+
reaction,
|
|
111
|
+
confidence: Number((baseConfidence + (Math.random() - 0.5) * 0.2).toFixed(2)),
|
|
112
|
+
impact: Number(impact.toFixed(2)),
|
|
113
|
+
trigger,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// ============================================
|
|
117
|
+
// EMERGENT DYNAMICS
|
|
118
|
+
// ============================================
|
|
119
|
+
/**
|
|
120
|
+
* Detect emergent dynamics from a round of reactions.
|
|
121
|
+
* These are patterns that arise from the combination of individual reactions.
|
|
122
|
+
*/
|
|
123
|
+
function detectEmergentDynamics(reactions) {
|
|
124
|
+
const dynamics = [];
|
|
125
|
+
// Check for consensus
|
|
126
|
+
const avgImpact = reactions.reduce((sum, r) => sum + r.impact, 0) / reactions.length;
|
|
127
|
+
if (Math.abs(avgImpact) > 0.5) {
|
|
128
|
+
dynamics.push(avgImpact > 0
|
|
129
|
+
? "Strong positive consensus emerging — stakeholders broadly supportive"
|
|
130
|
+
: "Strong negative consensus emerging — widespread opposition forming");
|
|
131
|
+
}
|
|
132
|
+
// Check for polarization
|
|
133
|
+
const positive = reactions.filter((r) => r.impact > 0.2).length;
|
|
134
|
+
const negative = reactions.filter((r) => r.impact < -0.2).length;
|
|
135
|
+
if (positive > 0 && negative > 0 && Math.abs(positive - negative) <= 1) {
|
|
136
|
+
dynamics.push("Polarization detected — stakeholder base splitting into opposing camps");
|
|
137
|
+
}
|
|
138
|
+
// Check for high-confidence negative reactions
|
|
139
|
+
const highConfidenceNegative = reactions.filter((r) => r.confidence > 0.7 && r.impact < -0.3);
|
|
140
|
+
if (highConfidenceNegative.length >= 2) {
|
|
141
|
+
dynamics.push(`Coalition risk: ${highConfidenceNegative.map((r) => r.stakeholder_id).join(" + ")} may form opposition bloc`);
|
|
142
|
+
}
|
|
143
|
+
return dynamics;
|
|
144
|
+
}
|
|
145
|
+
// ============================================
|
|
146
|
+
// TRAJECTORY ANALYSIS
|
|
147
|
+
// ============================================
|
|
148
|
+
/**
|
|
149
|
+
* Determine the overall trajectory from multiple simulation rounds.
|
|
150
|
+
*/
|
|
151
|
+
function analyzeTrajectory(rounds) {
|
|
152
|
+
if (rounds.length < 2)
|
|
153
|
+
return "stabilizing";
|
|
154
|
+
const avgImpacts = rounds.map((r) => r.reactions.reduce((sum, rx) => sum + rx.impact, 0) / r.reactions.length);
|
|
155
|
+
// Check trend
|
|
156
|
+
const trend = avgImpacts[avgImpacts.length - 1] - avgImpacts[0];
|
|
157
|
+
const variance = avgImpacts.reduce((sum, v) => {
|
|
158
|
+
const mean = avgImpacts.reduce((s, x) => s + x, 0) / avgImpacts.length;
|
|
159
|
+
return sum + (v - mean) ** 2;
|
|
160
|
+
}, 0) / avgImpacts.length;
|
|
161
|
+
if (variance > 0.15)
|
|
162
|
+
return "diverging";
|
|
163
|
+
if (Math.abs(trend) < 0.1 && variance < 0.05)
|
|
164
|
+
return "converging";
|
|
165
|
+
if (trend < -0.2)
|
|
166
|
+
return "escalating";
|
|
167
|
+
return "stabilizing";
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Identify inflection points across rounds.
|
|
171
|
+
*/
|
|
172
|
+
function findInflectionPoints(rounds) {
|
|
173
|
+
const points = [];
|
|
174
|
+
for (let i = 1; i < rounds.length; i++) {
|
|
175
|
+
const prevAvg = rounds[i - 1].reactions.reduce((s, r) => s + r.impact, 0) /
|
|
176
|
+
rounds[i - 1].reactions.length;
|
|
177
|
+
const currAvg = rounds[i].reactions.reduce((s, r) => s + r.impact, 0) /
|
|
178
|
+
rounds[i].reactions.length;
|
|
179
|
+
const shift = Math.abs(currAvg - prevAvg);
|
|
180
|
+
if (shift > 0.3) {
|
|
181
|
+
points.push(`Round ${i}: Significant sentiment shift (${shift > 0 ? "positive" : "negative"} swing of ${(shift * 100).toFixed(0)}%)`);
|
|
182
|
+
}
|
|
183
|
+
// Check for individual stakeholder flips
|
|
184
|
+
for (const reaction of rounds[i].reactions) {
|
|
185
|
+
const prevReaction = rounds[i - 1].reactions.find((r) => r.stakeholder_id === reaction.stakeholder_id);
|
|
186
|
+
if (prevReaction) {
|
|
187
|
+
const individualShift = Math.abs(reaction.impact - prevReaction.impact);
|
|
188
|
+
if (individualShift > 0.5) {
|
|
189
|
+
points.push(`Round ${i}: ${reaction.stakeholder_id} dramatically shifts position`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (points.length === 0) {
|
|
195
|
+
points.push("No major inflection points detected — scenario evolves gradually");
|
|
196
|
+
}
|
|
197
|
+
return points;
|
|
198
|
+
}
|
|
199
|
+
// ============================================
|
|
200
|
+
// MAIN SIMULATION
|
|
201
|
+
// ============================================
|
|
202
|
+
/**
|
|
203
|
+
* Run the swarm simulation.
|
|
204
|
+
*
|
|
205
|
+
* This is Echelon's native reaction model — NOT MiroFish.
|
|
206
|
+
* It simulates how stakeholders react to different reasoning paths
|
|
207
|
+
* over multiple rounds, detecting emergent dynamics.
|
|
208
|
+
*/
|
|
209
|
+
async function runSwarmSimulation(scenario, stakeholders, paths, config) {
|
|
210
|
+
const roundCount = config.rounds ?? 3;
|
|
211
|
+
const model = config.reaction_model ?? "mixed";
|
|
212
|
+
// Filter stakeholders if specific ones requested
|
|
213
|
+
const activeStakeholders = config.simulate_stakeholders
|
|
214
|
+
? stakeholders.filter((s) => config.simulate_stakeholders.includes(s.id))
|
|
215
|
+
: stakeholders;
|
|
216
|
+
// Use the first (most likely) path for simulation
|
|
217
|
+
const primaryPath = paths[0];
|
|
218
|
+
if (!primaryPath) {
|
|
219
|
+
return {
|
|
220
|
+
rounds: [],
|
|
221
|
+
trajectory: "stabilizing",
|
|
222
|
+
inflection_points: ["No reasoning paths available for simulation"],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// Run simulation rounds
|
|
226
|
+
const rounds = [];
|
|
227
|
+
for (let round = 0; round < roundCount; round++) {
|
|
228
|
+
const reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
|
|
229
|
+
const emergentDynamics = detectEmergentDynamics(reactions);
|
|
230
|
+
rounds.push({
|
|
231
|
+
round,
|
|
232
|
+
reactions,
|
|
233
|
+
emergent_dynamics: emergentDynamics,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// Analyze overall trajectory
|
|
237
|
+
const trajectory = analyzeTrajectory(rounds);
|
|
238
|
+
const inflectionPoints = findInflectionPoints(rounds);
|
|
239
|
+
return {
|
|
240
|
+
rounds,
|
|
241
|
+
trajectory,
|
|
242
|
+
inflection_points: inflectionPoints,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* POST /reason API — Type Contracts
|
|
4
|
+
*
|
|
5
|
+
* The external reasoning API that makes Echelon infrastructure.
|
|
6
|
+
* Any autonomous agent, application, or service can call POST /reason
|
|
7
|
+
* with a scenario and constraints, and receive structured reasoning output.
|
|
8
|
+
*
|
|
9
|
+
* Design principles:
|
|
10
|
+
* - Stateless: no server-side storage of scenarios or results
|
|
11
|
+
* - BYOK: caller provides their own API key for the underlying LLM
|
|
12
|
+
* - Governance traces included in every response (open governance layer)
|
|
13
|
+
* - Echelon reasoning engine is private/proprietary
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|