@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,294 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/circadian.oscillator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * CircadianOscillator — maintains an internal biological clock.
7
+ *
8
+ * Produces a ~24-hour rhythm that modulates:
9
+ * - Energy baseline (lower at night)
10
+ * - Cognitive performance (peaks during day)
11
+ * - Sleep propensity (highest at night)
12
+ * - Mood baseline (varies with time of day)
13
+ *
14
+ * The oscillator can be entrained by external light/dark signals
15
+ * and can drift if isolated (free-running period slightly > 24h).
16
+ *
17
+ * Part of Shard 0 (Regulatory Layer) — runs every tick, synchronous.
18
+ */
19
+
20
+ import type {
21
+ Duration,
22
+ Tick,
23
+ SimulationContext,
24
+ ReadonlySimulationState,
25
+ StateCommands,
26
+ SimulationEvent,
27
+ ModulationSignal,
28
+ } from '#core/types'
29
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
30
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
31
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
32
+ import { GenerativeModel } from '#cognition/generative.model'
33
+
34
+ export type CircadianPhase = 'morning' | 'afternoon' | 'evening' | 'night'
35
+
36
+ export interface CircadianConfig {
37
+ /** Period of the oscillator in hours (default 24.2 — free-running human) */
38
+ periodHours?: number
39
+ /** Phase offset in hours (shift the peak) */
40
+ phaseOffsetHours?: number
41
+ /** Whether external light signals entrain the oscillator */
42
+ entrainable?: boolean
43
+ /** Current simulated time of day in hours (0-24). If not provided, derived from tick. */
44
+ timeOfDayHours?: number
45
+ bus?: CognitiveBus
46
+ }
47
+
48
+ export class CircadianOscillator implements SimulationEngine, CognitiveEngine {
49
+ readonly name = 'circadian-oscillator'
50
+
51
+ private _periodHours: number
52
+ private _phaseOffsetHours: number
53
+ private _entrainable: boolean
54
+ private _timeOfDayHours?: number
55
+
56
+ private _bus: CognitiveBus | null = null
57
+
58
+ private readonly _model = new GenerativeModel()
59
+
60
+
61
+ constructor( config: CircadianConfig = {} ){
62
+ this._bus = config.bus ?? null
63
+ this._periodHours = config.periodHours ?? 24.2
64
+ this._phaseOffsetHours = config.phaseOffsetHours ?? 0
65
+ this._entrainable = config.entrainable ?? true
66
+ this._timeOfDayHours = config.timeOfDayHours
67
+ }
68
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
69
+
70
+ // ── Engine interface ─────────────────────────────────────
71
+
72
+
73
+ subscribes(): string[] {
74
+ return [
75
+ 'executive.prediction.formed'
76
+ ]
77
+ }
78
+ publishes(): CognitiveEventSchema[] { return [] }
79
+
80
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
81
+ this._model.observe( e.type, e.salience )
82
+ if( e.type === 'executive.prediction.formed' ){
83
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
84
+ if( p.predictedDomains.includes('circadian') )
85
+ this._model.setPrecision( 'circadian.alertness', 1.0 + p.confidence * 0.5 )
86
+ }
87
+ }
88
+
89
+ snapshot(): Record<string, unknown> { return {} }
90
+
91
+ async react(
92
+ delta: Duration,
93
+ tick: Tick,
94
+ state: ReadonlySimulationState,
95
+ context: SimulationContext
96
+ ): Promise<EngineResult> {
97
+ this._readConfigFromState( state )
98
+
99
+ const
100
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
101
+ commands: StateCommands = { metrics: [] }
102
+
103
+ // Determine current time of day
104
+ let timeOfDay = this._timeOfDayHours
105
+
106
+ if( timeOfDay === undefined ){
107
+ // Derive from tick, assuming 1 tick = 1 second
108
+ const totalSeconds = tick,
109
+ totalHours = totalSeconds / 3600,
110
+ dayFraction = ( totalHours % this._periodHours ) / this._periodHours
111
+
112
+ timeOfDay = ( dayFraction * 24 + this._phaseOffsetHours ) % 24
113
+ }
114
+
115
+ // Entrain to external light if available and enabled
116
+ if( this._entrainable ){
117
+ const externalLight = state.metrics.get('environment.light_level')
118
+ if( externalLight !== undefined ){
119
+ const isSubjectiveNight = timeOfDay >= 21 || timeOfDay < 6
120
+ if( isSubjectiveNight && externalLight > 0.5 )
121
+ timeOfDay = ( timeOfDay + ( externalLight - 0.5 ) * 0.1 ) % 24
122
+ }
123
+ }
124
+
125
+ // Clamp and normalize
126
+ timeOfDay = ( timeOfDay % 24 + 24 ) % 24
127
+
128
+ // Compute circadian outputs — all sinusoidal with phase offsets
129
+ const
130
+ radians = ( timeOfDay / 24 ) * Math.PI * 2,
131
+
132
+ // Core body temperature rhythm (peaks ~18:00, trough ~04:00)
133
+ temperaturePhase = radians - Math.PI * 0.75,
134
+ temperatureSignal = Math.sin( temperaturePhase ),
135
+
136
+ // Cognitive performance (peaks ~10:00-12:00, trough ~02:00-04:00)
137
+ cognitivePhase = radians - Math.PI * 0.25,
138
+ cognitiveSignal = Math.sin( cognitivePhase ),
139
+
140
+ // Sleep propensity (peaks ~02:00-04:00, secondary peak ~14:00)
141
+ sleepPhase = radians - Math.PI * 0.5,
142
+ sleepSignal = ( Math.sin( sleepPhase ) + 1 ) / 2, // Normalize 0-1
143
+
144
+ // Melatonin proxy (rises at dusk, peaks ~02:00, falls at dawn)
145
+ melatoninPhase = radians - Math.PI * 0.45,
146
+ melatoninSignal = Math.max( 0, Math.sin( melatoninPhase ) ),
147
+
148
+ // Alertness (peaks ~10:00, trough ~04:00)
149
+ alertnessPhase = radians - Math.PI * 0.3,
150
+ alertnessSignal = ( Math.sin( alertnessPhase ) + 1 ) / 2,
151
+
152
+ // Mood baseline (peaks ~12:00-14:00, trough ~03:00)
153
+ moodPhase = radians - Math.PI * 0.35,
154
+ moodSignal = ( Math.sin( moodPhase ) + 1 ) / 2
155
+
156
+ // Current phase label
157
+ const phaseLabel: CircadianPhase = timeOfDay >= 6 && timeOfDay < 12 ? 'morning'
158
+ : timeOfDay >= 12 && timeOfDay < 17 ? 'afternoon'
159
+ : timeOfDay >= 17 && timeOfDay < 21 ? 'evening'
160
+ : 'night'
161
+
162
+ // Phase transition detection
163
+ // state.metrics stores the previous phase as a numeric code for the
164
+ // metric system (metrics are always numbers). We compare the label
165
+ // against our own reconstruction.
166
+ const
167
+ previousPhaseCode = state.metrics.get('circadian.phase'),
168
+ previousPhaseLabel: CircadianPhase | undefined
169
+ = previousPhaseCode === 0 ? 'morning'
170
+ : previousPhaseCode === 1 ? 'afternoon'
171
+ : previousPhaseCode === 2 ? 'evening'
172
+ : previousPhaseCode === 3 ? 'night'
173
+ : undefined
174
+
175
+ if( previousPhaseLabel !== undefined && previousPhaseLabel !== phaseLabel )
176
+ events.push({
177
+ type: `circadian.${phaseLabel}`,
178
+ source: this.name,
179
+ payload: { timeOfDay, phase: phaseLabel, previousPhase: previousPhaseLabel },
180
+ })
181
+
182
+ // Phase code for metric storage (metrics are numeric)
183
+ const phaseCode = phaseLabel === 'morning' ? 0
184
+ : phaseLabel === 'afternoon' ? 1
185
+ : phaseLabel === 'evening' ? 2
186
+ : 3
187
+
188
+ // Update circadian metrics
189
+ commands.metrics!.push(
190
+ [ 'circadian.time_of_day', timeOfDay ],
191
+ [ 'circadian.phase', phaseCode ],
192
+ [ 'circadian.temperature_phase', temperatureSignal ],
193
+ [ 'circadian.cognitive_phase', cognitiveSignal ],
194
+ [ 'circadian.sleep_propensity', sleepSignal ],
195
+ [ 'circadian.melatonin', melatoninSignal ],
196
+ [ 'circadian.alertness', alertnessSignal ],
197
+ [ 'circadian.mood_baseline', moodSignal ],
198
+ )
199
+
200
+ // Modulation signals for other engines
201
+ events.push({
202
+ type: 'modulation.circadian_alertness',
203
+ source: this.name,
204
+ payload: {
205
+ target: 'attention-allocator',
206
+ parameter: 'capacity',
207
+ factor: 0.7 + alertnessSignal * 0.6,
208
+ source: this.name,
209
+ } satisfies ModulationSignal,
210
+ })
211
+
212
+ events.push({
213
+ type: 'modulation.circadian_cognitive',
214
+ source: this.name,
215
+ payload: {
216
+ target: 'decision-engine',
217
+ parameter: 'cognitive_phase',
218
+ factor: 0.8 + cognitiveSignal * 0.4,
219
+ source: this.name,
220
+ } satisfies ModulationSignal,
221
+ })
222
+
223
+ events.push({
224
+ type: 'modulation.circadian_sleep_propensity',
225
+ source: this.name,
226
+ payload: {
227
+ target: 'sleep-pressure-regulator',
228
+ parameter: 'sleep_gate',
229
+ factor: sleepSignal,
230
+ source: this.name,
231
+ } satisfies ModulationSignal,
232
+ })
233
+
234
+ events.push({
235
+ type: 'modulation.circadian_mood',
236
+ source: this.name,
237
+ payload: {
238
+ target: 'affective-blender',
239
+ parameter: 'mood_baseline',
240
+ factor: moodSignal,
241
+ source: this.name,
242
+ } satisfies ModulationSignal,
243
+ })
244
+
245
+
246
+ // Phase C: publish cognitive event
247
+ const _bus = this._bus
248
+ if( _bus )
249
+ _bus.publish({ type: 'circadian.alertness.shifted', version: 1, sourceEngine: this.name, salience: Math.abs(alertnessSignal - 0.5) * 0.6, payload: { alertness: alertnessSignal, phase: phaseLabel } })
250
+ // Phase D + F: rich state-change event — gated by prediction error
251
+ if( _bus ){
252
+ const predErr = this._model.observe( 'circadian.alertness', alertnessSignal )
253
+ if( !predErr.gated )
254
+ _bus.publish({ type: 'circadian.state.changed', version: 1, sourceEngine: this.name, salience: predErr.salience, payload: { alertness: alertnessSignal, timeOfDay, phaseCode, moodBaseline: moodSignal } })
255
+ }
256
+ return { events: events.length > 0 ? events : undefined, commands }
257
+ }
258
+
259
+ // ── Config hot-reload ────────────────────────────────────
260
+
261
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
262
+ const cfg = state.entities.get('engine-config-circadian')
263
+ if( !cfg ) return
264
+
265
+ const p = cfg.metadata?.params as Record<string, number> | undefined
266
+ if( !p ) return
267
+
268
+ if( p.periodHours != null ) this._periodHours = p.periodHours
269
+ if( p.phaseOffsetHours != null ) this._phaseOffsetHours = p.phaseOffsetHours
270
+
271
+ const entrainable = p.entrainable != null ? !!p.entrainable : undefined
272
+ if( entrainable != null ) this._entrainable = entrainable
273
+ }
274
+
275
+ // ── Public helpers ───────────────────────────────────────
276
+
277
+ /**
278
+ * Set the current time of day explicitly (for scenarios with controlled time).
279
+ */
280
+ setTimeOfDay( hours: number ): void {
281
+ this._timeOfDayHours = ( hours % 24 + 24 ) % 24
282
+ }
283
+
284
+ /**
285
+ * Get the current circadian phase.
286
+ */
287
+ getPhase(): CircadianPhase {
288
+ const h = this._timeOfDayHours ?? 12
289
+ return h >= 6 && h < 12 ? 'morning'
290
+ : h >= 12 && h < 17 ? 'afternoon'
291
+ : h >= 17 && h < 21 ? 'evening'
292
+ : 'night'
293
+ }
294
+ }
@@ -0,0 +1,324 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/confidence.calibrator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * ConfidenceCalibrator — compares decision confidence against actual
7
+ * outcomes to calibrate future confidence estimates.
8
+ *
9
+ * Maintains a calibration curve per action domain. If the agent
10
+ * consistently overestimates confidence in "planning" decisions,
11
+ * future planning confidence is adjusted downward.
12
+ *
13
+ * This is the mechanism behind "knowing what you don't know."
14
+ *
15
+ * Part of Shard 4 (Meta-Cognitive Layer) — runs every tick, synchronous.
16
+ */
17
+
18
+ import type {
19
+ Tick,
20
+ SimulationContext,
21
+ ReadonlySimulationState,
22
+ StateCommands,
23
+ SimulationEvent,
24
+ Duration,
25
+ } from '#core/types'
26
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
27
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
28
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
29
+ import { GenerativeModel, type GenerativeModelSnapshot } from '#cognition/generative.model'
30
+ import { readEffectiveParams } from '#cognition/persona.prior'
31
+
32
+ export interface ConfidenceCalibratorConfig {
33
+ /** Minimum outcome samples before calibration activates */
34
+ minSamplesPerDomain?: number
35
+ /** How aggressively to adjust calibration (learning rate) */
36
+ calibrationRate?: number
37
+ /** Maximum calibration adjustment per evaluation */
38
+ maxAdjustment?: number
39
+ bus?: CognitiveBus
40
+ }
41
+
42
+ interface CalibrationRecord {
43
+ confidence: number
44
+ outcomeQuality: number // 0-1: how good the outcome actually was
45
+ tick: Tick
46
+ }
47
+
48
+ export class ConfidenceCalibrator implements SimulationEngine, CognitiveEngine {
49
+ readonly name = 'confidence-calibrator'
50
+
51
+ private _executiveFlaggedBiasCount: number = 0
52
+ private _minSamplesPerDomain: number
53
+ private _calibrationRate: number
54
+ private _maxAdjustment: number
55
+
56
+ // Per-domain calibration records
57
+ private _records = new Map<string, CalibrationRecord[]>()
58
+
59
+ // Per-domain bias estimate (-1 = underconfident, +1 = overconfident)
60
+ private _domainBias = new Map<string, number>()
61
+
62
+ private _bus: CognitiveBus | null = null
63
+ private _restored = false
64
+
65
+ private readonly _model = new GenerativeModel()
66
+
67
+
68
+ constructor( config: ConfidenceCalibratorConfig = {} ){
69
+ this._bus = config.bus ?? null
70
+ this._minSamplesPerDomain = config.minSamplesPerDomain ?? 5
71
+ this._calibrationRate = config.calibrationRate ?? 0.1
72
+ this._maxAdjustment = config.maxAdjustment ?? 0.3
73
+ }
74
+
75
+ // ── Public API ───────────────────────────────────────────
76
+
77
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
78
+ subscribes(): string[] {
79
+ return [
80
+ 'executive.self.reflection',
81
+ 'executive.prediction.formed',
82
+ 'action.outcome'
83
+ ]
84
+ }
85
+ publishes(): CognitiveEventSchema[] { return [] }
86
+
87
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
88
+ this._model.observe( e.type, e.salience )
89
+ switch( e.type ){
90
+ case 'executive.self.reflection': {
91
+ // Executive named biases — flag for next calibration pass
92
+ const p = e.payload as Record<string, unknown>
93
+ this._executiveFlaggedBiasCount = ( ( p['identifiedBiases'] as string[] ) ?? [] ).length
94
+
95
+ break
96
+ }
97
+ case 'executive.prediction.formed': {
98
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
99
+ if( p.predictedDomains.includes('metacognition') )
100
+ this._model.setPrecision( 'metacognition.bias', 1.0 + p.confidence * 0.5 )
101
+
102
+ break
103
+ }
104
+ case 'action.outcome': {
105
+ // Pair the decision's predicted confidence against the realised outcome
106
+ // quality so per-domain over/under-confidence can be learned in react().
107
+ const p = e.payload as { domain?: string; confidence?: number; outcomeQuality?: number; tick?: number }
108
+ if( typeof p.confidence === 'number' && typeof p.outcomeQuality === 'number' )
109
+ this.recordOutcome( p.domain ?? 'general', p.confidence, p.outcomeQuality, ( ( p.tick ?? 0 ) as Tick ) )
110
+
111
+ break
112
+ }
113
+ }
114
+ }
115
+
116
+ snapshot(): Record<string, unknown> {
117
+ // FN9: capture the learned calibration curve (_domainBias) and the raw
118
+ // outcome records that produce it, plus the salience/generative sub-states,
119
+ // so a restored Will keeps "knowing what it doesn't know" instead of
120
+ // re-learning every domain bias from zero. Maps are flattened to entry
121
+ // arrays so the payload stays plain-JSON-safe.
122
+ return {
123
+ executiveFlaggedBiasCount: this._executiveFlaggedBiasCount,
124
+ records: [ ...this._records.entries() ].map( ([ k, v ]) => [ k, v.map( r => ({ ...r }) ) ] ),
125
+ domainBias: [ ...this._domainBias.entries() ],
126
+ model: this._model.snapshot()
127
+ }
128
+ }
129
+
130
+ restore( snap: Record<string, unknown> ): void {
131
+ if( !snap ) return
132
+
133
+ if( typeof snap.executiveFlaggedBiasCount === 'number' )
134
+ this._executiveFlaggedBiasCount = snap.executiveFlaggedBiasCount
135
+
136
+ if( Array.isArray( snap.records ) )
137
+ this._records = new Map( snap.records as Array<[ string, CalibrationRecord[] ]> )
138
+
139
+ if( Array.isArray( snap.domainBias ) )
140
+ this._domainBias = new Map( snap.domainBias as Array<[ string, number ]> )
141
+
142
+ if( snap.model ) this._model.restore( snap.model as GenerativeModelSnapshot )
143
+ }
144
+
145
+ /**
146
+ * Rehydrate the learned calibration curve from the persisted `calibration-state`
147
+ * entity (Phase 2, Option B — the entity-restore path that's actually wired at
148
+ * boot). Called once on the first react after a restore. Bias is restored
149
+ * directly so calibration is continuous immediately, rather than waiting to
150
+ * re-accumulate minSamplesPerDomain fresh outcomes.
151
+ */
152
+ private _restoreFromState( state: ReadonlySimulationState ): void {
153
+ const m = state.entities.get('calibration-state')?.metadata?.domainBias as Record<string, number> | undefined
154
+ if( !m ) return
155
+
156
+ for( const [ domain, bias ] of Object.entries( m ) )
157
+ if( typeof bias === 'number' )
158
+ this._domainBias.set( domain, bias )
159
+ }
160
+
161
+ /**
162
+ * Record a decision and its eventual outcome.
163
+ * Called when an action's outcome is known.
164
+ */
165
+ recordOutcome(
166
+ domain: string,
167
+ confidence: number,
168
+ outcomeQuality: number,
169
+ tick: Tick
170
+ ): void {
171
+ const records = this._records.get( domain ) ?? []
172
+ records.push({ confidence, outcomeQuality, tick })
173
+
174
+ // Keep last 100 records per domain
175
+ if( records.length > 100 ) records.shift()
176
+
177
+ this._records.set( domain, records )
178
+ }
179
+
180
+ /**
181
+ * Get the calibrated confidence for a given raw confidence in a domain.
182
+ * Adjusts based on historical over/under-confidence patterns.
183
+ */
184
+ getCalibratedConfidence( domain: string, rawConfidence: number ): number {
185
+ const bias = this._domainBias.get( domain ) ?? 0
186
+ // Overconfident → reduce. Underconfident → increase.
187
+ const adjusted = rawConfidence - bias * rawConfidence
188
+
189
+ return Math.max( 0, Math.min( 1, adjusted ) )
190
+ }
191
+
192
+ /**
193
+ * Effective config = base engine-config-confidence ⊕ persona-prior. Seeded base
194
+ * matches the constructor defaults (no drift), so this just single-sources the
195
+ * tunables and lets a future persona-prior modulate calibration aggressiveness.
196
+ */
197
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
198
+ const p = readEffectiveParams( state, 'engine-config-confidence' )
199
+
200
+ if( p.minSamplesPerDomain != null ) this._minSamplesPerDomain = p.minSamplesPerDomain
201
+ if( p.calibrationRate != null ) this._calibrationRate = p.calibrationRate
202
+ if( p.maxAdjustment != null ) this._maxAdjustment = p.maxAdjustment
203
+ }
204
+
205
+ // ── Engine interface ─────────────────────────────────────
206
+
207
+ async react(
208
+ _delta: Duration,
209
+ tick: Tick,
210
+ state: ReadonlySimulationState,
211
+ context: SimulationContext
212
+ ): Promise<EngineResult> {
213
+ const
214
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
215
+ commands: StateCommands = { set: [], metrics: [] }
216
+
217
+ // On the first tick after a restart, rehydrate the learned calibration curve
218
+ // from its persisted entity (Phase 2, Option B). Entity state is the wired
219
+ // restore path, so the Will keeps "knowing what it doesn't know" across
220
+ // restarts even though the engine-internal restore() seam isn't invoked at boot.
221
+ if( !this._restored ){
222
+ this._restoreFromState( state )
223
+ this._restored = true
224
+ }
225
+
226
+ // Effective config = base engine-config-confidence ⊕ persona-prior (single-source).
227
+ this._readConfigFromState( state )
228
+
229
+ // Recalibrate each domain with enough samples
230
+ for( const [ domain, records ] of this._records ){
231
+ if( records.length < this._minSamplesPerDomain ) continue
232
+
233
+ const bias = this._computeBias( records )
234
+ const previousBias = this._domainBias.get( domain ) ?? 0
235
+
236
+ // Smooth update
237
+ const newBias = previousBias + ( bias - previousBias ) * this._calibrationRate
238
+ const clampedBias = Math.max( -this._maxAdjustment, Math.min( this._maxAdjustment, newBias ) )
239
+
240
+ this._domainBias.set( domain, clampedBias )
241
+
242
+ // Metric
243
+ commands.metrics!.push([ `calibration.${domain}.bias`, clampedBias ])
244
+
245
+ // Event if bias changed significantly
246
+ if( Math.abs( clampedBias - previousBias ) > 0.1 )
247
+ events.push({
248
+ type: 'calibration.shift',
249
+ source: this.name,
250
+ payload: {
251
+ domain,
252
+ previousBias,
253
+ newBias: clampedBias,
254
+ interpretation: clampedBias > 0.1 ? 'overconfident'
255
+ : clampedBias < -0.1 ? 'underconfident'
256
+ : 'well_calibrated'
257
+ }
258
+ })
259
+ }
260
+
261
+ // Overall calibration metric
262
+ const allBiases = Array.from( this._domainBias.values() )
263
+ const overallBias = allBiases.length > 0 ? allBiases.reduce( ( s, b ) => s + b, 0 ) / allBiases.length : 0
264
+
265
+ commands.metrics!.push([ 'metacognition.calibration_bias', overallBias ])
266
+
267
+ // Persist the learned calibration curve as an entity so it survives restart
268
+ // (durable persona). Written from _domainBias — independent of _records,
269
+ // which re-accumulate from action.outcome after a restart. `tick` keeps the
270
+ // write deterministic (R2).
271
+ if( this._domainBias.size > 0 )
272
+ commands.set!.push({
273
+ id: 'calibration-state',
274
+ type: 'calibration.state',
275
+ metadata: {
276
+ domainBias: Object.fromEntries( this._domainBias ),
277
+ updatedAtTick: tick
278
+ }
279
+ })
280
+
281
+
282
+ // Phase C: publish cognitive event
283
+ const _bus = this._bus
284
+ if( _bus )
285
+ _bus.publish({
286
+ type: 'confidence.calibrated',
287
+ version: 1,
288
+ sourceEngine: this.name,
289
+ salience: Math.max( 0.2, this._model.observe( 'calibration.event', overallBias ).salience ),
290
+ payload: { calibrationBias: overallBias }
291
+ })
292
+
293
+ // Phase D + F: rich state-change event — gated by prediction error
294
+ if( _bus ){
295
+ const predErr = this._model.observe( 'metacognition.bias', overallBias )
296
+ if( !predErr.gated )
297
+ _bus.publish({
298
+ type: 'metacognition.state.changed',
299
+ version: 1,
300
+ sourceEngine: this.name,
301
+ salience: predErr.salience,
302
+ payload: { confidence: overallBias }
303
+ })
304
+ }
305
+
306
+ return { events: events.length > 0 ? events : undefined, commands }
307
+ }
308
+
309
+ // ── Internal ─────────────────────────────────────────────
310
+
311
+ /**
312
+ * Compute calibration bias from records.
313
+ * Positive bias = overconfident (confidence > outcome).
314
+ * Negative bias = underconfident (confidence < outcome).
315
+ */
316
+ private _computeBias( records: CalibrationRecord[] ): number {
317
+ let totalBias = 0
318
+
319
+ for( const record of records )
320
+ totalBias += record.confidence - record.outcomeQuality
321
+
322
+ return totalBias / records.length
323
+ }
324
+ }