@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,459 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/memory/vector.index.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Vector index interface — allows swapping different implementations.
7
+ *
8
+ * Implementations:
9
+ * - HNSWIndex (in-memory, deterministic)
10
+ * - QdrantClient (cloud)
11
+ * - PgVectorClient (Postgres)
12
+ * - PineconeClient (cloud)
13
+ */
14
+
15
+ import type { VectorRecord, VectorQueryResult } from '#memory/vector.types'
16
+ import type { SeededPRNG } from '#core/types'
17
+ import { createPRNG } from '#core/utils'
18
+
19
+ /** Fixed fallback seed so an unwired index is still deterministic per insertion order. */
20
+ const DEFAULT_HNSW_SEED = 42
21
+
22
+ export interface VectorIndex {
23
+ /** Insert a vector into the index */
24
+ insert( record: VectorRecord ): Promise<void>
25
+
26
+ /** Search for k nearest neighbors. Similarity-only — see VectorQueryFilter. */
27
+ search(
28
+ vector: number[],
29
+ k: number,
30
+ filter?: { minSimilarity?: number }
31
+ ): Promise<VectorQueryResult[]>
32
+
33
+ /** Delete a vector from the index */
34
+ delete( id: string ): Promise<boolean>
35
+
36
+ /** Get current size of index */
37
+ readonly size: number
38
+
39
+ /** Iterate all indexed ids in insertion order — lets callers rebuild an
40
+ * external id-set (e.g. the adapter's dedup/eviction set) after a load. */
41
+ keys?(): IterableIterator<string>
42
+
43
+ /** Serialize index to bytes for persistence (optional) */
44
+ serialize?(): Uint8Array
45
+
46
+ /** Deserialize index from bytes (optional) */
47
+ deserialize?( bytes: Uint8Array ): Promise<void>
48
+
49
+ /** Clear all entries */
50
+ clear(): Promise<void>
51
+ }
52
+
53
+ export interface HNSWIndexConfig {
54
+ dimensions?: number
55
+ similarityMetric?: 'cosine' | 'euclidean' | 'dot'
56
+ hnswM?: number
57
+ hnswEfConstruction?: number
58
+ /** Base-layer search beam width. Must be ≫ k for good recall; the effective
59
+ * beam is max(k, hnswEfSearch). Default 64. */
60
+ hnswEfSearch?: number
61
+ /** Seed for the level-assignment PRNG. Required for deterministic replay. */
62
+ seed?: number
63
+ }
64
+
65
+ /**
66
+ * HNSW-based vector index for semantic similarity search.
67
+ *
68
+ * In-memory index with optional persistence via StorageAdapter.
69
+ * Designed to be rebuilt from _store on snapshot restore.
70
+ * Deterministic for given insertion order (important for replay).
71
+ */
72
+ export class HNSWIndex implements VectorIndex {
73
+ private _nodes: Map<string, HNSWNode> = new Map()
74
+ private _entryPoint: string | null = null
75
+ private _maxLevel: number = 0
76
+ private _config: HNSWIndexConfig
77
+ private _seed: number
78
+ private _prng: SeededPRNG
79
+
80
+ constructor( config: HNSWIndexConfig = {} ){
81
+ this._config = {
82
+ dimensions: config.dimensions ?? 1536,
83
+ similarityMetric: config.similarityMetric ?? 'cosine',
84
+ hnswM: config.hnswM ?? 16,
85
+ hnswEfConstruction: config.hnswEfConstruction ?? 200,
86
+ hnswEfSearch: config.hnswEfSearch ?? 64
87
+ }
88
+ this._seed = config.seed ?? DEFAULT_HNSW_SEED
89
+ this._prng = createPRNG( this._seed )
90
+ }
91
+
92
+ get size(): number {
93
+ return this._nodes.size
94
+ }
95
+
96
+ /** Insertion-ordered ids (Map preserves insertion order; deserialize repopulates
97
+ * _nodes in the original order), so callers get oldest-first iteration. */
98
+ keys(): IterableIterator<string> {
99
+ return this._nodes.keys()
100
+ }
101
+
102
+ async insert( record: VectorRecord ): Promise<void> {
103
+ if( this._nodes.has( record.id ) )
104
+ await this.delete( record.id )
105
+
106
+ const level = this._randomLevel()
107
+ const node: HNSWNode = {
108
+ id: record.id,
109
+ vector: record.vector,
110
+ level,
111
+ connections: new Map()
112
+ }
113
+
114
+ for( let l = 0; l <= level; l++ )
115
+ node.connections.set( l, new Set() )
116
+
117
+ // Register the node up front so neighbour-list pruning below can resolve
118
+ // `record.id` to its node while wiring bidirectional links. Safe: no
119
+ // level-l edge points at the new node until *after* this insert's own
120
+ // _searchLayer call for level l, so it can never appear in its own
121
+ // candidate sets (which would change the deterministic graph).
122
+ this._nodes.set( record.id, node )
123
+
124
+ if( this._entryPoint === null ){
125
+ this._entryPoint = record.id
126
+ this._maxLevel = level
127
+
128
+ return
129
+ }
130
+
131
+ const entryNode = this._nodes.get( this._entryPoint )!
132
+ let currNode = entryNode
133
+
134
+ for( let l = this._maxLevel; l > level; l-- ){
135
+ const candidates = this._searchLayer( currNode, record.vector, l, 1 )
136
+ currNode = candidates[0] ?? currNode
137
+ }
138
+
139
+ for( let l = Math.min( level, this._maxLevel ); l >= 0; l-- ){
140
+ const candidates = this._searchLayer( currNode, record.vector, l, this._config.hnswEfConstruction! )
141
+ const neighbors = this._selectNeighbors( record.vector, candidates, this._config.hnswM! )
142
+
143
+ // Canonical HNSW degree caps: M for upper layers, M*2 at the base layer.
144
+ const maxConn = l === 0 ? this._config.hnswM! * 2 : this._config.hnswM!
145
+
146
+ for( const neighbor of neighbors ){
147
+ if( neighbor.id === record.id ) continue // never self-link
148
+
149
+ node.connections.get( l )!.add( neighbor.id )
150
+
151
+ const nset = neighbor.connections.get( l )!
152
+ nset.add( record.id )
153
+ // Shrink the touched neighbour back to its cap. Without this, popular
154
+ // nodes accumulate back-links without bound → memory bloat and an
155
+ // ever-slower _searchLayer (FN6). Re-selection is deterministic.
156
+ if( nset.size > maxConn )
157
+ this._shrinkConnections( neighbor, l, maxConn )
158
+ }
159
+
160
+ currNode = candidates[0] ?? currNode
161
+ }
162
+
163
+ if( level > this._maxLevel ){
164
+ this._maxLevel = level
165
+ this._entryPoint = record.id
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Re-select a node's `level` connections down to `cap`, keeping the closest
171
+ * by distance. Canonical HNSW neighbour pruning: bounds node degree so the
172
+ * graph stays sparse and search stays fast. Asymmetric drops (the removed
173
+ * peer may still link back) are expected and self-correct when that peer is
174
+ * itself pruned — matching reference implementations.
175
+ */
176
+ private _shrinkConnections( node: HNSWNode, level: number, cap: number ): void {
177
+ const set = node.connections.get( level )
178
+ if( !set || set.size <= cap ) return
179
+
180
+ const connected: HNSWNode[] = []
181
+ for( const id of set ){
182
+ const n = this._nodes.get( id )
183
+ if( n ) connected.push( n )
184
+ }
185
+
186
+ const kept = this._selectNeighbors( node.vector, connected, cap )
187
+ node.connections.set( level, new Set( kept.map( n => n.id ) ) )
188
+ }
189
+
190
+ async search(
191
+ vector: number[],
192
+ k: number,
193
+ filter?: { minSimilarity?: number }
194
+ ): Promise<VectorQueryResult[]> {
195
+ if( this._entryPoint === null || this._nodes.size === 0 ) return []
196
+
197
+ const entryNode = this._nodes.get( this._entryPoint )!
198
+ let currNode = entryNode
199
+
200
+ for( let l = this._maxLevel; l > 0; l-- ){
201
+ const candidates = this._searchLayer( currNode, vector, l, 1 )
202
+ currNode = candidates[0] ?? currNode
203
+ }
204
+
205
+ // Explore a base-layer beam much wider than k (canonical HNSW efSearch ≫ k),
206
+ // then keep the top-k. With ef==k the greedy walk under-explored and capped
207
+ // recall; widening it surfaces true nearest neighbours that a k-width beam
208
+ // skipped. _searchLayer returns nearest-first, so slicing keeps the best k.
209
+ const ef = Math.max( k, this._config.hnswEfSearch ?? 64 )
210
+ const candidates = this._searchLayer( currNode, vector, 0, ef )
211
+ const minSimilarity = filter?.minSimilarity ?? 0.35
212
+
213
+ const results: VectorQueryResult[] = []
214
+ for( const node of candidates ){
215
+ const similarity = this._computeSimilarity( vector, node.vector )
216
+ if( similarity >= minSimilarity )
217
+ results.push({ episodeId: node.id, similarity })
218
+ }
219
+
220
+ return results.slice( 0, k )
221
+ }
222
+
223
+ async delete( id: string ): Promise<boolean> {
224
+ const node = this._nodes.get( id )
225
+ if( !node ) return false
226
+
227
+ for( const [ level, neighbors ] of node.connections )
228
+ for( const neighborId of neighbors ){
229
+ const neighbor = this._nodes.get( neighborId )
230
+ if( neighbor )
231
+ neighbor.connections.get( level )?.delete( id )
232
+ }
233
+
234
+ this._nodes.delete( id )
235
+
236
+ // Reselect the entry point when we removed it. The old code grabbed an
237
+ // arbitrary `keys().next()` node — often a level-0 node — while _maxLevel
238
+ // stayed high, so the top-down descent began at a node with no high-level
239
+ // connections and returned only itself (compounding FN4). Promote the
240
+ // highest-level remaining node and resync _maxLevel to it. Deterministic:
241
+ // Map iteration is insertion order, ties resolve to the first inserted.
242
+ if( this._entryPoint === id ){
243
+ this._entryPoint = null
244
+ this._maxLevel = 0
245
+ for( const n of this._nodes.values() )
246
+ if( this._entryPoint === null || n.level > this._maxLevel ){
247
+ this._entryPoint = n.id
248
+ this._maxLevel = n.level
249
+ }
250
+ }
251
+
252
+ return true
253
+ }
254
+
255
+ async clear(): Promise<void> {
256
+ this._nodes.clear()
257
+ this._entryPoint = null
258
+ this._maxLevel = 0
259
+ // Reset the PRNG so a rebuild from the same insertion order (e.g.
260
+ // rebuildFromStore on snapshot restore) reproduces the identical graph.
261
+ this._prng = createPRNG( this._seed )
262
+ }
263
+
264
+ serialize(): Uint8Array {
265
+ const data = {
266
+ config: this._config,
267
+ seed: this._seed,
268
+ // Persist the live PRNG state so inserts after a load continue the
269
+ // same deterministic sequence rather than restarting from the seed.
270
+ // Mask to 32 bits: the Mulberry32 accumulator grows unbounded but only
271
+ // its low 32 bits affect output, and createPRNG re-applies `>>> 0` on
272
+ // load — storing the masked value keeps serialize/restore round-trips
273
+ // byte-stable.
274
+ prngState: this._prng.state >>> 0,
275
+ entryPoint: this._entryPoint,
276
+ maxLevel: this._maxLevel,
277
+ nodes: Array.from( this._nodes.entries() ).map( ( [ id, node ] ) => ( {
278
+ id,
279
+ vector: node.vector,
280
+ level: node.level,
281
+ connections: Array.from( node.connections.entries() ).map( ( [ level, neighbors ] ) => ( {
282
+ level,
283
+ neighbors: Array.from( neighbors )
284
+ } ) )
285
+ } ) )
286
+ }
287
+
288
+ const encoder = new TextEncoder()
289
+ return encoder.encode( JSON.stringify( data ) )
290
+ }
291
+
292
+ async deserialize( bytes: Uint8Array ): Promise<void> {
293
+ const decoder = new TextDecoder()
294
+ const data = JSON.parse( decoder.decode( bytes ) ) as {
295
+ config: HNSWIndexConfig
296
+ seed?: number
297
+ prngState?: number
298
+ entryPoint: string | null
299
+ maxLevel: number
300
+ nodes: Array<{
301
+ id: string
302
+ vector: number[]
303
+ level: number
304
+ connections: Array<{ level: number; neighbors: string[] }>
305
+ }>
306
+ }
307
+
308
+ this._config = data.config
309
+ this._seed = data.seed ?? this._seed
310
+ // createPRNG sets its internal state directly from the argument, so
311
+ // re-seeding with the persisted state resumes the exact sequence.
312
+ this._prng = createPRNG( data.prngState ?? this._seed )
313
+ this._entryPoint = data.entryPoint
314
+ this._maxLevel = data.maxLevel
315
+ this._nodes.clear()
316
+
317
+ for( const nodeData of data.nodes ){
318
+ const connections = new Map<number, Set<string>>()
319
+ for( const conn of nodeData.connections )
320
+ connections.set( conn.level, new Set( conn.neighbors ) )
321
+
322
+ this._nodes.set( nodeData.id, {
323
+ id: nodeData.id,
324
+ vector: nodeData.vector,
325
+ level: nodeData.level,
326
+ connections
327
+ } )
328
+ }
329
+ }
330
+
331
+ // ── Private methods ───────────────────────────────────────
332
+
333
+ private _randomLevel(): number {
334
+ let level = 0
335
+ while( this._prng.next() < 1 / this._config.hnswM! && level < 16 )
336
+ level++
337
+
338
+ return level
339
+ }
340
+
341
+ private _searchLayer(
342
+ entry: HNSWNode,
343
+ query: number[],
344
+ level: number,
345
+ ef: number
346
+ ): HNSWNode[] {
347
+ const visited = new Set<string>()
348
+ const candidates: Array<{ node: HNSWNode; dist: number }> = []
349
+ const results: Array<{ node: HNSWNode; dist: number }> = []
350
+
351
+ const entryDist = this._distance( query, entry.vector )
352
+ candidates.push( { node: entry, dist: entryDist } )
353
+ results.push( { node: entry, dist: entryDist } )
354
+ visited.add( entry.id )
355
+
356
+ while( candidates.length > 0 ){
357
+ candidates.sort( ( a, b ) => a.dist - b.dist )
358
+ const best = candidates[0]!
359
+
360
+ if( results.length >= ef && best.dist > results[results.length - 1]!.dist )
361
+ break
362
+
363
+ candidates.shift()
364
+
365
+ // Expand the node we just popped, not the fixed entry node. Using
366
+ // `entry` here collapsed the search into a single-hop scan of the
367
+ // entry's immediate neighbourhood and never walked the graph, badly
368
+ // degrading recall. Traverse `best`'s connections so the greedy walk
369
+ // actually hops across the layer.
370
+ const neighbors = best.node.connections.get( level ) ?? new Set()
371
+ for( const neighborId of neighbors ){
372
+ if( visited.has( neighborId ) ) continue
373
+
374
+ const neighbor = this._nodes.get( neighborId )
375
+ if( !neighbor ) continue
376
+
377
+ visited.add( neighborId )
378
+ const dist = this._distance( query, neighbor.vector )
379
+
380
+ const candidate = { node: neighbor, dist }
381
+
382
+ if( results.length < ef || dist < results[results.length - 1]!.dist ){
383
+ candidates.push( candidate )
384
+ results.push( candidate )
385
+ results.sort( ( a, b ) => a.dist - b.dist )
386
+
387
+ if( results.length > ef ) results.pop()
388
+ }
389
+ }
390
+ }
391
+
392
+ return results.map( r => r.node )
393
+ }
394
+
395
+ private _selectNeighbors( query: number[], candidates: HNSWNode[], m: number ): HNSWNode[] {
396
+ const withDist = candidates.map( node => ( {
397
+ node,
398
+ dist: this._distance( query, node.vector )
399
+ } ) )
400
+
401
+ withDist.sort( ( a, b ) => a.dist - b.dist )
402
+
403
+ return withDist.slice( 0, m ).map( r => r.node )
404
+ }
405
+
406
+ private _distance( a: number[], b: number[] ): number {
407
+ switch( this._config.similarityMetric ){
408
+ case 'cosine': return 1 - this._cosineSimilarity( a, b )
409
+ case 'euclidean': return this._euclideanDistance( a, b )
410
+ case 'dot': return -this._dotProduct( a, b )
411
+ default: return 1 - this._cosineSimilarity( a, b )
412
+ }
413
+ }
414
+
415
+ private _computeSimilarity( a: number[], b: number[] ): number {
416
+ switch( this._config.similarityMetric ){
417
+ case 'cosine': return this._cosineSimilarity( a, b )
418
+ case 'euclidean': return 1 / ( 1 + this._euclideanDistance( a, b ) )
419
+ case 'dot': return this._dotProduct( a, b )
420
+ default: return this._cosineSimilarity( a, b )
421
+ }
422
+ }
423
+
424
+ private _cosineSimilarity( a: number[], b: number[] ): number {
425
+ let dot = 0, magA = 0, magB = 0
426
+ for( let i = 0; i < a.length; i++ ){
427
+ dot += a[i]! * b[i]!
428
+ magA += a[i]! * a[i]!
429
+ magB += b[i]! * b[i]!
430
+ }
431
+
432
+ return dot / ( Math.sqrt( magA ) * Math.sqrt( magB ) + 1e-8 )
433
+ }
434
+
435
+ private _euclideanDistance( a: number[], b: number[] ): number {
436
+ let sum = 0
437
+ for( let i = 0; i < a.length; i++ ){
438
+ const diff = a[i]! - b[i]!
439
+ sum += diff * diff
440
+ }
441
+
442
+ return Math.sqrt( sum )
443
+ }
444
+
445
+ private _dotProduct( a: number[], b: number[] ): number {
446
+ let sum = 0
447
+ for( let i = 0; i < a.length; i++ )
448
+ sum += a[i]! * b[i]!
449
+
450
+ return sum
451
+ }
452
+ }
453
+
454
+ interface HNSWNode {
455
+ id: string
456
+ vector: number[]
457
+ level: number
458
+ connections: Map<number, Set<string>>
459
+ }
@@ -0,0 +1,65 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/memory/vector.types.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Vector memory types for semantic episodic retrieval.
7
+ *
8
+ * Provides similarity search over consolidated episodic memories
9
+ * without replacing the existing _store array or StateManager.
10
+ * Acts as a read-optimized secondary index.
11
+ */
12
+
13
+ import type { Tick } from '#core/types'
14
+
15
+ export interface VectorMemoryConfig {
16
+ /** Dimension of embedding vectors (default 1536 for OpenAI text-embedding-3-small) */
17
+ dimensions?: number
18
+ /** Similarity metric: 'cosine', 'euclidean', or 'dot' */
19
+ similarityMetric?: 'cosine' | 'euclidean' | 'dot'
20
+ /** Maximum number of episodes to index (older entries evicted) */
21
+ maxIndexedEpisodes?: number
22
+ /** Minimum similarity threshold for query results (0-1). Default 0.35, tuned
23
+ * for real sentence embeddings (text-embedding-3-small); raise for higher
24
+ * precision. */
25
+ minSimilarity?: number
26
+ /** Seed for the index's level-assignment PRNG — required for deterministic replay */
27
+ seed?: number
28
+ }
29
+
30
+ export interface VectorRecord {
31
+ id: string // matches EpisodicMemory.id
32
+ vector: number[]
33
+ embeddingModel: string // e.g., 'text-embedding-3-small'
34
+ createdAt: number
35
+ metadata: {
36
+ tick: Tick
37
+ sourceType: string
38
+ /** Encode-time affective valence (-1..1). Stamped here so index backends that
39
+ * CAN filter/rank on metadata (pgvector, Qdrant) may do affective filtering
40
+ * server-side. The HNSW path is similarity-only, so its mood-congruent recall
41
+ * (EpisodicConsolidator.semanticQuery affectiveBias) re-ranks on the
42
+ * authoritative resolved-episode valence instead. */
43
+ emotionalValence: number
44
+ tags: string[]
45
+ }
46
+ }
47
+
48
+ export interface VectorQueryResult {
49
+ episodeId: string
50
+ similarity: number
51
+ }
52
+
53
+ /**
54
+ * Query knobs for vector search.
55
+ *
56
+ * The HNSW index is **similarity-only**: it ranks by vector distance and
57
+ * applies a `minSimilarity` floor, nothing else. Metadata is stored on
58
+ * VectorRecord but is NOT indexed, so any metadata-based narrowing
59
+ * (sourceType / valence / tags / tick range) must be done by the caller
60
+ * AFTER the search returns. See episodic.consolidator.semanticQuery.
61
+ */
62
+ export interface VectorQueryFilter {
63
+ minSimilarity?: number
64
+ maxResults?: number
65
+ }
@@ -0,0 +1,142 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/orchestrator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * CognitiveOrchestrator — extends DefaultOrchestrator with the three concerns
7
+ * that belong to the cognitive layer:
8
+ *
9
+ * 1. Bus wiring — subscribe engines to the bus on addEngine; call attachBus()
10
+ * so engines hold a direct reference for publishing (Phase F constructor injection).
11
+ * 2. Cold-start snapshot — broadcast engine states before the first tick.
12
+ * 3. HeartbeatPublisher — registered as onAfterTick middleware; owns bus flush
13
+ * + clock.tick delivery.
14
+ *
15
+ * Context enrichment (cognitiveBus in SimulationContext) has been removed:
16
+ * engines now hold the bus directly via constructor injection or attachBus().
17
+ */
18
+
19
+ import type { SimulationContext, Tick, ReadonlySimulationState } from '#core/types'
20
+ import type { SimulationEngine, OrchestratorConfig } from '#core/orchestrator'
21
+ import type { SimulationClock } from '#core/clock'
22
+ import type { EventBus } from '#core/event.bus'
23
+ import type { StateManager } from '#core/state.manager'
24
+ import type { CognitiveBus } from '#cognition/bus'
25
+ import { DefaultOrchestrator } from '#core/orchestrator'
26
+ import { createProductionBus } from '#cognition/bus'
27
+ import { CompletionInbox } from '#cognition/completion.inbox'
28
+ import { isCognitiveEngine } from '#cognition/types'
29
+ import { HeartbeatPublisher } from '#cognition/heartbeat'
30
+
31
+ export interface CognitiveOrchestratorConfig extends OrchestratorConfig {
32
+ /**
33
+ * The shared CognitiveBus instance.
34
+ * When omitted, CognitiveOrchestrator creates a production bus internally.
35
+ * Pass an explicit bus only when you need a shared or test bus.
36
+ */
37
+ cognitiveBus?: CognitiveBus
38
+ }
39
+
40
+ export class CognitiveOrchestrator extends DefaultOrchestrator {
41
+ private readonly _bus: CognitiveBus
42
+ private readonly _heartbeat: HeartbeatPublisher
43
+ private readonly _inbox: CompletionInbox
44
+
45
+ constructor(
46
+ clock: SimulationClock,
47
+ eventBus: EventBus,
48
+ stateManager: StateManager,
49
+ config: CognitiveOrchestratorConfig
50
+ ){
51
+ super( clock, eventBus, stateManager, config )
52
+
53
+ this._bus = config.cognitiveBus ?? createProductionBus()
54
+ this._heartbeat = new HeartbeatPublisher( this._bus )
55
+ this._inbox = new CompletionInbox()
56
+ }
57
+
58
+ /** The tick-boundary landing queue for async completion effects. */
59
+ get completionInbox(): CompletionInbox { return this._inbox }
60
+
61
+ // ── Phase 2 hook ─────────────────────────────────────────────
62
+
63
+ protected override async _onAfterPhase1(
64
+ tick: Tick,
65
+ state: ReadonlySimulationState,
66
+ _ctx: SimulationContext
67
+ ): Promise<void> {
68
+ // Pass 0: land async completion effects (facet decisions staged at LLM-
69
+ // promise resolution). FIRST — so listener effects (plan mutations, outbox
70
+ // writes) and any bus events those listeners publish deliver in THIS phase,
71
+ // keeping each decision's landing atomic at one tick boundary.
72
+ this._inbox.drain( tick )
73
+
74
+ // Pass 1: deliver events engines queued during react()
75
+ this._bus.flush()
76
+
77
+ // Publish clock.tick (adaptive — may be a no-op during quiet periods)
78
+ this._heartbeat.publishTick( tick, state )
79
+
80
+ // Pass 2: deliver clock.tick to subscribers
81
+ this._bus.flush()
82
+
83
+ // Phase 2 commit: apply all StateCommands returned by onCognitiveEvent() handlers
84
+ for( const commands of this._bus.drainCommands() )
85
+ this._stateManager.applyCommands( commands )
86
+ }
87
+
88
+ protected override _enginesTick(): SimulationEngine[] {
89
+ return this._engines.filter( e => e.react !== undefined )
90
+ }
91
+
92
+ // ── Engine wiring ────────────────────────────────────────────
93
+
94
+ override addEngine( engine: SimulationEngine ): void {
95
+ super.addEngine( engine )
96
+
97
+ // Phase F: give the engine a direct bus reference for publishing
98
+ if( 'attachBus' in engine && typeof ( engine as any ).attachBus === 'function' ){
99
+ ( engine as any ).attachBus( this._bus )
100
+ }
101
+
102
+ // Tick-boundary landing: engines that stage async completion effects
103
+ // (the ExecutiveEngine, for its facets) receive the shared inbox.
104
+ if( 'attachCompletionInbox' in engine && typeof ( engine as any ).attachCompletionInbox === 'function' ){
105
+ ( engine as any ).attachCompletionInbox( this._inbox )
106
+ }
107
+
108
+ // Wire delivery: subscribe the engine to its declared topics
109
+ if( isCognitiveEngine( engine ) ){
110
+ const topics = engine.subscribes()
111
+ if( topics.length > 0 ){
112
+ const acceptsFn = engine.acceptsVersions?.bind( engine )
113
+ this._bus.subscribe( engine.name, topics, ev => engine.onCognitiveEvent( ev ), acceptsFn )
114
+ }
115
+ }
116
+ }
117
+
118
+ override removeEngine( name: string ): boolean {
119
+ const removed = super.removeEngine( name )
120
+ if( removed ) this._bus.unsubscribe( name )
121
+
122
+ return removed
123
+ }
124
+
125
+ // ── Cold-start snapshot ──────────────────────────────────────
126
+
127
+ override async start( context: SimulationContext ): Promise<void> {
128
+ for( const engine of this._engines )
129
+ if( isCognitiveEngine( engine ) )
130
+ this._bus.publish({
131
+ type: 'engine.snapshot',
132
+ version: 1,
133
+ sourceEngine: engine.name,
134
+ salience: 0,
135
+ payload: engine.snapshot()
136
+ })
137
+
138
+ this._bus.flush()
139
+
140
+ await super.start( context )
141
+ }
142
+ }