@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
@@ -32,6 +32,7 @@ import { schemaEntityId, availabilityEntityId } from '#agency/schemas/repertoire
32
32
  import { asFinality } from '#stem/policy/arbiter'
33
33
  import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
34
34
  import { readEffectiveParams } from '#cognition/persona.prior'
35
+ import { asProvenance } from '#senses/provenance'
35
36
  import {
36
37
  SENT_TYPE, DEFAULT_REPLY_WINDOW_TICKS, resolveReplyExpectations,
37
38
  } from '#agency/conversation.aim'
@@ -161,7 +162,11 @@ export class ReafferenceEngine implements CognitiveEngine {
161
162
  for( const [ , e ] of state.entities ){
162
163
  if( e.type !== 'percept') continue
163
164
  const m = ( e.metadata ?? {} ) as Record<string, unknown>
164
- if( str( m['provenance'] ) !== 'reafferent') continue
165
+ // asProvenance(), not a literal compare: this is an untyped read out of
166
+ // entity metadata, exactly what that normalizer exists for. Behaviour is
167
+ // unchanged — absent and garbage both normalize to 'exafferent', which
168
+ // fails this test the same way `str()` returning undefined did.
169
+ if( asProvenance( m['provenance'] ) !== 'reafferent') continue
165
170
  const iid = str( m['sourceIntentId'] )
166
171
  if( !iid || gradedIntentIds.has( iid ) || sensedIntentIds.has( iid ) ) continue
167
172
  const aw = awaiting.get( iid )
@@ -201,6 +206,7 @@ export class ReafferenceEngine implements CognitiveEngine {
201
206
  let updates = 0
202
207
  let discovered = 0
203
208
  let refused = 0
209
+ let withheld = 0
204
210
  for( const { id, meta: m, fromState } of outcomes ){
205
211
  const schema = str( m['schema'] )
206
212
  if( !schema ){ if( fromState ) del.push( id ); continue }
@@ -210,6 +216,25 @@ export class ReafferenceEngine implements CognitiveEngine {
210
216
  // habit, param priors), or the Will learns it is unskilled at something it
211
217
  // is merely forbidden to do. The awaiting intent is still freed, and a
212
218
  // refused plan step is signalled unsuccessful so the plan doesn't hang.
219
+ // Chosen silence. The mind considered speaking and decided not to, which
220
+ // is a decision — not a failure, and not a refusal either: nothing forbade
221
+ // it, so availability must not be dented and `recordRefusal` must not run.
222
+ // Free the intent, signal any plan step, and teach NOTHING: the question
223
+ // was never whether it can speak.
224
+ //
225
+ // Before this, a declined outreach timed out at AWAIT_TIMEOUT and folded
226
+ // into `reach-out`'s competence as a failure — the mind learning it is bad
227
+ // at speaking from the times it chose not to speak.
228
+ if( m['withheld'] === true ){
229
+ if( fromState ) del.push( id )
230
+ const heldIntent = str( m['intentId'] )
231
+ if( heldIntent ) del.push( heldIntent )
232
+ const heldPlan = str( m['planId'] )
233
+ if( heldPlan ) this._emitPlanOutcome( heldPlan, str( m['stepId'] ), schema, false, 0, 0, tick )
234
+ withheld++
235
+ continue
236
+ }
237
+
213
238
  if( m['refused'] === true ){
214
239
  const finality = asFinality( m['finality'] )
215
240
 
@@ -251,13 +276,13 @@ export class ReafferenceEngine implements CognitiveEngine {
251
276
  updates++
252
277
 
253
278
  // Plan advancement for the async path: a host-acked outcome that carries plan
254
- // provenance is the ONLY signal the PlanningEngine will get (the executor never
279
+ // the plan link is the ONLY signal the PlanningEngine will get (the executor never
255
280
  // saw the ack — the intent was 'awaiting'). Emit the action.outcome it advances
256
281
  // on. Sync/timeout outcomes never carry planId here (the executor emitted their
257
282
  // action.outcome already), so this never double-advances.
258
283
  const planId = str( m['planId'] )
259
284
  if( planId )
260
- this._emitPlanOutcome( planId, str( m['stepId'] ), schema, m['success'] === true, num( m['outcomeQuality'], 0 ), num( m['surprise'], 0 ), tick )
285
+ this._emitPlanOutcome( planId, str( m['stepId'] ), schema, m['success'] === true, num( m['outcomeQuality'], 0 ), num( m['surprise'], 0 ), tick, str( m['description'] ) )
261
286
 
262
287
  // Discovery: the first time the Will enacts a schema, it becomes a known part
263
288
  // of its repertoire (the new model's "discovered" — earned by doing, not catalogued).
@@ -320,6 +345,10 @@ export class ReafferenceEngine implements CognitiveEngine {
320
345
  // Only emit the refusal metric when it fired — a never-refused Will writes
321
346
  // nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
322
347
  if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
348
+ // Counted separately from refusals on purpose: one is the world saying no,
349
+ // the other is the mind saying not now. Collapsing them would read as a
350
+ // policy problem in the telemetry when nothing was forbidden.
351
+ if( withheld > 0 ) metrics.push([ 'agency.withheld.count', withheld ])
323
352
 
324
353
  return { commands: { set, delete: del, metrics } }
325
354
  }
@@ -435,6 +464,9 @@ export class ReafferenceEngine implements CognitiveEngine {
435
464
  private _emitPlanOutcome(
436
465
  planId: string, stepId: string | undefined, schema: string,
437
466
  success: boolean, outcomeQuality: number, surprise: number, tick: Tick,
467
+ /** The host's own words for what happened. Absent on withheld/refused paths,
468
+ * where there was no host and nothing to say beyond the fate. */
469
+ description?: string,
438
470
  ): void {
439
471
  if( !this._bus ) return
440
472
  try {
@@ -443,7 +475,14 @@ export class ReafferenceEngine implements CognitiveEngine {
443
475
  salience: Math.min( 1, outcomeQuality * 0.6 ),
444
476
  payload: {
445
477
  actionType: schema, domain: schema, success, outcomeQuality, surprise,
446
- description: success ? 'The world confirmed the action.' : 'The world rejected the action.',
478
+ // The host's own words for what happened, not a stock sentence. This
479
+ // hardcoded `'The world confirmed the action.'` — and since
480
+ // `action.record` is built from this payload, that phrase (or nothing,
481
+ // from the executor's side) is ALL the prompt's `## Recent Action
482
+ // Outcomes` ever showed. The `agency.outcome` entity has carried the
483
+ // real description the whole time (`reconcile.learning.ts:89`); it was
484
+ // read here as `m['description']` and dropped on the floor.
485
+ description: description ?? ( success ? 'The world confirmed the action.' : 'The world rejected the action.'),
447
486
  planId,
448
487
  ...( stepId ? { stepId } : {} ),
449
488
  tick,
@@ -95,10 +95,23 @@ function syncStance( ctx: EnactionContext ): Enaction {
95
95
  return sync( 0.5, 0.0, 'I let time pass; regulatory processes continue their quiet work.')
96
96
  case 'express':
97
97
  return sync( 0.6, 0.1, 'My inner state becomes outwardly visible.')
98
- case 'inspect': {
99
- const focus = str( parameters['focus'] ) ?? 'it'
100
- return sync( 0.65, 0.05, `I examine ${ focus } closely; more of its detail resolves.`)
101
- }
98
+ // No `inspect` case, deliberately.
99
+ //
100
+ // Looking is now outward ONLY a question put to the world, tagged external
101
+ // and dispatched. Turning attention inward already has three names on this
102
+ // very floor: `orient` sweeps the situation, `attend` mobilises attention,
103
+ // `reflect` turns inward and lets patterns settle. `inspect` naming that too
104
+ // was a second name for an act that already had one.
105
+ //
106
+ // The cost of the collision was not stylistic. The two readings have
107
+ // DIFFERENT failure modes — "I hold no record of it" versus "the world did not
108
+ // answer" — and one verb covering both meant a pure function needed three
109
+ // flags passed in to tell which it was. Live, the inward reading could not
110
+ // fail at all: a fresh Will proceduralized inspect to habit 0.64 in fifteen
111
+ // ticks, examining its own affordance entities and being told it went well.
112
+ //
113
+ // Outward-only, that is structurally impossible rather than conditionally
114
+ // caught. An unanswered look fails because nothing answered.
102
115
  default:
103
116
  return sync( 0.5, 0.0, `I enact ${ schema.id }.`)
104
117
  }
@@ -17,6 +17,7 @@
17
17
 
18
18
  import type { EntityInput, Tick } from '#core/types'
19
19
  import type { DenialFinality, PolicyCounterfactual } from '#stem/policy/arbiter'
20
+ import type { PlanLink } from '#agency/types'
20
21
 
21
22
  export interface HostAckResult {
22
23
  success: boolean
@@ -55,7 +56,7 @@ export interface HostAckResult {
55
56
  * `agency.invocation` payload); `predictedReward`/`predictedValence` are the
56
57
  * efference copy the executor persisted on the intent, so surprise is honest.
57
58
  *
58
- * `provenance` carries the awaiting intent's plan link (planId/stepId) when it was
59
+ * `planLink` carries the awaiting intent's plan step (planId/stepId) when it was
59
60
  * committed from a plan's frontier prior. It rides on the agency.outcome so the
60
61
  * ReafferenceEngine — the engine that consumes async-acked outcomes — can emit the
61
62
  * `action.outcome{planId,stepId}` the PlanningEngine advances on. (The executor
@@ -68,7 +69,7 @@ export function reconcileInvocation(
68
69
  result: HostAckResult,
69
70
  tick: Tick,
70
71
  predicted: { reward: number; valence: number } = { reward: 0.5, valence: 0 },
71
- provenance: { planId?: string; stepId?: string } = {},
72
+ planLink: PlanLink = {},
72
73
  ): EntityInput {
73
74
  const outcomeQuality = result.outcomeQuality ?? ( result.success ? 0.8 : 0.1 )
74
75
  const valence = result.valence ?? ( result.success ? 0.2 : -0.2 )
@@ -94,8 +95,8 @@ export function reconcileInvocation(
94
95
  // Only when the arbiter actually reported a bound — a refusal without one
95
96
  // writes no key at all, so the quiet path is unchanged.
96
97
  ...( result.refused && result.counterfactual ? { counterfactual: result.counterfactual } : {} ),
97
- ...( provenance.planId ? { planId: provenance.planId } : {} ),
98
- ...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
98
+ ...( planLink.planId ? { planId: planLink.planId } : {} ),
99
+ ...( planLink.stepId ? { stepId: planLink.stepId } : {} ),
99
100
  },
100
101
  }
101
102
  }
@@ -92,7 +92,102 @@ export const INNATE_SCHEMAS: MotorSchema[] = [
92
92
  cost: 0.06,
93
93
  preconditions: [ { metric: 'energy.level', op: 'gt', value: 8 } ],
94
94
  baseValence: 0.05,
95
- tags: [ 'perception', 'information' ],
95
+ /**
96
+ * Outward only — a question put to the world, which is the only thing that
97
+ * can answer it.
98
+ *
99
+ * Turning attention inward already has three names on this very floor:
100
+ * `orient` sweeps the situation, `attend` mobilises attention, `reflect`
101
+ * turns inward and lets patterns settle. `inspect` naming that too was a
102
+ * second name for an act that already had one.
103
+ *
104
+ * The collision was not stylistic. The two readings have DIFFERENT failure
105
+ * modes — "I hold no record of it" versus "the world did not answer" — so one
106
+ * verb covering both forced three disambiguation flags into a pure function,
107
+ * and left the inward reading unable to fail at all. Live, a fresh Will
108
+ * proceduralized this to habit 0.64 within fifteen ticks of birth and spent
109
+ * five of its first eight decisions on it, examining its own affordance
110
+ * entities and being told each time that it went well.
111
+ *
112
+ * Tagged external it rides the path `reach-out` already rides: dispatched to
113
+ * the host, held awaiting, acked or timed out. The ACK ITSELF carries the
114
+ * answer — `observation`, in whatever shape the host keeps it — and the
115
+ * engine turns that into a reafferent percept the mind judges for itself
116
+ * (SIGNAL_BOUNDARY P2). One act, one answer, one percept.
117
+ *
118
+ * This paragraph used to say the opposite: that an ack carried only
119
+ * `{success, description}` and so a host CANNOT hand facts back, which
120
+ * obliged every host to call `perceive()` a second time with its own result
121
+ * — the laundering that made a Will's own act arrive looking like news from
122
+ * the world. P2 removed the obligation; this comment outlived it by two
123
+ * merges, which is the ordinary way a false comment survives: nothing
124
+ * compiles it.
125
+ *
126
+ * A look nothing answers still fails, and that is what teaches a mind to
127
+ * stop examining what will not resolve. Through the SDK an effector with no
128
+ * handler is acked failed inside the tick; a host driving the stem directly
129
+ * leaves the intent awaiting until AWAIT_TIMEOUT abandons it.
130
+ *
131
+ * Innate AND host-dependent is not a contradiction; `reach-out` is both.
132
+ * Every mind can look, but whether looking finds anything depends on there
133
+ * being a world.
134
+ */
135
+ tags: [ 'perception', 'information', 'external' ],
136
+ },
137
+ {
138
+ /**
139
+ * Look at a clock.
140
+ *
141
+ * WHY THIS IS AN ACT AND NOT A FACT THE PROMPT HANDS OVER. A body knows its
142
+ * own rhythm — tired, alert, trough — because a rhythm is something a body
143
+ * DOES. It does not know that it is 15:42, because that is a fact about the
144
+ * world, and the only way a fact about the world reaches a mind is by the
145
+ * mind going and getting it. Every prompt used to carry the hour for free,
146
+ * and it was wrong in three ways at once precisely because nobody had to
147
+ * ask where it came from.
148
+ *
149
+ * Innate AND host-dependent, exactly as `inspect` and `reach-out` are:
150
+ * every mind can ask what time it is; whether anything answers depends on
151
+ * there being a world with a clock in it.
152
+ *
153
+ * A host that has one answers on the ack — `observation`, in whatever shape
154
+ * it keeps time: an ISO string, an hour and a zone, a mission-elapsed count.
155
+ * It does not have to phrase it, and it should not: the mind reads the data
156
+ * and makes the meaning of it (SIGNAL_BOUNDARY P2). The answer lands as a
157
+ * reafferent percept stamped with the intent that sought it, so what she
158
+ * knows about the hour is something she went and found, with a record of
159
+ * having found it.
160
+ *
161
+ * A host that has none never answers, and the failure is honest in either
162
+ * shape it takes: through the SDK an unregistered effector is acked failed
163
+ * inside the tick ("No handler registered for effector ..."), through the
164
+ * raw stem the intent sits awaiting until AWAIT_TIMEOUT abandons it. Either
165
+ * way the mind learns that time is not available here — which is a true
166
+ * thing about this world — rather than being handed a fiction.
167
+ *
168
+ * That degradation is the point of putting it here rather than in a config.
169
+ * A clock injected per-host is a fact one Will has and another does not,
170
+ * with no way for either to know which it is. Sought, it is the same
171
+ * mechanism for all of them, and the answer — or its absence — is
172
+ * something the mind can weigh.
173
+ *
174
+ * `binds: 'none'` because the time is not a referent. There is nothing to
175
+ * point at; you just look.
176
+ */
177
+ id: 'check-time',
178
+ kind: 'primitive',
179
+ // 'innate', not 'perceptual'. `inspect` is perceptual because a percept
180
+ // EVOKES it — it binds the thing it looks at. Nothing evokes this; it is
181
+ // always there, like `orient` and `rest`. The synthesizer caps
182
+ // percept-evoked affordances at attention capacity and never caps the
183
+ // floor, and a glance at a clock belongs to the floor.
184
+ source: 'innate',
185
+ binds: 'none',
186
+ // Cheaper than `inspect` (0.06): a glance at a clock, not an examination.
187
+ cost: 0.03,
188
+ preconditions: [ { metric: 'energy.level', op: 'gt', value: 5 } ],
189
+ baseValence: 0.0,
190
+ tags: [ 'perception', 'information', 'external' ],
96
191
  },
97
192
  {
98
193
  id: 'reach-out',
@@ -24,10 +24,11 @@
24
24
  // paramPriors — last known-good parameters become defaults
25
25
  // ─────────────────────────────────────────────────────────────
26
26
 
27
+ import { logger } from '#core/logger'
27
28
  import type { MotorSchema, LearnedSkill } from '#agency/types'
28
29
  import type { EntityInput, ReadonlySimulationState } from '#core/types'
29
30
  import type { DenialFinality } from '#stem/policy/arbiter'
30
- import { INNATE_SCHEMAS } from '#agency/schemas/innate'
31
+ import { INNATE_SCHEMAS, INNATE_SCHEMA_BY_ID } from '#agency/schemas/innate'
31
32
 
32
33
  const VALUE_ALPHA = 0.2 // value EMA rate
33
34
  const ERROR_BETA = 0.2 // prediction-error EMA rate
@@ -99,6 +100,23 @@ export class SchemaRepertoire {
99
100
  * reafference then builds skill on. Idempotent; re-registering updates it.
100
101
  */
101
102
  registerExternal( schema: MotorSchema ): void {
103
+ // An innate schema is part of the body, not a slot a tenant may redefine.
104
+ //
105
+ // This sets by id, so a host registering a handler for a name the floor
106
+ // already uses would REPLACE the innate schema with a generated one —
107
+ // silently dropping its `binds`, cost, preconditions and tags. `inspect` is
108
+ // the live case: a Discord bridge answering inquiries would have overwritten
109
+ // the very schema whose `binds: 'percept'` is how inquiry finds its targets,
110
+ // and the mind would have lost the ability to look at what it cannot place in
111
+ // exchange for gaining an answerer.
112
+ //
113
+ // Registering the HANDLER is still what the host wanted and still happens —
114
+ // only the redeclaration is refused. The container supplies the mechanism; a
115
+ // tenant supplies what answers it.
116
+ if( INNATE_SCHEMA_BY_ID.has( schema.id ) ){
117
+ logger.debug(`[repertoire] "${ schema.id }" is innate — keeping the body's schema, binding the handler only`)
118
+ return
119
+ }
102
120
  this._templates.set( schema.id, schema )
103
121
  }
104
122
 
@@ -77,6 +77,20 @@ export interface ScoreWeights {
77
77
  * pressing reason to speak again still wins.
78
78
  */
79
79
  repeat: number
80
+ /**
81
+ * How strongly a verdict System 2 already reached holds the competition.
82
+ *
83
+ * Peer of `will` and `repeat` in magnitude, and for the same reason each of
84
+ * those is: having thought a choice through should weigh about what willing it
85
+ * does, and no more. Large enough that the settled option clears the
86
+ * selector's ambiguity gate (0.06) for most of the settlement's life, so the
87
+ * same question is not re-deliberated every few ticks; small enough that a
88
+ * genuinely pressing affordance — threat, a person waiting, a drive gone
89
+ * urgent — still out-competes a standing verdict.
90
+ *
91
+ * Decays to 0 with the settlement, so the question re-opens on its own.
92
+ */
93
+ settled: number
80
94
  }
81
95
 
82
96
  export const DEFAULT_WEIGHTS: ScoreWeights = {
@@ -92,6 +106,7 @@ export const DEFAULT_WEIGHTS: ScoreWeights = {
92
106
  inhib: 0.30,
93
107
  risk: 0.20,
94
108
  repeat: 0.30,
109
+ settled: 0.30,
95
110
  }
96
111
 
97
112
  /**
@@ -190,6 +205,7 @@ export function scoreAffordance(
190
205
  - w.inhib * bias.inhibition
191
206
  - w.risk * risk( a, bias )
192
207
  - w.repeat * ( a.justEnacted ?? 0 )
208
+ + w.settled * ( a.settled ?? 0 )
193
209
  )
194
210
  // POLICY_REAFFERENCE P2 — policy availability damps a POSITIVE activation only,
195
211
  // never flipping its sign: a refused ability competes weakly (so it is rarely
@@ -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
@@ -184,6 +184,26 @@ export interface Affordance {
184
184
  * each time as though it were the first.
185
185
  */
186
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
187
207
  /** Provenance: the plan whose frontier step projected this affordance. */
188
208
  planId?: string
189
209
  /** Provenance: the frontier step id — flows through to action.outcome so the plan advances. */
@@ -191,6 +211,23 @@ export interface Affordance {
191
211
  tick: number
192
212
  }
193
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
+
194
231
  /**
195
232
  * EfferenceCopy — the forward model's prediction of an action's own consequences,
196
233
  * emitted *before* enaction so reafference has something to compare against.
@@ -103,7 +103,6 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
103
103
  params: {
104
104
  maxPerceptsPerTick: 50,
105
105
  defaultSalience: 0.3,
106
- emitPerceptEvents: 1,
107
106
  },
108
107
  },
109
108
  {
@@ -74,14 +74,6 @@ globalSchemaRegistry.register({
74
74
  },
75
75
  })
76
76
 
77
- globalSchemaRegistry.register({
78
- type: 'percept.batch.ingested', version: 1,
79
- validate( p ){
80
- if( !isObj(p) ) return 'payload must be object'
81
- return hasNum( p, 'count')
82
- },
83
- })
84
-
85
77
  globalSchemaRegistry.register({
86
78
  type: 'social.agents.present', version: 1,
87
79
  validate( p ){