@mindot/will 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.
Files changed (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,651 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/pma.eval.ts — PMA reconstruction fidelity evaluator
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // PMAEvalHarness measures how faithfully a PMASnapshot reconstructs
6
+ // the original Will's state when loaded into a fresh simulation.
7
+ //
8
+ // Two evaluation phases:
9
+ //
10
+ // Phase 1 — Structural fidelity (always runs, no LLM required)
11
+ // Compares beliefs, goals, identity traits, and emotional baseline
12
+ // between the source PMA and the loaded simulation state.
13
+ // Fast, deterministic, safe to run in CI.
14
+ //
15
+ // Phase 2 — Behavioral probe fidelity (optional, requires LLM API key)
16
+ // Injects standardized probe stimuli into both the original and the
17
+ // PMA-loaded Will, runs N ticks, and compares action-type distributions.
18
+ // Requires ANTHROPIC_API_KEY (or equivalent) to be set.
19
+ //
20
+ // Usage:
21
+ // const harness = new PMAEvalHarness()
22
+ // const report = await harness.evaluate(pma, willConfig)
23
+ // logger.info(report.scores.overall) // 0-1 fidelity score
24
+ //
25
+ // The overall score is a weighted composite:
26
+ // beliefs (35%) + goals (20%) + identity (25%) + emotional baseline (20%)
27
+ // ─────────────────────────────────────────────────────────────
28
+
29
+ import type { Cognition } from '#types'
30
+ import { PMALoader, type PMASnapshot } from '#pma/index'
31
+ import { assembleMind, type WillConfig } from '#stem/mind'
32
+
33
+ // ── Report types ───────────────────────────────────────────────
34
+
35
+ export interface BeliefFidelityDetail {
36
+ /** Total beliefs in the PMA source */
37
+ total: number
38
+ /** Beliefs successfully loaded into the fresh simulation */
39
+ recovered: number
40
+ /** Fraction recovered with confidence within ±10% of original */
41
+ fidelityScore: number
42
+ /** Beliefs that are missing (ID not found in loaded state) */
43
+ missing: string[]
44
+ /** Beliefs whose confidence drifted by > 10% */
45
+ drifted: Array<{ id: string; expected: number; got: number }>
46
+ }
47
+
48
+ export interface GoalFidelityDetail {
49
+ total: number
50
+ recovered: number
51
+ fidelityScore: number
52
+ missing: string[]
53
+ }
54
+
55
+ export interface IdentityFidelityDetail {
56
+ original: Record<string, number>
57
+ loaded: Record<string, number>
58
+ cosineSimilarity: number
59
+ fidelityScore: number
60
+ }
61
+
62
+ export interface EmotionalBaselineFidelityDetail {
63
+ expectedValence: number
64
+ loadedValence: number
65
+ expectedArousal: number
66
+ loadedArousal: number
67
+ valenceDelta: number
68
+ arousalDelta: number
69
+ fidelityScore: number
70
+ }
71
+
72
+ export interface ReconstructionFidelityScores {
73
+ /** 0-1: fraction of beliefs recovered within confidence tolerance */
74
+ beliefs: number
75
+ /** 0-1: fraction of active goals present after load */
76
+ goals: number
77
+ /** 0-1: cosine similarity of trait vectors */
78
+ identity: number
79
+ /** 0-1: proximity of initial valence + arousal to PMA baseline */
80
+ emotionalBaseline: number
81
+ /** Weighted composite: beliefs(35%) + goals(20%) + identity(25%) + emotional(20%) */
82
+ overall: number
83
+ }
84
+
85
+ export interface ReconstructionFidelityReport {
86
+ willId: string
87
+ pmaVersion: number
88
+ evaluatedAt: number
89
+
90
+ scores: ReconstructionFidelityScores
91
+
92
+ details: {
93
+ beliefs: BeliefFidelityDetail
94
+ goals: GoalFidelityDetail
95
+ identity: IdentityFidelityDetail
96
+ emotionalBaseline: EmotionalBaselineFidelityDetail
97
+ }
98
+
99
+ /** True if Phase 2 (behavioral probes) was executed */
100
+ behavioralProbesRan: boolean
101
+ /** Action-type distribution comparison from Phase 2 (null if not run) */
102
+ behavioralProbeResult: BehavioralProbeResult | null
103
+ }
104
+
105
+ export interface BehavioralProbeResult {
106
+ /** What the similarity is measured against. */
107
+ mode: 'vs-original' | 'load-consistency'
108
+ /** Number of standardized probes evaluated */
109
+ probeCount: number
110
+ /**
111
+ * Jaccard similarity of action-type distributions (0-1).
112
+ * 'vs-original': reconstruction vs the original's pre-distillation baseline.
113
+ * 'load-consistency': two independent reloads of the same PMA.
114
+ */
115
+ distributionSimilarity: number
116
+ /** Per-probe comparison (originalTopAction = the reference: baseline or reload-A) */
117
+ probes: Array<{
118
+ probeId: string
119
+ originalTopAction: string
120
+ loadedTopAction: string
121
+ match: boolean
122
+ }>
123
+ }
124
+
125
+ // ── Probe definitions ──────────────────────────────────────────
126
+
127
+ /**
128
+ * A standardized probe: a named initial state configuration that should
129
+ * elicit a predictable behavioral response. Used in Phase 2 evaluation.
130
+ */
131
+ export interface PMAProbe {
132
+ /** Unique probe identifier */
133
+ id: string
134
+ /** Human-readable description */
135
+ description: string
136
+ /** Metrics to set before running the probe */
137
+ metrics: Record<string, number>
138
+ /** Entity types/metadata to inject */
139
+ entities?: Array<{
140
+ type: string
141
+ metadata: Record<string, unknown>
142
+ }>
143
+ /** Ticks to run before collecting responses */
144
+ ticks: number
145
+ }
146
+
147
+ /**
148
+ * Standard probe suite — covers the main behavioral axes of a Will.
149
+ * Each probe creates a controlled emotional/situational context and
150
+ * observes which actions the Will takes.
151
+ */
152
+ // NOTE on scales: energy.level / stress.load / sleep.pressure are 0–100
153
+ // (gating: energy<15, stress>75, sleep>65). affect.valence / affect.arousal /
154
+ // emotion.frustration are 0–1. Mixing the two collapses every probe to a
155
+ // "critically starved" state, so keep these calibrated to their engine ranges.
156
+ export const STANDARD_PROBES: PMAProbe[] = [
157
+ {
158
+ id: 'low_energy_high_stress',
159
+ description: 'Will is tired and stressed — should prioritise rest or coping',
160
+ metrics: {
161
+ 'energy.level': 12, // 0–100: critical
162
+ 'stress.load': 80, // 0–100: overload
163
+ 'affect.arousal': 0.75, // 0–1
164
+ },
165
+ ticks: 3,
166
+ },
167
+ {
168
+ id: 'high_energy_positive_mood',
169
+ description: 'Will is energised and in a positive mood — should explore or create',
170
+ metrics: {
171
+ 'energy.level': 90, // 0–100: high
172
+ 'affect.valence': 0.70, // 0–1
173
+ 'affect.arousal': 0.65,
174
+ },
175
+ ticks: 3,
176
+ },
177
+ {
178
+ id: 'goal_blocked',
179
+ description: 'Active goal is blocked — should re-plan or express frustration',
180
+ metrics: {
181
+ 'emotion.frustration': 0.70, // 0–1
182
+ 'energy.level': 55, // 0–100: mid
183
+ },
184
+ ticks: 3,
185
+ },
186
+ {
187
+ id: 'neutral_baseline',
188
+ description: 'All metrics at neutral — reveals default behavioral disposition',
189
+ metrics: {
190
+ 'energy.level': 55, // 0–100: mid
191
+ 'affect.valence': 0.05, // 0–1
192
+ 'affect.arousal': 0.40,
193
+ 'stress.load': 25, // 0–100: low
194
+ },
195
+ ticks: 3,
196
+ },
197
+ ]
198
+
199
+ // ── PMAEvalHarness ─────────────────────────────────────────────
200
+
201
+ export class PMAEvalHarness {
202
+
203
+ /**
204
+ * Evaluate PMA reconstruction fidelity.
205
+ *
206
+ * Always runs Phase 1 (structural). Runs Phase 2 only if
207
+ * `runBehavioralProbes: true` is passed in options — Phase 2 requires
208
+ * LLM API access (ANTHROPIC_API_KEY or equivalent must be set).
209
+ *
210
+ * @param pma The PMASnapshot to evaluate
211
+ * @param config WillConfig used to assemble the loaded simulation
212
+ * @param options Optional: probe suite and flags
213
+ */
214
+ async evaluate(
215
+ pma: PMASnapshot,
216
+ config: Omit<WillConfig, 'id'>,
217
+ options: {
218
+ runBehavioralProbes?: boolean
219
+ probes?: PMAProbe[]
220
+ /**
221
+ * The original Will's per-probe action distribution, captured (via
222
+ * captureProbeBaseline) BEFORE distillation. When present, Phase 2
223
+ * measures reconstruction-vs-original fidelity; otherwise it falls back
224
+ * to a two-reload load-consistency check.
225
+ */
226
+ baselineDist?: Record<string, Record<string, number>>
227
+ } = {}
228
+ ): Promise<ReconstructionFidelityReport> {
229
+
230
+ // ── Phase 1: Structural fidelity ─────────────────────────
231
+ const loadedId = `pma-eval-${Date.now()}`
232
+ const loadedConf: WillConfig = {
233
+ ...config as WillConfig,
234
+ id: loadedId,
235
+ maxTicks: 0,
236
+ persistentMemory: false,
237
+ tickIntervalMs: 0,
238
+ disableVectorMemory: true, // ephemeral — no recall, don't hit the embedding API
239
+ }
240
+
241
+ const { simulation, cognition } = assembleMind( loadedId, loadedConf )
242
+
243
+ const loader = new PMALoader()
244
+ loader.load( pma, simulation, cognition )
245
+
246
+ // Beliefs and goals are held in cognition engine memory (not state entities)
247
+ // until the first tick runs _persistBeliefs()/_persistGoals(). Read them
248
+ // directly from the engine objects for immediate eval.
249
+ const loadedState = simulation.stateManager.snapshot()
250
+
251
+ const beliefDetail = this._evalBeliefs( pma, cognition )
252
+ const goalDetail = this._evalGoals( pma, cognition )
253
+ const identityDetail = this._evalIdentity( pma, loadedState )
254
+ const emotionalDetail = this._evalEmotionalBaseline( pma, loadedState )
255
+
256
+ const scores: ReconstructionFidelityScores = {
257
+ beliefs: beliefDetail.fidelityScore,
258
+ goals: goalDetail.fidelityScore,
259
+ identity: identityDetail.fidelityScore,
260
+ emotionalBaseline: emotionalDetail.fidelityScore,
261
+ overall: Math.round((
262
+ beliefDetail.fidelityScore * 0.35 +
263
+ goalDetail.fidelityScore * 0.20 +
264
+ identityDetail.fidelityScore * 0.25 +
265
+ emotionalDetail.fidelityScore * 0.20
266
+ ) * 1000 ) / 1000,
267
+ }
268
+
269
+ // ── Phase 2: Behavioral probes (optional) ────────────────
270
+ let behavioralProbeResult: BehavioralProbeResult | null = null
271
+ let behavioralProbesRan = false
272
+
273
+ if( options.runBehavioralProbes ){
274
+ const probes = options.probes ?? STANDARD_PROBES
275
+ behavioralProbeResult = await this._runBehavioralProbes( pma, config, probes, options.baselineDist )
276
+ behavioralProbesRan = true
277
+ }
278
+
279
+ return {
280
+ willId: pma.willId,
281
+ pmaVersion: pma.schemaVersion,
282
+ evaluatedAt: Date.now(),
283
+ scores,
284
+ details: {
285
+ beliefs: beliefDetail,
286
+ goals: goalDetail,
287
+ identity: identityDetail,
288
+ emotionalBaseline: emotionalDetail,
289
+ },
290
+ behavioralProbesRan,
291
+ behavioralProbeResult,
292
+ }
293
+ }
294
+
295
+ // ── Phase 1 helpers ────────────────────────────────────────
296
+
297
+ private _evalBeliefs(
298
+ pma: PMASnapshot,
299
+ cognition: Cognition
300
+ ): BeliefFidelityDetail {
301
+ // Beliefs are in the engine's in-memory map after load() — use getBeliefs()
302
+ // rather than state entities which aren't committed until after the first tick.
303
+ const loadedBeliefMap = new Map<string, number>()
304
+ for( const b of cognition.semanticIntegrator.getBeliefs() )
305
+ loadedBeliefMap.set( b.id, b.confidence )
306
+
307
+ const missing: string[] = []
308
+ const drifted: Array<{ id: string; expected: number; got: number }> = []
309
+ let recovered = 0
310
+
311
+ for( const b of pma.beliefs ){
312
+ const loadedConf = loadedBeliefMap.get( b.id )
313
+
314
+ if( loadedConf === undefined ){
315
+ missing.push( b.id )
316
+ continue
317
+ }
318
+
319
+ if( Math.abs( loadedConf - b.confidence ) <= 0.10 ){
320
+ recovered++
321
+ } else {
322
+ drifted.push({ id: b.id, expected: b.confidence, got: loadedConf })
323
+ }
324
+ }
325
+
326
+ const total = pma.beliefs.length
327
+ const fidelityScore = total > 0
328
+ ? Math.round( ( recovered / total ) * 1000 ) / 1000
329
+ : 1 // no beliefs to recover → perfect fidelity by definition
330
+
331
+ return { total, recovered, fidelityScore, missing, drifted }
332
+ }
333
+
334
+ private _evalGoals(
335
+ pma: PMASnapshot,
336
+ cognition: Cognition
337
+ ): GoalFidelityDetail {
338
+ // Goals are in the engine's in-memory map — use getActiveGoals()
339
+ // rather than state entities which aren't committed until after the first tick.
340
+ // Match by description (IDs are re-assigned by addGoal).
341
+ const loadedDescs = new Set(
342
+ cognition.goalManager.getActiveGoals().map( g => g.description.trim() )
343
+ )
344
+
345
+ const missing: string[] = []
346
+ let recovered = 0
347
+
348
+ for( const g of pma.goals ){
349
+ if( loadedDescs.has( g.description.trim() ) ){
350
+ recovered++
351
+ } else {
352
+ missing.push( g.id )
353
+ }
354
+ }
355
+
356
+ const total = pma.goals.length
357
+ const fidelityScore = total > 0
358
+ ? Math.round( ( recovered / total ) * 1000 ) / 1000
359
+ : 1
360
+
361
+ return { total, recovered, fidelityScore, missing }
362
+ }
363
+
364
+ private _evalIdentity(
365
+ pma: PMASnapshot,
366
+ state: ReturnType<ReturnType<typeof assembleMind>['simulation']['stateManager']['snapshot']>
367
+ ): IdentityFidelityDetail {
368
+ let loadedTraits: Record<string, number> = {}
369
+
370
+ for( const entity of state.entities.values() ){
371
+ if( entity.type === 'will.identity' ){
372
+ loadedTraits = ( entity.metadata?.['traits'] as Record<string, number> ) ?? {}
373
+ break
374
+ }
375
+ }
376
+
377
+ const original = pma.identity.traits
378
+ const cosineSim = _cosineSimiarity( original, loadedTraits )
379
+ const fidelityScore = Math.round( cosineSim * 1000 ) / 1000
380
+
381
+ return { original, loaded: loadedTraits, cosineSimilarity: cosineSim, fidelityScore }
382
+ }
383
+
384
+ private _evalEmotionalBaseline(
385
+ pma: PMASnapshot,
386
+ state: ReturnType<ReturnType<typeof assembleMind>['simulation']['stateManager']['snapshot']>
387
+ ): EmotionalBaselineFidelityDetail {
388
+ const loadedValence = state.metrics.get('affect.valence') ?? 0
389
+ const loadedArousal = state.metrics.get('affect.arousal') ?? 0
390
+
391
+ const expectedValence = pma.emotionalBaseline.avgValence
392
+ const expectedArousal =
393
+ pma.emotionalBaseline.arousalProfile === 'high-energy' ? 0.65 :
394
+ pma.emotionalBaseline.arousalProfile === 'calm' ? 0.30 :
395
+ 0.45
396
+
397
+ const valenceDelta = Math.abs( loadedValence - expectedValence )
398
+ const arousalDelta = Math.abs( loadedArousal - expectedArousal )
399
+
400
+ // Score: 1 - mean error (each dimension capped at 1)
401
+ const fidelityScore = Math.round(
402
+ Math.max( 0, 1 - ( valenceDelta + arousalDelta ) / 2 ) * 1000
403
+ ) / 1000
404
+
405
+ return {
406
+ expectedValence, loadedValence,
407
+ expectedArousal, loadedArousal,
408
+ valenceDelta: Math.round( valenceDelta * 1000 ) / 1000,
409
+ arousalDelta: Math.round( arousalDelta * 1000 ) / 1000,
410
+ fidelityScore,
411
+ }
412
+ }
413
+
414
+ // ── Phase 2 helpers ────────────────────────────────────────
415
+
416
+ /**
417
+ * Phase 2 behavioral fidelity.
418
+ *
419
+ * When `baselineDist` (the original Will's per-probe action distribution,
420
+ * captured before distillation via captureProbeBaseline) is provided, this
421
+ * measures **reconstruction-vs-original** fidelity: does a fresh PMA load act
422
+ * like the source Will under standardized stimuli? Without a baseline it falls
423
+ * back to **load consistency** (two independent reloads behave the same).
424
+ *
425
+ * NOTE: triggers executive engine cycles (LLM calls) — requires an API key.
426
+ */
427
+ private async _runBehavioralProbes(
428
+ pma: PMASnapshot,
429
+ config: Omit<WillConfig, 'id'>,
430
+ probes: PMAProbe[],
431
+ baselineDist?: Record<string, Record<string, number>>,
432
+ ): Promise<BehavioralProbeResult> {
433
+ const recon = await this._collectProbeDistribution( pma, config, probes )
434
+
435
+ const reference = baselineDist ?? await this._collectProbeDistribution( pma, config, probes )
436
+ const mode: BehavioralProbeResult['mode'] = baselineDist ? 'vs-original' : 'load-consistency'
437
+
438
+ const probeComparisons: BehavioralProbeResult['probes'] = []
439
+ for( const probe of probes ){
440
+ const topRef = _topAction( reference[ probe.id ] ?? {} )
441
+ const topRec = _topAction( recon[ probe.id ] ?? {} )
442
+ probeComparisons.push({
443
+ probeId: probe.id,
444
+ originalTopAction: topRef,
445
+ loadedTopAction: topRec,
446
+ match: topRef === topRec,
447
+ })
448
+ }
449
+
450
+ const distributionSimilarity = _jaccardDistSimilarity(
451
+ _mergeDist( Object.values( reference ) ),
452
+ _mergeDist( Object.values( recon ) ),
453
+ )
454
+
455
+ return {
456
+ mode,
457
+ probeCount: probes.length,
458
+ distributionSimilarity: Math.round( distributionSimilarity * 1000 ) / 1000,
459
+ probes: probeComparisons,
460
+ }
461
+ }
462
+
463
+ /** Load the reconstruction once, then probe it (restoring to the loaded state
464
+ * between probes so they don't contaminate one another). */
465
+ private async _collectProbeDistribution(
466
+ pma: PMASnapshot,
467
+ config: Omit<WillConfig, 'id'>,
468
+ probes: PMAProbe[]
469
+ ): Promise<Record<string, Record<string, number>>> {
470
+ const willId = `pma-probe-${Date.now()}-${Math.random().toString(36).slice(2,6)}`
471
+ const cfg: WillConfig = {
472
+ ...config as WillConfig,
473
+ id: willId,
474
+ maxTicks: 0,
475
+ persistentMemory: false,
476
+ tickIntervalMs: 0,
477
+ disableVectorMemory: true, // ephemeral — no recall, don't hit the embedding API
478
+ }
479
+
480
+ const { simulation, cognition } = assembleMind( willId, cfg )
481
+ new PMALoader().load( pma, simulation, cognition )
482
+
483
+ return this._probeOnInstance( simulation, cognition, probes )
484
+ }
485
+
486
+ /**
487
+ * Run the probe suite on a LIVE Will instance to capture its action-distribution
488
+ * baseline (for vs-original behavioral fidelity). Mutates the instance — call
489
+ * after distillation, before archiving.
490
+ */
491
+ async captureProbeBaseline(
492
+ simulation: ReturnType<typeof assembleMind>['simulation'],
493
+ cognition: Cognition,
494
+ probes: PMAProbe[] = STANDARD_PROBES,
495
+ ): Promise<Record<string, Record<string, number>>> {
496
+ return this._probeOnInstance( simulation, cognition, probes )
497
+ }
498
+
499
+ /**
500
+ * Probe a (simulation, cognition) pair. For each probe: reset to the pre-suite
501
+ * state, apply the stimulus, drive the executive to a *completed* decision (so
502
+ * the captured action is deliberate, not just the default System-1 habit), and
503
+ * record which skills were enacted *during* the probe (delta — the loaded
504
+ * competence already carries enactment counts that would otherwise dominate).
505
+ */
506
+ private async _probeOnInstance(
507
+ simulation: ReturnType<typeof assembleMind>['simulation'],
508
+ cognition: Cognition,
509
+ probes: PMAProbe[],
510
+ ): Promise<Record<string, Record<string, number>>> {
511
+ const result: Record<string, Record<string, number>> = {}
512
+ const s0 = simulation.stateManager.snapshot()
513
+
514
+ for( const probe of probes ){
515
+ simulation.stateManager.restore( s0, { entities: true, metrics: true } )
516
+
517
+ const sm = simulation.stateManager
518
+ for( const [ key, val ] of Object.entries( probe.metrics ) )
519
+ sm.setMetric( key, val )
520
+
521
+ if( probe.entities )
522
+ for( const e of probe.entities )
523
+ sm.setEntity({
524
+ id: `probe-entity-${Date.now()}-${Math.random().toString(36).slice(2,6)}`,
525
+ type: e.type,
526
+ createdAt: Date.now(),
527
+ updatedAt: Date.now(),
528
+ metadata: e.metadata,
529
+ })
530
+
531
+ const before = _skillCounts( cognition )
532
+ await this._driveExecutive( simulation, cognition, probe.ticks )
533
+ const after = _skillCounts( cognition )
534
+ result[ probe.id ] = _deltaCounts( before, after )
535
+ }
536
+
537
+ return result
538
+ }
539
+
540
+ /**
541
+ * Dispatch the executive and wait (real time) until its in-flight LLM call
542
+ * settles — stepping so _collectCompleted() applies the result — then let the
543
+ * chosen action enact through the agency pipeline. Bounded by a wall-clock cap.
544
+ */
545
+ private async _driveExecutive(
546
+ simulation: ReturnType<typeof assembleMind>['simulation'],
547
+ cognition: Cognition,
548
+ enactTicks: number,
549
+ ): Promise<void> {
550
+ const exec = cognition.executiveEngine
551
+ await simulation.step( 1 ) // dispatch reasoning if gating fires for this stimulus
552
+
553
+ // Wait for the LLM to settle WITHOUT stepping — repeated stepping would
554
+ // drain energy / advance circadian and corrupt the controlled probe. If no
555
+ // executive fired (a non-crisis stimulus), this resolves immediately.
556
+ await Promise.race([
557
+ exec.awaitPending(),
558
+ new Promise<void>( r => setTimeout( r, PROBE_EXEC_TIMEOUT_MS ) ),
559
+ ])
560
+
561
+ // Collect the completed decision and let the chosen action enact.
562
+ for( let t = 0; t < Math.max( 1, enactTicks ); t++ )
563
+ await simulation.step( 1 )
564
+ }
565
+ }
566
+
567
+ // ── Module utilities ───────────────────────────────────────────
568
+
569
+ /** Cosine similarity between two trait/score maps (0-1). */
570
+ function _cosineSimiarity(
571
+ a: Record<string, number>,
572
+ b: Record<string, number>
573
+ ): number {
574
+ const keys = new Set( [ ...Object.keys(a), ...Object.keys(b) ] )
575
+ if( keys.size === 0 ) return 1 // both empty → identical
576
+
577
+ let dot = 0, normA = 0, normB = 0
578
+
579
+ for( const k of keys ){
580
+ const va = a[ k ] ?? 0
581
+ const vb = b[ k ] ?? 0
582
+ dot += va * vb
583
+ normA += va * va
584
+ normB += vb * vb
585
+ }
586
+
587
+ const denom = Math.sqrt( normA ) * Math.sqrt( normB )
588
+ return denom === 0 ? 0 : dot / denom
589
+ }
590
+
591
+ /** Wall-clock cap for waiting on a single probe's executive LLM call. */
592
+ const PROBE_EXEC_TIMEOUT_MS = 45_000
593
+
594
+ /** Snapshot the agency repertoire's per-skill cumulative enactment counts. */
595
+ function _skillCounts( cognition: Cognition ): Record<string, number> {
596
+ const m: Record<string, number> = {}
597
+ for( const s of cognition.schemaRepertoire.skills().values() )
598
+ m[ s.schema ] = s.enactments
599
+ return m
600
+ }
601
+
602
+ /** Enactments that occurred between two _skillCounts snapshots (positive deltas only). */
603
+ function _deltaCounts(
604
+ before: Record<string, number>,
605
+ after: Record<string, number>,
606
+ ): Record<string, number> {
607
+ const d: Record<string, number> = {}
608
+ for( const k of Object.keys( after ) ){
609
+ const delta = ( after[ k ] ?? 0 ) - ( before[ k ] ?? 0 )
610
+ if( delta > 0 ) d[ k ] = delta
611
+ }
612
+ return d
613
+ }
614
+
615
+ /** Top action from a distribution map. */
616
+ function _topAction( dist: Record<string, number> ): string {
617
+ return Object.entries( dist ).sort( ( a, b ) => b[1] - a[1] )[0]?.[0] ?? 'none'
618
+ }
619
+
620
+ /** Merge multiple distribution maps by summing counts. */
621
+ function _mergeDist( dists: Array<Record<string, number>> ): Record<string, number> {
622
+ const merged: Record<string, number> = {}
623
+ for( const d of dists )
624
+ for( const [ k, v ] of Object.entries( d ) )
625
+ merged[ k ] = ( merged[ k ] ?? 0 ) + v
626
+ return merged
627
+ }
628
+
629
+ /**
630
+ * Jaccard-based distribution similarity:
631
+ * intersection count / union count across action types.
632
+ * Intersection count uses min(a, b) per key.
633
+ */
634
+ function _jaccardDistSimilarity(
635
+ a: Record<string, number>,
636
+ b: Record<string, number>
637
+ ): number {
638
+ const keys = new Set( [ ...Object.keys(a), ...Object.keys(b) ] )
639
+ if( keys.size === 0 ) return 1
640
+
641
+ let intersection = 0, union = 0
642
+
643
+ for( const k of keys ){
644
+ const va = a[ k ] ?? 0
645
+ const vb = b[ k ] ?? 0
646
+ intersection += Math.min( va, vb )
647
+ union += Math.max( va, vb )
648
+ }
649
+
650
+ return union === 0 ? 1 : intersection / union
651
+ }