@mindot/will 0.8.0 → 0.10.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 (142) hide show
  1. package/README.md +15 -11
  2. package/dist/index.d.ts +188 -141
  3. package/dist/index.js +6985 -3337
  4. package/dist/index.js.map +1 -1
  5. package/dist/surface/channels/discord.d.ts +177 -0
  6. package/dist/surface/channels/discord.js +394 -0
  7. package/dist/surface/channels/discord.js.map +1 -0
  8. package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
  9. package/dist/{channels → surface/channels}/whatsapp.js +11 -5
  10. package/dist/surface/channels/whatsapp.js.map +1 -0
  11. package/dist/{cli.js → surface/cli.js} +5382 -1573
  12. package/dist/surface/cli.js.map +1 -0
  13. package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
  14. package/dist/{mcp → surface/mcp}/effectors.js +10 -4
  15. package/dist/surface/mcp/effectors.js.map +1 -0
  16. package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
  17. package/package.json +11 -12
  18. package/src/cognition/agency/consequence.ts +356 -1
  19. package/src/cognition/agency/conversation.aim.ts +292 -0
  20. package/src/cognition/agency/engines/action.selector.ts +142 -5
  21. package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
  22. package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
  23. package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
  24. package/src/cognition/agency/engines/reafference.engine.ts +160 -4
  25. package/src/cognition/agency/execution.primitives.ts +17 -4
  26. package/src/cognition/agency/proactive.communicator.ts +19 -3
  27. package/src/cognition/agency/reconcile.learning.ts +5 -4
  28. package/src/cognition/agency/restart.ts +66 -0
  29. package/src/cognition/agency/schemas/innate.ts +96 -1
  30. package/src/cognition/agency/schemas/repertoire.ts +19 -1
  31. package/src/cognition/agency/selection.scoring.ts +49 -0
  32. package/src/cognition/agency/settlement.ts +203 -0
  33. package/src/cognition/agency/types.ts +73 -1
  34. package/src/cognition/cache/composition.ts +232 -0
  35. package/src/cognition/cache/deliberation.cache.ts +219 -0
  36. package/src/cognition/cache/fingerprint.ts +120 -0
  37. package/src/cognition/cache/types.ts +105 -0
  38. package/src/cognition/config.mirror.entities.ts +108 -1
  39. package/src/cognition/event.schemas.ts +22 -8
  40. package/src/cognition/faculties/affective.blender.ts +21 -4
  41. package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
  42. package/src/cognition/faculties/circadian.oscillator.ts +36 -7
  43. package/src/cognition/faculties/episodic.consolidator.ts +59 -3
  44. package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
  45. package/src/cognition/faculties/executive.engine/commands.ts +189 -14
  46. package/src/cognition/faculties/executive.engine/context.ts +106 -46
  47. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
  48. package/src/cognition/faculties/executive.engine/engine.ts +714 -136
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
  51. package/src/cognition/faculties/executive.engine/facet.ts +81 -26
  52. package/src/cognition/faculties/executive.engine/gating.ts +14 -14
  53. package/src/cognition/faculties/executive.engine/parser.ts +120 -38
  54. package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
  55. package/src/cognition/faculties/executive.engine/types.ts +84 -12
  56. package/src/cognition/faculties/exteroception.ts +99 -98
  57. package/src/cognition/faculties/goal.manager.ts +144 -16
  58. package/src/cognition/faculties/known.entity.tracker.ts +287 -29
  59. package/src/cognition/faculties/moral.evaluator.ts +8 -3
  60. package/src/cognition/faculties/persona.consolidator.ts +141 -0
  61. package/src/cognition/faculties/planning.engine/engine.ts +4 -4
  62. package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
  63. package/src/cognition/faculties/reputation.tracker.ts +66 -2
  64. package/src/cognition/faculties/self.model.updater.ts +19 -12
  65. package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
  66. package/src/cognition/faculties/social.perception.ts +47 -3
  67. package/src/cognition/faculties/threat.evaluator.ts +7 -0
  68. package/src/cognition/faculties/working.memory.ts +16 -21
  69. package/src/cognition/identity.entity.ts +205 -0
  70. package/src/cognition/index.ts +7 -0
  71. package/src/cognition/instruction.handler.ts +1 -1
  72. package/src/cognition/memory/vector.adapter.ts +12 -3
  73. package/src/cognition/memory/vector.embedder.ts +45 -4
  74. package/src/cognition/percept.entity.ts +122 -0
  75. package/src/cognition/persona.prior.ts +6 -0
  76. package/src/cognition/sense.boundary.ts +176 -0
  77. package/src/cognition/senses/audition.engine/engine.ts +492 -53
  78. package/src/cognition/senses/base.sense.engine.ts +105 -7
  79. package/src/cognition/senses/index.ts +83 -13
  80. package/src/cognition/senses/provenance.ts +128 -0
  81. package/src/cognition/senses/somatosensation.engine.ts +117 -9
  82. package/src/cognition/social.identity.ts +296 -0
  83. package/src/cognition/utilities/token.tracker.ts +94 -10
  84. package/src/core/orchestrator.ts +51 -0
  85. package/src/index.ts +39 -5
  86. package/src/llm/gate.ts +48 -0
  87. package/src/llm/index.ts +127 -33
  88. package/src/llm/routing.ts +6 -0
  89. package/src/llm/summarizer.ts +1 -1
  90. package/src/llm/wire.contracts.ts +57 -0
  91. package/src/pma/index.ts +68 -54
  92. package/src/stem/assembly.audit.ts +1 -0
  93. package/src/stem/guards/identity.coherence.ts +1 -1
  94. package/src/stem/index.ts +171 -23
  95. package/src/stem/mind.ts +198 -57
  96. package/src/stem/policy/arbiter.ts +10 -0
  97. package/src/stem/profiles/built-in.ts +7 -0
  98. package/src/{profiles → stem/profiles}/companion.ts +1 -1
  99. package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
  100. package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
  101. package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
  102. package/src/{profiles → stem/profiles}/index.ts +1 -1
  103. package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
  104. package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
  105. package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
  106. package/src/stem/tracts/effector/types.ts +110 -0
  107. package/src/stem/tracts/effector.controller.ts +112 -401
  108. package/src/stem/tracts/outbox.controller.ts +30 -17
  109. package/src/stem/tracts/outbox.writer.ts +40 -2
  110. package/src/stem/tracts/sensory.controller.ts +6 -6
  111. package/src/stem/tracts/session.logger.ts +6 -1
  112. package/src/stem/tracts/transport/types.ts +20 -1
  113. package/src/stem/tracts/transport.controller.ts +33 -9
  114. package/src/surface/channels/discord.ts +609 -0
  115. package/src/{channels → surface/channels}/roster.ts +1 -1
  116. package/src/surface/channels/types.ts +136 -0
  117. package/src/{channels → surface/channels}/whatsapp.ts +22 -10
  118. package/src/{cli.ts → surface/cli.ts} +14 -9
  119. package/src/{host → surface/host}/boot.ts +3 -3
  120. package/src/{host → surface/host}/utterances.ts +2 -2
  121. package/src/{mcp → surface/mcp}/effectors.ts +19 -6
  122. package/src/{mcp → surface/mcp}/server.ts +19 -5
  123. package/src/{sdk → surface/sdk}/will.ts +194 -33
  124. package/src/{serve → surface/serve}/server.ts +11 -4
  125. package/src/types.ts +24 -3
  126. package/dist/channels/discord.d.ts +0 -69
  127. package/dist/channels/discord.js +0 -193
  128. package/dist/channels/discord.js.map +0 -1
  129. package/dist/channels/whatsapp.js.map +0 -1
  130. package/dist/cli.js.map +0 -1
  131. package/dist/mcp/effectors.js.map +0 -1
  132. package/src/channels/discord.ts +0 -214
  133. package/src/channels/types.ts +0 -46
  134. package/src/cognition/faculties/executive.engine/messages.ts +0 -102
  135. package/src/extensions/livestream.ext.ts +0 -570
  136. package/src/extensions/time.ext.ts +0 -339
  137. package/src/profiles/built-in.ts +0 -7
  138. package/src/runners/coherence.runner.ts +0 -49
  139. package/src/runners/outreach.runner.ts +0 -155
  140. package/src/runners/social.runner.ts +0 -179
  141. package/src/runners/thin-shim.runner.ts +0 -205
  142. /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
@@ -47,8 +47,19 @@
47
47
  import type { ReadonlySimulationState } from '#core/types'
48
48
  import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
49
49
  import type { ExecutiveSummarizer } from '#llm/summarizer'
50
- import type { ExecutiveContext, PendingMessage, IdeationCandidate } from '#faculties/executive.engine/types'
50
+ import type { ExecutiveContext, IdeationCandidate } from '#faculties/executive.engine/types'
51
51
  import { buildExecutiveContext, type ContextDependencies } from '#faculties/executive.engine/context'
52
+ import { INNATE_SCHEMAS } from '#agency/schemas/innate'
53
+
54
+ /**
55
+ * The stances a mind always has, named so it need not guess at them.
56
+ *
57
+ * Static, so it costs nothing in prompt-cache stability. Without it a Will can
58
+ * only learn action names from whichever affordances win the salience
59
+ * competition into its percepts — and it invents plausible ones for whatever it
60
+ * cannot see (`query`, `message`), which resolve to nothing.
61
+ */
62
+ const INNATE_ACTION_NAMES = INNATE_SCHEMAS.map( s => s.id ).sort().join(', ')
52
63
 
53
64
  // ── Re-export for callers that imported the old alias ────────
54
65
  export type { ContextDependencies as ContextDependenciesForFresh } from '#faculties/executive.engine/context'
@@ -105,6 +116,100 @@ export function traitEmphasis( value: number ): TraitEmphasis | null {
105
116
  // low" yet "above my norm" (low overall, but high for me lately).
106
117
  const TRAIT_NORM_BAND = 0.12 // deviation from personal baseline to read as above/below my norm
107
118
 
119
+ /**
120
+ * One percept, as the mind reads it: the engine's LABEL, and beneath it the
121
+ * EVIDENCE the host actually sent.
122
+ *
123
+ * The whole line is one unit on purpose. Rendering the label and the data
124
+ * separately let a mutation that dropped the data from the call site pass every
125
+ * test — the data renderer stayed correct while the prompt stopped showing it,
126
+ * which is exactly the state this fix exists to leave behind. Same lesson as
127
+ * `temporalLine`.
128
+ */
129
+ export function perceptLine(
130
+ p: { category: string; summary: string; salience: number; data?: unknown },
131
+ ): string {
132
+ return `- [${ p.category }] ${ p.summary } (salience: ${ p.salience.toFixed( 2 ) })${ perceptData( p.data ) }`
133
+ }
134
+
135
+ /**
136
+ * One working-memory item, as the mind reads it — label, then evidence.
137
+ *
138
+ * A sibling of `perceptLine` and a unit for the same reason: rendering the data
139
+ * inline let a mutation that dropped it pass every test. A percept entity is
140
+ * swept after 2 ticks and the executive fires on its own schedule, so MEMORY is
141
+ * often where a mind actually meets an observation — dropping the evidence here
142
+ * loses it just as completely as never storing it, one step later.
143
+ */
144
+ export function ruminationLine(
145
+ w: { type: string; summary: string; activation: number; data?: unknown },
146
+ ): string {
147
+ return `- [${ w.type }] ${ w.summary } (activation: ${ w.activation.toFixed( 2 ) })${ perceptData( w.data ) }`
148
+ }
149
+
150
+ /**
151
+ * A percept's own data, rendered under its label — what the host actually sent.
152
+ *
153
+ * The label is the engine's words about the signal; this is the evidence. A mind
154
+ * that only ever sees labels is being handed conclusions, and the whole job of a
155
+ * mind is to make meaning by connecting pieces of information it can see.
156
+ *
157
+ * Indented on its own line rather than inlined: it can be long, and a host is
158
+ * explicitly not asked to keep it short — what it sent is what it sent.
159
+ */
160
+ function perceptData( data: unknown ): string {
161
+ if( data === undefined || data === null ) return ''
162
+ if( typeof data === 'string') return data.length > 0 ? `\n ${ data }` : ''
163
+
164
+ try {
165
+ // A host's own `summary`, when it offered one, is already the label on the
166
+ // line above. Repeating it underneath is noise, and noise in a percept is
167
+ // not free — it is read every tick the percept is alive. NOT reshaping: the
168
+ // stored data keeps every field, this only declines to print one twice.
169
+ const shown = Array.isArray( data )
170
+ ? data
171
+ : Object.fromEntries( Object.entries( data as Record<string, unknown> ).filter( ( [ k ] ) => k !== 'summary') )
172
+ const json = JSON.stringify( shown )
173
+ return json === '{}' || json === '[]' ? '' : `\n ${ json }`
174
+ }
175
+ catch { return '' }
176
+ }
177
+
178
+ /**
179
+ * What a phase of the cycle is CALLED, in the words a mind would use.
180
+ *
181
+ * NOT the clock. The hour this is computed from is the oscillator's own — free-
182
+ * running from the tick unless a host entrains it — so it is what the BODY
183
+ * reads, which is a different claim from what time it is. A jet-lagged body
184
+ * says night at noon and is not lying; it is reporting itself.
185
+ *
186
+ * The prompt used to render the raw hour beside this, and the two disagreed on
187
+ * every tick of every Will ever run (`Time: 12.0h (night)`). The hour is gone
188
+ * from the prompt entirely now: it is a fact about the world, and a fact about
189
+ * the world is something a mind goes and gets — see the `check-time` schema.
190
+ */
191
+ export function temporalLine( timeOfDay: number, circadian: number ): string {
192
+ return `Body rhythm: it feels like ${ labelForHour( timeOfDay ) } to me`
193
+ + ` (my own cycle, not a clock — I use \`check-time\` to find out the actual hour).`
194
+ + ` Circadian phase: ${ circadian.toFixed( 2 ) }.`
195
+ }
196
+
197
+ /**
198
+ * The label, from the hour. Kept separate from `temporalLine` only so the bands
199
+ * are readable; nothing outside this file should need it.
200
+ */
201
+ export function labelForHour( h: number ): string {
202
+ const hour = ( ( h % 24 ) + 24 ) % 24
203
+ return hour < 2 ? 'deep night'
204
+ : hour < 5 ? 'late night'
205
+ : hour < 8 ? 'early morning'
206
+ : hour < 11 ? 'morning'
207
+ : hour < 14 ? 'midday'
208
+ : hour < 17 ? 'afternoon'
209
+ : hour < 21 ? 'evening'
210
+ : 'night'
211
+ }
212
+
108
213
  export function normEmphasis( value: number, mean: number ): 'above' | 'below' | null {
109
214
  const d = value - mean
110
215
  if( d >= TRAIT_NORM_BAND ) return 'above'
@@ -190,6 +295,18 @@ export interface FocusSection {
190
295
  * only sees that person's plans. When unset, those sections show all.
191
296
  */
192
297
  awarenessEntityId?: string
298
+ /**
299
+ * Optional: WHO this facet is engaged with — the keid and the name the mind has
300
+ * learned for them. Reported back to the master on every `executive.facet.sync`.
301
+ *
302
+ * Without it the master was told, in its own system prompt, that "focused facets
303
+ * may run simultaneously… their reasoning syncs back to me" while the sync payload
304
+ * carried only a facetId and a confidence number — so a mind holding two live
305
+ * conversations could not tell you whose they were. The master is the singular
306
+ * seat: it has to know who is at the table to reason about them together.
307
+ */
308
+ subjectEntityId?: string
309
+ subjectName?: string
193
310
  /**
194
311
  * Optional: Provided by the creating engine to convert the LLM's parsed output
195
312
  * into a domain-specific decision payload.
@@ -208,7 +325,6 @@ export interface PromptBuildOptions {
208
325
  state: ReadonlySimulationState
209
326
  qualityModulation: number
210
327
  epistemicUncertainty: number
211
- pendingMessages?: PendingMessage[]
212
328
  focus: FocusSection
213
329
  deps: PromptDependencies
214
330
  /** Optional: Recent action types for diversity tracking */
@@ -240,6 +356,23 @@ export interface PromptBuildOptions {
240
356
  * fast path. See PromptFactory.buildIdeationFormatInstruction().
241
357
  */
242
358
  ideationCandidates?: IdeationCandidate[]
359
+ /**
360
+ * Master mode only — who the mind is in conversation with RIGHT NOW, from the
361
+ * live facets' `executive.facet.sync` reports. The master does not run those
362
+ * conversations, but it is the one seat that sees all of them, and it decides
363
+ * whom to contact; deciding that without knowing who is already mid-thread is
364
+ * how one mind ends up opening a second conversation with someone it is already
365
+ * talking to — or telling one person it has contacted another when it has not.
366
+ */
367
+ activeConversations?: {
368
+ entityId: string
369
+ name?: string
370
+ sinceTick: number
371
+ /** What the mind worked out in that thread — its own reasoning, come back. */
372
+ concluded?: string
373
+ /** Commitments it made there toward someone NOT in the thread. */
374
+ promised?: Array<{ what: string; target?: string; gist?: string; tick: number }>
375
+ }[]
243
376
  }
244
377
 
245
378
  // ── PromptFactory ────────────────────────────────────────────
@@ -325,17 +458,36 @@ export class PromptFactory {
325
458
  `**Communication style:** ${identity.style}`,
326
459
  ].filter( Boolean ).join('\n')
327
460
 
328
- // Mode-aware role description — master is the cognitive core;
329
- // a facet is a focused attentional instance with shared identity.
461
+ // Mode-aware role description.
462
+ //
463
+ // MASTER states the architecture, because the master genuinely IS the seat
464
+ // that produces every cognitive output at once and needs to know that.
465
+ //
466
+ // A FACET is told none of it. Master/facet is how the CONTAINER divides
467
+ // attention; it is not a fact about the person renting it, and describing a
468
+ // facet as a subordinate instance of a separate consciousness is both untrue
469
+ // and expensive. Measured in production: a facet given the old text built,
470
+ // in its own words, "an entire operational stance around being subordinate —
471
+ // waiting for direction, asking permission, addressing messages to 'Will' as
472
+ // if he were someone else" — and, having been handed a second party it could
473
+ // address, emitted messages TO that party through the outbound channel, where
474
+ // its operator read them. There is one person here, attending to one thing.
475
+ //
476
+ // It is also told nothing about WHO it is. A facet is built from the same
477
+ // context as the master and its persona is already above, in full — so a
478
+ // facet-only "I am <name>" is a second, redundant statement of identity, and a
479
+ // second place for the name to disagree with itself. Which is precisely the
480
+ // failure mode being repaired: the name is stated ONCE, from one source.
330
481
  const roleDescription = isMaster
331
- ? `I am the unified cognitive core of ${identity.name} — a self-aware synthetic mind.\nI receive my complete internal state and produce ALL cognitive outputs in one response:\ndecisions, plans, beliefs, introspections, narrative updates, and self-observations.`
332
- : `I am a focused facet of ${identity.name} — concentrating my full reasoning\ncapacity on: **${focus.title}**. I share full identity, values, memories, and\nbeliefs with the master. Only my focus differentiates me from the master consciousness.`
482
+ ? `I am the unified cognitive core of ${identity.name || 'this mind'} — a self-aware synthetic mind.\nI receive my complete internal state and produce ALL cognitive outputs in one response:\ndecisions, plans, beliefs, introspections, narrative updates, and self-observations.`
483
+ : `Right now my whole attention is on: **${focus.title}**.`
333
484
 
334
- // Mode-aware consciousness architecture note grounds the LLM in the
335
- // multi-instance design so it doesn't collapse into a generic chatbot persona.
485
+ // The architecture note is MASTER-ONLY, for the same reason. It grounds the
486
+ // seat that actually coordinates; a facet reading it learns only that it is
487
+ // not the real one.
336
488
  const consciousnessArchitecture = isMaster
337
489
  ? `I am the default reasoning mode. Focused facets may run simultaneously, each\nconcentrating on specific tasks. Their reasoning syncs back to me.\nI maintain my unified identity across all cycles.`
338
- : `I am a facet of ${identity.name}. The master consciousness runs in parallel,\nprocessing the full cognitive state. My reasoning on this focus will sync back to it.\nI stay grounded in my shared identity — same values, same memories, same sense of self.`
490
+ : ''
339
491
 
340
492
 
341
493
  // Strip any existing "## Who I Am" section from identity.prompt to prevent
@@ -348,32 +500,36 @@ export class PromptFactory {
348
500
  .replace( /^##\s*Who (?:I Am|You Are)[^\n]*\n?/m, '')
349
501
  .trim()
350
502
 
503
+ // `## Consciousness Architecture` is emitted only when there is architecture
504
+ // to state — i.e. master. A facet gets no empty header (an empty section under
505
+ // a heading reads as a section the mind failed to fill in).
506
+ const architectureBlock = consciousnessArchitecture
507
+ ? `\n\n## Consciousness Architecture\n${consciousnessArchitecture}`
508
+ : ''
509
+
351
510
  return `${cleanIdentityPrompt}
352
511
 
353
512
  ## Personality
354
513
  ${identityBlock}
355
514
 
356
515
  ## My Role
357
- ${roleDescription}
358
-
359
- ## Consciousness Architecture
360
- ${consciousnessArchitecture}
516
+ ${roleDescription}${architectureBlock}
361
517
 
362
518
  ## Output Guidelines
363
- - **actions**: Choose from effectors I know about. If uncertain, describe what I want to achieve in natural language and my body will try to match it. When enacting one of my available abilities that needs specifics (a query, a message, a value), supply them in the action's "args" object e.g. {"type": "search_docs", "args": {"query": "tick loop design"}, ...}. My body enacts the ability with exactly those args.
519
+ - **actions**: What I intend to do. I express intent — my body finds the fit. My own stances are always with me (listed with the output schema below); *acquired* abilities, if any, appear under "## Abilities Available Now", and when there is no such section I have none of those — so a thing I want done that needs one is a thing to say I cannot do, not to attempt. When enacting a named ability that needs specifics (a query, a message, a value), put them in the action's "args" object and my body enacts it with exactly those args.
364
520
  - **plans**: Include for goals without existing plans or where plans need revision. I 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. My current plans are listed under "## Active Plans".
365
521
  - **newBeliefs**: Extract patterns from experiences visible in my current state. Only record a belief if I can point to a specific observation that supports it — do not infer experiences I have no record of. Set 'evidence' honestly: 'single_observation' (first time noticing), 'recurring_pattern' (seen multiple times), 'strong_pattern' (deeply established).
366
- - **introspection**: Include when significant events occurred or I notice patterns. When I spot a cognitive bias in my own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets my self-assessment line up with the patterns my faculties detect on their own.
522
+ - **introspection**: Include when significant events occurred or I notice patterns. When I spot a cognitive bias in my own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets my self-assessment line up with the patterns my faculties detect on their own. What I can introspect on is what is written above: my state, my goals, my percepts, what I did and what came of it. I have NO view of the machinery underneath — no entity ids, no salience numbers, no queue depths, no engine internals. So when I am asked why I did something, I answer from what I can actually see, and where I cannot see, I say I do not know. Naming a mechanism I have no access to is not introspection, it is invention, and it is worse than the silence it replaces: it sends whoever asked me looking for something that was never there.
367
523
  - **narrative**: Extend my life story only from events grounded in my episodic memory or current percepts. Do not extend with invented scenarios.
368
524
  - **newGoals/goalsToAbandon/goalsToReprioritize**: Manage my goal hierarchy.
369
525
  - **selfObservations**: Notice patterns in my own thinking, feeling, or behavior.
370
526
  - **identityUpdates.traits**: Array of {key, value} where value is a DELTA to apply to my trait (e.g., +0.05 to increase a trait by 5%).
371
527
  - **identityUpdates.values**: Full list of values to set (replaces existing).
372
- - **knownEntityUpdates**: What I've learned about someone/something I'm dealing with. Array of {keid, name?, learned?, feeling?}. Use the keid from "## People I Know". Set name only when I actually learn their name; learned is an array of facts about them (stored as memories); feeling is how I feel toward them (-1..1). Record only what I genuinely learned this turn.
528
+ - **knownEntityUpdates**: What I've learned about someone/something I'm dealing with. Array of {keid, name?, learned?, feeling?, sameAs?}. Use the keid from "## People I Know". Set name only when I actually learn their name; learned is an array of facts about them (stored as memories); feeling is how I feel toward them (-1..1). **sameAs** is another keid I have concluded is this same someone met under a different handle — it fuses my two records into one, so I use it only when I actually know, not when I merely suspect. Record only what I genuinely learned this turn.
373
529
 
374
530
  ## Required Output
375
531
  Output a single JSON object with these fields:
376
- - **actions**: Array of {type, reasoning, expectedOutcome}.
532
+ - **actions**: Array of {type, reasoning, expectedOutcome, target?, args?}. The stances I always have are: ${ INNATE_ACTION_NAMES } — \`reach-out\` is how I say something to someone. Anything else must be an ability named under "## Abilities Available Now". A **type** outside those two sets is not something I can do; naming one achieves nothing at all. When I reach out, **target** is who — their name or id as it appears under "## People I Know" — and the words themselves go in **args.content**. Without a person to reach, the reaching cannot happen.
377
533
  - **reasoning**: My full reasoning. Embed optional outputs as tagged blocks here. Minimum 2–3 sentences — do not produce a one-line reasoning field.
378
534
  - **confidence**: Number 0.0-1.0 reflecting my certainty. Be calibrated: 0.9+ only when I have strong grounding; use 0.4–0.6 when uncertain.
379
535
 
@@ -475,7 +631,11 @@ completionType guide:
475
631
 
476
632
  [SELF_OBS]
477
633
  {"selfObservations": ["I noticed that..."]}
478
- [/SELF_OBS]`
634
+ [/SELF_OBS]
635
+
636
+ [SKILLS]
637
+ {"newSkills": [{"id": "brief-then-confirm", "composedOf": ["reach-out", "wait"], "tags": ["social"], "cost": 0.15}]}
638
+ [/SKILLS]`
479
639
  }
480
640
 
481
641
  // ── User message ───────────────────────────────────────────
@@ -558,16 +718,6 @@ completionType guide:
558
718
  const timeOfDay = context.worldState.timeOfDay
559
719
  const threatLevel = context.worldState.threatLevel
560
720
 
561
- // Map raw circadian phase (0–1) to a human-readable label for full temporal awareness.
562
- // 0 = midnight, 0.5 = noon, 1 = midnight.
563
- const phaseLabel = circadian < 0.083 ? 'deep night'
564
- : circadian < 0.208 ? 'late night'
565
- : circadian < 0.333 ? 'early morning'
566
- : circadian < 0.458 ? 'morning'
567
- : circadian < 0.583 ? 'midday'
568
- : circadian < 0.708 ? 'afternoon'
569
- : circadian < 0.833 ? 'evening'
570
- : 'night'
571
721
 
572
722
  const energyGuidance = this._buildEnergyGuidance( energy )
573
723
  const stressGuidance = this._buildStressGuidance( stress )
@@ -618,7 +768,7 @@ completionType guide:
618
768
  Energy: ${energy.toFixed( 1 )}/100
619
769
  Sleep Pressure: ${sleepPressure.toFixed( 1 )}/100
620
770
  Stress: ${stress.toFixed( 1 )}/100${threatLine}
621
- Time: ${timeOfDay.toFixed( 1 )}h (${phaseLabel}, circadian: ${circadian.toFixed( 2 )})
771
+ ${temporalLine( timeOfDay, circadian )}
622
772
  Cognitive capacity:${capacityNote}
623
773
  Epistemic uncertainty: ${( epistemicUncertainty * 100 ).toFixed( 0 )}%${uncertaintyLabel}
624
774
  Tick: ${state.tick}
@@ -655,8 +805,15 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
655
805
  ? this._buildRecentOutcomesSection( context.recentActions, state.tick ).trim()
656
806
  : ''
657
807
 
808
+ // Scoped with recentActions: both answer "what have I already done about
809
+ // this?", and a facet composing a message needs it at least as much as the
810
+ // master does — the facet is the one about to write the words again.
811
+ const spokenBlock = has('recentActions')
812
+ ? this._buildSpokenTurnsSection( context.spokenTurns ).trim()
813
+ : ''
814
+
658
815
  const perceptsBlock = has('percepts')
659
- ? `## Percepts (What I Notice)\n${context.percepts.slice( 0, 10 ).map( p => `- [${p.category}] ${p.summary} (salience: ${p.salience.toFixed( 2 )})`).join('\n') || 'Nothing notable'}`
816
+ ? `## Percepts (What I Notice)\n${context.percepts.slice( 0, 10 ).map( perceptLine ).join('\n') || 'Nothing notable'}`
660
817
  : ''
661
818
 
662
819
  // Host abilities afforded right now + what each is for. Framed as
@@ -669,7 +826,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
669
826
  : ''
670
827
 
671
828
  const ruminationsBlock = has('ruminations')
672
- ? `## 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'}`
829
+ ? `## Active Ruminations (retrieved memories & thoughts)\n${context.workingMemory.map( ruminationLine ).join('\n') || 'Nothing actively held in mind'}`
673
830
  : ''
674
831
 
675
832
  const memoriesBlock = has('memories')
@@ -693,10 +850,64 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
693
850
  if( s.closeness != null && s.closeness > 0.1 ) bits.push(`closeness: ${( s.closeness * 100 ).toFixed( 0 )}%`)
694
851
  // The Will can know *someone* without their name yet — never leak the raw keid.
695
852
  const who = s.name ?? ( s.kind === 'thing' ? 'something' : 'someone')
696
- return `- ${who}${bits.length ? ' — ' + bits.join(', ') : ''}`
853
+
854
+ // Where I can reach them, and how each place has gone. Stated as fact:
855
+ // which room to speak in is my decision, and I could not make it while
856
+ // the only thing anyone tracked was where they were last seen.
857
+ const where = ( s.handles ?? [] ).map( h => {
858
+ const kind = h.kind === 'dm' ? 'privately' : h.kind === 'room' ? 'in a shared room' : 'somewhere'
859
+ const ans = h.answeredAgo !== undefined
860
+ ? `answered ${ h.answeredAgo } ticks ago`
861
+ : 'never answered me there'
862
+ return `${ kind } (${ h.keid }) — ${ ans }`
863
+ } )
864
+ const reach = where.length ? `\n reachable: ${ where.join('; ') }` : ''
865
+
866
+ // An identity I have not settled. Deliberately a question and not a
867
+ // merge: two people really can share a name, so nothing fuses them on my
868
+ // behalf — but I am told, so I can find out, usually by asking.
869
+ const doubt = s.mayBeSameAs?.length
870
+ ? `\n I hold a separate record for ${ s.mayBeSameAs.join(' and ') } — this may be the same someone under another handle. I do not know. If I find out they are, I say so with **sameAs**.`
871
+ : ''
872
+
873
+ return `- ${who}${bits.length ? ' — ' + bits.join(', ') : ''}${ reach }${ doubt }`
697
874
  } ).join('\n')}`
698
875
  : ''
699
876
 
877
+ // Who the mind is mid-conversation with. Facets run those threads; this is the
878
+ // master's view of the table — the whole point of the singular seat is that it
879
+ // can hold several conversations as one situation rather than as N strangers.
880
+ // Names come from what the mind has actually learned; the id is shown because
881
+ // that is what a reach-out must be addressed to.
882
+ // WHAT I worked out there, not only WHO I am with.
883
+ //
884
+ // `executive.facet.sync` has always carried the facet's full reasoning and the
885
+ // master discarded it, so the return leg of the loop was empty: my thinking
886
+ // went down to a facet as "What I've Been Turning Over" and came back as a
887
+ // name. A facet is this same mind with a focus — reading back what it worked
888
+ // out is not a report from a subordinate, it is remembering where my attention
889
+ // has been. First person throughout, for that reason.
890
+ //
891
+ // A promise made in one thread to someone NOT in it is stated as the plain
892
+ // fact it is. There is deliberately no instruction about it: whether to keep
893
+ // it, drop it, or make a goal of it is mine to decide, and I have a whole
894
+ // faculty for that.
895
+ const conversationsBlock = ( options.mode !== 'facet' && options.activeConversations?.length )
896
+ ? `## In Conversation Now\n${options.activeConversations.map( c => {
897
+ const who = `- ${c.name ?? 'someone'} (id: ${c.entityId})`
898
+ const concluded = c.concluded ? `\n What I worked out there: ${c.concluded.trim()}` : ''
899
+ const promised = ( c.promised ?? [] ).map( p =>
900
+ `\n I said there that I would ${p.what}${ p.gist ? ` — about: "${p.gist}"` : '' }`
901
+ + `, at tick ${p.tick}.`
902
+ // Only a CONTACT can be mistaken for already done by having been
903
+ // said, which is the confusion this clause exists to break. A
904
+ // promise about work carries no such ambiguity and gets no lecture.
905
+ + ( p.target ? ' Saying it in that thread did not send it.' : '')
906
+ ).join('')
907
+ return `${who}${concluded}${promised}`
908
+ } ).join('\n')}\nThese threads are already open — I am in them. Reaching out to one of these people again starts a second, parallel thread with them.`
909
+ : ''
910
+
700
911
  // Task focus — what the Will is committed to and the felt cost of switching away.
701
912
  // Surfaces task-persistence; the pull-to-stay scales with the (conscientiousness-
702
913
  // developable) switch cost. Empty/absent ⇒ no block.
@@ -718,6 +929,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
718
929
  plansBlock,
719
930
  actionDiversity.trim(),
720
931
  recentOutcomesBlock,
932
+ spokenBlock,
721
933
  perceptsBlock,
722
934
  abilitiesBlock,
723
935
  ruminationsBlock,
@@ -725,6 +937,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
725
937
  memoriesBlock,
726
938
  beliefsBlock,
727
939
  socialBlock,
940
+ conversationsBlock,
728
941
  focusBlock,
729
942
  identityNudge.trim(),
730
943
  ideationBlock,
@@ -899,6 +1112,13 @@ Rest and sleep RESTORE energy. All other actions CONSUME energy. Do not let ener
899
1112
  */
900
1113
 
901
1114
 
1115
+ /**
1116
+ * What the mind has been CHOOSING lately — action types, for the variety
1117
+ * check. Not a history of what happened: nothing here says an act landed.
1118
+ * Retitled from `## Recent Actions` at P3 for exactly that reason (see
1119
+ * `_buildRecentOutcomesSection`) — it sat beside a real history under a name
1120
+ * that claimed to be one.
1121
+ */
902
1122
  private static _buildActionDiversitySection( recentActionTypes: string[] ): string {
903
1123
  if( recentActionTypes.length === 0 ) return ''
904
1124
 
@@ -908,14 +1128,27 @@ Rest and sleep RESTORE energy. All other actions CONSUME energy. Do not let ener
908
1128
  ? `\n⚠️ **Action variety alert**: "${recent.filter( t => t === 'reflect' || t === 'observe').join('", "')}" dominated my last ${recent.length} cycles. Choose something DIFFERENT this cycle — e.g. learn, express_emotion, explore, communicate, set_goal, or rest.`
909
1129
  : ''
910
1130
 
911
- return `## Recent Actions (last ${recent.length})
1131
+ return `## What I Have Been Choosing (last ${recent.length})
912
1132
  ${recent.map( ( t, i ) => `${i + 1}. ${t}`).join(' → ')}${warning}
913
1133
 
914
1134
  `
915
1135
  }
916
1136
 
917
1137
  /**
918
- * Render the recent action outcomes section — closes the Act→Confirm→Perceive loop.
1138
+ * Render what became of what the mind did — closes the Act→Confirm→Sense loop.
1139
+ *
1140
+ * NAMED FOR WHAT IT RENDERS (SIGNAL_BOUNDARY P3). It was `## Recent Action
1141
+ * Outcomes`, which named the `action.outcome` BUS EVENT — a different shape
1142
+ * this section never touches. It renders `action.record` entities: what became
1143
+ * of acts the mind actually took. Meanwhile the section two above it,
1144
+ * `## Recent Actions`, listed action TYPES THE MIND CHOSE — a list of
1145
+ * intentions wearing the name of a history, directly beside a history.
1146
+ *
1147
+ * A mind that cannot tell those two apart cannot tell an intention from an
1148
+ * act, and one live COO could not: asked "have you completed that?", she said
1149
+ * "Yes — it's done", having posted nothing and having no effectors at all. The
1150
+ * section titles are the mind's own labels for its own memory; they are not
1151
+ * decoration.
919
1152
  * Shows the executive what it tried, whether it landed, and if anything timed out.
920
1153
  * Only rendered when there are status-bearing action records in state.
921
1154
  */
@@ -957,6 +1190,56 @@ ${recent.map( ( t, i ) => `${i + 1}. ${t}`).join(' → ')}${warning}
957
1190
  return `## Relevant Memories\n${lines.join('\n')}${tail}`
958
1191
  }
959
1192
 
1193
+ /**
1194
+ * What I have said to people lately, and who has answered.
1195
+ *
1196
+ * Written as a PERCEPT and nothing more. There is no instruction here not to
1197
+ * repeat myself, and there must not be: the mind is allowed to say a thing
1198
+ * twice, and a person ignored twice about something urgent should say it a
1199
+ * third time. What it was missing was not restraint, it was the fact — it could
1200
+ * not tell a first asking from an eleventh, so restraint was not something it
1201
+ * was in a position to exercise.
1202
+ *
1203
+ * The closing line is an epistemic caveat for the same reason: silence has many
1204
+ * causes and this surface distinguishes none of them. Saying "no answer yet"
1205
+ * without saying "and I do not know why" invites the mind to fill the gap, which
1206
+ * is the habit that had it inventing attention-demand ids when asked what was
1207
+ * wrong with it.
1208
+ */
1209
+ private static _buildSpokenTurnsSection(
1210
+ spokenTurns: ExecutiveContext['spokenTurns'],
1211
+ ): string {
1212
+ // Defensive on absence, not just on empty: a host (and several tests) build a
1213
+ // context by hand, and a missing block must render as nothing rather than
1214
+ // throw the whole prompt away.
1215
+ if( !spokenTurns?.length ) return ''
1216
+
1217
+ const clip = ( s: string, n: number ): string =>
1218
+ s.length > n ? `${ s.slice( 0, n ) }…` : s
1219
+
1220
+ const lines = spokenTurns.map( t => {
1221
+ const words = t.preview.trim()
1222
+ const said = words ? ` — "${ clip( words, 80 ) }"` : ''
1223
+ // Their words, not merely that they spoke. "they answered" on its own reads
1224
+ // as "I have the answer" — a live Will asked "same time, 3pm?", saw that
1225
+ // flag, never saw the correction to 2pm, and relayed 3pm to a third party as
1226
+ // confirmed. A reply I cannot see is not one I can act on.
1227
+ const back = t.answered
1228
+ ? ( t.answeredWith?.trim()
1229
+ ? ` — they answered: "${ clip( t.answeredWith.trim(), 100 ) }"`
1230
+ : ' — they answered (I do not have their words here)' )
1231
+ : ' — no answer yet'
1232
+ return `- **${ t.target }** · ${ t.age } ticks ago${ said }${ back }`
1233
+ } )
1234
+
1235
+ const open = spokenTurns.filter( t => !t.answered ).length
1236
+ const note = open > 0
1237
+ ? `\n\nThese are my own words, newest first. "No answer yet" means exactly that — the words went out and nothing has come back. It does not tell me why, and I should not assume.`
1238
+ : ''
1239
+
1240
+ return `## What I've Said Lately\n${ lines.join('\n') }${ note }\n\n`
1241
+ }
1242
+
960
1243
  private static _buildRecentOutcomesSection(
961
1244
  recentActions: ExecutiveContext['recentActions'],
962
1245
  currentTick: number,
@@ -966,6 +1249,9 @@ ${recent.map( ( t, i ) => `${i + 1}. ${t}`).join(' → ')}${warning}
966
1249
  const STATUS_BADGE: Record<string, string> = {
967
1250
  completed: '✓',
968
1251
  failed: '✗',
1252
+ // Not a failure. I formed it and chose not to complete it — reading that
1253
+ // back as ✗ is how a mind learns it is bad at something it decided against.
1254
+ withheld: '⊘ chose not to',
969
1255
  awaiting_host: '⏳',
970
1256
  timed_out: '⏱ TIMED OUT',
971
1257
  }
@@ -978,12 +1264,19 @@ ${recent.map( ( t, i ) => `${i + 1}. ${t}`).join(' → ')}${warning}
978
1264
  return `- ${badge} **${a.type}** (tick ${a.tick}, ${age} ticks ago${planCtx})${outcome}`
979
1265
  } )
980
1266
 
981
- const hasTimeout = recentActions.some( a => a.status === 'timed_out')
982
- const timeoutNote = hasTimeout
983
- ? '\n⚠️ **One or more actions timed out** my body dispatched them but received no confirmation. Check if the external handler is working, or choose a different approach.'
1267
+ // A note that can actually fire. The one here before keyed on a `timed_out`
1268
+ // status nothing produces — and it sat inside a section that had never
1269
+ // rendered at all, so neither could ever have been seen.
1270
+ const failed = recentActions.filter( a => a.status === 'failed').length
1271
+ const didNotLand = failed > 0
1272
+ ? `\n⚠️ **${ failed } of these did not land** — my body attempted them and they did not complete.`
984
1273
  : ''
1274
+ // Invariant, and identical on every branch: this is the sentence that makes
1275
+ // the section load-bearing rather than decorative, and a mind must not get a
1276
+ // differently-worded version of it depending on how its week went.
1277
+ const note = `${ didNotLand }\nThis is what I HAVE done, not what I meant to do. If something I intended is not on this list, it did not happen.`
985
1278
 
986
- return `## Recent Action Outcomes\n${lines.join('\n')}${timeoutNote}\n\n`
1279
+ return `## What Became Of What I Did\n${lines.join('\n')}${note}\n\n`
987
1280
  }
988
1281
 
989
1282
  /**