@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,625 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/orchestrator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import { logger } from '#core/logger'
6
+ import type { EventBus } from '#core/event.bus'
7
+ import type { SimulationClock } from '#core/clock'
8
+ import type { StateManager } from '#core/state.manager'
9
+ import type {
10
+ SimulationContext,
11
+ SimulationEvent,
12
+ StateCommands,
13
+ ReadonlySimulationState,
14
+ Tick,
15
+ Duration,
16
+ MinimalContext,
17
+ } from '#core/types'
18
+ import { createContext } from '#core/utils'
19
+ import { wallClock } from '#core/wall.clock'
20
+
21
+ // ── Engine contract ──────────────────────────────────────────
22
+
23
+ /**
24
+ * What an engine returns from react().
25
+ * Events and commands are collected from all engines, then applied
26
+ * atomically — no engine sees another engine's writes from the same tick.
27
+ */
28
+ export interface EngineResult {
29
+ /** Events to publish after commands are applied. tick is stamped by Orchestrator. */
30
+ events?: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>>
31
+ /** State mutations to apply after all engines have finished reading. */
32
+ commands?: StateCommands
33
+ }
34
+
35
+ export interface SimulationEngine {
36
+ readonly name: string
37
+
38
+ /**
39
+ * Called once per tick with a frozen read-only snapshot.
40
+ * Engines must NOT mutate state directly — return commands instead.
41
+ * Omit entirely on purely event-driven engines — the orchestrator skips them.
42
+ */
43
+ react?(
44
+ delta: Duration,
45
+ tick: Tick,
46
+ state: ReadonlySimulationState,
47
+ context: SimulationContext
48
+ ): Promise<EngineResult>
49
+
50
+ /**
51
+ * Called when this engine's react() throws. Allows the orchestrator
52
+ * to isolate engine failures without crashing the entire tick.
53
+ *
54
+ * Return EngineResult to provide fallback commands/events,
55
+ * or return null to skip this engine's contribution for this tick.
56
+ * If not provided, the error propagates (old behavior).
57
+ */
58
+ onError?( error: Error, tick: Tick, context: SimulationContext ): Promise<EngineResult | null>
59
+
60
+ onAttach?( state: StateManager, context: SimulationContext ): Promise<void>
61
+ onDetach?( state: StateManager, context: SimulationContext ): Promise<void>
62
+ }
63
+
64
+
65
+ // ── Middleware ───────────────────────────────────────────────
66
+
67
+ export type TickMiddleware = (
68
+ tick: Tick,
69
+ state: ReadonlySimulationState,
70
+ context: SimulationContext
71
+ ) => void | Promise<void>
72
+
73
+ /**
74
+ * Pre-commit validator signature.
75
+ * Receives all pending commands for the current tick.
76
+ * Return string[] of validation errors to abort the commit,
77
+ * or void/true to proceed.
78
+ */
79
+ export type CommitValidator = (
80
+ commands: StateCommands[],
81
+ tick: Tick,
82
+ context: SimulationContext
83
+ ) => string[] | true | void | Promise<string[] | true | void>
84
+
85
+ // ── Config ───────────────────────────────────────────────────
86
+
87
+ export interface OrchestratorConfig {
88
+ tickIntervalMs?: number
89
+ maxTicks?: number
90
+ maxRealTimeMs?: number
91
+ /**
92
+ * Stop the loop when the event bus is empty AND no engine produced
93
+ * events or state commands in the last tick.
94
+ */
95
+ stopOnEmptyEvents?: boolean
96
+ idleThresholdMs?: number
97
+ /** Fired before engines run — receives the frozen pre-tick snapshot. */
98
+ onBeforeTick?: TickMiddleware | TickMiddleware[]
99
+ /** Fired after commands are applied and events are flushed. */
100
+ onAfterTick?: TickMiddleware | TickMiddleware[]
101
+ /**
102
+ * Pre-commit validation hooks. Called after engines run but
103
+ * before commands are applied. Receives all pending commands.
104
+ * All validators must pass — if any returns string[], the commit
105
+ * is aborted and events are not published for this tick.
106
+ */
107
+ onBeforeCommit?: CommitValidator[]
108
+ }
109
+
110
+ // ── Interface ────────────────────────────────────────────────
111
+
112
+ export interface Orchestrator {
113
+ readonly isRunning: boolean
114
+ readonly currentTick: Tick
115
+
116
+ /**
117
+ * Register an onAfterTick handler. Multiple handlers can be registered
118
+ * and all will be called in registration order after each tick.
119
+ * Returns an unsubscribe function.
120
+ */
121
+ onAfterTick( handler: TickMiddleware ): () => void
122
+
123
+ /** Register an onBeforeTick handler. Returns unsubscribe function. */
124
+ onBeforeTick( handler: TickMiddleware ): () => void
125
+
126
+ /** Register a pre-commit validator. All validators must pass. Returns unsubscribe function. */
127
+ onBeforeCommit( validator: CommitValidator ): () => void
128
+
129
+ /**
130
+ * Update non-middleware config fields at runtime.
131
+ * Accepts a partial config — only provided fields are changed.
132
+ * undefined values are ignored.
133
+ *
134
+ * NOTE: Does NOT accept onBeforeTick/onAfterTick/onBeforeCommit.
135
+ * Use the dedicated registration methods for middleware.
136
+ */
137
+ updateConfig( config: Partial<Omit<OrchestratorConfig, 'onBeforeTick' | 'onAfterTick' | 'onBeforeCommit'>> ): void
138
+
139
+ addEngine( engine: SimulationEngine ): void
140
+ removeEngine( name: string ): boolean
141
+ hasPendingAsyncWork(): boolean
142
+ /** Engine names in execution order (assembly-order snapshot artifact). */
143
+ readonly engineNames: string[]
144
+ /** Registered engine instances, execution-ordered (assembly wiring audit). */
145
+ readonly engines: readonly SimulationEngine[]
146
+
147
+ start( context: SimulationContext ): Promise<void>
148
+ stop(): void
149
+ pause(): void
150
+ resume(): void
151
+
152
+ step( count?: number, context?: MinimalContext ): Promise<void>
153
+ runUntilTick( targetTick: Tick, context?: MinimalContext ): Promise<void>
154
+ runUntilTime( targetTime: number, context?: MinimalContext ): Promise<void>
155
+ }
156
+
157
+ // ── Implementation ───────────────────────────────────────────
158
+
159
+ export class DefaultOrchestrator implements Orchestrator {
160
+ protected _engines: SimulationEngine[] = []
161
+
162
+ /** Override in subclasses to control which engines participate in the tick loop. */
163
+ protected _enginesTick(): SimulationEngine[] { return this._engines }
164
+ private _isRunning = false
165
+ private _isPaused = false
166
+ private _shouldStop = false
167
+ private _currentTick: Tick = 0
168
+ private _clock: SimulationClock
169
+ private _eventBus: EventBus
170
+ protected _stateManager: StateManager
171
+ private _config: OrchestratorConfig
172
+ private _lastTickHadActivity = false
173
+ private _lastEventTimestamp: number = 0
174
+ private _idleTimer: NodeJS.Timeout | null = null
175
+ private _tickTimer: NodeJS.Timeout | null = null
176
+ // Re-entrancy guard: true while _executeTick is in flight. Prevents an
177
+ // overlapping tick when a tick's async work exceeds tickIntervalMs.
178
+ private _executing = false
179
+
180
+ /** Runtime middleware arrays — the single source of truth for tick execution. */
181
+ private _onBeforeTickHandlers: TickMiddleware[] = []
182
+ private _onAfterTickHandlers: TickMiddleware[] = []
183
+ private _onBeforeCommitHandlers: CommitValidator[] = []
184
+
185
+ // Tick benchmarking — records per-tick latency
186
+ private _tickLatencies: number[] = []
187
+ private _maxLatencySamples = 1000
188
+
189
+ constructor(
190
+ clock: SimulationClock,
191
+ eventBus: EventBus,
192
+ stateManager: StateManager,
193
+ config: OrchestratorConfig = {}
194
+ ){
195
+ this._clock = clock
196
+ this._eventBus = eventBus
197
+ this._stateManager = stateManager
198
+ this._config = config
199
+
200
+ // Seed runtime arrays from constructor config
201
+ this._seedMiddleware( config )
202
+ }
203
+
204
+ /**
205
+ * Normalize constructor config into the runtime middleware arrays.
206
+ * Accepts single handler or array for onBeforeTick/onAfterTick.
207
+ * onBeforeCommit is always an array.
208
+ */
209
+ private _seedMiddleware( config: OrchestratorConfig ): void {
210
+ if( config.onBeforeTick ) {
211
+ if( Array.isArray( config.onBeforeTick ) )
212
+ this._onBeforeTickHandlers.push( ...config.onBeforeTick )
213
+ else
214
+ this._onBeforeTickHandlers.push( config.onBeforeTick )
215
+ }
216
+
217
+ if( config.onAfterTick ) {
218
+ if( Array.isArray( config.onAfterTick ) )
219
+ this._onAfterTickHandlers.push( ...config.onAfterTick )
220
+ else
221
+ this._onAfterTickHandlers.push( config.onAfterTick )
222
+ }
223
+
224
+ if( config.onBeforeCommit )
225
+ this._onBeforeCommitHandlers.push( ...config.onBeforeCommit )
226
+ }
227
+
228
+ get isRunning(): boolean { return this._isRunning }
229
+ get currentTick(): Tick { return this._currentTick }
230
+
231
+ /**
232
+ * Returns recent tick latency measurements for benchmarking.
233
+ * Array of durations in milliseconds, most recent last.
234
+ */
235
+ get tickLatencies(): ReadonlyArray<number> {
236
+ return this._tickLatencies
237
+ }
238
+
239
+ /**
240
+ * Returns average tick latency over the sample window.
241
+ */
242
+ get averageTickLatency(): number {
243
+ if( this._tickLatencies.length === 0 ) return 0
244
+ return this._tickLatencies.reduce( ( a, b ) => a + b, 0 ) / this._tickLatencies.length
245
+ }
246
+
247
+ // ── Middleware registration (post-construction) ──────────
248
+
249
+ /**
250
+ * Register an onBeforeTick handler. Called before engines run each tick.
251
+ * Returns an unsubscribe function to remove this specific handler.
252
+ */
253
+ onBeforeTick( handler: TickMiddleware ): () => void {
254
+ this._onBeforeTickHandlers.push( handler )
255
+ return () => {
256
+ const idx = this._onBeforeTickHandlers.indexOf( handler )
257
+ if( idx !== -1 ) this._onBeforeTickHandlers.splice( idx, 1 )
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Register an onAfterTick handler. Called after commands are applied
263
+ * and events are flushed each tick. Multiple handlers are called in
264
+ * registration order.
265
+ * Returns an unsubscribe function.
266
+ */
267
+ onAfterTick( handler: TickMiddleware ): () => void {
268
+ this._onAfterTickHandlers.push( handler )
269
+ return () => {
270
+ const idx = this._onAfterTickHandlers.indexOf( handler )
271
+ if( idx !== -1 ) this._onAfterTickHandlers.splice( idx, 1 )
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Register a pre-commit validator. All registered validators must pass
277
+ * (return void/true) for commands to be applied. If any validator returns
278
+ * string[], the commit is aborted for this tick.
279
+ * Returns an unsubscribe function.
280
+ */
281
+ onBeforeCommit( validator: CommitValidator ): () => void {
282
+ this._onBeforeCommitHandlers.push( validator )
283
+ return () => {
284
+ const idx = this._onBeforeCommitHandlers.indexOf( validator )
285
+ if( idx !== -1 ) this._onBeforeCommitHandlers.splice( idx, 1 )
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Update non-middleware configuration at runtime.
291
+ * Only numeric/boolean config fields are accepted — middleware hooks
292
+ * must be registered via onBeforeTick() / onAfterTick() / onBeforeCommit().
293
+ * undefined values are ignored (no un-set support).
294
+ */
295
+ updateConfig( config: Partial<Omit<OrchestratorConfig, 'onBeforeTick' | 'onAfterTick' | 'onBeforeCommit'>> ): void {
296
+ if( config.tickIntervalMs !== undefined )
297
+ this._config.tickIntervalMs = config.tickIntervalMs
298
+
299
+ if( config.maxTicks !== undefined )
300
+ this._config.maxTicks = config.maxTicks
301
+
302
+ if( config.maxRealTimeMs !== undefined )
303
+ this._config.maxRealTimeMs = config.maxRealTimeMs
304
+
305
+ if( config.stopOnEmptyEvents !== undefined )
306
+ this._config.stopOnEmptyEvents = config.stopOnEmptyEvents
307
+
308
+ if( config.idleThresholdMs !== undefined )
309
+ this._config.idleThresholdMs = config.idleThresholdMs
310
+ }
311
+
312
+ // ── Engine registry ──────────────────────────────────────
313
+
314
+ addEngine( engine: SimulationEngine ): void {
315
+ this._engines.push( engine )
316
+ }
317
+
318
+ /**
319
+ * Registered engine names IN EXECUTION ORDER. Registration order = serial
320
+ * tick order = replay determinism — this getter makes the order observable
321
+ * so the assembly-order snapshot test can pin it as a reviewed artifact
322
+ * (the true order comes from priority fields scattered across engine files;
323
+ * without this it is visible nowhere).
324
+ */
325
+ get engineNames(): string[] {
326
+ return this._engines.map( e => e.name )
327
+ }
328
+
329
+ /** The registered engine instances, execution-ordered (assembly audit). */
330
+ get engines(): readonly SimulationEngine[] {
331
+ return this._engines
332
+ }
333
+
334
+ removeEngine( name: string ): boolean {
335
+ const index = this._engines.findIndex( e => e.name === name )
336
+ if( index === -1 ) return false
337
+
338
+ this._engines.splice( index, 1 )
339
+ return true
340
+ }
341
+
342
+ /**
343
+ * Returns true if any registered engine is an AsyncEngine
344
+ * with pending reasoning operations. Useful for stopOnEmptyEvents
345
+ * logic — the orchestrator should not stop while engines are thinking.
346
+ */
347
+ hasPendingAsyncWork(): boolean {
348
+ // AsyncEngine instances maintain internal pending state.
349
+ // This is a conservative check — we check if any engine's react()
350
+ // returned no commands AND no events last tick (indicating waiting).
351
+ // More precise tracking would require the engine to expose pending count.
352
+ return false // Default: orchestrator doesn't introspect engine internals
353
+ }
354
+
355
+ /**
356
+ * Hook called after Phase 1 commands are applied and the EventBus is flushed,
357
+ * but BEFORE after-tick middleware runs. Override in CognitiveOrchestrator to
358
+ * flush the CognitiveBus and apply Phase 2 (event-handler) commands so that
359
+ * metrics and snapshot after-tick handlers see the fully updated state.
360
+ */
361
+ protected async _onAfterPhase1(
362
+ _tick: Tick,
363
+ _state: ReadonlySimulationState,
364
+ _context: SimulationContext
365
+ ): Promise<void> { /* no-op in base orchestrator */ }
366
+
367
+ // ── Lifecycle ────────────────────────────────────────────
368
+
369
+ async start( context: SimulationContext ): Promise<void> {
370
+ if( this._isRunning ) return
371
+
372
+ this._shouldStop = false
373
+ this._isRunning = true
374
+ this._isPaused = false
375
+
376
+ for( const engine of this._engines )
377
+ engine.onAttach && await engine.onAttach( this._stateManager, context )
378
+
379
+ this._runLoop( context )
380
+ }
381
+
382
+ /**
383
+ * Tick-loop scheduling. Uses setInterval for consistent pacing;
384
+ * calls _executeTick() which handles pause/stop checks internally.
385
+ * The orchestrator is the sole driver of ticks.
386
+ */
387
+ private _runLoop( context: SimulationContext ): void {
388
+ if( this._tickTimer ) return
389
+
390
+ this._tickTimer = setInterval( async () => {
391
+ if( this._shouldStop || this._isPaused ) return
392
+
393
+ // Drop this firing if the previous tick is still in flight. setInterval does
394
+ // not wait for an async callback, so without this guard a tick whose async
395
+ // work (engine reacts, eventBus.flush) exceeds tickIntervalMs would overlap
396
+ // the next one and interleave applyCommands on the double buffer.
397
+ if( this._executing ) return
398
+
399
+ if( this._config.maxTicks && this._currentTick >= this._config.maxTicks ){
400
+ await this._shutdown( context )
401
+ return
402
+ }
403
+
404
+ if( this._config.maxRealTimeMs && wallClock() >= this._config.maxRealTimeMs ){
405
+ await this._shutdown( context )
406
+ return
407
+ }
408
+
409
+ if( this._config.stopOnEmptyEvents
410
+ && this._eventBus.getPendingCount() === 0
411
+ && !this._lastTickHadActivity ){
412
+ await this._shutdown( context )
413
+ return
414
+ }
415
+
416
+ this._executing = true
417
+ try {
418
+ await this._executeTick( context )
419
+ }
420
+ finally {
421
+ this._executing = false
422
+ }
423
+ }, this._config.tickIntervalMs ?? 0 )
424
+ }
425
+
426
+ private async _shutdown( context: SimulationContext ): Promise<void> {
427
+ this._shouldStop = true
428
+ this._tickTimer && clearInterval( this._tickTimer )
429
+ this._tickTimer = null
430
+
431
+ for( const engine of this._engines )
432
+ engine.onDetach && await engine.onDetach( this._stateManager, context )
433
+
434
+ this._isRunning = false
435
+ }
436
+
437
+ stop(): void {
438
+ this._shouldStop = true
439
+ this._tickTimer && clearInterval( this._tickTimer )
440
+ this._tickTimer = null
441
+ }
442
+
443
+ pause(): void { this._isPaused = true; this._clock.pause() }
444
+ resume(): void { this._isPaused = false; this._clock.resume() }
445
+
446
+ // ── Core tick ────────────────────────────────────────────
447
+
448
+ private async _executeTick( context: SimulationContext ): Promise<void> {
449
+ const tickStart = wallClock()
450
+
451
+ // 1. Advance clock
452
+ this._clock.tick()
453
+ this._currentTick = this._clock.currentTick
454
+
455
+ // 2. Sync state manager clock reference
456
+ this._stateManager.updateClock( this._currentTick, this._clock.now )
457
+
458
+ // 3. Drain future events due at this tick into the pending queue
459
+ this._eventBus.prepareTick( this._currentTick )
460
+
461
+ // 4. Frozen read-only snapshot — all engines read this same view
462
+ const snapshot = this._stateManager.snapshot() as ReadonlySimulationState
463
+
464
+ // Allow subclasses to enrich the context (e.g. inject cognitiveBus)
465
+ const engineContext = this._buildEngineContext( context )
466
+
467
+ // 5. Before-tick: iterate all handlers in registration order
468
+ for( const handler of this._onBeforeTickHandlers )
469
+ await handler( this._currentTick, snapshot, engineContext )
470
+
471
+ // 6. Run engines — strictly sequential in priority (registration) order.
472
+ // This serial await chain is DELIBERATE, not an oversight: it fixes the order
473
+ // in which engines emit commands, which is what makes record-and-replay
474
+ // reproducible. The synchronous heuristic engines are mutually independent
475
+ // within a tick and *could* run via Promise.all for throughput — do NOT do
476
+ // that. Parallelizing reintroduces nondeterministic command ordering and
477
+ // silently breaks replay. Throughput is traded for determinism on purpose.
478
+ const allResults: EngineResult[] = []
479
+
480
+ const runEngine = async ( engine: SimulationEngine ): Promise<void> => {
481
+ try {
482
+ const result = await engine.react?.( this._clock.delta, this._currentTick, snapshot, engineContext )
483
+ if( result ) allResults.push( result )
484
+ }
485
+ catch( error ){
486
+ if( engine.onError ){
487
+ const fallback = await engine.onError( error as Error, this._currentTick, engineContext )
488
+ if( fallback ) allResults.push( fallback )
489
+ }
490
+ else {
491
+ logger.error(
492
+ `[Orchestrator] Engine "${engine.name}" threw at tick ${this._currentTick}:`,
493
+ error
494
+ )
495
+ }
496
+ }
497
+ }
498
+
499
+ for( const engine of this._enginesTick() )
500
+ await runEngine( engine )
501
+
502
+ // Track pending async work across engines (check all engines, not just tick engines)
503
+ const hasPendingAsync = this._engines.some( e => ( e as any ).hasPendingWork === true )
504
+
505
+ // 7. Collect all commands for pre-commit validation
506
+ const allCommands: StateCommands[] = []
507
+ for( const result of allResults )
508
+ result.commands && allCommands.push( result.commands )
509
+
510
+ // 8. Pre-commit validation — all validators must pass.
511
+ // If any validator returns string[], abort the commit for this tick.
512
+ if( this._onBeforeCommitHandlers.length > 0 && allCommands.length > 0 ){
513
+ let abort = false
514
+ for( const validator of this._onBeforeCommitHandlers ){
515
+ const result = await validator( allCommands, this._currentTick, engineContext )
516
+ if( Array.isArray( result ) && result.length > 0 ){
517
+ logger.error(
518
+ `[Orchestrator] Pre-commit validation failed at tick ${this._currentTick}:`,
519
+ result.join(', ')
520
+ )
521
+ abort = true
522
+ break // Stop at first failing validator
523
+ }
524
+ }
525
+
526
+ if( abort ) return // Skip command application and event publishing
527
+ }
528
+
529
+ // 9. Apply all state commands atomically (double-buffer commit)
530
+ for( const commands of allCommands )
531
+ this._stateManager.applyCommands( commands )
532
+
533
+ // 10. Publish engine events with tick stamped
534
+ const allEvents: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = []
535
+ for( const result of allResults )
536
+ result.events && allEvents.push( ...result.events )
537
+
538
+ for( const event of allEvents )
539
+ this._eventBus.publish( event, context, this._currentTick )
540
+
541
+ // 12. Flush event bus — handlers see the post-command state
542
+ await this._eventBus.flush()
543
+
544
+ // Phase 2: CognitiveBus flush + event-handler command commit (overridden in CognitiveOrchestrator)
545
+ await this._onAfterPhase1( this._currentTick, snapshot, engineContext )
546
+
547
+ // 13. After-tick: iterate all handlers in registration order
548
+ const postSnapshot = this._stateManager.snapshot() as ReadonlySimulationState
549
+ for( const handler of this._onAfterTickHandlers )
550
+ await handler( this._currentTick, postSnapshot, engineContext )
551
+
552
+ // 13. Track activity for stopOnEmptyEvents
553
+ const commandActivity = allResults.some( r => ( r.commands?.set?.length ?? 0 ) + ( r.commands?.delete?.length ?? 0 ) > 0 )
554
+ this._lastTickHadActivity = allEvents.length > 0 || commandActivity || hasPendingAsync
555
+
556
+ // 14. Idle detection
557
+ if( this._lastTickHadActivity ){
558
+ this._lastEventTimestamp = wallClock()
559
+ this._resetIdleTimer()
560
+ }
561
+ else if( this._config.idleThresholdMs )
562
+ this._resetIdleTimer()
563
+
564
+ // 15. Record tick latency for benchmarking
565
+ const tickDuration = wallClock() - tickStart
566
+ this._tickLatencies.push( tickDuration )
567
+
568
+ if( this._tickLatencies.length > this._maxLatencySamples )
569
+ this._tickLatencies.shift()
570
+ }
571
+
572
+ /**
573
+ * Override in subclasses to enrich the SimulationContext passed to engines.
574
+ * Default: returns context unchanged.
575
+ * CognitiveOrchestrator overrides this to inject cognitiveBus.
576
+ */
577
+ protected _buildEngineContext( context: SimulationContext ): SimulationContext {
578
+ return context
579
+ }
580
+
581
+ private _resetIdleTimer(): void {
582
+ if( !this._config.idleThresholdMs ) return
583
+
584
+ this._idleTimer && clearTimeout( this._idleTimer )
585
+ this._idleTimer = setTimeout( () => {
586
+ if( this._isRunning && !this._isPaused ){
587
+ const idleDuration = wallClock() - this._lastEventTimestamp
588
+ idleDuration >= ( this._config.idleThresholdMs ?? 0 ) && ( this._shouldStop = true )
589
+ }
590
+ }, this._config.idleThresholdMs )
591
+ }
592
+
593
+ private _wait( ms: number ): Promise<void> {
594
+ return new Promise( resolve => setTimeout( resolve, ms ) )
595
+ }
596
+
597
+ // ── Utility run modes ────────────────────────────────────
598
+
599
+ async step(
600
+ count: number = 1,
601
+ { simulationId, runId, seed }: MinimalContext = { simulationId: 'step', runId: 'single' }
602
+ ): Promise<void> {
603
+ const fullContext = createContext( simulationId, runId, seed ?? 42 )
604
+ for( let i = 0; i < count; i++ )
605
+ await this._executeTick( fullContext )
606
+ }
607
+
608
+ async runUntilTick(
609
+ targetTick: Tick,
610
+ { simulationId, runId, seed }: MinimalContext = { simulationId: 'batch', runId: 'until-tick' }
611
+ ): Promise<void> {
612
+ const fullContext = createContext( simulationId, runId, seed ?? 42 )
613
+ while( this._currentTick < targetTick && !this._shouldStop )
614
+ await this._executeTick( fullContext )
615
+ }
616
+
617
+ async runUntilTime(
618
+ targetTime: number,
619
+ { simulationId, runId, seed }: MinimalContext = { simulationId: 'batch', runId: 'until-time' }
620
+ ): Promise<void> {
621
+ const fullContext = createContext( simulationId, runId, seed ?? 42 )
622
+ while( this._clock.now < targetTime && !this._shouldStop )
623
+ await this._executeTick( fullContext )
624
+ }
625
+ }