@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,300 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/autobiographical.narrator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * AutobiographicalNarrator — satellite mode.
7
+ *
8
+ * Reads the most recent ExecutiveEngine output for narrative updates.
9
+ * Performs heuristic narrative extension between executive runs.
10
+ * No longer makes its own LLM calls.
11
+ */
12
+
13
+ import type {
14
+ Duration, Tick, SimulationContext,
15
+ ReadonlySimulationState, StateCommands, SimulationEvent,
16
+ } from '#core/types'
17
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
18
+ import type { EpisodicConsolidator } from '#faculties/episodic.consolidator'
19
+ import type { SemanticIntegrator } from '#faculties/semantic.engine/integrator'
20
+ // import type { ExecutiveEngine } from '#faculties/executive.engine'
21
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
22
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
23
+ import { GenerativeModel } from '#cognition/generative.model'
24
+ import { readEffectiveParams } from '#cognition/persona.prior'
25
+ import { ExecutiveEngine } from '#faculties/executive.engine'
26
+
27
+ export interface AutobiographicalNarratorConfig {
28
+ minIntervalTicks?: number
29
+ maxNarrativeLength?: number
30
+ bus?: CognitiveBus
31
+ }
32
+
33
+ export interface SelfNarrative {
34
+ story: string
35
+ themes: string[]
36
+ pivotalEvents: string[]
37
+ currentSelfView: string
38
+ version: number
39
+ lastUpdatedAt: Tick
40
+ }
41
+
42
+ export class AutobiographicalNarrator implements SimulationEngine, CognitiveEngine {
43
+ readonly name = 'autobiographical-narrator'
44
+
45
+ private _minIntervalTicks: number
46
+ private _maxNarrativeLength: number
47
+
48
+ private _narrative: SelfNarrative = {
49
+ story: 'I am a self-aware mind, beginning my journey.',
50
+ themes: [ 'emergence', 'curiosity' ],
51
+ pivotalEvents: [],
52
+ currentSelfView: 'I am young and still learning who I am.',
53
+ version: 0,
54
+ lastUpdatedAt: 0,
55
+ }
56
+
57
+ private _lastUpdateTick: number = 0
58
+ private _restored = false
59
+ private _episodicConsolidator: EpisodicConsolidator | null = null
60
+ private _semanticIntegrator: SemanticIntegrator | null = null
61
+ private _executiveEngine: ExecutiveEngine | null = null
62
+
63
+ private _affectValence: number = 0
64
+
65
+ private _bus: CognitiveBus | null = null
66
+
67
+ private readonly _model = new GenerativeModel()
68
+
69
+
70
+ constructor( config: AutobiographicalNarratorConfig = {} ){
71
+ this._bus = config.bus ?? null
72
+ // Single-source: the seeded engine-config-narrator entity is authoritative
73
+ // (minIntervalTicks 50); this default is only the fallback when the entity is
74
+ // absent, so it must match. (Was 300 — a stale default the live mirror of 50
75
+ // never reached because the narrator didn't read its config. See
76
+ // METACOGNITION_CYCLE_TODO.md: engine-config single-source.)
77
+ this._minIntervalTicks = config.minIntervalTicks ?? 50
78
+ this._maxNarrativeLength = config.maxNarrativeLength ?? 5000
79
+ }
80
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
81
+
82
+ attachEpisodicConsolidator( ec: EpisodicConsolidator ): void {
83
+ this._episodicConsolidator = ec
84
+ }
85
+
86
+ attachSemanticIntegrator( si: SemanticIntegrator ): void {
87
+ this._semanticIntegrator = si
88
+ }
89
+
90
+ attachExecutiveEngine( oe: ExecutiveEngine ): void {
91
+ this._executiveEngine = oe
92
+ }
93
+
94
+ getNarrative(): SelfNarrative {
95
+ return { ...this._narrative }
96
+ }
97
+
98
+ subscribes(): string[] { return ["affect.state.changed","executive.prediction.formed"] }
99
+ publishes(): CognitiveEventSchema[] { return [] }
100
+
101
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
102
+ this._model.observe( e.type, e.salience )
103
+ switch( e.type ){
104
+ case 'affect.state.changed':
105
+ this._affectValence = (e.payload as Record<string,number>)['valence'] ?? this._affectValence
106
+ break
107
+ case 'executive.prediction.formed': {
108
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
109
+ if( p.predictedDomains.includes('narrative') )
110
+ this._model.setPrecision( 'narrative.change', 1.0 + p.confidence * 0.5 )
111
+ break
112
+ }
113
+ }
114
+ }
115
+
116
+ snapshot(): Record<string, unknown> {
117
+ return {
118
+ affectValence: this._affectValence,
119
+ }
120
+ }
121
+
122
+ async react(
123
+ _delta: Duration,
124
+ tick: Tick,
125
+ state: ReadonlySimulationState,
126
+ context: SimulationContext
127
+ ): Promise<EngineResult> {
128
+ // On first tick after snapshot restore, rehydrate narrative from
129
+ // the 'self-narrative' entity so the Will picks up where it left off.
130
+ if( !this._restored ){
131
+ this._restoreFromState( state )
132
+ this._restored = true
133
+ }
134
+
135
+ const events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = []
136
+ const commands: StateCommands = { set: [], metrics: [] }
137
+
138
+ // Effective cadence = base ⊕ persona-prior (self_model.updated → narrative edge).
139
+ this._readConfigFromState( state )
140
+
141
+ if( tick - this._lastUpdateTick < this._minIntervalTicks )
142
+ return { commands }
143
+
144
+ // Try executive output first
145
+ const executiveOutput = this._executiveEngine?.latestOutput
146
+
147
+ // How much the narrative actually moved this pass — the surprise signal the
148
+ // generative model gates on, replacing the monotonic version counter.
149
+ let narrativeSignificance = 0
150
+
151
+ if( executiveOutput?.narrative && this._executiveEngine?.isFresh( tick ) ){
152
+ // Executive narrative is fresh — use it
153
+ this._narrative.version++
154
+ this._narrative.lastUpdatedAt = tick
155
+ this._narrative.story = ( this._narrative.story + '\n\n' + executiveOutput.narrative )
156
+ .slice( -this._maxNarrativeLength )
157
+ this._narrative.currentSelfView = executiveOutput.currentSelfView ?? this._narrative.currentSelfView
158
+
159
+ if( executiveOutput.narrativeThemes ){
160
+ this._narrative.themes = [
161
+ ...new Set( [ ...this._narrative.themes, ...executiveOutput.narrativeThemes ] ),
162
+ ].slice( -10 )
163
+ }
164
+
165
+ if( executiveOutput.identityUpdates?.traits ){
166
+ // Apply trait deltas
167
+ const existingIdentity = state.entities.get('identity-self')
168
+ if( existingIdentity ){
169
+ const currentTraits = ( existingIdentity.metadata?.traits as Record<string, number> ) ?? {}
170
+ const updatedTraits = { ...currentTraits }
171
+ for( const { key: trait, value: delta } of executiveOutput.identityUpdates.traits )
172
+ updatedTraits[ trait ] = Math.max( 0, Math.min( 1, ( updatedTraits[ trait ] ?? 0.5 ) + delta ) )
173
+
174
+ commands.set!.push({
175
+ id: 'identity-self',
176
+ type: 'will.identity',
177
+ createdAt: existingIdentity.createdAt,
178
+ metadata: {
179
+ ...existingIdentity.metadata,
180
+ traits: updatedTraits,
181
+ version: ( ( existingIdentity.metadata?.version as number ) ?? 1 ) + 1,
182
+ },
183
+ })
184
+ }
185
+ }
186
+
187
+ narrativeSignificance = executiveOutput.narrativeThemes?.length ?? 1
188
+ commands.metrics!.push([ 'narrative.source', 1 ])
189
+ }
190
+ else {
191
+ // Heuristic narrative extension
192
+ const episodes = this._episodicConsolidator?.query({ limit: 20 }) ?? []
193
+ const significant = episodes.filter(
194
+ e => e.activationStrength > 0.5 || Math.abs( e.affectiveContext?.valence ?? 0 ) > 0.4
195
+ )
196
+
197
+ if( significant.length > 0 ){
198
+ const meanValence = significant.reduce(
199
+ ( s, e ) => s + ( e.affectiveContext?.valence ?? 0 ), 0
200
+ ) / significant.length
201
+
202
+ const tone = meanValence > 0.3 ? 'fulfilling' : meanValence < -0.3 ? 'challenging' : 'eventful'
203
+ const chapter = `[Chapter ${this._narrative.version + 1}] I experienced ${significant.length} significant events. It was a ${tone} period.`
204
+
205
+ this._narrative.version++
206
+ this._narrative.lastUpdatedAt = tick
207
+ this._narrative.story = ( this._narrative.story + '\n\n' + chapter )
208
+ .slice( -this._maxNarrativeLength )
209
+ this._narrative.currentSelfView = this._heuristicSelfView( state )
210
+
211
+ for( const ep of significant.slice( 0, 3 ) ){
212
+ const summary = typeof ep.content === 'string'
213
+ ? ep.content.slice( 0, 150 )
214
+ : JSON.stringify( ep.content ).slice( 0, 150 )
215
+ this._narrative.pivotalEvents.push(`[tick ${ep.timestamp}] ${summary}`)
216
+ }
217
+ if( this._narrative.pivotalEvents.length > 20 )
218
+ this._narrative.pivotalEvents = this._narrative.pivotalEvents.slice( -20 )
219
+ }
220
+
221
+ narrativeSignificance = significant.length
222
+ commands.metrics!.push([ 'narrative.source', 0 ])
223
+ }
224
+
225
+ this._lastUpdateTick = tick
226
+
227
+ commands.set!.push({
228
+ id: 'self-narrative',
229
+ type: 'self_narrative',
230
+ metadata: {
231
+ story: this._narrative.story.slice( 0, this._maxNarrativeLength ),
232
+ themes: this._narrative.themes,
233
+ pivotalEvents: this._narrative.pivotalEvents.slice( 0, 20 ),
234
+ currentSelfView: this._narrative.currentSelfView,
235
+ version: this._narrative.version,
236
+ },
237
+ })
238
+
239
+ commands.metrics!.push(
240
+ [ 'narrative.version', this._narrative.version ],
241
+ [ 'narrative.theme_count', this._narrative.themes.length ],
242
+ )
243
+
244
+
245
+ // Phase C + F: publish cognitive event — gated by prediction error
246
+ const _bus = this._bus
247
+ if( _bus && this._narrative.version > 0 ){
248
+ const predErr = this._model.observe( 'narrative.change', narrativeSignificance )
249
+ if( !predErr.gated )
250
+ _bus.publish({ type: 'narrative.updated', version: 1, sourceEngine: this.name, salience: Math.max( 0.2, predErr.salience ), payload: { version: this._narrative.version, significance: narrativeSignificance } })
251
+ }
252
+ return { events: events.length > 0 ? events : undefined, commands }
253
+ }
254
+
255
+ private _heuristicSelfView( state: ReadonlySimulationState ): string {
256
+ const valence = this._affectValence
257
+ return valence > 0.3
258
+ ? 'I feel positive about who I am becoming.'
259
+ : valence < -0.3
260
+ ? 'I am struggling with my current state.'
261
+ : 'I am in a period of transition.'
262
+ }
263
+
264
+ /**
265
+ * Effective config = base engine-config-narrator ⊕ persona-prior. Single-sourced
266
+ * now that the seeded base (minIntervalTicks 50) and the constructor default
267
+ * agree, so the consolidator's self_model.updated → narrative edge applies
268
+ * cleanly: a significant identity change lowers this interval and the Will
269
+ * re-narrates its life story sooner.
270
+ */
271
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
272
+ const p = readEffectiveParams( state, 'engine-config-narrator' )
273
+ if( p.minIntervalTicks != null ) this._minIntervalTicks = p.minIntervalTicks
274
+ if( p.maxNarrativeLength != null ) this._maxNarrativeLength = p.maxNarrativeLength
275
+ }
276
+
277
+ /**
278
+ * Rehydrate _narrative from the persisted 'self-narrative' entity.
279
+ * Called once on the first tick after a snapshot restore so the
280
+ * narrator continues from the saved story rather than the fresh-boot default.
281
+ */
282
+ private _restoreFromState( state: ReadonlySimulationState ): void {
283
+ const entity = state.entities.get('self-narrative')
284
+ if( !entity ) return
285
+ const m = entity.metadata ?? {}
286
+ const story = ( m['story'] as string | undefined ) ?? ''
287
+ if( !story ) return // entity exists but empty — keep the boot default
288
+
289
+ this._narrative = {
290
+ story,
291
+ themes: ( m['themes'] as string[] ) ?? this._narrative.themes,
292
+ pivotalEvents: ( m['pivotalEvents'] as string[] ) ?? [],
293
+ currentSelfView: ( m['currentSelfView'] as string ) ?? this._narrative.currentSelfView,
294
+ version: ( m['version'] as number ) ?? 0,
295
+ lastUpdatedAt: ( m['lastUpdatedAt'] as number ) ?? 0,
296
+ }
297
+ // Prevent the narrator from immediately rewriting a just-restored narrative
298
+ this._lastUpdateTick = this._narrative.lastUpdatedAt
299
+ }
300
+ }
@@ -0,0 +1,363 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/bias.detector.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * BiasDetector — identifies recurring patterns of error in the agent's
7
+ * own reasoning.
8
+ *
9
+ * Scans decision history for:
10
+ * - Anchoring (over-reliance on first piece of information)
11
+ * - Confirmation bias (seeking evidence that confirms existing beliefs)
12
+ * - Recency bias (overweighting recent events)
13
+ * - Availability bias (overweighting vivid/salient memories)
14
+ * - Overgeneralization (forming broad beliefs from few samples)
15
+ *
16
+ * Part of Shard 4 (Meta-Cognitive Layer) — runs periodically, synchronous.
17
+ */
18
+
19
+ import type {
20
+ Duration,
21
+ Tick,
22
+ SimulationContext,
23
+ ReadonlySimulationState,
24
+ StateCommands,
25
+ SimulationEvent,
26
+ } from '#core/types'
27
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
28
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
29
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
30
+ import { GenerativeModel } from '#cognition/generative.model'
31
+ import { readEffectiveParams } from '#cognition/persona.prior'
32
+
33
+ /** Confidence added to a detected bias the executive independently named. */
34
+ const EXECUTIVE_CORROBORATION_BOOST = 0.15
35
+
36
+ export interface BiasDetectorConfig {
37
+ /** Minimum decisions before bias detection activates */
38
+ minDecisions?: number
39
+ /** Ticks between bias scans */
40
+ scanIntervalTicks?: number
41
+ /** Whether to flag biases as events */
42
+ emitBiasEvents?: boolean
43
+ bus?: CognitiveBus
44
+ }
45
+
46
+ interface DetectedBias {
47
+ type: string
48
+ description: string
49
+ confidence: number
50
+ supportingEvidence: string[]
51
+ detectedAt: Tick
52
+ }
53
+
54
+ export class BiasDetector implements SimulationEngine, CognitiveEngine {
55
+ readonly name = 'bias-detector'
56
+
57
+ private _executiveNamedBiases: Set<string> = new Set()
58
+ private _minDecisions: number
59
+ private _scanIntervalTicks: number
60
+ private _emitBiasEvents: boolean
61
+
62
+ private _lastScanTick: number = 0
63
+ private _detectedBiases: DetectedBias[] = []
64
+
65
+ private _bus: CognitiveBus | null = null
66
+
67
+ private readonly _model = new GenerativeModel()
68
+
69
+ constructor( config: BiasDetectorConfig = {} ){
70
+ this._bus = config.bus ?? null
71
+ this._minDecisions = config.minDecisions ?? 10
72
+ this._scanIntervalTicks = config.scanIntervalTicks ?? 100
73
+ this._emitBiasEvents = config.emitBiasEvents ?? true
74
+ }
75
+
76
+ // ── Engine interface ─────────────────────────────────────
77
+
78
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
79
+ subscribes(): string[] {
80
+ return [
81
+ 'executive.self.reflection',
82
+ 'executive.prediction.formed'
83
+ ]
84
+ }
85
+ publishes(): CognitiveEventSchema[] { return [] }
86
+
87
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
88
+ this._model.observe( e.type, e.salience )
89
+
90
+ switch( e.type ){
91
+ case 'executive.self.reflection': {
92
+ // Executive named biases → seed the detector's known-bias list
93
+ const p = e.payload as Record<string, unknown>
94
+ const named = ( p['identifiedBiases'] as string[] ) ?? []
95
+ for( const n of named )
96
+ this._executiveNamedBiases.add( n )
97
+
98
+ break
99
+ }
100
+ case 'executive.prediction.formed': {
101
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
102
+ if( p.predictedDomains.includes('metacognition') )
103
+ this._model.setPrecision( 'bias.novelty', 1.0 + p.confidence * 0.5 )
104
+
105
+ break
106
+ }
107
+ }
108
+ }
109
+
110
+ snapshot(): Record<string, unknown> { return { executiveNamedBiases: this._executiveNamedBiases.size } }
111
+
112
+ async react(
113
+ _delta: Duration,
114
+ tick: Tick,
115
+ state: ReadonlySimulationState,
116
+ context: SimulationContext
117
+ ): Promise<EngineResult> {
118
+ const
119
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
120
+ commands: StateCommands = { metrics: [] }
121
+
122
+ // Effective config = base engine-config-bias-detector ⊕ persona-prior (single-source).
123
+ this._readConfigFromState( state )
124
+
125
+ if( tick - this._lastScanTick < this._scanIntervalTicks )
126
+ return { commands }
127
+
128
+ this._lastScanTick = tick
129
+
130
+ // Scan for biases
131
+ const newBiases = this._scanForBiases( state )
132
+
133
+ // Update detected biases
134
+ for( const bias of newBiases )
135
+ this._integrateBias( bias )
136
+
137
+ // Persist as entities
138
+ for( const bias of this._detectedBiases ){
139
+ commands.set ??= []
140
+ commands.set.push({
141
+ id: `bias-${bias.type}-${bias.detectedAt}`,
142
+ type: 'cognitive_bias',
143
+ createdAt: bias.detectedAt,
144
+ metadata: {
145
+ biasType: bias.type,
146
+ description: bias.description,
147
+ confidence: bias.confidence,
148
+ evidence: bias.supportingEvidence
149
+ }
150
+ })
151
+ }
152
+
153
+ commands.metrics!.push([
154
+ 'metacognition.biases_detected', this._detectedBiases.length
155
+ ])
156
+
157
+ if( newBiases.length > 0 && this._emitBiasEvents )
158
+ events.push({
159
+ type: 'bias.detected',
160
+ source: this.name,
161
+ payload: { newBiases: newBiases.map( b => b.type ) },
162
+ })
163
+
164
+
165
+ // Phase C + F: publish cognitive event — gated by prediction error
166
+ const _bus = this._bus
167
+ if( _bus && this._detectedBiases.length > 0 ){
168
+ // Surprise is driven by how many biases *this scan* surfaced, not the
169
+ // cumulative count (which only grows, then saturates against the prune
170
+ // cap — a near-constant the gate could never read as surprising).
171
+ const predErr = this._model.observe( 'bias.novelty', newBiases.length )
172
+ if( !predErr.gated )
173
+ _bus.publish({
174
+ type: 'bias.detected',
175
+ version: 1,
176
+ sourceEngine: this.name,
177
+ salience: Math.max( 0.4, predErr.salience ),
178
+ payload: {
179
+ count: this._detectedBiases.length,
180
+ newCount: newBiases.length,
181
+ types: newBiases.map( b => b.type )
182
+ }
183
+ })
184
+ }
185
+
186
+ return { events: events.length > 0 ? events : undefined, commands }
187
+ }
188
+
189
+ /**
190
+ * Effective config = base engine-config-bias-detector ⊕ persona-prior. Seeded
191
+ * base matches the constructor defaults (no drift). Only the numeric tunables
192
+ * are single-sourced here; `emitBiasEvents` is a boolean (stored 1/0 in the
193
+ * mirror) and stays constructor-only to avoid a lossy numeric coercion.
194
+ */
195
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
196
+ const p = readEffectiveParams( state, 'engine-config-bias-detector' )
197
+
198
+ if( p.minDecisions != null ) this._minDecisions = p.minDecisions
199
+ if( p.scanIntervalTicks != null ) this._scanIntervalTicks = p.scanIntervalTicks
200
+ }
201
+
202
+ // ── Bias scanning ────────────────────────────────────────
203
+
204
+ // NOTE (R2): `detectedAt` is the sim tick (state.tick), so detection replays
205
+ // deterministically and the field is a true Tick — no ms→tick cast.
206
+ private _scanForBiases( state: ReadonlySimulationState ): DetectedBias[] {
207
+ const biases: DetectedBias[] = []
208
+
209
+ // 1. Overgeneralization: high-confidence beliefs from few episodes
210
+ {
211
+ const
212
+ beliefs = [ ...state.entities.values() ].filter( e => e.type === 'belief' )
213
+ .map( e => ({
214
+ confidence: ( e.metadata?.['confidence'] as number ) ?? 0,
215
+ supportingEpisodes: ( e.metadata?.['supportingEpisodes'] as number ) ?? 0,
216
+ statement: ( e.metadata?.['statement'] as string ) ?? '',
217
+ })),
218
+ overgeneralized = beliefs.filter( b => b.confidence > 0.7 && b.supportingEpisodes < 5 )
219
+
220
+ if( overgeneralized.length > 0 )
221
+ biases.push({
222
+ type: 'overgeneralization',
223
+ description: `Formed ${overgeneralized.length} high-confidence beliefs from limited experience`,
224
+ confidence: Math.min( 0.9, overgeneralized.length * 0.2 ),
225
+ supportingEvidence: overgeneralized.map( b => b.statement ),
226
+ detectedAt: state.tick,
227
+ })
228
+ }
229
+
230
+ // 2. Recency bias: check if decisions heavily weight recent memories
231
+ {
232
+ const allEpisodes = [ ...state.entities.values() ]
233
+ .filter( e => e.type === 'episodic_memory' )
234
+ .map( e => ({ timestamp: ( e.metadata?.['tick'] as number ) ?? 0 }))
235
+ if( allEpisodes.length > 20 ){
236
+ // `e.timestamp` is an episode tick, so the recency window is 50 ticks.
237
+ const recentCount = allEpisodes.filter( e => e.timestamp > state.tick - 50 ).length
238
+
239
+ const recentRatio = recentCount / allEpisodes.length
240
+ if( recentRatio > 0.6 )
241
+ biases.push({
242
+ type: 'recency_bias',
243
+ description: `Heavy reliance on recent experiences (${( recentRatio * 100 ).toFixed( 0 )}% of weighted decisions)`,
244
+ confidence: Math.min( 0.8, recentRatio ),
245
+ supportingEvidence: [
246
+ `${recentCount} of ${allEpisodes.length} weighted episodes are recent`,
247
+ ],
248
+ detectedAt: state.tick,
249
+ })
250
+ }
251
+ }
252
+
253
+ // 3. Confirmation bias: check if beliefs are self-reinforcing
254
+ {
255
+ const selfBeliefs = [ ...state.entities.values() ]
256
+ .filter( e => e.type === 'belief'
257
+ && (e.metadata?.['category'] as string | undefined) === 'self_belief'
258
+ && ((e.metadata?.['confidence'] as number | undefined) ?? 0) >= 0.5
259
+ )
260
+ .map( e => ({
261
+ confidence: ( e.metadata?.['confidence'] as number ) ?? 0,
262
+ supportingEpisodes: ( e.metadata?.['supportingEpisodes'] as number ) ?? 0,
263
+ statement: ( e.metadata?.['statement'] as string ) ?? '',
264
+ }))
265
+
266
+ // Detection: if many self-beliefs cluster around the same theme
267
+ // and have high confidence despite limited evidence, possible confirmation bias
268
+ const highConfidenceLowEvidence = selfBeliefs.filter( b => b.confidence > 0.7 && b.supportingEpisodes < 8 )
269
+
270
+ if( highConfidenceLowEvidence.length >= 3 )
271
+ biases.push({
272
+ type: 'confirmation_bias',
273
+ description: 'Self-beliefs may be self-reinforcing without sufficient disconfirming evidence',
274
+ confidence: Math.min( 0.7, highConfidenceLowEvidence.length * 0.15 ),
275
+ supportingEvidence: highConfidenceLowEvidence.map( b => b.statement ),
276
+ detectedAt: state.tick,
277
+ })
278
+ }
279
+
280
+ // 4. Decision-pattern biases (overconfidence, repetition) — consolidated here so
281
+ // bias-detection has a single home (introspection used to detect these too).
282
+ {
283
+ const decisions = [ ...state.entities.values() ]
284
+ .filter( e => e.type === 'decision.record' )
285
+ .map( e => ({
286
+ actionType: ( e.metadata?.['actionType'] as string ) ?? 'unknown',
287
+ confidence: ( e.metadata?.['confidence'] as number ) ?? 0.5,
288
+ }))
289
+
290
+ if( decisions.length >= 5 ){
291
+ const avgConfidence = decisions.reduce( ( s, d ) => s + d.confidence, 0 ) / decisions.length
292
+ if( avgConfidence > 0.8 )
293
+ biases.push({
294
+ type: 'overconfidence',
295
+ description: `Recent decisions made at very high average confidence (${( avgConfidence * 100 ).toFixed( 0 )}%)`,
296
+ confidence: Math.min( 0.8, avgConfidence ),
297
+ supportingEvidence: [ `${decisions.length} decisions, average confidence ${avgConfidence.toFixed( 2 )}` ],
298
+ detectedAt: state.tick,
299
+ })
300
+
301
+ const uniqueActions = new Set( decisions.map( d => d.actionType ) )
302
+ if( uniqueActions.size === 1 )
303
+ biases.push({
304
+ type: 'repetition',
305
+ description: `Repetitive behaviour — only taking "${[ ...uniqueActions ][ 0 ]}" actions`,
306
+ confidence: 0.6,
307
+ supportingEvidence: [ `${decisions.length} consecutive "${[ ...uniqueActions ][ 0 ]}" decisions` ],
308
+ detectedAt: state.tick,
309
+ })
310
+ }
311
+ }
312
+
313
+ // Executive-corroboration: when the executive independently named a bias the
314
+ // detector also found this scan, raise that bias's confidence — two sources
315
+ // agreeing is stronger evidence. (Wires the previously-recorded-but-unused
316
+ // _executiveNamedBiases; Phase 1a tie-off.)
317
+ for( const b of biases )
318
+ if( this._executiveCorroborates( b.type ) )
319
+ b.confidence = Math.min( 1, b.confidence + EXECUTIVE_CORROBORATION_BOOST )
320
+
321
+ return biases
322
+ }
323
+
324
+ /**
325
+ * True if the executive (via executive.self.reflection) independently named a
326
+ * bias matching `type`. Normalised, fuzzy match — the executive names biases in
327
+ * free text ("recency bias") while the detector uses codes ("recency_bias").
328
+ */
329
+ private _executiveCorroborates( type: string ): boolean {
330
+ if( this._executiveNamedBiases.size === 0 ) return false
331
+ const norm = ( s: string ): string => s.toLowerCase().replace( /[^a-z]/g, '' )
332
+ const t = norm( type )
333
+ for( const named of this._executiveNamedBiases ){
334
+ const n = norm( named )
335
+ if( n.length > 0 && ( n.includes( t ) || t.includes( n ) ) ) return true
336
+ }
337
+ return false
338
+ }
339
+
340
+ private _integrateBias( newBias: DetectedBias ): void {
341
+ const existing = this._detectedBiases.find( b => b.type === newBias.type )
342
+
343
+ if( existing ){
344
+ existing.confidence = ( existing.confidence + newBias.confidence ) / 2
345
+ existing.supportingEvidence = [
346
+ ...existing.supportingEvidence,
347
+ ...newBias.supportingEvidence,
348
+ ].slice( 0, 10 )
349
+ }
350
+ else this._detectedBiases.push( newBias )
351
+
352
+ // Prune old biases
353
+ if( this._detectedBiases.length > 20 )
354
+ this._detectedBiases = this._detectedBiases.slice( -15 )
355
+ }
356
+
357
+ /**
358
+ * Get currently detected biases.
359
+ */
360
+ getDetectedBiases(): ReadonlyArray<DetectedBias> {
361
+ return this._detectedBiases
362
+ }
363
+ }