@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,628 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/facet.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * ExecutiveFacet — focused attentional instance of the executive consciousness.
7
+ *
8
+ * Facets are true projections of the master executive identity:
9
+ * - Share master's identity, values, beliefs, and memories
10
+ * - Access LIVE state each reasoning cycle (not frozen snapshot)
11
+ * - Use the same PromptFactory as master for consistent prompting
12
+ * - Only the FOCUS section differentiates them from master
13
+ *
14
+ * The facet syncs bidirectionally with the master via cognitive bus events.
15
+ * The focus section is provided by the creator engine (PlanningEngine, etc.)
16
+ * via setFocus() — the facet has no hardcoded assumptions about what it's
17
+ * reasoning about.
18
+ *
19
+ * Part of Shard 3 (Executive Layer) — runs asynchronously outside tick cycle.
20
+ */
21
+
22
+ import { logger } from '#core/logger'
23
+ import type { ReadonlySimulationState } from '#core/types'
24
+ import { wallClock } from '#core/wall.clock'
25
+ import type { CognitiveBus } from '#cognition/bus'
26
+ import type { CompletionInbox } from '#cognition/completion.inbox'
27
+ import type { LLMDirector, LLMCallMeta } from '#llm/index'
28
+ import type { ExecutiveOutputFull, IdeationCandidate } from '#faculties/executive.engine/types'
29
+ import type { ContextDependencies } from '#faculties/executive.engine/context'
30
+ import type { SessionLogger } from '#stem/tracts/session.logger'
31
+ import {
32
+ PromptFactory,
33
+ type PromptDependencies,
34
+ type FocusSection
35
+ } from '#faculties/executive.engine/prompt.factory'
36
+ import { parseResponse, buildFallbackOutput } from '#faculties/executive.engine/parser'
37
+ import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
38
+ import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
39
+ import { readEffectiveParams } from '#cognition/persona.prior'
40
+
41
+ // ── Facet event types ─────────────────────────────────────────
42
+
43
+ /**
44
+ * Generic report interface — the facet doesn't interpret this.
45
+ * The creator engine defines the structure and interprets responses.
46
+ */
47
+ export interface FacetReport {
48
+ /** The reason for this report (creator-defined) */
49
+ type: string
50
+ /** The payload content (creator-defined structure) */
51
+ payload: unknown
52
+ /** Optional context identifier for routing */
53
+ contextId?: string
54
+ /** Optional dynamic instructions to append to the user message */
55
+ instructions?: string
56
+ }
57
+
58
+ export interface FacetDecision {
59
+ facetId: string
60
+ /** The reason/report type this decision responds to */
61
+ respondingToType: string
62
+ /** The decision payload (creator-defined structure) */
63
+ decision: unknown
64
+ reasoning: string
65
+ confidence: number
66
+ }
67
+
68
+ export type FacetEventListener = ( decision: FacetDecision ) => void
69
+
70
+ export interface ExecutiveFacetHandle {
71
+ /** Push a report into the facet — triggers a reasoning cycle */
72
+ report: ( report: FacetReport ) => void
73
+ /** Subscribe to facet decisions — returns unsubscribe function */
74
+ subscribe: ( listener: FacetEventListener ) => () => void
75
+ /** The facet's unique identifier */
76
+ facetId: string
77
+ /** Destroy the facet — unsubscribes from bus, cleans up */
78
+ destroy: () => void
79
+ /** Set the focus for this facet (called by creator before first report) */
80
+ setFocus: ( focus: FocusSection ) => void
81
+ /** Set the current state reference (called by orchestrator) */
82
+ setStateRef: ( state: ReadonlySimulationState ) => void
83
+ /**
84
+ * Register a per-facet chunk handler.
85
+ * Called when the creating engine wants per-entity token streaming
86
+ * (e.g. AuditionEngine, one handler per conversation session).
87
+ * Fires for every LLM token during `_reason()` — in addition to any
88
+ * global `_chunkBroadcaster` wired via `setChunkBroadcaster()` on the
89
+ * master ExecutiveEngine.
90
+ */
91
+ onChunk: ( handler: ( chunk: string ) => void ) => void
92
+ /**
93
+ * Register a callback fired when the supervisor REAPS this facet (idle TTL or
94
+ * LRU eviction), as opposed to an explicit `destroy()`. Lets the owner (e.g.
95
+ * AuditionEngine) drop its handle reference + session state when the facet is
96
+ * reclaimed out from under it. Not fired by `destroy()`.
97
+ */
98
+ onReaped: ( handler: () => void ) => void
99
+ }
100
+
101
+ // ── ExecutiveFacet ────────────────────────────────────────────
102
+
103
+ export class ExecutiveFacet {
104
+ readonly facetId: string
105
+
106
+ private _bus: CognitiveBus
107
+ private _llmDirector: LLMDirector
108
+ private _contextDeps: ContextDependencies
109
+ private _promptDeps: PromptDependencies
110
+ private _willId: string
111
+ private _listeners = new Set<FacetEventListener>()
112
+ private _destroyed = false
113
+ private _sessionLogger: SessionLogger | null = null
114
+
115
+ /**
116
+ * Tick-boundary landing (see cognition/completion.inbox.ts). When present,
117
+ * subscriber notification is STAGED at LLM-promise resolution and applied by
118
+ * the CognitiveOrchestrator in Phase 2 — so PlanningEngine / AuditionEngine
119
+ * effects (plan mutations, outbox writes) never interleave with a tick in
120
+ * flight. Null = legacy direct notification (bare facets in unit tests).
121
+ */
122
+ private _inbox: CompletionInbox | null = null
123
+
124
+ /**
125
+ * Reports waiting for the per-tick pump (tick-discipline mode only). Reasoning
126
+ * launches from pump(), never from report() — see report() for why.
127
+ */
128
+ private _pendingReports: FacetReport[] = []
129
+
130
+ /** Current focus for this facet — set by creator (PlanningEngine, etc.) */
131
+ private _currentFocus: FocusSection | null = null
132
+
133
+ /** Accumulated reasoning history for continuity across report() calls */
134
+ private _facetReasoningHistory: string[] = []
135
+ private _masterSyncHistory: string[] = []
136
+
137
+ /** Confidence of this facet's *previous* decision — a dual-process gate signal. */
138
+ private _lastConfidence = 0.5
139
+
140
+ /** Current state reference (updated by orchestrator each tick) */
141
+ private _currentStateRef: ReadonlySimulationState | null = null
142
+
143
+ /**
144
+ * Sim tick of the last activity (report). Used by FacetSupervisor's idle
145
+ * reaper + LRU eviction. Sim tick (deterministic), never wallClock.
146
+ */
147
+ private _lastActiveTick = 0
148
+ get lastActiveTick(): number { return this._lastActiveTick }
149
+ /** Stamp activity at `tick` (called at spawn and on each report). */
150
+ markActive( tick: number ): void { this._lastActiveTick = tick }
151
+
152
+ /**
153
+ * Per-facet chunk handler — fires for every LLM token during _reason().
154
+ * Set by the creating engine (e.g. AuditionEngine) for entity-scoped streaming.
155
+ * Independent from the master's global _chunkBroadcaster.
156
+ */
157
+ private _chunkHandler: (( chunk: string ) => void) | null = null
158
+
159
+ constructor(
160
+ facetId: string,
161
+ bus: CognitiveBus,
162
+ llmDirector: LLMDirector,
163
+ contextDeps: ContextDependencies,
164
+ promptDeps: PromptDependencies,
165
+ willId: string,
166
+ inbox: CompletionInbox | null = null
167
+ ){
168
+ this.facetId = facetId
169
+ this._bus = bus
170
+ this._llmDirector = llmDirector
171
+ this._contextDeps = contextDeps
172
+ this._promptDeps = promptDeps
173
+ this._willId = willId
174
+ this._inbox = inbox
175
+
176
+ // Subscribe to master sync events
177
+ this._bus.subscribe(
178
+ `executive-facet-${facetId}`,
179
+ [ 'executive.master.sync' ],
180
+ this._onMasterSync
181
+ )
182
+
183
+ logger.info( `[executive.facet] ${facetId} created` )
184
+ }
185
+
186
+ // ── Public API ─────────────────────────────────────────────
187
+
188
+ /**
189
+ * Set the focus for this facet.
190
+ * Called by the creator (PlanningEngine, etc.) before first report.
191
+ */
192
+ setFocus( focus: FocusSection ): void {
193
+ this._currentFocus = focus
194
+ }
195
+
196
+ /**
197
+ * Set the current state reference.
198
+ * Called by the orchestrator each tick to keep facet in sync.
199
+ */
200
+ setStateRef( state: ReadonlySimulationState ): void {
201
+ this._currentStateRef = state
202
+ }
203
+
204
+ attachSessionLogger( logger: SessionLogger | null ): void {
205
+ this._sessionLogger = logger
206
+ }
207
+
208
+ /** Register a per-facet chunk handler for entity-scoped token streaming. */
209
+ setChunkHandler( handler: ( chunk: string ) => void ): void {
210
+ this._chunkHandler = handler
211
+ }
212
+
213
+ async report( report: FacetReport ): Promise<void> {
214
+ if( this._destroyed ) return
215
+
216
+ // Stamp activity so the supervisor's idle reaper keeps an active facet alive.
217
+ this.markActive( ( this._currentStateRef?.tick as number ) ?? this._lastActiveTick )
218
+
219
+ logger.info(
220
+ `[executive.facet] ${this.facetId} received report: type=${report.type}`
221
+ )
222
+
223
+ // Tick-discipline mode (inbox attached — the same signal that quantizes
224
+ // decision LANDING): reasoning must not START at raw report time either.
225
+ // A report can arrive from any wall-clock context (an ingest chain, another
226
+ // chain's resolution), and launching _reason() there makes both the issue
227
+ // tick and the prompt bytes race-dependent — the prompt reads live manager
228
+ // state at whatever moment the chain happened to run. Queue instead; the
229
+ // ExecutiveEngine pumps every facet once per tick (react() → supervisor
230
+ // .pump()) with that tick's frozen snapshot, so reasoning launches at a
231
+ // deterministic point with deterministic inputs. See
232
+ // .TODO/FACET_REPLAY_DETERMINISM.md (layer 3).
233
+ if( this._inbox ){
234
+ this._pendingReports.push( report )
235
+ return
236
+ }
237
+
238
+ // Legacy (bare facets in unit tests): trigger reasoning immediately.
239
+ this._reason( report ).catch( err =>
240
+ logger.error( `[executive.facet] ${this.facetId} reasoning error:`, err )
241
+ )
242
+ }
243
+
244
+ /**
245
+ * Per-tick pump — called by the FacetSupervisor from the ExecutiveEngine's
246
+ * react() with the tick's FROZEN snapshot. Refreshes the state reference,
247
+ * then launches reasoning for every queued report, FIFO. The launch happens
248
+ * at a fixed point in the serial engine order, so prompt inputs are a pure
249
+ * function of (tick, seed, recorded external inputs) — replayable.
250
+ */
251
+ pump( state: ReadonlySimulationState ): void {
252
+ if( this._destroyed ) return
253
+ this.setStateRef( state )
254
+
255
+ if( this._pendingReports.length === 0 ) return
256
+ const batch = this._pendingReports
257
+ this._pendingReports = []
258
+
259
+ for( const report of batch )
260
+ this._reason( report ).catch( err =>
261
+ logger.error( `[executive.facet] ${this.facetId} reasoning error:`, err )
262
+ )
263
+ }
264
+
265
+ subscribe( listener: FacetEventListener ): () => void {
266
+ this._listeners.add( listener )
267
+ return () => { this._listeners.delete( listener ) }
268
+ }
269
+
270
+ destroy(): void {
271
+ if( this._destroyed ) return
272
+ this._destroyed = true
273
+
274
+ this._bus.unsubscribe( `executive-facet-${this.facetId}` )
275
+ this._listeners.clear()
276
+
277
+ logger.info( `[executive.facet] ${this.facetId} destroyed` )
278
+ }
279
+
280
+ // ── Master sync ────────────────────────────────────────────
281
+
282
+ private _onMasterSync = ( event: any ): void => {
283
+ if( this._destroyed ) return
284
+
285
+ const payload = event.payload as {
286
+ facetId?: string
287
+ reasoning?: string
288
+ confidence?: number
289
+ actionTypes?: string[]
290
+ coherenceVersion?: number
291
+ tick?: number
292
+ }
293
+
294
+ // Master syncs to all facets or a specific one
295
+ if( payload.facetId && payload.facetId !== this.facetId ) return
296
+
297
+ logger.info( `[executive.facet] ${this.facetId} synced from master (tick=${payload.tick})` )
298
+
299
+ // Store master's latest reasoning for context in next report()
300
+ if( payload.reasoning )
301
+ this._masterSyncHistory.push( `[Master sync — tick ${payload.tick}] ${payload.reasoning.slice( 0, 400 )}` )
302
+
303
+ // Keep only last 5 sync entries
304
+ if( this._masterSyncHistory.length > 5 )
305
+ this._masterSyncHistory = this._masterSyncHistory.slice( -5 )
306
+ }
307
+
308
+ // ── Reasoning ──────────────────────────────────────────────
309
+
310
+ private async _reason( report: FacetReport ): Promise<void> {
311
+ // Ensure we have current state
312
+ if( !this._currentStateRef )
313
+ throw new Error( `[executive.facet] ${this.facetId} no state reference available` )
314
+
315
+ if( !this._currentFocus )
316
+ throw new Error( `[executive.facet] ${this.facetId} no focus set. Call setFocus() before report().` )
317
+
318
+ const currentState = this._currentStateRef
319
+
320
+ // Build fresh context from current state
321
+ // A focus may supply a recall query (e.g. the live conversation message) to
322
+ // drive the single "## Relevant Memories" section — one recall surface (§5).
323
+ const execContext = await PromptFactory.buildFreshContext( this._contextDeps, currentState, this._currentFocus?.recallQuery )
324
+
325
+ const qualityModulation = PromptFactory.computeQualityModulation( currentState )
326
+ const epistemicUncertainty = PromptFactory.computeEpistemicUncertainty( execContext, currentState )
327
+
328
+ // Build focus section — the creator is responsible for providing
329
+ // the appropriate content via setFocus()
330
+ const focus: FocusSection = this._masterSyncHistory.length > 0
331
+ ? {
332
+ ...this._currentFocus,
333
+ content: `${this._currentFocus.content}\n\n## Master Consciousness Updates\n${this._masterSyncHistory.join('\n')}`
334
+ }
335
+ : this._currentFocus
336
+
337
+ // Build system prompt using PromptFactory — same schema as master, [REPLY] gated out.
338
+ const systemPrompt = PromptFactory.buildSystemPrompt( {
339
+ context: execContext,
340
+ focus,
341
+ deps: this._promptDeps,
342
+ mode: 'facet'
343
+ } )
344
+
345
+ // Build user message — same live-state baseline as master.
346
+ // Creator-engine context is injected via reportContent (per-report payload)
347
+ // and focus.instructions (per-focus recurring context).
348
+ // outputFormat overrides the standard schema when the creating engine needs it.
349
+ //
350
+ // Facet reasoning continuity: prepend this facet's own prior reasoning so it
351
+ // isn't cold on each cycle. Injected before the caller's instructions so the
352
+ // caller content always comes last (highest recency bias from the LLM).
353
+ const continuityBlock = this._facetReasoningHistory.length > 0
354
+ ? `## My Prior Reasoning (this facet)\n${this._facetReasoningHistory.join( '\n' )}`
355
+ : ''
356
+
357
+ const reportContent = [
358
+ continuityBlock,
359
+ report.instructions ?? ''
360
+ ].filter( Boolean ).join( '\n\n' ) || undefined
361
+
362
+ // Dual-process — a facet is "the master over its focus", so it deliberates the same
363
+ // way. The a-priori effort gate (shared `selectProcess`) picks fast vs deliberate
364
+ // using the SAME developable `deliberateThreshold` as master (engine-config-executive),
365
+ // so the Will's deliberativeness is unified across master and every focus. On the
366
+ // deliberate path a propose pass generates options FOR THIS FOCUS (e.g. a planning
367
+ // facet's continue/revise/replan/abandon), injected into the decision call below.
368
+ const deliberateThreshold = readEffectiveParams( currentState, 'engine-config-executive' ).deliberateThreshold ?? DELIBERATE_THRESHOLD
369
+ const processSelection = selectProcess( {
370
+ epistemicUncertainty,
371
+ priorConfidence: this._lastConfidence,
372
+ novelty: currentState.metrics.get( 'perception.novelty' ) ?? 0,
373
+ stressLoad: currentState.metrics.get( 'stress.load' ) ?? 0,
374
+ // A facet's "stakes-bearing moment" is a live message awaiting reply (conversation
375
+ // facets set focus.recallQuery to it); planning/other facets rely on uncertainty.
376
+ hasPendingMessage: !!this._currentFocus?.recallQuery,
377
+ }, deliberateThreshold )
378
+
379
+ let ideationCandidates: IdeationCandidate[] | undefined
380
+ if( processSelection.process === 'deliberate' ){
381
+ const proposeTemperature = ideationTemperature( execContext.identity.traits[ 'creativity' ] ?? 0.5 )
382
+ const ideationUserMessage = PromptFactory.buildUserMessage( {
383
+ context: execContext,
384
+ state: currentState,
385
+ qualityModulation,
386
+ epistemicUncertainty,
387
+ pendingMessages: [],
388
+ focus,
389
+ deps: this._promptDeps,
390
+ recentActionTypes: [],
391
+ mode: 'facet',
392
+ reportContent,
393
+ outputFormat: PromptFactory.buildIdeationFormatInstruction(),
394
+ } )
395
+ ideationCandidates = await proposeCandidates( {
396
+ director: this._llmDirector,
397
+ systemPrompt,
398
+ ideationUserMessage,
399
+ tick: currentState.tick,
400
+ proposeTemperature,
401
+ meta: { category: 'executive', attribute: 'facet', function: 'ideation', scope: this.facetId },
402
+ } )
403
+ logger.info(
404
+ `[executive.facet] ${this.facetId} ◆ deliberate propose tick=${currentState.tick} ` +
405
+ `candidates=${ideationCandidates?.length ?? 0} temp=${proposeTemperature.toFixed( 2 )}`
406
+ )
407
+ }
408
+
409
+ const userMessage = PromptFactory.buildUserMessage( {
410
+ context: execContext,
411
+ state: currentState,
412
+ qualityModulation,
413
+ epistemicUncertainty,
414
+ pendingMessages: [],
415
+ focus,
416
+ deps: this._promptDeps,
417
+ recentActionTypes: [],
418
+ mode: 'facet',
419
+ reportContent,
420
+ outputFormat: focus.outputFormat,
421
+ ideationCandidates
422
+ } )
423
+
424
+ // Log the call
425
+ this._sessionLogger?.write( {
426
+ type: 'executive.facet.call',
427
+ tick: currentState.tick,
428
+ facetId: this.facetId,
429
+ promptChars: systemPrompt.length + userMessage.length,
430
+ promptTokensEst: Math.round( ( systemPrompt.length + userMessage.length ) / 4 ),
431
+ systemChars: systemPrompt.length,
432
+ userChars: userMessage.length
433
+ } )
434
+
435
+ let output: ExecutiveOutputFull
436
+ const llmStart = wallClock() // perf timing only — latency is telemetry, never replay state
437
+
438
+ try {
439
+ // Use streaming call when a per-facet chunk handler is registered —
440
+ // this enables entity-scoped token delivery (e.g. AuditionEngine SSE).
441
+ // Falls back to regular call when no handler is present.
442
+ const facetMeta: LLMCallMeta = {
443
+ category: 'executive',
444
+ attribute: 'facet',
445
+ function: this._currentFocus?.function ?? 'facet',
446
+ scope: this.facetId,
447
+ }
448
+ const result = this._chunkHandler
449
+ ? await this._llmDirector.callStream( systemPrompt, userMessage, currentState.tick, this._chunkHandler, undefined, facetMeta )
450
+ : await this._llmDirector.call( systemPrompt, userMessage, currentState.tick, undefined, facetMeta )
451
+ output = parseResponse( result.text, currentState, [] )
452
+
453
+ // System 2 — retain the options this facet weighed for its focus decision.
454
+ if( ideationCandidates && ideationCandidates.length > 0 )
455
+ output.consideredAlternatives = ideationCandidates.map( c => c.approach || c.description )
456
+
457
+ logger.info(
458
+ `[executive.facet] ${this.facetId} ✓ tick=${currentState.tick} ` +
459
+ `in=${result.inputTok} tok out=${result.outputTok} tok ` +
460
+ `cache=${result.cacheReadTok ?? 0}r/${result.cacheWriteTok ?? 0}w ` +
461
+ `latency=${wallClock() - llmStart}ms`
462
+ )
463
+
464
+ this._sessionLogger?.write( {
465
+ type: 'executive.facet.response',
466
+ tick: currentState.tick,
467
+ facetId: this.facetId,
468
+ latencyMs: wallClock() - llmStart,
469
+ responseChars: result.text.length,
470
+ promptTokens: result.inputTok,
471
+ completionTokens: result.outputTok,
472
+ cacheReadTokens: result.cacheReadTok ?? 0,
473
+ cacheWriteTokens: result.cacheWriteTok ?? 0,
474
+ responseExcerpt: result.text.slice( 0, 600 )
475
+ } )
476
+ }
477
+ catch( err ){
478
+ const msg = err instanceof Error ? err.message : String( err )
479
+ logger.error( `[executive.facet] ${this.facetId} LLM call failed: ${msg.slice( 0, 200 )}` )
480
+
481
+ this._sessionLogger?.write( {
482
+ type: 'executive.facet.response',
483
+ tick: currentState.tick,
484
+ facetId: this.facetId,
485
+ latencyMs: wallClock() - llmStart,
486
+ error: msg.slice( 0, 300 )
487
+ } )
488
+
489
+ output = buildFallbackOutput( currentState, [] )
490
+ }
491
+
492
+ // Log the parsed output
493
+ this._sessionLogger?.write( {
494
+ type: 'executive.facet.output',
495
+ tick: currentState.tick,
496
+ facetId: this.facetId,
497
+ confidence: output.confidence,
498
+ reasoning: output.reasoning.slice( 0, 500 ),
499
+ actions: output.actions,
500
+ newBeliefs: output.newBeliefs ?? [],
501
+ plansCount: output.plans?.length ?? 0,
502
+ goalsNew: output.newGoals ?? [],
503
+ goalsAbandon: output.goalsToAbandon ?? [],
504
+ hasIntrospection: !!output.introspection,
505
+ hasNarrative: !!output.narrative
506
+ } )
507
+
508
+ // Track this decision's confidence — a dual-process gate signal for the next report.
509
+ this._lastConfidence = output.confidence
510
+
511
+ // Store reasoning for continuity
512
+ this._facetReasoningHistory.push( `[Report: ${report.type}] ${output.reasoning.slice( 0, 400 )}` )
513
+ if( this._facetReasoningHistory.length > 10 )
514
+ this._facetReasoningHistory = this._facetReasoningHistory.slice( -10 )
515
+
516
+ // Convert executive output to facet decision — the creator defines the decision shape
517
+ // via the focus.outputFormat. The facet simply passes through the parsed output
518
+ // as the decision payload.
519
+ const decision: FacetDecision = {
520
+ facetId: this.facetId,
521
+ respondingToType: report.type,
522
+ decision: this._extractDecisionPayload( output, focus ),
523
+ reasoning: output.reasoning,
524
+ confidence: output.confidence
525
+ }
526
+
527
+ // Promote subscriber-visible fields from the decision object to the event payload
528
+ // top-level so that GoalManager and SemanticIntegrator can read them without
529
+ // knowing or casting the opaque `decision` type.
530
+ // PlanningEngine (and other creating engines) still receive the full `decision`.
531
+ const dec = ( typeof decision.decision === 'object' && decision.decision !== null )
532
+ ? decision.decision as Record<string, unknown>
533
+ : {}
534
+
535
+ this._bus.publish( {
536
+ type: 'executive.facet.progress',
537
+ version: 1,
538
+ sourceEngine: `executive-facet-${this.facetId}`,
539
+ salience: Math.max( 0.5, decision.confidence ),
540
+ payload: {
541
+ facetId: this.facetId,
542
+ respondingToType: report.type,
543
+ decision: decision.decision,
544
+ // promoted for GoalManager
545
+ goalId: dec[ 'goalId' ] as string | undefined,
546
+ goalProgress: dec[ 'goalProgress' ] as number | undefined,
547
+ newGoals: dec[ 'newGoals' ],
548
+ goalsToAbandon: dec[ 'goalsToAbandon' ],
549
+ // promoted for SemanticIntegrator
550
+ newBeliefs: dec[ 'newBeliefs' ],
551
+ // promoted for SemanticIntegrator (learned facts about others → keid-tagged beliefs)
552
+ knownEntityUpdates: dec[ 'knownEntityUpdates' ],
553
+ contextId: report.contextId,
554
+ tick: currentState.tick
555
+ }
556
+ } )
557
+
558
+ // Conscious learning about others — route name/feeling to known.entity.tracker (the
559
+ // dossier owner) the same way the master does, so routine (un-escalated) conversation
560
+ // also records who/what it's dealing with. Facts ride the promoted field above.
561
+ const keUpdates = dec[ 'knownEntityUpdates' ] as ExecutiveOutputFull['knownEntityUpdates'] | undefined
562
+ if( keUpdates )
563
+ for( const u of keUpdates )
564
+ if( u.keid && u.keid !== 'agent-self' && ( u.name || u.feeling != null ) )
565
+ this._bus.publish( {
566
+ type: 'known.entity.learned', version: 1,
567
+ sourceEngine: `executive-facet-${this.facetId}`,
568
+ salience: 0.5, payload: { keid: u.keid, name: u.name, feeling: u.feeling }
569
+ } )
570
+
571
+ // Sync back to master
572
+ this._bus.publish( {
573
+ type: 'executive.facet.sync',
574
+ version: 1,
575
+ sourceEngine: `executive-facet-${this.facetId}`,
576
+ salience: Math.max( 0.5, decision.confidence ),
577
+ payload: {
578
+ facetId: this.facetId,
579
+ reasoning: output.reasoning,
580
+ confidence: output.confidence,
581
+ actionTypes: output.actions.map( a => a.type ),
582
+ tick: currentState.tick
583
+ }
584
+ } )
585
+
586
+ // Notify all listeners (PlanningEngine, AuditionEngine, etc.).
587
+ //
588
+ // Through the CompletionInbox when attached: this runs at LLM-promise
589
+ // resolution — an arbitrary wall-clock moment — and listeners mutate shared
590
+ // state (plans, the outbox). Staging the notification quantizes the landing
591
+ // to the next tick's Phase 2, alongside the bus events published above, so
592
+ // the whole decision (events + listener effects) lands atomically at one
593
+ // tick boundary. Without an inbox (bare facets in tests): direct, as before.
594
+ const notify = (): void => {
595
+ for( const listener of this._listeners )
596
+ try { listener( decision ) }
597
+ catch( err ){ logger.error( `[executive.facet] ${this.facetId} listener error:`, err ) }
598
+ }
599
+
600
+ if( this._inbox ) this._inbox.enqueue( `${this.facetId}:decision`, notify )
601
+ else notify()
602
+ }
603
+
604
+ /**
605
+ * Extract the decision payload from the executive output.
606
+ *
607
+ * If the creating engine supplied a `focus.extractDecision` callback, delegate
608
+ * entirely to it — the engine knows what fields it needs and how to derive them
609
+ * from the LLM output (e.g. reading action type as a directive, computing
610
+ * goalProgress from plan state, etc.).
611
+ *
612
+ * If no callback is provided, fall back to a generic passthrough of the
613
+ * LLM's raw actions and goal-mutation lists. This is safe for any facet that
614
+ * doesn't need domain-specific decision routing.
615
+ */
616
+ private _extractDecisionPayload( output: ExecutiveOutputFull, focus: FocusSection ): unknown {
617
+ if( focus.extractDecision ) return focus.extractDecision( output )
618
+
619
+ // Generic fallback — no plan-specific assumptions
620
+ return {
621
+ actions: output.actions,
622
+ newGoals: output.newGoals,
623
+ goalsToAbandon: output.goalsToAbandon,
624
+ newBeliefs: output.newBeliefs,
625
+ knownEntityUpdates: output.knownEntityUpdates
626
+ }
627
+ }
628
+ }