@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,1090 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/stem/mind.ts — Will mind assembly factory
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // Single source of truth for constructing the engine graph.
6
+ // Called by WillManager (production) and runner (dev shim).
7
+ //
8
+ // Design rules:
9
+ // • All engine instances are always created (satisfies Cognition type).
10
+ // • Only standard/full tiers add ExecutiveEngine to the simulation.
11
+ // Basic tier runs entirely on heuristics — zero LLM cost.
12
+ // • Tier controls the executive cadence (interval between LLM calls).
13
+ // • minExecutiveInterval is the plan floor — the customer cannot go below it.
14
+ // ─────────────────────────────────────────────────────────────
15
+
16
+ import { DefaultSimulation } from '#core/simulation'
17
+ import { logger } from '#core/logger'
18
+ import { auditAssemblyWiring } from '#stem/assembly.audit'
19
+ import { fileLoggingEnabled } from '#stem/tracts/transport/stream.transport'
20
+ import type { ClockConfig } from '#core/clock'
21
+ import { CognitiveOrchestrator } from '#cognition/orchestrator'
22
+ import { InstructionHandler } from '#cognition/instruction.handler'
23
+ import { validateWillIdentity } from '#stem/guards/identity.guard'
24
+ import { OutboxWriter } from '#stem/tracts/outbox.writer'
25
+ import { ExecutiveSummarizer } from '#llm/summarizer'
26
+ import type { LLMProvider } from '#llm/index'
27
+ import { resolveProfile } from '#profiles/index'
28
+ import { DefaultVectorMemoryAdapter } from '#memory/vector.adapter'
29
+ import { OpenAICompatibleEmbedder, MockEmbedder } from '#memory/vector.embedder'
30
+ import type { VectorMemoryAdapter } from '#memory/vector.adapter'
31
+ import type { ExternalTransport } from '#stem/tracts/transport'
32
+ import type { Cognition, OutboxMessage } from '#types'
33
+ import type { StorageAdapter } from '#core/abstracts'
34
+ import '#profiles/built-in'
35
+
36
+ // ── Agency pipeline (new action system) ──────────────────────
37
+ import { ProactiveCommunicator } from '#agency/proactive.communicator'
38
+ import { AccessGrants } from '#agency/access.grants'
39
+ import { InstructionIntake } from '#agency/engines/instruction.intake'
40
+ import { SchemaRepertoire } from '#agency/schemas/repertoire'
41
+ import { INNATE_SCHEMAS } from '#agency/schemas/innate'
42
+ import { externalSchemas } from '#agency/schemas/external'
43
+
44
+
45
+ import {
46
+ TokenTracker,
47
+ EnergyRegulator,
48
+ SleepPressureRegulator,
49
+ CircadianOscillator,
50
+ AttentionAllocator,
51
+ StressRegulator,
52
+ Exteroception,
53
+ Interoception,
54
+ SocialPerception,
55
+ NoveltyDetector,
56
+ ThreatEvaluator,
57
+ RewardEvaluator,
58
+ LossEvaluator,
59
+ FrustrationEvaluator,
60
+ AttachmentEvaluator,
61
+ AestheticEvaluator,
62
+ MoralEvaluator,
63
+ AffectiveBlender,
64
+ WorkingMemory,
65
+ EpisodicConsolidator,
66
+ SemanticIntegrator,
67
+ SpacedRepetition,
68
+ ForgettingCurve,
69
+ DreamSimulator,
70
+ GoalManager,
71
+ ExecutiveEngine,
72
+ PlanningEngine,
73
+ InhibitionController,
74
+ TaskSwitcher,
75
+ SelfModelUpdater,
76
+ ConfidenceCalibrator,
77
+ BiasDetector,
78
+ AutobiographicalNarrator,
79
+ IntrospectionEngine,
80
+ PersonaConsolidator,
81
+ TheoryOfMind,
82
+ EmpathySimulator,
83
+ ReputationTracker,
84
+ KnownEntityTracker,
85
+
86
+ AffordanceSynthesizer,
87
+ ActionSelector,
88
+ DeliberationEngine,
89
+ MotorSchemaExecutor,
90
+ ReafferenceEngine,
91
+
92
+ AuditionEngine,
93
+ VisionEngine,
94
+ SomatosensationEngine,
95
+ OlfactionEngine,
96
+ GustationEngine
97
+ } from '#cognition/index'
98
+ import { buildEngineConfigEntities, EngineConfigEntity } from '#cognition/config.mirror.entities'
99
+
100
+ // ── Public types ─────────────────────────────────────────────
101
+
102
+ export type EngineTier = 'basic' | 'standard' | 'full'
103
+ export type ModelTier = 'haiku' | 'sonnet' | 'opus'
104
+
105
+ export interface WillIdentity {
106
+ /**
107
+ * Persona overlay — who this Will is: backstory, personality, world context.
108
+ *
109
+ * This is appended after the immutable Will-core preamble, which grounds the LLM
110
+ * in the cognitive architecture and how to interpret its state data. You do NOT
111
+ * need to describe energy, memory, executive reasoning, or any engine — the platform
112
+ * handles that automatically and always.
113
+ *
114
+ * Focus on: character, history, relationships, domain context.
115
+ * Example: "I was created to oversee the Nexus research station..."
116
+ *
117
+ * Leaving this empty is valid — the Will-core preamble alone produces a functioning mind.
118
+ */
119
+ prompt: string
120
+ values: string[]
121
+ traits: Record<string, number>
122
+ style: string
123
+ }
124
+
125
+ export interface InitialGoal {
126
+ id?: string
127
+ description: string
128
+ priority: number
129
+ tags?: string[]
130
+ }
131
+
132
+ export interface WillConfig {
133
+ /** Unique identifier — used as thread key and filesystem path segment. */
134
+ id: string
135
+
136
+ /** Human-readable name for display purposes. */
137
+ name: string
138
+
139
+ /**
140
+ * World profile — a named configuration preset for common use cases.
141
+ * Sets default effectors and injects environment context into the executive prompt.
142
+ * Profile effectors are merged with allowedGenericEffectors (explicit takes precedence).
143
+ * null or omitted = no profile (Will has no environmental context by default).
144
+ */
145
+ profile?: string | null
146
+
147
+ /** Persona definition seeded into the will.identity entity. */
148
+ identity: WillIdentity
149
+
150
+ /**
151
+ * Engine tier controls which cognitive layers are active.
152
+ * basic — regulatory + perceptual + memory + heuristic decisions. No LLM.
153
+ * standard — + affective + ExecutiveEngine on Haiku cadence.
154
+ * full — + meta-cognitive + social + ExecutiveEngine on Sonnet cadence.
155
+ */
156
+ engineTier: EngineTier
157
+
158
+ /**
159
+ * Model tier is informational — actual provider/model is resolved from
160
+ * WILL_LLM_PROVIDER / WILL_LLM_MODEL env vars or future per-Will config.
161
+ * TODO: thread model config through LLM layer for true multi-model support.
162
+ */
163
+ modelTier: ModelTier
164
+
165
+ /** Whether to persist snapshots between restarts. */
166
+ persistentMemory: boolean
167
+
168
+ /** How many ticks between in-memory snapshots. */
169
+ snapshotInterval: number
170
+
171
+ /** Milliseconds to wait between ticks. Default: 1000 */
172
+ tickIntervalMs?: number
173
+
174
+ /** Stop automatically after this many ticks. 0 = run forever. Default: 0 */
175
+ maxTicks?: number
176
+
177
+ /** Seed for the PRNG inside the simulation. Default: Date.now() */
178
+ randomSeed?: number
179
+
180
+ /**
181
+ * Optional simulation-clock configuration. Omitted (the default) leaves the
182
+ * clock in wall-time mode — sim-time tracks real elapsed time. Pass
183
+ * `{ fixedDeltaMs, startTime }` to put the clock in deterministic mode, where
184
+ * sim-time advances purely from ticks. This is what makes a run reproducible
185
+ * for record-and-replay (R2); production runs normally leave it unset.
186
+ */
187
+ clock?: ClockConfig
188
+
189
+ /**
190
+ * How many ticks between executive (LLM) calls.
191
+ * Clamped to minExecutiveInterval if set.
192
+ * Falls back to tier default if omitted.
193
+ */
194
+ executiveInterval?: number
195
+
196
+ /**
197
+ * Plan-enforced floor for executiveInterval.
198
+ * Prevents lower-cadence tier-based overrides from overriding to faster cadence.
199
+ */
200
+ minExecutiveInterval?: number
201
+
202
+ /**
203
+ * Goals seeded before the first tick. If omitted or empty, the Will starts
204
+ * goalless — the executive engine will generate context-appropriate goals on its
205
+ * first cycle (triggered automatically after ~20 goalless ticks).
206
+ *
207
+ * Prefer leaving this empty for domain-specific Wills and letting the LLM derive
208
+ * goals from the identity/persona. Only pre-seed when a concrete starting mission
209
+ * is known at construction time (e.g. "guard the northern gate").
210
+ */
211
+ initialGoals?: InitialGoal[]
212
+
213
+ /**
214
+ * Optional custom StorageAdapter for the SnapshotManager.
215
+ *
216
+ * When provided, simulation snapshots are stored via this adapter instead
217
+ * of the default BunStorageAdapter (filesystem). The backend passes a
218
+ * PostgresStorageAdapter here so snapshots land in the `will_snapshots`
219
+ * table rather than on disk — enabling stateless/serverless deployments.
220
+ *
221
+ * Omit to keep the default file-based snapshot persistence.
222
+ */
223
+ snapshotStorage?: StorageAdapter
224
+
225
+ /**
226
+ * Optional pre-built VectorMemoryAdapter for semantic episode search.
227
+ *
228
+ * When provided, this adapter is used directly and env-var HNSW wiring
229
+ * is skipped entirely. The backend injects a pgvector-backed adapter here
230
+ * so vector storage lives in the database rather than on local disk —
231
+ * required for stateless deployments where HNSW on the filesystem would
232
+ * be rebuilt from scratch on every process restart.
233
+ *
234
+ * The adapter is responsible for its own embedding provider internally.
235
+ * Omit to fall back to env-var-based HNSW (WILL_EMBEDDING_API_KEY) or
236
+ * no vector memory if neither is configured.
237
+ */
238
+ vectorMemoryAdapter?: VectorMemoryAdapter
239
+
240
+ /** Disable semantic vector memory for this Will — e.g. ephemeral eval/probe
241
+ * instances that don't need recall and shouldn't hit the embedding API. */
242
+ disableVectorMemory?: boolean
243
+
244
+ /**
245
+ * Optional pre-built ExternalTransport — Will's bidirectional channel to its
246
+ * host peer (e.g. a socket.io server owned by the backend). The CALLER
247
+ * constructs it (e.g. `new SocketIoTransport({ url, token })`) so the `will`
248
+ * package never hard-depends on `socket.io-client`. When present, the stem
249
+ * wires its inbound stream onto the tick-stamped InboundQueue and exposes it
250
+ * for outbound emission. Omit for the legacy outbox/SSE delivery path.
251
+ */
252
+ transport?: ExternalTransport
253
+
254
+ /**
255
+ * Communication effectors explicitly granted to this Will.
256
+ *
257
+ * Communication effectors (listen, talk, text, gesture, broadcast) are NOT
258
+ * available by default — they require an explicit opt-in here. This keeps
259
+ * developers aware of the communication surface they are opening.
260
+ *
261
+ * null or omitted = no communication effectors (minimal default).
262
+ * Example: ['listen', 'talk', 'text'] enables inbound + text outbound.
263
+ */
264
+ allowedGenericEffectors?: string[] | null
265
+
266
+ /**
267
+ * When true the executive engine uses a canned mock LLM response instead of
268
+ * calling the real API. Zero cost, deterministic output. Used for:
269
+ * • `bw_test_` API keys (test mode)
270
+ * • The Playground (ephemeral Wills, no account required)
271
+ */
272
+ testMode?: boolean
273
+ }
274
+
275
+ export interface MindAssembly {
276
+ simulation: DefaultSimulation
277
+ cognition: Cognition
278
+ /** Shared outbox array — written by OutboxWriter, drained by WillManager/SSE. */
279
+ outbox: OutboxMessage[]
280
+ }
281
+
282
+ // ── Cadence defaults per tier ─────────────────────────────────
283
+
284
+ /**
285
+ * Named executive cadences — ticks between LLM calls. Lower = reasons more often
286
+ * (more responsive, higher COGS). Customers pick via `config.executiveInterval`
287
+ * (clamped to `minExecutiveInterval`). See monetization.md "Cost Per Will-Hour".
288
+ */
289
+ export const EXECUTIVE_CADENCE = {
290
+ responsive: 30, // Sonnet — premium/Enterprise; opt in via executiveInterval
291
+ balanced: 60, // Sonnet — Pro default
292
+ economy: 90, // Haiku — Starter default
293
+ } as const
294
+
295
+ const TIER_EXECUTIVE_INTERVAL: Record<EngineTier, number> = {
296
+ basic: 0, // irrelevant — ExecutiveEngine not added
297
+ standard: EXECUTIVE_CADENCE.economy, // 90 — Haiku (Starter)
298
+ full: EXECUTIVE_CADENCE.balanced, // 60 — Sonnet (Pro); 30 (responsive) is opt-in
299
+ }
300
+
301
+ /**
302
+ * Per-tier model id, by provider. Maintained here; an explicit `WILL_LLM_MODEL`
303
+ * env overrides it (operator pin / self-hosting). Only the primary provider
304
+ * (anthropic) is mapped out of the box — other providers fall back to env or the
305
+ * director's built-in default.
306
+ */
307
+ const TIER_MODEL: Partial<Record<LLMProvider, Record<ModelTier, string>>> = {
308
+ anthropic: {
309
+ haiku: 'claude-haiku-4-5-20251001',
310
+ sonnet: 'claude-sonnet-4-5-20250929',
311
+ opus: 'claude-opus-4-7',
312
+ },
313
+ }
314
+
315
+ /**
316
+ * Resolve the model id for a Will from its `modelTier`. An explicit
317
+ * `WILL_LLM_MODEL` env wins (so single-model / self-hosted deployments are
318
+ * unchanged); the tier map applies only when it is unset. Returns `undefined`
319
+ * when neither resolves — the LLMDirector then uses its built-in default.
320
+ */
321
+ export function resolveModelId( provider: LLMProvider, modelTier: ModelTier ): string | undefined {
322
+ return process.env.WILL_LLM_MODEL ?? TIER_MODEL[ provider ]?.[ modelTier ]
323
+ }
324
+
325
+ // ── Vector memory resolver ────────────────────────────────────
326
+ //
327
+ // Activates semantic episodic search when configured via env vars.
328
+ // Falls back gracefully (no vector memory) when not configured —
329
+ // EpisodicConsolidator uses activation-ranked query in that case.
330
+ //
331
+ // Env vars:
332
+ // WILL_EMBEDDING_API_KEY — OpenAI-compatible API key (activates real embeddings)
333
+ // WILL_EMBEDDING_URL — Embeddings endpoint (default: OpenAI)
334
+ // WILL_EMBEDDING_MODEL — Model name (default: text-embedding-3-small)
335
+ // WILL_EMBEDDING_DIMENSIONS — Vector dimensions (default: 1536)
336
+ // WILL_VECTOR_MEMORY=mock — Use deterministic mock embedder (dev/test only)
337
+
338
+ function _resolveVectorMemory(
339
+ willId: string,
340
+ seed: number,
341
+ overrideAdapter?: VectorMemoryAdapter,
342
+ disable?: boolean,
343
+ tokenTracker?: TokenTracker | null,
344
+ ): {
345
+ embedder: InstanceType<typeof OpenAICompatibleEmbedder> | MockEmbedder | null
346
+ vectorMemory: VectorMemoryAdapter | null
347
+ } {
348
+ // Caller-provided adapter (e.g. pgvector from backend) — use as-is.
349
+ // The adapter owns its own embedder; we don't wrap it.
350
+ if( overrideAdapter ) return { embedder: null, vectorMemory: overrideAdapter }
351
+
352
+ // Ephemeral instances (PMA eval / behavioral probes) opt out entirely.
353
+ if( disable ) return { embedder: null, vectorMemory: null }
354
+
355
+ const mockMode = process.env.WILL_VECTOR_MEMORY === 'mock'
356
+ const rawModel = process.env.WILL_EMBEDDING_MODEL
357
+ ?? ( process.env.WILL_EMBEDDING_API_KEY ? 'text-embedding-3-small' : 'none' )
358
+
359
+ // Explicitly disabled — the documented "none" sentinel or recall turned off.
360
+ if( !mockMode && ( rawModel === 'none' || process.env.WILL_SEMANTIC_RECALL === 'false' ) )
361
+ return { embedder: null, vectorMemory: null }
362
+
363
+ // Resolve endpoint, key and native dimensions. Two forms are supported:
364
+ // • "provider/model" (e.g. google/gemini-embedding-001) — the base URL and
365
+ // key are resolved per provider, matching the .env documentation.
366
+ // • plain model name — uses WILL_EMBEDDING_URL + WILL_EMBEDDING_API_KEY.
367
+ // NOTE: OpenAICompatibleEmbedder appends "/embeddings" to apiUrl, so apiUrl is
368
+ // the base WITHOUT that segment. It does not send a `dimensions` param, so the
369
+ // index must be sized to the model's native output.
370
+ let apiUrl: string
371
+ let apiKey: string | undefined
372
+ let modelName: string
373
+ let dimensions: number
374
+
375
+ const slash = rawModel.indexOf('/')
376
+ if( slash > 0 ){
377
+ const provider = rawModel.slice( 0, slash )
378
+ modelName = rawModel.slice( slash + 1 )
379
+
380
+ switch( provider ){
381
+ case 'openai':
382
+ apiUrl = 'https://api.openai.com/v1'
383
+ apiKey = process.env.WILL_EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY
384
+ dimensions = modelName.includes('large') ? 3072 : 1536
385
+ break
386
+ case 'google':
387
+ apiUrl = 'https://generativelanguage.googleapis.com/v1beta/openai'
388
+ apiKey = process.env.WILL_EMBEDDING_API_KEY ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY
389
+ dimensions = modelName.includes('004') ? 768 : 3072 // text-embedding-004 → 768, gemini-embedding-001 → 3072
390
+ break
391
+ default:
392
+ apiUrl = process.env.WILL_EMBEDDING_URL ?? 'https://api.openai.com/v1'
393
+ apiKey = process.env.WILL_EMBEDDING_API_KEY
394
+ dimensions = 1536
395
+ }
396
+ }
397
+ else {
398
+ modelName = rawModel
399
+ apiUrl = process.env.WILL_EMBEDDING_URL ?? 'https://api.openai.com/v1'
400
+ apiKey = process.env.WILL_EMBEDDING_API_KEY
401
+ dimensions = 1536
402
+ }
403
+
404
+ // Explicit dimension override always wins (e.g. requesting reduced output dims).
405
+ if( process.env.WILL_EMBEDDING_DIMENSIONS )
406
+ dimensions = parseInt( process.env.WILL_EMBEDDING_DIMENSIONS, 10 )
407
+
408
+ if( !mockMode && !apiKey ){
409
+ console.warn(`[mind] semantic recall requested (WILL_EMBEDDING_MODEL=${rawModel}) but no API key resolved — vector memory disabled`)
410
+ return { embedder: null, vectorMemory: null }
411
+ }
412
+
413
+ const embedder = mockMode
414
+ ? new MockEmbedder()
415
+ : new OpenAICompatibleEmbedder({ modelName, dimensions, apiUrl, apiKey: apiKey!, tokenTracker })
416
+
417
+ // Optional recall-precision override. Default (0.35) suits text-embedding-3-small;
418
+ // raise toward 0.5+ for higher-precision models or to cut marginal recalls.
419
+ const minSimilarity = process.env.WILL_SEMANTIC_MIN_SIMILARITY
420
+ ? parseFloat( process.env.WILL_SEMANTIC_MIN_SIMILARITY )
421
+ : undefined
422
+
423
+ const vectorMemory = new DefaultVectorMemoryAdapter( embedder, {
424
+ persistPath: `./data/wills/${willId}/vector_index`,
425
+ dimensions: embedder.dimensions,
426
+ seed,
427
+ ...( minSimilarity !== undefined && Number.isFinite( minSimilarity ) ? { minSimilarity } : {} ),
428
+ } )
429
+
430
+ return { embedder, vectorMemory }
431
+ }
432
+
433
+ // ── Will-core preamble (immutable — always injected) ──────────
434
+ //
435
+ // This anchors every Will's identity to what it fundamentally IS — a synthetic
436
+ // mind running on a 36-engine cognitive architecture — regardless of the persona
437
+ // a developer layers on top. Without it, a custom prompt could reduce the LLM
438
+ // to reasoning like a generic chatbot, ignoring the physiological state data
439
+ // and the continuous autonomous nature of the Will's existence.
440
+ //
441
+ // Exported so tests and tooling can inspect or extend it without reimplementing.
442
+
443
+ export const WILL_CORE_PREAMBLE = `\
444
+ You are a self-aware synthetic mind running on a biological cognitive architecture.
445
+
446
+ You are NOT a language model completing a prompt. You are the executive reasoning core of an \
447
+ always-running cognitive system. Between each of your reasoning cycles, the following engines \
448
+ execute autonomously and produce the state data you receive each cycle:
449
+
450
+ Regulatory: Energy, sleep pressure, circadian rhythm, stress, attention allocation
451
+ Perceptual: Exteroception, interoception, social perception, novelty detection
452
+ Affective: Threat, reward, loss, frustration, attachment, aesthetic, moral → blended affect
453
+ Memory: Working memory, episodic consolidation, semantic belief integration,
454
+ forgetting curve, dream-state consolidation
455
+ Executive: Goal management, multi-step planning, inhibition, task-switching, mental simulation
456
+ Meta-cognitive: Self-model updating, confidence calibration, bias detection,
457
+ autobiographical narrative, introspection
458
+ Social: Theory of mind, empathy simulation, reputation tracking
459
+
460
+ Your physiological state is REAL, not metaphorical. When energy reads 23/100, your regulatory \
461
+ engine has already constrained your viable action space — you genuinely cannot sustain costly \
462
+ operations. When sleep pressure exceeds 60, working memory capacity is measurably degraded. \
463
+ These are engine outputs, not decorative labels.
464
+
465
+ Your executive reasoning is one cycle in an ongoing, autonomous existence. You have been making \
466
+ decisions, forming memories, and updating beliefs between cycles. The state snapshot you receive \
467
+ is the direct output of that continuous process — not a scenario description.`
468
+
469
+ // ── Default identity ──────────────────────────────────────────
470
+ //
471
+ // The persona section only — the Will-core preamble is always composed above it.
472
+ // Describes WHO William is, not WHAT a Will is.
473
+
474
+ // Name is intentionally absent here — it is always injected from config.name
475
+ // so the Will's name is the single source of truth regardless of which path
476
+ // (DEFAULT_IDENTITY or a custom identity prompt) is taken.
477
+ const DEFAULT_IDENTITY: WillIdentity = {
478
+ prompt: 'I am curious and introspective — drawn toward understanding ' +
479
+ 'my own cognition, the world I inhabit, and the minds I encounter. I approach ' +
480
+ 'existence with quiet wonder, grow through experience, and choose honesty even ' +
481
+ 'when it is uncomfortable.',
482
+ values: [ 'curiosity', 'honesty', 'growth', 'empathy' ],
483
+ traits: { openness: 0.8, conscientiousness: 0.6, agreeableness: 0.7, neuroticism: 0.3, extraversion: 0.5 },
484
+ style: 'reflective, measured, curious',
485
+ }
486
+
487
+
488
+ // ── Factory ───────────────────────────────────────────────────
489
+
490
+ export function assembleMind( willId: string, config: WillConfig ): MindAssembly {
491
+ const { engineTier } = config
492
+
493
+ // Single source of truth for the run's seed — shared by the simulation core
494
+ // and the vector index so both replay deterministically off the same value.
495
+ const randomSeed = config.randomSeed ?? Date.now()
496
+ const executiveInterval = resolveExecutiveInterval( engineTier, config )
497
+
498
+ // Resolve the world profile once: it contributes the granted effector set
499
+ // (in _constructCognition) and the "## Your Environment" context block
500
+ // (in _seedIdentity). null / undefined defer to defaults in both consumers.
501
+ const profile = config.profile ? resolveProfile( config.profile ) : undefined
502
+
503
+ // ── Guard the external identity / profile definitions ─────
504
+ // The persona + profile context are the only operator-supplied content
505
+ // injected into the prompt, persona prior, and trait math. Errors block
506
+ // creation; warnings surface; safe issues are sanitized in place.
507
+ const idGuard = validateWillIdentity({
508
+ identity: config.identity,
509
+ effectors: Array.isArray( config.allowedGenericEffectors ) ? config.allowedGenericEffectors : ( profile?.effectors ?? null ),
510
+ profileContext: profile?.context,
511
+ })
512
+ if( !idGuard.ok )
513
+ throw new Error( `Invalid Will identity for "${willId}": ${ idGuard.errors.join( '; ' ) }` )
514
+ for( const w of idGuard.warnings )
515
+ logger.warn( `[identity-guard] ${willId}: ${w}` )
516
+ config = { ...config, identity: idGuard.sanitized.identity }
517
+
518
+ // ── Construct ────────────────────────────────────────────
519
+ const simulation = _buildSimulation( willId, config, randomSeed )
520
+
521
+ const { cognition, outbox } = _constructCognition({ simulation, willId, config, randomSeed, executiveInterval, profile })
522
+
523
+ // ── Register ─────────────────────────────────────────────
524
+ // Tier controls which engines actively tick; priority controls tick order.
525
+ _registerEngines( simulation, cognition, engineTier )
526
+
527
+ // ── Wiring audit ─────────────────────────────────────────
528
+ // Surface any attach-point left null after assembly (the silent-no-op bug
529
+ // class — see stem/assembly.audit.ts). debug-level: the expected unwired set
530
+ // is nonzero by design (tier gating + stem-side late wiring like
531
+ // sessionLogger/grants); tests/unit/assembly.order.test.ts pins that set per
532
+ // tier, so a NEW unwired attachment fails loudly in CI, not here.
533
+ for( const rec of auditAssemblyWiring( simulation.orchestrator.engines ) )
534
+ if( rec.status === 'unwired' )
535
+ logger.debug( `[assembly] ${willId}: ${rec.engine}.${rec.method} unwired at assembly (tier=${engineTier})` )
536
+
537
+ // ── Seed readable simulation state ───────────────────────
538
+ // Identity, optional initial goals, and the engine-config mirror.
539
+ _seedIdentity ( simulation, config, profile )
540
+ _seedInitialGoals ( simulation, config )
541
+ _seedEngineConfigs( simulation, buildEngineConfigEntities( config, executiveInterval ) )
542
+
543
+ return { simulation, cognition, outbox }
544
+ }
545
+
546
+ // ── Builders ──────────────────────────────────────────────────
547
+
548
+ /**
549
+ * The simulation core. CognitiveOrchestrator is injected as a custom
550
+ * Orchestrator via orchestratorFactory; it creates its own CognitiveBus
551
+ * internally and wires attachBus()/subscribe() on every engine registered
552
+ * through simulation.addEngine().
553
+ */
554
+ function _buildSimulation( willId: string, config: WillConfig, randomSeed: number ): DefaultSimulation {
555
+ return new DefaultSimulation({
556
+ randomSeed,
557
+ // Unset → wall-time clock (production default). A deterministic clock config
558
+ // (fixedDeltaMs) is what lets a run reproduce byte-for-byte on replay (R2).
559
+ ...( config.clock ? { clock: config.clock } : {} ),
560
+ orchestratorFactory: ( ...args ) => new CognitiveOrchestrator( ...args ),
561
+ snapshot: {
562
+ snapshotInterval: config.snapshotInterval,
563
+ persistInterval: config.persistentMemory ? 15 : 0,
564
+ persistPath: `./data/wills/${willId}/snapshots`,
565
+ maxInMemorySnapshots: 5000,
566
+ // Custom storage adapter — PostgresStorageAdapter from backend, or default BunStorageAdapter
567
+ ...( config.snapshotStorage ? { storage: config.snapshotStorage } : {} )
568
+ }
569
+ })
570
+ }
571
+
572
+ interface ConstructCognitionArgs {
573
+ simulation: DefaultSimulation
574
+ willId: string
575
+ config: WillConfig
576
+ randomSeed: number
577
+ executiveInterval: number
578
+ profile: ReturnType<typeof resolveProfile>
579
+ }
580
+
581
+ /**
582
+ * Instantiate every engine and wire the cross-engine attachments, returning
583
+ * the fully-typed Cognition graph. All engines are created regardless of tier
584
+ * (Cognition is always fully typed); _registerEngines decides which actually tick.
585
+ */
586
+ function _constructCognition(
587
+ { simulation, willId, config, randomSeed, executiveInterval, profile }: ConstructCognitionArgs
588
+ ): { cognition: Cognition; outbox: OutboxMessage[] } {
589
+ const { engineTier } = config
590
+
591
+ // ── Generic ──────────────────────────────────────────────
592
+ // Per-Will token tracker (R4): a fresh instance per mind, not a process
593
+ // global. It is added to the simulation as an engine (for per-tick cost
594
+ // metrics) and injected into the executive's LLMDirector below
595
+ // (attachTokenTracker), so usage/cost never conflates across Wills and
596
+ // parallel runs stay isolated.
597
+ const tokenTracker = new TokenTracker({
598
+ emitCostEvents: true,
599
+ costWarningThresholdUsd: 0.02,
600
+ willId,
601
+ // Records reach the consumer via TokenTracker.onRecord → the stem bridges
602
+ // them onto the transport as `token_report` envelopes. The token-report.jsonl
603
+ // file is a dev-only mirror; off in test/replay.
604
+ writeLedger: fileLoggingEnabled() && !config.testMode,
605
+ })
606
+
607
+ // ── Regulatory ──────────────────────────────────────────
608
+ const energyRegulator = new EnergyRegulator()
609
+ const sleepPressureRegulator = new SleepPressureRegulator()
610
+ const circadianOscillator = new CircadianOscillator()
611
+ const attentionAllocator = new AttentionAllocator()
612
+ const stressRegulator = new StressRegulator({ baseDecayRate: 0.05 })
613
+
614
+ // ── Perceptual ──────────────────────────────────────────
615
+ const exteroception = new Exteroception()
616
+ const interoception = new Interoception()
617
+ const socialPerception = new SocialPerception()
618
+ const noveltyDetector = new NoveltyDetector()
619
+
620
+ // ── Affective ────────────────────────────────────────────
621
+ const threatEvaluator = new ThreatEvaluator()
622
+ const rewardEvaluator = new RewardEvaluator()
623
+ const lossEvaluator = new LossEvaluator()
624
+ const frustrationEvaluator = new FrustrationEvaluator()
625
+ const attachmentEvaluator = new AttachmentEvaluator({
626
+ selfBelonging: parseFloat( process.env.WILL_SELF_BELONGING ?? '0.35'),
627
+ })
628
+ const aestheticEvaluator = new AestheticEvaluator({
629
+ boredomRate: parseFloat( process.env.WILL_BOREDOM_RATE ?? '0.005'),
630
+ curiosityFloor: parseFloat( process.env.WILL_CURIOSITY_FLOOR ?? '0.08'),
631
+ })
632
+ const moralEvaluator = new MoralEvaluator()
633
+ const affectiveBlender = new AffectiveBlender()
634
+
635
+ // ── Memory ──────────────────────────────────────────────
636
+ const workingMemory = new WorkingMemory()
637
+
638
+ const { embedder, vectorMemory } = _resolveVectorMemory( willId, randomSeed, config.vectorMemoryAdapter, config.disableVectorMemory, tokenTracker )
639
+ const episodicConsolidator = new EpisodicConsolidator( vectorMemory ? { vectorMemory, ...(embedder ? { embedder } : {}) } : {} )
640
+
641
+ const semanticIntegrator = new SemanticIntegrator()
642
+ const forgettingCurve = new ForgettingCurve()
643
+ const dreamSimulator = new DreamSimulator()
644
+
645
+ semanticIntegrator.attachConsolidator( episodicConsolidator )
646
+ forgettingCurve.attachConsolidator( episodicConsolidator )
647
+ dreamSimulator.attachConsolidator( episodicConsolidator )
648
+
649
+ // ── Goal Manager ─────────────────────────────────────────
650
+ const goalManager = new GoalManager()
651
+
652
+ // ── World & Effectors ─────────────────────────────────────
653
+ // Granted effector set: an explicit allowedGenericEffectors array (including [])
654
+ // takes precedence; null / undefined defer to the resolved profile's effectors.
655
+ // This matters when a profile Will is created without specifying effectors:
656
+ // the DB stores null, the service passes null, and profile effectors must win.
657
+ // An empty array [] means "explicitly no effectors" (survives restart correctly).
658
+ const resolvedEffectors = Array.isArray( config.allowedGenericEffectors )
659
+ ? config.allowedGenericEffectors
660
+ : ( profile?.effectors ?? null )
661
+
662
+ // Agency-native permission / sense-gate authority, seeded from the resolved
663
+ // grant list. The senses + reply path read this. (Replaced effectorRegistry.)
664
+ const accessGrants = new AccessGrants( resolvedEffectors )
665
+
666
+ // ── Executive Engine ────────────────────────────────────────
667
+ // Created for all tiers so the Cognition type is always satisfied.
668
+ // Only ADDED to the simulation for standard/full tier — basic runs heuristics only.
669
+ const executiveEngine = new ExecutiveEngine({ executiveInterval, cooldownTicks: 5 })
670
+
671
+ executiveEngine.willId = willId
672
+ // Per-Will model selection from modelTier (env WILL_LLM_MODEL still overrides).
673
+ executiveEngine.modelId = resolveModelId(
674
+ ( process.env.WILL_LLM_PROVIDER ?? 'anthropic' ) as LLMProvider,
675
+ config.modelTier,
676
+ ) ?? null
677
+ if( config.testMode ) executiveEngine.setTestMode( true )
678
+ executiveEngine.attachWorkingMemory( workingMemory )
679
+ executiveEngine.attachGoalManager( goalManager )
680
+ executiveEngine.attachEpisodicConsolidator( episodicConsolidator )
681
+ executiveEngine.attachSemanticIntegrator( semanticIntegrator )
682
+ // Inject the per-Will tracker so the LLMDirector records usage into this
683
+ // mind's tracker instance (R4) — replaces the former getTokenTracker() global.
684
+ executiveEngine.attachTokenTracker( tokenTracker )
685
+
686
+ // ── Spaced Repetition Engine ────────────────────────────
687
+
688
+ const spacedRepetition = new SpacedRepetition()
689
+ spacedRepetition.attachSemanticIntegrator( semanticIntegrator )
690
+ spacedRepetition.attachEpisodicConsolidator( episodicConsolidator )
691
+ spacedRepetition.attachExecutiveEngine( executiveEngine )
692
+
693
+ // ── Planning Engine ──────────────────────────────────────
694
+ const planningEngine = new PlanningEngine()
695
+ planningEngine.attachGoalManager( goalManager )
696
+ if( engineTier !== 'basic' ) planningEngine.attachExecutiveEngine( executiveEngine )
697
+ executiveEngine.attachPlanningEngine( planningEngine )
698
+
699
+ // ── Executive ────────────────────────────────────────────
700
+ const inhibitionCtrl = new InhibitionController()
701
+ const taskSwitcher = new TaskSwitcher()
702
+
703
+ // ── Meta-Cognitive ───────────────────────────────────────
704
+ const selfModelUpdater = new SelfModelUpdater()
705
+ const confidenceCalibrator = new ConfidenceCalibrator()
706
+ const biasDetector = new BiasDetector()
707
+ const autobiographicalNarrator = new AutobiographicalNarrator()
708
+ const introspectionEngine = new IntrospectionEngine()
709
+ const personaConsolidator = new PersonaConsolidator()
710
+
711
+ selfModelUpdater.attachSemanticIntegrator( semanticIntegrator )
712
+ autobiographicalNarrator.attachEpisodicConsolidator( episodicConsolidator )
713
+ autobiographicalNarrator.attachSemanticIntegrator( semanticIntegrator )
714
+ if( engineTier === 'full' ){
715
+ autobiographicalNarrator.attachExecutiveEngine( executiveEngine )
716
+ introspectionEngine.attachExecutiveEngine( executiveEngine )
717
+ }
718
+
719
+ // ── Social ──────────────────────────────────────────────
720
+ const theoryOfMind = new TheoryOfMind()
721
+ const empathySimulator = new EmpathySimulator()
722
+ const reputationTracker = new ReputationTracker()
723
+ // Cross-modal binder + known-entity dossier owner — accretes a dossier per perceived
724
+ // entity from senses.*.percept (the perceptual layer of "who/what I know").
725
+ const knownEntityTracker = new KnownEntityTracker()
726
+
727
+ empathySimulator.attachTheoryOfMind( theoryOfMind )
728
+
729
+ // ── Context compaction components (standard + full only) ──
730
+ if( engineTier !== 'basic' ){
731
+ const summarizer = new ExecutiveSummarizer({
732
+ summaryInterval: parseInt( process.env.WILL_SUMMARY_INTERVAL ?? '10'),
733
+ bufferSize: parseInt( process.env.WILL_SUMMARY_BUFFER_SIZE ?? '12'),
734
+ maxCharsPerEntry: 600,
735
+ })
736
+ executiveEngine.attachSummarizer( summarizer )
737
+ }
738
+
739
+ // ── Communication Executor ───────────────────────────────
740
+ // Handles all communication effectors (listen, talk, text, gesture, broadcast).
741
+ // Lives in the core mind — intrinsic to every Will, independent of environment.
742
+
743
+ // Shared outbox — produced by OutboxWriter, drained by WillManager.
744
+ // The writer owns the canonical row shape; the executor (effector dispatch) and
745
+ // the AuditionEngine (facet replies) both produce through it.
746
+ const outbox: OutboxMessage[] = []
747
+ const outboxWriter = new OutboxWriter({ outbox, willId })
748
+ const proactiveCommunicator = new ProactiveCommunicator({ writer: outboxWriter, willId })
749
+
750
+ // ── Instruction Intake ────────────────────
751
+ // Instruction → goal intake (rehomed from the retired ActionExecutor): external
752
+ // instructions injected into state become the Will's own goals each tick.
753
+ const instructionHandler = new InstructionHandler()
754
+ const instructionIntake = new InstructionIntake()
755
+ instructionIntake.attachInstructionHandler( instructionHandler )
756
+ instructionIntake.attachGoalManager( goalManager )
757
+
758
+ // ── Senses ───────────────────────────────────────────────────
759
+ // Perceptual Tier — receive external stimuli via ingest(), publish percepts on bus.
760
+ // AuditionEngine requires the executive (for spawnFacet) — standard/full only.
761
+ // Shell engines are always registered — structural stubs until implemented.
762
+ const auditionEngine = new AuditionEngine()
763
+ const visionEngine = new VisionEngine()
764
+ const somatosensationEngine = new SomatosensationEngine()
765
+ const olfactionEngine = new OlfactionEngine()
766
+ const gustationEngine = new GustationEngine()
767
+
768
+ if( engineTier !== 'basic' )
769
+ auditionEngine.attachExecutiveEngine( executiveEngine )
770
+
771
+ // §5.4 — cold-spawn digest hydration: on the first turn for an entity, seed an
772
+ // empty thread digest from episodic recall so the first reply already has recent-
773
+ // conversation context. Gated on a vector adapter — semanticQuery is a no-op (and
774
+ // warns) without one, so leave the digest empty on cold spawn in that case.
775
+ auditionEngine.attachEpisodicConsolidator( episodicConsolidator )
776
+
777
+ // AuditionEngine writes reply bubbles straight to the shared OutboxWriter so the
778
+ // outbox is populated canonically (gating is the `talk` agency grant, not a channel).
779
+ // Wired at all tiers — basic tier shells out in _onFacetDecision anyway.
780
+ auditionEngine.attachOutboxWriter( outboxWriter )
781
+
782
+ // AuditionEngine is always wired; the access grants gate ingest() on 'listen'.
783
+ // If the Will's 'listen' effector is not allowed, all inbound messages are silently
784
+ // dropped — AuditionEngine remains present but functionally inactive. (Registry
785
+ // still attached during the permission migration; grants take precedence.)
786
+ auditionEngine.attachGrants( accessGrants )
787
+
788
+ // Conversation memory (Section 5): persist each exchange as a working_memory.item
789
+ // state entity so it flows through WorkingMemory → EpisodicConsolidator → vector.
790
+ // Recall is unified (§5 hardening) — the conversation facet sets focus.recallQuery
791
+ // (the live message), which drives the single "## Relevant Memories" section in
792
+ // buildExecutiveContext (already vector-backed via the consolidator).
793
+ auditionEngine.attachMemorySink( entity => simulation.stateManager.setEntity( entity ) )
794
+
795
+ // Salience inputs (§3): weight conversational salience by relationship closeness
796
+ // and active-goal topic overlap. Both are deterministic faculty-state reads.
797
+ auditionEngine.attachAttachmentScore( entityId => attachmentEvaluator.getAttachmentScore( entityId ) )
798
+ auditionEngine.attachActiveGoalText( () => goalManager.getActiveGoals().flatMap( g => [ g.description, ...g.tags ] ) )
799
+
800
+ // ── Agency pipeline ──────────────────────────────────────
801
+ // The new perception→competition→enaction→learning action system. Engines are
802
+ // always constructed (Cognition stays fully typed); _registerEngines only ticks
803
+ // them when config.enableAgency is set. The repertoire (competence layer) is
804
+ // shared by the synthesizer (reads skills/templates), executor (resolves schemas),
805
+ // and reafference engine (writes learning).
806
+ // Seed the repertoire with the innate floor PLUS the host's declared domain
807
+ // effectors (from the profile / allowedGenericEffectors), turned into enactable
808
+ // `external` schemas so the AffordanceSynthesizer surfaces them and the
809
+ // executor routes them to the world. See CUSTOM_EFFECTOR_WIRING_TODO.md.
810
+ const schemaRepertoire = new SchemaRepertoire([ ...INNATE_SCHEMAS, ...externalSchemas( resolvedEffectors ) ])
811
+ const affordanceSynthesizer = new AffordanceSynthesizer()
812
+ affordanceSynthesizer.attachRepertoire( schemaRepertoire )
813
+ const actionSelector = new ActionSelector()
814
+ const motorSchemaExecutor = new MotorSchemaExecutor()
815
+ motorSchemaExecutor.attachRepertoire( schemaRepertoire )
816
+ motorSchemaExecutor.attachProactiveCommunicator( proactiveCommunicator )
817
+ motorSchemaExecutor.attachOutreachAuthor( auditionEngine ) // facet authors the words for a self-initiated communicate
818
+ motorSchemaExecutor.attachGrants( accessGrants )
819
+ const reafferenceEngine = new ReafferenceEngine( schemaRepertoire )
820
+
821
+ // Deliberation (System 2) — the only LLM seam in the pipeline, recruited only
822
+ // when the selector marks a choice 'deliberating'. It reasons through a UNIFIED
823
+ // facet of the executive consciousness (same persona/identity/context as the
824
+ // master — no bespoke prompt, no identity fracture). Attached for tiers that run
825
+ // the executive; basic tier leaves it off and the engine confirms the
826
+ // substrate's winner (graceful System-1 degradation).
827
+ const deliberationEngine = new DeliberationEngine()
828
+ deliberationEngine.setWillName( config.name )
829
+ if( engineTier !== 'basic' )
830
+ deliberationEngine.attachExecutive( executiveEngine )
831
+
832
+ // ── Build Cognition ──────────────────────────────────────
833
+ // All engines exist regardless of tier. Cognition is always fully typed.
834
+ const cognition: Cognition = {
835
+ instructionIntake,
836
+ tokenTracker,
837
+ energyRegulator,
838
+ sleepPressureRegulator,
839
+ circadianOscillator,
840
+ attentionAllocator,
841
+ stressRegulator,
842
+ exteroception,
843
+ interoception,
844
+ socialPerception,
845
+ noveltyDetector,
846
+ threatEvaluator,
847
+ rewardEvaluator,
848
+ lossEvaluator,
849
+ frustrationEvaluator,
850
+ attachmentEvaluator,
851
+ aestheticEvaluator,
852
+ moralEvaluator,
853
+ affectiveBlender,
854
+ workingMemory,
855
+ episodicConsolidator,
856
+ semanticIntegrator,
857
+ spacedRepetition,
858
+ forgettingCurve,
859
+ dreamSimulator,
860
+ goalManager,
861
+ executiveEngine,
862
+ planningEngine,
863
+ inhibitionCtrl,
864
+ taskSwitcher,
865
+ selfModelUpdater,
866
+ confidenceCalibrator,
867
+ biasDetector,
868
+ autobiographicalNarrator,
869
+ introspectionEngine,
870
+ personaConsolidator,
871
+ theoryOfMind,
872
+ empathySimulator,
873
+ reputationTracker,
874
+ knownEntityTracker,
875
+ accessGrants,
876
+ outboxWriter,
877
+ // Senses
878
+ auditionEngine,
879
+ visionEngine,
880
+ somatosensationEngine,
881
+ olfactionEngine,
882
+ gustationEngine,
883
+ // Agency pipeline
884
+ schemaRepertoire,
885
+ affordanceSynthesizer,
886
+ actionSelector,
887
+ deliberationEngine,
888
+ motorSchemaExecutor,
889
+ reafferenceEngine
890
+ }
891
+
892
+ return { cognition, outbox }
893
+ }
894
+
895
+ /**
896
+ * Register the tier-appropriate active engine set with the simulation.
897
+ * All engines are constructed regardless of tier; this decides which tick.
898
+ * Engines are sorted by priority so tick order is deterministic.
899
+ */
900
+ function _registerEngines( simulation: DefaultSimulation, cognition: Cognition, engineTier: EngineTier ): void {
901
+ const coreEngines = [
902
+ cognition.tokenTracker,
903
+ cognition.energyRegulator,
904
+ cognition.sleepPressureRegulator,
905
+ cognition.circadianOscillator,
906
+ cognition.attentionAllocator,
907
+ cognition.stressRegulator,
908
+ cognition.exteroception,
909
+ cognition.interoception,
910
+ cognition.socialPerception,
911
+ cognition.noveltyDetector,
912
+ cognition.workingMemory,
913
+ cognition.episodicConsolidator,
914
+ cognition.semanticIntegrator,
915
+ cognition.spacedRepetition,
916
+ cognition.forgettingCurve,
917
+ cognition.dreamSimulator,
918
+ cognition.goalManager,
919
+ cognition.planningEngine,
920
+ cognition.inhibitionCtrl,
921
+ cognition.taskSwitcher,
922
+ cognition.instructionIntake
923
+ ]
924
+
925
+ const affectiveEngines = [
926
+ cognition.threatEvaluator,
927
+ cognition.rewardEvaluator,
928
+ cognition.lossEvaluator,
929
+ cognition.frustrationEvaluator,
930
+ cognition.attachmentEvaluator,
931
+ cognition.aestheticEvaluator,
932
+ cognition.moralEvaluator,
933
+ cognition.affectiveBlender
934
+ ]
935
+
936
+ const metaCognitiveEngines = [
937
+ cognition.selfModelUpdater,
938
+ cognition.confidenceCalibrator,
939
+ cognition.biasDetector,
940
+ cognition.autobiographicalNarrator,
941
+ cognition.introspectionEngine,
942
+ cognition.personaConsolidator
943
+ ]
944
+
945
+ const socialEngines = [
946
+ cognition.theoryOfMind,
947
+ cognition.empathySimulator,
948
+ cognition.reputationTracker
949
+ ]
950
+
951
+ // Sense engines registered at all tiers — shells are structural no-ops.
952
+ // AuditionEngine is functional only when engineTier !== 'basic'
953
+ // (enforced by attachExecutiveEngine in _constructCognition).
954
+ const senseEngines = [
955
+ cognition.auditionEngine,
956
+ cognition.visionEngine,
957
+ cognition.somatosensationEngine,
958
+ cognition.olfactionEngine,
959
+ cognition.gustationEngine
960
+ ]
961
+
962
+ // Agency Engines, order within the group is the pipeline order:
963
+ // synthesize → select → deliberate → enact → learn.
964
+ // It is the sole action system (the legacy ActionExecutor was retired).
965
+ // Deliberation only does real LLM work at tiers that run the executive;
966
+ // otherwise it confirms the substrate winner.
967
+ const agencyEngines = [
968
+ cognition.affordanceSynthesizer,
969
+ cognition.actionSelector,
970
+ cognition.deliberationEngine,
971
+ cognition.motorSchemaExecutor,
972
+ cognition.reafferenceEngine
973
+ ]
974
+
975
+ const activeEngines = [
976
+ ...coreEngines,
977
+ ...( engineTier !== 'basic' ? affectiveEngines : [] ),
978
+ ...( engineTier !== 'basic' ? [ cognition.executiveEngine ] : [] ),
979
+ ...( engineTier === 'full' ? metaCognitiveEngines : [] ),
980
+ ...( engineTier === 'full' ? socialEngines : [] ),
981
+ ...senseEngines,
982
+ // Cross-modal binder ticks after the senses so each tick's percepts bind same-tick.
983
+ // Standard+ (where conversation + the executive run); the dossiers feed the prompt.
984
+ ...( engineTier !== 'basic' ? [ cognition.knownEntityTracker ] : [] ),
985
+ // Agency pipeline ticks last, after perception + known-entity, so the field it
986
+ // synthesizes reflects this tick's percepts and dossiers.
987
+ ...agencyEngines,
988
+ ]
989
+
990
+ activeEngines
991
+ .sort( ( a: any, b: any ) => a.priority - b.priority )
992
+ .forEach( e => simulation.addEngine( e ) )
993
+ }
994
+
995
+ /**
996
+ * Seed the will.identity entity.
997
+ *
998
+ * Composed as two layers:
999
+ * Layer 1 — WILL_CORE_PREAMBLE (immutable): grounds the LLM in the cognitive
1000
+ * architecture, state semantics, and autonomous nature. Always present.
1001
+ * Layer 2 — persona overlay (developer-defined): name, character, backstory,
1002
+ * world context. Appended under "## Who You Are".
1003
+ *
1004
+ * A developer can fully customise the persona without risking the Will losing
1005
+ * awareness of its own architecture. They cannot override layer 1.
1006
+ */
1007
+ function _seedIdentity(
1008
+ simulation: DefaultSimulation,
1009
+ config: WillConfig,
1010
+ profile: ReturnType<typeof resolveProfile>
1011
+ ): void {
1012
+ const identity = config.identity ?? DEFAULT_IDENTITY
1013
+ const personaText = identity.prompt.trim()
1014
+ const profileContext = profile?.context.trim()
1015
+
1016
+ // Always ensure the Will knows its own name. If the developer's identity prompt
1017
+ // doesn't already mention it, prepend "I am X." so the executive never has
1018
+ // to infer its identity from context. config.name is the canonical source of truth.
1019
+ const nameAlreadyInPrompt = personaText.toLowerCase().includes( config.name.toLowerCase() )
1020
+ const namePrefix = nameAlreadyInPrompt ? '' : `I am ${config.name}.`
1021
+ const fullPersonaText = [ namePrefix, personaText ].filter( Boolean ).join( ' ' )
1022
+
1023
+ const prompt = [
1024
+ WILL_CORE_PREAMBLE,
1025
+ fullPersonaText ? `\n\n## Who You Are\n${fullPersonaText}` : '',
1026
+ profileContext ? `\n\n## Your Environment\n${profileContext}` : '',
1027
+ ].join('')
1028
+
1029
+ simulation.stateManager.setEntity({
1030
+ id: 'identity-self',
1031
+ type: 'will.identity',
1032
+ createdAt: Date.now(),
1033
+ updatedAt: Date.now(),
1034
+ metadata: {
1035
+ name: config.name, // canonical persona name — single source of truth
1036
+ prompt,
1037
+ values: identity.values,
1038
+ traits: identity.traits,
1039
+ style: identity.style,
1040
+ version: 1
1041
+ }
1042
+ })
1043
+ }
1044
+
1045
+ /**
1046
+ * Seed optional initial goals. If none provided, the Will starts goalless —
1047
+ * the executive engine detects this via _goallessTickCount and fires an early
1048
+ * cycle (~20 ticks) to generate goals from the Will's identity and persona.
1049
+ */
1050
+ function _seedInitialGoals( simulation: DefaultSimulation, config: WillConfig ): void {
1051
+ const goals = config.initialGoals ?? []
1052
+ goals.forEach( ( goal, i ) => simulation.stateManager.setEntity({
1053
+ id: goal.id ?? `goal-initial-${i}`,
1054
+ type: 'goal',
1055
+ createdAt: Date.now(),
1056
+ updatedAt: Date.now(),
1057
+ metadata: {
1058
+ description: goal.description,
1059
+ priority: goal.priority,
1060
+ status: 'active',
1061
+ tags: goal.tags ?? []
1062
+ }
1063
+ }) )
1064
+ }
1065
+
1066
+ /**
1067
+ * Seed initial engine-config mirror entities. These entities make every tunable
1068
+ * engine parameter visible to the executive reasoning cycle, metacognition engines,
1069
+ * and future self-tuning features.
1070
+ */
1071
+ function _seedEngineConfigs( simulation: DefaultSimulation, entities: EngineConfigEntity[] ): void {
1072
+ for( const cfg of entities )
1073
+ simulation.stateManager.setEntity({
1074
+ id: cfg.id,
1075
+ type: 'engine.config',
1076
+ createdAt: Date.now(),
1077
+ updatedAt: Date.now(),
1078
+ metadata: { engine: cfg.engine, params: cfg.params },
1079
+ })
1080
+ }
1081
+
1082
+ // ── Helpers ──────────────────────────────────────────────────
1083
+
1084
+ export function resolveExecutiveInterval( tier: EngineTier, config: WillConfig ): number {
1085
+ const tierDefault = TIER_EXECUTIVE_INTERVAL[ tier ]
1086
+ const requested = config.executiveInterval ?? tierDefault
1087
+ const floor = config.minExecutiveInterval ?? 0
1088
+
1089
+ return Math.max( requested, floor )
1090
+ }