@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,750 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/semantic.engine/integrator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * SemanticIntegrator — satellite mode.
7
+ *
8
+ * Integrates beliefs from the ExecutiveEngine and performs heuristic
9
+ * pattern detection for simple statistical patterns.
10
+ * No longer makes its own LLM calls.
11
+ *
12
+ * UPGRADE: Uses semanticQuery from EpisodicConsolidator for more
13
+ * intelligent pattern detection when vector memory is available.
14
+ */
15
+
16
+ import { logger } from '#core/logger'
17
+ import type {
18
+ Duration, Tick, SimulationContext,
19
+ ReadonlySimulationState, StateCommands,
20
+ } from '#core/types'
21
+ import type { SimulationEngine, EngineResult } from '#cognition/types'
22
+ import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
23
+ import type { SessionLogger } from '#stem/tracts/session.logger'
24
+ import type { CognitiveEngine } from '#cognition/types'
25
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
26
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
27
+ import { EVIDENCE_TO_COUNT } from '#faculties/executive.engine/commands'
28
+ import { GenerativeModel } from '#cognition/generative.model'
29
+ import { readEffectiveParams } from '#cognition/persona.prior'
30
+ import { _STOP_WORDS, type Belief, type BeliefHistoryEntry, type SemanticIntegratorConfig } from '#faculties/semantic.engine/types'
31
+ import { SemanticClustering } from '#faculties/semantic.engine/clustering'
32
+
33
+ // Re-export the belief types so consumers that import SemanticIntegrator from
34
+ // this module can also pull Belief/BeliefHistoryEntry from the same path.
35
+ export type { Belief, BeliefHistoryEntry } from './types'
36
+
37
+ export class SemanticIntegrator implements SimulationEngine, CognitiveEngine {
38
+ readonly name = 'semantic-integrator'
39
+
40
+ private _minIntervalTicks: number
41
+ private _minNewEpisodes: number
42
+ private _maxBeliefs: number
43
+ private _beliefStalenessThreshold: number
44
+ private _beliefDecayRate: number
45
+ private _semanticSimilarityThreshold: number
46
+ private _semanticQueryLimit: number
47
+
48
+ private _beliefs: Belief[] = []
49
+ private _lastIntegrationTick: number = 0
50
+ private _episodeCountAtLastIntegration: number = 0
51
+ private _semanticClustering: SemanticClustering | null = null
52
+ private _episodicConsolidator: EpisodicConsolidator | null = null
53
+ private _sessionLogger: SessionLogger | null = null
54
+ private _restored = false
55
+
56
+ /**
57
+ * Monotonic suffix counter for belief ids. Replaces Math.random() AND the
58
+ * former `Date.now()` component so the same seed+inputs reproduce the same
59
+ * belief ids on replay (R2). Combined with the id prefix it is unique per
60
+ * engine instance, which is sufficient for belief ids.
61
+ */
62
+ private _idSeq = 0
63
+
64
+ private _bus: CognitiveBus | null = null
65
+
66
+ private readonly _model = new GenerativeModel()
67
+
68
+
69
+ constructor( config: SemanticIntegratorConfig = {} ){
70
+ this._bus = config.bus ?? null
71
+ this._minIntervalTicks = config.minIntervalTicks ?? 30
72
+ this._minNewEpisodes = config.minNewEpisodes ?? 10
73
+ this._maxBeliefs = config.maxBeliefs ?? 500
74
+ this._beliefStalenessThreshold = config.beliefStalenessThreshold ?? 300
75
+ this._beliefDecayRate = config.beliefDecayRate ?? 0.001
76
+ this._semanticSimilarityThreshold = config.semanticSimilarityThreshold ?? 0.65
77
+ this._semanticQueryLimit = config.semanticQueryLimit ?? 20
78
+ }
79
+
80
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
81
+ attachConsolidator( consolidator: EpisodicConsolidator ): void { this._episodicConsolidator = consolidator }
82
+ attachSemanticClustering( ct: SemanticClustering ): void { this._semanticClustering = ct }
83
+ attachSessionLogger( logger: SessionLogger | null ): void { this._sessionLogger = logger }
84
+
85
+ /**
86
+ * Called by ExecutiveEngine when it produces new beliefs.
87
+ * Confidence is capped by evidence count before integration — prevents the
88
+ * executive from asserting high-certainty beliefs from thin episodic support.
89
+ */
90
+ integrateExecutiveBelief( belief: Belief, tick?: number, cause = 'executive' ): void {
91
+ const capped = this._capConfidenceByEvidence( belief.confidence, belief.supportingEpisodes )
92
+ const result = this._integrateBelief({ ...belief, confidence: capped }, cause )
93
+
94
+ this._sessionLogger?.write({
95
+ type: 'belief.integrate',
96
+ tick,
97
+ id: result.id,
98
+ statement: result.statement,
99
+ category: result.category,
100
+ rawConfidence: belief.confidence,
101
+ cappedConfidence: capped,
102
+ storedConfidence: result.confidence,
103
+ supportingEpisodes: belief.supportingEpisodes,
104
+ tags: belief.tags,
105
+ merged: result.id !== belief.id,
106
+ })
107
+ }
108
+
109
+ /**
110
+ * Evidence-based confidence ceiling.
111
+ * Thin evidence cannot support high confidence regardless of how the executive reasons.
112
+ * Mirrors how scientific confidence scales with replications, not just reasoning quality.
113
+ */
114
+ /**
115
+ * Rehydrate _beliefs from 'belief' entities in state.
116
+ * Called once on the first tick after a snapshot restore so that beliefs
117
+ * formed in previous sessions survive a server restart.
118
+ */
119
+ private _restoreFromState( state: ReadonlySimulationState ): void {
120
+ for( const entity of state.entities.values() ){
121
+ if( entity.type !== 'belief' ) continue
122
+ if( this._beliefs.some( b => b.id === entity.id ) ) continue
123
+
124
+ const m = entity.metadata ?? {}
125
+ // Copy the arrays out of the (frozen) restored state — the integrator mutates them
126
+ // in place later (_recordHistory), which would crash on the frozen originals.
127
+ this._beliefs.push({
128
+ id: entity.id,
129
+ statement: ( m['statement'] as string ) ?? '',
130
+ category: ( m['category'] as Belief['category'] ) ?? 'world_fact',
131
+ confidence: ( m['confidence'] as number ) ?? 0.5,
132
+ supportingEpisodes: ( m['supportingEpisodes'] as number ) ?? 0,
133
+ lastUpdatedAt: ( m['lastUpdatedAt'] as number ) ?? 0,
134
+ tags: [ ...( ( m['tags'] as string[] ) ?? [] ) ],
135
+ history: [ ...( ( m['history'] as BeliefHistoryEntry[] ) ?? [] ) ],
136
+ })
137
+ }
138
+ }
139
+
140
+ private _capConfidenceByEvidence( confidence: number, episodes: number ): number {
141
+ if( episodes < 2 ) return Math.min( confidence, 0.60 )
142
+ if( episodes < 5 ) return Math.min( confidence, 0.72 )
143
+ if( episodes < 10 ) return Math.min( confidence, 0.82 )
144
+ if( episodes < 20 ) return Math.min( confidence, 0.90 )
145
+ return Math.min( confidence, 0.97 )
146
+ }
147
+
148
+ subscribes(): string[] {
149
+ return [
150
+ 'executive.prediction.formed',
151
+ 'executive.facet.progress'
152
+ ]
153
+ }
154
+ publishes(): CognitiveEventSchema[] { return [] }
155
+
156
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
157
+ this._model.observe( e.type, e.salience )
158
+
159
+ switch( e.type ){
160
+ case 'executive.prediction.formed': {
161
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
162
+ if( p.predictedDomains.includes('memory') )
163
+ this._model.setPrecision( 'belief.count', 1.0 + p.confidence * 0.5 )
164
+
165
+ break
166
+ }
167
+ case 'executive.facet.progress': {
168
+ const payload = e.payload as {
169
+ facetId?: string
170
+ planId?: string
171
+ goalProgress?: number
172
+ newGoals?: Array<{
173
+ description: string; priority: number; tags: string[]
174
+ completionType: string; completionCondition?: string
175
+ }>
176
+ goalsToAbandon?: Array<{ goalId: string; reason: string }>
177
+ newBeliefs?: Array<{
178
+ statement: string; category: string; confidence: number
179
+ evidence: 'single_observation' | 'recurring_pattern' | 'strong_pattern'; tags: string[]
180
+ }>
181
+ knownEntityUpdates?: Array<{ keid: string; name?: string; learned?: string[]; feeling?: number }>
182
+ }
183
+
184
+ // Forward new beliefs to SemanticIntegrator
185
+ if( payload.newBeliefs )
186
+ for( const nb of payload.newBeliefs )
187
+ this.integrateExecutiveBelief({
188
+ id: `belief-facet-${( this._idSeq++ ).toString( 36 )}`,
189
+ statement: nb.statement,
190
+ category: nb.category as any,
191
+ confidence: nb.confidence,
192
+ supportingEpisodes: EVIDENCE_TO_COUNT[ nb.evidence ] ?? 1,
193
+ // lastUpdatedAt is a sim Tick (used in `tick - belief.lastUpdatedAt`
194
+ // staleness math), so use the last integration tick, not wall-clock (R2).
195
+ lastUpdatedAt: this._lastIntegrationTick,
196
+ tags: nb.tags
197
+ }, undefined, 'facet')
198
+
199
+ // Facts the facet learned about an other → keid-tagged social beliefs (the
200
+ // conversation-side analogue of the master's buildStateCommands path).
201
+ if( payload.knownEntityUpdates )
202
+ for( const u of payload.knownEntityUpdates )
203
+ for( const fact of u.learned ?? [] )
204
+ if( fact && u.keid && u.keid !== 'agent-self' )
205
+ this.integrateExecutiveBelief({
206
+ id: `belief-ke-facet-${( this._idSeq++ ).toString( 36 )}`,
207
+ statement: fact,
208
+ category: 'social_belief' as any,
209
+ confidence: 0.7,
210
+ supportingEpisodes: 1,
211
+ lastUpdatedAt: this._lastIntegrationTick,
212
+ tags: [ 'social', 'known-entity', `keid:${u.keid}` ]
213
+ }, undefined, 'facet')
214
+
215
+ break
216
+ }
217
+ }
218
+ }
219
+
220
+ snapshot(): Record<string, unknown> { return {} }
221
+
222
+ /**
223
+ * Effective config = base engine-config-semantic ⊕ persona-prior (single-source).
224
+ * No-op at boot: mirror params equal the constructor defaults (reconciled in #83).
225
+ */
226
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
227
+ const p = readEffectiveParams( state, 'engine-config-semantic' )
228
+ if( p.minIntervalTicks != null ) this._minIntervalTicks = p.minIntervalTicks
229
+ if( p.minNewEpisodes != null ) this._minNewEpisodes = p.minNewEpisodes
230
+ if( p.maxBeliefs != null ) this._maxBeliefs = p.maxBeliefs
231
+ if( p.beliefStalenessThreshold != null ) this._beliefStalenessThreshold = p.beliefStalenessThreshold
232
+ if( p.beliefDecayRate != null ) this._beliefDecayRate = p.beliefDecayRate
233
+ }
234
+
235
+ async react(
236
+ _delta: Duration,
237
+ tick: Tick,
238
+ state: ReadonlySimulationState,
239
+ context: SimulationContext
240
+ ): Promise<EngineResult> {
241
+ // Effective config = base engine-config-semantic ⊕ persona-prior (single-source).
242
+ this._readConfigFromState( state )
243
+
244
+ // On the very first tick (including after snapshot restore), rehydrate
245
+ // the in-memory _beliefs array from persisted 'belief' state entities.
246
+ // Without this, every restart begins with zero beliefs regardless of snapshot.
247
+ if( !this._restored ){
248
+ this._restoreFromState( state )
249
+ this._restored = true
250
+ }
251
+
252
+ const commands: StateCommands = { set: [], metrics: [] }
253
+
254
+ // Executive beliefs are integrated in real-time via integrateExecutiveBelief()
255
+ // called by the ExecutiveEngine's onReasoningComplete().
256
+ // Here we just handle heuristic pattern detection and persistence.
257
+
258
+ // Heuristic pattern detection — upgraded to use semantic query
259
+ if( this._episodicConsolidator && this._shouldRunHeuristic( tick ) ){
260
+ const newBeliefs = await this._heuristicPatternDetection( tick, state )
261
+ for( const belief of newBeliefs )
262
+ this._integrateBelief( belief, 'heuristic' )
263
+
264
+ this._lastIntegrationTick = tick
265
+ if( this._episodicConsolidator )
266
+ this._episodeCountAtLastIntegration = this._episodicConsolidator.getAllEpisodes().length
267
+ }
268
+
269
+ // Staleness decay — beliefs not reinforced for a long time lose confidence gradually.
270
+ // This prevents early high-confidence beliefs from dominating forever and forces
271
+ // the executive to re-affirm important beliefs to keep them strong.
272
+ //
273
+ // Guard: only decay a belief once at least one consolidation cycle has run
274
+ // *after* the belief was created or last updated. This ensures every belief
275
+ // gets at least one reinforcement opportunity before erosion begins. Beliefs
276
+ // from a restored session are protected until the first heuristic cycle fires
277
+ // this session (i.e. _lastIntegrationTick advances past their lastUpdatedAt).
278
+ for( const belief of this._beliefs ){
279
+ const staleness = tick - belief.lastUpdatedAt
280
+
281
+ const hadOpportunity = this._lastIntegrationTick > belief.lastUpdatedAt
282
+ if( staleness > this._beliefStalenessThreshold && hadOpportunity ){
283
+ const prev = belief.confidence
284
+ belief.confidence = Math.max( 0.10, belief.confidence - this._beliefDecayRate )
285
+ if( belief.confidence !== prev )
286
+ SemanticIntegrator._recordHistory( belief, tick, prev, 'decayed' )
287
+ }
288
+ }
289
+
290
+ // Prune beliefs that have decayed to near-zero
291
+ this._beliefs = this._beliefs.filter( b => b.confidence > 0.12 )
292
+
293
+ // Persist all beliefs
294
+ for( const belief of this._beliefs )
295
+ commands.set!.push({
296
+ id: belief.id,
297
+ type: 'belief',
298
+ metadata: {
299
+ statement: belief.statement,
300
+ category: belief.category,
301
+ confidence: belief.confidence,
302
+ supportingEpisodes: belief.supportingEpisodes,
303
+ lastUpdatedAt: belief.lastUpdatedAt,
304
+ // Copy the arrays into state. Applied state is deep-frozen, so handing over the
305
+ // live reference would freeze the integrator's own working array and crash the
306
+ // next in-place mutation (_recordHistory push/shift on a reinforced belief).
307
+ tags: [ ...belief.tags ],
308
+ history: [ ...( belief.history ?? [] ) ],
309
+ },
310
+ })
311
+
312
+ if( !commands.metrics ) commands.metrics = []
313
+ commands.metrics.push([ 'memory.beliefs_total', this._beliefs.length ])
314
+
315
+ // Phase C + F: publish cognitive event — gated by prediction error
316
+ const _bus = this._bus
317
+ if( _bus && this._beliefs.length > 0 ){
318
+ const predErr = this._model.observe( 'belief.count', this._beliefs.length )
319
+ if( !predErr.gated )
320
+ _bus.publish({
321
+ type: 'belief.updated',
322
+ version: 1,
323
+ sourceEngine: this.name,
324
+ salience: Math.max( 0.2, predErr.salience ),
325
+ payload: { total: this._beliefs.length }
326
+ })
327
+ }
328
+
329
+ return { commands }
330
+ }
331
+
332
+ // ── Heuristic pattern detection (upgraded) ─────────────────
333
+
334
+ private _shouldRunHeuristic( tick: Tick ): boolean {
335
+ if( !this._episodicConsolidator ) return false
336
+
337
+ const ticksSince = tick - this._lastIntegrationTick
338
+ const totalEpisodes = this._episodicConsolidator.getAllEpisodes().length
339
+ const newEpisodes = totalEpisodes - this._episodeCountAtLastIntegration
340
+
341
+ return ticksSince >= this._minIntervalTicks && newEpisodes >= this._minNewEpisodes
342
+ }
343
+
344
+ private async _heuristicPatternDetection(
345
+ tick: Tick,
346
+ state: ReadonlySimulationState
347
+ ): Promise<Belief[]> {
348
+ if( !this._episodicConsolidator ) return []
349
+
350
+ const beliefs: Belief[] = []
351
+
352
+ // ── Try semantic pattern detection first ─────────
353
+ const semanticBeliefs = await this._semanticPatternDetection( tick, state )
354
+ if( semanticBeliefs.length > 0 ){
355
+ logger.info(`[semantic] detected ${semanticBeliefs.length} semantic patterns`)
356
+ return semanticBeliefs
357
+ }
358
+
359
+ // ── FALLBACK: Traditional heuristic detection ────────────
360
+ const episodes = this._episodicConsolidator.getAllEpisodes()
361
+ const newEpisodes = episodes.slice( this._episodeCountAtLastIntegration )
362
+
363
+ // Skip if no new episodes
364
+ if( newEpisodes.length === 0 ) return beliefs
365
+
366
+ // Frequency-based patterns
367
+ if( newEpisodes.length >= 5 ){
368
+ const sourceTypes = new Map<string, number>()
369
+ for( const ep of newEpisodes )
370
+ sourceTypes.set( ep.sourceType, ( sourceTypes.get( ep.sourceType ) ?? 0 ) + 1 )
371
+
372
+ for( const [ sourceType, count ] of sourceTypes ){
373
+ if( count >= 3 )
374
+ beliefs.push({
375
+ id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
376
+ statement: `I frequently encounter situations of type: ${sourceType}`,
377
+ category: 'pattern',
378
+ confidence: Math.min( 0.6, count / 10 ),
379
+ supportingEpisodes: count,
380
+ lastUpdatedAt: tick,
381
+ tags: [ sourceType, 'frequency', 'pattern' ],
382
+ })
383
+ }
384
+ }
385
+
386
+ // Emotional pattern detection
387
+ if( newEpisodes.length >= 8 ){
388
+ let positiveCount = 0
389
+ let negativeCount = 0
390
+
391
+ for( const ep of newEpisodes ){
392
+ const valence = ep.affectiveContext?.valence ?? 0
393
+
394
+ if( valence > 0.2 ) positiveCount++
395
+ if( valence < -0.2 ) negativeCount++
396
+ }
397
+
398
+ if( positiveCount > negativeCount * 2 )
399
+ beliefs.push({
400
+ id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
401
+ statement: 'Recent experiences have been predominantly positive',
402
+ category: 'pattern',
403
+ confidence: Math.min( 0.5, positiveCount / newEpisodes.length ),
404
+ supportingEpisodes: positiveCount,
405
+ lastUpdatedAt: tick,
406
+ tags: [ 'emotional', 'positive', 'pattern' ],
407
+ })
408
+
409
+ else if( negativeCount > positiveCount * 2 )
410
+ beliefs.push({
411
+ id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
412
+ statement: 'Recent experiences have been predominantly negative',
413
+ category: 'pattern',
414
+ confidence: Math.min( 0.5, negativeCount / newEpisodes.length ),
415
+ supportingEpisodes: negativeCount,
416
+ lastUpdatedAt: tick,
417
+ tags: [ 'emotional', 'negative', 'pattern' ],
418
+ })
419
+ }
420
+
421
+ // Semantic clustering
422
+ if( this._semanticClustering ){
423
+ const
424
+ newEpisodes = this._episodicConsolidator.getAllEpisodes().slice( -30 ),
425
+ clusterBeliefs = await this._semanticClustering.discoverClusters( tick, newEpisodes )
426
+
427
+ beliefs.push( ...clusterBeliefs )
428
+ }
429
+
430
+ return beliefs
431
+ }
432
+
433
+ /**
434
+ * UPGRADE: Semantic pattern detection using vector memory.
435
+ *
436
+ * Identifies clusters of semantically similar episodes that share
437
+ * emotional or thematic patterns not captured by simple tag/type matching.
438
+ *
439
+ * Falls back to traditional heuristics if vector memory not available
440
+ * or if no meaningful clusters are found.
441
+ */
442
+ private async _semanticPatternDetection(
443
+ tick: Tick,
444
+ state: ReadonlySimulationState
445
+ ): Promise<Belief[]> {
446
+ if( !this._episodicConsolidator ) return []
447
+
448
+ // Check if vector memory is available by attempting semanticQuery
449
+ // (will return empty or throw if not configured)
450
+ const allEpisodes = this._episodicConsolidator.getAllEpisodes()
451
+ if( allEpisodes.length < 10 ) return []
452
+
453
+ // Query for semantically similar episodes — use a neutral query that
454
+ // captures the "essence" of recent experiences
455
+ const recentEpisodes = allEpisodes.slice( -this._semanticQueryLimit )
456
+ if( recentEpisodes.length < 5 ) return []
457
+
458
+ // Build a query from recent episode content
459
+ const queryText = this._buildSemanticQueryForPatterns( recentEpisodes )
460
+
461
+ let semanticResults: EpisodicMemory[] = []
462
+ try {
463
+ semanticResults = await this._episodicConsolidator.semanticQuery(
464
+ queryText,
465
+ {
466
+ limit: this._semanticQueryLimit,
467
+ minSimilarity: this._semanticSimilarityThreshold
468
+ }
469
+ )
470
+ }
471
+ catch( err ){
472
+ // Vector memory not available — fall back to traditional heuristics
473
+ return []
474
+ }
475
+
476
+ if( semanticResults.length < 3 ) return []
477
+
478
+ const beliefs: Belief[] = []
479
+
480
+ // 1. Detect emotional pattern from semantically similar episodes
481
+ const valenceSum = semanticResults.reduce(
482
+ ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0
483
+ )
484
+ const meanValence = valenceSum / semanticResults.length
485
+
486
+ if( Math.abs( meanValence ) > 0.3 ){
487
+ beliefs.push({
488
+ id: `belief-semantic-${( this._idSeq++ ).toString( 36 )}`,
489
+ statement: meanValence > 0
490
+ ? 'Recent meaningful experiences have been emotionally positive'
491
+ : 'Recent meaningful experiences have been emotionally challenging',
492
+ category: 'pattern',
493
+ confidence: Math.min( 0.65, Math.abs( meanValence ) * 0.8 ),
494
+ supportingEpisodes: semanticResults.length,
495
+ lastUpdatedAt: tick,
496
+ tags: [ 'semantic', 'emotional_pattern', meanValence > 0 ? 'positive' : 'negative' ],
497
+ })
498
+ }
499
+
500
+ // 2. Detect thematic pattern from tags across similar episodes
501
+ const tagFrequency = new Map<string, number>()
502
+ for( const ep of semanticResults ){
503
+ for( const tag of ep.tags ){
504
+ tagFrequency.set( tag, ( tagFrequency.get( tag ) ?? 0 ) + 1 )
505
+ }
506
+ }
507
+
508
+ const dominantTag = Array.from( tagFrequency.entries() )
509
+ .sort( ( a, b ) => b[1] - a[1] )
510
+ .find( ( [ , count ] ) => count > semanticResults.length * 0.6 )
511
+
512
+ if( dominantTag ){
513
+ beliefs.push({
514
+ id: `belief-semantic-tag-${( this._idSeq++ ).toString( 36 )}`,
515
+ statement: `A recurring theme in my experiences is: ${dominantTag[0]}`,
516
+ category: 'pattern',
517
+ confidence: Math.min( 0.55, dominantTag[1] / semanticResults.length ),
518
+ supportingEpisodes: dominantTag[1],
519
+ lastUpdatedAt: tick,
520
+ tags: [ 'semantic', 'thematic_pattern', dominantTag[0] ],
521
+ })
522
+ }
523
+
524
+ // 3. Detect sourceType pattern
525
+ const sourceTypes = new Map<string, number>()
526
+ for( const ep of semanticResults ){
527
+ sourceTypes.set( ep.sourceType, ( sourceTypes.get( ep.sourceType ) ?? 0 ) + 1 )
528
+ }
529
+
530
+ const dominantSource = Array.from( sourceTypes.entries() )
531
+ .sort( ( a, b ) => b[1] - a[1] )
532
+ .find( ( [ , count ] ) => count > semanticResults.length * 0.5 )
533
+
534
+ if( dominantSource && dominantSource[0] !== 'unknown' ){
535
+ beliefs.push({
536
+ id: `belief-semantic-source-${( this._idSeq++ ).toString( 36 )}`,
537
+ statement: `My semantically similar experiences tend to come from: ${dominantSource[0]}`,
538
+ category: 'pattern',
539
+ confidence: Math.min( 0.5, dominantSource[1] / semanticResults.length ),
540
+ supportingEpisodes: dominantSource[1],
541
+ lastUpdatedAt: tick,
542
+ tags: [ 'semantic', 'source_pattern', dominantSource[0] ],
543
+ })
544
+ }
545
+
546
+ return beliefs
547
+ }
548
+
549
+ /**
550
+ * Build a semantic query string from recent episodes for pattern detection.
551
+ * Combines content snippets and tags to create a representative query.
552
+ */
553
+ private _buildSemanticQueryForPatterns( episodes: EpisodicMemory[] ): string {
554
+ const parts: string[] = []
555
+
556
+ // Add dominant tags from recent episodes
557
+ const tagFrequency = new Map<string, number>()
558
+ for( const ep of episodes ){
559
+ for( const tag of ep.tags ){
560
+ tagFrequency.set( tag, ( tagFrequency.get( tag ) ?? 0 ) + 1 )
561
+ }
562
+ }
563
+
564
+ const topTags = Array.from( tagFrequency.entries() )
565
+ .sort( ( a, b ) => b[1] - a[1] )
566
+ .slice( 0, 3 )
567
+ .map( ( [ tag ] ) => tag )
568
+
569
+ if( topTags.length > 0 ){
570
+ parts.push( `Themes: ${topTags.join( ', ' )}` )
571
+ }
572
+
573
+ // Add a sample of recent episode content (first 3, truncated)
574
+ const contentSamples = episodes
575
+ .slice( 0, 3 )
576
+ .map( ep => {
577
+ const content = typeof ep.content === 'string'
578
+ ? ep.content
579
+ : JSON.stringify( ep.content )
580
+ return content.slice( 0, 150 )
581
+ })
582
+
583
+ if( contentSamples.length > 0 ){
584
+ parts.push( `Recent experiences: ${contentSamples.join( '; ' )}` )
585
+ }
586
+
587
+ // Add affective context if consistent
588
+ const valenceSum = episodes.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0 )
589
+ const meanValence = valenceSum / episodes.length
590
+ if( Math.abs( meanValence ) > 0.2 ){
591
+ parts.push( `Emotional tone: ${meanValence > 0 ? 'positive' : 'negative'}` )
592
+ }
593
+
594
+ return parts.length > 0 ? parts.join( '. ' ) : 'What patterns emerge from my recent experiences?'
595
+ }
596
+
597
+ // ── Belief management ────────────────────────────────────
598
+
599
+ private static readonly _MAX_HISTORY = 20
600
+
601
+ /** Append a history entry to a belief, dropping the oldest if the buffer is full. */
602
+ private static _recordHistory(
603
+ belief: Belief,
604
+ tick: Tick,
605
+ prevConfidence: number,
606
+ cause: string
607
+ ): void {
608
+ const entry: BeliefHistoryEntry = {
609
+ tick,
610
+ confidence: belief.confidence,
611
+ delta: belief.confidence - prevConfidence,
612
+ cause,
613
+ }
614
+ // Build a fresh array rather than mutating in place: a belief loaded from a
615
+ // PMA (or a frozen snapshot) carries a readonly `history`, and .push() on it
616
+ // throws "Attempted to assign to readonly property".
617
+ const next = belief.history ? [ ...belief.history, entry ] : [ entry ]
618
+ if( next.length > SemanticIntegrator._MAX_HISTORY )
619
+ next.shift()
620
+ belief.history = next
621
+ }
622
+
623
+ private _integrateBelief( newBelief: Belief, cause = 'created' ): Belief {
624
+ const existing = this._beliefs.find( b => this._shouldMerge( b, newBelief ) )
625
+
626
+ if( existing ){
627
+ const prev = existing.confidence
628
+ existing.confidence = ( existing.confidence + newBelief.confidence ) / 2
629
+ existing.supportingEpisodes += newBelief.supportingEpisodes
630
+ existing.lastUpdatedAt = newBelief.lastUpdatedAt
631
+ SemanticIntegrator._recordHistory( existing, newBelief.lastUpdatedAt, prev, 'reinforced' )
632
+ return existing
633
+ }
634
+
635
+ // Seed or preserve history.
636
+ // If the belief arrives with an existing history (e.g. from a PMM load or
637
+ // snapshot restore), preserve it and append the load event so provenance
638
+ // is traceable without losing the full trajectory.
639
+ // For brand-new beliefs (no prior history) seed a single creation entry.
640
+ if( newBelief.history && newBelief.history.length > 0 ){
641
+ SemanticIntegrator._recordHistory( newBelief, newBelief.lastUpdatedAt, newBelief.confidence, cause )
642
+ } else {
643
+ newBelief.history = [{
644
+ tick: newBelief.lastUpdatedAt,
645
+ confidence: newBelief.confidence,
646
+ delta: newBelief.confidence,
647
+ cause,
648
+ }]
649
+ }
650
+ this._beliefs.push( newBelief )
651
+
652
+ if( this._beliefs.length > this._maxBeliefs ){
653
+ this._beliefs = this._beliefs
654
+ .filter( b => b.confidence >= 0.3 )
655
+ .sort( ( a, b ) => b.confidence - a.confidence )
656
+ .slice( 0, this._maxBeliefs )
657
+ }
658
+
659
+ return newBelief
660
+ }
661
+
662
+ /**
663
+ * Determines whether two beliefs are similar enough to merge.
664
+ *
665
+ * Primary gate: same category + at least one shared substantive tag.
666
+ * If tags overlap by ≥ 50%, we trust the category+tag match and skip
667
+ * text comparison — this prevents Jaccard word-overlap from merging
668
+ * semantically unrelated beliefs that share filler words.
669
+ * Weak tag overlap falls back to stop-word-filtered content similarity.
670
+ */
671
+ private _shouldMerge( existing: Belief, incoming: Belief ): boolean {
672
+ if( existing.category !== incoming.category ) return false
673
+
674
+ const sharedTags = existing.tags.filter( t => incoming.tags.includes( t ) )
675
+ if( sharedTags.length === 0 ) return false
676
+
677
+ const tagSimilarity = sharedTags.length / Math.min( existing.tags.length, incoming.tags.length )
678
+ if( tagSimilarity >= 0.5 ) return true
679
+
680
+ // Weak tag overlap only: require text similarity as a secondary check
681
+ return this._contentSimilarity( existing.statement, incoming.statement ) > 0.45
682
+ }
683
+
684
+ /**
685
+ * Jaccard similarity on content words — stop words stripped before comparison
686
+ * so common filler ("I", "my", "the", "have") can't drive false merges.
687
+ */
688
+ private _contentSimilarity( a: string, b: string ): number {
689
+ const tokenise = ( s: string ) =>
690
+ new Set(
691
+ s.toLowerCase()
692
+ .split( /\s+/ )
693
+ .filter( w => w.length > 2 && !_STOP_WORDS.has( w ) )
694
+ )
695
+ const wordsA = tokenise( a )
696
+ const wordsB = tokenise( b )
697
+ if( wordsA.size === 0 || wordsB.size === 0 ) return 0
698
+ const intersection = [ ...wordsA ].filter( w => wordsB.has( w ) ).length
699
+ const union = new Set( [ ...wordsA, ...wordsB ] ).size
700
+ return union > 0 ? intersection / union : 0
701
+ }
702
+
703
+ /**
704
+ * Restore beliefs verbatim from a PMA / snapshot — NOT through the live
705
+ * executive-integration path. A stored belief is a known prior state: its id
706
+ * and final confidence must be preserved exactly. integrateExecutiveBelief()
707
+ * would corrupt a faithful reconstruction two ways: (a) _shouldMerge absorbs
708
+ * semantically-similar stored beliefs into one another, dropping ids and
709
+ * averaging confidence, and (b) it re-caps confidence by evidence count. The
710
+ * live merge/decay dynamics resume once the Will ticks.
711
+ */
712
+ restoreBeliefs( beliefs: Belief[] ): void {
713
+ for( const b of beliefs )
714
+ this._beliefs.push({
715
+ ...b,
716
+ confidence: Math.max( 0, Math.min( 1, b.confidence ) ),
717
+ history: ( b.history && b.history.length > 0 )
718
+ ? [ ...b.history ]
719
+ : [ { tick: b.lastUpdatedAt, confidence: b.confidence, delta: b.confidence, cause: 'restored' } ],
720
+ })
721
+
722
+ if( this._beliefs.length > this._maxBeliefs )
723
+ this._beliefs = this._beliefs
724
+ .filter( b => b.confidence >= 0.3 )
725
+ .sort( ( a, b ) => b.confidence - a.confidence )
726
+ .slice( 0, this._maxBeliefs )
727
+ }
728
+
729
+ getBeliefs(): ReadonlyArray<Belief> {
730
+ return this._beliefs
731
+ }
732
+
733
+ queryBeliefs( filters: {
734
+ category?: string
735
+ tags?: string[]
736
+ minConfidence?: number
737
+ }): Belief[] {
738
+ let results = [ ...this._beliefs ]
739
+
740
+ if( filters.category )
741
+ results = results.filter( b => b.category === filters.category )
742
+ if( filters.tags?.length )
743
+ results = results.filter( b => filters.tags!.some( t => b.tags.includes( t ) ) )
744
+ if( filters.minConfidence !== undefined )
745
+ results = results.filter( b => b.confidence >= filters.minConfidence! )
746
+
747
+ results.sort( ( a, b ) => b.confidence - a.confidence )
748
+ return results
749
+ }
750
+ }