@mindot/will 0.9.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 (110) hide show
  1. package/README.md +15 -11
  2. package/dist/index.d.ts +188 -141
  3. package/dist/index.js +16660 -15206
  4. package/dist/index.js.map +1 -1
  5. package/dist/{channels → surface/channels}/discord.d.ts +50 -3
  6. package/dist/{channels → surface/channels}/discord.js +102 -7
  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 +7 -4
  10. package/dist/surface/channels/whatsapp.js.map +1 -0
  11. package/dist/{cli.js → surface/cli.js} +4406 -2904
  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-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
  17. package/package.json +11 -12
  18. package/src/cognition/agency/consequence.ts +237 -3
  19. package/src/cognition/agency/conversation.aim.ts +32 -0
  20. package/src/cognition/agency/engines/action.selector.ts +59 -3
  21. package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
  22. package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
  23. package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
  24. package/src/cognition/agency/engines/reafference.engine.ts +43 -4
  25. package/src/cognition/agency/execution.primitives.ts +17 -4
  26. package/src/cognition/agency/reconcile.learning.ts +5 -4
  27. package/src/cognition/agency/schemas/innate.ts +96 -1
  28. package/src/cognition/agency/schemas/repertoire.ts +19 -1
  29. package/src/cognition/agency/selection.scoring.ts +16 -0
  30. package/src/cognition/agency/settlement.ts +203 -0
  31. package/src/cognition/agency/types.ts +38 -1
  32. package/src/cognition/config.mirror.entities.ts +0 -1
  33. package/src/cognition/event.schemas.ts +0 -8
  34. package/src/cognition/faculties/affective.blender.ts +21 -4
  35. package/src/cognition/faculties/circadian.oscillator.ts +36 -7
  36. package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
  37. package/src/cognition/faculties/executive.engine/context.ts +41 -35
  38. package/src/cognition/faculties/executive.engine/engine.ts +270 -113
  39. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
  40. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
  41. package/src/cognition/faculties/executive.engine/parser.ts +100 -38
  42. package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
  43. package/src/cognition/faculties/executive.engine/types.ts +18 -15
  44. package/src/cognition/faculties/exteroception.ts +99 -98
  45. package/src/cognition/faculties/goal.manager.ts +50 -2
  46. package/src/cognition/faculties/known.entity.tracker.ts +22 -3
  47. package/src/cognition/faculties/planning.engine/engine.ts +4 -4
  48. package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
  49. package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
  50. package/src/cognition/faculties/working.memory.ts +6 -1
  51. package/src/cognition/instruction.handler.ts +1 -1
  52. package/src/cognition/memory/vector.embedder.ts +1 -3
  53. package/src/cognition/percept.entity.ts +122 -0
  54. package/src/cognition/sense.boundary.ts +176 -0
  55. package/src/cognition/senses/audition.engine/engine.ts +97 -16
  56. package/src/cognition/senses/base.sense.engine.ts +104 -6
  57. package/src/cognition/senses/index.ts +71 -13
  58. package/src/cognition/senses/provenance.ts +128 -0
  59. package/src/cognition/senses/somatosensation.engine.ts +117 -9
  60. package/src/cognition/social.identity.ts +23 -0
  61. package/src/cognition/utilities/token.tracker.ts +36 -5
  62. package/src/core/orchestrator.ts +13 -0
  63. package/src/index.ts +39 -5
  64. package/src/llm/gate.ts +48 -0
  65. package/src/llm/index.ts +102 -25
  66. package/src/llm/wire.contracts.ts +38 -0
  67. package/src/pma/index.ts +1 -1
  68. package/src/stem/index.ts +92 -21
  69. package/src/stem/mind.ts +26 -2
  70. package/src/stem/policy/arbiter.ts +10 -0
  71. package/src/stem/profiles/built-in.ts +7 -0
  72. package/src/{profiles → stem/profiles}/companion.ts +1 -1
  73. package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
  74. package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
  75. package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
  76. package/src/{profiles → stem/profiles}/index.ts +1 -1
  77. package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
  78. package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
  79. package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
  80. package/src/stem/tracts/effector/types.ts +110 -0
  81. package/src/stem/tracts/effector.controller.ts +112 -401
  82. package/src/stem/tracts/outbox.controller.ts +30 -17
  83. package/src/stem/tracts/sensory.controller.ts +6 -6
  84. package/src/stem/tracts/session.logger.ts +6 -1
  85. package/src/stem/tracts/transport/types.ts +20 -1
  86. package/src/stem/tracts/transport.controller.ts +33 -9
  87. package/src/{channels → surface/channels}/discord.ts +224 -7
  88. package/src/{channels → surface/channels}/roster.ts +1 -1
  89. package/src/{channels → surface/channels}/types.ts +1 -1
  90. package/src/{channels → surface/channels}/whatsapp.ts +9 -6
  91. package/src/{cli.ts → surface/cli.ts} +6 -6
  92. package/src/{host → surface/host}/boot.ts +3 -3
  93. package/src/{host → surface/host}/utterances.ts +2 -2
  94. package/src/{mcp → surface/mcp}/effectors.ts +19 -6
  95. package/src/{mcp → surface/mcp}/server.ts +19 -5
  96. package/src/{sdk → surface/sdk}/will.ts +156 -28
  97. package/src/{serve → surface/serve}/server.ts +11 -4
  98. package/src/types.ts +24 -3
  99. package/dist/channels/discord.js.map +0 -1
  100. package/dist/channels/whatsapp.js.map +0 -1
  101. package/dist/cli.js.map +0 -1
  102. package/dist/mcp/effectors.js.map +0 -1
  103. package/src/extensions/livestream.ext.ts +0 -570
  104. package/src/extensions/time.ext.ts +0 -339
  105. package/src/profiles/built-in.ts +0 -7
  106. package/src/runners/coherence.runner.ts +0 -49
  107. package/src/runners/outreach.runner.ts +0 -155
  108. package/src/runners/social.runner.ts +0 -179
  109. package/src/runners/thin-shim.runner.ts +0 -205
  110. /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
@@ -47,6 +47,21 @@ const ACP_CONFIDENCE = 0.4
47
47
  * Numeric encoding for dominant emotion metric.
48
48
  * Consumers decode using DOMINANT_EMOTION_LABELS[code].
49
49
  */
50
+ /**
51
+ * The entity carrying the non-numeric side of affect — what the mind is feeling,
52
+ * by name.
53
+ *
54
+ * Exported as a CONSTANT because the id is a contract between two files and it
55
+ * silently broke: `context.ts` read `state.entities.get('affective-state')`, an
56
+ * id nothing has ever written, so `dominantEmotion` fell back to 'neutral' in
57
+ * every prompt ever rendered. Measured live: a COO sitting at frustration 1.000
58
+ * and boredom 1.000 was told "Dominant emotion: neutral".
59
+ *
60
+ * Two string literals in two files cannot disagree if there is only one.
61
+ */
62
+ export const AFFECT_STATE_ID = 'affect-blends'
63
+ export const AFFECT_STATE_TYPE = 'affect.blends'
64
+
50
65
  const DOMINANT_EMOTION_CODES: Record<string, number> = {
51
66
  'neutral': 0,
52
67
  'fear': 1,
@@ -381,11 +396,13 @@ export class AffectiveBlender implements SimulationEngine, CognitiveEngine {
381
396
  const dominantCode = DOMINANT_EMOTION_CODES[ dominant ?? 'neutral' ] ?? 0
382
397
  commands.metrics!.push([ 'affect.dominant_emotion', dominantCode ])
383
398
 
384
- // Persist active blends as a state entity so the executive bridge can read them
399
+ // The mind's own read on what it is feeling, as an entity the executive can
400
+ // render. `dominantEmotion` is written as the LABEL, not the code: the code
401
+ // goes to metrics for telemetry, and a prompt cannot say "I feel 20".
385
402
  commands.set!.push({
386
- id: 'affect-blends',
387
- type: 'affect.blends',
388
- metadata: { blends },
403
+ id: AFFECT_STATE_ID,
404
+ type: AFFECT_STATE_TYPE,
405
+ metadata: { dominantEmotion: dominant ?? 'neutral', blends },
389
406
  })
390
407
 
391
408
  // 8. Track emotion history for blending detection
@@ -41,7 +41,22 @@ export interface CircadianConfig {
41
41
  /** Whether external light signals entrain the oscillator */
42
42
  entrainable?: boolean
43
43
  /** Current simulated time of day in hours (0-24). If not provided, derived from tick. */
44
- timeOfDayHours?: number
44
+ /**
45
+ * The hour of day, 0–24. A NUMBER freezes it; a FUNCTION is read every tick,
46
+ * which is what a host with a real clock should pass.
47
+ *
48
+ * WHY THIS IS THE HOST'S TO SUPPLY. Left unset, the oscillator derives the
49
+ * hour from the tick count (1 tick = 1 second), which makes it *hours since
50
+ * tick zero, mod 24* — and after a snapshot restore, tick zero is an
51
+ * arbitrary point in a previous life. A mind reasoning about whether anyone
52
+ * is awake right now was reasoning from that.
53
+ *
54
+ * A circadian oscillator is a BODY RHYTHM, not a clock. It models what a body
55
+ * does over a day; it cannot know what day it is or where on Earth it runs.
56
+ * What time it is, is a fact about the world — afference — and arrives the
57
+ * way `environment.light_level` already does: from whoever can see it.
58
+ */
59
+ timeOfDayHours?: number | ( () => number )
45
60
  bus?: CognitiveBus
46
61
  }
47
62
 
@@ -51,7 +66,7 @@ export class CircadianOscillator implements SimulationEngine, CognitiveEngine {
51
66
  private _periodHours: number
52
67
  private _phaseOffsetHours: number
53
68
  private _entrainable: boolean
54
- private _timeOfDayHours?: number
69
+ private _timeOfDayHours?: number | ( () => number )
55
70
 
56
71
  private _bus: CognitiveBus | null = null
57
72
 
@@ -100,8 +115,11 @@ export class CircadianOscillator implements SimulationEngine, CognitiveEngine {
100
115
  events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
101
116
  commands: StateCommands = { metrics: [] }
102
117
 
103
- // Determine current time of day
104
- let timeOfDay = this._timeOfDayHours
118
+ // Determine current time of day. A function is re-read every tick — a host
119
+ // clock moves; a config value does not.
120
+ let timeOfDay = typeof this._timeOfDayHours === 'function'
121
+ ? normalizeHour( this._timeOfDayHours() )
122
+ : this._timeOfDayHours
105
123
 
106
124
  if( timeOfDay === undefined ){
107
125
  // Derive from tick, assuming 1 tick = 1 second
@@ -278,17 +296,28 @@ export class CircadianOscillator implements SimulationEngine, CognitiveEngine {
278
296
  * Set the current time of day explicitly (for scenarios with controlled time).
279
297
  */
280
298
  setTimeOfDay( hours: number ): void {
281
- this._timeOfDayHours = ( hours % 24 + 24 ) % 24
299
+ this._timeOfDayHours = normalizeHour( hours )
300
+ }
301
+
302
+ /** Read a live clock every tick instead of holding a fixed hour. */
303
+ setClock( clock: ( () => number ) | null ): void {
304
+ this._timeOfDayHours = clock ?? undefined
282
305
  }
283
306
 
284
307
  /**
285
308
  * Get the current circadian phase.
286
309
  */
287
310
  getPhase(): CircadianPhase {
288
- const h = this._timeOfDayHours ?? 12
311
+ const h = typeof this._timeOfDayHours === 'function'
312
+ ? normalizeHour( this._timeOfDayHours() )
313
+ : this._timeOfDayHours ?? 12
289
314
  return h >= 6 && h < 12 ? 'morning'
290
315
  : h >= 12 && h < 17 ? 'afternoon'
291
316
  : h >= 17 && h < 21 ? 'evening'
292
317
  : 'night'
293
318
  }
294
- }
319
+ }
320
+ /** 0–24, wrapping — a host clock that hands over 25 or −1 must not poison the rhythm. */
321
+ function normalizeHour( h: number ): number {
322
+ return Number.isFinite( h ) ? ( ( h % 24 ) + 24 ) % 24 : 12
323
+ }
@@ -0,0 +1,129 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/action.record.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // What became of what I did.
6
+ //
7
+ // The executive's prompt has a `## What Became Of What I Did` section (called
8
+ // `## Recent Action Outcomes` until P3 renamed it after what it renders), a builder
9
+ // for it, and a type for its data. `context.ts` fills it by scanning
10
+ // `decision.record` entities that carry an `actionStatus`. Nothing in the engine
11
+ // has ever written `actionStatus` — it is read in exactly one place and set in
12
+ // none — so the section has never had a single row. Measured across every
13
+ // prompt a live COO received over two runs: that section appears
14
+ // 0 times, while `## What I've Said Lately` appears in all of them.
15
+ //
16
+ // So the mind could see what it had SAID and never what it had DONE. Its only
17
+ // other record of its own doing is `## What I Have Been Choosing` (`## Recent
18
+ // Actions` at the time), which is fed from
19
+ // `output.actions` — the executive's DECISIONS — and is therefore a list of
20
+ // intentions wearing the name of a history.
21
+ //
22
+ // A mind whose record of acting contains only intentions cannot tell an
23
+ // intention from an act. Live, asked directly "have you completed that?", a COO
24
+ // answered "Yes — it's done. I drafted the full v0.1 spec: user stories, command
25
+ // surface, integration points, success metrics. Posted it to FKEM." She had
26
+ // posted nothing; she had no effectors at all and could not have drafted
27
+ // anything anywhere. Her deliberation history said "I produce the scoping doc
28
+ // now" across twenty cycles, and nothing in her state said whether she had.
29
+ //
30
+ // This is the same distinction `pending` draws for utterances — attempting to
31
+ // speak is not speaking (#124) — carried to acts in general. It is written from
32
+ // the `action.outcome` the executive ALREADY receives (it subscribes to `*`) and
33
+ // from an explicit withheld signal, and it is an ordinary entity, so it
34
+ // snapshots and replays with the rest of the mind.
35
+ // ─────────────────────────────────────────────────────────────
36
+
37
+ import type { EntityInput, Tick } from '#core/types'
38
+
39
+ export const ACTION_RECORD_TYPE = 'action.record'
40
+
41
+ /**
42
+ * How many outcomes the mind carries.
43
+ *
44
+ * Matches the cap `context.ts` already applies when rendering, so the state
45
+ * holds what the prompt shows and no more — an unbounded history of everything
46
+ * ever done is a memory system, not a working record, and this is the latter.
47
+ */
48
+ export const ACTION_RECORD_KEEP = 6
49
+
50
+ /**
51
+ * `withheld` is a first-class outcome, not a flavour of failure.
52
+ *
53
+ * The mind considered an act, formed it, and chose not to complete it. Folding
54
+ * that into `failed` is the mistake #123 was written to correct — it taught a
55
+ * COO it was bad at speaking from the times it decided not to speak.
56
+ */
57
+ export type ActionStatus = 'completed' | 'failed' | 'withheld'
58
+
59
+ export interface ActionRecord {
60
+ type: string
61
+ status: ActionStatus
62
+ tick: Tick
63
+ targetEntityId?: string
64
+ /** The mind's own account of what happened, in its words where it has any. */
65
+ outcome: string
66
+ planId?: string
67
+ }
68
+
69
+ /** Stable id — one record per (tick, act), so two acts on a tick both survive. */
70
+ export function actionRecordId( tick: Tick, type: string, targetEntityId?: string ): string {
71
+ return `action-record-${ tick }-${ type }${ targetEntityId ? `-${ targetEntityId }` : '' }`
72
+ }
73
+
74
+ export function actionRecordEntity( r: ActionRecord ): EntityInput {
75
+ return {
76
+ id: actionRecordId( r.tick, r.type, r.targetEntityId ),
77
+ type: ACTION_RECORD_TYPE,
78
+ metadata: { ...r },
79
+ }
80
+ }
81
+
82
+ /** Read a record back off entity metadata — every field, including the ones it is easy to forget. */
83
+ export function readActionRecord(
84
+ m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined,
85
+ ): ActionRecord | null {
86
+ const meta = ( m instanceof Map ? Object.fromEntries( m ) : m ?? {} ) as Record<string, unknown>
87
+ const type = typeof meta['type'] === 'string' ? meta['type'] as string : undefined
88
+ const status = meta['status']
89
+ if( !type || ( status !== 'completed' && status !== 'failed' && status !== 'withheld') ) return null
90
+
91
+ return {
92
+ type, status,
93
+ tick: typeof meta['tick'] === 'number' ? meta['tick'] as number : 0,
94
+ targetEntityId: typeof meta['targetEntityId'] === 'string' ? meta['targetEntityId'] as string : undefined,
95
+ outcome: typeof meta['outcome'] === 'string' ? meta['outcome'] as string : '',
96
+ planId: typeof meta['planId'] === 'string' ? meta['planId'] as string : undefined,
97
+ }
98
+ }
99
+
100
+ /** The mind's recent acts, newest first. */
101
+ export function recentActionRecords(
102
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
103
+ keep: number = ACTION_RECORD_KEEP,
104
+ ): ActionRecord[] {
105
+ const out: ActionRecord[] = []
106
+ for( const [ , e ] of entities ){
107
+ if( e.type !== ACTION_RECORD_TYPE ) continue
108
+ const r = readActionRecord( e.metadata )
109
+ if( r ) out.push( r )
110
+ }
111
+ // Newest first, ties broken by act name so a run and its replay agree.
112
+ out.sort( ( a, b ) => b.tick - a.tick || ( a.type < b.type ? -1 : a.type > b.type ? 1 : 0 ) )
113
+ return out.slice( 0, keep )
114
+ }
115
+
116
+ /** Records that have fallen off the end of the window — swept by the writer. */
117
+ export function staleActionRecordIds(
118
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
119
+ keep: number = ACTION_RECORD_KEEP,
120
+ ): string[] {
121
+ const all: Array<{ id: string; r: ActionRecord }> = []
122
+ for( const [ id, e ] of entities ){
123
+ if( e.type !== ACTION_RECORD_TYPE ) continue
124
+ const r = readActionRecord( e.metadata )
125
+ if( r ) all.push({ id, r })
126
+ }
127
+ all.sort( ( a, b ) => b.r.tick - a.r.tick || ( a.r.type < b.r.type ? -1 : a.r.type > b.r.type ? 1 : 0 ) )
128
+ return all.slice( keep ).map( x => x.id )
129
+ }
@@ -2,6 +2,8 @@
2
2
  // src/cognition/faculties/executive.engine/context.ts
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
+ import { recentActionRecords } from '#faculties/executive.engine/action.record'
6
+ import { AFFECT_STATE_ID } from '#faculties/affective.blender'
5
7
  import type { ReadonlySimulationState } from '#core/types'
6
8
  import type { GoalManager } from '#faculties/goal.manager'
7
9
  import type { WorkingMemory } from '#faculties/working.memory'
@@ -53,7 +55,7 @@ export async function buildExecutiveContext(
53
55
 
54
56
  // Working memory — getItems() returns WMItem[] sorted by activation descending.
55
57
  // Filter types that are already rendered elsewhere in the prompt:
56
- // conversation.exchange → shown in Recent Action Outcomes
58
+ // conversation.exchange → shown in What Became Of What I Did
57
59
  // goal → shown in Active Goals
58
60
  const WM_FILTER_TYPES = new Set([ 'conversation.exchange', 'goal' ])
59
61
  const wmItems = ( deps.workingMemory?.getItems() ?? [] )
@@ -61,7 +63,12 @@ export async function buildExecutiveContext(
61
63
  const workingMemory = wmItems.map( item => ({
62
64
  type: item.type,
63
65
  summary: extractSummary( item.content ),
64
- activation: item.activation
66
+ activation: item.activation,
67
+ // Storing the evidence in memory and then rendering only the label would
68
+ // lose it just as completely, one step later. A percept entity is swept
69
+ // after 2 ticks and the executive fires on its own schedule, so memory is
70
+ // often where the mind meets an observation at all.
71
+ ...( itemData( item.content ) !== undefined ? { data: itemData( item.content ) } : {} ),
65
72
  }))
66
73
 
67
74
  // Episodic memory — semantic query for relevant memories based on current goals
@@ -256,33 +263,15 @@ export async function buildExecutiveContext(
256
263
  impulsivity: execParams.impulsivity ?? 0.3,
257
264
  } : undefined
258
265
 
259
- // Recent action outcomesscan decision.record entities that have been processed
260
- // (actionStatus is set) and return the last 5 most recent, newest first.
261
- // This closes the Act → Confirm → Perceive loop: the executive can see what it
262
- // tried, whether it landed, and whether an external dispatch went unanswered.
263
- const recentActions: Array<{
264
- type: string; status: 'completed' | 'failed' | 'awaiting_host' | 'timed_out'
265
- tick: number; outcome: string; planId?: string
266
- }> = []
267
-
268
- for( const entity of state.entities.values() ){
269
- if( entity.type !== 'decision.record') continue
270
- const actionStatus = entity.metadata?.actionStatus as string | undefined
271
- if( !actionStatus ) continue
272
- if( !( [ 'completed', 'failed', 'awaiting_host', 'timed_out' ] as string[] ).includes( actionStatus ) ) continue
273
-
274
- recentActions.push({
275
- type: ( entity.metadata?.actionType as string ) ?? 'unknown',
276
- status: actionStatus as 'completed' | 'failed' | 'awaiting_host' | 'timed_out',
277
- tick: ( entity.metadata?.executionTick as number ) ?? ( entity.metadata?.dispatchedAt as number ) ?? 0,
278
- outcome: String( entity.metadata?.outcome ?? '').slice( 0, 120 ),
279
- planId: entity.metadata?.planId as string | undefined,
280
- })
281
- }
282
-
283
- // Sort newest-first, cap at 5
284
- recentActions.sort( ( a, b ) => b.tick - a.tick )
285
- const recentActionsCapped = recentActions.slice( 0, 5 )
266
+ // What became of what I did this is what closes the Act → Confirm → Perceive
267
+ // loop the prompt's `## What Became Of What I Did` section was written for.
268
+ //
269
+ // It used to scan `decision.record` for an `actionStatus`, a field READ here
270
+ // and written nowhere in the engine. The section therefore rendered zero times
271
+ // across every prompt a live COO ever received, while `## What I've Said
272
+ // Lately` rendered in all of them — the mind could see what it had said and
273
+ // never what it had done. See `action.record.ts` for what that cost.
274
+ const recentActionsCapped = recentActionRecords( state.entities as never )
286
275
 
287
276
  // Current best name for a person, from the known-entity roster.
288
277
  const nameOf = ( keid: string ): string | undefined => {
@@ -353,7 +342,11 @@ export async function buildExecutiveContext(
353
342
  sleepPressure: state.metrics.get('sleep.pressure') ?? 0,
354
343
  stressLoad: state.metrics.get('stress.load') ?? 0,
355
344
  circadianPhase: state.metrics.get('circadian.phase') ?? 0,
356
- timeOfDay: state.metrics.get('time.of_day') ?? 12,
345
+ // `circadian.time_of_day`, not `time.of_day`. The oscillator has always
346
+ // written the first and this has always read the second, so the fallback
347
+ // fired on EVERY tick a Will has ever run: the prompt said `Time: 12.0h`
348
+ // — noon, permanently — beside a phase label that disagreed with it.
349
+ timeOfDay: state.metrics.get('circadian.time_of_day') ?? 12,
357
350
  // Tonic threat representation — survives event habituation (guardrail).
358
351
  threatLevel: state.metrics.get('threat.level') ?? 0
359
352
  },
@@ -623,6 +616,13 @@ function mapEpisodeToMemory( ep: {
623
616
  }
624
617
  }
625
618
 
619
+ /** A working-memory item's host payload, when it carries one. */
620
+ function itemData( content: unknown ): unknown {
621
+ if( content && typeof content === 'object')
622
+ return ( content as Record<string, unknown> )['data']
623
+ return undefined
624
+ }
625
+
626
626
  function extractSummary( content: unknown ): string {
627
627
  if( typeof content === 'string')
628
628
  return content.slice( 0, 120 )
@@ -638,8 +638,8 @@ function extractSummary( content: unknown ): string {
638
638
  return String( content ?? '').slice( 0, 120 )
639
639
  }
640
640
 
641
- function extractPercepts( state: ReadonlySimulationState ): Array<{ category: string; summary: string; salience: number }> {
642
- const percepts: Array<{ category: string; summary: string; salience: number }> = []
641
+ function extractPercepts( state: ReadonlySimulationState ): Array<{ category: string; summary: string; salience: number; data?: unknown }> {
642
+ const percepts: Array<{ category: string; summary: string; salience: number; data?: unknown }> = []
643
643
 
644
644
  for( const entity of state.entities.values() ){
645
645
  if( entity.type !== 'percept' && entity.type !== 'percept.social') continue
@@ -652,6 +652,10 @@ function extractPercepts( state: ReadonlySimulationState ): Array<{ category: st
652
652
  percepts.push({
653
653
  category: (entity.metadata?.category as string) ?? 'general',
654
654
  summary,
655
+ // What the host actually sent, beside the label the engine wrote. A mind
656
+ // reasoning only from labels is reasoning from somebody else's summary of
657
+ // the evidence.
658
+ ...( entity.metadata?.data !== undefined ? { data: entity.metadata.data } : {} ),
655
659
  salience: (entity.metadata?.salience as number) ?? 0
656
660
  })
657
661
  }
@@ -672,12 +676,14 @@ function extractAffect( state: ReadonlySimulationState ): ExecutiveContext['affe
672
676
  const arousal = state.metrics.get('affect.arousal') ?? 0.5
673
677
  const dominance = state.metrics.get('affect.dominance') ?? 0.5
674
678
 
675
- // Non-numeric affect data — read from the affective-state entity.
676
- // AffectiveBlender writes this entity each tick with dominantEmotion and blends.
679
+ // Non-numeric affect data — read from the entity AffectiveBlender writes each
680
+ // tick. The id comes from the writer rather than being spelled again here: this
681
+ // read used to look for 'affective-state', which nothing has ever written, so
682
+ // both values silently took their fallbacks in every prompt ever rendered.
677
683
  let dominantEmotion = 'neutral'
678
684
  let blends: string[] = []
679
685
 
680
- const affectEntity = state.entities.get('affective-state')
686
+ const affectEntity = state.entities.get( AFFECT_STATE_ID )
681
687
  if( affectEntity ){
682
688
  dominantEmotion = (affectEntity.metadata?.dominantEmotion as string) ?? 'neutral'
683
689
  blends = (affectEntity.metadata?.blends as string[]) ?? []