@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,90 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/escalation.buffer.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // EscalationBuffer — pending audition→executive escalations (R5-g-2).
6
+ //
7
+ // The AuditionEngine publishes `audition.task.signal` events when a facet
8
+ // conversation surfaces work the master should own. Those events arrive
9
+ // between tick boundaries, while state is read-only, so they can't be written
10
+ // as percepts directly. This buffer holds them until the next
11
+ // onReasoningComplete(), where they are drained into high-salience percept
12
+ // entities (StateCommands.set) so Exteroception surfaces them as
13
+ // "## Percepts (What You Notice)" on the following master cycle.
14
+ //
15
+ // The master reads these as environmental signals — NEVER as incoming
16
+ // messages — and responds by creating plans/goals, never by emitting [REPLY];
17
+ // the facet handles the actual communication.
18
+ //
19
+ // Extracted verbatim from ExecutiveEngine (R5-g-2) as a pure collaborator.
20
+ // The salience-spike that wakes the master after a push stays in the engine
21
+ // (it touches gating state, not this buffer).
22
+ // ─────────────────────────────────────────────────────────────
23
+
24
+ import type { EntityInput } from '#core/types'
25
+
26
+ export interface PendingEscalation {
27
+ entityId: string
28
+ threadId: string
29
+ reasoning: string
30
+ tick: number
31
+ }
32
+
33
+ /** Requester context captured from the first buffered escalation, used to tag new goals. */
34
+ export interface EscalationRequester {
35
+ entityId: string
36
+ threadId: string
37
+ }
38
+
39
+ export interface DrainedEscalations {
40
+ /** High-salience percept entities to merge into StateCommands.set. */
41
+ percepts: EntityInput[]
42
+ /** First escalation's requester context, or undefined when the buffer was empty. */
43
+ requester?: EscalationRequester
44
+ }
45
+
46
+ export class EscalationBuffer {
47
+ private _pending: PendingEscalation[] = []
48
+
49
+ /** Buffer one escalation for injection on the next master cycle. */
50
+ push( escalation: PendingEscalation ): void {
51
+ this._pending.push( escalation )
52
+ }
53
+
54
+ get size(): number { return this._pending.length }
55
+ get isEmpty(): boolean { return this._pending.length === 0 }
56
+
57
+ /**
58
+ * Convert every buffered escalation into a high-salience percept entity and
59
+ * clear the buffer. Returns the percepts plus the first escalation's
60
+ * requester context (used to tag goals the master creates in response).
61
+ */
62
+ drainToPercepts(): DrainedEscalations {
63
+ const percepts: EntityInput[] = []
64
+ for( const esc of this._pending ){
65
+ percepts.push({
66
+ id: `escalation-percept-${esc.entityId}-${esc.tick}`,
67
+ type: 'percept',
68
+ metadata: {
69
+ category: 'task-escalation',
70
+ summary: `[Task from conversation with ${esc.entityId}] ${esc.reasoning}`,
71
+ salience: 0.85,
72
+ source: 'audition-facet',
73
+ entityId: esc.entityId,
74
+ threadId: esc.threadId,
75
+ // Guides the master's response: plan, don't reply
76
+ directive: 'Create a plan or update goals. Do not emit [REPLY] — the facet handles communication.',
77
+ }
78
+ })
79
+ }
80
+
81
+ // Capture requester context before clearing — used to tag new goals.
82
+ const first = this._pending[0]
83
+ const requester: EscalationRequester | undefined =
84
+ first ? { entityId: first.entityId, threadId: first.threadId } : undefined
85
+
86
+ this._pending = []
87
+
88
+ return { percepts, requester }
89
+ }
90
+ }
@@ -0,0 +1,271 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/facet.supervisor.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // FacetSupervisor — facet lifecycle + attention budget (R5-g-3).
6
+ //
7
+ // A "facet" is a focused instance of the executive consciousness: an
8
+ // independent ExecutiveFacet reasoning loop that shares the master's
9
+ // cognitive state (identity, values, beliefs, memories) but runs OUTSIDE the
10
+ // tick cycle. PlanningEngine / AuditionEngine spawn facets to pursue work in
11
+ // parallel with the master; the master stays in charge of the global
12
+ // workspace.
13
+ //
14
+ // This collaborator owns the facet registry and the attention budget that
15
+ // caps how many may run at once:
16
+ // • _facets — live facet instances, keyed by id
17
+ // • _facetCounter — monotonic id source (facet-1, facet-2, …)
18
+ // • _attentionFreeCapacity — free attention (0–1), updated from
19
+ // `attention.state.changed`; one facet is allowed per ~0.3 free units.
20
+ //
21
+ // It also keeps `_lastStateRef` / `_sessionLogger` fresh so a facet's
22
+ // deferred destroy() logs against the same live values the engine would —
23
+ // broadcastStateRef() runs in the engine's reasonAsync (the only place the
24
+ // engine's own state ref changes), so the two never diverge.
25
+ //
26
+ // Deliberately left in the engine (not here):
27
+ // • the `executive.facet.sync` / `audition.task.signal` bus subscriptions
28
+ // and the `_facetSyncSubscribed` guard — they push into the engine's
29
+ // gating salience buffer and are shared with the escalation path;
30
+ // • the `executive.master.sync` publish — it reads the master's reasoning
31
+ // output; the engine publishes it gated on `size`.
32
+ //
33
+ // Extracted from ExecutiveEngine (R5-g-3) as a delegating collaborator.
34
+ // `ExecutiveEngine.spawnFacet()` forwards here with an unchanged signature.
35
+ // ─────────────────────────────────────────────────────────────
36
+
37
+ import { logger } from '#core/logger'
38
+ import type { ReadonlySimulationState } from '#core/types'
39
+ import type { CognitiveBus } from '#cognition/bus'
40
+ import type { LLMDirector } from '#llm/index'
41
+ import type { SessionLogger } from '#stem/tracts/session.logger'
42
+ import { ExecutiveFacet, type ExecutiveFacetHandle } from '#faculties/executive.engine/facet'
43
+ import type { CompletionInbox } from '#cognition/completion.inbox'
44
+ import type { ContextDependencies } from '#faculties/executive.engine/context'
45
+ import type { PromptDependencies } from '#faculties/executive.engine/prompt.factory'
46
+
47
+ /**
48
+ * The engine attachments a spawn needs, captured at call time. bus / director
49
+ * / stateRef are required (spawn throws when any is missing); contextDeps /
50
+ * promptDeps are built fresh by the engine from its current manager handles.
51
+ */
52
+ export interface FacetSpawnDeps {
53
+ bus: CognitiveBus | null
54
+ llmDirector: LLMDirector | null
55
+ stateRef: ReadonlySimulationState | null
56
+ contextDeps: ContextDependencies
57
+ promptDeps: PromptDependencies
58
+ willId: string | null
59
+ /** Tick-boundary landing for decision effects (see cognition/completion.inbox). */
60
+ inbox?: CompletionInbox | null
61
+ }
62
+
63
+ export type SpawnResult =
64
+ { attention: 'available' | 'full', handle?: ExecutiveFacetHandle }
65
+
66
+ export class FacetSupervisor {
67
+ private _facets = new Map<string, ExecutiveFacet>()
68
+ private _facetCounter = 0
69
+ private _attentionFreeCapacity = 1
70
+
71
+ // Kept fresh so a facet's (later) destroy() logs against the same live
72
+ // values the engine would — see broadcastStateRef / attachSessionLogger.
73
+ private _lastStateRef: ReadonlySimulationState | null = null
74
+ private _sessionLogger: SessionLogger | null = null
75
+
76
+ /** Reap callbacks per facet — fired when the supervisor reaps (idle/LRU), not on explicit destroy(). */
77
+ private _onReaped = new Map<string, () => void>()
78
+ /** Ticks of inactivity before a facet is reaped (reclaims its attention budget). */
79
+ private readonly _idleTtlTicks: number
80
+ /** When the budget is full, evict the least-recently-active facet instead of refusing a spawn. */
81
+ private readonly _evictLruOnPressure: boolean
82
+
83
+ constructor( opts: { idleTtlTicks?: number; evictLruOnPressure?: boolean } = {} ){
84
+ this._idleTtlTicks = opts.idleTtlTicks ?? 50
85
+ this._evictLruOnPressure = opts.evictLruOnPressure ?? true
86
+ }
87
+
88
+ /** Number of live facets — the engine gates `master.sync` on this. */
89
+ get size(): number { return this._facets.size }
90
+
91
+ attachSessionLogger( logger: SessionLogger | null ): void {
92
+ this._sessionLogger = logger
93
+ }
94
+
95
+ /**
96
+ * Update the attention budget from an `attention.state.changed` event.
97
+ *
98
+ * `freeFraction` is the allocator's normalized 0–1 spare-attention signal
99
+ * (free capacity ÷ baseline capacity). It is consumed directly — one facet per
100
+ * ~0.3 free units — so the budget binds on the same scale the `0.3` constant
101
+ * and the default (`1` → 3 facets) assume. (Pre-fix this received the raw 0–100
102
+ * capacity, inflating the budget ~100× so facets were bounded only by TTL/LRU.)
103
+ */
104
+ setAttentionState( freeFraction: number ): void {
105
+ this._attentionFreeCapacity = Math.max( 0, freeFraction )
106
+ }
107
+
108
+ /** Push the latest state reference to every live facet (called each reasonAsync). */
109
+ broadcastStateRef( state: ReadonlySimulationState ): void {
110
+ this._lastStateRef = state
111
+ for( const facet of this._facets.values() )
112
+ facet.setStateRef( state )
113
+
114
+ // Idle reaper: reclaim facets with no activity within the TTL window so a
115
+ // quiet conversation never leaks attention budget forever. Sim tick only.
116
+ this._reapIdle( state.tick as unknown as number )
117
+ }
118
+
119
+ /**
120
+ * Per-tick pump — called from ExecutiveEngine.react() with the tick's frozen
121
+ * snapshot. Refreshes every facet's state reference AND launches reasoning for
122
+ * their queued reports (tick-discipline mode), in facet-creation order — a
123
+ * fixed point in the serial engine schedule, so issue timing is deterministic.
124
+ * Subsumes broadcastStateRef for the per-tick path; broadcastStateRef remains
125
+ * for the master's mid-reasonAsync refresh.
126
+ */
127
+ pump( state: ReadonlySimulationState ): void {
128
+ this._lastStateRef = state
129
+ for( const facet of this._facets.values() )
130
+ facet.pump( state )
131
+
132
+ this._reapIdle( state.tick as unknown as number )
133
+ }
134
+
135
+ private _reapIdle( tick: number ): void {
136
+ for( const [ id, facet ] of [ ...this._facets ] )
137
+ if( tick - facet.lastActiveTick > this._idleTtlTicks )
138
+ this._reap( id, 'idle' )
139
+ }
140
+
141
+ /** Destroy + deregister a facet and notify its owner. Shared by the reaper + LRU eviction. */
142
+ private _reap( facetId: string, reason: 'idle' | 'lru' ): void {
143
+ const facet = this._facets.get( facetId )
144
+ if( !facet ) return
145
+
146
+ facet.destroy()
147
+ this._facets.delete( facetId )
148
+ const onReaped = this._onReaped.get( facetId )
149
+ this._onReaped.delete( facetId )
150
+
151
+ logger.info( `[executive] facet ${facetId} reaped (${reason}); remaining: ${this._facets.size}` )
152
+ this._sessionLogger?.write({
153
+ type: 'executive.facet.destroy',
154
+ tick: this._lastStateRef?.tick as unknown as number ?? 0,
155
+ facetId,
156
+ reason,
157
+ totalFacets: this._facets.size,
158
+ } as any)
159
+
160
+ // Notify the owner (e.g. AuditionEngine) so it drops its handle + session state.
161
+ try { onReaped?.() }
162
+ catch( err ){ logger.error( `[executive] facet ${facetId} onReaped error:`, err ) }
163
+ }
164
+
165
+ private _leastRecentlyActive(): string | null {
166
+ let id: string | null = null
167
+ let min = Infinity
168
+ for( const [ fid, facet ] of this._facets )
169
+ if( facet.lastActiveTick < min ){ min = facet.lastActiveTick; id = fid }
170
+ return id
171
+ }
172
+
173
+ /**
174
+ * Spawn a focused facet of the executive consciousness.
175
+ *
176
+ * Creates an independent reasoning instance that shares the master's
177
+ * cognitive state (identity, values, beliefs, memories) but operates
178
+ * outside the tick cycle. The facet syncs bidirectionally with the
179
+ * master via cognitive bus events.
180
+ *
181
+ * Returns a handle with report() and subscribe() methods.
182
+ * The caller (PlanningEngine) uses report() to push step outcomes
183
+ * and subscribe() to receive facet decisions.
184
+ */
185
+ spawn( deps: FacetSpawnDeps ): SpawnResult {
186
+ if( !deps.bus )
187
+ throw new Error( 'Cannot spawn facet — CognitiveBus not attached. Was addEngine() called?' )
188
+
189
+ if( !deps.llmDirector )
190
+ throw new Error( 'Cannot spawn facet — LLM director not initialized. Set willId first.' )
191
+
192
+ if( !deps.stateRef )
193
+ throw new Error( 'Cannot spawn facet — no state snapshot available. Wait for first tick.' )
194
+
195
+ // Authoritative current state ref from the engine — keep it so a later
196
+ // destroy() logs the same tick the engine would.
197
+ this._lastStateRef = deps.stateRef
198
+
199
+ // One facet per ~0.3 free capacity units, floor at 1
200
+ const maxFacets = Math.max( 1, Math.floor( this._attentionFreeCapacity / 0.3 ) )
201
+ if( this._facets.size >= maxFacets ){
202
+ if( !this._evictLruOnPressure ){
203
+ logger.info( `[executive] attention full (${this._facets.size}/${maxFacets} facets) ` )
204
+ return { attention: 'full' }
205
+ }
206
+ // Budget full but a new conversation arrived — evict the least-recently-active
207
+ // facet so a fresh, live conversation preempts a stale one (no silent drop).
208
+ const lru = this._leastRecentlyActive()
209
+ if( !lru ) return { attention: 'full' } // nothing evictable (shouldn't happen)
210
+ logger.info( `[executive] attention full (${this._facets.size}/${maxFacets}) — evicting LRU facet ${lru}` )
211
+ this._reap( lru, 'lru' )
212
+ }
213
+
214
+ this._facetCounter++
215
+ const facetId = `facet-${this._facetCounter}`
216
+
217
+ const facet = new ExecutiveFacet(
218
+ facetId,
219
+ deps.bus,
220
+ deps.llmDirector,
221
+ deps.contextDeps,
222
+ deps.promptDeps,
223
+ deps.willId!,
224
+ deps.inbox ?? null
225
+ )
226
+
227
+ // Attach session logger if available
228
+ if( this._sessionLogger )
229
+ facet.attachSessionLogger( this._sessionLogger )
230
+
231
+ // Set initial state reference + stamp initial activity (spawn tick).
232
+ facet.setStateRef( deps.stateRef )
233
+ facet.markActive( deps.stateRef.tick as unknown as number )
234
+
235
+ this._facets.set( facetId, facet )
236
+
237
+ logger.info( `[executive] spawned facet → ${facetId} (total facets: ${this._facets.size})` )
238
+
239
+ this._sessionLogger?.write({
240
+ type: 'executive.facet.spawn',
241
+ tick: deps.stateRef.tick as unknown as number,
242
+ facetId,
243
+ totalFacets: this._facets.size,
244
+ } as any)
245
+
246
+ return {
247
+ attention: 'available',
248
+ handle: {
249
+ facetId,
250
+ setFocus: ( focus ) => facet.setFocus( focus ),
251
+ setStateRef: ( state ) => facet.setStateRef( state ),
252
+ report: ( report ) => facet.report( report ),
253
+ subscribe: ( listener ) => facet.subscribe( listener ),
254
+ onChunk: ( handler ) => facet.setChunkHandler( handler ),
255
+ onReaped: ( handler ) => { this._onReaped.set( facetId, handler ) },
256
+ destroy: () => {
257
+ facet.destroy()
258
+ this._facets.delete( facetId )
259
+ this._onReaped.delete( facetId ) // explicit close — owner already knows; don't fire onReaped
260
+ logger.info( `[executive] facet ${facetId} destroyed (remaining: ${this._facets.size})` )
261
+ this._sessionLogger?.write({
262
+ type: 'executive.facet.destroy',
263
+ tick: this._lastStateRef?.tick as unknown as number ?? 0,
264
+ facetId,
265
+ totalFacets: this._facets.size,
266
+ } as any)
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }