@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,1028 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/engine.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * ExecutiveEngine — the unified LLM reasoning engine.
7
+ *
8
+ * Replaces separate LLM calls from Decision, Planning, Semantic,
9
+ * Introspection, and Narrative engines with a single consolidated
10
+ * call every executiveInterval ticks.
11
+ *
12
+ * Uses a minimal structured output schema (actions, reasoning, confidence)
13
+ * with optional cognitive outputs embedded as tagged JSON blocks inside
14
+ * the reasoning text. This avoids Anthropic's grammar size limit.
15
+ *
16
+ * Between executive runs, satellite engines use heuristic fast paths
17
+ * informed by the most recent executive output.
18
+ */
19
+
20
+ import { logger } from '#core/logger'
21
+ import type { SessionLogger } from '#stem/tracts/session.logger'
22
+ import type {
23
+ Tick,
24
+ SimulationContext,
25
+ ReadonlySimulationState,
26
+ ReasoningFootprint,
27
+ StateCommands
28
+ } from '#core/types'
29
+ import { AsyncEngine } from '#core/async.engine'
30
+ import type { IntermediateStream } from '#core/async.engine'
31
+ import type { WorkingMemory } from '#faculties/working.memory'
32
+ import type { GoalManager, GoalState } from '#faculties/goal.manager'
33
+ import type { EpisodicConsolidator } from '#faculties/episodic.consolidator'
34
+ import type { SemanticIntegrator, Belief } from '#faculties/semantic.engine'
35
+ import type { PlanningEngine } from '#cognition/faculties/planning.engine/engine'
36
+ import type { TokenTracker } from '#cognition/utilities/token.tracker'
37
+ import type { CognitiveEngine } from '#cognition/types'
38
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
39
+ import type { CompletionInbox } from '#cognition/completion.inbox'
40
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
41
+ import { GenerativeModel } from '#cognition/generative.model'
42
+ import { ExecutiveSummarizer } from '#llm/summarizer'
43
+ import { ExecutiveFacet, type ExecutiveFacetHandle } from '#faculties/executive.engine/facet'
44
+ import type { ExecutiveOutputFull, IdeationCandidate } from '#faculties/executive.engine/types'
45
+ import {
46
+ PromptFactory,
47
+ type PromptDependencies,
48
+ type FocusSection
49
+ } from '#faculties/executive.engine/prompt.factory'
50
+ import {
51
+ WORKSPACE_THRESHOLD,
52
+ DEFAULT_EXECUTIVE_INTERVAL,
53
+ DEFAULT_COOLDOWN_TICKS,
54
+ readRuntimeConfig
55
+ } from '#faculties/executive.engine/config'
56
+ import {
57
+ evaluateGating,
58
+ updateGatingState,
59
+ type GatingDependencies,
60
+ type GatingState
61
+ } from '#faculties/executive.engine/gating'
62
+ import { LLMDirector } from '#llm/index'
63
+ import type { LLMProvider } from '#llm/index'
64
+ import { buildFallbackOutput, parseResponse } from '#faculties/executive.engine/parser'
65
+ import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
66
+ import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
67
+ import { readEffectiveParams } from '#cognition/persona.prior'
68
+ import { MessageQueue } from '#faculties/executive.engine/messages'
69
+ import {
70
+ buildStateCommands,
71
+ publishCognitiveEvents,
72
+ type CommandDependencies
73
+ } from '#faculties/executive.engine/commands'
74
+ import { DeferredEffectQueue } from '#faculties/executive.engine/deferred.effects'
75
+ import { EscalationBuffer } from '#faculties/executive.engine/escalation.buffer'
76
+ import { FacetSupervisor } from '#faculties/executive.engine/facet.supervisor'
77
+ import type { EngineResult } from '#core/orchestrator'
78
+ import type { Duration } from '#core/types'
79
+ import { wallClock } from '#core/wall.clock'
80
+
81
+ // Re-export for compatibility
82
+ export { ExecutiveFacet, type ExecutiveFacetHandle }
83
+
84
+ // ── Engine config ───────────────────────────────────────────
85
+
86
+ export interface ExecutiveEngineConfig {
87
+ executiveInterval?: number
88
+ cooldownTicks?: number
89
+ bus?: CognitiveBus
90
+ }
91
+
92
+ /**
93
+ * Map the executive's chosen action types to a voluntary attention-effort target
94
+ * (Option C dynamic attention budget):
95
+ * - a `focus` action → 1.0 (mobilize: more capacity / parallel facets)
96
+ * - `rest`/`sleep`/`wait`/`meditate` → 0.4 (stand down: conserve)
97
+ * - neither → `null` (no preference — the AttentionAllocator decays effort back
98
+ * to baseline on its own).
99
+ * `focus` wins if the cycle somehow chose both. The returned value is the payload
100
+ * for the `attention.regulate` event the engine publishes; the allocator clamps
101
+ * it to its [EFFORT_MIN, EFFORT_MAX] band.
102
+ */
103
+ export function effortTargetForActions( actionTypes: Iterable<string> ): number | null {
104
+ const types = new Set( actionTypes )
105
+ if( types.has( 'focus' ) ) return 1.0
106
+ if( [ 'rest', 'sleep', 'wait', 'meditate' ].some( t => types.has( t ) ) ) return 0.4
107
+ return null
108
+ }
109
+
110
+ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
111
+ readonly name = 'executive-engine'
112
+
113
+ // ── Configuration ──────────────────────────────────────────
114
+ private _executiveInterval: number
115
+ private _cooldownTicks: number
116
+
117
+ // ── Gating state ───────────────────────────────────────────
118
+ private _gatingState: GatingState
119
+
120
+ // Snapshot of the salience-buffer entries this reasoning cycle consumed.
121
+ // Captured at reasonAsync() start; on completion only these are retired, so
122
+ // events that landed mid-call (never seen by the broadcast) survive to compete
123
+ // on the next cycle instead of being silently dropped.
124
+ private _consumedBufferEntries: GatingState[ 'salienceBuffer' ] = []
125
+
126
+ // ── LLM director ───────────────────────────────────────────
127
+ private _llmDirector: LLMDirector | null = null
128
+ private _testMode = false
129
+
130
+ // ── Message queue ──────────────────────────────────────────
131
+ private _messageQueue = new MessageQueue()
132
+
133
+ // ── Action diversity tracking ──────────────────────────────
134
+ private _recentActionTypes: string[] = []
135
+
136
+ // ── Coherence version ──────────────────────────────────────
137
+ private _coherenceVersion = 0
138
+
139
+ // ── Epistemic uncertainty ──────────────────────────────────
140
+ private _lastEpistemicUncertainty = 0.5
141
+
142
+ // ── Last output ────────────────────────────────────────────
143
+ private _lastExecutiveOutput: ExecutiveOutputFull | null = null
144
+ private _lastExecutiveTick: number = -100
145
+
146
+ // ── Injected dependencies ──────────────────────────────────
147
+ private _willId: string | null = null
148
+ /** Per-Will model id (resolved from modelTier in mind.ts). Null → env/default. */
149
+ private _modelId: string | null = null
150
+ private _workingMemory: WorkingMemory | null = null
151
+ private _goalManager: GoalManager | null = null
152
+ private _episodicConsolidator: EpisodicConsolidator | null = null
153
+ private _semanticIntegrator: SemanticIntegrator | null = null
154
+ private _planningEngine: PlanningEngine | null = null
155
+ private _summarizer: ExecutiveSummarizer | null = null
156
+ private _sessionLogger: SessionLogger | null = null
157
+ private _bus: CognitiveBus | null = null
158
+ /** Tick-boundary landing for facet decisions — injected by the orchestrator. */
159
+ private _inbox: CompletionInbox | null = null
160
+ // Per-Will token tracker (R4), injected and threaded into the LLMDirector so
161
+ // LLM usage records into this mind's instance — never a process global.
162
+ private _tokenTracker: TokenTracker | null = null
163
+
164
+ // ── Facet Sync ──────────────────────────────────────────────
165
+ // Facet lifecycle + attention budget live in FacetSupervisor (R5-g-3); the
166
+ // engine keeps only the bus-subscription guard, since those subscriptions
167
+ // feed the gating salience buffer and are shared with the escalation path.
168
+ private readonly _facetSupervisor = new FacetSupervisor()
169
+ private _facetSyncSubscribed = false
170
+
171
+ // ── Cognitive models ───────────────────────────────────────
172
+ private readonly _model = new GenerativeModel()
173
+ private readonly _generativeModel = new GenerativeModel( 0.2, 100 )
174
+
175
+ // ── Summarizer restore flag ────────────────────────────────
176
+ private _summarizerRestored = false
177
+
178
+ // ── Last state reference (for onReasoningComplete and facets) ─
179
+ private _lastStateRef: ReadonlySimulationState | null = null
180
+
181
+ // ── Deferred manager side-effects (FN11) ───────────────────
182
+ // Commit-gated queue for the mirroring manager writes returned by
183
+ // buildStateCommands. The queue runs them only after the orchestrator
184
+ // confirms the tick committed (and drops them for aborted ticks); see
185
+ // DeferredEffectQueue for the full rationale.
186
+ private readonly _deferred = new DeferredEffectQueue()
187
+
188
+ // ── LLM streaming chunk broadcaster ────────────────────────
189
+ // When set, called with each token during LLM generation so SSE streams
190
+ // can forward it to connected clients in real-time (F3).
191
+ private _chunkBroadcaster: (( chunk: string ) => void) | null = null
192
+
193
+ // ── Pending escalations from AuditionEngine ─────────────────
194
+ // Buffered between bus event receipt and next tick boundary, then
195
+ // flushed as percept entities via StateCommands in onReasoningComplete().
196
+ // The master reads them as Percepts — NEVER as incoming messages.
197
+ // See EscalationBuffer for the full rationale (R5-g-2).
198
+ private readonly _escalations = new EscalationBuffer()
199
+
200
+ /** Set/clear the chunk broadcaster (called by WillManager when SSE clients connect). */
201
+ setChunkBroadcaster( fn: (( chunk: string ) => void) | null ): void {
202
+ this._chunkBroadcaster = fn
203
+ }
204
+
205
+ constructor( config: ExecutiveEngineConfig = {} ){
206
+ super( {
207
+ defaultStrategy: 'FORCE',
208
+ maxPendingTicks: 600,
209
+ logConflicts: false,
210
+ rerunOnRejection: false
211
+ } )
212
+
213
+ this._executiveInterval = config.executiveInterval ?? DEFAULT_EXECUTIVE_INTERVAL
214
+ this._cooldownTicks = config.cooldownTicks ?? DEFAULT_COOLDOWN_TICKS
215
+ this._bus = config.bus ?? null
216
+
217
+ this._gatingState = {
218
+ executiveInterval: this._executiveInterval,
219
+ cooldownTicks: this._cooldownTicks,
220
+ lastExecutiveTick: -100,
221
+ salienceBuffer: [],
222
+ goallessTickCount: 0,
223
+ lowValenceTickCount: 0
224
+ }
225
+
226
+ this._ensureFacetSyncSubscription()
227
+ }
228
+
229
+ // ── Dependency injection ───────────────────────────────────
230
+
231
+ attachWorkingMemory( wm: WorkingMemory ): void { this._workingMemory = wm }
232
+ attachGoalManager( gm: GoalManager ): void { this._goalManager = gm }
233
+ attachEpisodicConsolidator( ec: EpisodicConsolidator ): void { this._episodicConsolidator = ec }
234
+ attachSemanticIntegrator( si: SemanticIntegrator ): void { this._semanticIntegrator = si }
235
+ attachPlanningEngine( pe: PlanningEngine ): void { this._planningEngine = pe }
236
+ attachSummarizer( s: ExecutiveSummarizer ): void { this._summarizer = s }
237
+ attachSessionLogger( logger: SessionLogger | null ): void {
238
+ this._sessionLogger = logger
239
+ this._facetSupervisor.attachSessionLogger( logger )
240
+ }
241
+ attachTokenTracker( t: TokenTracker ): void { this._tokenTracker = t }
242
+
243
+ /** Enable test mode — all LLM calls return canned mock responses at zero cost. */
244
+ setTestMode( enabled: boolean ): void { this._testMode = enabled }
245
+
246
+
247
+ /** Called by CognitiveOrchestrator.addEngine() — injects the shared bus. */
248
+ attachBus( bus: CognitiveBus ): void {
249
+ this._bus = bus
250
+ this._ensureFacetSyncSubscription()
251
+ }
252
+
253
+ /**
254
+ * Called by CognitiveOrchestrator.addEngine() — injects the completion inbox
255
+ * so facet decision effects land at tick boundaries (Phase 2) instead of at
256
+ * raw LLM-promise resolution. See cognition/completion.inbox.ts.
257
+ */
258
+ attachCompletionInbox( inbox: CompletionInbox ): void {
259
+ this._inbox = inbox
260
+ }
261
+
262
+ set willId( willId: string ){ this._willId = willId }
263
+
264
+ /** Per-Will model id (from modelTier). Must be set before the first tick. */
265
+ set modelId( id: string | null ){ this._modelId = id }
266
+
267
+ // ── Public surface ─────────────────────────────────────────
268
+
269
+ get latestOutput(): ExecutiveOutputFull | null {
270
+ return this._lastExecutiveOutput
271
+ }
272
+
273
+ isFresh( currentTick: Tick ): boolean {
274
+ return this._lastExecutiveOutput !== null
275
+ && ( currentTick - this._lastExecutiveTick ) < this._executiveInterval
276
+ }
277
+
278
+ // ── Facets ─────────────────────────────────────────────────
279
+
280
+ /**
281
+ * Spawn a focused facet of the executive consciousness.
282
+ *
283
+ * Creates an independent reasoning instance that shares the master's
284
+ * cognitive state (identity, values, beliefs, memories) but operates
285
+ * outside the tick cycle. The facet syncs bidirectionally with the
286
+ * master via cognitive bus events.
287
+ *
288
+ * Returns a handle with report() and subscribe() methods.
289
+ * The caller (PlanningEngine) uses report() to push step outcomes
290
+ * and subscribe() to receive facet decisions.
291
+ */
292
+ spawnFacet(): { attention: 'available' | 'full', handle?: ExecutiveFacetHandle } {
293
+ // Delegate to FacetSupervisor (R5-g-3), passing the current engine
294
+ // attachments. The supervisor owns the registry + attention budget and
295
+ // performs the throw-checks on bus / director / state ref.
296
+ return this._facetSupervisor.spawn( {
297
+ bus: this._bus,
298
+ llmDirector: this._llmDirector,
299
+ stateRef: this._lastStateRef,
300
+ willId: this._willId,
301
+ inbox: this._inbox,
302
+ contextDeps: {
303
+ workingMemory: this._workingMemory,
304
+ goalManager: this._goalManager,
305
+ episodicConsolidator: this._episodicConsolidator,
306
+ semanticIntegrator: this._semanticIntegrator
307
+ },
308
+ promptDeps: {
309
+ summarizer: this._summarizer
310
+ }
311
+ } )
312
+ }
313
+
314
+ // ── CognitiveEngine interface ──────────────────────────────
315
+
316
+ subscribes(): string[] { return [ '*' ] }
317
+ publishes(): CognitiveEventSchema[] { return [] }
318
+
319
+ snapshot(): Record<string, unknown> {
320
+ return {
321
+ bufferSize: this._gatingState.salienceBuffer.length,
322
+ coherenceVersion: this._coherenceVersion,
323
+ lastConfidence: this._lastExecutiveOutput?.confidence ?? 0
324
+ }
325
+ }
326
+
327
+ onCognitiveEvent( event: CognitiveEvent ): StateCommands | void {
328
+ if( event.sourceEngine === this.name ) return
329
+
330
+ // Set attention state for bandwidth allowance
331
+ // One facet per ~0.3 free capacity units, floor at 1
332
+ if( event.type === 'attention.state.changed' ){
333
+ const p = event.payload as { freeFraction: number }
334
+ this._facetSupervisor.setAttentionState( p.freeFraction )
335
+ return
336
+ }
337
+
338
+ // GWT salience-based buffer
339
+ ( event.salience ?? 0 ) >= WORKSPACE_THRESHOLD
340
+ && this._gatingState.salienceBuffer.push( {
341
+ event,
342
+ tick: event.logicalTime ?? 0
343
+ } )
344
+ }
345
+
346
+ // ── AsyncEngine contract ───────────────────────────────────
347
+
348
+ /**
349
+ * Tick entry point (FN11). Before delegating to AsyncEngine.react(), flush any
350
+ * deferred manager side-effects whose tick is now confirmed committed (and drop
351
+ * any whose tick aborted). `state` is the start-of-tick committed snapshot, so
352
+ * its `executive.last_tick` metric reflects whether our prior commands landed.
353
+ */
354
+ override async react(
355
+ delta: Duration,
356
+ tick: Tick,
357
+ state: ReadonlySimulationState,
358
+ context: SimulationContext,
359
+ ): Promise<EngineResult> {
360
+ this._deferred.flush( state, tick as unknown as number )
361
+ this._deferred.markReactTick( tick as unknown as number )
362
+ // Per-tick facet pump: refresh every facet's state ref to THIS tick's frozen
363
+ // snapshot and launch reasoning for queued reports (tick-discipline mode).
364
+ // Sits at a fixed point in the serial engine order — the issue-side twin of
365
+ // the CompletionInbox drain in Phase 2. See .TODO/FACET_REPLAY_DETERMINISM.md.
366
+ this._facetSupervisor.pump( state )
367
+ return super.react( delta, tick, state, context )
368
+ }
369
+
370
+ protected override shouldAct(
371
+ state: ReadonlySimulationState,
372
+ tick: Tick,
373
+ _context: SimulationContext,
374
+ ): boolean {
375
+ // Restore rolling summary from snapshot on first tick
376
+ if( !this._summarizerRestored ){
377
+ this._restoreSummarizer( state )
378
+ this._summarizerRestored = true
379
+ }
380
+
381
+ // Read runtime config overrides from state
382
+ const rtConfig = readRuntimeConfig( state, {
383
+ executiveInterval: this._executiveInterval,
384
+ cooldownTicks: this._cooldownTicks
385
+ } )
386
+ this._gatingState.executiveInterval = rtConfig.executiveInterval
387
+ this._gatingState.cooldownTicks = rtConfig.cooldownTicks
388
+
389
+ // Initialize LLM director if not yet done (requires willId)
390
+ if( !this._llmDirector && this._willId ){
391
+ this._llmDirector = new LLMDirector( {
392
+ willId: this._willId,
393
+ // Per-Will model (from modelTier, resolved in mind.ts). An explicit
394
+ // WILL_LLM_MODEL env still wins (operator pin / self-hosting); the tier
395
+ // model only applies when it's unset.
396
+ model: this._modelId ?? process.env.WILL_LLM_MODEL ?? 'claude-sonnet-4-5-20250929',
397
+ maxOutputTokens: parseInt( process.env.WILL_MAX_OUTPUT_TOKENS ?? '8096' ),
398
+ // Provider-agnostic key; falls back to ANTHROPIC_API_KEY for back-compat.
399
+ apiKey: process.env.WILL_LLM_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? '',
400
+ provider: ( process.env.WILL_LLM_PROVIDER ?? 'anthropic' ) as LLMProvider,
401
+ // Optional base-URL override (e.g. Ollama / Azure / self-hosted). Unset →
402
+ // the director uses the provider's official endpoint.
403
+ baseUrl: process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
404
+ timeoutMs: process.env.WILL_LLM_TIMEOUT_MS ? parseInt( process.env.WILL_LLM_TIMEOUT_MS ) : undefined,
405
+ sessionLogger: this._sessionLogger,
406
+ mock: this._testMode,
407
+ // Inject the per-Will tracker (R4) so live calls record usage here, not
408
+ // through a process global. null is fine — the director skips recording.
409
+ tokenTracker: this._tokenTracker,
410
+ } )
411
+ // Share the director with the summarizer so it uses the same
412
+ // provider, model, and token-tracking as the executive itself.
413
+ this._summarizer?.attachLLMDirector( this._llmDirector )
414
+ }
415
+
416
+ // Evaluate gating
417
+ const gatingDeps: GatingDependencies = {
418
+ generativeModel: this._generativeModel,
419
+ pendingMessages: this._messageQueue.pendingMessages,
420
+ hasPendingWork: this.hasPendingWork
421
+ }
422
+
423
+ const result = evaluateGating( state, tick, gatingDeps, this._gatingState )
424
+
425
+ // Always update counters, even when not activating
426
+ updateGatingState( this._gatingState, state, tick, result.shouldActivate, result.cleanedBuffer )
427
+
428
+ if( result.shouldActivate )
429
+ logger.info( `[executive] activating — reason: ${result.reason} (tick=${tick})` )
430
+
431
+ return result.shouldActivate
432
+ }
433
+
434
+ protected override readState(
435
+ state: ReadonlySimulationState,
436
+ tick: Tick,
437
+ ): ReasoningFootprint {
438
+ const stableTypes = new Set( [ 'goal', 'belief', 'will.identity', 'plan' ] )
439
+ const stableIds = new Set<string>()
440
+
441
+ for( const [ id, entity ] of state.entities )
442
+ stableTypes.has( entity.type ) && stableIds.add( id )
443
+
444
+ return {
445
+ tickObserved: tick,
446
+ entitiesRead: stableIds,
447
+ metricsRead: new Set(),
448
+ entitiesModified: new Set(),
449
+ intendedCommands: {},
450
+ source: this.name
451
+ }
452
+ }
453
+
454
+ protected async reasonAsync(
455
+ footprint: ReasoningFootprint,
456
+ state: ReadonlySimulationState,
457
+ context: SimulationContext,
458
+ stream: IntermediateStream,
459
+ ): Promise<unknown> {
460
+ this._lastStateRef = state
461
+ this._messageQueue.pendingCallStartTick = state.tick
462
+
463
+ // Mark which buffer entries this cycle consumes. Anything pushed after this
464
+ // point arrives during the LLM call and was never part of the broadcast, so
465
+ // onReasoningComplete() preserves it rather than wiping the whole buffer.
466
+ this._consumedBufferEntries = [ ...this._gatingState.salienceBuffer ]
467
+
468
+ // Update all facets with the latest state reference
469
+ this._facetSupervisor.broadcastStateRef( state )
470
+
471
+ // Build executive context using PromptFactory's helper
472
+ const execContext = await PromptFactory.buildFreshContext( {
473
+ workingMemory: this._workingMemory,
474
+ goalManager: this._goalManager,
475
+ episodicConsolidator: this._episodicConsolidator,
476
+ semanticIntegrator: this._semanticIntegrator
477
+ }, state )
478
+
479
+ stream.report( 'context_assembled', {
480
+ workingMemoryItems: execContext.workingMemory.length,
481
+ activeGoals: execContext.goals.length,
482
+ totalEpisodes: this._episodicConsolidator?.getAllEpisodes().length ?? 0,
483
+ totalBeliefs: execContext.beliefs.length
484
+ } )
485
+
486
+ // Compute modulation and uncertainty using PromptFactory
487
+ const qualityModulation = PromptFactory.computeQualityModulation( state )
488
+ stream.report( 'modulation', { qualityModulation } )
489
+
490
+ const epistemicUncertainty = PromptFactory.computeEpistemicUncertainty( execContext, state )
491
+ this._lastEpistemicUncertainty = epistemicUncertainty
492
+
493
+ // Dual-process control (a-priori effort gate). Decide BEFORE the call whether the
494
+ // master reasons fast (System 1) or deliberately (System 2). All inputs are already
495
+ // computed this tick, so the choice is free and deterministic (R2). The threshold is
496
+ // the *effective* one (base ⊕ persona-prior): an analytical Will develops a lower
497
+ // threshold and deliberates more readily — effort allocation as a developing trait.
498
+ const deliberateThreshold = readEffectiveParams( state, 'engine-config-executive' ).deliberateThreshold ?? DELIBERATE_THRESHOLD
499
+ const processSelection = selectProcess( {
500
+ epistemicUncertainty,
501
+ priorConfidence: this._lastExecutiveOutput?.confidence ?? 0.5,
502
+ novelty: state.metrics.get( 'perception.novelty' ) ?? 0,
503
+ stressLoad: state.metrics.get( 'stress.load' ) ?? 0,
504
+ hasPendingMessage: this._messageQueue.pendingMessages.length > 0,
505
+ }, deliberateThreshold )
506
+ stream.report( 'process_selected', {
507
+ process: processSelection.process,
508
+ effortScore: processSelection.effortScore,
509
+ reason: processSelection.reason,
510
+ } )
511
+
512
+ // Build focus for master executive
513
+ const focus: FocusSection = {
514
+ title: 'Core Consciousness',
515
+ content: 'You are the master executive — the unified cognitive core of a self-aware simulated mind.',
516
+ outputFormat: undefined, // Use default format
517
+ instructions: undefined
518
+ }
519
+
520
+ // Build system prompt using PromptFactory
521
+ const promptDeps: PromptDependencies = {
522
+ summarizer: this._summarizer
523
+ }
524
+
525
+ const systemPrompt = PromptFactory.buildSystemPrompt( {
526
+ context: execContext,
527
+ focus,
528
+ deps: promptDeps,
529
+ mode: 'master'
530
+ } )
531
+
532
+ // System 2 (deliberate) — propose pass. When the effort gate engaged deliberation,
533
+ // first generate a divergent candidate set at elevated temperature. This call is
534
+ // non-streaming (internal scratch — nothing leaks to the user) and reuses the SAME
535
+ // system prompt (so it shares the prompt cache) but swaps in the ideation output
536
+ // format, so it sees the full situational context yet asks for options, not a
537
+ // decision. R2-safe: a second same-tick call with a distinct prompt gets its own
538
+ // replay entry. A failure degrades gracefully to System 1 (no candidates injected).
539
+ let ideationCandidates: IdeationCandidate[] | undefined
540
+ if( processSelection.process === 'deliberate' && this._llmDirector ){
541
+ const ideationStart = wallClock()
542
+ const ideationUserMessage = PromptFactory.buildUserMessage( {
543
+ context: execContext,
544
+ state,
545
+ qualityModulation,
546
+ epistemicUncertainty,
547
+ pendingMessages: [ ...this._messageQueue.pendingMessages ],
548
+ focus,
549
+ deps: promptDeps,
550
+ recentActionTypes: [ ...this._recentActionTypes ],
551
+ mode: 'master',
552
+ outputFormat: PromptFactory.buildIdeationFormatInstruction(),
553
+ } )
554
+ // Propose temperature scales with the creativity trait (TODO #4): a creative Will
555
+ // diverges harder when generating options. Reads the live self-model trait, so it
556
+ // rises for free as creativity develops. The propose pass itself is the shared
557
+ // master+facet helper (graceful degradation lives there).
558
+ const proposeTemperature = ideationTemperature( execContext.identity.traits[ 'creativity' ] ?? 0.5 )
559
+ ideationCandidates = await proposeCandidates( {
560
+ director: this._llmDirector,
561
+ systemPrompt,
562
+ ideationUserMessage,
563
+ tick: state.tick,
564
+ proposeTemperature,
565
+ meta: { category: 'executive', attribute: 'master', function: 'ideation' },
566
+ } )
567
+ logger.info(
568
+ `[executive] ◆ deliberate propose tick=${state.tick} ` +
569
+ `candidates=${ideationCandidates?.length ?? 0} temp=${proposeTemperature.toFixed( 2 )} latency=${wallClock() - ideationStart}ms`
570
+ )
571
+ }
572
+ stream.report( 'ideation_complete', {
573
+ process: processSelection.process,
574
+ candidateCount: ideationCandidates?.length ?? 0,
575
+ } )
576
+
577
+ // Build user message — includes live state, dynamic guidance, focus context, and
578
+ // output format. On the deliberate path the propose pass's candidates are injected
579
+ // so this (the decision/evaluate pass) weighs concrete options before committing.
580
+ const userMessage = PromptFactory.buildUserMessage( {
581
+ context: execContext,
582
+ state,
583
+ qualityModulation,
584
+ epistemicUncertainty,
585
+ pendingMessages: [ ...this._messageQueue.pendingMessages ],
586
+ focus,
587
+ deps: promptDeps,
588
+ recentActionTypes: [ ...this._recentActionTypes ],
589
+ mode: 'master',
590
+ ideationCandidates
591
+ } )
592
+
593
+ this._sessionLogger?.write( {
594
+ type: 'executive.call',
595
+ tick: state.tick,
596
+ promptChars: systemPrompt.length + userMessage.length,
597
+ promptTokensEst: Math.round( ( systemPrompt.length + userMessage.length ) / 4 ),
598
+ systemChars: systemPrompt.length,
599
+ userChars: userMessage.length,
600
+ // D2: context counts for per-tick cognitive state snapshot
601
+ workingMemoryItems: execContext.workingMemory.length,
602
+ goalCount: execContext.goals.length,
603
+ pendingMessages: this._messageQueue.pendingMessages.length,
604
+ beliefCount: execContext.beliefs.length,
605
+ beliefsOmitted: execContext.beliefsOmitted,
606
+ promptPath: this._llmDirector?.writeDebugPrompt( state.tick, systemPrompt, userMessage ) ?? ''
607
+ } )
608
+
609
+ // Call LLM
610
+ if( !this._llmDirector )
611
+ throw new Error( 'LLM director not initialized — willId must be set before first tick' )
612
+
613
+ const llmStart = wallClock() // perf timing only — latency is telemetry, never replay state
614
+ let executiveOutput: ExecutiveOutputFull
615
+
616
+ try {
617
+ // Use streaming call when clients are connected (F3); fall back to regular call.
618
+ const masterMeta = { category: 'executive', attribute: 'master', function: 'decision' }
619
+ const result = this._chunkBroadcaster
620
+ ? await this._llmDirector.callStream( systemPrompt, userMessage, state.tick, this._chunkBroadcaster, undefined, masterMeta )
621
+ : await this._llmDirector.call( systemPrompt, userMessage, state.tick, undefined, masterMeta )
622
+
623
+ logger.info(
624
+ `[executive] ✓ tick=${state.tick} ` +
625
+ `in=${result.inputTok} tok out=${result.outputTok} tok ` +
626
+ `latency=${wallClock() - llmStart}ms`
627
+ )
628
+
629
+ // token-report.jsonl is now written by the TokenTracker for every call
630
+ // (master, facets, summarizer, embedding) with full attribution + cost —
631
+ // no longer the master-only writeTokenReport().
632
+
633
+ // C2: persist full response text for session auditing
634
+ this._llmDirector.writeDebugResponse(
635
+ state.tick, result.text,
636
+ result.inputTok, result.outputTok, wallClock() - llmStart
637
+ )
638
+
639
+ const responsePath = this._willId
640
+ ? `./data/wills/${this._willId}/debug/response-tick-${String( state.tick ).padStart( 6, '0' )}.txt`
641
+ : ''
642
+ this._sessionLogger?.write( {
643
+ type: 'executive.response',
644
+ tick: state.tick,
645
+ latencyMs: wallClock() - llmStart,
646
+ responseChars: result.text.length,
647
+ promptTokens: result.inputTok,
648
+ completionTokens: result.outputTok,
649
+ responseExcerpt: result.text.slice( 0, 600 ),
650
+ responsePath,
651
+ } )
652
+
653
+ // Parse the response
654
+ executiveOutput = parseResponse( result.text, state, this._recentActionTypes )
655
+
656
+ // System 2 — retain the considered set on the committed output (guaranteed, not
657
+ // dependent on the model echoing it back): explainability now, regret/counterfactual
658
+ // substrate later. Undefined on the System 1 fast path.
659
+ if( ideationCandidates && ideationCandidates.length > 0 )
660
+ executiveOutput.consideredAlternatives = ideationCandidates.map( c => c.approach || c.description )
661
+ }
662
+ catch( err: unknown ){
663
+ const msg = err instanceof Error ? err.message : String( err )
664
+ logger.error( `[executive] LLM call failed: ${msg.slice( 0, 200 )}` )
665
+
666
+ this._sessionLogger?.write( {
667
+ type: 'executive.response',
668
+ tick: state.tick,
669
+ latencyMs: wallClock() - llmStart,
670
+ error: msg.slice( 0, 300 )
671
+ } )
672
+
673
+ // Use fallback
674
+ executiveOutput = buildFallbackOutput( state, this._recentActionTypes )
675
+ }
676
+
677
+ // Log the parsed output
678
+ this._sessionLogger?.write( {
679
+ type: 'executive.output',
680
+ tick: state.tick,
681
+ confidence: executiveOutput.confidence,
682
+ reasoning: executiveOutput.reasoning.slice( 0, 1000 ),
683
+ actions: executiveOutput.actions,
684
+ newBeliefs: executiveOutput.newBeliefs ?? [],
685
+ plansCount: executiveOutput.plans?.length ?? 0,
686
+ goalsNew: executiveOutput.newGoals ?? [],
687
+ goalsAbandon: executiveOutput.goalsToAbandon ?? [],
688
+ replies: ( executiveOutput.conversationReplies ?? [] ).map( r => ( {
689
+ targetEntityId: r.targetEntityId,
690
+ targetEntityName: r.targetEntityName,
691
+ messages: r.messages,
692
+ } ) ),
693
+ hasIntrospection: !!executiveOutput.introspection,
694
+ hasNarrative: !!executiveOutput.narrative
695
+ } )
696
+
697
+ stream.report( 'executive_complete', {
698
+ actionCount: executiveOutput.actions.length,
699
+ planCount: executiveOutput.plans?.length ?? 0,
700
+ newBeliefCount: executiveOutput.newBeliefs?.length ?? 0,
701
+ hasIntrospection: !!executiveOutput.introspection,
702
+ hasNarrative: !!executiveOutput.narrative
703
+ } )
704
+
705
+ return executiveOutput
706
+ }
707
+
708
+ protected override onIntermediateResult(
709
+ step: string,
710
+ result: unknown,
711
+ _footprint: ReasoningFootprint,
712
+ _context: SimulationContext,
713
+ ): StateCommands | null {
714
+ const data = result as Record<string, unknown>
715
+
716
+ if( step === 'context_assembled' )
717
+ return {
718
+ metrics: [
719
+ [ 'executive.phase', 0 ],
720
+ [ 'executive.context_items', ( data.workingMemoryItems as number ) ?? 0 ]
721
+ ]
722
+ }
723
+
724
+ if( step === 'modulation' )
725
+ return {
726
+ metrics: [
727
+ [ 'executive.phase', 1 ],
728
+ [ 'executive.quality_modulation', ( data.qualityModulation as number ) ?? 1 ]
729
+ ]
730
+ }
731
+
732
+ if( step === 'process_selected' )
733
+ return {
734
+ metrics: [
735
+ [ 'executive.process', ( data.process as string ) === 'deliberate' ? 1 : 0 ],
736
+ [ 'executive.effort_score', ( data.effortScore as number ) ?? 0 ]
737
+ ]
738
+ }
739
+
740
+ if( step === 'ideation_complete' )
741
+ return {
742
+ metrics: [
743
+ [ 'executive.deliberate_candidates', ( data.candidateCount as number ) ?? 0 ]
744
+ ]
745
+ }
746
+
747
+ if( step === 'executive_complete' )
748
+ return {
749
+ metrics: [
750
+ [ 'executive.phase', 2 ],
751
+ [ 'executive.action_count', ( data.actionCount as number ) ?? 0 ],
752
+ [ 'executive.plan_count', ( data.planCount as number ) ?? 0 ],
753
+ [ 'executive.new_belief_count', ( data.newBeliefCount as number ) ?? 0 ],
754
+ [ 'executive.has_introspection', ( data.hasIntrospection as boolean ) ? 1 : 0 ],
755
+ [ 'executive.has_narrative', ( data.hasNarrative as boolean ) ? 1 : 0 ]
756
+ ]
757
+ }
758
+
759
+ return null
760
+ }
761
+
762
+ protected override onReasoningComplete(
763
+ output: unknown,
764
+ footprint: ReasoningFootprint,
765
+ _context: SimulationContext,
766
+ ): StateCommands {
767
+ const executiveOutput = output as ExecutiveOutputFull
768
+
769
+ this._lastExecutiveOutput = executiveOutput
770
+ this._lastExecutiveTick = footprint.tickObserved
771
+ this._coherenceVersion++
772
+
773
+ logger.info(
774
+ `[executive] reasoning complete — tick=${footprint.tickObserved}` +
775
+ ` actions=${executiveOutput.actions.length}` +
776
+ ` plans=${executiveOutput.plans?.length ?? 0}` +
777
+ ` beliefs=${executiveOutput.newBeliefs?.length ?? 0}` +
778
+ ` hasIntrospection=${!!executiveOutput.introspection}` +
779
+ ` hasNarrative=${!!executiveOutput.narrative}`
780
+ )
781
+
782
+ // ── Flush pending escalation percepts ──────────────────────
783
+ // Convert buffered audition.task.signal events into high-salience
784
+ // percept entities so Exteroception surfaces them as
785
+ // "## Percepts (What You Notice)" on the NEXT master cycle.
786
+ // The master sees them as environmental signals — not as messages to reply to.
787
+ // It responds by creating plans/goals, never by emitting [REPLY].
788
+ const { percepts: escalationPercepts, requester: escalationRequester } =
789
+ this._escalations.drainToPercepts()
790
+
791
+ // Build state commands
792
+ const commandDeps: CommandDependencies = {
793
+ goalManager: this._goalManager,
794
+ semanticIntegrator: this._semanticIntegrator,
795
+ summarizer: this._summarizer,
796
+ bus: this._bus,
797
+ salience: this._model,
798
+ requestingEntityId: escalationRequester?.entityId,
799
+ requestingThreadId: escalationRequester?.threadId,
800
+ }
801
+
802
+ const { commands, effects } = buildStateCommands(
803
+ executiveOutput,
804
+ footprint,
805
+ this._lastStateRef!,
806
+ commandDeps,
807
+ this._recentActionTypes
808
+ )
809
+
810
+ // FN11: do NOT run the manager writes now — they mirror `commands`, which a
811
+ // pre-commit validator can still abort. Queue them; the DeferredEffectQueue
812
+ // runs them on the next react() once this tick is confirmed committed.
813
+ this._deferred.enqueue( footprint.tickObserved as unknown as number, effects )
814
+
815
+ // Publish cognitive events
816
+ publishCognitiveEvents(
817
+ executiveOutput,
818
+ footprint,
819
+ this._bus,
820
+ this._coherenceVersion,
821
+ this._model,
822
+ )
823
+
824
+ // Publish executive.active
825
+ this._bus?.publish( {
826
+ type: 'executive.active',
827
+ version: 1,
828
+ sourceEngine: this.name,
829
+ salience: 0.7,
830
+ payload: { tick: footprint.tickObserved }
831
+ } )
832
+
833
+ // ── Voluntary attention regulation (Option C) ────────────
834
+ // The mind explicitly chooses how much cognitive capacity to engage through
835
+ // its action vocabulary: a `focus` action mobilizes attention (more parallel
836
+ // facets); `rest`/`sleep`/`wait`/`meditate` stand it down (fewer facets,
837
+ // conserve energy/tokens). Absent either, the AttentionAllocator decays effort
838
+ // back to baseline. Replay-safe — derived from recorded LLM output. Vitals
839
+ // still cap the result (energy/sleep collapse the ceiling; `focus` is gated on
840
+ // energy), so this cannot override a body compelled to rest.
841
+ if( this._bus ){
842
+ const effortTarget = effortTargetForActions( executiveOutput.actions.map( a => a.type ) )
843
+ if( effortTarget != null )
844
+ this._bus.publish( {
845
+ type: 'attention.regulate',
846
+ version: 1,
847
+ sourceEngine: this.name,
848
+ salience: 0.6,
849
+ payload: { effortTarget }
850
+ } )
851
+ }
852
+
853
+ // ── Sync to all active facets ────────────────────────────
854
+ if( this._bus && this._facetSupervisor.size > 0 )
855
+ this._bus.publish( {
856
+ type: 'executive.master.sync',
857
+ version: 1,
858
+ sourceEngine: this.name,
859
+ salience: 0.8,
860
+ payload: {
861
+ reasoning: executiveOutput.reasoning.slice( 0, 600 ),
862
+ confidence: executiveOutput.confidence,
863
+ actionTypes: executiveOutput.actions.map( a => a.type ),
864
+ coherenceVersion: this._coherenceVersion,
865
+ tick: footprint.tickObserved
866
+ }
867
+ } )
868
+
869
+ // Clear processed messages
870
+ this._messageQueue.clearProcessedMessages()
871
+
872
+ // Merge escalation percepts into final commands
873
+ if( escalationPercepts.length ){
874
+ commands.set ??= []
875
+ commands.set.push( ...escalationPercepts )
876
+ }
877
+
878
+ // Track entities modified
879
+ if( commands.set?.length )
880
+ for( const entity of commands.set )
881
+ ( footprint.entitiesModified as Set<string> ).add( entity.id )
882
+
883
+ // Retire only the entries this cycle consumed. Events that landed during the
884
+ // LLM call were never represented in the broadcast context, so they survive to
885
+ // compete on the next cycle (the BUFFER_MAX_AGE_TICKS filter still ages them out).
886
+ const consumed = new Set( this._consumedBufferEntries )
887
+ this._gatingState.salienceBuffer = this._gatingState.salienceBuffer.filter( e => !consumed.has( e ) )
888
+ this._consumedBufferEntries = []
889
+
890
+ return commands
891
+ }
892
+
893
+ // ── Private helpers ────────────────────────────────────────
894
+
895
+ private _ensureFacetSyncSubscription(): void {
896
+ if( this._facetSyncSubscribed || !this._bus ) return
897
+ this._facetSyncSubscribed = true
898
+
899
+ // ── executive.facet.sync ─────────────────────────────────────
900
+ // Facet sync events enter the salience buffer so the master
901
+ // can re-evaluate when enough facet activity accumulates.
902
+ this._bus.subscribe(
903
+ this.name,
904
+ [ 'executive.facet.sync' ],
905
+ ( event ) => {
906
+ const payload = event.payload as {
907
+ facetId?: string
908
+ reasoning?: string
909
+ confidence?: number
910
+ tick?: number
911
+ }
912
+
913
+ const syntheticEvent = {
914
+ id: '',
915
+ type: 'executive.facet.sync',
916
+ version: 1,
917
+ sequenceNumber: 1,
918
+ sourceEngine: `executive-facet-${payload.facetId ?? 'unknown'}`,
919
+ salience: Math.max( 0.5, payload.confidence ?? 0.5 ),
920
+ payload,
921
+ wallTime: payload.tick as number,
922
+ logicalTime: payload.tick as number
923
+ }
924
+
925
+ this._gatingState.salienceBuffer.push( {
926
+ event: syntheticEvent,
927
+ tick: payload.tick ?? 0
928
+ } )
929
+
930
+ logger.info(
931
+ `[executive] master received facet sync from ${payload.facetId} ` +
932
+ `(confidence=${payload.confidence?.toFixed( 2 )})`
933
+ )
934
+ }
935
+ )
936
+
937
+ // ── audition.task.signal ─────────────────────────────────────
938
+ // Published by AuditionEngine when a conversation facet emits the
939
+ // 'escalate' action type — signalling that the conversation revealed
940
+ // a task requiring the master's cognitive machinery (plan creation,
941
+ // goal reprioritization, identity reflection).
942
+ //
943
+ // IMPORTANT — master stays out of the reply path entirely:
944
+ // • The conversation facet has already sent (or will send) the
945
+ // acknowledgement to the user ("Got it, I'll get started on that.").
946
+ // • The master's job is purely cognitive: create a [PLANS] block,
947
+ // update goals, or reflect. Any follow-up communication to the user
948
+ // flows through plan step execution (effector: 'text') via
949
+ // ActionExecutor → ProactiveCommunicator — NEVER via [REPLY].
950
+ //
951
+ // Implementation:
952
+ // • Write a high-salience 'percept' entity to simulation state so
953
+ // Exteroception surfaces it under "## Percepts (What You Notice)".
954
+ // • Spike the salience buffer so the master fires soon.
955
+ // • Do NOT push into _messageQueue.pendingMessages — that would
956
+ // cause the master to produce a [REPLY], creating a duplicate
957
+ // message and breaking the facet/master communication boundary.
958
+ this._bus.subscribe(
959
+ this.name,
960
+ [ 'audition.task.signal' ],
961
+ ( event ) => {
962
+ const payload = event.payload as {
963
+ entityId: string
964
+ threadId: string
965
+ reasoning: string
966
+ confidence: number
967
+ }
968
+
969
+ // Write a percept entity so Exteroception surfaces this in
970
+ // "## Percepts (What You Notice)" — master sees it as an
971
+ // environmental signal prompting cognitive work, not a reply.
972
+ if( this._lastStateRef ){
973
+ // State is read-only here; we can't write directly.
974
+ // Instead, buffer the escalation for injection on next tick.
975
+ // The master's shouldAct() will fire due to the salience spike below.
976
+ // The actual percept write happens in onReasoningComplete() via
977
+ // a synthetic percept we store here and emit as a StateCommand.
978
+ this._escalations.push({
979
+ entityId: payload.entityId,
980
+ threadId: payload.threadId,
981
+ reasoning: payload.reasoning.slice( 0, 400 ),
982
+ tick: this._lastExecutiveTick ?? 0,
983
+ })
984
+ }
985
+
986
+ // Spike the salience buffer so the master fires soon rather than
987
+ // waiting for the next scheduled interval.
988
+ const syntheticEvent = {
989
+ id: '',
990
+ type: 'audition.task.signal',
991
+ version: 1,
992
+ sequenceNumber: 1,
993
+ sourceEngine: 'audition-engine',
994
+ salience: event.salience ?? 0.9,
995
+ payload,
996
+ wallTime: wallClock(), // telemetry field; logicalTime carries the deterministic clock
997
+ logicalTime: this._lastExecutiveTick ?? 0
998
+ }
999
+
1000
+ this._gatingState.salienceBuffer.push( {
1001
+ event: syntheticEvent,
1002
+ tick: this._lastExecutiveTick ?? 0
1003
+ } )
1004
+
1005
+ logger.info(
1006
+ `[executive] master queued escalation percept from entity ${payload.entityId} ` +
1007
+ `(confidence=${payload.confidence.toFixed( 2 )})`
1008
+ )
1009
+ }
1010
+ )
1011
+ }
1012
+
1013
+ private _restoreSummarizer( state: ReadonlySimulationState ): void {
1014
+ if( !this._summarizer ) return
1015
+
1016
+ const entity = state.entities.get( 'executive-rolling-summary' )
1017
+ if( !entity ) return
1018
+
1019
+ const m = entity.metadata ?? {}
1020
+ const summary = ( m[ 'summary' ] as string ) ?? ''
1021
+ const buffer = ( m[ 'buffer' ] as string[] ) ?? []
1022
+ const callCount = ( m[ 'callCount' ] as number ) ?? 0
1023
+
1024
+ this._summarizer.restore( summary, buffer, callCount )
1025
+ if( summary )
1026
+ logger.info( `[executive] summarizer restored (${summary.length} chars, ${callCount} prior calls)` )
1027
+ }
1028
+ }