@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,673 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/semantic.engine/clustering.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * SemanticClustering — advanced pattern detection using vector similarity.
7
+ *
8
+ * Extends the SemanticIntegrator's pattern detection with:
9
+ * - Hierarchical clustering of semantically similar episodes
10
+ * - Prototype extraction (centroid of cluster)
11
+ * - Temporal trend detection (beliefs that strengthen/weaken over time)
12
+ * - Anomaly detection (episodes that contradict existing beliefs)
13
+ *
14
+ * Runs as a satellite to SemanticIntegrator, triggered during
15
+ * heuristic pattern detection cycles.
16
+ *
17
+ * Part of Shard 2 (Memory Layer)
18
+ */
19
+
20
+ import { logger } from '#core/logger'
21
+ import type { Tick } from '#core/types'
22
+ import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
23
+ import type { CognitiveBus } from '#cognition/bus'
24
+ import { GenerativeModel } from '#cognition/generative.model'
25
+ import { _STOP_WORDS, Belief } from '#faculties/semantic.engine/types'
26
+
27
+ export interface Cluster {
28
+ id: string
29
+ episodes: EpisodicMemory[]
30
+ centroid: number[] // Vector centroid of the cluster (if vector available)
31
+ prototypeStatement: string // Natural language description of the cluster
32
+ valenceMean: number
33
+ valenceStd: number
34
+ dominantEmotions: Array<[string, number]> // Emotion → average intensity
35
+ dominantTags: Array<[string, number]> // Tag → frequency
36
+ sourceTypes: Array<[string, number]> // SourceType → frequency
37
+ firstSeen: Tick
38
+ lastSeen: Tick
39
+ episodeCount: number
40
+ stabilityScore: number // How consistent the cluster is (0-1)
41
+ }
42
+
43
+ export interface TemporalTrend {
44
+ beliefStatement: string
45
+ direction: 'increasing' | 'decreasing' | 'stable'
46
+ slope: number
47
+ confidence: number
48
+ observations: Array<{ tick: Tick; valence: number; count: number }>
49
+ }
50
+
51
+ export interface SemanticClusteringConfig {
52
+ /** Minimum episodes to form a cluster */
53
+ minClusterSize?: number
54
+ /** Minimum similarity threshold for cluster membership (0-1) */
55
+ clusterSimilarityThreshold?: number
56
+ /** Maximum distance from centroid to be considered in cluster (0-1) */
57
+ maxCentroidDistance?: number
58
+ /** Minimum number of temporal windows for trend detection */
59
+ minTrendWindows?: number
60
+ /** Ticks per temporal window for trend analysis */
61
+ trendWindowTicks?: number
62
+ bus?: CognitiveBus
63
+ }
64
+
65
+ export class SemanticClustering {
66
+ readonly name = 'semantic-clustering'
67
+
68
+ private _minClusterSize: number
69
+ private _clusterSimilarityThreshold: number
70
+ private _maxCentroidDistance: number
71
+ private _minTrendWindows: number
72
+ private _trendWindowTicks: number
73
+
74
+ private _episodicConsolidator: EpisodicConsolidator | null = null
75
+ private _bus: CognitiveBus | null = null
76
+ private _clusters: Cluster[] = []
77
+
78
+ private _idSeq = 0
79
+ private readonly _model = new GenerativeModel()
80
+
81
+ constructor( config: SemanticClusteringConfig = {} ){
82
+ this._bus = config.bus ?? null
83
+ this._minClusterSize = config.minClusterSize ?? 5
84
+ this._clusterSimilarityThreshold = config.clusterSimilarityThreshold ?? 0.65
85
+ this._maxCentroidDistance = config.maxCentroidDistance ?? 0.4
86
+ this._minTrendWindows = config.minTrendWindows ?? 3
87
+ this._trendWindowTicks = config.trendWindowTicks ?? 200
88
+ }
89
+
90
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
91
+ attachConsolidator( consolidator: EpisodicConsolidator ): void { this._episodicConsolidator = consolidator }
92
+
93
+ /**
94
+ * Main entry point: discover clusters from recent episodes.
95
+ * Called by SemanticIntegrator during heuristic pattern detection.
96
+ */
97
+ async discoverClusters( tick: Tick, recentEpisodes: EpisodicMemory[] ): Promise<Belief[]> {
98
+ if( !this._episodicConsolidator || recentEpisodes.length < this._minClusterSize )
99
+ return []
100
+
101
+ const newBeliefs: Belief[] = []
102
+
103
+ // 1. Find clusters using vector similarity
104
+ const clusters = await this._findClusters( recentEpisodes, tick )
105
+
106
+ // 2. For each cluster, generate belief statements
107
+ for( const cluster of clusters ){
108
+ const belief = this._clusterToBelief( cluster, tick )
109
+ if( belief ){
110
+ newBeliefs.push(belief)
111
+ this._clusters.push(cluster)
112
+ }
113
+ }
114
+
115
+ // 3. Detect temporal trends (beliefs that are becoming stronger/weaker)
116
+ const trends = await this._detectTemporalTrends( tick )
117
+ for( const trend of trends )
118
+ if( trend.confidence > 0.7 ){
119
+ const belief = this._trendToBelief( trend, tick )
120
+ belief && newBeliefs.push( belief )
121
+ }
122
+
123
+ // 4. Detect anomalies (episodes that contradict existing beliefs)
124
+ const anomalies = await this._detectAnomalies( recentEpisodes, tick )
125
+ for( const anomaly of anomalies )
126
+ if( anomaly.confidence > 0.6 )
127
+ newBeliefs.push( this._anomalyToBelief( anomaly, tick ) )
128
+
129
+
130
+ if( newBeliefs.length > 0 )
131
+ logger.info(`[semantic-clustering] discovered ${newBeliefs.length} beliefs from ` +
132
+ `${recentEpisodes.length} episodes (${clusters.length} clusters, ` +
133
+ `${trends.length} trends, ${anomalies.length} anomalies)`)
134
+
135
+ return newBeliefs
136
+ }
137
+
138
+ // ── Private clustering methods ───────────────────────────
139
+
140
+ private async _findClusters( episodes: EpisodicMemory[], tick: Tick ): Promise<Cluster[]> {
141
+ if( !this._episodicConsolidator ) return []
142
+
143
+ const
144
+ clusters: Cluster[] = [],
145
+ assigned = new Set<string>()
146
+
147
+ // Try vector-based clustering first
148
+ let useVectorClustering = true
149
+
150
+ // Test if vector memory is available by attempting a query
151
+ try { await this._episodicConsolidator.semanticQuery('test', { limit: 1 }) }
152
+ catch { useVectorClustering = false }
153
+
154
+ if( useVectorClustering ){
155
+ // Use vector similarity for clustering
156
+ for( let i = 0; i < episodes.length && clusters.length < 10; i++ ){
157
+ const seed = episodes[i]
158
+ if( !seed || assigned.has( seed.id ) ) continue
159
+
160
+ // Find similar episodes using vector query
161
+ const similar = await this._episodicConsolidator.semanticQuery(
162
+ this._episodeToQuery( seed ),
163
+ {
164
+ limit: this._minClusterSize * 2,
165
+ minSimilarity: this._clusterSimilarityThreshold
166
+ }
167
+ )
168
+
169
+ // Filter to only recent episodes and not already assigned
170
+ const clusterEpisodes = similar.filter( ep => episodes.includes( ep ) && !assigned.has( ep.id ) )
171
+ if( clusterEpisodes.length >= this._minClusterSize ){
172
+ const cluster = await this._buildCluster( clusterEpisodes, tick )
173
+ clusters.push( cluster )
174
+
175
+ for( const ep of clusterEpisodes )
176
+ assigned.add( ep.id )
177
+ }
178
+ }
179
+ }
180
+
181
+ // Fallback: content-similarity clustering for episodes that don't have vectors
182
+ const unassigned = episodes.filter( ep => !assigned.has( ep.id ) )
183
+ if( unassigned.length >= this._minClusterSize ){
184
+ const fallbackClusters = this._fallbackClustering( unassigned, tick )
185
+ clusters.push( ...fallbackClusters )
186
+ }
187
+
188
+ return clusters
189
+ }
190
+
191
+ private _fallbackClustering( episodes: EpisodicMemory[], tick: Tick ): Cluster[] {
192
+ const
193
+ clusters: Cluster[] = [],
194
+ assigned = new Set<string>()
195
+
196
+ for( let i = 0; i < episodes.length; i++ ){
197
+ const seed = episodes[i]
198
+ if( !seed || assigned.has( seed.id ) ) continue
199
+
200
+ const clusterEpisodes: EpisodicMemory[] = [ seed ]
201
+
202
+ for( let j = i + 1; j < episodes.length; j++ ){
203
+ const other = episodes[j]
204
+ if( !other || assigned.has( other.id ) ) continue
205
+
206
+ const similarity = this._contentSimilarity(
207
+ this._episodeToText( seed ),
208
+ this._episodeToText( other )
209
+ )
210
+
211
+ if( similarity > this._clusterSimilarityThreshold )
212
+ clusterEpisodes.push( other )
213
+ }
214
+
215
+ if( clusterEpisodes.length >= this._minClusterSize ){
216
+ const cluster = this._buildClusterFromContent( clusterEpisodes, tick )
217
+ clusters.push( cluster )
218
+
219
+ for( const ep of clusterEpisodes )
220
+ assigned.add( ep.id )
221
+ }
222
+ }
223
+
224
+ return clusters
225
+ }
226
+
227
+ private async _buildCluster( episodes: EpisodicMemory[], tick: Tick ): Promise<Cluster> {
228
+ const valenceSum = episodes.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0 )
229
+ const valenceMean = valenceSum / episodes.length
230
+ const valenceStd = Math.sqrt( episodes.reduce( ( s, ep ) => {
231
+ const diff = ( ep.affectiveContext?.valence ?? 0 ) - valenceMean
232
+ return s + diff * diff
233
+ }, 0 ) / episodes.length
234
+ )
235
+
236
+ // Aggregate emotions
237
+ const emotionMap = new Map<string, number>()
238
+ for( const ep of episodes )
239
+ for( const [ emotion, intensity ] of Object.entries( ep.emotionalTags ?? {} ) )
240
+ emotionMap.set( emotion, ( emotionMap.get( emotion ) ?? 0 ) + intensity )
241
+
242
+
243
+ const dominantEmotions = Array.from( emotionMap.entries() )
244
+ .map( ([ e, sum ]) => [ e, sum / episodes.length ] as [string, number])
245
+ .sort( ( a, b ) => b[1] - a[1] )
246
+ .slice( 0, 3 )
247
+
248
+ // Aggregate tags
249
+ const tagMap = new Map<string, number>()
250
+ for( const ep of episodes )
251
+ for( const tag of ep.tags )
252
+ tagMap.set( tag, ( tagMap.get( tag ) ?? 0) + 1 )
253
+
254
+ const dominantTags = Array.from( tagMap.entries() )
255
+ .map( ([ t, count ]) => [ t, count / episodes.length ] as [string, number])
256
+ .sort( ( a, b ) => b[1] - a[1] )
257
+ .slice( 0, 5 )
258
+
259
+ // Aggregate source types
260
+ const sourceMap = new Map<string, number>()
261
+ for( const ep of episodes )
262
+ sourceMap.set( ep.sourceType, ( sourceMap.get( ep.sourceType ) ?? 0 ) + 1 )
263
+
264
+ const sourceTypes = Array.from( sourceMap.entries() )
265
+ .map( ([ s, count ]) => [ s, count / episodes.length ] as [string, number])
266
+ .sort( ( a, b ) => b[1] - a[1] )
267
+
268
+ // Find first and last seen timestamps
269
+ const timestamps = episodes.map( ep => ep.timestamp )
270
+ const firstSeen = Math.min( ...timestamps )
271
+ const lastSeen = Math.max( ...timestamps )
272
+
273
+ // Calculate stability (inverse of valence variance)
274
+ const stabilityScore = Math.max( 0, 1 - Math.min( 1, valenceStd / 0.5 ) )
275
+
276
+ // Generate prototype statement
277
+ const prototypeStatement = this._generatePrototypeStatement(
278
+ episodes,
279
+ dominantEmotions,
280
+ dominantTags,
281
+ valenceMean
282
+ )
283
+
284
+ this._idSeq++
285
+ return {
286
+ id: `cluster-${tick}-${this._idSeq}`,
287
+ episodes,
288
+ centroid: [], // Would be populated from vector if available
289
+ prototypeStatement,
290
+ valenceMean,
291
+ valenceStd,
292
+ dominantEmotions,
293
+ dominantTags,
294
+ sourceTypes,
295
+ firstSeen,
296
+ lastSeen,
297
+ episodeCount: episodes.length,
298
+ stabilityScore
299
+ }
300
+ }
301
+
302
+ private _buildClusterFromContent( episodes: EpisodicMemory[], tick: Tick ): Cluster {
303
+ // Same as _buildCluster but without vector centroid
304
+ const valenceSum = episodes.reduce( ( s, ep ) => s + (ep.affectiveContext?.valence ?? 0), 0 )
305
+ const valenceMean = valenceSum / episodes.length
306
+ const valenceStd = Math.sqrt( episodes.reduce( ( s, ep ) => {
307
+ const diff = (ep.affectiveContext?.valence ?? 0) - valenceMean
308
+ return s + diff * diff
309
+ }, 0 ) / episodes.length
310
+ )
311
+
312
+ const emotionMap = new Map<string, number>()
313
+ for( const ep of episodes )
314
+ for( const [ emotion, intensity ] of Object.entries( ep.emotionalTags ?? {}) )
315
+ emotionMap.set( emotion, ( emotionMap.get( emotion ) ?? 0 ) + intensity )
316
+
317
+ const dominantEmotions = Array.from( emotionMap.entries() )
318
+ .map( ([ e, sum] ) => [ e, sum / episodes.length ] as [string, number])
319
+ .sort( ( a, b ) => b[1] - a[1] )
320
+ .slice( 0, 3 )
321
+
322
+ const tagMap = new Map<string, number>()
323
+ for( const ep of episodes )
324
+ for( const tag of ep.tags )
325
+ tagMap.set( tag, (tagMap.get( tag ) ?? 0) + 1 )
326
+
327
+ const dominantTags = Array.from( tagMap.entries() )
328
+ .map( ([ t, count ]) => [ t, count / episodes.length ] as [string, number])
329
+ .sort( ( a, b ) => b[1] - a[1] )
330
+ .slice( 0, 5 )
331
+
332
+ const sourceMap = new Map<string, number>()
333
+ for( const ep of episodes )
334
+ sourceMap.set( ep.sourceType, (sourceMap.get( ep.sourceType ) ?? 0) + 1 )
335
+
336
+ const sourceTypes = Array.from( sourceMap.entries() )
337
+ .map( ([ s, count ]) => [ s, count / episodes.length ] as [string, number])
338
+ .sort( ( a, b ) => b[1] - a[1] )
339
+
340
+ const timestamps = episodes.map( ep => ep.timestamp )
341
+ const firstSeen = Math.min( ...timestamps )
342
+ const lastSeen = Math.max( ...timestamps )
343
+ const stabilityScore = Math.max( 0, 1 - Math.min( 1, valenceStd / 0.5 ) )
344
+
345
+ const prototypeStatement = this._generatePrototypeStatement(
346
+ episodes,
347
+ dominantEmotions,
348
+ dominantTags,
349
+ valenceMean
350
+ )
351
+
352
+ this._idSeq++
353
+ return {
354
+ id: `cluster-fallback-${tick}-${this._idSeq}`,
355
+ episodes,
356
+ centroid: [],
357
+ prototypeStatement,
358
+ valenceMean,
359
+ valenceStd,
360
+ dominantEmotions,
361
+ dominantTags,
362
+ sourceTypes,
363
+ firstSeen,
364
+ lastSeen,
365
+ episodeCount: episodes.length,
366
+ stabilityScore
367
+ }
368
+ }
369
+
370
+ // ── Belief generation ────────────────────────────────────
371
+
372
+ private _clusterToBelief( cluster: Cluster, tick: Tick ): Belief | null {
373
+ // Only form beliefs from stable clusters with meaningful patterns
374
+ if( cluster.stabilityScore < 0.4 ) return null
375
+ if( cluster.episodeCount < this._minClusterSize ) return null
376
+
377
+ let statement = cluster.prototypeStatement
378
+ let confidence = Math.min( 0.7, 0.3 + cluster.stabilityScore * 0.4 )
379
+ let category: Belief['category'] = 'pattern'
380
+ let tags = [ ...cluster.dominantTags.map( ([ t ]) => t ), 'semantic_cluster' ]
381
+
382
+ // Add emotional qualifier if valence is extreme
383
+ if( cluster.valenceMean > 0.4 ){
384
+ statement = `Positive experiences often center around: ${statement}`
385
+ tags.push('positive_pattern')
386
+
387
+ confidence += 0.05
388
+ }
389
+ else if( cluster.valenceMean < -0.3 ){
390
+ statement = `Challenging experiences often involve: ${statement}`
391
+ tags.push('negative_pattern')
392
+
393
+ confidence += 0.05
394
+ }
395
+
396
+ // Categorize based on source
397
+ if( cluster.sourceTypes.some( ([ s ]) => s === 'social' || s === 'conversation') ){
398
+ category = 'social_belief'
399
+ tags.push('social')
400
+ }
401
+ else if( cluster.sourceTypes.some( ([ s ]) => s === 'interoception') ){
402
+ category = 'self_belief'
403
+ tags.push('self')
404
+ }
405
+
406
+ confidence = Math.min( 0.75, confidence )
407
+
408
+ this._idSeq++
409
+ return {
410
+ id: `belief-cluster-${tick}-${this._idSeq}`,
411
+ statement,
412
+ category,
413
+ confidence,
414
+ supportingEpisodes: cluster.episodeCount,
415
+ lastUpdatedAt: tick,
416
+ tags: tags.slice( 0, 8 )
417
+ }
418
+ }
419
+
420
+ private _generatePrototypeStatement(
421
+ episodes: EpisodicMemory[],
422
+ dominantEmotions: Array<[string, number]>,
423
+ dominantTags: Array<[string, number]>,
424
+ valenceMean: number
425
+ ): string {
426
+ // Extract common themes from episode content
427
+ const contentWords = new Map<string, number>()
428
+
429
+ for( const ep of episodes.slice( 0, 20 ) ){
430
+ const text = this._episodeToText( ep )
431
+ const words = text.toLowerCase()
432
+ .split(/\s+/)
433
+ .filter( w => w.length > 3 && !_STOP_WORDS.has( w ) )
434
+
435
+ for( const word of words.slice( 0, 10 ) )
436
+ contentWords.set( word, ( contentWords.get( word ) ?? 0 ) + 1 )
437
+ }
438
+
439
+ const topWords = Array.from( contentWords.entries() )
440
+ .sort( ( a, b ) => b[1] - a[1] )
441
+ .slice( 0, 4 )
442
+ .map( ([ w ]) => w)
443
+
444
+ if( dominantTags.length > 0 && dominantTags[0] && dominantTags[0][1] > 0.5 )
445
+ return `Themes related to "${dominantTags[0][0]}" appear repeatedly`
446
+
447
+ if( topWords.length >= 2 )
448
+ return `Patterns involving ${topWords.slice(0, 3).join(', ')} emerge in my experiences`
449
+
450
+ if( dominantEmotions.length > 0 && dominantEmotions[0] && dominantEmotions[0][1] > 0.4 ){
451
+ const emotion = dominantEmotions[0][0]
452
+ return `I frequently experience ${emotion} in similar situations`
453
+ }
454
+
455
+ return `A recurring pattern has emerged in my recent experiences`
456
+ }
457
+
458
+ // ── Temporal trend detection ─────────────────────────────
459
+
460
+ private async _detectTemporalTrends( tick: Tick ): Promise<TemporalTrend[]> {
461
+ if( !this._episodicConsolidator ) return []
462
+
463
+ const trends: TemporalTrend[] = []
464
+ const windowSize = this._trendWindowTicks
465
+ const startTick = tick - windowSize * this._minTrendWindows
466
+
467
+ // Group episodes by time window
468
+ const windows: Map<number, EpisodicMemory[]> = new Map()
469
+ const allEpisodes = this._episodicConsolidator.getAllEpisodes()
470
+
471
+ for( const ep of allEpisodes ){
472
+ if( ep.timestamp < startTick ) continue
473
+ const windowIndex = Math.floor( ep.timestamp / windowSize )
474
+ const window = windows.get( windowIndex ) ?? []
475
+
476
+ window.push( ep )
477
+ windows.set( windowIndex, window )
478
+ }
479
+
480
+ const sortedWindows = Array.from( windows.keys() ).sort( ( a, b ) => a - b )
481
+ if( sortedWindows.length < this._minTrendWindows ) return []
482
+
483
+ // For each source type and emotion, compute trend
484
+ const sourceTypes = [ 'percept', 'social', 'goal', 'thought', 'interoception' ]
485
+ for( const sourceType of sourceTypes ){
486
+ const observations: Array<{ tick: number; valence: number; count: number }> = []
487
+
488
+ for( const winIdx of sortedWindows ){
489
+ const episodes = windows.get( winIdx ) ?? []
490
+
491
+ const filtered = episodes.filter( ep => ep.sourceType === sourceType )
492
+ if( filtered.length === 0 ) continue
493
+
494
+ const valenceSum = filtered.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0)
495
+ observations.push({
496
+ tick: winIdx * windowSize,
497
+ valence: valenceSum / filtered.length,
498
+ count: filtered.length,
499
+ })
500
+ }
501
+
502
+ if( observations.length >= this._minTrendWindows ){
503
+ const trend = this._computeLinearTrend( observations )
504
+ if( Math.abs( trend.slope ) > 0.02 && trend.confidence > 0.6 )
505
+ trends.push({
506
+ beliefStatement: `My experiences with ${sourceType} are becoming ${trend.direction === 'increasing' ? 'more positive' : 'more negative'}`,
507
+ direction: trend.direction,
508
+ slope: trend.slope,
509
+ confidence: trend.confidence,
510
+ observations
511
+ })
512
+ }
513
+ }
514
+
515
+ return trends
516
+ }
517
+
518
+ private _computeLinearTrend( observations: Array<{ tick: number; valence: number; count: number }> ): {
519
+ slope: number
520
+ direction: 'increasing' | 'decreasing' | 'stable'
521
+ confidence: number
522
+ }{
523
+ const n = observations.length
524
+ if( n < 2 )
525
+ return { slope: 0, direction: 'stable', confidence: 0 }
526
+
527
+ // Simple linear regression
528
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0
529
+ for( let i = 0; i < n; i++ ){
530
+ const x = i // Use index as x for normalized time
531
+ const y = observations[i]!.valence
532
+
533
+ sumX += x
534
+ sumY += y
535
+ sumXY += x * y
536
+ sumX2 += x * x
537
+ }
538
+
539
+ const denominator = n * sumX2 - sumX * sumX
540
+ if( denominator === 0 )
541
+ return { slope: 0, direction: 'stable', confidence: 0 }
542
+
543
+ const slope = ( n * sumXY - sumX * sumY ) / denominator
544
+
545
+ // Calculate R-squared for confidence
546
+ const yMean = sumY / n
547
+ let ssTot = 0, ssRes = 0
548
+ for( let i = 0; i < n; i++ ){
549
+ const x = i
550
+ const y = observations[i]!.valence
551
+ const yPred = yMean + slope * ( x - ( sumX / n ) )
552
+
553
+ ssTot += (y - yMean) ** 2
554
+ ssRes += (y - yPred) ** 2
555
+ }
556
+
557
+ const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0
558
+ const direction: 'increasing' | 'decreasing' | 'stable' = slope > 0.02 ? 'increasing' : slope < -0.02 ? 'decreasing' : 'stable'
559
+
560
+ return {
561
+ slope,
562
+ direction,
563
+ confidence: Math.min( 0.8, 0.3 + rSquared * 0.5 ),
564
+ }
565
+ }
566
+
567
+ private _trendToBelief( trend: TemporalTrend, tick: Tick ): Belief | null {
568
+ if( trend.confidence < 0.6) return null
569
+
570
+ this._idSeq++
571
+ return {
572
+ id: `belief-trend-${tick}-${this._idSeq}`,
573
+ statement: trend.beliefStatement,
574
+ category: 'pattern',
575
+ confidence: trend.confidence,
576
+ supportingEpisodes: trend.observations.reduce( ( s, o ) => s + o.count, 0 ),
577
+ lastUpdatedAt: tick,
578
+ tags: [ 'temporal_trend', trend.direction, 'semantic_cluster' ],
579
+ }
580
+ }
581
+
582
+ // ── Anomaly detection ────────────────────────────────────
583
+
584
+ private async _detectAnomalies(
585
+ episodes: EpisodicMemory[],
586
+ tick: Tick
587
+ ): Promise<Array<{ statement: string; confidence: number; episode: EpisodicMemory }>> {
588
+ const anomalies: Array<{ statement: string; confidence: number; episode: EpisodicMemory }> = []
589
+
590
+ // For each existing cluster, check if new episodes contradict it
591
+ for( const cluster of this._clusters ){
592
+ for( const episode of episodes ){
593
+ const similarity = this._contentSimilarity( cluster.prototypeStatement, this._episodeToText( episode ) )
594
+
595
+ // If episode is semantically similar but emotionally opposite, it's an anomaly
596
+ if( similarity > 0.4 ){
597
+ const valenceDiff = Math.abs( episode.affectiveContext?.valence ?? 0 - cluster.valenceMean )
598
+ if( valenceDiff > 0.6 )
599
+ anomalies.push({
600
+ statement: `Not all experiences about ${cluster.prototypeStatement.toLowerCase()} follow the usual pattern`,
601
+ confidence: Math.min( 0.65, 0.4 + valenceDiff * 0.3 ),
602
+ episode,
603
+ })
604
+ }
605
+ }
606
+ }
607
+
608
+ return anomalies.slice( 0, 3 )
609
+ }
610
+
611
+ private _anomalyToBelief(
612
+ anomaly: { statement: string; confidence: number; episode: EpisodicMemory },
613
+ tick: Tick
614
+ ): Belief {
615
+ this._idSeq++
616
+
617
+ return {
618
+ id: `belief-anomaly-${tick}-${this._idSeq}`,
619
+ statement: anomaly.statement,
620
+ category: 'pattern',
621
+ confidence: anomaly.confidence,
622
+ supportingEpisodes: 1,
623
+ lastUpdatedAt: tick,
624
+ tags: ['anomaly', 'exception', 'semantic_cluster']
625
+ }
626
+ }
627
+
628
+ // ── Helpers ──────────────────────────────────────────────
629
+
630
+ private _episodeToText( episode: EpisodicMemory ): string {
631
+ if( typeof episode.content === 'string')
632
+ return episode.content
633
+
634
+ if( typeof episode.content === 'object' && episode.content !== null ){
635
+ const content = episode.content as Record<string, unknown>
636
+ if( typeof content['summary'] === 'string') return content['summary']
637
+ if( typeof content['description'] === 'string') return content['description']
638
+ if( typeof content['userMessage'] === 'string') return content['userMessage']
639
+
640
+ return JSON.stringify( episode.content )
641
+ }
642
+
643
+ return String( episode.content )
644
+ }
645
+
646
+ private _episodeToQuery( episode: EpisodicMemory ): string {
647
+ // Build a rich query string that captures emotional and thematic content
648
+ const text = this._episodeToText( episode )
649
+ const valence = episode.affectiveContext?.valence ?? 0
650
+ const emotionHint = valence > 0.3 ? 'positive' : valence < -0.3 ? 'negative' : 'neutral'
651
+
652
+ return `${text} ${emotionHint} ${episode.tags.slice( 0, 3 ).join(' ')}`
653
+ }
654
+
655
+ private _contentSimilarity( a: string, b: string ): number {
656
+ const tokenize = ( s: string ) => new Set( s.toLowerCase().split(/\s+/).filter( w => w.length > 2 && !_STOP_WORDS.has( w ) ) )
657
+
658
+ const wordsA = tokenize(a)
659
+ const wordsB = tokenize(b)
660
+
661
+ if( wordsA.size === 0 || wordsB.size === 0 ) return 0
662
+
663
+ const intersection = [ ...wordsA].filter( w => wordsB.has( w ) ).length
664
+ const union = new Set([ ...wordsA, ...wordsB ]).size
665
+
666
+ return union > 0 ? intersection / union : 0
667
+ }
668
+
669
+ // ── Public API ───────────────────────────────────────────
670
+
671
+ getClusters(): ReadonlyArray<Cluster> { return this._clusters }
672
+ clearClusters(): void { this._clusters = [] }
673
+ }
@@ -0,0 +1,6 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/semantic.engine/index.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ export { SemanticIntegrator } from '#faculties/semantic.engine/integrator'
6
+ export type { SemanticIntegratorConfig, BeliefHistoryEntry, Belief } from '#faculties/semantic.engine/types'