@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
@@ -0,0 +1,203 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/settlement.ts — having thought about it
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The agency has always been able to represent having ACTED. It could not
6
+ // represent having DECIDED.
7
+ //
8
+ // System 2 is recruited when the field is ambiguous — `margin < marginGate` in
9
+ // the ActionSelector, where margin is the activation gap between the top two
10
+ // affordances. The DeliberationEngine then resolves the contest and writes back
11
+ // exactly one thing: that intent flipped to 'selected', carrying `deliberated:
12
+ // true`. Nothing reads `deliberated`. The synthesizer rebuilds the field from
13
+ // scratch next tick, the same rivals score the same, the margin is again below
14
+ // the gate, and the same question is deliberated again.
15
+ //
16
+ // Measured on a live COO over 7 hours: 149 deliberations, median gap between
17
+ // them 17 seconds, 139 of 148 gaps under a minute — one LLM call every ~3 ticks
18
+ // for the whole run, and the single largest line in her cost. She could see it
19
+ // and had no mechanism to leave it:
20
+ //
21
+ // "Nothing has changed since my last ten deliberation cycles. Goal-7 is
22
+ // externally blocked — this is a fact, not a diagnosis I need to reach again."
23
+ // "I have composed the same message to FKEM across twelve deliberation cycles
24
+ // and not sent it."
25
+ // "There is nothing left to deliberate. I am looking."
26
+ //
27
+ // So: a settlement is the trace a verdict leaves in the field it was called in
28
+ // to resolve. It is the missing peer of a family the engine already has —
29
+ // having acted damps re-acting (`justEnacted`), having spoken damps re-speaking
30
+ // (`spokeAnywhereAt`), having committed can be withdrawn (revocation
31
+ // tombstones) — and having thought damped nothing at all.
32
+ //
33
+ // The mechanism is deliberately ONE quantity doing both jobs. Raising the
34
+ // settled option's activation means (a) the verdict has force, so System 1 does
35
+ // not coin-flip its way to a different answer on the next tick of the same flat
36
+ // field, and (b) the margin to the runner-up widens past the gate, so System 2
37
+ // is not recruited for a question it has already answered. The flatness is what
38
+ // summoned deliberation; the verdict is what gives the field the shape it
39
+ // lacked.
40
+ //
41
+ // And it DECAYS, like everything else here. This is a refractory period, not a
42
+ // lock — the user's objection to "a hard conditional gate that does not promise
43
+ // any flexibility or dynamism of the mind" is the whole design constraint. As
44
+ // the settlement ages the field flattens again and the question genuinely
45
+ // re-opens; a rupture revokes it outright through the machinery that already
46
+ // exists. A mind may always change its mind. It should not have to reach the
47
+ // same conclusion every three seconds to keep holding it.
48
+ // ─────────────────────────────────────────────────────────────
49
+
50
+ import type { EntityInput, Tick } from '#core/types'
51
+
52
+ export const SETTLEMENT_TYPE = 'agency.settlement'
53
+
54
+ /**
55
+ * Ticks a verdict holds before the question is genuinely open again.
56
+ *
57
+ * Set to the satiation window rather than the echo window, and the symmetry is
58
+ * the argument: having thought about something should hold about as long as
59
+ * having done it. The echo window (`CONSEQUENCE_TTL_TICKS`, 30) answers "could
60
+ * the world still be replying to me" — a different and necessarily shorter
61
+ * question that has nothing to do with whether a choice is settled.
62
+ *
63
+ * At the COO's observed rate (~5s/tick) this is roughly five minutes of held
64
+ * verdict, against a mind that was re-deciding every three ticks.
65
+ */
66
+ export const SETTLEMENT_TTL_TICKS = 60
67
+
68
+ export interface SettlementDescriptor {
69
+ /** The act that won the contest. */
70
+ schema: string
71
+ /** Its object, when it has one — keyed exactly as satiation is, on purpose. */
72
+ targetEntityId?: string
73
+ /**
74
+ * The rivals it was weighed against.
75
+ *
76
+ * Not used for matching — a settlement is keyed on what WON, so a slightly
77
+ * differently-framed contest still meets a verdict the mind has already
78
+ * reached. Carried because "I decided this over that" is the introspectable
79
+ * fact, and a settlement with no memory of what it beat is a preference with
80
+ * no reason attached.
81
+ */
82
+ over?: readonly string[]
83
+ tick: Tick
84
+ expiresAt: Tick
85
+ }
86
+
87
+ /** Stable id — one settlement per (schema, target), so re-deciding refreshes it. */
88
+ export function settlementId( schema: string, targetEntityId?: string ): string {
89
+ return `agency-settlement-${ schema }${ targetEntityId ? `-${ targetEntityId }` : '' }`
90
+ }
91
+
92
+ /** Build the settlement entity. Ordinary state, so it snapshots and replays (FN9). */
93
+ export function settlementEntity( d: SettlementDescriptor ): EntityInput {
94
+ return {
95
+ id: settlementId( d.schema, d.targetEntityId ),
96
+ type: SETTLEMENT_TYPE,
97
+ metadata: { ...d, ...( d.over ? { over: [ ...d.over ] } : {} ) },
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Read a settlement back off entity metadata.
103
+ *
104
+ * Decoded field-for-field, including `over`. A value written by one side and
105
+ * never read by the other is the defect shape this codebase has now hit six
106
+ * times, and it is the exact defect this module exists to fix — `deliberated:
107
+ * true` was written on every deliberated intent and read by nobody.
108
+ */
109
+ export function readSettlement(
110
+ m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined,
111
+ ): SettlementDescriptor | null {
112
+ const meta = ( m instanceof Map ? Object.fromEntries( m ) : m ?? {} ) as Record<string, unknown>
113
+ const schema = typeof meta['schema'] === 'string' ? meta['schema'] as string : undefined
114
+ if( !schema ) return null
115
+
116
+ const over = Array.isArray( meta['over'] )
117
+ ? ( meta['over'] as unknown[] ).filter( ( x ): x is string => typeof x === 'string' )
118
+ : undefined
119
+
120
+ return {
121
+ schema,
122
+ targetEntityId: typeof meta['targetEntityId'] === 'string' ? meta['targetEntityId'] as string : undefined,
123
+ ...( over && over.length > 0 ? { over } : {} ),
124
+ tick: typeof meta['tick'] === 'number' ? meta['tick'] as number : 0,
125
+ expiresAt: typeof meta['expiresAt'] === 'number' ? meta['expiresAt'] as number : 0,
126
+ }
127
+ }
128
+
129
+ /** The live (unexpired) settlements in frozen state, in stable id order. */
130
+ export function liveSettlements(
131
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
132
+ tick: Tick,
133
+ /**
134
+ * alias id → anchor id. Settlement is keyed exactly as satiation is — see the
135
+ * note on `settlementForce` — so it has to share satiation's id space too, or a
136
+ * verdict reached about a person under one id would not hold about them under
137
+ * the other. A function, not the table: see `liveConsequences`.
138
+ */
139
+ canon?: ( id: string ) => string,
140
+ ): SettlementDescriptor[] {
141
+ const out: Array<{ id: string; d: SettlementDescriptor }> = []
142
+
143
+ for( const [ id, e ] of entities ){
144
+ if( e.type !== SETTLEMENT_TYPE ) continue
145
+ const d = readSettlement( e.metadata )
146
+ if( !d ) continue
147
+ // Stamped LATER than now ⇒ restored from a previous session, where the tick
148
+ // counter restarts at 1 on wake. Without this a woken mind reads every
149
+ // settlement it ever made as freshly decided and cannot deliberate at all.
150
+ // The same trap `liveConsequences` documents, and the same fix.
151
+ if( d.tick > tick ) continue
152
+ if( tick < d.expiresAt )
153
+ out.push({ id, d: canon && d.targetEntityId
154
+ ? { ...d, targetEntityId: canon( d.targetEntityId ) }
155
+ : d })
156
+ }
157
+
158
+ return out.sort( ( a, b ) => ( a.id < b.id ? -1 : a.id > b.id ? 1 : 0 ) ).map( x => x.d )
159
+ }
160
+
161
+ /** Settlement ids that have aged out — swept by the DeliberationEngine. */
162
+ export function expiredSettlementIds(
163
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
164
+ tick: Tick,
165
+ ): string[] {
166
+ const out: string[] = []
167
+ for( const [ id, e ] of entities ){
168
+ if( e.type !== SETTLEMENT_TYPE ) continue
169
+ const d = readSettlement( e.metadata )
170
+ if( d && tick >= d.expiresAt ) out.push( id )
171
+ }
172
+ return out
173
+ }
174
+
175
+ /**
176
+ * How much of a verdict on this (schema, target) is still standing, 1 → 0.
177
+ *
178
+ * Linear decay over the window, matching `enactionFootprint` — the two are
179
+ * mirror images and should feel the same from inside: one is the fading pull
180
+ * not to repeat an act, this is the fading weight of having already chosen.
181
+ *
182
+ * An objectless act matches an objectless settlement only. Deciding to `rest`
183
+ * says nothing about whether to `reach-out` to Ada, and a verdict that leaked
184
+ * across objects would be a mind that mistakes one question for another.
185
+ */
186
+ export function settlementForce(
187
+ settlements: readonly SettlementDescriptor[],
188
+ schema: string,
189
+ targetEntityId: string | undefined,
190
+ tick: Tick,
191
+ windowTicks: number = SETTLEMENT_TTL_TICKS,
192
+ ): number {
193
+ if( windowTicks <= 0 ) return 0
194
+
195
+ let strongest = 0
196
+ for( const s of settlements ){
197
+ if( s.schema !== schema || s.targetEntityId !== targetEntityId ) continue
198
+ const remaining = ( windowTicks - ( tick - s.tick ) ) / windowTicks
199
+ if( remaining > strongest ) strongest = remaining
200
+ }
201
+
202
+ return strongest < 0 ? 0 : strongest > 1 ? 1 : strongest
203
+ }
@@ -118,7 +118,7 @@ export interface Affordance {
118
118
  parameters: Record<string, unknown>
119
119
  /** Bound target entity id, when the schema binds an entity. */
120
120
  targetEntityId?: string
121
- /** Entity id of the percept / known-entity that evoked this (provenance). */
121
+ /** Entity id of the percept / known-entity that evoked this. */
122
122
  evokedBy?: string
123
123
  /** Anticipated affective outcome −1..1 (learned, falling back to schema prior). */
124
124
  expectedValence: number
@@ -140,6 +140,29 @@ export interface Affordance {
140
140
  * competition WITHOUT bypassing it (the plan biases; the field still decides).
141
141
  */
142
142
  planBias?: number
143
+ /**
144
+ * Top-down volitional bias 0..1 — set when the executive DELIBERATELY willed this
145
+ * affordance (source 'ideomotor'), carrying the confidence it decided with. Like
146
+ * planBias it lifts activation without bypassing the competition: a willed act must
147
+ * still out-compete the field, but it no longer arrives with the same standing as an
148
+ * ambient possibility. Without this term the ideomotor leg created candidates the
149
+ * arithmetic could not tell apart from idle ones, and a cheap unconditional reflex
150
+ * ('express', cost 0.02) beat a deliberate 'reach-out' 176 times out of 176.
151
+ */
152
+ willBias?: number
153
+ /**
154
+ * Learned social standing of this affordance's addressee, −1..1 (0 = neutral or
155
+ * unknown). Set only for an act aimed at someone. Every input is LEARNED, none
156
+ * hardcoded: ReputationTracker's trustworthiness — which is fed by
157
+ * `interaction.occurred` and so only became live once inbound conversation reached
158
+ * social cognition (#113) — weighted by that model's own confidence, plus the mind's
159
+ * current affective tone as a gentle tilt on reaching out at all.
160
+ *
161
+ * This is what makes "they never answer me" reach the competition: it arrives as a
162
+ * fading opinion of a person the mind formed itself and can revise, not as a damping
163
+ * curve applied behind its back.
164
+ */
165
+ socialPrior?: number
143
166
  /**
144
167
  * Policy availability 0..1 (POLICY_REAFFERENCE P2) — learned from refusals,
145
168
  * distinct from `available` (precondition satisfaction) and from competence.
@@ -149,6 +172,38 @@ export interface Affordance {
149
172
  * gets an occasional re-probe and can climb back as availability recovers.
150
173
  */
151
174
  availability?: number
175
+ /**
176
+ * How much of this act's OWN footprint is still live, 1 → 0 (EXAFFERENCE P5).
177
+ * Set when a consequence descriptor for the same (schema, target) has not yet
178
+ * expired — the mind has just done this and does not yet know how it landed.
179
+ *
180
+ * Damps the pull to do it again, and decays back to 0 on its own, so a silence
181
+ * that starts to matter can still out-compete it. Without this the executive's
182
+ * standing `ideomotor.intent` outlived the act it produced: the same two
183
+ * messages were delivered to the same person three times, ~21 ticks apart,
184
+ * each time as though it were the first.
185
+ */
186
+ justEnacted?: number
187
+ /**
188
+ * How much of a DELIBERATED verdict on this act is still standing, 1 → 0.
189
+ *
190
+ * The mirror of `justEnacted`: that one is the fading pull not to repeat
191
+ * something done, this is the fading weight of having already chosen. Set
192
+ * when System 2 resolved a contest in this act's favour and the settlement
193
+ * has not yet aged out.
194
+ *
195
+ * It does two things with one quantity, which is why it is a quantity and not
196
+ * a flag. The verdict gets force, so System 1 does not coin-flip its way to a
197
+ * different answer on the next tick of the same flat field; and the margin to
198
+ * the runner-up widens past the selector's gate, so System 2 is not recruited
199
+ * for a question it has already answered. Flatness is what summons
200
+ * deliberation — the verdict is what gives the field the shape it lacked.
201
+ *
202
+ * Without it, a live COO deliberated 149 times in 7 hours, median 17s apart,
203
+ * re-reaching the same conclusion about an externally-blocked goal until she
204
+ * wrote "this is a fact, not a diagnosis I need to reach again".
205
+ */
206
+ settled?: number
152
207
  /** Provenance: the plan whose frontier step projected this affordance. */
153
208
  planId?: string
154
209
  /** Provenance: the frontier step id — flows through to action.outcome so the plan advances. */
@@ -156,6 +211,23 @@ export interface Affordance {
156
211
  tick: number
157
212
  }
158
213
 
214
+ /**
215
+ * PlanLink — where an act sits in a plan: the frontier step it serves, if any.
216
+ * Rides on the `agency.outcome` so the PlanningEngine can advance the step the
217
+ * act was committed from.
218
+ *
219
+ * NAMED, AND NAMED THIS, BECAUSE IT USED TO BE CALLED `provenance`
220
+ * (SIGNAL_BOUNDARY P3). Two unrelated concepts shared that word in one codebase:
221
+ * this one, and `SignalProvenance` — whether a signal came from the world or
222
+ * from the mind's own act. The signal sense is the one the vocabulary needs, so
223
+ * this one gives the word up. An inline `{ planId?, stepId? }` in three
224
+ * signatures is also how the collision stayed invisible; a name can be grepped.
225
+ */
226
+ export interface PlanLink {
227
+ planId?: string
228
+ stepId?: string
229
+ }
230
+
159
231
  /**
160
232
  * EfferenceCopy — the forward model's prediction of an action's own consequences,
161
233
  * emitted *before* enaction so reafference has something to compare against.
@@ -0,0 +1,232 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/cache/composition.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Compositional operators for cache interpolation.
7
+ *
8
+ * Design principle (from the research sketch): interpolate what is safe to
9
+ * interpolate, copy verbatim what is not. Executive actions carry free-text
10
+ * fields (`reasoning`, `expectedOutcome`) that would turn to gibberish under
11
+ * blending, so the ACTIONS block uses a weighted *vote* over action type and
12
+ * then copies the winning neighbor's action objects verbatim. Numeric scalars
13
+ * (goal priority, belief confidence) are the only fields we mean-blend.
14
+ *
15
+ * Every operator is deterministic: inputs arrive pre-sorted from the cache and
16
+ * ties are resolved by neighbor order (already deterministic), never by hashing.
17
+ */
18
+
19
+ import type { ExecutiveOutputFull } from '#faculties/executive.engine/types'
20
+ import type { ScoredNeighbor, CacheScope } from './types'
21
+
22
+ /**
23
+ * Compose a valid ExecutiveOutputFull from scored neighbors. `neighbors` must be
24
+ * non-empty and ordered best-first (as the cache returns them). The three
25
+ * required fields (actions, reasoning, confidence) are always populated.
26
+ */
27
+ export function composeOutput(
28
+ neighbors: ScoredNeighbor[],
29
+ tau: number,
30
+ scopes: CacheScope[],
31
+ ): ExecutiveOutputFull {
32
+ const weights = _softmaxWeights( neighbors, tau )
33
+
34
+ // The best neighbor anchors required fields when a scope leaves them empty.
35
+ const anchor = neighbors[ 0 ]!.pattern.output
36
+
37
+ const out: ExecutiveOutputFull = {
38
+ actions: [],
39
+ reasoning: anchor.reasoning ?? '',
40
+ confidence: anchor.confidence ?? 0.5,
41
+ }
42
+
43
+ if( scopes.includes('actions') ){
44
+ const composed = _composeActions( neighbors, weights )
45
+ out.actions = composed.actions
46
+ out.reasoning = composed.reasoning
47
+ out.confidence = composed.confidence
48
+ } else {
49
+ // Actions are required downstream — fall back to the anchor's verbatim actions.
50
+ out.actions = _clone( anchor.actions ?? [] )
51
+ }
52
+
53
+ if( scopes.includes('goals') )
54
+ out.newGoals = _composeGoals( neighbors, weights )
55
+
56
+ if( scopes.includes('beliefs') )
57
+ out.newBeliefs = _composeBeliefs( neighbors, weights )
58
+
59
+ return out
60
+ }
61
+
62
+ // ── Weight computation ─────────────────────────────────────
63
+
64
+ function _softmaxWeights( neighbors: ScoredNeighbor[], tau: number ): number[] {
65
+ const t = tau > 0 ? tau : 1e-6
66
+ const sims = neighbors.map( n => n.similarity )
67
+ const maxSim = Math.max( ...sims )
68
+ const exps = sims.map( s => Math.exp( ( s - maxSim ) / t ) ) // shift for stability
69
+ const sum = exps.reduce( ( a, b ) => a + b, 0 )
70
+ return exps.map( e => ( sum === 0 ? 1 / exps.length : e / sum ) )
71
+ }
72
+
73
+ // ── ACTIONS: weighted type vote + verbatim copy ────────────
74
+
75
+ function _composeActions(
76
+ neighbors: ScoredNeighbor[],
77
+ weights: number[],
78
+ ): { actions: ExecutiveOutputFull['actions']; reasoning: string; confidence: number } {
79
+ // Vote on each neighbor's primary (first) action type.
80
+ const typeWeight = new Map<string, number>()
81
+ for( let i = 0; i < neighbors.length; i++ ){
82
+ const acts = neighbors[ i ]!.pattern.output.actions ?? []
83
+ const primary = acts[ 0 ]?.type
84
+ if( primary === undefined ) continue
85
+ typeWeight.set( primary, ( typeWeight.get( primary ) ?? 0 ) + ( weights[ i ] ?? 0 ) )
86
+ }
87
+
88
+ // Winning type — deterministic: highest weight, ties broken by neighbor order
89
+ // (the first neighbor to reach the max keeps it, and neighbors are best-first).
90
+ let winningType: string | null = null
91
+ let bestWeight = -1
92
+ for( let i = 0; i < neighbors.length; i++ ){
93
+ const primary = neighbors[ i ]!.pattern.output.actions?.[ 0 ]?.type
94
+ if( primary === undefined ) continue
95
+ const w = typeWeight.get( primary ) ?? 0
96
+ if( w > bestWeight ){ bestWeight = w; winningType = primary }
97
+ }
98
+
99
+ // Copy the full action list from the highest-weight neighbor whose primary
100
+ // action type matches the winner — verbatim, so text fields stay coherent.
101
+ let source = neighbors[ 0 ]!
102
+ for( let i = 0; i < neighbors.length; i++ ){
103
+ if( neighbors[ i ]!.pattern.output.actions?.[ 0 ]?.type === winningType ){
104
+ source = neighbors[ i ]!
105
+ break
106
+ }
107
+ }
108
+
109
+ const src = source.pattern.output
110
+ return {
111
+ actions: _clone( src.actions ?? [] ),
112
+ reasoning: src.reasoning ?? '',
113
+ confidence: src.confidence ?? 0.5,
114
+ }
115
+ }
116
+
117
+ // ── GOALS: merge by description, mean priority (Phase 2) ────
118
+
119
+ function _composeGoals(
120
+ neighbors: ScoredNeighbor[],
121
+ weights: number[],
122
+ ): ExecutiveOutputFull['newGoals'] {
123
+ type Agg = {
124
+ weight: number
125
+ priority: number
126
+ tags: string[]
127
+ completionType: string
128
+ completionCondition?: string
129
+ bestW: number
130
+ }
131
+ const byDesc = new Map<string, Agg>()
132
+
133
+ for( let i = 0; i < neighbors.length; i++ ){
134
+ const w = weights[ i ] ?? 0
135
+ for( const g of neighbors[ i ]!.pattern.output.newGoals ?? [] ){
136
+ const cur = byDesc.get( g.description )
137
+ if( cur ){
138
+ cur.weight += w
139
+ cur.priority += g.priority * w
140
+ if( w > cur.bestW ){
141
+ cur.bestW = w
142
+ cur.tags = g.tags
143
+ cur.completionType = g.completionType
144
+ cur.completionCondition = g.completionCondition
145
+ }
146
+ } else {
147
+ byDesc.set( g.description, {
148
+ weight: w,
149
+ priority: g.priority * w,
150
+ tags: g.tags,
151
+ completionType: g.completionType,
152
+ completionCondition: g.completionCondition,
153
+ bestW: w,
154
+ } )
155
+ }
156
+ }
157
+ }
158
+
159
+ const result: NonNullable<ExecutiveOutputFull['newGoals']> = []
160
+ for( const [ description, a ] of byDesc ){
161
+ result.push( {
162
+ description,
163
+ priority: a.weight === 0 ? 0 : a.priority / a.weight,
164
+ tags: [ ...a.tags ],
165
+ completionType: a.completionType,
166
+ ...( a.completionCondition !== undefined ? { completionCondition: a.completionCondition } : {} ),
167
+ } )
168
+ }
169
+ result.sort( ( x, y ) => y.priority - x.priority )
170
+ return result.slice( 0, 3 )
171
+ }
172
+
173
+ // ── BELIEFS: merge by statement, mean confidence (Phase 3) ─
174
+
175
+ function _composeBeliefs(
176
+ neighbors: ScoredNeighbor[],
177
+ weights: number[],
178
+ ): ExecutiveOutputFull['newBeliefs'] {
179
+ type Agg = {
180
+ weight: number
181
+ confidence: number
182
+ category: string
183
+ evidence: 'single_observation' | 'recurring_pattern' | 'strong_pattern'
184
+ tags: string[]
185
+ bestW: number
186
+ }
187
+ const byStmt = new Map<string, Agg>()
188
+
189
+ for( let i = 0; i < neighbors.length; i++ ){
190
+ const w = weights[ i ] ?? 0
191
+ for( const b of neighbors[ i ]!.pattern.output.newBeliefs ?? [] ){
192
+ const cur = byStmt.get( b.statement )
193
+ if( cur ){
194
+ cur.weight += w
195
+ cur.confidence += b.confidence * w
196
+ if( w > cur.bestW ){
197
+ cur.bestW = w
198
+ cur.category = b.category
199
+ cur.evidence = b.evidence
200
+ cur.tags = b.tags
201
+ }
202
+ } else {
203
+ byStmt.set( b.statement, {
204
+ weight: w,
205
+ confidence: b.confidence * w,
206
+ category: b.category,
207
+ evidence: b.evidence,
208
+ tags: b.tags,
209
+ bestW: w,
210
+ } )
211
+ }
212
+ }
213
+ }
214
+
215
+ const result: NonNullable<ExecutiveOutputFull['newBeliefs']> = []
216
+ for( const [ statement, a ] of byStmt ){
217
+ result.push( {
218
+ statement,
219
+ category: a.category,
220
+ confidence: a.weight === 0 ? 0 : a.confidence / a.weight,
221
+ evidence: a.evidence,
222
+ tags: [ ...a.tags ],
223
+ } )
224
+ }
225
+ return result
226
+ }
227
+
228
+ // ── Deterministic deep clone for verbatim-copied blocks ────
229
+
230
+ function _clone<T>( v: T ): T {
231
+ return JSON.parse( JSON.stringify( v ) ) as T
232
+ }