@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,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 });