@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,539 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/async.engine.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Base class for simulation engines that perform asynchronous reasoning
7
+ * (e.g., LLM calls). Extends SimulationEngine with
8
+ * non-blocking execution, state re-validation, and progressive streaming
9
+ * of intermediate results.
10
+ *
11
+ * Engines that extend this class:
12
+ * - readState() captures a ReasoningFootprint at reasoning start
13
+ * - reasonAsync() performs the actual async work (LLM call, etc.)
14
+ * and receives an IntermediateStream to report progress
15
+ * - onReasoningComplete() converts the reasoning result to StateCommands
16
+ * - onIntermediateResult() converts streaming steps to StateCommands
17
+ *
18
+ * The base class handles:
19
+ * - Non-blocking tick execution (react() never awaits LLM calls)
20
+ * - Footprint capture
21
+ * - Progressive intermediate result application (bypasses conflict detection)
22
+ * - Conflict detection and resolution on final results
23
+ * - Re-evaluation triggering
24
+ */
25
+
26
+ import { logger } from '#core/logger'
27
+ import type {
28
+ Duration,
29
+ Tick,
30
+ SimulationContext,
31
+ ReadonlySimulationState,
32
+ ReasoningFootprint,
33
+ StateCommands,
34
+ ConflictStrategy,
35
+ AsyncEngineConfig,
36
+ SimulationEvent,
37
+ ConflictReport,
38
+ } from '#core/types'
39
+ import type { SimulationEngine, EngineResult } from '#core/orchestrator'
40
+ import { ConflictDetector } from '#core/conflict.detector'
41
+ import { wallClock } from '#core/wall.clock'
42
+
43
+ // ── Streaming ─────────────────────────────────────────────────
44
+
45
+ /**
46
+ * A write-side channel that async reasoning can push intermediate
47
+ * results into. Each push triggers onIntermediateResult() and the
48
+ * returned StateCommands are applied immediately to the current tick
49
+ * (no conflict detection — progressive disclosure only).
50
+ */
51
+ export interface IntermediateStream {
52
+ /**
53
+ * Report an intermediate step result.
54
+ * Commands returned by onIntermediateResult() are merged into the
55
+ * current tick's command batch immediately.
56
+ */
57
+ report( step: string, result: unknown ): void
58
+
59
+ /** The footprint this stream is associated with */
60
+ readonly footprint: ReasoningFootprint
61
+
62
+ /** Number of intermediate results reported so far */
63
+ readonly count: number
64
+ }
65
+
66
+ // ── Internal state ────────────────────────────────────────────
67
+
68
+ interface IntermediateEntry {
69
+ step: string
70
+ result: unknown
71
+ timestamp: number
72
+ }
73
+
74
+ /**
75
+ * Internal state for a pending reasoning operation.
76
+ */
77
+ interface PendingReasoning {
78
+ /** Unique key for this pending operation */
79
+ key: string
80
+ /** The footprint captured when reasoning began */
81
+ footprint: ReasoningFootprint
82
+ /** The promise that resolves with reasoning output */
83
+ promise: Promise<unknown>
84
+ /** Tick when reasoning was initiated */
85
+ startedAtTick: Tick
86
+ /** Intermediate results that arrived while reasoning was in-flight */
87
+ intermediates: IntermediateEntry[]
88
+ /** Set to true via .then()/.catch() when the promise settles */
89
+ settled: boolean
90
+ /** Set to true by _collectCompleted() after result is processed (prevents double-processing) */
91
+ resolved: boolean
92
+ }
93
+
94
+ export abstract class AsyncEngine implements SimulationEngine {
95
+ abstract readonly name: string
96
+
97
+ private _pending: Map<string, PendingReasoning> = new Map()
98
+ private _detector: ConflictDetector
99
+ private _config: AsyncEngineConfig
100
+ private _pendingCounter = 0
101
+
102
+ constructor( config: AsyncEngineConfig = {} ){
103
+ this._detector = new ConflictDetector()
104
+ this._config = {
105
+ defaultStrategy: config.defaultStrategy ?? 'REJECT',
106
+ maxPendingTicks: config.maxPendingTicks ?? 50,
107
+ logConflicts: config.logConflicts ?? true,
108
+ rerunOnRejection: config.rerunOnRejection ?? true,
109
+ }
110
+ }
111
+
112
+ /** True when there is at least one in-flight reasoning promise. */
113
+ get hasPendingWork(): boolean {
114
+ return this._pending.size > 0
115
+ }
116
+
117
+ /**
118
+ * Await all in-flight reasoning promises to settle WITHOUT advancing the
119
+ * simulation. A caller can then collect the completed decision on the next
120
+ * single step — instead of stepping repeatedly to poll, which would drain
121
+ * simulation state (energy, circadian) and corrupt a controlled stimulus.
122
+ * Resolves immediately when nothing is pending.
123
+ */
124
+ async awaitPending(): Promise<void> {
125
+ await Promise.allSettled( [ ...this._pending.values() ].map( p => p.promise ) )
126
+ }
127
+
128
+ // ── SimulationEngine implementation ──────────────────────
129
+
130
+ /**
131
+ * Non-blocking tick react.
132
+ * 1. Drains intermediate results from in-flight reasoning
133
+ * 2. Checks for completed reasoning from previous ticks
134
+ * 3. Starts new reasoning if the engine is idle (shouldAct() returns true)
135
+ * 4. Returns validated results for this tick
136
+ */
137
+ async react(
138
+ delta: Duration,
139
+ tick: Tick,
140
+ state: ReadonlySimulationState,
141
+ context: SimulationContext
142
+ ): Promise<EngineResult> {
143
+ const
144
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
145
+ commands: StateCommands = {}
146
+
147
+ // 1. Drain intermediate results from in-flight reasoning
148
+ this._drainIntermediates( tick, context, events, commands )
149
+
150
+ // 2. Collect completed reasoning, re-validated against current state
151
+ await this._collectCompleted( tick, state, context, events, commands )
152
+
153
+ // 3. Prune stale pending operations
154
+ this._pruneStale( tick )
155
+
156
+ // 4. Start new reasoning if conditions are met
157
+ if( this.shouldAct( state, tick, context ) ){
158
+ const footprint = this.readState( state, tick )
159
+
160
+ // Build the intermediate stream that reasoning can push into
161
+ const stream = this._createStream( footprint, context, events, commands )
162
+ const reasoningPromise = this.reasonAsync( footprint, state, context, stream )
163
+
164
+ this._pendingCounter++
165
+ const key = `${this.name}-${this._pendingCounter}-tick${tick}`
166
+
167
+ const pendingEntry: PendingReasoning = {
168
+ key,
169
+ footprint,
170
+ promise: reasoningPromise,
171
+ startedAtTick: tick,
172
+ intermediates: [],
173
+ settled: false,
174
+ resolved: false,
175
+ }
176
+
177
+ // Mark settled when the promise completes (micro-task safe flag)
178
+ reasoningPromise.then(
179
+ () => { pendingEntry.settled = true },
180
+ () => { pendingEntry.settled = true }
181
+ )
182
+
183
+ this._pending.set( key, pendingEntry )
184
+ }
185
+
186
+ // Emit queue depth metric so orchestrator/runner can detect overloaded engines
187
+ const metricKey = `engine.${this.name}.pending_depth`
188
+ commands.metrics ??= []
189
+ commands.metrics.push([ metricKey, this._pending.size ])
190
+
191
+ return { events: events.length > 0 ? events : undefined, commands }
192
+ }
193
+
194
+ // ── Optional lifecycle hooks ─────────────────────────────
195
+
196
+ onAttach?(): Promise<void>
197
+ onDetach?(): Promise<void>
198
+
199
+ /**
200
+ * Called when this engine's react() throws.
201
+ * Default behavior: log and return empty result (engine contributes nothing this tick).
202
+ */
203
+ async onError( error: Error, _tick: Tick, _context: SimulationContext ): Promise<EngineResult | null> {
204
+ logger.error(`[AsyncEngine] ${this.name} error:`, error )
205
+ return null
206
+ }
207
+
208
+ // ── Subclass contract ────────────────────────────────────
209
+
210
+ /**
211
+ * Whether the engine should initiate reasoning this tick.
212
+ * Override to control when the engine activates.
213
+ * Default: activates when no reasoning is pending.
214
+ */
215
+ protected shouldAct(
216
+ _state: ReadonlySimulationState,
217
+ _tick: Tick,
218
+ _context: SimulationContext
219
+ ): boolean {
220
+ return this._pending.size === 0
221
+ }
222
+
223
+ /**
224
+ * Capture a reasoning footprint from the current state snapshot.
225
+ * Override to specify which entities/metrics the engine will observe.
226
+ * Default: captures all entity IDs and metric keys (conservative).
227
+ */
228
+ protected readState(
229
+ state: ReadonlySimulationState,
230
+ tick: Tick
231
+ ): ReasoningFootprint {
232
+ return {
233
+ tickObserved: tick,
234
+ entitiesRead: new Set( state.entities.keys() ),
235
+ metricsRead: new Set( state.metrics.keys() ),
236
+ entitiesModified: new Set(),
237
+ intendedCommands: {},
238
+ source: this.name,
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Perform the actual async reasoning.
244
+ * Receives the footprint captured at reasoning start, the frozen snapshot,
245
+ * and an IntermediateStream for reporting progress.
246
+ *
247
+ * Call stream.report( stepName, stepResult ) at each meaningful boundary
248
+ * to enable progressive state updates during long-running reasoning.
249
+ *
250
+ * Must return the raw reasoning output — onReasoningComplete() converts it.
251
+ */
252
+ protected abstract reasonAsync(
253
+ footprint: ReasoningFootprint,
254
+ state: ReadonlySimulationState,
255
+ context: SimulationContext,
256
+ stream: IntermediateStream
257
+ ): Promise<unknown>
258
+
259
+ /**
260
+ * Convert reasoning output to StateCommands.
261
+ * Called after conflict validation passes on the final result.
262
+ */
263
+ protected abstract onReasoningComplete(
264
+ output: unknown,
265
+ footprint: ReasoningFootprint,
266
+ context: SimulationContext
267
+ ): StateCommands
268
+
269
+ /**
270
+ * Convert an intermediate step result to StateCommands.
271
+ * Called immediately when stream.report() is invoked during reasoning.
272
+ * These commands bypass conflict detection — use only for:
273
+ * - Metric updates (progress, confidence, phase indicators)
274
+ * - Engine-owned entities (draft plans, working notes, partial analyses)
275
+ * - Signal events (not entity mutations)
276
+ *
277
+ * Return StateCommands to apply now, or null to skip.
278
+ * Default: no intermediate updates.
279
+ */
280
+ protected onIntermediateResult(
281
+ step: string,
282
+ result: unknown,
283
+ footprint: ReasoningFootprint,
284
+ context: SimulationContext
285
+ ): StateCommands | null {
286
+ return null
287
+ }
288
+
289
+ /**
290
+ * Choose the conflict strategy for this reasoning.
291
+ * Override to vary strategy based on context (e.g., high-stakes actions use REJECT).
292
+ */
293
+ protected chooseStrategy(
294
+ _footprint: ReasoningFootprint,
295
+ _context: SimulationContext
296
+ ): ConflictStrategy {
297
+ return this._config.defaultStrategy!
298
+ }
299
+
300
+ /**
301
+ * Called when reasoning is rejected due to conflicts.
302
+ * Override to handle rejection (logging, notification, etc.).
303
+ */
304
+ protected onConflictRejected(
305
+ footprint: ReasoningFootprint,
306
+ report: ConflictReport,
307
+ _context: SimulationContext
308
+ ): void {
309
+ this._config.logConflicts && logger.warn(
310
+ `[AsyncEngine] ${this.name} reasoning from tick ${footprint.tickObserved} rejected:`,
311
+ `${report.readConflicts.length} read conflicts, ${report.writeConflicts.length} write conflicts`
312
+ )
313
+ }
314
+
315
+ // ── Stream creation ──────────────────────────────────────
316
+
317
+ /**
318
+ * Create an IntermediateStream wired to a specific pending entry.
319
+ * The stream pushes directly into the pending intermediates array,
320
+ * which are drained each tick by _drainIntermediates().
321
+ */
322
+ private _createStream(
323
+ footprint: ReasoningFootprint,
324
+ context: SimulationContext,
325
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>>,
326
+ commands: StateCommands
327
+ ): IntermediateStream {
328
+ let count = 0
329
+
330
+ return {
331
+ footprint,
332
+
333
+ get count(){ return count },
334
+
335
+ report: ( step: string, result: unknown ): void => {
336
+ count++
337
+
338
+ // Apply the intermediate result immediately through the subclass hook
339
+ const intermediateCommands = this.onIntermediateResult(
340
+ step, result, footprint, context
341
+ )
342
+
343
+ if( intermediateCommands ){
344
+ this._mergeCommands( commands, intermediateCommands )
345
+ }
346
+
347
+ // Also store in the pending entry for historical record
348
+ // Find our pending entry (the last one added for this footprint's tick)
349
+ for( const pending of this._pending.values() ){
350
+ if( pending.footprint.tickObserved === footprint.tickObserved
351
+ && !pending.resolved ){
352
+ pending.intermediates.push({
353
+ step,
354
+ result,
355
+ timestamp: wallClock(),
356
+ })
357
+ break
358
+ }
359
+ }
360
+ },
361
+ }
362
+ }
363
+
364
+ // ── Internal: intermediate draining ──────────────────────
365
+
366
+ /**
367
+ * Drain intermediate results from in-flight reasoning.
368
+ * Called at the start of each tick — applies any intermediates
369
+ * that arrived since the last tick.
370
+ *
371
+ * Note: intermediates are already applied via the stream's report()
372
+ * callback at the moment they're pushed. This method processes any
373
+ * that were buffered in the pending entry (e.g., if report() was
374
+ * called between ticks via a background process).
375
+ */
376
+ private _drainIntermediates(
377
+ _currentTick: Tick,
378
+ context: SimulationContext,
379
+ _events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>>,
380
+ commands: StateCommands
381
+ ): void {
382
+ for( const pending of this._pending.values() ){
383
+ if( pending.resolved ) continue
384
+
385
+ // Apply any intermediates that haven't been applied yet
386
+ // (They were already applied via stream.report() inline,
387
+ // but this catches any edge case where intermediates arrived
388
+ // outside the stream callback path)
389
+ for( const entry of pending.intermediates ){
390
+ const intermediateCommands = this.onIntermediateResult(
391
+ entry.step, entry.result, pending.footprint, context
392
+ )
393
+
394
+ if( intermediateCommands ){
395
+ this._mergeCommands( commands, intermediateCommands )
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ // ── Internal: completion collection ──────────────────────
402
+
403
+ /**
404
+ * Collect all completed reasoning, validate against current state,
405
+ * and merge validated commands into the provided collections.
406
+ */
407
+ private async _collectCompleted(
408
+ currentTick: Tick,
409
+ currentState: ReadonlySimulationState,
410
+ context: SimulationContext,
411
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>>,
412
+ commands: StateCommands
413
+ ): Promise<void> {
414
+ const completed: PendingReasoning[] = []
415
+
416
+ for( const pending of this._pending.values() ){
417
+ if( pending.settled && !pending.resolved )
418
+ completed.push( pending )
419
+ }
420
+
421
+ for( const pending of completed ){
422
+ pending.resolved = true
423
+
424
+ let rawOutput: unknown
425
+ try {
426
+ rawOutput = await pending.promise
427
+ }
428
+ catch( error ){
429
+ logger.error(
430
+ `[AsyncEngine] ${this.name} reasoning at tick ${pending.startedAtTick} failed:`,
431
+ error
432
+ )
433
+ this._pending.delete( pending.key )
434
+ continue
435
+ }
436
+
437
+ // Conflict detection on final result
438
+ const report = this._detector.detect( pending.footprint, currentState )
439
+
440
+ if( report.hasConflicts ){
441
+ this.onConflictRejected( pending.footprint, report, context )
442
+
443
+ const strategy = this.chooseStrategy( pending.footprint, context )
444
+
445
+ if( strategy === 'FORCE'){
446
+ // Apply the full reasoning result despite conflicts —
447
+ // intendedCommands is always empty so we must call onReasoningComplete.
448
+ const resultCommands = this.onReasoningComplete( rawOutput, pending.footprint, context )
449
+ this._mergeCommands( commands, resultCommands )
450
+ }
451
+ else {
452
+ const resolution = this._detector.resolve( report, strategy )
453
+
454
+ if( resolution.resolvedCommands )
455
+ this._mergeCommands( commands, resolution.resolvedCommands )
456
+
457
+ if( resolution.shouldRerun && this._config.rerunOnRejection ){
458
+ // Re-run with current state — new footprint, new stream
459
+ const newFootprint = this.readState( currentState, currentTick )
460
+ const newStream = this._createStream( newFootprint, context, events, commands )
461
+ const newPromise = this.reasonAsync( newFootprint, currentState, context, newStream )
462
+
463
+ this._pendingCounter++
464
+ const key = `${this.name}-${this._pendingCounter}-tick${currentTick}-rerun`
465
+
466
+ const rerunEntry: PendingReasoning = {
467
+ key,
468
+ footprint: newFootprint,
469
+ promise: newPromise,
470
+ startedAtTick: currentTick,
471
+ intermediates: [],
472
+ settled: false,
473
+ resolved: false,
474
+ }
475
+
476
+ newPromise.then(
477
+ () => { rerunEntry.settled = true },
478
+ () => { rerunEntry.settled = true }
479
+ )
480
+
481
+ this._pending.set( key, rerunEntry )
482
+ }
483
+ }
484
+
485
+ // Clean up the original pending entry
486
+ this._pending.delete( pending.key )
487
+ }
488
+ else {
489
+ // Clean commit — no conflicts
490
+ const resultCommands = this.onReasoningComplete( rawOutput, pending.footprint, context )
491
+ this._mergeCommands( commands, resultCommands )
492
+
493
+ // Clean up
494
+ this._pending.delete( pending.key )
495
+ }
496
+ }
497
+ }
498
+
499
+ // ── Internal: stale pruning ──────────────────────────────
500
+
501
+ /**
502
+ * Remove pending operations that have exceeded maxPendingTicks.
503
+ */
504
+ private _pruneStale( currentTick: Tick ): void {
505
+ const maxAge = this._config.maxPendingTicks!
506
+
507
+ for( const [ key, pending ] of this._pending ){
508
+ if( currentTick - pending.startedAtTick > maxAge ){
509
+ this._pending.delete( key )
510
+ logger.warn(
511
+ `[AsyncEngine] ${this.name} pruned stale reasoning from tick ${pending.startedAtTick} (current: ${currentTick})`
512
+ )
513
+ }
514
+ }
515
+ }
516
+
517
+ // ── Internal: command merging ────────────────────────────
518
+
519
+ /**
520
+ * Merge validated StateCommands into the collector.
521
+ * Preserves arrays — appends set, delete, and metrics entries.
522
+ */
523
+ private _mergeCommands( target: StateCommands, source: StateCommands ): void {
524
+ if( source.set?.length ){
525
+ target.set ??= []
526
+ target.set.push( ...source.set )
527
+ }
528
+
529
+ if( source.delete?.length ){
530
+ target.delete ??= []
531
+ target.delete.push( ...source.delete )
532
+ }
533
+
534
+ if( source.metrics?.length ){
535
+ target.metrics ??= []
536
+ target.metrics.push( ...source.metrics )
537
+ }
538
+ }
539
+ }