@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,1082 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/pma.ts — Personal Mind Model (PMA) layer
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // A Persistent Mind Artifact (PMA) is a compressed, portable, versioned JSON
6
+ // artifact that captures a Will's psychological profile and enduring self — beliefs,
7
+ // goals, identity traits, emotional baseline, behavioral fingerprints, and relationship
8
+ // stubs — sufficient to bootstrap a fresh simulation with the same personality
9
+ // and memory trajectory.
10
+ //
11
+ // PMA = compressed, portable identity artifact that captures a Will's
12
+ // accumulated state well enough to bootstrap a new simulation with the
13
+ // same beliefs, personality, goals, and emotional baseline.
14
+ //
15
+ // Two classes:
16
+ // PMADistiller — reads simulation state + JSONL profile logs → PMASnapshot
17
+ // PMALoader — seeds a fresh simulation from a PMASnapshot
18
+ //
19
+ // Artifact format: plain JSON, ~10–50 KB depending on belief count.
20
+ // schemaVersion is bumped on any breaking change to PMASnapshot.
21
+ //
22
+ // Typical usage in WillManager:
23
+ // const pma = distiller.distill(willId, willName, state, sessionId)
24
+ // fs.writeFileSync(path, JSON.stringify(pma))
25
+ //
26
+ // // — later, new instance —
27
+ // const pma = JSON.parse(fs.readFileSync(path, 'utf8'))
28
+ // loader.load(pma, simulation, cognition)
29
+ // ─────────────────────────────────────────────────────────────
30
+
31
+ import { readFileSync, existsSync } from 'node:fs'
32
+ import { join } from 'node:path'
33
+
34
+ import type { SimulationState } from '#core/types'
35
+ import type { DefaultSimulation } from '#core/simulation'
36
+ import type { Cognition } from '#types'
37
+ import type {
38
+ Belief,
39
+ BeliefHistoryEntry,
40
+ } from '#faculties/semantic.engine/integrator'
41
+ import { PERSONA_PRIOR_ID, PERSONA_PRIOR_TYPE } from '#cognition/persona.prior'
42
+ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
43
+ import {
44
+ distillCompetence,
45
+ loadCompetence,
46
+ type CompetenceSnapshot,
47
+ } from '#agency/competence.codec'
48
+
49
+ // ── Schema version ─────────────────────────────────────────────
50
+ // Bump this when any field is removed or semantically changed.
51
+ // Additive field additions do not require a bump.
52
+
53
+ export const PMA_SCHEMA_VERSION = 2 // BUMPED due to new fields
54
+
55
+ // ── PMA types ──────────────────────────────────────────────────
56
+
57
+ export interface PMAIdentity {
58
+ /** Core identity prompt */
59
+ prompt: string
60
+ values: string[]
61
+ traits: Record<string, number>
62
+ /**
63
+ * Per-trait self-knowledge — personal baseline EMA + recent-shift direction. Persisted
64
+ * so the Will's sense of its OWN norm (graded salience B/C) carries across sessions
65
+ * instead of rebuilding from the population prior on every cold load. Optional: absent
66
+ * in older PMAs (and the self-model rebuilds it either way).
67
+ */
68
+ traitStats?: Record<string, { mean: number; shiftDir: number; shiftTick: number }>
69
+ style: string
70
+ version: number
71
+ /** Social orientation: 'gregarious', 'ambivert', 'reserved' */
72
+ socialOrientation?: string
73
+ /** Trust propensity (0-1) — how quickly the Will trusts new agents */
74
+ trustPropensity?: number
75
+ /** Memory persistence (0-1) — influences forgetting curve rate */
76
+ memoryPersistence?: number
77
+ }
78
+
79
+ export interface PMABelief {
80
+ id: string
81
+ statement: string
82
+ category: string
83
+ confidence: number
84
+ supportingEpisodes: number
85
+ tags: string[]
86
+ /** Up to 20 history entries — see BeliefHistoryEntry in semantic.integrator.ts */
87
+ history: BeliefHistoryEntry[]
88
+ }
89
+
90
+ export interface PMAGoal {
91
+ id: string
92
+ description: string
93
+ priority: number
94
+ progress: number
95
+ status: string
96
+ tags: string[]
97
+ /** 'metric' | 'action' | 'epistemic' — preserved so the restored goal completes via its original mechanism */
98
+ completionType: 'metric' | 'action' | 'epistemic'
99
+ /** Condition expression for 'metric' goals, e.g. "stress.load < 0.3" */
100
+ completionCondition: string | undefined
101
+ }
102
+
103
+ export interface PMAEmotionalBaseline {
104
+ /** Dominant mood label averaged over last sessions: 'positive' | 'neutral' | 'negative' */
105
+ dominantMood: string
106
+ /** Mean valence across last N sessions (-1 to +1) */
107
+ avgValence: number
108
+ /** Arousal activation profile: 'high-energy' | 'moderate' | 'calm' */
109
+ arousalProfile: string
110
+ /** Mean spike events per session */
111
+ avgSpikeFrequency: number
112
+ /** Inherited temperament from identity (0-1, unaffected by recent sessions) */
113
+ temperamentValence: number // -1 to 1, where 0 = neutral, >0 = optimistic baseline
114
+ /** Emotional reactivity (0-1) — how strongly the Will responds to events */
115
+ reactivity: number
116
+ }
117
+
118
+ export interface PMABehavioral {
119
+ /** Top 3 action effector names by frequency across recent sessions */
120
+ topActions: string[]
121
+ /** Mean executive.confidence across recent sessions */
122
+ avgConfidence: number
123
+ /** Goal completion rate — null if no goals observed */
124
+ completionRate: number | null
125
+ /** Risk tolerance inferred from action outcomes (0=cautious, 1=risk-seeking) */
126
+ riskTolerance?: number
127
+ /** Exploration rate (0-1) — how often the Will tries novel actions */
128
+ explorationRate?: number
129
+ /** Impulsivity (0-1) — tendency to act without deliberation */
130
+ impulsivity?: number
131
+ }
132
+
133
+ /**
134
+ * A relationship stub — enough data to re-seed the Will's model of another
135
+ * agent on first session start, preventing a cold-social-reset.
136
+ *
137
+ * Combines `attachment.bond` (emotional closeness) and `reputation` (social trust)
138
+ * into a single record per will. Both can be present, or just one.
139
+ *
140
+ * At load time, `PMALoader` writes these as state entities in the formats that
141
+ * `AttachmentEvaluator._restoreFromState()` and `ReputationTracker._restoreFromState()`
142
+ * already read on the first tick — no new engine APIs required.
143
+ */
144
+ export interface PMARelationshipStub {
145
+ /** The other agent's ID */
146
+ keid: string
147
+ /** Optional display name for readability */
148
+ agentName?: string
149
+ /**
150
+ * Compact digest of the last conversation, derived from the most recent
151
+ * consolidated conversation.exchange episode. On load it is re-seeded as a
152
+ * conversation.exchange working-memory item so the Will recalls it through the
153
+ * normal memory pipeline (consolidator → vector → unified recall).
154
+ */
155
+ lastConversationDigest?: string
156
+
157
+ // ── Attachment bond ──────────────────────────────────────
158
+ attachment?: {
159
+ attachmentStrength: number
160
+ trustLevel: number
161
+ positiveRatio: number
162
+ interactionCount: number
163
+ sharedExperiences: number
164
+ dependency: number
165
+ }
166
+
167
+ // ── Reputation ───────────────────────────────────────────
168
+ reputation?: {
169
+ reliability: number
170
+ cooperativeness: number
171
+ socialStanding: number
172
+ trustworthiness: number
173
+ interactionCount: number
174
+ positiveInteractions: number
175
+ negativeInteractions: number
176
+ confidence: number
177
+ }
178
+
179
+ // ── Mental model (theory of mind) ────────────────────────
180
+ // The crystallised *gist* of how the Will modelled this mind — not the full belief
181
+ // trail (that fades, soul-true). Rides the attachment/reputation-weighted top-N, so a
182
+ // mind only carries across a restart when it also mattered enough to bond/track.
183
+ mentalModel?: {
184
+ modelConfidence: number
185
+ dominantIntention: string | null
186
+ estimatedEmotion: string
187
+ }
188
+
189
+ // ── Known-entity dossier (the unified node — perceptual + conscious layer) ──
190
+ // The crystallised summary of who/what this is: kind, learned name, how familiar, how
191
+ // it feels, how well-resolved. The senses' encounter trail itself fades; what carries
192
+ // across is the *residue* — that this someone matters and roughly who they are.
193
+ dossier?: {
194
+ kind: 'sentient' | 'thing'
195
+ name?: string
196
+ familiarity: number
197
+ valence: number
198
+ reliability: number
199
+ encounterCount: number
200
+ resolutionConfidence: number
201
+ }
202
+ }
203
+
204
+ /**
205
+ * PMASnapshot — the portable identity artifact.
206
+ *
207
+ * Top-level contract:
208
+ * - ~10–50 KB for a typical Will (50 beliefs × history)
209
+ * - Self-contained: can bootstrap a Will with no other files
210
+ * - Versioned: schemaVersion guards against stale artifacts
211
+ */
212
+ export interface PMASnapshot {
213
+ /** Schema version — see PMA_SCHEMA_VERSION */
214
+ schemaVersion: number
215
+ willId: string
216
+ willName: string
217
+ /** Unix ms when this snapshot was produced */
218
+ distilledAt: number
219
+ /** Session ID that triggered distillation */
220
+ sourceSessionId: string
221
+
222
+ identity: PMAIdentity
223
+ /** Top 50 beliefs ranked by confidence × log(1 + supportingEpisodes) */
224
+ beliefs: PMABelief[]
225
+ /** Top 10 active/in_progress goals by priority */
226
+ goals: PMAGoal[]
227
+ emotionalBaseline: PMAEmotionalBaseline
228
+ behavioral: PMABehavioral
229
+ /** Top 20 relationship stubs (bonds + reputation) by interaction count */
230
+ relationships: PMARelationshipStub[]
231
+ /** Total episodic memory count at snapshot time (metadata only — episodes not stored) */
232
+ episodicCount: number
233
+
234
+ /**
235
+ * Learned self-tuning from the metacognition closing cycle — the accommodation
236
+ * the Will has made to itself. `configPriors` are the bounded persona-prior
237
+ * deltas over engine config (engineConfigId → param → delta); `calibrationBias`
238
+ * is the per-domain confidence calibration. Omitted when nothing has been
239
+ * learned yet. Carrying these makes the *accreted* persona portable, not just
240
+ * the seeded identity.
241
+ */
242
+ persona?: {
243
+ configPriors: Record<string, Record<string, number>>
244
+ calibrationBias: Record<string, number>
245
+ }
246
+
247
+ /**
248
+ * The competence layer — the Will's learned skills (habit strength, value,
249
+ * param priors) and the composite schemas it invented. This is what makes a
250
+ * re-embodied Will *act like itself* (its habits, its proceduralized skills),
251
+ * not just believe/feel like itself. Omitted when nothing has been learned.
252
+ * See #agency/pma/competence.codec.
253
+ */
254
+ competence?: CompetenceSnapshot
255
+
256
+ meta: {
257
+ beliefCount: number
258
+ goalCount: number
259
+ relationshipCount: number
260
+ /** How many sessions contributed to emotional / behavioral baselines */
261
+ sessionSummaryCount: number
262
+ }
263
+ }
264
+
265
+ // ── PMADistiller ───────────────────────────────────────────────
266
+
267
+ /**
268
+ * Reads simulation state and JSONL profile logs to
269
+ * produce a PMASnapshot.
270
+ *
271
+ * Profile logs read (last 5 session summaries each):
272
+ * data/wills/{willId}/profiles/emotional_biography.jsonl
273
+ * data/wills/{willId}/profiles/behavioral.jsonl
274
+ */
275
+ export class PMADistiller {
276
+
277
+ /**
278
+ * Produce a PMASnapshot for a Will.
279
+ *
280
+ * @param willId The Will's ID
281
+ * @param willName The Will's display name
282
+ * @param state Current simulation state (from stateManager.snapshot())
283
+ * @param sessionId Active session ID — recorded as provenance
284
+ * @param dataDir Root data dir (defaults to WILL_DATA_DIR env or './data')
285
+ * @param repertoire The agency competence layer (in-memory manager). When
286
+ * provided, the Will's learned skills + composite schemas are
287
+ * distilled into the snapshot. Omit to skip the competence layer.
288
+ */
289
+ distill(
290
+ willId: string,
291
+ willName: string,
292
+ state: SimulationState,
293
+ sessionId: string,
294
+ dataDir: string = process.env[ 'WILL_DATA_DIR' ] ?? './data',
295
+ repertoire?: SchemaRepertoire,
296
+ ): PMASnapshot {
297
+ const identity = this._extractIdentity( state )
298
+ const beliefs = this._extractBeliefs( state )
299
+ const goals = this._extractGoals( state )
300
+ const relationships = this._extractRelationships( state )
301
+ const episodicCount = state.metrics.get('memory.episodic_total') ?? 0
302
+
303
+ const emotionalBio = this._readEmotionalBio( willId, dataDir )
304
+ const behavioral = this._readBehavioral( willId, dataDir )
305
+
306
+ // Enhance identity with inferred social/behavioral parameters
307
+ const enhancedIdentity = this._enhanceIdentity( identity, behavioral )
308
+
309
+ const persona = this._extractPersona( state )
310
+
311
+ // Competence layer — learned skills + invented composite schemas.
312
+ const competence = repertoire ? distillCompetence( repertoire ) : undefined
313
+ const carryCompetence = competence && ( competence.skills.length > 0 || competence.composites.length > 0 )
314
+
315
+ return {
316
+ schemaVersion: PMA_SCHEMA_VERSION,
317
+ willId,
318
+ willName,
319
+ distilledAt: Date.now(),
320
+ sourceSessionId: sessionId,
321
+ identity: enhancedIdentity,
322
+ beliefs,
323
+ goals,
324
+ emotionalBaseline: emotionalBio.baseline,
325
+ behavioral: behavioral.fingerprint,
326
+ relationships,
327
+ episodicCount,
328
+ ...( persona ? { persona } : {} ),
329
+ ...( carryCompetence ? { competence } : {} ),
330
+ meta: {
331
+ beliefCount: beliefs.length,
332
+ goalCount: goals.length,
333
+ relationshipCount: relationships.length,
334
+ sessionSummaryCount: Math.max( emotionalBio.sessionsRead, behavioral.sessionsRead ),
335
+ },
336
+ }
337
+ }
338
+
339
+ // ── Private extraction helpers ────────────────────────────
340
+
341
+ /**
342
+ * Extract the learned self-tuning (metacognition closing cycle): the
343
+ * persona-prior config deltas + the confidence-calibration curve. Returns
344
+ * undefined when neither has accreted anything, so the snapshot omits the field.
345
+ */
346
+ private _extractPersona( state: SimulationState ): PMASnapshot['persona'] {
347
+ const priorMeta = state.entities.get( PERSONA_PRIOR_ID )?.metadata as { priors?: Record<string, Record<string, number>> } | undefined
348
+ const calibMeta = state.entities.get( 'calibration-state' )?.metadata as { domainBias?: Record<string, number> } | undefined
349
+
350
+ const configPriors = priorMeta?.priors ?? {}
351
+ const calibrationBias = calibMeta?.domainBias ?? {}
352
+
353
+ if( Object.keys( configPriors ).length === 0 && Object.keys( calibrationBias ).length === 0 )
354
+ return undefined
355
+
356
+ return { configPriors, calibrationBias }
357
+ }
358
+
359
+ private _extractIdentity( state: SimulationState ): PMAIdentity {
360
+ for( const entity of state.entities.values() ){
361
+ if( entity.type === 'will.identity' ){
362
+ const m = entity.metadata ?? {}
363
+ return {
364
+ prompt: ( m['prompt'] as string ) ?? '',
365
+ values: ( m['values'] as string[] ) ?? [],
366
+ traits: ( m['traits'] as Record<string, number> ) ?? {},
367
+ traitStats: ( m['traitStats'] as PMAIdentity['traitStats'] ) ?? undefined,
368
+ style: ( m['style'] as string ) ?? '',
369
+ version: ( m['version'] as number ) ?? 1,
370
+ // These may not exist in older PMAs, but that's fine
371
+ socialOrientation: ( m['socialOrientation'] as string ) ?? undefined,
372
+ trustPropensity: ( m['trustPropensity'] as number ) ?? undefined,
373
+ memoryPersistence: ( m['memoryPersistence'] as number ) ?? undefined,
374
+ }
375
+ }
376
+ }
377
+ return { prompt: '', values: [], traits: {}, style: '', version: 1 }
378
+ }
379
+
380
+ /**
381
+ * Enhance identity with inferred parameters from behavioral data.
382
+ * This bridges the gap between observed behavior and latent personality traits.
383
+ */
384
+ private _enhanceIdentity(
385
+ identity: PMAIdentity,
386
+ behavioral: { fingerprint: PMABehavioral; sessionsRead: number }
387
+ ): PMAIdentity {
388
+ const enhanced = { ...identity }
389
+
390
+ // If socialOrientation not explicitly set, infer from top actions
391
+ if( !enhanced.socialOrientation && behavioral.fingerprint.topActions.length > 0 ){
392
+ const topAction = behavioral.fingerprint.topActions[0] ?? ''
393
+ if( topAction.includes( 'social' ) || topAction.includes( 'talk' ) || topAction.includes( 'text' ) ){
394
+ enhanced.socialOrientation = 'gregarious'
395
+ } else if( topAction.includes( 'reflect' ) || topAction.includes( 'meditate' ) ){
396
+ enhanced.socialOrientation = 'reserved'
397
+ } else {
398
+ enhanced.socialOrientation = 'ambivert'
399
+ }
400
+ }
401
+
402
+ // If trustPropensity not explicitly set, infer from completion rate
403
+ if( enhanced.trustPropensity === undefined && behavioral.fingerprint.completionRate !== null ){
404
+ // Higher completion rate suggests more optimistic trust in plans
405
+ enhanced.trustPropensity = Math.min( 1, ( behavioral.fingerprint.completionRate * 0.7 ) + 0.3 )
406
+ }
407
+
408
+ // If memoryPersistence not explicitly set, infer from confidence patterns
409
+ if( enhanced.memoryPersistence === undefined ){
410
+ // Higher confidence suggests more persistent memory encoding
411
+ enhanced.memoryPersistence = Math.min( 1, ( behavioral.fingerprint.avgConfidence * 0.5 ) + 0.3 )
412
+ }
413
+
414
+ return enhanced
415
+ }
416
+
417
+ private _extractBeliefs( state: SimulationState ): PMABelief[] {
418
+ const raw: PMABelief[] = []
419
+
420
+ for( const entity of state.entities.values() ){
421
+ if( entity.type !== 'belief' ) continue
422
+
423
+ const m = entity.metadata ?? {}
424
+ raw.push({
425
+ id: entity.id,
426
+ statement: ( m['statement'] as string ) ?? '',
427
+ category: ( m['category'] as string ) ?? 'general',
428
+ confidence: ( m['confidence'] as number ) ?? 0.5,
429
+ supportingEpisodes: ( m['supportingEpisodes'] as number ) ?? 0,
430
+ tags: ( m['tags'] as string[] ) ?? [],
431
+ history: ( m['history'] as BeliefHistoryEntry[] ) ?? [],
432
+ })
433
+ }
434
+
435
+ // Rank by confidence × log(1 + supportingEpisodes); cap at 50
436
+ raw.sort( ( a, b ) => {
437
+ const sa = a.confidence * Math.log( 1 + a.supportingEpisodes )
438
+ const sb = b.confidence * Math.log( 1 + b.supportingEpisodes )
439
+ return sb - sa
440
+ })
441
+
442
+ return raw.slice( 0, 50 )
443
+ }
444
+
445
+ private _extractGoals( state: SimulationState ): PMAGoal[] {
446
+ const goals: PMAGoal[] = []
447
+
448
+ for( const entity of state.entities.values() ){
449
+ if( entity.type !== 'goal' ) continue
450
+
451
+ const m = entity.metadata ?? {}
452
+ const status = ( m['status'] as string ) ?? 'active'
453
+ if( status !== 'active' && status !== 'in_progress' ) continue
454
+
455
+ goals.push({
456
+ id: entity.id,
457
+ description: ( m['description'] as string ) ?? '',
458
+ priority: ( m['priority'] as number ) ?? 0,
459
+ progress: ( m['progress'] as number ) ?? 0,
460
+ status,
461
+ tags: ( m['tags'] as string[] ) ?? [],
462
+ completionType: ( m['completionType'] as 'metric' | 'action' | 'epistemic' ) ?? 'epistemic',
463
+ completionCondition: ( m['completionCondition'] as string ) ?? undefined,
464
+ })
465
+ }
466
+
467
+ goals.sort( ( a, b ) => b.priority - a.priority )
468
+ return goals.slice( 0, 10 )
469
+ }
470
+
471
+ private _extractRelationships( state: SimulationState ): PMARelationshipStub[] {
472
+ const stubs = new Map<string, PMARelationshipStub>()
473
+ // Tracks the tick of the digest currently held per agent, so we keep only
474
+ // the most recent conversation.exchange episode as `lastConversationDigest`.
475
+ const digestTick = new Map<string, number>()
476
+
477
+ for( const entity of state.entities.values() ){
478
+ if( entity.type === 'attachment.bond' ){
479
+ const m = entity.metadata ?? {}
480
+ const keid = m['keid'] as string | undefined
481
+ if( !keid ) continue
482
+
483
+ const stub = stubs.get( keid ) ?? { keid }
484
+ stub.attachment = {
485
+ attachmentStrength: ( m['strength'] as number ) ?? 0,
486
+ trustLevel: ( m['trust'] as number ) ?? 0.5,
487
+ positiveRatio: ( m['positiveRatio'] as number ) ?? 0.5,
488
+ interactionCount: ( m['interactionCount'] as number ) ?? 0,
489
+ sharedExperiences: ( m['sharedExperiences'] as number ) ?? 0,
490
+ dependency: ( m['dependency'] as number ) ?? 0,
491
+ }
492
+ stubs.set( keid, stub )
493
+ }
494
+
495
+ else if( entity.type === 'reputation' ){
496
+ const m = entity.metadata ?? {}
497
+ const keid = m['keid'] as string | undefined
498
+ if( !keid ) continue
499
+
500
+ const stub = stubs.get( keid ) ?? { keid }
501
+ stub.agentName = ( m['name'] as string ) ?? undefined
502
+ stub.reputation = {
503
+ reliability: ( m['reliability'] as number ) ?? 0.5,
504
+ cooperativeness: ( m['cooperativeness'] as number ) ?? 0.5,
505
+ socialStanding: ( m['socialStanding'] as number ) ?? 0.5,
506
+ trustworthiness: ( m['trustworthiness'] as number ) ?? 0.5,
507
+ interactionCount: ( m['interactionCount'] as number ) ?? 0,
508
+ positiveInteractions: ( m['positiveInteractions'] as number ) ?? 0,
509
+ negativeInteractions: ( m['negativeInteractions'] as number ) ?? 0,
510
+ confidence: ( m['confidence'] as number ) ?? 0.5,
511
+ }
512
+ stubs.set( keid, stub )
513
+ }
514
+
515
+ else if( entity.type === 'theory_of_mind' ){
516
+ const m = entity.metadata ?? {}
517
+ const keid = m['keid'] as string | undefined
518
+ if( !keid ) continue
519
+
520
+ const stub = stubs.get( keid ) ?? { keid }
521
+ stub.mentalModel = {
522
+ modelConfidence: ( m['modelConfidence'] as number ) ?? 0.3,
523
+ dominantIntention: ( m['dominantIntention'] as string | null ) ?? null,
524
+ estimatedEmotion: ( m['estimatedEmotion'] as string ) ?? 'neutral',
525
+ }
526
+ stubs.set( keid, stub )
527
+ }
528
+
529
+ else if( entity.type === 'known-entity' ){
530
+ const m = entity.metadata ?? {}
531
+ const keid = m['keid'] as string | undefined
532
+ if( !keid ) continue
533
+
534
+ const stub = stubs.get( keid ) ?? { keid }
535
+ if( !stub.agentName && typeof m['name'] === 'string' ) stub.agentName = m['name'] as string
536
+ stub.dossier = {
537
+ kind: ( m['kind'] as 'sentient' | 'thing' ) ?? 'sentient',
538
+ name: ( m['name'] as string | undefined ),
539
+ familiarity: ( m['familiarity'] as number ) ?? 0,
540
+ valence: ( m['valence'] as number ) ?? 0,
541
+ reliability: ( m['reliability'] as number ) ?? 0.5,
542
+ encounterCount: ( m['encounterCount'] as number ) ?? 0,
543
+ resolutionConfidence: ( m['resolutionConfidence'] as number ) ?? 0,
544
+ }
545
+ stubs.set( keid, stub )
546
+ }
547
+
548
+ // Derive the per-agent conversation digest from consolidated
549
+ // conversation.exchange episodes (the durable record written by the
550
+ // AuditionEngine memory sink + ProactiveCommunicator). The most recent
551
+ // exchange summary wins.
552
+ else if( entity.type === 'episodic_memory'
553
+ && entity.metadata?.['sourceType'] === 'conversation.exchange' ){
554
+ const content = ( entity.metadata['content'] as Record<string, unknown> | undefined ) ?? {}
555
+ const keid = content['entityId'] as string | undefined
556
+ if( !keid ) continue
557
+
558
+ const tick = ( content['tick'] as number | undefined )
559
+ ?? ( entity.metadata['tick'] as number | undefined )
560
+ ?? 0
561
+ if( tick < ( digestTick.get( keid ) ?? -Infinity ) ) continue
562
+
563
+ const stub = stubs.get( keid ) ?? { keid }
564
+ stub.agentName ??= ( content['entityName'] as string ) ?? undefined
565
+ const digest = content['summary'] as string | undefined
566
+ if( digest ){
567
+ stub.lastConversationDigest = digest
568
+ digestTick.set( keid, tick )
569
+ }
570
+ stubs.set( keid, stub )
571
+ }
572
+ }
573
+
574
+ // Soul doctrine: what crystallises across a restart is what *mattered* — attachment is
575
+ // the primary driver, then familiarity + how resolved the referent is, with interaction
576
+ // volume only a faint tiebreaker. The most-attached/most-familiar known entities carry;
577
+ // fleeting acquaintances fade (the forgetting curve, made portable).
578
+ const salienceOf = ( s: PMARelationshipStub ): number =>
579
+ ( s.attachment?.attachmentStrength ?? 0 ) * 2
580
+ + ( s.dossier?.familiarity ?? 0 )
581
+ + ( s.dossier?.resolutionConfidence ?? 0 ) * 0.5
582
+ + Math.min( 1, ( ( s.attachment?.interactionCount ?? 0 ) + ( s.reputation?.interactionCount ?? 0 ) ) * 0.02 )
583
+
584
+ return Array.from( stubs.values() )
585
+ .sort( ( a, b ) => salienceOf( b ) - salienceOf( a ) )
586
+ .slice( 0, 20 )
587
+ }
588
+
589
+ private _readEmotionalBio(
590
+ willId: string,
591
+ dataDir: string
592
+ ): { baseline: PMAEmotionalBaseline; sessionsRead: number } {
593
+ const defaultBaseline: PMAEmotionalBaseline = {
594
+ dominantMood: 'neutral',
595
+ avgValence: 0,
596
+ arousalProfile: 'moderate',
597
+ avgSpikeFrequency: 0,
598
+ temperamentValence: 0,
599
+ reactivity: 0.5,
600
+ }
601
+
602
+ const filePath = join( dataDir, 'wills', willId, 'profiles', 'emotional_biography.jsonl' )
603
+ if( !existsSync( filePath ) ) return { baseline: defaultBaseline, sessionsRead: 0 }
604
+
605
+ const summaries = _readLastNSummaries( filePath, 'session_summary', 5 )
606
+ if( summaries.length === 0 ) return { baseline: defaultBaseline, sessionsRead: 0 }
607
+
608
+ const moodCounts: Record<string, number> = {}
609
+ let totalValence = 0
610
+ let totalSpikes = 0
611
+ let totalAvgArousal = 0
612
+ let arousalSamples = 0
613
+ let totalReactivity = 0
614
+ let reactivitySamples = 0
615
+
616
+ for( const s of summaries ){
617
+ const mood = ( s['dominantMood'] as string ) ?? 'neutral'
618
+ moodCounts[ mood ] = ( moodCounts[ mood ] ?? 0 ) + 1
619
+
620
+ totalValence += ( s['avgValence'] as number ) ?? 0
621
+ totalSpikes += ( s['spikeCount'] as number ) ?? 0
622
+
623
+ const arc = s['arousalArc'] as Record<string, number> | undefined
624
+ if( arc?.['min'] !== undefined && arc?.['max'] !== undefined ){
625
+ totalAvgArousal += ( arc['min'] + arc['max'] ) / 2
626
+ arousalSamples++
627
+ }
628
+
629
+ // Reactivity inferred from valence range (max - min) within session
630
+ const valenceArc = s['valenceArc'] as Record<string, number> | undefined
631
+ if( valenceArc?.['min'] !== undefined && valenceArc?.['max'] !== undefined ){
632
+ totalReactivity += Math.abs( valenceArc['max'] - valenceArc['min'] )
633
+ reactivitySamples++
634
+ }
635
+ }
636
+
637
+ const dominantMood = Object.entries( moodCounts )
638
+ .sort( ( a, b ) => b[1] - a[1] )[ 0 ]?.[0] ?? 'neutral'
639
+
640
+ const avgValence = totalValence / summaries.length
641
+ const avgSpikeFrequency = totalSpikes / summaries.length
642
+ const avgArousal = arousalSamples > 0 ? totalAvgArousal / arousalSamples : 0.45
643
+ const arousalProfile = avgArousal > 0.58 ? 'high-energy' : avgArousal > 0.38 ? 'moderate' : 'calm'
644
+ const reactivity = reactivitySamples > 0 ? totalReactivity / reactivitySamples : 0.5
645
+
646
+ // Temperament valence is the long-term average, not session-specific
647
+ // This could also come from identity traits (e.g., 'optimism' trait)
648
+ const temperamentValence = avgValence
649
+
650
+ return {
651
+ baseline: {
652
+ dominantMood,
653
+ avgValence: Math.round( avgValence * 1000 ) / 1000,
654
+ arousalProfile,
655
+ avgSpikeFrequency: Math.round( avgSpikeFrequency * 100 ) / 100,
656
+ temperamentValence: Math.round( temperamentValence * 1000 ) / 1000,
657
+ reactivity: Math.round( reactivity * 1000 ) / 1000,
658
+ },
659
+ sessionsRead: summaries.length,
660
+ }
661
+ }
662
+
663
+ private _readBehavioral(
664
+ willId: string,
665
+ dataDir: string
666
+ ): { fingerprint: PMABehavioral; sessionsRead: number } {
667
+ const defaultFingerprint: PMABehavioral = {
668
+ topActions: [],
669
+ avgConfidence: 0.5,
670
+ completionRate: null,
671
+ riskTolerance: 0.5,
672
+ explorationRate: 0.3,
673
+ impulsivity: 0.3,
674
+ }
675
+
676
+ const filePath = join( dataDir, 'wills', willId, 'profiles', 'behavioral.jsonl' )
677
+ if( !existsSync( filePath ) ) return { fingerprint: defaultFingerprint, sessionsRead: 0 }
678
+
679
+ const summaries = _readLastNSummaries( filePath, 'session_summary', 5 )
680
+ if( summaries.length === 0 ) return { fingerprint: defaultFingerprint, sessionsRead: 0 }
681
+
682
+ const actionTotals: Record<string, number> = {}
683
+ let totalConf = 0
684
+ let confCount = 0
685
+ let totalCompleted = 0
686
+ let totalGoals = 0
687
+ let totalNovelActions = 0
688
+ let totalActions = 0
689
+ let totalImpulsiveActions = 0
690
+
691
+ for( const s of summaries ){
692
+ const dist = ( s['actionDist'] as Record<string, number> | undefined ) ?? {}
693
+ for( const [ k, v ] of Object.entries( dist ) ){
694
+ actionTotals[ k ] = ( actionTotals[ k ] ?? 0 ) + v
695
+ totalActions += v
696
+ }
697
+
698
+ // Track novel actions (actions not in top 3 from previous sessions)
699
+ const novelCount = s['novelActionCount'] as number | undefined
700
+ if( novelCount !== undefined ){
701
+ totalNovelActions += novelCount
702
+ }
703
+
704
+ // Track impulsive actions (actions taken with low confidence or high arousal)
705
+ const impulsiveCount = s['impulsiveActionCount'] as number | undefined
706
+ if( impulsiveCount !== undefined ){
707
+ totalImpulsiveActions += impulsiveCount
708
+ }
709
+
710
+ const avgConf = s['avgConfidence'] as number | null | undefined
711
+ if( avgConf !== null && avgConf !== undefined ){
712
+ totalConf += avgConf
713
+ confCount ++
714
+ }
715
+
716
+ const gt = s['goalsTotal'] as number | undefined
717
+ const gc = s['goalsCompleted'] as number | undefined
718
+ if( gt !== undefined ) totalGoals += gt
719
+ if( gc !== undefined ) totalCompleted += gc
720
+ }
721
+
722
+ const topActions = Object.entries( actionTotals )
723
+ .sort( ( a, b ) => b[1] - a[1] )
724
+ .slice( 0, 3 )
725
+ .map( ([ k ]) => k )
726
+
727
+ const avgConfidence = confCount > 0 ? totalConf / confCount : 0.5
728
+ const completionRate = totalGoals > 0 ? totalCompleted / totalGoals : null
729
+
730
+ // Exploration rate: proportion of actions that were novel
731
+ const explorationRate = totalActions > 0
732
+ ? Math.min( 1, totalNovelActions / ( totalActions * 0.3 ) ) // Normalized: 30% novel = 1.0
733
+ : 0.3
734
+
735
+ // Impulsivity: proportion of actions flagged as impulsive
736
+ const impulsivity = totalActions > 0
737
+ ? Math.min( 1, totalImpulsiveActions / ( totalActions * 0.2 ) ) // Normalized: 20% impulsive = 1.0
738
+ : 0.3
739
+
740
+ // Risk tolerance: inverse correlation with average confidence (lower confidence = higher risk tolerance)
741
+ // Or could be derived from action types that are inherently risky
742
+ const riskTolerance = 1 - ( avgConfidence * 0.6 )
743
+
744
+ return {
745
+ fingerprint: {
746
+ topActions,
747
+ avgConfidence: Math.round( avgConfidence * 1000 ) / 1000,
748
+ completionRate: completionRate !== null ? Math.round( completionRate * 1000 ) / 1000 : null,
749
+ riskTolerance: Math.round( riskTolerance * 1000 ) / 1000,
750
+ explorationRate: Math.round( explorationRate * 1000 ) / 1000,
751
+ impulsivity: Math.round( impulsivity * 1000 ) / 1000,
752
+ },
753
+ sessionsRead: summaries.length,
754
+ }
755
+ }
756
+ }
757
+
758
+ // ── PMALoader ──────────────────────────────────────────────────
759
+
760
+ /**
761
+ * Seeds a fresh Will simulation from a PMASnapshot.
762
+ *
763
+ * Seeding order matters:
764
+ * 1. Identity → sets 'identity-self' entity so the executive has
765
+ * character from tick 1.
766
+ * 2. Beliefs → injected via semanticIntegrator.integrateExecutiveBelief()
767
+ * with cause='pma-load'. Existing beliefs are merged, not duplicated.
768
+ * 3. Goals → re-injected via goalManager.addGoal() for active goals.
769
+ * Progress is not restored — goals start fresh.
770
+ * 4. Emotional baseline → sets affect.valence + affect.arousal metrics so
771
+ * the affective system doesn't start from a cold 0/0 state.
772
+ * 5. Temperament → sets identity traits that influence emotional set-point
773
+ * 6. Behavioral parameters → configure executive and memory engines
774
+ *
775
+ * Call AFTER createWill() / assembleMind() but BEFORE the tick loop starts,
776
+ * and only when no prior snapshot was restored (avoids overwriting live state).
777
+ */
778
+ export class PMALoader {
779
+
780
+ /**
781
+ * Hydrate a simulation from a PMASnapshot.
782
+ *
783
+ * @param pma The PMASnapshot to load
784
+ * @param simulation The DefaultSimulation to seed into
785
+ * @param cognition The Will's Cognition registry
786
+ */
787
+ load(
788
+ pma: PMASnapshot,
789
+ simulation: DefaultSimulation,
790
+ cognition: Cognition,
791
+ ): void {
792
+ const sm = simulation.stateManager
793
+
794
+ // ── 1. Identity (with enhanced fields) ────────────────────
795
+ sm.setEntity({
796
+ id: 'identity-self',
797
+ type: 'will.identity',
798
+ createdAt: Date.now(),
799
+ updatedAt: Date.now(),
800
+ metadata: {
801
+ prompt: pma.identity.prompt,
802
+ values: pma.identity.values,
803
+ traits: pma.identity.traits,
804
+ traitStats: pma.identity.traitStats, // restore the Will's own norm (graded salience B/C)
805
+ version: pma.identity.version,
806
+ style: pma.identity.style,
807
+ socialOrientation: pma.identity.socialOrientation,
808
+ trustPropensity: pma.identity.trustPropensity,
809
+ memoryPersistence: pma.identity.memoryPersistence,
810
+ },
811
+ })
812
+
813
+ // ── 1b. Persona (learned self-tuning from the metacognition cycle) ──
814
+ // Restored as entities — the wired path — so the accreted persona-prior +
815
+ // calibration curve survive into the new session and engines pick them up
816
+ // via readEffectiveParams / the calibrator's first-react rehydration.
817
+ if( pma.persona ){
818
+ if( Object.keys( pma.persona.configPriors ).length > 0 )
819
+ sm.setEntity({
820
+ id: PERSONA_PRIOR_ID,
821
+ type: PERSONA_PRIOR_TYPE,
822
+ createdAt: Date.now(),
823
+ updatedAt: Date.now(),
824
+ metadata: { priors: pma.persona.configPriors, version: 1, updatedAtTick: 0 },
825
+ })
826
+ if( Object.keys( pma.persona.calibrationBias ).length > 0 )
827
+ sm.setEntity({
828
+ id: 'calibration-state',
829
+ type: 'calibration.state',
830
+ createdAt: Date.now(),
831
+ updatedAt: Date.now(),
832
+ metadata: { domainBias: pma.persona.calibrationBias, updatedAtTick: 0 },
833
+ })
834
+ }
835
+
836
+ // ── 1c. Competence (learned skills + invented composite schemas) ──
837
+ // Reloaded into the live repertoire so a re-embodied Will resumes with its
838
+ // habits and skills intact — it acts like itself, not just believes/feels like
839
+ // itself. The ReafferenceEngine mirrors these back into `agency.skill` entities
840
+ // as they are next touched.
841
+ loadCompetence( pma.competence, cognition.schemaRepertoire )
842
+
843
+ // ── 2. Beliefs ────────────────────────────────────────────
844
+ // Restore verbatim (preserve id + final confidence). Re-integrating through
845
+ // integrateExecutiveBelief would merge similar stored beliefs into one
846
+ // another (dropping ids) and re-cap confidence by evidence — both corrupt
847
+ // the reconstruction. The live merge/decay dynamics resume once the Will ticks.
848
+ cognition.semanticIntegrator.restoreBeliefs(
849
+ pma.beliefs.map( b => ( {
850
+ id: b.id,
851
+ statement: b.statement,
852
+ category: b.category as Belief['category'],
853
+ confidence: b.confidence,
854
+ supportingEpisodes: b.supportingEpisodes,
855
+ lastUpdatedAt: 0,
856
+ tags: b.tags,
857
+ history: b.history,
858
+ } ) )
859
+ )
860
+
861
+ // ── 3. Goals ──────────────────────────────────────────────
862
+ for( const g of pma.goals ){
863
+ if( g.status !== 'active' && g.status !== 'in_progress' ) continue
864
+
865
+ cognition.goalManager.addGoal(
866
+ g.description,
867
+ g.priority,
868
+ g.tags,
869
+ undefined,
870
+ undefined,
871
+ g.completionType,
872
+ g.completionCondition,
873
+ g.id
874
+ )
875
+ }
876
+
877
+ // ── 4. Emotional baseline ─────────────────────────────────
878
+ const valence = pma.emotionalBaseline.avgValence
879
+
880
+ const arousal =
881
+ pma.emotionalBaseline.arousalProfile === 'high-energy' ? 0.65 :
882
+ pma.emotionalBaseline.arousalProfile === 'calm' ? 0.30 :
883
+ 0.45
884
+
885
+ sm.setMetric( 'affect.valence', valence )
886
+ sm.setMetric( 'affect.arousal', arousal )
887
+
888
+ // ── 5. Temperament (influences emotional set-point) ───────
889
+ const temperamentValence = pma.emotionalBaseline.temperamentValence
890
+ const reactivity = pma.emotionalBaseline.reactivity
891
+
892
+ sm.setEntity({
893
+ id: 'engine-config-affective-blender',
894
+ type: 'engine.config',
895
+ createdAt: Date.now(),
896
+ updatedAt: Date.now(),
897
+ metadata: {
898
+ engine: 'affective-blender',
899
+ params: {
900
+ inertia: 1 - reactivity,
901
+ temperamentValence,
902
+ },
903
+ },
904
+ })
905
+
906
+ // ── 6. Behavioral parameters ──────────────────────────────
907
+ if( pma.behavioral.riskTolerance !== undefined ||
908
+ pma.behavioral.explorationRate !== undefined ||
909
+ pma.behavioral.impulsivity !== undefined ){
910
+
911
+ sm.setEntity({
912
+ id: 'engine-config-executive',
913
+ type: 'engine.config',
914
+ createdAt: Date.now(),
915
+ updatedAt: Date.now(),
916
+ metadata: {
917
+ engine: 'executive',
918
+ params: {
919
+ riskTolerance: pma.behavioral.riskTolerance ?? 0.5,
920
+ explorationRate: pma.behavioral.explorationRate ?? 0.3,
921
+ impulsivity: pma.behavioral.impulsivity ?? 0.3,
922
+ },
923
+ },
924
+ })
925
+ }
926
+
927
+ // Configure memory persistence for ForgettingCurve
928
+ if( pma.identity.memoryPersistence !== undefined ){
929
+ sm.setEntity({
930
+ id: 'engine-config-forgetting',
931
+ type: 'engine.config',
932
+ createdAt: Date.now(),
933
+ updatedAt: Date.now(),
934
+ metadata: {
935
+ engine: 'forgetting-curve',
936
+ params: {
937
+ baseForgettingRate: 1 - ( pma.identity.memoryPersistence * 0.7 ),
938
+ },
939
+ },
940
+ })
941
+ }
942
+
943
+ // ── 7. Relationship stubs ─────────────────────────────────
944
+ const now = Date.now()
945
+ for( const rel of pma.relationships ){
946
+ if( rel.attachment ){
947
+ sm.setEntity({
948
+ id: `bond-${rel.keid}`,
949
+ type: 'attachment.bond',
950
+ createdAt: now,
951
+ updatedAt: now,
952
+ metadata: {
953
+ keid: rel.keid,
954
+ strength: rel.attachment.attachmentStrength,
955
+ trust: rel.attachment.trustLevel,
956
+ positiveRatio: rel.attachment.positiveRatio,
957
+ interactionCount: rel.attachment.interactionCount,
958
+ lastInteractionTick: 0,
959
+ sharedExperiences: rel.attachment.sharedExperiences,
960
+ dependency: rel.attachment.dependency,
961
+ tick: 0,
962
+ },
963
+ })
964
+ }
965
+
966
+ if( rel.reputation ){
967
+ sm.setEntity({
968
+ id: `reputation-${rel.keid}`,
969
+ type: 'reputation',
970
+ createdAt: now,
971
+ updatedAt: now,
972
+ metadata: {
973
+ keid: rel.keid,
974
+ name: rel.agentName ?? rel.keid,
975
+ reliability: rel.reputation.reliability,
976
+ cooperativeness: rel.reputation.cooperativeness,
977
+ socialStanding: rel.reputation.socialStanding,
978
+ trustworthiness: rel.reputation.trustworthiness,
979
+ interactionCount: rel.reputation.interactionCount,
980
+ positiveInteractions: rel.reputation.positiveInteractions,
981
+ negativeInteractions: rel.reputation.negativeInteractions,
982
+ lastInteractionTick: 0,
983
+ confidence: rel.reputation.confidence,
984
+ },
985
+ })
986
+ }
987
+
988
+ if( rel.mentalModel ){
989
+ // Re-seed the tom-<id> gist in the format TheoryOfMind._restoreFromState() reads,
990
+ // so a re-embodied Will recovers its *sense* of this mind (not the lost detail).
991
+ sm.setEntity({
992
+ id: `tom-${rel.keid}`,
993
+ type: 'theory_of_mind',
994
+ createdAt: now,
995
+ updatedAt: now,
996
+ metadata: {
997
+ keid: rel.keid,
998
+ modelConfidence: rel.mentalModel.modelConfidence,
999
+ dominantIntention: rel.mentalModel.dominantIntention,
1000
+ estimatedEmotion: rel.mentalModel.estimatedEmotion,
1001
+ beliefCount: 0,
1002
+ intentionCount: rel.mentalModel.dominantIntention ? 1 : 0,
1003
+ },
1004
+ })
1005
+ }
1006
+
1007
+ if( rel.dossier ){
1008
+ // Re-seed the ke-<keid> dossier in the format KnownEntityTracker._restoreFromState()
1009
+ // reads, so a re-embodied Will recovers its sense of *who/what* this is — the
1010
+ // crystallised residue (kind, name, how familiar, how it feels), not the lost
1011
+ // encounter trail. lastSeenTick resets to 0 (a fresh embodiment).
1012
+ sm.setEntity({
1013
+ id: `ke-${rel.keid}`,
1014
+ type: 'known-entity',
1015
+ createdAt: now,
1016
+ updatedAt: now,
1017
+ metadata: {
1018
+ keid: rel.keid,
1019
+ kind: rel.dossier.kind,
1020
+ name: rel.dossier.name ?? rel.agentName,
1021
+ familiarity: rel.dossier.familiarity,
1022
+ valence: rel.dossier.valence,
1023
+ reliability: rel.dossier.reliability,
1024
+ encounterCount: rel.dossier.encounterCount,
1025
+ lastSeenTick: 0,
1026
+ resolutionConfidence: rel.dossier.resolutionConfidence,
1027
+ },
1028
+ })
1029
+ }
1030
+
1031
+ // ── 8. Restore conversation context ─────────────────────
1032
+ // Re-seed the last conversation as a conversation.exchange working-memory
1033
+ // item so it flows through the same pipeline as a live exchange: the
1034
+ // EpisodicConsolidator consolidates it into episodic + vector memory, and
1035
+ // the executive/facets surface it via unified recall — no cold restart,
1036
+ // no dedicated ConversationManager.
1037
+ if( rel.lastConversationDigest ){
1038
+ sm.setEntity({
1039
+ id: `wm-exchange-restored-${rel.keid}`,
1040
+ type: 'working_memory.item',
1041
+ createdAt: now,
1042
+ updatedAt: now,
1043
+ metadata: {
1044
+ wmType: 'conversation.exchange',
1045
+ activation: 0.7,
1046
+ attendedCount: 2,
1047
+ tags: [ 'conversation', 'exchange', 'pma-restored', `entity:${rel.keid}` ],
1048
+ summary: rel.lastConversationDigest,
1049
+ entityId: rel.keid,
1050
+ entityName: rel.agentName ?? rel.keid,
1051
+ tick: 0,
1052
+ },
1053
+ })
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+
1059
+ // ── Module helpers ─────────────────────────────────────────────
1060
+
1061
+ /**
1062
+ * Read the last N `type: typeName` entries from an NDJSON file.
1063
+ * Returns an empty array on any read or parse error.
1064
+ */
1065
+ function _readLastNSummaries(
1066
+ filePath: string,
1067
+ typeName: string,
1068
+ n: number
1069
+ ): Array<Record<string, unknown>> {
1070
+ try {
1071
+ return readFileSync( filePath, 'utf8' )
1072
+ .split('\n')
1073
+ .filter( l => l.trim().length > 0 )
1074
+ .map( l => { try { return JSON.parse(l) as Record<string, unknown> } catch { return null } })
1075
+ .filter( ( e ): e is Record<string, unknown> =>
1076
+ e !== null && e['type'] === typeName
1077
+ )
1078
+ .slice( -n )
1079
+ } catch {
1080
+ return []
1081
+ }
1082
+ }