@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,677 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/self.model.updater.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * SelfModelUpdater — maintains beliefs about own capabilities, traits,
7
+ * and patterns.
8
+ *
9
+ * Periodically evaluates recent performance across domains and updates
10
+ * the will.identity entity with refined self-knowledge. This is how
11
+ * the mind learns "I am good at X, bad at Y" through experience.
12
+ *
13
+ * Uses AsyncEngine — deep self-evaluation may involve LLM introspection.
14
+ *
15
+ * Part of Shard 4 (Meta-Cognitive Layer).
16
+ */
17
+
18
+ import type {
19
+ Tick,
20
+ SimulationContext,
21
+ ReadonlySimulationState,
22
+ ReasoningFootprint,
23
+ StateCommands,
24
+ } from '#core/types'
25
+ import { AsyncEngine } from '#core/async.engine'
26
+ import type { IntermediateStream } from '#core/async.engine'
27
+ import type { SemanticIntegrator, Belief } from '#faculties/semantic.engine/integrator'
28
+ import type { CognitiveEngine } from '#cognition/types'
29
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
30
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
31
+ import { GenerativeModel, type GenerativeModelSnapshot } from '#cognition/generative.model'
32
+ import { readEffectiveParams } from '#cognition/persona.prior'
33
+
34
+ /** Shape of the will.identity entity — defined locally to avoid external dependencies. */
35
+ interface Keidentity {
36
+ prompt: string
37
+ values: string[]
38
+ traits: Record<string, number>
39
+ style: string
40
+ version: number
41
+ }
42
+
43
+ /**
44
+ * Per-trait self-knowledge accumulated over the Will's life — the substrate for graded
45
+ * salience options B (baseline-relative) and C (recency). Stored on identity-self
46
+ * beside `traits` and recomputed only at each (infrequent) self-model evaluation, so it
47
+ * changes exactly when identity-self changes — the same cache breakpoint the trait
48
+ * values already ride, adding no extra prompt churn between evaluations. R2-safe:
49
+ * derived purely from state + sim-tick.
50
+ */
51
+ export interface TraitStat {
52
+ /** Slow EMA of the trait value — the Will's personal baseline ("my own norm"). */
53
+ mean: number
54
+ /** Direction of the most recent SIGNIFICANT shift: +1 rising / −1 easing / 0 none. */
55
+ shiftDir: number
56
+ /** Sim-tick of that shift — drives the eval-aligned recency decay. */
57
+ shiftTick: number
58
+ }
59
+
60
+ // Population baselines seed a fresh Will's personal mean before it has a history of its
61
+ // own — this is how Option B's lighter "population" sub-option folds into the personal
62
+ // one. 0.5 for all by default; this map is the extension point for a trait with a known
63
+ // population prior.
64
+ const TRAIT_POPULATION_BASELINE: Record<string, number> = {}
65
+ const DEFAULT_TRAIT_BASELINE = 0.5
66
+ const TRAIT_MEAN_EMA_ALPHA = 0.2 // baseline tracks slowly → "my norm" reflects the sustained level, not a blip
67
+ const TRAIT_SHIFT_SIGNIFICANCE = 0.05 // |Δ| at one evaluation to count as a real shift (matches _diffTraits)
68
+ const TRAIT_RECENCY_WINDOW = 600 // ticks a shift stays "lately" (~3 evals at the 200-tick cadence), decayed at an eval
69
+
70
+ export interface SelfModelUpdaterConfig {
71
+ /** Minimum ticks between self-model evaluations */
72
+ minIntervalTicks?: number
73
+ /** Minimum new experiences before re-evaluating */
74
+ minNewExperiences?: number
75
+ bus?: CognitiveBus
76
+ }
77
+
78
+ // Keyword cues mapping a self-belief statement to the trait(s) it is actually about, so a
79
+ // positive self-belief nudges only the relevant trait — NOT every trait. (The prior code
80
+ // nudged all traits up indiscriminately, inflating every Channel-A disposition the
81
+ // persona-prior reads, regardless of behaviour.) Trait keys match the self-model traits.
82
+ const SELF_BELIEF_TRAIT_KEYWORDS: Record<string, string[]> = {
83
+ openness: [ 'open', 'curious', 'curiosity', 'explore', 'novel' ],
84
+ conscientiousness: [ 'careful', 'diligent', 'organized', 'thorough', 'responsible', 'disciplined', 'reliable' ],
85
+ agreeableness: [ 'kind', 'warm', 'helpful', 'caring', 'compassion', 'cooperative', 'friendly', 'gentle' ],
86
+ analytical: [ 'analytical', 'logical', 'rational', 'reason', 'precise' ],
87
+ creativity: [ 'creative', 'inventive', 'original', 'imaginative' ],
88
+ persistence: [ 'persistent', 'determined', 'persevere', 'tenacious', 'driven' ],
89
+ resilience: [ 'resilient', 'tough', 'recover', 'cope', 'bounce back' ],
90
+ decisiveness: [ 'decisive', 'confident', 'assertive', 'bold' ],
91
+ 'emotional-stability': [ 'calm', 'steady', 'stable', 'composed', 'even-keeled', 'unflappable' ],
92
+ }
93
+
94
+ /** Traits a self-belief statement is plausibly *about*, by keyword cue. Pure. */
95
+ export function traitsCuedBySelfBelief( statement: string ): string[] {
96
+ const text = statement.toLowerCase()
97
+ return Object.entries( SELF_BELIEF_TRAIT_KEYWORDS )
98
+ .filter( ( [ , keywords ] ) => keywords.some( k => text.includes( k ) ) )
99
+ .map( ( [ trait ] ) => trait )
100
+ }
101
+
102
+ export class SelfModelUpdater extends AsyncEngine implements CognitiveEngine {
103
+ readonly name = 'self-model-updater'
104
+
105
+ private _executiveReflectionBiases: string[] = []
106
+ private _executiveReflectionTick: number = 0
107
+ private _minIntervalTicks: number
108
+ private _minNewExperiences: number
109
+
110
+ private _lastEvaluationTick: number = 0
111
+ private _experienceCountAtLastEval: number = 0
112
+ private _cachedEpisodicTotal: number = 0
113
+
114
+ // Track domain performance for self-assessment
115
+ private _domainPerformance = new Map<string, Array<{ success: boolean; tick: Tick }>>()
116
+
117
+ // Affect-stability self-observation → the evidence behind the 'emotional-stability'
118
+ // trait. A slow EMA of how steady the Will's negative affect has been, sampled every
119
+ // tick in shouldAct. The trait then forms from this the same way task traits form from
120
+ // domain success rates — only here the "behaviour" observed is the Will's own affect.
121
+ private _affectStabilityEma: number = 0.5
122
+ private _affectObservations: number = 0
123
+
124
+ private _bus: CognitiveBus | null = null
125
+ private _semanticIntegrator: SemanticIntegrator | null = null
126
+
127
+ private readonly _model = new GenerativeModel()
128
+
129
+
130
+ constructor( config: SelfModelUpdaterConfig = {} ){
131
+ // _bus set after super()
132
+ super({
133
+ defaultStrategy: 'FORCE',
134
+ maxPendingTicks: 300,
135
+ logConflicts: false,
136
+ rerunOnRejection: false,
137
+ })
138
+
139
+ this._minIntervalTicks = config.minIntervalTicks ?? 200
140
+ this._minNewExperiences = config.minNewExperiences ?? 20
141
+ this._bus = config.bus ?? null
142
+ }
143
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
144
+
145
+ attachSemanticIntegrator( si: SemanticIntegrator ): void { this._semanticIntegrator = si }
146
+
147
+ subscribes(): string[] {
148
+ return [
149
+ 'executive.self.reflection',
150
+ 'executive.prediction.formed',
151
+ 'action.outcome'
152
+ ]
153
+ }
154
+ publishes(): CognitiveEventSchema[] { return [] }
155
+
156
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
157
+ this._model.observe( e.type, e.salience )
158
+ switch( e.type ){
159
+ case 'executive.self.reflection': {
160
+ const p = e.payload as Record<string, unknown>
161
+
162
+ this._executiveReflectionBiases = ( p['identifiedBiases'] as string[] ) ?? []
163
+ this._executiveReflectionTick = ( p['tick'] as number ) ?? 0
164
+
165
+ break
166
+ }
167
+ case 'executive.prediction.formed': {
168
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
169
+ if( p.predictedDomains.includes('metacognition') )
170
+ this._model.setPrecision( 'self_model.change', 1.0 + p.confidence * 0.5 )
171
+
172
+ break
173
+ }
174
+ case 'action.outcome': {
175
+ const p = e.payload as { actionType: string; domain: string; success: boolean; outcomeQuality: number; tick: Tick }
176
+ this.recordOutcome( p.domain, p.success, p.tick )
177
+ break
178
+ }
179
+ }
180
+ }
181
+
182
+ snapshot(): Record<string, unknown> {
183
+ // FN9: capture the per-domain performance history (the basis for "I am good
184
+ // at X / bad at Y"), the evaluation-gating ticks (so re-evaluation timing
185
+ // replays deterministically — R2), and the salience/generative sub-states.
186
+ return {
187
+ executiveReflectionTick: this._executiveReflectionTick,
188
+ biasCount: this._executiveReflectionBiases.length,
189
+ lastEvaluationTick: this._lastEvaluationTick,
190
+ experienceCountAtLastEval: this._experienceCountAtLastEval,
191
+ domainPerformance: [ ...this._domainPerformance.entries() ].map( ([ k, v ]) => [ k, v.map( r => ({ ...r }) ) ] ),
192
+ model: this._model.snapshot()
193
+ }
194
+ }
195
+
196
+ restore( snap: Record<string, unknown> ): void {
197
+ if( !snap ) return
198
+
199
+ if( typeof snap.executiveReflectionTick === 'number' )
200
+ this._executiveReflectionTick = snap.executiveReflectionTick
201
+
202
+ if( typeof snap.lastEvaluationTick === 'number' )
203
+ this._lastEvaluationTick = snap.lastEvaluationTick
204
+
205
+ if( typeof snap.experienceCountAtLastEval === 'number' )
206
+ this._experienceCountAtLastEval = snap.experienceCountAtLastEval
207
+
208
+ if( Array.isArray( snap.domainPerformance ) )
209
+ this._domainPerformance = new Map( snap.domainPerformance as Array<[ string, Array<{ success: boolean; tick: Tick }> ]> )
210
+
211
+ if( snap.model )
212
+ this._model.restore( snap.model as GenerativeModelSnapshot )
213
+ }
214
+
215
+ /**
216
+ * Record an action outcome for domain-specific self-assessment.
217
+ */
218
+ recordOutcome( domain: string, success: boolean, tick: Tick ): void {
219
+ const records = this._domainPerformance.get( domain ) ?? []
220
+ records.push({ success, tick })
221
+
222
+ // Keep last 100 records per domain
223
+ if( records.length > 100 ) records.shift()
224
+ this._domainPerformance.set( domain, records )
225
+ }
226
+
227
+ // ── AsyncEngine contract ─────────────────────────────────
228
+
229
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
230
+ // Effective config = base engine-config ⊕ persona-prior. This is the first
231
+ // closed edge of the metacognition cycle: a learned prior (written back by
232
+ // the consolidator from the Will's own introspection) modulates how often
233
+ // the self-model re-evaluates, without ever mutating the seeded base.
234
+ const p = readEffectiveParams( state, 'engine-config-self-model' )
235
+
236
+ if( p.minIntervalTicks != null ) this._minIntervalTicks = p.minIntervalTicks
237
+ if( p.minNewExperiences != null ) this._minNewExperiences = p.minNewExperiences
238
+ }
239
+
240
+ /**
241
+ * Sample current negative affect into a slow EMA — the running self-observation the
242
+ * self-model turns into an 'emotional-stability' trait. Called every tick (in
243
+ * shouldAct), so it tracks affect continuously even between the gated evaluations.
244
+ * Pure/deterministic: the same emotion-metric stream reproduces the same EMA on
245
+ * replay (no wall-clock, no RNG). Stability is read as low sustained negative affect;
246
+ * volatility (variance) would be a finer refinement but a level EMA is enough to drive
247
+ * the self-regulation loop (steadier ⇒ slower frustration build ⇒ steadier).
248
+ */
249
+ private _sampleAffectStability( state: ReadonlySimulationState ): void {
250
+ const negativeAffect = Math.max(
251
+ state.metrics.get('emotion.frustration') ?? 0,
252
+ state.metrics.get('emotion.anger') ?? 0,
253
+ state.metrics.get('emotion.irritability') ?? 0,
254
+ )
255
+ const stabilityInstant = Math.max( 0, Math.min( 1, 1 - negativeAffect ) )
256
+ const alpha = 0.05
257
+ this._affectStabilityEma = this._affectObservations === 0
258
+ ? stabilityInstant
259
+ : this._affectStabilityEma * ( 1 - alpha ) + stabilityInstant * alpha
260
+ this._affectObservations++
261
+ }
262
+
263
+ protected override shouldAct(
264
+ state: ReadonlySimulationState,
265
+ tick: Tick,
266
+ _context: SimulationContext
267
+ ): boolean {
268
+ this._readConfigFromState( state )
269
+ this._sampleAffectStability( state )
270
+
271
+ const
272
+ ticksSinceLast = tick - this._lastEvaluationTick,
273
+ totalExperiences = state.metrics.get('memory.episodic_total') ?? 0,
274
+ newExperiences = totalExperiences - this._experienceCountAtLastEval
275
+
276
+ this._cachedEpisodicTotal = totalExperiences
277
+
278
+ return ticksSinceLast >= this._minIntervalTicks
279
+ && newExperiences >= this._minNewExperiences
280
+ }
281
+
282
+ protected override readState(
283
+ state: ReadonlySimulationState,
284
+ tick: Tick
285
+ ): ReasoningFootprint {
286
+ return {
287
+ tickObserved: tick,
288
+ entitiesRead: new Set([ 'identity-self' ]),
289
+ metricsRead: new Set([
290
+ 'affect.valence', 'affect.dominance',
291
+ 'metacognition.confidence',
292
+ ]),
293
+ entitiesModified: new Set([ 'identity-self' ]),
294
+ intendedCommands: {},
295
+ source: this.name,
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Recompute per-trait self-knowledge at an evaluation (Options B/C substrate). Pure +
301
+ * deterministic — sim-tick only. The EMA tracks the personal baseline; a significant Δ
302
+ * this evaluation stamps a recency direction that ages out after TRAIT_RECENCY_WINDOW.
303
+ * The window is checked ONLY here (at evals), so the recency stamp never churns the
304
+ * prompt between evaluations.
305
+ */
306
+ private _computeTraitStats(
307
+ oldTraits: Record<string, number>,
308
+ newTraits: Record<string, number>,
309
+ prevStats: Record<string, TraitStat>,
310
+ evalTick: number,
311
+ ): Record<string, TraitStat> {
312
+ const stats: Record<string, TraitStat> = {}
313
+
314
+ for( const key of Object.keys( newTraits ) ){
315
+ const newVal = newTraits[ key ] ?? DEFAULT_TRAIT_BASELINE
316
+ const oldVal = oldTraits[ key ] ?? newVal
317
+ const prev = prevStats[ key ]
318
+
319
+ // Personal baseline: EMA seeded from the population prior on first sight.
320
+ const seed = prev?.mean ?? ( TRAIT_POPULATION_BASELINE[ key ] ?? DEFAULT_TRAIT_BASELINE )
321
+ const mean = seed + TRAIT_MEAN_EMA_ALPHA * ( newVal - seed )
322
+
323
+ // Recency: a significant move this eval (re)stamps the direction; otherwise carry the
324
+ // prior stamp until it ages past the window, then clear it.
325
+ const delta = newVal - oldVal
326
+ let shiftDir = prev?.shiftDir ?? 0
327
+ let shiftTick = prev?.shiftTick ?? evalTick
328
+ if( Math.abs( delta ) >= TRAIT_SHIFT_SIGNIFICANCE ){
329
+ shiftDir = Math.sign( delta )
330
+ shiftTick = evalTick
331
+ }
332
+ else if( shiftDir !== 0 && evalTick - shiftTick > TRAIT_RECENCY_WINDOW ){
333
+ shiftDir = 0
334
+ }
335
+
336
+ stats[ key ] = { mean, shiftDir, shiftTick }
337
+ }
338
+
339
+ return stats
340
+ }
341
+
342
+ protected async reasonAsync(
343
+ footprint: ReasoningFootprint,
344
+ state: ReadonlySimulationState,
345
+ context: SimulationContext,
346
+ stream: IntermediateStream
347
+ ): Promise<unknown> {
348
+ // 1. Compute domain success rates
349
+ const domainAssessments = this._computeDomainAssessments()
350
+
351
+ stream.report('domains_evaluated', {
352
+ domainCount: domainAssessments.length,
353
+ })
354
+
355
+ // 2. Gather self-beliefs from state entities
356
+ const selfBeliefs = [ ...state.entities.values() ]
357
+ .filter( e => e.type === 'belief'
358
+ && (e.metadata?.['category'] as string | undefined) === 'self_belief'
359
+ && ((e.metadata?.['confidence'] as number | undefined) ?? 0) >= 0.3
360
+ )
361
+ .map( e => ({
362
+ id: e.id,
363
+ statement: ( e.metadata?.['statement'] as string ) ?? '',
364
+ category: 'self_belief' as const,
365
+ confidence: ( e.metadata?.['confidence'] as number ) ?? 0.5,
366
+ supportingEpisodes: ( e.metadata?.['supportingEpisodes'] as number ) ?? 0,
367
+ lastUpdatedAt: 0,
368
+ tags: ( e.metadata?.['tags'] as string[] ) ?? [],
369
+ }))
370
+
371
+ // 3. Extract current identity from state
372
+ const currentIdentity = this._extractIdentity( state )
373
+
374
+ // 4. Generate updated identity
375
+ const updatedIdentity = this._evaluateSelf(
376
+ currentIdentity,
377
+ domainAssessments,
378
+ selfBeliefs
379
+ )
380
+
381
+ const changeMagnitude = this._identityChangeMagnitude( currentIdentity, updatedIdentity )
382
+
383
+ // Per-trait self-knowledge (baseline + recency) for graded salience B/C. Read the
384
+ // prior stats off identity-self and roll them forward at this evaluation's sim-tick.
385
+ const prevTraitStats = ( state.entities.get('identity-self')?.metadata?.traitStats ?? {} ) as Record<string, TraitStat>
386
+ const traitStats = this._computeTraitStats(
387
+ currentIdentity.traits, updatedIdentity.traits, prevTraitStats, footprint.tickObserved as unknown as number
388
+ )
389
+
390
+ stream.report('identity_updated', {
391
+ traitChanges: this._diffTraits( currentIdentity.traits, updatedIdentity.traits ),
392
+ newValues: updatedIdentity.values.filter( v => !currentIdentity.values.includes( v ) )
393
+ })
394
+
395
+ return { updatedIdentity, domainAssessments, changeMagnitude, traitStats }
396
+ }
397
+
398
+ protected override onIntermediateResult(
399
+ step: string,
400
+ result: unknown,
401
+ _footprint: ReasoningFootprint,
402
+ _context: SimulationContext
403
+ ): StateCommands | null {
404
+ const data = result as Record<string, unknown>
405
+
406
+ if( step === 'domains_evaluated')
407
+ return {
408
+ metrics: [
409
+ [ 'self_model.phase', 0 ],
410
+ [ 'self_model.domains', ( data.domainCount as number ) ?? 0 ]
411
+ ]
412
+ }
413
+
414
+ if( step === 'identity_updated')
415
+ return {
416
+ metrics: [
417
+ [ 'self_model.phase', 1 ]
418
+ ]
419
+ }
420
+
421
+ return null
422
+ }
423
+
424
+ protected onReasoningComplete(
425
+ output: unknown,
426
+ footprint: ReasoningFootprint,
427
+ context: SimulationContext
428
+ ): StateCommands {
429
+ const { updatedIdentity, domainAssessments, changeMagnitude, traitStats } = output as {
430
+ updatedIdentity: Keidentity
431
+ domainAssessments: Array<{ domain: string; successRate: number; sampleSize: number }>
432
+ changeMagnitude: number
433
+ traitStats: Record<string, TraitStat>
434
+ }
435
+
436
+ // Sim tick of this evaluation (footprint.tickObserved), not wall-clock. It
437
+ // gates re-evaluation via `tick - _lastEvaluationTick >= minIntervalTicks`
438
+ // (shouldAct), so it must be a deterministic tick to replay identically (R2).
439
+ this._lastEvaluationTick = footprint.tickObserved
440
+ this._experienceCountAtLastEval = this._cachedEpisodicTotal
441
+
442
+ const commands: StateCommands = {
443
+ set: [{
444
+ id: 'identity-self',
445
+ type: 'will.identity',
446
+ metadata: {
447
+ prompt: updatedIdentity.prompt,
448
+ values: updatedIdentity.values,
449
+ traits: updatedIdentity.traits,
450
+ traitStats, // per-trait baseline + recency (B/C)
451
+ style: updatedIdentity.style,
452
+ version: ( updatedIdentity.version ?? 1 ) + 1
453
+ }
454
+ }],
455
+ metrics: []
456
+ }
457
+
458
+ // Domain-specific success rates as metrics
459
+ for( const da of domainAssessments )
460
+ commands.metrics!.push(
461
+ [ `self_model.${da.domain}.success_rate`, da.successRate ],
462
+ [ `self_model.${da.domain}.sample_size`, da.sampleSize ]
463
+ )
464
+
465
+ commands.metrics!.push([ 'self_model.version', ( updatedIdentity.version ?? 1 ) + 1 ])
466
+
467
+ // Push domain competence beliefs to SemanticIntegrator.
468
+ // Self-model traits are a numeric signal; these natural-language beliefs
469
+ // are what actually surface in the executive's reasoning context.
470
+ // Only domains with ≥5 samples and a clear directional signal produce beliefs.
471
+ if( this._semanticIntegrator ){
472
+ const tick = footprint.tickObserved
473
+ for( const da of domainAssessments ){
474
+ if( da.sampleSize < 5 ) continue
475
+ const confidence = Math.min( 0.82, ( da.sampleSize / ( da.sampleSize + 10 ) ) * 0.9 )
476
+
477
+ if( da.successRate > 0.70 )
478
+ this._semanticIntegrator.integrateExecutiveBelief({
479
+ id: `belief-competence-${da.domain}-positive`,
480
+ statement: `I am effective at ${da.domain} tasks`,
481
+ category: 'self_belief',
482
+ confidence,
483
+ supportingEpisodes: da.sampleSize,
484
+ lastUpdatedAt: tick,
485
+ tags: [ da.domain, 'competence', 'self', 'positive' ]
486
+ }, tick, 'self-model' )
487
+
488
+ else if( da.successRate < 0.30 )
489
+ this._semanticIntegrator.integrateExecutiveBelief({
490
+ id: `belief-competence-${da.domain}-negative`,
491
+ statement: `I often struggle with ${da.domain} tasks`,
492
+ category: 'self_belief',
493
+ confidence,
494
+ supportingEpisodes: da.sampleSize,
495
+ lastUpdatedAt: tick,
496
+ tags: [ da.domain, 'competence', 'self', 'negative' ]
497
+ }, tick, 'self-model' )
498
+ }
499
+ }
500
+
501
+ // Phase C + F: publish cognitive event — gated by prediction error
502
+ const _bus = this._bus
503
+ if( _bus ){
504
+ const predErr = this._model.observe( 'self_model.change', changeMagnitude )
505
+ if( !predErr.gated )
506
+ _bus.publish({
507
+ type: 'self_model.updated',
508
+ version: 1,
509
+ sourceEngine: this.name,
510
+ salience: Math.max( 0.4, predErr.salience ),
511
+ payload: {
512
+ version: ( updatedIdentity.version ?? 1 ) + 1,
513
+ changeMagnitude
514
+ }
515
+ })
516
+ }
517
+
518
+ return commands
519
+ }
520
+
521
+ // ── Self-evaluation ──────────────────────────────────────
522
+
523
+ private _computeDomainAssessments(): Array<{
524
+ domain: string
525
+ successRate: number
526
+ sampleSize: number
527
+ }> {
528
+ const assessments: Array<{ domain: string; successRate: number; sampleSize: number }> = []
529
+
530
+ for( const [ domain, records ] of this._domainPerformance ){
531
+ if( records.length < 3 ) continue
532
+
533
+ const successCount = records.filter( r => r.success ).length
534
+ assessments.push({
535
+ domain,
536
+ successRate: successCount / records.length,
537
+ sampleSize: records.length,
538
+ })
539
+ }
540
+
541
+ // Self-observed emotional stability — formed from affect dynamics, not a task
542
+ // success rate. Confidence ramps with how much affect has been sampled (capped),
543
+ // so a fresh Will's neutral 0.5 barely moves the trait until evidence accumulates.
544
+ if( this._affectObservations > 0 )
545
+ assessments.push({
546
+ domain: 'emotional-regulation',
547
+ successRate: this._affectStabilityEma,
548
+ sampleSize: Math.min( 20, this._affectObservations ),
549
+ })
550
+
551
+ assessments.sort( ( a, b ) => b.sampleSize - a.sampleSize )
552
+ return assessments
553
+ }
554
+
555
+ private _extractIdentity( state: ReadonlySimulationState ): Keidentity {
556
+ for( const entity of state.entities.values() )
557
+ if( entity.type === 'will.identity')
558
+ return {
559
+ prompt: ( entity.metadata?.prompt as string ) ?? '',
560
+ values: ( entity.metadata?.values as string[] ) ?? [],
561
+ traits: ( entity.metadata?.traits as Record<string, number> ) ?? {},
562
+ style: ( entity.metadata?.style as string ) ?? '',
563
+ version: ( entity.metadata?.version as number ) ?? 1
564
+ }
565
+
566
+ return { prompt: '', values: [], traits: {}, style: '', version: 1 }
567
+ }
568
+
569
+ /**
570
+ * Evaluate self based on performance data and existing beliefs.
571
+ * Adjusts trait estimates and updates values.
572
+ */
573
+ private _evaluateSelf(
574
+ current: Keidentity,
575
+ assessments: Array<{ domain: string; successRate: number; sampleSize: number }>,
576
+ beliefs: Belief[]
577
+ ): Keidentity {
578
+ const updated = {
579
+ ...current,
580
+ traits: { ...current.traits },
581
+ values: [ ...current.values ]
582
+ }
583
+
584
+ // Update traits based on performance
585
+ for( const assessment of assessments ){
586
+ const traitKey = this._domainToTrait( assessment.domain )
587
+ if( !traitKey ) continue
588
+
589
+ const currentTrait = updated.traits[ traitKey ] ?? 0.5
590
+ const confidence = Math.min( 1, assessment.sampleSize / 20 )
591
+
592
+ // Exponential moving average — weight by sample confidence
593
+ updated.traits[ traitKey ] = currentTrait * ( 1 - confidence * 0.3 )
594
+ + assessment.successRate * confidence * 0.3
595
+ }
596
+
597
+ // Integrate self-beliefs into trait updates
598
+ for( const belief of beliefs ){
599
+ if( belief.category !== 'self_belief') continue
600
+
601
+ // A positive self-belief nudges only the trait(s) it is actually about (keyword
602
+ // cue) — not every trait. Self-concept still shapes traits, without inflating
603
+ // unrelated dispositions the persona-prior would then develop.
604
+ const text = belief.statement.toLowerCase()
605
+ const isPositive = !text.includes('not') && !text.includes('bad') && !text.includes('poor')
606
+ if( !isPositive ) continue
607
+
608
+ for( const trait of traitsCuedBySelfBelief( belief.statement ) )
609
+ updated.traits[ trait ] = Math.min( 1, ( updated.traits[ trait ] ?? 0.5 ) + belief.confidence * 0.02 )
610
+ }
611
+
612
+ // Adopt new values if consistently successful in related domains
613
+ if( assessments.some( a => a.domain === 'helping' && a.successRate > 0.7 ) ){
614
+ if( !updated.values.includes('compassion') )
615
+ updated.values.push('compassion')
616
+ }
617
+
618
+ if( assessments.some( a => a.domain === 'learning' && a.successRate > 0.6 ) ){
619
+ if( !updated.values.includes('growth') )
620
+ updated.values.push('growth')
621
+ }
622
+
623
+ return updated
624
+ }
625
+
626
+ private _domainToTrait( domain: string ): string | null {
627
+ const mapping: Record<string, string> = {
628
+ 'planning': 'conscientiousness',
629
+ 'deciding': 'decisiveness',
630
+ 'exploring': 'openness',
631
+ 'social': 'agreeableness',
632
+ 'helping': 'agreeableness',
633
+ 'learning': 'openness',
634
+ 'persisting': 'persistence',
635
+ 'coping': 'resilience',
636
+ 'creating': 'creativity',
637
+ 'analyzing': 'analytical',
638
+ 'emotional-regulation': 'emotional-stability',
639
+ }
640
+
641
+ return mapping[ domain ] ?? null
642
+ }
643
+
644
+ private _diffTraits(
645
+ oldTraits: Record<string, number>,
646
+ newTraits: Record<string, number>
647
+ ): string[] {
648
+ const changes: string[] = []
649
+
650
+ for( const key of new Set([ ...Object.keys( oldTraits ), ...Object.keys( newTraits ) ]) ){
651
+ const oldVal = oldTraits[ key ] ?? 0.5
652
+ const newVal = newTraits[ key ] ?? 0.5
653
+
654
+ if( Math.abs( newVal - oldVal ) > 0.05 )
655
+ changes.push(`${key}: ${oldVal.toFixed( 2 )} → ${newVal.toFixed( 2 )}`)
656
+ }
657
+
658
+ return changes
659
+ }
660
+
661
+ /**
662
+ * Scalar magnitude of how much the identity actually moved this evaluation —
663
+ * summed absolute trait deltas plus a per-new-value increment. This is the
664
+ * surprise signal the generative model gates on (a real self-revision is a
665
+ * large change), replacing the monotonically-incrementing version counter
666
+ * which always looked like the same "+1" of error.
667
+ */
668
+ private _identityChangeMagnitude( oldId: Keidentity, newId: Keidentity ): number {
669
+ let mag = 0
670
+ for( const key of new Set([ ...Object.keys( oldId.traits ), ...Object.keys( newId.traits ) ]) )
671
+ mag += Math.abs( ( newId.traits[ key ] ?? 0.5 ) - ( oldId.traits[ key ] ?? 0.5 ) )
672
+
673
+ mag += newId.values.filter( v => !oldId.values.includes( v ) ).length * 0.1
674
+
675
+ return mag
676
+ }
677
+ }