@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,532 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/motor.schema.executor.ts — enaction & the efference copy
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The MotorSchemaExecutor consumes the committed `agency.intent`, predicts its
6
+ // own consequences (the efference copy), enacts it, and writes `agency.outcome`
7
+ // — the reafference record whose prediction error becomes the teaching signal in
8
+ // Phase 4. No LLM.
9
+ //
10
+ // Three things happen here that the old catalog executor could not:
11
+ // 1. Every enaction emits an efference copy (predicted reward/valence) BEFORE
12
+ // the result is known, so an async outcome can still be compared on arrival.
13
+ // 2. Parameters are already bound (by the synthesizer), so there is no
14
+ // "empty args" failure mode.
15
+ // 3. A COMPOSITE schema actually executes — it expands into ordered primitive
16
+ // sub-intents that run one per tick, accumulate, and finalize into the
17
+ // composite's own outcome. Created skills are runnable, not inert records.
18
+ //
19
+ // Intent lifecycle (the selector commits 'selected' and then defers while busy):
20
+ // selected ─┬─ primitive sync → outcome written, intent deleted
21
+ // ├─ composite → 'expanding' + first sub-intent ('selected')
22
+ // └─ communicate/external→ 'awaiting' (reconciled in Phase 4)
23
+ // ─────────────────────────────────────────────────────────────
24
+
25
+ import { logger } from '#core/logger'
26
+ import type {
27
+ Duration, Tick, SimulationContext,
28
+ ReadonlySimulationState, StateCommands, EntityInput,
29
+ } from '#core/types'
30
+ import type { CognitiveBus } from '#cognition/bus'
31
+ import type { CognitiveEngine, EngineResult } from '#cognition/types'
32
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
33
+ import type { ProactiveCommunicator } from '#agency/proactive.communicator'
34
+ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
35
+ import type { SessionLogger } from '#stem/tracts/session.logger'
36
+ import type { MotorSchema, EfferenceCopy } from '#agency/types'
37
+ import type { AccessGrants } from '#agency/access.grants'
38
+ import type { ActionRequest, ActionResult } from '#types'
39
+ import { INNATE_SCHEMAS } from '#agency/schemas/innate'
40
+ import { enact, type Enaction } from '#agency/execution.primitives'
41
+
42
+ /** Ticks an async (communicate/external) intent may stay 'awaiting' before it is abandoned. */
43
+ const AWAIT_TIMEOUT = 15
44
+
45
+ /** Map a communicate schema to the ProactiveCommunicator effector + the permission name. */
46
+ const COMM_SCHEMA_TO_EFFECTOR: Record<string, string> = {
47
+ 'reach-out': 'text', talk: 'talk', text: 'text', gesture: 'gesture', broadcast: 'broadcast',
48
+ }
49
+
50
+ interface Intent {
51
+ id: string
52
+ schema: string
53
+ affordanceId?: string
54
+ targetEntityId?: string
55
+ parameters: Record<string, unknown>
56
+ expectedReward: number
57
+ expectedValence: number
58
+ parentIntentId?: string
59
+ stepIndex?: number
60
+ /**
61
+ * Provenance: set when this intent was committed from a plan's frontier-step prior
62
+ * that won the competition (NOT a dispatched step — there is no dispatch). Threaded
63
+ * out on `action.outcome` so the PlanningEngine advances the plan that biased it.
64
+ */
65
+ planId?: string
66
+ planStepId?: string
67
+ }
68
+
69
+ /** Authors the words for a self-initiated communicate the agency selected (no inbound triggered it). */
70
+ export interface OutreachAuthor {
71
+ authorOutreach( entityId: string, entityName: string, gist?: string ): Promise<string[]>
72
+ }
73
+
74
+ export class MotorSchemaExecutor implements CognitiveEngine {
75
+ readonly name = 'motor-schema-executor'
76
+
77
+ private _schemas = new Map<string, MotorSchema>()
78
+ private _repertoire: SchemaRepertoire | null = null
79
+ private _comms: ProactiveCommunicator | null = null
80
+ private _author: OutreachAuthor | null = null
81
+ private _grants: AccessGrants | null = null
82
+ private _bus: CognitiveBus | null = null
83
+
84
+ constructor( schemas: MotorSchema[] = INNATE_SCHEMAS ){
85
+ for( const s of schemas ) this._schemas.set( s.id, s )
86
+ }
87
+
88
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
89
+ /** Resolve schemas (incl. learned composites) from the live repertoire first. */
90
+ attachRepertoire( repertoire: SchemaRepertoire ): void { this._repertoire = repertoire }
91
+ /** Inject the communicate-enaction handler (owned here; the only caller). */
92
+ attachProactiveCommunicator( c: ProactiveCommunicator ): void { this._comms = c }
93
+ /** Inject the outreach author — words for a self-initiated communicate are authored on selection. */
94
+ attachOutreachAuthor( a: OutreachAuthor ): void { this._author = a }
95
+ /** Forward the session logger to the owned ProactiveCommunicator (it audits outbound). */
96
+ attachSessionLogger( logger: SessionLogger | null ): void { this._comms?.attachSessionLogger( logger ) }
97
+ /** Inject the permission gate so outbound communication is grant-checked. */
98
+ attachGrants( g: AccessGrants ): void { this._grants = g }
99
+ registerSchema( schema: MotorSchema ): void { this._schemas.set( schema.id, schema ) }
100
+
101
+ /** Repertoire-first schema resolution, falling back to the local seed set. */
102
+ private _resolve( id: string ): MotorSchema | undefined {
103
+ return this._repertoire?.getSchema( id ) ?? this._schemas.get( id )
104
+ }
105
+
106
+ publishes(): CognitiveEventSchema[] {
107
+ return [
108
+ { type: 'agency.enacted', version: 1, validate: () => null },
109
+ { type: 'agency.invocation', version: 1, validate: () => null },
110
+ { type: 'agency.communicate', version: 1, validate: () => null },
111
+ ]
112
+ }
113
+ subscribes(): string[] { return [] }
114
+
115
+ /**
116
+ * The executor is plan-agnostic. A plan does NOT dispatch steps here — it biases
117
+ * the affordance competition (see PLANNING_AS_PRIOR_TODO.md), so a plan-driven
118
+ * action reaches the executor as an ordinary committed `agency.intent` the
119
+ * selector won. That intent already carries planId/stepId provenance (stamped by
120
+ * the selector from the winning affordance); `_emitActionOutcome` threads it back
121
+ * out, which is how the PlanningEngine advances. Nothing plan-specific here.
122
+ */
123
+ onCognitiveEvent(): void { /* pull model — reads committed intents from frozen state */ }
124
+
125
+ snapshot(): Record<string, unknown> { return { schemas: this._schemas.size } }
126
+
127
+ async react(
128
+ _delta: Duration,
129
+ tick: Tick,
130
+ state: ReadonlySimulationState,
131
+ _context: SimulationContext,
132
+ ): Promise<EngineResult> {
133
+ const set: EntityInput[] = []
134
+ const del: string[] = []
135
+ const metrics: Array<[ string, number ]> = []
136
+
137
+ const energy = state.metrics.get( 'energy.level' ) ?? 100
138
+ const stress = state.metrics.get( 'stress.load' ) ?? 0
139
+
140
+ // ── Timeout stranded async intents ───────────────────────────
141
+ // An 'awaiting' intent whose host/delivery never returned would block the
142
+ // serial Will forever. After AWAIT_TIMEOUT ticks, abandon it as a failed
143
+ // outcome (which also teaches reafference the action is unreliable here).
144
+ for( const [ id, e ] of state.entities ){
145
+ if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting' ) continue
146
+ const dispatchedAt = num( e.metadata?.['dispatchedAt'], tick )
147
+ if( tick - dispatchedAt < AWAIT_TIMEOUT ) continue
148
+
149
+ const intent = readIntent( id, e.metadata )
150
+ const predicted: EfferenceCopy = {
151
+ expectedReward: num( e.metadata?.['predictedReward'], intent.expectedReward ),
152
+ expectedValence: num( e.metadata?.['predictedValence'], intent.expectedValence ),
153
+ }
154
+ const timedOut: Enaction = {
155
+ mode: 'sync', success: false, outcomeQuality: 0, valence: -0.1,
156
+ description: `"${ intent.schema }" timed out — the world never answered.`,
157
+ }
158
+ set.push( outcomeEntity( tick, intent, timedOut, predicted ) )
159
+ del.push( id )
160
+ this._emitEnacted( intent, timedOut, predicted, tick )
161
+ if( intent.planId && intent.planStepId )
162
+ this._emitActionOutcome( intent, false, 0, 1, tick )
163
+ logger.info( `[motor] ⏱ "${ intent.schema }" timed out after ${ tick - dispatchedAt } ticks` )
164
+ }
165
+
166
+ // Process committed intents in a stable order (determinism / replay).
167
+ const selected = [ ...state.entities.entries() ]
168
+ .filter( ( [ , e ] ) => e.type === 'agency.intent' && str( e.metadata?.['status'] ) === 'selected' )
169
+ .sort( ( a, b ) => ( a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0 ) )
170
+
171
+ let enactedCount = 0
172
+
173
+ for( const [ id, e ] of selected ){
174
+ const intent = readIntent( id, e.metadata )
175
+ const schema = this._resolve( intent.schema )
176
+
177
+ // ── composite → expand into ordered primitive sub-intents ──
178
+ if( schema?.kind === 'composite' && schema.composedOf && schema.composedOf.length > 0 ){
179
+ this._expand( id, e.metadata, schema.composedOf, intent, tick, set )
180
+ continue
181
+ }
182
+
183
+ const predicted: EfferenceCopy = {
184
+ expectedReward: intent.expectedReward,
185
+ expectedValence: intent.expectedValence,
186
+ }
187
+
188
+ const enaction: Enaction = schema
189
+ ? enact({ schema, parameters: intent.parameters, targetEntityId: intent.targetEntityId, energy, stress })
190
+ : { mode: 'external', success: true, outcomeQuality: 0.5, valence: 0,
191
+ description: `Unknown schema "${ intent.schema }" routed to the host.` }
192
+
193
+ if( enaction.mode === 'sync' ){
194
+ set.push( outcomeEntity( tick, intent, enaction, predicted ) )
195
+ del.push( id )
196
+ if( enaction.metricDeltas ) metrics.push( ...enaction.metricDeltas )
197
+ enactedCount++
198
+ this._emitEnacted( intent, enaction, predicted, tick )
199
+ this._emitActionOutcome( intent, enaction.success, enaction.outcomeQuality,
200
+ clamp01( Math.abs( predicted.expectedReward - enaction.outcomeQuality ) ), tick )
201
+
202
+ // If this was a macro step, advance (or finalize) its parent.
203
+ if( intent.parentIntentId )
204
+ this._advance( intent, enaction, state, tick, set, del )
205
+ }
206
+ else {
207
+ // Communicate: deliver through the shared ProactiveCommunicator (Phase 5b).
208
+ // Returns false when it can't (no delivery layer, no content to send) → fall
209
+ // through to the async 'awaiting' hold (host delivers, or it times out).
210
+ const delivered = enaction.mode === 'communicate'
211
+ && await this._deliver( id, intent, predicted, state, tick, set, del, metrics )
212
+
213
+ if( delivered ){
214
+ enactedCount++
215
+ } else {
216
+ // Async hold: persist the efference copy so reconciliation can score it.
217
+ set.push({
218
+ id, type: 'agency.intent',
219
+ metadata: {
220
+ ...( e.metadata as Record<string, unknown> ),
221
+ status: 'awaiting',
222
+ dispatchedAt: tick,
223
+ predictedReward: predicted.expectedReward,
224
+ predictedValence: predicted.expectedValence,
225
+ },
226
+ })
227
+ this._emitDispatch( intent, enaction.mode, tick )
228
+ metrics.push([ enaction.mode === 'communicate'
229
+ ? 'agency.communicate.dispatched'
230
+ : 'agency.invocation.dispatched', 1 ])
231
+ }
232
+ }
233
+ }
234
+
235
+ metrics.push([ 'agency.executor.enacted', enactedCount ])
236
+ return { commands: { set, delete: del, metrics } }
237
+ }
238
+
239
+ // ── composite machinery ──────────────────────────────────────
240
+
241
+ private _expand(
242
+ parentId: string,
243
+ parentMeta: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined,
244
+ steps: string[],
245
+ intent: Intent,
246
+ tick: Tick,
247
+ set: EntityInput[],
248
+ ): void {
249
+ const meta = ( parentMeta ?? {} ) as Record<string, unknown>
250
+ // Parent stays open as 'expanding' and tracks progress.
251
+ set.push({
252
+ id: parentId, type: 'agency.intent',
253
+ metadata: { ...meta, status: 'expanding', steps, cursor: 0, accumQuality: 0, accumValence: 0, completed: 0 },
254
+ })
255
+ set.push( this._subIntent( parentId, intent.targetEntityId, intent.parameters, steps[0]!, 0, tick ) )
256
+ logger.info( `[motor] composite "${ intent.schema }" → ${ steps.length } steps` )
257
+ }
258
+
259
+ private _advance(
260
+ sub: Intent,
261
+ enaction: Enaction,
262
+ state: ReadonlySimulationState,
263
+ tick: Tick,
264
+ set: EntityInput[],
265
+ del: string[],
266
+ ): void {
267
+ const parentId = sub.parentIntentId!
268
+ const parent = state.entities.get( parentId )
269
+ if( !parent ){ return } // parent already finalized / gone
270
+
271
+ const pm = ( parent.metadata ?? {} ) as Record<string, unknown>
272
+ const steps = Array.isArray( pm['steps'] ) ? ( pm['steps'] as string[] ) : []
273
+ const accumQuality = num( pm['accumQuality'], 0 ) + enaction.outcomeQuality
274
+ const accumValence = num( pm['accumValence'], 0 ) + enaction.valence
275
+ const completed = num( pm['completed'], 0 ) + 1
276
+ const nextK = ( sub.stepIndex ?? 0 ) + 1
277
+
278
+ if( nextK < steps.length ){
279
+ set.push({ id: parentId, type: 'agency.intent',
280
+ metadata: { ...pm, cursor: nextK, accumQuality, accumValence, completed } })
281
+ set.push( this._subIntent(
282
+ parentId,
283
+ str( pm['targetEntityId'] ),
284
+ ( pm['parameters'] as Record<string, unknown> ) ?? {},
285
+ steps[nextK]!, nextK, tick,
286
+ ) )
287
+ return
288
+ }
289
+
290
+ // All steps done → finalize the composite's own outcome.
291
+ const avgQuality = completed > 0 ? accumQuality / completed : 0
292
+ const avgValence = completed > 0 ? accumValence / completed : 0
293
+ const compIntent: Intent = {
294
+ id: parentId,
295
+ schema: str( pm['schema'] ) ?? 'composite',
296
+ affordanceId: str( pm['affordanceId'] ),
297
+ targetEntityId: str( pm['targetEntityId'] ),
298
+ parameters: ( pm['parameters'] as Record<string, unknown> ) ?? {},
299
+ expectedReward: num( pm['expectedReward'], 0.5 ),
300
+ expectedValence: num( pm['expectedValence'], 0 ),
301
+ }
302
+ const compEnaction: Enaction = {
303
+ mode: 'sync', success: true, outcomeQuality: avgQuality, valence: avgValence,
304
+ description: `Composite "${ compIntent.schema }" completed — ${ completed } steps.`,
305
+ }
306
+ set.push( outcomeEntity( tick, compIntent, compEnaction,
307
+ { expectedReward: compIntent.expectedReward, expectedValence: compIntent.expectedValence } ) )
308
+ del.push( parentId )
309
+ this._emitEnacted( compIntent, compEnaction,
310
+ { expectedReward: compIntent.expectedReward, expectedValence: compIntent.expectedValence }, tick )
311
+ this._emitActionOutcome( compIntent, true, avgQuality,
312
+ clamp01( Math.abs( compIntent.expectedReward - avgQuality ) ), tick )
313
+ }
314
+
315
+ private _subIntent(
316
+ parentId: string,
317
+ targetEntityId: string | undefined,
318
+ parameters: Record<string, unknown>,
319
+ subSchemaId: string,
320
+ k: number,
321
+ tick: Tick,
322
+ ): EntityInput {
323
+ const sub = this._resolve( subSchemaId )
324
+ const baseV = sub?.baseValence ?? 0
325
+ return {
326
+ id: `${ parentId }-sub-${ k }`,
327
+ type: 'agency.intent',
328
+ metadata: {
329
+ schema: subSchemaId,
330
+ parentIntentId: parentId,
331
+ stepIndex: k,
332
+ status: 'selected',
333
+ targetEntityId,
334
+ parameters,
335
+ expectedReward: clamp01( ( baseV + 1 ) / 2 ),
336
+ expectedValence: baseV,
337
+ tick,
338
+ },
339
+ }
340
+ }
341
+
342
+ // ── communication delivery (Phase 5b) ────────────────────────
343
+
344
+ /**
345
+ * Deliver a communicate intent through the shared ProactiveCommunicator, gated by
346
+ * AccessGrants. Returns true when it handled the intent (delivered + outcome written),
347
+ * false when it cannot (no delivery layer / no authored content) so the caller holds
348
+ * the intent 'awaiting'. Message *content* is authored upstream (the deliberation /
349
+ * conversation facet); this is the single enaction → delivery path.
350
+ */
351
+ private async _deliver(
352
+ id: string, intent: Intent, predicted: EfferenceCopy,
353
+ state: ReadonlySimulationState, tick: Tick,
354
+ set: EntityInput[], del: string[], metrics: Array<[ string, number ]>,
355
+ ): Promise<boolean> {
356
+ if( !this._comms ) return false
357
+ const effector = COMM_SCHEMA_TO_EFFECTOR[ intent.schema ] ?? intent.schema
358
+
359
+ // Permission gate — denial RESOLVES as a failed outcome (not an awaiting hold).
360
+ if( this._grants && !this._grants.isAllowed( effector ) ){
361
+ const blocked: Enaction = {
362
+ mode: 'communicate',
363
+ success: false,
364
+ outcomeQuality: 0,
365
+ valence: -0.1,
366
+ description: `"${ effector }" is not permitted for this Will.`
367
+ }
368
+ set.push( outcomeEntity( tick, intent, blocked, predicted ) )
369
+ del.push( id )
370
+ this._emitEnacted( intent, blocked, predicted, tick )
371
+ metrics.push([ 'agency.communicate.blocked', 1 ])
372
+ return true
373
+ }
374
+
375
+ // Content authored upstream when present (host / host-facet); otherwise, for a
376
+ // self-initiated communicate the agency just selected, author it now via a facet
377
+ // — the unified conversation voice, since no inbound triggered it. Empty ⇒ await.
378
+ const authored = str( intent.parameters['content'] ) ?? firstMessage( intent.parameters['messages'] )
379
+ let bubbles: string[] = authored ? [ authored ] : []
380
+ if( bubbles.length === 0 && this._author ){
381
+ const name = str( intent.parameters['targetEntityName'] ) ?? intent.targetEntityId ?? 'them'
382
+ try { bubbles = await this._author.authorOutreach( intent.targetEntityId ?? '', name, str( intent.parameters['gist'] ) ) }
383
+ catch( err ){ logger.warn( `[motor] outreach authoring failed: ${ errMsg( err ) }` ) }
384
+ }
385
+ if( bubbles.length === 0 ) return false // nothing authored to send → await
386
+
387
+ const request: ActionRequest = {
388
+ effector,
389
+ parameters: { ...intent.parameters, messages: bubbles },
390
+ targetEntityId: intent.targetEntityId,
391
+ reasoning: '',
392
+ expectedOutcome: '',
393
+ decidedAt: tick,
394
+ }
395
+ let result: ActionResult
396
+ try { result = await this._comms.executeAction( request, state ) }
397
+ catch( err ){ logger.warn( `[motor] communicate delivery failed: ${ errMsg( err ) }` ); return false }
398
+
399
+ if( result.commands.set?.length ) set.push( ...result.commands.set )
400
+ const out: Enaction = { mode: 'communicate', success: result.success,
401
+ outcomeQuality: result.feedback.outcomeQuality, valence: 0, description: result.description }
402
+ set.push( outcomeEntity( tick, intent, out, predicted ) )
403
+ del.push( id )
404
+ this._emitEnacted( intent, out, predicted, tick )
405
+ this._emitActionOutcome( intent, result.success, result.feedback.outcomeQuality,
406
+ clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick )
407
+ metrics.push([ 'agency.communicate.delivered', 1 ])
408
+ return true
409
+ }
410
+
411
+ // ── bus emission ─────────────────────────────────────────────
412
+
413
+ private _emitEnacted( intent: Intent, enaction: Enaction, predicted: EfferenceCopy, tick: Tick ): void {
414
+ if( !this._bus ) return
415
+ const surprise = clamp01( Math.abs( predicted.expectedReward - enaction.outcomeQuality ) )
416
+ try {
417
+ this._bus.publish({
418
+ type: 'agency.enacted', version: 1, sourceEngine: this.name,
419
+ salience: Math.min( 1, 0.4 + surprise ),
420
+ payload: { schema: intent.schema, success: enaction.success, outcomeQuality: enaction.outcomeQuality, surprise, tick },
421
+ })
422
+ }
423
+ catch( err ){ logger.warn( `[motor] enacted publish failed: ${ errMsg( err ) }` ) }
424
+ }
425
+
426
+ /**
427
+ * Publish `action.outcome` for EVERY enaction — the shared metacognitive/affective
428
+ * sink. The PlanningEngine consumes it (when planId/stepId are present) to advance
429
+ * a plan; the ConfidenceCalibrator calibrates predicted-vs-actual from it; the
430
+ * RewardEvaluator reads it as a reward signal. `confidence` carries the agency's
431
+ * own forward-model prior so calibration has a real prediction to score.
432
+ */
433
+ private _emitActionOutcome(
434
+ intent: Intent, success: boolean, outcomeQuality: number, surprise: number, tick: Tick,
435
+ ): void {
436
+ if( !this._bus ) return
437
+ try {
438
+ this._bus.publish({
439
+ type: 'action.outcome', version: 1, sourceEngine: this.name,
440
+ salience: Math.min( 1, outcomeQuality * 0.6 ),
441
+ payload: {
442
+ actionType: intent.schema,
443
+ domain: intent.schema,
444
+ confidence: intent.expectedReward,
445
+ success,
446
+ outcomeQuality,
447
+ surprise,
448
+ targetEntityId: intent.targetEntityId,
449
+ ...( intent.planId ? { planId: intent.planId } : {} ),
450
+ ...( intent.planStepId ? { stepId: intent.planStepId } : {} ),
451
+ tick,
452
+ },
453
+ })
454
+ }
455
+ catch( err ){ logger.warn( `[motor] action outcome publish failed: ${ errMsg( err ) }` ) }
456
+ }
457
+
458
+ private _emitDispatch( intent: Intent, mode: 'communicate' | 'external', tick: Tick ): void {
459
+ if( !this._bus ) return
460
+ try {
461
+ this._bus.publish({
462
+ type: mode === 'communicate' ? 'agency.communicate' : 'agency.invocation',
463
+ version: 1, sourceEngine: this.name, salience: 0.6,
464
+ payload: {
465
+ schema: intent.schema, intentId: intent.id,
466
+ targetEntityId: intent.targetEntityId, parameters: intent.parameters, tick,
467
+ },
468
+ })
469
+ }
470
+ catch( err ){ logger.warn( `[motor] dispatch publish failed: ${ errMsg( err ) }` ) }
471
+ }
472
+ }
473
+
474
+ // ─── entity helpers ──────────────────────────────────────────────────────────
475
+
476
+ function outcomeEntity( tick: Tick, intent: Intent, enaction: Enaction, predicted: EfferenceCopy ): EntityInput {
477
+ const surprise = clamp01( Math.abs( predicted.expectedReward - enaction.outcomeQuality ) )
478
+ return {
479
+ id: `agency-outcome-${ tick }-${ intent.id }`,
480
+ type: 'agency.outcome',
481
+ metadata: {
482
+ schema: intent.schema,
483
+ affordanceId: intent.affordanceId,
484
+ intentId: intent.id,
485
+ targetEntityId: intent.targetEntityId,
486
+ success: enaction.success,
487
+ outcomeQuality: enaction.outcomeQuality,
488
+ valence: enaction.valence,
489
+ predictedReward: predicted.expectedReward,
490
+ predictedValence: predicted.expectedValence,
491
+ surprise,
492
+ description: enaction.description,
493
+ mode: enaction.mode,
494
+ tick,
495
+ },
496
+ }
497
+ }
498
+
499
+ function readIntent( id: string, m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined ): Intent {
500
+ const meta = ( m ?? {} ) as Record<string, unknown>
501
+ return {
502
+ id,
503
+ schema: str( meta['schema'] ) ?? '',
504
+ affordanceId: str( meta['affordanceId'] ),
505
+ targetEntityId: str( meta['targetEntityId'] ),
506
+ parameters: ( meta['parameters'] as Record<string, unknown> ) ?? {},
507
+ expectedReward: num( meta['expectedReward'], 0.5 ),
508
+ expectedValence: num( meta['expectedValence'], 0 ),
509
+ parentIntentId: str( meta['parentIntentId'] ),
510
+ stepIndex: typeof meta['stepIndex'] === 'number' ? ( meta['stepIndex'] as number ) : undefined,
511
+ planId: str( meta['planId'] ),
512
+ planStepId: str( meta['stepId'] ),
513
+ }
514
+ }
515
+
516
+ // ─── primitives ──────────────────────────────────────────────────────────────
517
+
518
+ function str( v: unknown ): string | undefined {
519
+ return typeof v === 'string' ? v : undefined
520
+ }
521
+ function firstMessage( v: unknown ): string | undefined {
522
+ return Array.isArray( v ) ? v.find( ( m ): m is string => typeof m === 'string' && m.length > 0 ) : undefined
523
+ }
524
+ function num( v: unknown, fallback: number ): number {
525
+ return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
526
+ }
527
+ function clamp01( n: number ): number {
528
+ return n < 0 ? 0 : n > 1 ? 1 : n
529
+ }
530
+ function errMsg( err: unknown ): string {
531
+ return err instanceof Error ? err.message : String( err )
532
+ }