@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,380 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/stem/tracts/transport.controller.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // TransportController — owns the ExternalTransport ↔ tick-loop boundary.
6
+ //
7
+ // - attach() wires the transport's inbound stream into the instance's
8
+ // tick-stamped InboundQueue (NEVER applies inline)
9
+ // - applyInbound() drains the queue and applies every envelope at a fixed
10
+ // point in the tick (called by WillStem before step())
11
+ // - detach() tears the transport down
12
+ //
13
+ // DETERMINISM (see EXTERNAL_TRANSPORT_TODO.md § guardrails):
14
+ // - Inbound arrival (onInbound) only enqueues — application is deferred to the
15
+ // tick so timing is decoupled from the non-deterministic socket.
16
+ // - Result-acks cross into simulation state via confirmExecution(), applied
17
+ // synchronously before step() so the step sees them.
18
+ // - Messages/percepts are fire-and-forget: their async reasoning is recorded
19
+ // and replayed by the LLM layer, not the deterministic core, so the tick
20
+ // must not block on them.
21
+ // ─────────────────────────────────────────────────────────────
22
+
23
+ import { logger } from '#core/logger'
24
+ import { wallClock } from '#core/wall.clock'
25
+ import { getInboundRecorder, getInboundSource } from '#core/inbound.recorder'
26
+ import type { SensoryInput } from '#senses/index'
27
+ import type { effectorInvocation, OutboxMessage } from '#types'
28
+ import type { WillInstance } from '#stem/index'
29
+ import type { InboundEnvelope, OutboundEnvelope } from './transport/types'
30
+ import type { effectorController } from './effector.controller'
31
+ import type { OutboxController } from './outbox.controller'
32
+ import type { SensoryController } from './sensory.controller'
33
+ import { AckReconciler } from './ack.reconciler'
34
+
35
+ /**
36
+ * Cap on un-acked outbound envelopes buffered per Will for reconnect re-emit.
37
+ * A peer that never acks would otherwise grow `_pending` without bound; past the
38
+ * cap the oldest un-acked envelope is dropped (FIFO), matching the outbox TTL's
39
+ * "stale outbound eventually gives up" stance.
40
+ */
41
+ const MAX_PENDING_PER_WILL = 1_000
42
+
43
+ /** Collaborators applyInbound() needs to route each inbound channel. */
44
+ export interface InboundApplyDeps {
45
+ effector: effectorController
46
+ outbox: OutboxController
47
+ sensory: SensoryController
48
+ }
49
+
50
+ export class TransportController {
51
+ /** Monotonic outbound sequence number per Will — for peer ordering / dedup. */
52
+ private readonly _seq = new Map<string, number>()
53
+ /** Unsubscribe for the engine chunk-stream subscription, per Will. */
54
+ private readonly _chunkUnsub = new Map<string, () => void>()
55
+ /** Per-Will ack deduper for the dual (callback + discrete-event) ack paths. */
56
+ private readonly _reconcilers = new Map<string, AckReconciler>()
57
+ /** Activity-listener + status-listener unsubscribes, per Will. */
58
+ private readonly _activityUnsub = new Map<string, () => void>()
59
+ private readonly _statusUnsub = new Map<string, () => void>()
60
+ /**
61
+ * Un-acked outbound envelopes per Will (correlationId → envelope), for
62
+ * messages/invocations only — re-emitted when the transport reconnects.
63
+ * Replies/chunks/activity are ephemeral ('none') and not buffered.
64
+ */
65
+ private readonly _pending = new Map<string, Map<string, OutboundEnvelope>>()
66
+
67
+ private _pendingFor( id: string ): Map<string, OutboundEnvelope> {
68
+ let m = this._pending.get( id )
69
+ if( !m ){ m = new Map(); this._pending.set( id, m ) }
70
+ return m
71
+ }
72
+
73
+ private _nextSeq( id: string ): number {
74
+ const n = ( this._seq.get( id ) ?? 0 ) + 1
75
+ this._seq.set( id, n )
76
+ return n
77
+ }
78
+
79
+ private _reconciler( id: string ): AckReconciler {
80
+ let r = this._reconcilers.get( id )
81
+ if( !r ){ r = new AckReconciler(); this._reconcilers.set( id, r ) }
82
+ return r
83
+ }
84
+
85
+ /**
86
+ * Emit an outbound envelope. When `reconcileAs` is set, the socket.io emit-callback
87
+ * ack is marshaled back onto the tick-stamped InboundQueue as an `ack` envelope —
88
+ * the SAME path discrete ack events take — so applyInbound() reconciles both
89
+ * idempotently. 'none' = fire-and-forget (chunks, replies: no outbox entry to mark).
90
+ */
91
+ private _emit( instance: WillInstance, env: OutboundEnvelope, reconcileAs: 'delivery' | 'result' | 'none' ): void {
92
+ const transport = instance.transport
93
+ if( !transport ) return
94
+
95
+ // Buffer state-affecting outbound until acked, so a reconnect can re-emit it.
96
+ if( reconcileAs !== 'none' ){
97
+ const pending = this._pendingFor( instance.config.id )
98
+ pending.set( env.correlationId, env )
99
+ // Bound the buffer — drop the oldest un-acked envelope past the cap (FIFO).
100
+ while( pending.size > MAX_PENDING_PER_WILL ){
101
+ const oldest = pending.keys().next().value
102
+ if( oldest === undefined ) break
103
+ pending.delete( oldest )
104
+ }
105
+ }
106
+
107
+ const pending = transport.emit( env )
108
+ if( reconcileAs === 'none' ){ void pending; return }
109
+
110
+ void pending.then( ack => {
111
+ if( !ack.acked ) return
112
+ const result = reconcileAs === 'result' ? this._asResult( ack.payload ) : null
113
+ instance.inbound.enqueue(
114
+ result
115
+ ? { channel: 'ack', ackKind: 'result', result, willId: instance.config.id, correlationId: env.correlationId, seq: 0, wallTime: wallClock() }
116
+ : { channel: 'ack', ackKind: 'delivery', delivered: true, willId: instance.config.id, correlationId: env.correlationId, seq: 0, wallTime: wallClock() }
117
+ )
118
+ } ).catch( () => {} )
119
+ }
120
+
121
+ /** Coerce an emit-callback ack payload into a confirmExecution result, or null. */
122
+ private _asResult( payload: unknown ): { success: boolean; description: string; metrics?: Record<string, number> } | null {
123
+ if( payload && typeof payload === 'object' && typeof ( payload as { success?: unknown } ).success === 'boolean' ){
124
+ const p = payload as { success: boolean; description?: unknown; metrics?: Record<string, number> }
125
+ return { success: p.success, description: String( p.description ?? '' ), ...( p.metrics ? { metrics: p.metrics } : {} ) }
126
+ }
127
+ return null
128
+ }
129
+
130
+ /**
131
+ * Emit a SessionLogger entry as a `session_log` observability envelope.
132
+ * Fire-and-forget — the consumer's transport owns persistence. No-op without a
133
+ * transport (dev file logging still happens inside SessionLogger).
134
+ */
135
+ emitSessionLog( instance: WillInstance, entry: Record<string, unknown> ): void {
136
+ if( !instance.transport ) return
137
+ const seq = this._nextSeq( instance.config.id )
138
+ this._emit( instance, {
139
+ channel: 'session_log',
140
+ willId: instance.config.id,
141
+ correlationId: `session_log-${seq}`,
142
+ seq,
143
+ wallTime: wallClock(),
144
+ entry,
145
+ }, 'none' )
146
+ }
147
+
148
+ /**
149
+ * Emit an attributed token/cost ledger record as a `token_report` envelope so
150
+ * the consumer can meter/re-bill from the stream. Fire-and-forget.
151
+ */
152
+ emitTokenReport( instance: WillInstance, report: Record<string, unknown> ): void {
153
+ if( !instance.transport ) return
154
+ const seq = this._nextSeq( instance.config.id )
155
+ this._emit( instance, {
156
+ channel: 'token_report',
157
+ willId: instance.config.id,
158
+ correlationId: `token_report-${seq}`,
159
+ seq,
160
+ wallTime: wallClock(),
161
+ report,
162
+ }, 'none' )
163
+ }
164
+
165
+ /**
166
+ * Wire the transport's inbound stream into the instance's tick-stamped queue,
167
+ * and the engines' outbound fast-paths (reply) into transport emits.
168
+ */
169
+ attach( instance: WillInstance ): void {
170
+ const transport = instance.transport
171
+ if( !transport ) return
172
+
173
+ instance._transportUnsub = transport.onInbound( env => instance.inbound.enqueue( env ) )
174
+
175
+ // Outbound reply fast-path (2.1): emit the instant the facet decides — off-tick.
176
+ // 'none': the reply bubbles aren't in the outbox when a transport is present
177
+ // (AuditionEngine skips the outbox push), so there's no delivery-ack to apply.
178
+ instance.cognition.auditionEngine.attachReplyCallback( ( entityId, threadId, bubbles ) => {
179
+ this._emit( instance, {
180
+ channel: 'reply',
181
+ willId: instance.config.id,
182
+ correlationId: `reply-${entityId}-${wallClock()}`,
183
+ seq: this._nextSeq( instance.config.id ),
184
+ wallTime: wallClock(),
185
+ entityId,
186
+ threadId,
187
+ bubbles,
188
+ }, 'none' )
189
+ } )
190
+
191
+ // Outbound chunk fast-path (2.2): stream each filtered reply token off-tick.
192
+ // Multi-subscriber chunk callback — coexists with the SSE fan-out.
193
+ const chunkUnsub = instance.cognition.auditionEngine.addChunkCallback( ( entityId, threadId, content ) => {
194
+ this._emit( instance, {
195
+ channel: 'chunk',
196
+ willId: instance.config.id,
197
+ correlationId: `chunk-${entityId}`,
198
+ seq: this._nextSeq( instance.config.id ),
199
+ wallTime: wallClock(),
200
+ entityId,
201
+ threadId,
202
+ content,
203
+ }, 'none' )
204
+ } )
205
+ this._chunkUnsub.set( instance.config.id, chunkUnsub )
206
+
207
+ // Outbound activity projection (2.5): forward all plan/activity events
208
+ // ('*' wildcard = no entity filter). Observability only — fire-and-forget.
209
+ const activityUnsub = instance.cognition.planningEngine.addActivityListener( '*', event => {
210
+ this._emit( instance, {
211
+ channel: 'activity',
212
+ willId: instance.config.id,
213
+ correlationId: `activity-${event.planId}-${event.type}-${wallClock()}`,
214
+ seq: this._nextSeq( instance.config.id ),
215
+ wallTime: wallClock(),
216
+ entityId: event.requestingEntityId ?? '',
217
+ eventType: event.type,
218
+ payload: { ...event },
219
+ }, 'none' )
220
+ } )
221
+ this._activityUnsub.set( instance.config.id, activityUnsub )
222
+
223
+ // Reconnect: re-emit un-acked messages/invocations when the link returns.
224
+ const statusUnsub = transport.onStatus( status => {
225
+ if( status === 'connected' ) this._reemitPending( instance )
226
+ } )
227
+ this._statusUnsub.set( instance.config.id, statusUnsub )
228
+
229
+ logger.info(`[transport] attached inbound + outbound streams for ${instance.config.id}`)
230
+ }
231
+
232
+ /** Re-emit un-acked outbound envelopes after the transport reconnects. */
233
+ private _reemitPending( instance: WillInstance ): void {
234
+ const pending = this._pending.get( instance.config.id )
235
+ if( !pending || pending.size === 0 ) return
236
+
237
+ logger.info(`[transport] reconnect — re-emitting ${pending.size} un-acked envelope(s) for ${instance.config.id}`)
238
+ for( const env of [ ...pending.values() ] )
239
+ this._emit( instance, env, env.channel === 'effector_invocation' ? 'result' : 'delivery' )
240
+ }
241
+
242
+ /**
243
+ * Emit drained external effector invocations over the transport (2.4). Called
244
+ * by the tick loop after `pendingEffectorInvocations` is spliced. The peer
245
+ * executes each and returns a result-ack (correlationId = decisionRecordId),
246
+ * which inbound dispatch routes to `confirmExecution`.
247
+ */
248
+ emitInvocations( instance: WillInstance, invocations: effectorInvocation[] ): void {
249
+ if( !instance.transport || invocations.length === 0 ) return
250
+
251
+ for( const invocation of invocations )
252
+ // 'result': the peer's ack (callback or discrete event) carries the execution
253
+ // result → reconciled once → confirmExecution.
254
+ this._emit( instance, {
255
+ channel: 'effector_invocation',
256
+ willId: instance.config.id,
257
+ correlationId: invocation.decisionRecordId,
258
+ seq: this._nextSeq( instance.config.id ),
259
+ wallTime: wallClock(),
260
+ invocation,
261
+ }, 'result' )
262
+ }
263
+
264
+ /**
265
+ * Emit drained outbox messages over the transport (2.3). Called by the tick loop
266
+ * after the outbox is spliced. Facet replies are NOT here — they go via the 2.1
267
+ * fast-path and skip the outbox when a transport is present, so this carries only
268
+ * master/action messages (talk/broadcast/gesture). correlationId = message id, so
269
+ * a delivery-ack marks the right outbox row via confirmDelivery.
270
+ */
271
+ emitOutbox( instance: WillInstance, messages: OutboxMessage[] ): void {
272
+ if( !instance.transport || messages.length === 0 ) return
273
+
274
+ for( const message of messages )
275
+ this._emit( instance, {
276
+ channel: 'message',
277
+ willId: instance.config.id,
278
+ correlationId: message.id,
279
+ seq: this._nextSeq( instance.config.id ),
280
+ wallTime: wallClock(),
281
+ message,
282
+ }, 'delivery' )
283
+ }
284
+
285
+ /** Tear down the transport and discard any un-applied inbound. */
286
+ detach( instance: WillInstance ): void {
287
+ instance._transportUnsub?.()
288
+ instance._transportUnsub = null
289
+ this._chunkUnsub.get( instance.config.id )?.()
290
+ this._chunkUnsub.delete( instance.config.id )
291
+ this._activityUnsub.get( instance.config.id )?.()
292
+ this._activityUnsub.delete( instance.config.id )
293
+ this._statusUnsub.get( instance.config.id )?.()
294
+ this._statusUnsub.delete( instance.config.id )
295
+ instance.transport?.close()
296
+ // Null the handle so any engine callback that still holds this instance (e.g.
297
+ // the AuditionEngine single-slot reply callback, which has no unsubscribe) sees
298
+ // no transport and its _emit() becomes a no-op instead of emitting on a closed link.
299
+ instance.transport = null
300
+ instance.inbound.clear()
301
+ this._seq.delete( instance.config.id )
302
+ this._reconcilers.delete( instance.config.id )
303
+ this._pending.delete( instance.config.id )
304
+ }
305
+
306
+ /**
307
+ * Drain the inbound queue and apply every envelope deterministically at `tick`.
308
+ * Called once per tick BEFORE simulation.step().
309
+ */
310
+ applyInbound( instance: WillInstance, tick: number, deps: InboundApplyDeps ): void {
311
+ const willId = instance.config.id
312
+ const source = getInboundSource( willId )
313
+
314
+ // Replay re-feed (1.3): when a source is registered, pull the recorded
315
+ // envelopes for this tick instead of draining the live socket queue — the
316
+ // transport analog of the LLM completion re-feed. A live run drains the queue.
317
+ const envelopes: InboundEnvelope[] = source
318
+ ? ( source.envelopesAt( tick ) as InboundEnvelope[] )
319
+ : instance.inbound.drain( tick ).map( s => s.envelope )
320
+
321
+ if( envelopes.length === 0 ) return
322
+
323
+ // Record the batch (live runs only — a source means we are replaying). The
324
+ // transport is a non-deterministic input; capturing each envelope at its
325
+ // applied tick lets a deterministic re-execution re-inject them.
326
+ if( !source ){
327
+ const recorder = getInboundRecorder( willId )
328
+ if( recorder )
329
+ for( const envelope of envelopes )
330
+ recorder.recordInbound({ tick, willId, envelope, timestamp: wallClock() })
331
+ }
332
+
333
+ for( const envelope of envelopes ){
334
+ try { this._dispatch( instance, envelope, deps ) }
335
+ catch( err ){ logger.error(`[transport] inbound dispatch failed (${envelope.channel}):`, err ) }
336
+ }
337
+ }
338
+
339
+ private _dispatch( instance: WillInstance, env: InboundEnvelope, deps: InboundApplyDeps ): void {
340
+ switch( env.channel ){
341
+ case 'inbound_message': {
342
+ // Fire-and-forget: AuditionEngine.ingest() serializes per-entity
343
+ // internally; the tick must not block on the LLM reply.
344
+ const input: SensoryInput = env.kind === 'voice'
345
+ ? { kind: 'voice', entityId: env.entityId, threadId: env.threadId, transcription: env.content }
346
+ : {
347
+ kind: 'text',
348
+ entityId: env.entityId,
349
+ threadId: env.threadId,
350
+ content: env.content,
351
+ ...( env.speakerName ? { speakerName: env.speakerName } : {} ),
352
+ }
353
+ void instance.cognition.auditionEngine.ingest( input )
354
+ break
355
+ }
356
+
357
+ case 'inbound_percept':
358
+ // Materialize as an external event entity; perceptual engines pick it up next tick.
359
+ deps.sensory.injectEvent( instance, { type: `senses.${env.domain}`, payload: env.payload } )
360
+ break
361
+
362
+ case 'ack': {
363
+ // Idempotent across the dual paths: the emit-callback ack and the discrete
364
+ // ack event share a correlationId — apply only the first.
365
+ if( !this._reconciler( instance.config.id ).shouldApply( env.correlationId ) ) break
366
+
367
+ // Acked — drop it from the reconnect re-emit buffer.
368
+ this._pending.get( instance.config.id )?.delete( env.correlationId )
369
+
370
+ if( env.ackKind === 'result' && env.result )
371
+ // Reafference — crosses into deterministic state via confirmExecution().
372
+ deps.effector.confirmExecution( instance, env.correlationId, env.result )
373
+ else
374
+ // Delivery receipt — outbox status update.
375
+ deps.outbox.confirmDelivery( instance, env.correlationId, env.delivered ?? true )
376
+ break
377
+ }
378
+ }
379
+ }
380
+ }
package/src/types.ts ADDED
@@ -0,0 +1,116 @@
1
+ import type { EngineRegistry } from '#cognition/index'
2
+ import type { ReadonlySimulationState, StateCommands } from '#core/types'
3
+ import type { OutboxWriter } from '#stem/tracts/outbox.writer'
4
+ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
5
+ import type { AccessGrants } from '#agency/access.grants'
6
+
7
+ export interface Cognition extends EngineRegistry {
8
+ /** Shared outbox producer (row shape + reply audit); see #stem/tracts/outbox.writer. */
9
+ outboxWriter: OutboxWriter
10
+ /** Agency pipeline competence layer (learned schemas + skills) — see #agency. */
11
+ schemaRepertoire: SchemaRepertoire
12
+ /** Permission / sense-gate authority (replaces effectorRegistry's grant role). */
13
+ accessGrants: AccessGrants
14
+ }
15
+
16
+ /**
17
+ * Emitted when Will decides to use an effector that has no internal handler.
18
+ * The host system receives this via SSE and is responsible for execution.
19
+ * When done, the host calls injectEvent() with the result.
20
+ */
21
+ export interface effectorInvocation {
22
+ id: string
23
+ /** Correlation handle — the awaiting `agency.intent` id. Echo it when POSTing to
24
+ * `POST /v1/wills/:id/effectors/invoked/ack`; the Will reconciles the result onto
25
+ * that intent. (Field name kept for wire-contract stability; no longer a
26
+ * `decision.record` id since the agency cutover.) */
27
+ decisionRecordId: string
28
+ effectorName: string
29
+ parameters: Record<string, unknown>
30
+ targetEntityId: string | undefined
31
+ reasoning: string
32
+ tick: number
33
+ timestamp: number
34
+ }
35
+
36
+ export interface WorldEntity {
37
+ id: string
38
+ type: string
39
+ name: string
40
+ description: string
41
+ /** What effectors can be used on this entity */
42
+ affordances: string[] // Effector names
43
+ /** Current state */
44
+ state: Record<string, unknown>
45
+ /** Is this entity currently reachable/interactable */
46
+ reachable: boolean
47
+ }
48
+
49
+ export interface ActionRequest {
50
+ /** The effector being invoked */
51
+ effector: string
52
+ /** Parameters for the effector */
53
+ parameters: Record<string, unknown>
54
+ /** Target entity (if any) */
55
+ targetEntityId?: string
56
+ /** The Will's reasoning for this action */
57
+ reasoning: string
58
+ /** Expected outcome description */
59
+ expectedOutcome: string
60
+ /** The tick when this was decided */
61
+ decidedAt: number
62
+ }
63
+
64
+ export interface ActionResult {
65
+ /** Whether the action succeeded */
66
+ success: boolean
67
+ /** Human-readable description of what happened */
68
+ description: string
69
+ /** State changes that occurred as a result */
70
+ commands: StateCommands
71
+ /** New entities created by this action */
72
+ createdEntities?: WorldEntity[]
73
+ /** Feedback for the Will to learn from */
74
+ feedback: {
75
+ outcomeQuality: number // 0-1: how good was the outcome
76
+ surprise: number // 0-1: how unexpected was the outcome
77
+ lessons: string[] // What can be learned
78
+ }
79
+ }
80
+
81
+ export interface WorldInterface {
82
+ /** Get all entities the Will can perceive right now */
83
+ getPerceptibleEntities( state: ReadonlySimulationState ): WorldEntity[]
84
+
85
+ /** Get entities the Will can interact with */
86
+ getInteractableEntities( state: ReadonlySimulationState ): WorldEntity[]
87
+
88
+ /** Returns true if this world can handle the given effector name. */
89
+ canHandle( effectorName: string ): boolean
90
+
91
+ /** Execute a Will's action in the world */
92
+ executeAction( request: ActionRequest, state: ReadonlySimulationState ): Promise<ActionResult>
93
+ }
94
+
95
+ export interface OutboxMessage {
96
+ id: string
97
+ targetEntityId: string
98
+ targetEntityName?: string
99
+ content: string
100
+ effectorName: 'talk' | 'text' | 'gesture' | 'broadcast'
101
+ gestureType?: string // for gesture effector
102
+ createdAtTick: number
103
+ createdAt: number
104
+ /** If this message is a reply, the incoming message ID it is replying to. */
105
+ replyToMessageId?: string
106
+ /**
107
+ * Conversation thread this message belongs to.
108
+ * Set by AuditionEngine/OutboxWriter.enqueueReply() — ties all bubbles from one exchange
109
+ * to the same thread so the SSE consumer can group them into a single reply_complete.
110
+ */
111
+ threadId?: string
112
+ /** Delivery lifecycle: 'pending' until the SSE consumer confirms receipt. */
113
+ deliveryStatus: 'pending' | 'delivered' | 'failed'
114
+ /** Token usage from the outbound() composition call — used for exchange billing. */
115
+ usage?: { promptTokens: number; completionTokens: number }
116
+ }