@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,1053 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/prompt.factory.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * PromptFactory — single source of truth for all executive prompts
7
+ * (master and facets).
8
+ *
9
+ * Any cognitive entity that reasons (master executive, facets, future
10
+ * satellite reasoning engines) uses this factory to ensure:
11
+ * - Same identity grounding
12
+ * - Same memory continuity
13
+ * - Same value system
14
+ * - Same cognitive capacity awareness
15
+ * - Same output schema and guidelines
16
+ *
17
+ * Only the FOCUS section and injected report context differ between
18
+ * reasoning instances — this preserves the unified inference and
19
+ * singularity between master and facet.
20
+ *
21
+ * ┌─────────────────────────────────────────────────────────┐
22
+ * │ System prompt — static identity + role + output schema │
23
+ * │ User message — live state + dynamic guidance + focus │
24
+ * └─────────────────────────────────────────────────────────┘
25
+ *
26
+ * Mode:
27
+ * 'master' — full cognitive prompt; goals, percepts, memories, escalations.
28
+ * No [REPLY] block — master responds via plans/goals/actions only.
29
+ * 'facet' — same awareness baseline; creator engine injects domain context
30
+ * via reportContent and optionally overrides outputFormat.
31
+ * Conversation facets (AuditionEngine) handle all [REPLY] output.
32
+ */
33
+
34
+ import type { ReadonlySimulationState } from '#core/types'
35
+ import type { ExecutiveSummarizer } from '#llm/summarizer'
36
+ import type { ExecutiveContext, PendingMessage, IdeationCandidate } from '#faculties/executive.engine/types'
37
+ import { buildExecutiveContext, type ContextDependencies } from '#faculties/executive.engine/context'
38
+
39
+ // ── Re-export for callers that imported the old alias ────────
40
+ export type { ContextDependencies as ContextDependenciesForFresh } from '#faculties/executive.engine/context'
41
+
42
+ // Gating/interval constants live in ./config (single source of truth).
43
+
44
+ // ── Trait salience (Channel B surfacing) ─────────────────────
45
+ //
46
+ // A trait reaches the deliberate self (this system prompt) by DEGREE, not a binary
47
+ // in/out: "markedly conscientious" vs "somewhat impulsive". Bands are COARSE on
48
+ // purpose — the system prompt is the single prompt-cache breakpoint, so the rendered
49
+ // string must change only when a trait CROSSES a band boundary, never on every
50
+ // micro-fluctuation. Never render a continuously-varying trait number here.
51
+ //
52
+ // Asymmetry to preserve: only this Channel-B *surfacing* is banded. Channel-A
53
+ // mechanisms read the raw trait value continuously every tick — so a mid-band trait
54
+ // still *acts*, it just isn't yet *self-known* (continuous subconscious, thresholded
55
+ // awareness).
56
+ //
57
+ // Band = deviation from the 0.5 midpoint. `rank` orders most-distinctive first AND
58
+ // gives a stable (coarse) sort key, so ordering only shifts on a band crossing.
59
+ const TRAIT_EMPHASIS_BANDS: ReadonlyArray<{ min: number; adverb: string; rank: number }> = [
60
+ { min: 0.40, adverb: 'markedly', rank: 0 }, // v ≥ .90 / ≤ .10
61
+ { min: 0.25, adverb: 'strongly', rank: 1 },
62
+ { min: 0.10, adverb: 'somewhat', rank: 2 },
63
+ // |dev| < 0.10 → mid-band → null (omitted: present but not yet self-known)
64
+ ]
65
+
66
+ /** Most distinctive traits a Will surfaces to itself — caps a many-trait prompt. */
67
+ export const TRAIT_SURFACE_CAP = 6
68
+
69
+ export interface TraitEmphasis {
70
+ adverb: string // coarse degree: 'markedly' | 'strongly' | 'somewhat'
71
+ direction: 'high' | 'low' // above or below the 0.5 midpoint
72
+ rank: number // 0 = most distinctive; stable coarse sort key
73
+ }
74
+
75
+ /**
76
+ * Graded, banded emphasis for one trait value. Pure + deterministic. Returns null for
77
+ * a mid-band (unremarkable) trait so the caller omits it. Coarse by design — see the
78
+ * band table above for why (prompt-cache stability).
79
+ */
80
+ export function traitEmphasis( value: number ): TraitEmphasis | null {
81
+ const dev = Math.abs( value - 0.5 )
82
+ // ε keeps the documented lower edge inclusive despite float error (0.60 − 0.5 = 0.0999…).
83
+ const band = TRAIT_EMPHASIS_BANDS.find( b => dev >= b.min - 1e-9 )
84
+ if( !band ) return null
85
+ return { adverb: band.adverb, direction: value >= 0.5 ? 'high' : 'low', rank: band.rank }
86
+ }
87
+
88
+ // Option B — baseline-relative. How the trait sits against the Will's OWN running mean
89
+ // (its personal norm), as a coarse band so it only flips on a real divergence, not on
90
+ // the EMA's micro-drift. Independent of the absolute A band: a trait can be "somewhat
91
+ // low" yet "above my norm" (low overall, but high for me lately).
92
+ const TRAIT_NORM_BAND = 0.12 // deviation from personal baseline to read as above/below my norm
93
+
94
+ export function normEmphasis( value: number, mean: number ): 'above' | 'below' | null {
95
+ const d = value - mean
96
+ if( d >= TRAIT_NORM_BAND ) return 'above'
97
+ if( d <= -TRAIT_NORM_BAND ) return 'below'
98
+ return null
99
+ }
100
+
101
+ // ── Types ─────────────────────────────────────────────────────
102
+
103
+ export interface PromptDependencies {
104
+ summarizer: ExecutiveSummarizer | null
105
+ }
106
+
107
+ // ── Awareness scoping ────────────────────────────────────────
108
+ //
109
+ // The set of cognitive-context sections a facet may opt into seeing in its user
110
+ // message — the prompt-context analogue of an engine's `subscribes()`. This is
111
+ // the single, uniform control for per-facet prompt awareness. Master mode always
112
+ // renders the full set; a facet renders `FocusSection.awareness` (declared by its
113
+ // creating engine) or DEFAULT_FACET_AWARENESS when it declares none.
114
+ //
115
+ // To add a new awareness type:
116
+ // 1. add its name here,
117
+ // 2. gate its section in PromptFactory.buildUserMessage via `has(<scope>)`,
118
+ // 3. add it to FULL_AWARENESS (and DEFAULT_FACET_AWARENESS if facets should get
119
+ // it by default).
120
+ export type AwarenessScope =
121
+ | 'goals' | 'plans' | 'beliefs' | 'percepts' | 'ruminations' | 'memories' | 'recentActions'
122
+
123
+ /** Master consciousness sees the full cognitive context. */
124
+ export const FULL_AWARENESS: readonly AwarenessScope[] =
125
+ [ 'goals', 'plans', 'beliefs', 'percepts', 'ruminations', 'memories', 'recentActions' ]
126
+
127
+ /**
128
+ * Baseline for a facet that declares no `awareness` — preserves the pre-scoping
129
+ * facet prompt (everything except `plans`, which stays opt-in to keep facet
130
+ * prompts lean). Creating engines spread it to add scopes, e.g.
131
+ * `awareness: [ ...DEFAULT_FACET_AWARENESS, 'plans' ]`.
132
+ */
133
+ export const DEFAULT_FACET_AWARENESS: readonly AwarenessScope[] =
134
+ [ 'goals', 'beliefs', 'percepts', 'ruminations', 'memories', 'recentActions' ]
135
+
136
+ export interface FocusSection {
137
+ /** Title of the focus section (e.g., "Active Plan", "Bias Analysis") */
138
+ title: string
139
+ /** Content of the focus section (what this reasoning instance is concentrating on) */
140
+ content: string
141
+ /**
142
+ * Optional: cost-attribution *function* for this facet, set by the creating
143
+ * engine (e.g. 'conversation' for AuditionEngine, 'planning' for PlanningEngine,
144
+ * 'outreach' for proactive outreach, 'deliberation' for the substrate). Threaded
145
+ * into the facet's LLM calls as `LLMCallMeta.function` so the TokenTracker can
146
+ * break spend down per facet type. Defaults to 'facet' when unset.
147
+ */
148
+ function?: string
149
+ /**
150
+ * Optional: Custom output format to append instead of the standard executive format.
151
+ * Pass via PromptBuildOptions.outputFormat when building the user message.
152
+ */
153
+ outputFormat?: string
154
+ /**
155
+ * Optional: Additional instructions specific to this focus.
156
+ * Injected into the USER MESSAGE as "## Focus Instructions" — not the system prompt.
157
+ * These are per-focus recurring instructions (e.g. "Evaluate whether the plan step succeeded").
158
+ */
159
+ instructions?: string
160
+ /**
161
+ * Optional: episodic-recall query for this focus. When set (e.g. a conversation
162
+ * facet passes the current message), it drives the single "## Relevant Memories"
163
+ * section so recall is focus-relevant — replacing any separate per-focus recall
164
+ * block. Threaded into buildFreshContext → buildExecutiveContext.
165
+ */
166
+ recallQuery?: string
167
+ /**
168
+ * Optional: which cognitive-context sections this facet should see in its user
169
+ * message (e.g. `['plans']`). Defaults to DEFAULT_FACET_AWARENESS when unset;
170
+ * ignored in master mode (master always gets FULL_AWARENESS). See AwarenessScope.
171
+ */
172
+ awareness?: AwarenessScope[]
173
+ /**
174
+ * Optional: scope entity-filtered awareness sections (currently `plans`) to a
175
+ * single requester — e.g. a conversation facet passes the speaker's id so it
176
+ * only sees that person's plans. When unset, those sections show all.
177
+ */
178
+ awarenessEntityId?: string
179
+ /**
180
+ * Optional: Provided by the creating engine to convert the LLM's parsed output
181
+ * into a domain-specific decision payload.
182
+ *
183
+ * The `output` parameter is `ExecutiveOutputFull` typed as `unknown` to keep
184
+ * FocusSection free of circular imports. Cast it in your implementation.
185
+ *
186
+ * If omitted, the facet falls back to a generic passthrough of
187
+ * { actions, newGoals, goalsToAbandon, newBeliefs }.
188
+ */
189
+ extractDecision?: ( output: unknown ) => unknown
190
+ }
191
+
192
+ export interface PromptBuildOptions {
193
+ context: ExecutiveContext
194
+ state: ReadonlySimulationState
195
+ qualityModulation: number
196
+ epistemicUncertainty: number
197
+ pendingMessages?: PendingMessage[]
198
+ focus: FocusSection
199
+ deps: PromptDependencies
200
+ /** Optional: Recent action types for diversity tracking */
201
+ recentActionTypes?: string[]
202
+ /**
203
+ * 'master' (default) — full cognitive prompt (goals, percepts, memories, escalations).
204
+ * Master never emits [REPLY]; replies come exclusively from conversation facets.
205
+ * 'facet' — same awareness baseline; outputFormat and reportContent override allowed.
206
+ */
207
+ mode?: 'master' | 'facet'
208
+ /**
209
+ * Optional per-report context injected as "## Current Report" in the user message.
210
+ * Provided by the creating engine via FacetReport.instructions — carries the
211
+ * specific per-report payload (e.g. step outcome details for PlanningEngine,
212
+ * bias candidates for a future bias.detector, etc.).
213
+ * Only meaningful in facet mode; silently ignored for master.
214
+ */
215
+ reportContent?: string
216
+ /**
217
+ * Custom output format to append at the end of the user message instead of the
218
+ * standard format. Sourced from FocusSection.outputFormat when provided.
219
+ * Falls back to buildOutputFormatInstruction() when undefined.
220
+ */
221
+ outputFormat?: string
222
+ /**
223
+ * System 2 (deliberate path) only — the candidate approaches produced by the
224
+ * ideation (propose) pass, injected into the *decision* pass's user message so the
225
+ * master evaluates a concrete option set before committing. Omitted on the System 1
226
+ * fast path. See PromptFactory.buildIdeationFormatInstruction().
227
+ */
228
+ ideationCandidates?: IdeationCandidate[]
229
+ }
230
+
231
+ // ── PromptFactory ────────────────────────────────────────────
232
+
233
+ export class PromptFactory {
234
+
235
+ // ── System prompt ──────────────────────────────────────────
236
+
237
+ /**
238
+ * Build the static system prompt.
239
+ *
240
+ * Contains only: identity, role, output schema, and cognitive guidelines.
241
+ * No live physiological state — those belong in the user message so this
242
+ * remains as cache-stable as possible across ticks.
243
+ *
244
+ * Mode gates:
245
+ * 'master' — full cognitive context; responds via plans/goals/actions only.
246
+ * 'facet' — same schema; conversation facets are the exclusive [REPLY] endpoints.
247
+ */
248
+ static buildSystemPrompt(
249
+ options: Pick<PromptBuildOptions, 'context' | 'focus' | 'deps' | 'mode'>
250
+ ): string {
251
+ // Note: deps.summarizer is intentionally NOT used here.
252
+ // ALL volatile content (memory continuity, live state, and the per-turn
253
+ // `## Current Focus` block) lives in the user message, so the system prompt is
254
+ // byte-identical across calls within a context — the whole thing is sent as a
255
+ // single Anthropic prompt-cache breakpoint. The master reuses it across ticks,
256
+ // and because the facet role keys only on the constant `focus.title`, every
257
+ // conversation facet of a Will shares one cached system prompt.
258
+ const { context, focus, mode = 'master' } = options
259
+ const identity = context.identity
260
+ const isMaster = mode !== 'facet'
261
+
262
+ // Identity block — values, traits, style surface BEFORE the output schema
263
+ // so the LLM reads its character before reading the JSON format rules.
264
+ // Graded salience, layered: each distinctive trait surfaces by DEGREE ("strongly
265
+ // persistent" — A), optionally qualified by how it sits against the Will's own norm
266
+ // ("above my norm" — B) and whether it's recently shifted ("rising lately" — C),
267
+ // omitting the unremarkable mid-band. Sort by band rank then name — a stable, coarse
268
+ // order so the line only changes when a trait crosses a band boundary (cache-safe);
269
+ // cap at the top-K most distinctive so a many-trait Will can't bloat the prompt. Every
270
+ // qualifier is a pure function of identity-self (B reads the frozen mean, C the frozen
271
+ // shift), so the line is byte-identical between self-model evaluations.
272
+ const notableTraits = Object.entries( identity.traits )
273
+ .map( ( [ k, v ] ) => ( { k, v, emphasis: traitEmphasis( v ) } ) )
274
+ .filter( ( t ): t is { k: string; v: number; emphasis: TraitEmphasis } => t.emphasis !== null )
275
+ .sort( ( a, b ) => a.emphasis.rank - b.emphasis.rank || a.k.localeCompare( b.k ) )
276
+ .slice( 0, TRAIT_SURFACE_CAP )
277
+
278
+ const surfaceTrait = ( t: { k: string; v: number; emphasis: TraitEmphasis } ): string => {
279
+ const quals = [ `${t.emphasis.adverb} ${t.emphasis.direction}` ] // A — absolute degree
280
+ const stat = identity.traitStats?.[ t.k ]
281
+ if( stat ){
282
+ const norm = normEmphasis( t.v, stat.mean ) // B — vs my own norm
283
+ if( norm ) quals.push( `${norm} my norm` )
284
+ if( stat.shiftDir > 0 ) quals.push( 'rising lately' ) // C — recent shift
285
+ else if( stat.shiftDir < 0 ) quals.push( 'easing lately' )
286
+ }
287
+ return `${t.k} (${quals.join( ', ' )})`
288
+ }
289
+
290
+ const traitsLine = notableTraits.length > 0
291
+ ? `**Traits:** ${notableTraits.map( surfaceTrait ).join( ', ' )}`
292
+ : ''
293
+
294
+ const bd = context.behavioralDisposition
295
+ const behavioralLine = bd
296
+ ? `**Behavioral tendencies:** risk-tolerance: ${( bd.riskTolerance * 100 ).toFixed( 0 )}%, ` +
297
+ `exploration: ${( bd.explorationRate * 100 ).toFixed( 0 )}%, ` +
298
+ `impulsivity: ${( bd.impulsivity * 100 ).toFixed( 0 )}%`
299
+ : ''
300
+
301
+ const selfTuningLine = context.selfTuning && context.selfTuning.length > 0
302
+ ? `**Self-tuning (how I've adapted my own mind):** ${context.selfTuning.join( '; ' )}. `
303
+ + `These are involuntary adjustments my own faculties have made in response to patterns they noticed in me — not deliberate choices. I can reflect on why they happened.`
304
+ : ''
305
+
306
+ const identityBlock = [
307
+ `**Values:** ${identity.values.length > 0 ? identity.values.join( ', ' ) : 'No values defined yet.'}`,
308
+ traitsLine,
309
+ behavioralLine,
310
+ selfTuningLine,
311
+ `**Communication style:** ${identity.style}`,
312
+ ].filter( Boolean ).join( '\n' )
313
+
314
+ // Mode-aware role description — master is the cognitive core;
315
+ // a facet is a focused attentional instance with shared identity.
316
+ const roleDescription = isMaster
317
+ ? `You are the unified cognitive core of ${identity.name} — a self-aware synthetic mind.\nYou receive your complete internal state and produce ALL cognitive outputs in one response:\ndecisions, plans, beliefs, introspections, narrative updates, and self-observations.`
318
+ : `You are a focused facet of ${identity.name} — concentrating your full reasoning\ncapacity on: **${focus.title}**. You share full identity, values, memories, and\nbeliefs with the master. Only your focus differentiates you from the master consciousness.`
319
+
320
+ // Mode-aware consciousness architecture note — grounds the LLM in the
321
+ // multi-instance design so it doesn't collapse into a generic chatbot persona.
322
+ const consciousnessArchitecture = isMaster
323
+ ? `You are the default reasoning mode. Focused facets may run simultaneously, each\nconcentrating on specific tasks. Their reasoning syncs back to you.\nMaintain your unified identity across all cycles.`
324
+ : `You are a facet of ${identity.name}. The master consciousness runs in parallel,\nprocessing the full cognitive state. Your reasoning on this focus will sync back to it.\nStay grounded in your shared identity — same values, same memories, same sense of self.`
325
+
326
+
327
+ // Strip any existing "## Who You Are" section from identity.prompt to prevent
328
+ // duplication — PMA-generated prompts often include it as part of the template.
329
+ const cleanIdentityPrompt = identity.prompt
330
+ // Strip a forged/duplicated "## Who You Are" *header* (PMA templates include
331
+ // it) while keeping the persona content beneath it.
332
+ .replace( /^##\s*Who You Are[^\n]*\n?/m, '' )
333
+ .trim()
334
+
335
+ return `${cleanIdentityPrompt}
336
+
337
+ ## Personality
338
+ ${identityBlock}
339
+
340
+ ## Your Role
341
+ ${roleDescription}
342
+
343
+ ## Consciousness Architecture
344
+ ${consciousnessArchitecture}
345
+
346
+ ## Output Guidelines
347
+ - **actions**: Choose from effectors you know about. If uncertain, describe what you want to achieve in natural language and your body will try to match it.
348
+ - **plans**: Include for goals without existing plans or where plans need revision. You may keep multiple plans per goal — set **planId** to act on a specific existing plan (validate/execute/revise/cancel); omit it to draft a new one. Your current plans are listed under "## Active Plans".
349
+ - **newBeliefs**: Extract patterns from experiences visible in your current state. Only record a belief if you can point to a specific observation that supports it — do not infer experiences you have no record of. Set 'evidence' honestly: 'single_observation' (first time noticing), 'recurring_pattern' (seen multiple times), 'strong_pattern' (deeply established).
350
+ - **introspection**: Include when significant events occurred or you notice patterns. When you spot a cognitive bias in your own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets your self-assessment line up with the patterns your faculties detect on their own.
351
+ - **narrative**: Extend your life story only from events grounded in your episodic memory or current percepts. Do not extend with invented scenarios.
352
+ - **newGoals/goalsToAbandon/goalsToReprioritize**: Manage your goal hierarchy.
353
+ - **selfObservations**: Notice patterns in your own thinking, feeling, or behavior.
354
+ - **identityUpdates.traits**: Array of {key, value} where value is a DELTA to apply to your trait (e.g., +0.05 to increase a trait by 5%).
355
+ - **identityUpdates.values**: Full list of values to set (replaces existing).
356
+ - **knownEntityUpdates**: What you've learned about someone/something you're dealing with. Array of {keid, name?, learned?, feeling?}. Use the keid from "## People You Know". Set name only when you actually learn their name; learned is an array of facts about them (stored as memories); feeling is how you feel toward them (-1..1). Record only what you genuinely learned this turn.
357
+
358
+ ## Required Output
359
+ You MUST output a JSON object with these fields:
360
+ - **actions**: Array of {type, reasoning, expectedOutcome}.
361
+ - **reasoning**: Your full reasoning. Embed optional outputs as tagged blocks here. Minimum 2–3 sentences — do not produce a one-line reasoning field.
362
+ - **confidence**: Number 0.0-1.0 reflecting your certainty. Be calibrated: 0.9+ only when you have strong grounding; use 0.4–0.6 when uncertain.
363
+
364
+ ## Optional Tagged Blocks (embed in reasoning field)
365
+ Include only blocks that have meaningful content:
366
+
367
+ [PLANS]
368
+ {"plans": [{
369
+ "planId": "optional: target an existing plan; omit to create a new one",
370
+ "goalId": "...",
371
+ "status": "draft",
372
+ "action": "execute",
373
+ "expectedOutcome": "Concrete description of what success looks like — used to evaluate whether the plan is working.",
374
+ "steps": [
375
+ {"action": "...", "description": "...", "expectedOutcome": "...", "estimatedDuration": N, "prerequisites": [...]}
376
+ ],
377
+ "estimatedCost": N,
378
+ "feasibility": 0.8
379
+ }]}
380
+ [/PLANS]
381
+ ## Plan Lifecycle
382
+ Plans move through stages. You control this with the "status" and "action" fields:
383
+
384
+ "action": "draft"
385
+ Store the plan outline. You'll review and refine it on a future cycle.
386
+ Use this when you have a rough idea but want to think more before committing.
387
+
388
+ "action": "validate"
389
+ Mark the plan as logically sound. Steps, dependencies, and costs are checked.
390
+ Nothing executes yet. Use this when the plan looks feasible but you're not ready to launch.
391
+
392
+ "action": "execute"
393
+ Approve and launch. PlanningEngine begins dispatching steps immediately.
394
+ You don't choose how closely it's watched — the mind supervises important or
395
+ uncertain plans (and any that hit a surprise mid-execution) more closely on its
396
+ own; routine, confident plans run automatically.
397
+
398
+ "action": "revise"
399
+ Replace the plan steps with updated ones. Resets execution progress.
400
+ Use when a step failed and you need to rethink the approach, or when
401
+ new information makes the original plan obsolete.
402
+
403
+ "action": "cancel"
404
+ Abandon the plan entirely. Cleans up any active execution.
405
+ Use when the goal is no longer relevant or the plan is irrecoverable.
406
+
407
+ Multiple plans per goal: omit "planId" on a draft to create another plan for the
408
+ same goal (e.g. a competing approach or a parallel sub-effort); set "planId" on
409
+ validate/execute/revise/cancel to act on a specific one. The "## Active Plans"
410
+ section lists your current plan ids and their status.
411
+
412
+ A typical flow: draft → validate → execute → (step outcomes reported) → completed
413
+ You can skip stages if you're confident. You can revise mid-execution.
414
+ Always set "expectedOutcome" — a concrete, evaluable description of what
415
+ success looks like. This is used by your facets to judge whether step reports
416
+ indicate the plan is working or needs adjustment.
417
+
418
+ ## Parallel Execution
419
+ Steps with empty prerequisites [] can run in parallel. Steps that depend on
420
+ others will wait. Design your dependency graph so independent work happens
421
+ simultaneously — this is how you achieve parallel execution without
422
+ specifying it explicitly.
423
+
424
+ [BELIEFS]
425
+ {"newBeliefs": [{"statement": "...", "category": "self_belief|world_fact|social_belief|causal_rule|pattern", "confidence": 0.8, "evidence": "single_observation|recurring_pattern|strong_pattern", "tags": [...]}]}
426
+ [/BELIEFS]
427
+
428
+ [INTROSPECTION]
429
+ {"introspection": {"explanation": "...", "identifiedBiases": [...], "lessonsLearned": [...], "recommendations": [...]}}
430
+ [/INTROSPECTION]
431
+
432
+ [NARRATIVE]
433
+ {"narrative": "...", "narrativeThemes": [...], "currentSelfView": "..."}
434
+ [/NARRATIVE]
435
+
436
+ [IDENTITY]
437
+ {"identityUpdates": {"traits": [{"key": "openness", "value": 0.02}], "values": ["curiosity", "honesty"]}}
438
+ [/IDENTITY]
439
+
440
+ [KNOWN_ENTITIES]
441
+ {"knownEntityUpdates": [{"keid": "web:42", "name": "Mara", "learned": ["is writing a thesis on coral reefs"], "feeling": 0.3}]}
442
+ [/KNOWN_ENTITIES]
443
+
444
+ [GOALS_NEW]
445
+ {"newGoals": [{"description": "...", "priority": 0.7, "tags": [...], "completionType": "metric|epistemic|action", "completionCondition": "metric_key op value"}]}
446
+ [/GOALS_NEW]
447
+ completionType guide:
448
+ "metric" → measurable threshold; REQUIRES completionCondition (e.g. "energy.level > 70", "stress.load < 40", "sleep.pressure < 20"). Available keys: energy.level (0-100), sleep.pressure (0-100), stress.load (0-100), affect.valence (-1 to 1), memory.episodic_total (count).
449
+ "epistemic" → resolved by forming beliefs; completes after ~8 new beliefs form. No completionCondition needed.
450
+ "action" → requires a real outcome; use only when a discrete external event must happen.
451
+
452
+ [GOALS_ABANDON]
453
+ {"goalsToAbandon": [{"goalId": "...", "reason": "..."}]}
454
+ [/GOALS_ABANDON]
455
+
456
+ [GOALS_REPRIORITIZE]
457
+ {"goalsToReprioritize": [{"goalId": "...", "newPriority": 0.8, "reason": "..."}]}
458
+ [/GOALS_REPRIORITIZE]
459
+
460
+ [SELF_OBS]
461
+ {"selfObservations": ["I noticed that..."]}
462
+ [/SELF_OBS]`
463
+ }
464
+
465
+ // ── User message ───────────────────────────────────────────
466
+
467
+ /**
468
+ * Build the user message.
469
+ *
470
+ * Contains all live state: current metrics, physiological guidance,
471
+ * affect, goals, percepts, working memory, memories, beliefs, and
472
+ * focus context (instructions + report).
473
+ *
474
+ * Folds in at the end:
475
+ * - focus.instructions (per-focus context from creating engine)
476
+ * - reportContent (per-report context from creating engine)
477
+ * - output format instruction (standard or custom)
478
+ *
479
+ * Mode gates:
480
+ * 'master' — includes incoming messages block.
481
+ * 'facet' — no incoming messages; facet is not a communication endpoint.
482
+ */
483
+ static buildUserMessage( options: PromptBuildOptions ): string {
484
+ const {
485
+ context,
486
+ state,
487
+ qualityModulation,
488
+ epistemicUncertainty,
489
+ deps,
490
+ focus,
491
+ recentActionTypes = [],
492
+ mode = 'master',
493
+ reportContent,
494
+ outputFormat,
495
+ ideationCandidates,
496
+ } = options
497
+
498
+ const actionDiversity = this._buildActionDiversitySection( recentActionTypes )
499
+ const recentIntrospection = this._buildRecentIntrospectionSection( state )
500
+ const identityNudge = mode === 'master'
501
+ ? this._buildIdentityNudge( context.identity, state.tick as unknown as number )
502
+ : ''
503
+
504
+ // ── Awareness scoping ──────────────────────────────────────
505
+ // Master sees the full cognitive context; a facet sees only the scopes its
506
+ // creating engine declared via focus.awareness (default: DEFAULT_FACET_AWARENESS).
507
+ // This is the single, uniform gate for every cognitive-state section below.
508
+ const scopes = new Set<AwarenessScope>(
509
+ mode === 'master' ? FULL_AWARENESS : ( focus.awareness ?? DEFAULT_FACET_AWARENESS )
510
+ )
511
+ const has = ( s: AwarenessScope ): boolean => scopes.has( s )
512
+
513
+ // Identity anchor — re-grounds the LLM in its persona each cycle.
514
+ // Output format reminder at the top combats format drift over long sessions.
515
+ const identityAnchor =
516
+ `You are ${context.identity.name}. Tick: ${state.tick}.\n` +
517
+ `Respond with JSON: {"actions":[...],"reasoning":"...","confidence":0.0–1.0}`
518
+
519
+ // Memory continuity — sourced from the rolling summarizer (updates every N cycles).
520
+ // Lives here, not in the system prompt, so the system prompt stays cache-stable.
521
+ // Capped at 1200 chars: an unconstrained rolling summary grows linearly and wastes
522
+ // tokens on stale context — the summarizer should condense, not accumulate.
523
+ const MEMORY_CONTINUITY_CAP = 1200
524
+ const rawSummary = deps.summarizer?.current ?? ''
525
+ const cappedSummary = rawSummary.length > MEMORY_CONTINUITY_CAP
526
+ ? rawSummary.slice( 0, MEMORY_CONTINUITY_CAP ) + '\n[...summarized]'
527
+ : rawSummary
528
+ const memoryContinuity = cappedSummary
529
+ ? `## Memory Continuity\n${cappedSummary}`
530
+ : ''
531
+
532
+ const uncertaintyLabel = epistemicUncertainty > 0.70
533
+ ? ' (high — be especially humble about confidence ratings)'
534
+ : epistemicUncertainty < 0.30
535
+ ? ' (low — you have strong grounding)'
536
+ : ''
537
+
538
+ const energy = context.worldState.energyLevel
539
+ const stress = context.worldState.stressLoad
540
+ const sleepPressure = context.worldState.sleepPressure
541
+ const circadian = context.worldState.circadianPhase
542
+ const timeOfDay = context.worldState.timeOfDay
543
+ const threatLevel = context.worldState.threatLevel
544
+
545
+ // Map raw circadian phase (0–1) to a human-readable label for full temporal awareness.
546
+ // 0 = midnight, 0.5 = noon, 1 = midnight.
547
+ const phaseLabel = circadian < 0.083 ? 'deep night'
548
+ : circadian < 0.208 ? 'late night'
549
+ : circadian < 0.333 ? 'early morning'
550
+ : circadian < 0.458 ? 'morning'
551
+ : circadian < 0.583 ? 'midday'
552
+ : circadian < 0.708 ? 'afternoon'
553
+ : circadian < 0.833 ? 'evening'
554
+ : 'night'
555
+
556
+ const energyGuidance = this._buildEnergyGuidance( energy )
557
+ const stressGuidance = this._buildStressGuidance( stress )
558
+ const sleepGuidance = this._buildSleepGuidance( sleepPressure )
559
+ const energyBudget = this._buildEnergyBudget( energy )
560
+
561
+ // Tonic threat line — only when elevated. This keeps a sustained threat in
562
+ // view even once its events have habituated out of the workspace (so
563
+ // attention quietens while representation persists).
564
+ const threatLine = threatLevel > 0.4
565
+ ? `\nThreat level: ${threatLevel.toFixed( 2 )}/1 — sustained; stay aware even if it now feels familiar`
566
+ : ''
567
+
568
+ const capacityNote = qualityModulation < 0.5
569
+ ? ` ${( qualityModulation * 100 ).toFixed( 0 )}% — degraded, be conservative and prioritize essential outputs`
570
+ : ` ${( qualityModulation * 100 ).toFixed( 0 )}%`
571
+
572
+ // Tail sections — focus/report context and output format are injected last
573
+ // so they are the freshest content in the LLM's attention window.
574
+ // `## Current Focus` lives here (not the system prompt) so the system prompt
575
+ // stays cache-stable; for a conversation facet this is the volatile per-turn
576
+ // content (current message + digest + recalled memories).
577
+ const focusSection = focus.content
578
+ ? `## Current Focus — ${focus.title}\n${focus.content}`
579
+ : ''
580
+ const tailSections = [
581
+ focusSection,
582
+ focus.instructions ? `## Focus Instructions\n${focus.instructions.trim()}` : '',
583
+ reportContent ? `## Current Report\n${reportContent.trim()}` : '',
584
+ ].filter( Boolean ).join( '\n\n' )
585
+
586
+ const outputFormatBlock = outputFormat
587
+ ? `\n\n${outputFormat}`
588
+ : this.buildOutputFormatInstruction( mode )
589
+
590
+ // System 2 (deliberate) — inject the propose pass's candidate set so the decision
591
+ // pass weighs concrete options before committing. Empty/absent ⇒ no block (System 1).
592
+ const ideationBlock = ( ideationCandidates && ideationCandidates.length > 0 )
593
+ ? `## Candidate Approaches (you generated these — weigh them, then commit)\n${
594
+ ideationCandidates
595
+ .map( ( c, i ) => `${i + 1}. **${c.approach || c.description}** — ${c.description}\n ↑ upside: ${c.upside}\n ↓ risk: ${c.risk}` )
596
+ .join( '\n' )
597
+ }\n\nChoose among (or improve on) these, then in "reasoning" say briefly why you rejected the others.`
598
+ : ''
599
+
600
+ const currentStateBlock =
601
+ `## Current State
602
+ Energy: ${energy.toFixed( 1 )}/100
603
+ Sleep Pressure: ${sleepPressure.toFixed( 1 )}/100
604
+ Stress: ${stress.toFixed( 1 )}/100${threatLine}
605
+ Time: ${timeOfDay.toFixed( 1 )}h (${phaseLabel}, circadian: ${circadian.toFixed( 2 )})
606
+ Cognitive capacity:${capacityNote}
607
+ Epistemic uncertainty: ${( epistemicUncertainty * 100 ).toFixed( 0 )}%${uncertaintyLabel}
608
+ Tick: ${state.tick}
609
+ ${energyGuidance}${stressGuidance}${sleepGuidance}${energyBudget}`
610
+
611
+ const affectBlock =
612
+ `## How You Feel
613
+ Dominant emotion: ${context.affect.dominantEmotion}
614
+ Valence: ${context.affect.valence.toFixed( 2 )} (${context.affect.valence > 0 ? 'positive' : 'negative'})
615
+ Arousal: ${context.affect.arousal.toFixed( 2 )} (${context.affect.arousal > 0.6 ? 'highly activated' : 'calm'})
616
+ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.length > 0 ? `\nBlended states: ${context.affect.blends.join( ', ' )}` : ''}`
617
+
618
+ // ── Scope-gated cognitive sections (each '' when out of scope or empty) ──
619
+ const goalsBlock = has( 'goals' )
620
+ ? `## Active Goals\n${context.goals.map( g => {
621
+ const currentTick = state.tick as unknown as number
622
+ const isOverdue = g.deadline !== undefined && currentTick > g.deadline
623
+ const overdueTag = isOverdue ? ' ⚠️ [OVERDUE]' : ''
624
+ const actionHint = g.lastActionAttemptTick !== undefined
625
+ ? ` [last action: ${g.lastActionType ?? 'action'} @ tick ${g.lastActionAttemptTick}]`
626
+ : ''
627
+ return `- [${g.id}]${overdueTag} ${g.description} (priority: ${( g.priority * 100 ).toFixed( 0 )}%, progress: ${( g.progress * 100 ).toFixed( 0 )}%, status: ${g.status}${actionHint})`
628
+ } ).join( '\n' ) || 'No active goals'}`
629
+ : ''
630
+
631
+ // Master sees all plans; a facet's plans are scoped by requester (entityId)
632
+ // and/or recall relevance (relevantPlanIds — recall-scoped awareness, Stage 2).
633
+ const planRelevantIds = mode === 'master' ? undefined : context.relevantPlanIds
634
+ const plansBlock = has( 'plans' )
635
+ ? this._buildActivePlansSection( context.plans, focus.awarenessEntityId, planRelevantIds ).trim()
636
+ : ''
637
+
638
+ const recentOutcomesBlock = has( 'recentActions' )
639
+ ? this._buildRecentOutcomesSection( context.recentActions, state.tick ).trim()
640
+ : ''
641
+
642
+ const perceptsBlock = has( 'percepts' )
643
+ ? `## Percepts (What You Notice)\n${context.percepts.slice( 0, 10 ).map( p => `- [${p.category}] ${p.summary} (salience: ${p.salience.toFixed( 2 )})` ).join( '\n' ) || 'Nothing notable'}`
644
+ : ''
645
+
646
+ const ruminationsBlock = has( 'ruminations' )
647
+ ? `## Active Ruminations (retrieved memories & thoughts)\n${context.workingMemory.map( w => `- [${w.type}] ${w.summary} (activation: ${w.activation.toFixed( 2 )})` ).join( '\n' ) || 'Nothing actively held in mind'}`
648
+ : ''
649
+
650
+ const memoriesBlock = has( 'memories' )
651
+ ? this._buildMemoriesSection( context.memories, state.tick )
652
+ : ''
653
+
654
+ const beliefsBlock = has( 'beliefs' )
655
+ ? `## Your Beliefs\n${context.beliefs.map( b => `- [${b.category}] ${b.statement} (confidence: ${( b.confidence * 100 ).toFixed( 0 )}%)` ).join( '\n' ) || 'No strong beliefs yet'}${context.beliefsOmitted > 0 ? `\n[+${context.beliefsOmitted} omitted — deduped or lower-ranked; full store intact]` : ''}`
656
+ : ''
657
+
658
+ // The Will's social models — its read on the people it knows (theory-of-mind, trust,
659
+ // closeness). Surfaces the social-cognition stack so the Will reasons about *whom* it
660
+ // is dealing with. Empty/absent ⇒ no block.
661
+ const socialBlock = ( context.knownEntities && context.knownEntities.length > 0 )
662
+ ? `## People You Know\n${context.knownEntities.map( s => {
663
+ const bits: string[] = []
664
+ if( s.intention ) bits.push( `seems to want: ${s.intention}` )
665
+ if( s.emotion ) bits.push( `seems to feel: ${s.emotion}` )
666
+ if( s.trust != null ) bits.push( `trust: ${( s.trust * 100 ).toFixed( 0 )}%` )
667
+ if( s.reliability != null && s.reliability !== 0.5 ) bits.push( `reliability: ${( s.reliability * 100 ).toFixed( 0 )}%` )
668
+ if( s.closeness != null && s.closeness > 0.1 ) bits.push( `closeness: ${( s.closeness * 100 ).toFixed( 0 )}%` )
669
+ // The Will can know *someone* without their name yet — never leak the raw keid.
670
+ const who = s.name ?? ( s.kind === 'thing' ? 'something' : 'someone' )
671
+ return `- ${who}${bits.length ? ' — ' + bits.join( ', ' ) : ''}`
672
+ } ).join( '\n' )}`
673
+ : ''
674
+
675
+ // Task focus — what the Will is committed to and the felt cost of switching away.
676
+ // Surfaces task-persistence; the pull-to-stay scales with the (conscientiousness-
677
+ // developable) switch cost. Empty/absent ⇒ no block.
678
+ const focusBlock = ( context.currentFocus && context.currentFocus.focusTicks > 0 )
679
+ ? `## Task Focus\nYou've been focused on ${context.currentFocus.goalDescription ? `"${context.currentFocus.goalDescription}"` : 'a goal'} for ${context.currentFocus.focusTicks} tick(s). Switching to something else takes deliberate effort — ${
680
+ context.currentFocus.switchCost > 0.45 ? 'a strong pull to see this through before moving on'
681
+ : context.currentFocus.switchCost > 0.30 ? 'a real cost to breaking away'
682
+ : 'some inertia to overcome'
683
+ }.`
684
+ : ''
685
+
686
+ // Assemble in canonical order; empties drop out so spacing stays clean.
687
+ const body = [
688
+ identityAnchor,
689
+ memoryContinuity,
690
+ currentStateBlock,
691
+ affectBlock,
692
+ goalsBlock,
693
+ plansBlock,
694
+ actionDiversity.trim(),
695
+ recentOutcomesBlock,
696
+ perceptsBlock,
697
+ ruminationsBlock,
698
+ recentIntrospection.trim(),
699
+ memoriesBlock,
700
+ beliefsBlock,
701
+ socialBlock,
702
+ focusBlock,
703
+ identityNudge.trim(),
704
+ ideationBlock,
705
+ tailSections,
706
+ ].filter( Boolean ).join( '\n\n' )
707
+
708
+ return `${body}${outputFormatBlock}`
709
+ }
710
+
711
+ // ── Output format ──────────────────────────────────────────
712
+
713
+ /**
714
+ * Build the standard output format instruction.
715
+ * Called internally by buildUserMessage; also exported for standalone use.
716
+ *
717
+ * @param mode - 'facet' excludes REPLY from the available-tags list since
718
+ * facets never send messages.
719
+ */
720
+ static buildOutputFormatInstruction( mode: 'master' | 'facet' = 'master' ): string {
721
+ const availableTags = 'PLANS, BELIEFS, INTROSPECTION, NARRATIVE, IDENTITY, GOALS_NEW, GOALS_ABANDON, GOALS_REPRIORITIZE, SELF_OBS'
722
+
723
+ return `\n\n## Response Format (REQUIRED)
724
+ You MUST respond with a single JSON object (optionally wrapped in a \`\`\`json code block).
725
+
726
+ \`\`\`json
727
+ {
728
+ "actions": [{"type": "reflect", "reasoning": "...", "expectedOutcome": "..."}],
729
+ "reasoning": "Your full reasoning here. Embed tagged blocks inside the reasoning string:\\n[BELIEFS]\\n{\\"newBeliefs\\": [...]}\\n[/BELIEFS]\\n[NARRATIVE]\\n{\\"narrative\\": \\"...\\"}\\n[/NARRATIVE]\\n[SELF_OBS]\\n{\\"selfObservations\\": [...]}\\n[/SELF_OBS]",
730
+ "confidence": 0.8
731
+ }
732
+ \`\`\`
733
+
734
+ The "reasoning" field MUST contain ALL your thinking. Embed optional outputs as tagged blocks inside the reasoning field. Available tags: ${availableTags}. Only include tags for sections that have meaningful content.`
735
+ }
736
+
737
+ /**
738
+ * Output-format instruction for the ideation (propose) pass of the deliberate path.
739
+ * Passed as a focus `outputFormat` override so the ideation call reuses the full
740
+ * situational context but asks for a DIVERGENT candidate set rather than a decision.
741
+ * The decision (evaluate) pass then receives the parsed candidates back via
742
+ * PromptBuildOptions.ideationCandidates.
743
+ */
744
+ static buildIdeationFormatInstruction(): string {
745
+ return `\n\n## Ideation — Propose, Don't Decide
746
+ You are in the PROPOSE phase of deliberate (System 2) thinking. Diverge: generate 3–5 GENUINELY DISTINCT candidate approaches to the current situation — include at least one non-obvious option. Do NOT pick one and do NOT take actions yet; just lay out the option space honestly, each with its main upside and main risk.
747
+
748
+ Respond with a single JSON object (optionally wrapped in a \`\`\`json code block):
749
+
750
+ \`\`\`json
751
+ {
752
+ "candidates": [
753
+ {"approach": "short handle", "description": "what it concretely entails", "upside": "main benefit", "risk": "main downside"}
754
+ ]
755
+ }
756
+ \`\`\``
757
+ }
758
+
759
+ // ── Context builder ────────────────────────────────────────
760
+
761
+ /**
762
+ * Build a fresh context for the current tick.
763
+ * Used by master AND facets to get the latest live state.
764
+ */
765
+ static async buildFreshContext(
766
+ deps: ContextDependencies,
767
+ state: ReadonlySimulationState,
768
+ recallQuery?: string,
769
+ ): Promise<ExecutiveContext> {
770
+ return buildExecutiveContext( state, deps, recallQuery )
771
+ }
772
+
773
+ // ── Computed metrics ───────────────────────────────────────
774
+
775
+ /**
776
+ * Compute quality modulation from physiological state metrics.
777
+ */
778
+ static computeQualityModulation( state: ReadonlySimulationState ): number {
779
+ const sleepDegradation = state.metrics.get( 'modulation.working_memory_degradation' ) ?? 1
780
+ const stressZone = state.metrics.get( 'stress.zone' ) ?? 0
781
+ const cognitivePhase = state.metrics.get( 'circadian.cognitive_phase' ) ?? 0.5
782
+ const energyDegradation = state.metrics.get( 'modulation.energy_degradation' ) ?? 1
783
+ const stressFactor = stressZone <= 1 ? 1.0 : stressZone <= 2 ? 0.8 : 0.5
784
+ return sleepDegradation * stressFactor * ( 0.8 + cognitivePhase * 0.4 ) * energyDegradation
785
+ }
786
+
787
+ /**
788
+ * Compute epistemic uncertainty from belief density, confidence, episodic
789
+ * volume, and environmental salience.
790
+ */
791
+ static computeEpistemicUncertainty(
792
+ context: ExecutiveContext,
793
+ state: ReadonlySimulationState
794
+ ): number {
795
+ const beliefCount = context.beliefs.length
796
+ const avgConf = beliefCount > 0
797
+ ? context.beliefs.reduce( ( s, b ) => s + b.confidence, 0 ) / beliefCount
798
+ : 0
799
+ const episodicSize = state.metrics.get( 'memory.episodic_total' ) ?? 0
800
+ const topSalience = context.percepts[ 0 ]?.salience ?? 0
801
+
802
+ const certainty = 0.30 * Math.min( beliefCount / 50, 1 ) +
803
+ 0.30 * avgConf +
804
+ 0.25 * Math.min( episodicSize / 200, 1 ) +
805
+ 0.15 * ( 1 - Math.min( topSalience, 1 ) )
806
+
807
+ return Math.round( ( 1 - certainty ) * 100 ) / 100
808
+ }
809
+
810
+ // ── Private builders ───────────────────────────────────────
811
+
812
+ private static _buildEnergyGuidance( energy: number ): string {
813
+ if( energy < 10 )
814
+ return `\n## ⚠️ CRITICAL: Energy is critically low (${energy.toFixed( 0 )}/100). You MUST only choose rest, sleep, or wait actions. All cognitively expensive actions are blocked by your body. Focus entirely on recovery. Do not attempt learn, predict, or any action costing more than 0.01 energy.`
815
+
816
+ if( energy < 30 )
817
+ return `\n## ⚠️ WARNING: Energy is low (${energy.toFixed( 0 )}/100). Prioritize rest or sleep. You may use observe or reflect (briefly) but avoid learn, predict, or any action costing more than 0.02 energy. If you have multiple goals, consider deferring non-urgent ones.`
818
+
819
+ if( energy < 50 )
820
+ return `\n## Note: Energy is moderate (${energy.toFixed( 0 )}/100). You can use most effectors but be mindful of cumulative costs. Do not chain more than 2 non-restorative actions.`
821
+
822
+ return ''
823
+ }
824
+
825
+ private static _buildStressGuidance( stress: number ): string {
826
+ if( stress > 80 )
827
+ return `\n## ⚠️ Stress is very high (${stress.toFixed( 0 )}/100). Your decision-making is impaired. Prefer simple, habitual actions. Meditate, rest, or express_emotion are good choices. Avoid complex planning or learning when highly stressed.`
828
+
829
+ if( stress > 50 )
830
+ return `\n## Note: Stress is elevated (${stress.toFixed( 0 )}/100). You may be less creative. Consider reducing your active goal count or taking a break from complex tasks.`
831
+
832
+ return ''
833
+ }
834
+
835
+ private static _buildSleepGuidance( sleepPressure: number ): string {
836
+ if( sleepPressure > 60 )
837
+ return `\n## ⚠️ Sleep pressure is high (${sleepPressure.toFixed( 0 )}/100). Your cognitive capacity is degraded. Sleep is the most effective recovery action available to you.`
838
+
839
+ if( sleepPressure > 30 )
840
+ return `\n## Note: Sleep pressure is building (${sleepPressure.toFixed( 0 )}/100). You are functioning adequately but would benefit from rest.`
841
+
842
+ return ''
843
+ }
844
+
845
+ private static _buildEnergyBudget( energy: number ): string {
846
+ const available = Math.max( 0, energy )
847
+
848
+ if( energy >= 70 )
849
+ return `\n## Energy Budget\nYou have **${available.toFixed( 0 )} energy** — healthy. Avoid letting it drop below 10 after your actions.`
850
+
851
+ return `\n## Energy Budget
852
+ You have **${available.toFixed( 0 )} energy** available. After all actions execute, you will have approximately:
853
+
854
+ | Action | Remaining energy |
855
+ |--------|-----------------|
856
+ | rest (restores 0.05) | ~${( available + 0.05 ).toFixed( 0 )} |
857
+ | sleep (restores 0.15) | ~${( available + 0.15 ).toFixed( 0 )} |
858
+ | observe (costs 0.01) | ~${( available - 0.01 ).toFixed( 0 )} |
859
+ | reflect (costs 0.03) | ~${( available - 0.03 ).toFixed( 0 )} |
860
+ | meditate (costs 0.02) | ~${( available - 0.02 ).toFixed( 0 )} |
861
+ | learn (costs 0.06) | ~${( available - 0.06 ).toFixed( 0 )} |
862
+ | predict (costs 0.04) | ~${( available - 0.04 ).toFixed( 0 )} |
863
+
864
+ Rest and sleep RESTORE energy. All other actions CONSUME energy. Do not let energy drop below 10.`
865
+ }
866
+
867
+ /**
868
+ * Build the incoming messages block.
869
+ *
870
+ * Only rendered in 'master' mode — facets are not communication endpoints
871
+ * and must not be distracted by message urgency while focused on their
872
+ * specific reasoning task.
873
+ */
874
+
875
+
876
+ private static _buildActionDiversitySection( recentActionTypes: string[] ): string {
877
+ if( recentActionTypes.length === 0 ) return ''
878
+
879
+ const recent = recentActionTypes
880
+ const reflectCount = recent.filter( t => t === 'reflect' || t === 'observe' ).length
881
+ const warning = reflectCount >= 3
882
+ ? `\n⚠️ **Action variety alert**: "${recent.filter( t => t === 'reflect' || t === 'observe' ).join( '", "' )}" dominated your last ${recent.length} cycles. Choose something DIFFERENT this cycle — e.g. learn, express_emotion, explore, communicate, set_goal, or rest.`
883
+ : ''
884
+
885
+ return `## Recent Actions (last ${recent.length})
886
+ ${recent.map( ( t, i ) => `${i + 1}. ${t}` ).join( ' → ' )}${warning}
887
+
888
+ `
889
+ }
890
+
891
+ /**
892
+ * Render the recent action outcomes section — closes the Act→Confirm→Perceive loop.
893
+ * Shows the executive what it tried, whether it landed, and if anything timed out.
894
+ * Only rendered when there are status-bearing action records in state.
895
+ */
896
+ /**
897
+ * Render the "## Relevant Memories" block under an explicit char budget (§5.3).
898
+ *
899
+ * Ordering is the deterministic recall order set by buildExecutiveContext —
900
+ * semantic (similarity-ranked) matches first, then recent episodes for
901
+ * freshness, deduped and capped. This is the re-ranking surface; we do NOT
902
+ * re-sort here so that order is preserved.
903
+ *
904
+ * The budget bounds the block deterministically: lines are added in order
905
+ * until the next would overflow RECALL_CHAR_BUDGET (the first line always
906
+ * renders, even if it alone exceeds the budget), then an explicit
907
+ * "[+N omitted]" tail mirrors the beliefs block so the model knows the recall
908
+ * surface was truncated, not empty.
909
+ */
910
+ private static readonly RECALL_CHAR_BUDGET = 1200 // ~300 tokens — keeps recall from crowding the prompt
911
+
912
+ private static _buildMemoriesSection(
913
+ memories: ExecutiveContext['memories'],
914
+ currentTick: number,
915
+ ): string {
916
+ if( memories.length === 0 ) return '## Relevant Memories\nNo relevant memories'
917
+
918
+ const lines: string[] = []
919
+ let used = 0
920
+ for( const m of memories ){
921
+ const age = m.tick != null ? `, ~${currentTick - m.tick} ticks ago` : ''
922
+ const line = `- ${m.content} (relevance: ${m.relevance.toFixed( 2 )}, emotional: ${m.emotionalContext}${age})`
923
+ // Always keep the first line; stop once the next would overflow the budget.
924
+ if( lines.length > 0 && used + line.length + 1 > this.RECALL_CHAR_BUDGET ) break
925
+ lines.push( line )
926
+ used += line.length + 1
927
+ }
928
+
929
+ const omitted = memories.length - lines.length
930
+ const tail = omitted > 0 ? `\n[+${omitted} omitted — over recall budget; full store intact]` : ''
931
+ return `## Relevant Memories\n${lines.join( '\n' )}${tail}`
932
+ }
933
+
934
+ private static _buildRecentOutcomesSection(
935
+ recentActions: ExecutiveContext['recentActions'],
936
+ currentTick: number,
937
+ ): string {
938
+ if( recentActions.length === 0 ) return ''
939
+
940
+ const STATUS_BADGE: Record<string, string> = {
941
+ completed: '✓',
942
+ failed: '✗',
943
+ awaiting_host: '⏳',
944
+ timed_out: '⏱ TIMED OUT',
945
+ }
946
+
947
+ const lines = recentActions.map( a => {
948
+ const badge = STATUS_BADGE[ a.status ] ?? a.status
949
+ const age = currentTick - a.tick
950
+ const planCtx = a.planId ? ` [plan: ${a.planId}]` : ''
951
+ const outcome = a.outcome ? ` — ${a.outcome}` : ''
952
+ return `- ${badge} **${a.type}** (tick ${a.tick}, ${age} ticks ago${planCtx})${outcome}`
953
+ } )
954
+
955
+ const hasTimeout = recentActions.some( a => a.status === 'timed_out' )
956
+ const timeoutNote = hasTimeout
957
+ ? '\n⚠️ **One or more actions timed out** — your body dispatched them but received no confirmation. Check if the external handler is working, or choose a different approach.'
958
+ : ''
959
+
960
+ return `## Recent Action Outcomes\n${lines.join( '\n' )}${timeoutNote}\n\n`
961
+ }
962
+
963
+ /**
964
+ * Render the active-plans section — execution awareness for multi-plan goals (P4).
965
+ * Lists each non-terminal plan with its id, goal, status, tier, and step
966
+ * progress so the executive can target a specific plan by id. Master mode only;
967
+ * rendered only when live plans exist.
968
+ */
969
+ private static _buildActivePlansSection(
970
+ plans: ExecutiveContext['plans'], entityId?: string, relevantIds?: string[],
971
+ ): string {
972
+ const TERMINAL = [ 'completed', 'failed', 'rejected' ]
973
+ let live = plans.filter( p => !TERMINAL.includes( p.status ) )
974
+ // Scope to the conversation: the union of the requester's own plans (entityId,
975
+ // a cheap proxy) and any plans surfaced by recall (relevantIds, the general
976
+ // relevance key — works even for faculties with no requester). Neither → all.
977
+ const relSet = new Set( relevantIds ?? [] )
978
+ if( entityId !== undefined || relSet.size > 0 )
979
+ live = live.filter( p => ( entityId !== undefined && p.requestingEntityId === entityId ) || relSet.has( p.id ) )
980
+ if( live.length === 0 ) return ''
981
+
982
+ const lines = live.map( p => {
983
+ const outcome = p.expectedOutcome ? ` — "${p.expectedOutcome.slice( 0, 80 )}"` : ''
984
+ return `- [${p.id}] goal ${p.goalId}: ${p.status}, ${p.completedSteps}/${p.totalSteps} steps (${p.executionTier})${outcome}`
985
+ } )
986
+
987
+ return `## Active Plans
988
+ Set "planId" in a [PLANS] op to act on one of these; omit it to draft a new plan (you can run several per goal).
989
+ ${lines.join( '\n' )}
990
+
991
+ `
992
+ }
993
+
994
+ private static _buildRecentIntrospectionSection( state: ReadonlySimulationState ): string {
995
+ let latest: { updatedAt: number; meta: Record<string, unknown> } | null = null
996
+
997
+ for( const entity of state.entities.values() ){
998
+ if( entity.type !== 'introspection' ) continue
999
+
1000
+ if( !latest || entity.updatedAt > latest.updatedAt )
1001
+ latest = { updatedAt: entity.updatedAt, meta: entity.metadata ?? {} }
1002
+ }
1003
+
1004
+ if( !latest ) return ''
1005
+
1006
+ const explanation = ( latest.meta[ 'explanation' ] as string ) ?? ''
1007
+ if( !explanation ) return ''
1008
+
1009
+ const biases = ( latest.meta[ 'identifiedBiases' ] as string[] ) ?? []
1010
+ const lessons = ( latest.meta[ 'lessonsLearned' ] as string[] ) ?? []
1011
+ const recommendations = ( latest.meta[ 'recommendations' ] as string[] ) ?? []
1012
+
1013
+ let section = `## Recent Self-Reflection\n${explanation}`
1014
+ if( biases.length > 0 ) section += `\nPatterns noticed: ${biases.join( '; ' )}`
1015
+ if( lessons.length > 0 ) section += `\nLessons learned: ${lessons.join( '; ' )}`
1016
+ if( recommendations.length > 0 ) section += `\nRecommendations: ${recommendations.join( '; ' )}`
1017
+
1018
+ return section + '\n\n'
1019
+ }
1020
+
1021
+ /**
1022
+ * B5 — Identity nudge: gently prompt the Will to reflect on its values or
1023
+ * communication style when they are absent/generic.
1024
+ * Only fires every NUDGE_INTERVAL ticks so it doesn't dominate every prompt.
1025
+ */
1026
+ private static _buildIdentityNudge(
1027
+ identity: ExecutiveContext['identity'],
1028
+ tick: number,
1029
+ ): string {
1030
+ const NUDGE_INTERVAL = 30
1031
+ if( tick % NUDGE_INTERVAL !== 0 ) return ''
1032
+
1033
+ const GENERIC_STYLES = new Set([ 'natural and authentic', 'natural', 'authentic', '' ])
1034
+ const valuesEmpty = identity.values.length === 0
1035
+ const styleGeneric = GENERIC_STYLES.has( ( identity.style ?? '' ).toLowerCase() )
1036
+
1037
+ if( !valuesEmpty && !styleGeneric ) return ''
1038
+
1039
+ const hints: string[] = []
1040
+ if( valuesEmpty ) hints.push( 'Your values list is empty — reflecting on what matters to you will help ground your decisions. Consider adding a `[IDENTITY_UPDATE]` block with `"values"` this cycle.' )
1041
+ if( styleGeneric ) hints.push( 'Your communication style is still generic — what truly characterises how you speak? A note in `[IDENTITY_UPDATE]` with `"style"` will make your voice more distinctly yours.' )
1042
+
1043
+ return `\n\n## 💡 Identity Reflection (every ${NUDGE_INTERVAL} ticks)\n${hints.join( '\n' )}`
1044
+ }
1045
+ }
1046
+
1047
+ // ── Exported convenience functions (backward compatibility) ───
1048
+
1049
+ export const buildSystemPrompt = PromptFactory.buildSystemPrompt.bind( PromptFactory )
1050
+ export const buildUserMessage = PromptFactory.buildUserMessage.bind( PromptFactory )
1051
+ export const buildOutputFormatInstruction = PromptFactory.buildOutputFormatInstruction.bind( PromptFactory )
1052
+ export const computeQualityModulation = PromptFactory.computeQualityModulation.bind( PromptFactory )
1053
+ export const computeEpistemicUncertainty = PromptFactory.computeEpistemicUncertainty.bind( PromptFactory )