@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
@@ -31,6 +31,11 @@ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
31
31
  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
+ import { readEffectiveParams } from '#cognition/persona.prior'
35
+ import { asProvenance } from '#senses/provenance'
36
+ import {
37
+ SENT_TYPE, DEFAULT_REPLY_WINDOW_TICKS, resolveReplyExpectations,
38
+ } from '#agency/conversation.aim'
34
39
 
35
40
  const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
36
41
 
@@ -81,6 +86,11 @@ export class ReafferenceEngine implements CognitiveEngine {
81
86
  // action.outcome the PlanningEngine advances on. (The executor is the emitter
82
87
  // for sync/timeout outcomes; this is its async counterpart — one emitter each.)
83
88
  { type: 'action.outcome', version: 1, validate: () => null },
89
+ // Whether a person answers the mind when it speaks to them. Distinct from
90
+ // `interaction.occurred`, which reports something someone DID — a silence is
91
+ // not an act and so can never appear there, which is why "they never answer
92
+ // me" was unlearnable despite `socialStanding` being built to carry it.
93
+ { type: 'social.responsiveness', version: 1, validate: () => null },
84
94
  ]
85
95
  }
86
96
  /** Creation seam: register a composite proposed by the executive/deliberation facet. */
@@ -152,7 +162,11 @@ export class ReafferenceEngine implements CognitiveEngine {
152
162
  for( const [ , e ] of state.entities ){
153
163
  if( e.type !== 'percept') continue
154
164
  const m = ( e.metadata ?? {} ) as Record<string, unknown>
155
- 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
156
170
  const iid = str( m['sourceIntentId'] )
157
171
  if( !iid || gradedIntentIds.has( iid ) || sensedIntentIds.has( iid ) ) continue
158
172
  const aw = awaiting.get( iid )
@@ -192,6 +206,7 @@ export class ReafferenceEngine implements CognitiveEngine {
192
206
  let updates = 0
193
207
  let discovered = 0
194
208
  let refused = 0
209
+ let withheld = 0
195
210
  for( const { id, meta: m, fromState } of outcomes ){
196
211
  const schema = str( m['schema'] )
197
212
  if( !schema ){ if( fromState ) del.push( id ); continue }
@@ -201,6 +216,25 @@ export class ReafferenceEngine implements CognitiveEngine {
201
216
  // habit, param priors), or the Will learns it is unskilled at something it
202
217
  // is merely forbidden to do. The awaiting intent is still freed, and a
203
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
+
204
238
  if( m['refused'] === true ){
205
239
  const finality = asFinality( m['finality'] )
206
240
 
@@ -242,13 +276,13 @@ export class ReafferenceEngine implements CognitiveEngine {
242
276
  updates++
243
277
 
244
278
  // Plan advancement for the async path: a host-acked outcome that carries plan
245
- // 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
246
280
  // saw the ack — the intent was 'awaiting'). Emit the action.outcome it advances
247
281
  // on. Sync/timeout outcomes never carry planId here (the executor emitted their
248
282
  // action.outcome already), so this never double-advances.
249
283
  const planId = str( m['planId'] )
250
284
  if( planId )
251
- 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'] ) )
252
286
 
253
287
  // Discovery: the first time the Will enacts a schema, it becomes a known part
254
288
  // of its repertoire (the new model's "discovered" — earned by doing, not catalogued).
@@ -281,6 +315,19 @@ export class ReafferenceEngine implements CognitiveEngine {
281
315
  // Availability entries (P2) mirror the same way — empty until a refusal lands.
282
316
  for( const e of this._repertoire.availabilityEntities() ) set.push( e )
283
317
 
318
+ // ── 3.5 Did the words achieve what they were for? ────────────
319
+ // The motor loop above graded whether each act EXECUTED. For a communicative
320
+ // act that is the smaller half of the question: the outbox accepting a message
321
+ // is not the world answering it. Both were the same fact until now, which is
322
+ // how `reach-out` came to sit at 28 enactments / 28 successes while the person
323
+ // it kept reaching had said nothing back.
324
+ //
325
+ // Folded here rather than in a new engine because this is the same question
326
+ // this engine already exists to ask, one layer out — reafference over a social
327
+ // act instead of a motor one. The resolution itself is pure (conversation.aim);
328
+ // all that happens here is writing it down and saying it out loud.
329
+ const replied = this._resolveReplies( tick, state, set )
330
+
284
331
  // ── 4. Telemetry ─────────────────────────────────────────────
285
332
  const skills = this._repertoire.skills()
286
333
  const habitual = [ ...skills.values() ].filter( s => s.habitStrength >= PROC_THRESHOLD ).length
@@ -291,13 +338,112 @@ export class ReafferenceEngine implements CognitiveEngine {
291
338
  [ 'agency.habitual.count', habitual ],
292
339
  [ 'agency.sensory.confirmed', sensory ],
293
340
  )
341
+ // Quiet path stays byte-identical: a Will that has spoken to nobody, or whose
342
+ // every turn was answered, writes nothing here (cf. EXAFFERENCE P3).
343
+ if( replied.answered > 0 ) metrics.push([ 'social.answered.count', replied.answered ])
344
+ if( replied.unanswered > 0 ) metrics.push([ 'social.unanswered.count', replied.unanswered ])
294
345
  // Only emit the refusal metric when it fired — a never-refused Will writes
295
346
  // nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
296
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 ])
297
352
 
298
353
  return { commands: { set, delete: del, metrics } }
299
354
  }
300
355
 
356
+ /**
357
+ * Latch each open turn's fate onto its own record and announce it once.
358
+ *
359
+ * Two rules earn their keep here:
360
+ *
361
+ * • MERGE, never replace. `StateManager.setEntity` overwrites the whole entity,
362
+ * and a `conversation.sent` carries `outboxMessageIds` — the sole key by which
363
+ * a later delivery ack can find it. Rewriting the record with only the fields
364
+ * this method cares about would sever that, silently, for every turn that got
365
+ * an answer.
366
+ *
367
+ * • Latch, don't recompute. `answeredAt`/`unansweredAt` persist, so the event
368
+ * fires on the one tick the fact changed rather than every tick for the rest
369
+ * of the session — which for an unanswered turn would be thousands of
370
+ * identical reputation hits against one person for one silence.
371
+ */
372
+ private _resolveReplies(
373
+ tick: Tick,
374
+ state: ReadonlySimulationState,
375
+ set: EntityInput[],
376
+ ): { answered: number; unanswered: number } {
377
+ // A trait, not a constant: how long a silence takes to mean something differs
378
+ // between minds, so it tunes through the persona prior like every other
379
+ // developable parameter. Absent ⇒ the default ⇒ the pre-seam behaviour.
380
+ //
381
+ // Read from the SELECTOR's config despite being applied here, because it and
382
+ // `repeatWindowTicks` are two readings of one disposition — how long this mind
383
+ // sits with something it has said. Held apart, a mind could tune itself into
384
+ // saying a thing again while still calling the silence too fresh to count.
385
+ const window = Math.max(
386
+ 1,
387
+ Math.round(
388
+ readEffectiveParams( state, 'engine-config-action-selector').replyWindowTicks
389
+ ?? DEFAULT_REPLY_WINDOW_TICKS
390
+ ),
391
+ )
392
+
393
+ const { answered, unanswered } = resolveReplyExpectations( state.entities, tick, window )
394
+ if( answered.length === 0 && unanswered.length === 0 ) return { answered: 0, unanswered: 0 }
395
+
396
+ const merge = ( id: string, patch: Record<string, unknown> ): void => {
397
+ const existing = state.entities.get( id )
398
+ if( !existing ) return
399
+ set.push({
400
+ id, type: SENT_TYPE,
401
+ metadata: { ...( existing.metadata as Record<string, unknown> ?? {} ), ...patch },
402
+ })
403
+ }
404
+
405
+ for( const { turn, at, with: said } of answered ){
406
+ // Their words, not merely the fact of them — see SpokenTurn.answeredWith.
407
+ merge( turn.entityId, { answeredAt: at, ...( said ? { answeredWith: said } : {} ) } )
408
+ this._emitResponsiveness( turn.targetEntityId, true, at - turn.tick, tick )
409
+ }
410
+
411
+ for( const turn of unanswered ){
412
+ merge( turn.entityId, { unansweredAt: tick } )
413
+ this._emitResponsiveness( turn.targetEntityId, false, tick - turn.tick, tick )
414
+ logger.info(
415
+ `[reafference] no answer from ${ turn.targetEntityName ?? turn.targetEntityId } ` +
416
+ `after ${ tick - turn.tick } ticks — "${ turn.preview.slice( 0, 60 ) }"`
417
+ )
418
+ }
419
+
420
+ return { answered: answered.length, unanswered: unanswered.length }
421
+ }
422
+
423
+ /**
424
+ * Publish how a person responded to being spoken to.
425
+ *
426
+ * Deliberately NOT an `interaction.occurred`: that event means "someone did
427
+ * something toward us" and carries a valence for what they did. A silence is
428
+ * nobody doing anything, and forcing it through that channel would have the
429
+ * ReputationTracker book a hostile *act* where there was only an absence — the
430
+ * mind would come to think it was being rebuffed rather than simply not
431
+ * answered yet. Separate signal, separate meaning, one consumer decides what
432
+ * either is worth.
433
+ */
434
+ private _emitResponsiveness( keid: string, answered: boolean, waitedTicks: number, tick: Tick ): void {
435
+ if( !this._bus ) return
436
+ try {
437
+ this._bus.publish({
438
+ type: 'social.responsiveness', version: 1, sourceEngine: this.name,
439
+ // An answer is ordinary; being ignored is the one worth interrupting for.
440
+ salience: answered ? 0.3 : 0.55,
441
+ payload: { keid, answered, waitedTicks, tick },
442
+ })
443
+ }
444
+ catch( err ){ logger.warn(`[reafference] responsiveness publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
445
+ }
446
+
301
447
  private _emitProceduralized( skill: LearnedSkill, tick: Tick ): void {
302
448
  if( !this._bus ) return
303
449
  try {
@@ -318,6 +464,9 @@ export class ReafferenceEngine implements CognitiveEngine {
318
464
  private _emitPlanOutcome(
319
465
  planId: string, stepId: string | undefined, schema: string,
320
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,
321
470
  ): void {
322
471
  if( !this._bus ) return
323
472
  try {
@@ -326,7 +475,14 @@ export class ReafferenceEngine implements CognitiveEngine {
326
475
  salience: Math.min( 1, outcomeQuality * 0.6 ),
327
476
  payload: {
328
477
  actionType: schema, domain: schema, success, outcomeQuality, surprise,
329
- 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.'),
330
486
  planId,
331
487
  ...( stepId ? { stepId } : {} ),
332
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
  }
@@ -190,7 +190,17 @@ export class ProactiveCommunicator {
190
190
  // Generate outbox message IDs first so we can embed them in conversation.sent
191
191
  // and correlate deliveries back to this intent (TODO 11.3).
192
192
  const originalMessage = ( request.parameters?.originalMessage as string ) ?? ''
193
- const deliveryTick = ( request.parameters?.tick as number ) ?? 0
193
+ // `decidedAt` is the enacting tick and is always set by the caller; the
194
+ // `parameters.tick` fallback is for hosts that build a request by hand.
195
+ //
196
+ // This defaulted to 0 for every agency-path delivery, because MotorSchemaExecutor
197
+ // builds `parameters` from the intent and never put a tick in it. Two
198
+ // consequences, both silent: every `conversation.sent` was stamped tick 0, so
199
+ // anything comparing against it (satiation, undertaking discharge) compared
200
+ // against zero and never fired; and the id `conv-sent-<target>-0` was CONSTANT,
201
+ // so each send to a person overwrote the record of the last one — the mind kept
202
+ // exactly one memory of having spoken to each person, forever.
203
+ const deliveryTick = ( request.parameters?.tick as number ) ?? request.decidedAt ?? 0
194
204
  const replyToMessageId = ( request.parameters?.replyToMessageId as string ) ?? undefined
195
205
  const isAck = ( request.parameters?.isAck as boolean ) ?? false
196
206
 
@@ -264,9 +274,15 @@ export class ProactiveCommunicator {
264
274
  description: `I reach out to ${targetEntityName}: "${fullReply.slice( 0, 80 )}${fullReply.length > 80 ? '…' : ''}"`,
265
275
  commands,
266
276
  feedback: {
267
- outcomeQuality: 0.85,
277
+ // NOT a success yet. This reports the TRANSPORT, and the act's point is to be
278
+ // answered — an outcome not yet known at this moment. Scoring delivery as 0.85
279
+ // taught the mind that reaching out works every time, including the times it
280
+ // was ignored, which is why it re-asked the same question 19 times in two
281
+ // minutes. Neutral here; the unanswered/answered signal in outreach.silence.ts
282
+ // is what actually moves the pull toward this person.
283
+ outcomeQuality: 0.5,
268
284
  surprise: 0.15,
269
- lessons: [ `My message is queued for delivery to ${targetEntityName}.` ],
285
+ lessons: [ `My words are on their way to ${targetEntityName}. Whether they land is not yet known.` ],
270
286
  },
271
287
  }
272
288
  }
@@ -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
  }
@@ -0,0 +1,66 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/agency/restart.ts — the restart boundary
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // Two different things happen when a Will wakes from a snapshot, and conflating
6
+ // them is what made this a recurring class of bug rather than a single one:
7
+ //
8
+ // 1. TIME must not go backwards. Entities come back stamped with the tick they
9
+ // were written at, so the clock has to resume from the snapshot's tick
10
+ // rather than restart at 0 (WillStem.createWill does this via
11
+ // `clock.setTick`). Otherwise every `tick - stampedTick` in the codebase —
12
+ // 42 comparison sites across 19 files — computes a NEGATIVE age, and the
13
+ // guards built on them inverted spectacularly: an awaiting intent whose age
14
+ // read -589 could never time out, and the selector's staleness decay
15
+ // `1 - staleness × STALE_DECAY` became `1 + 19.6`, AMPLIFYING an incumbent
16
+ // from 0.47 to 9.74 and making it permanently unpreemptable.
17
+ //
18
+ // 2. Work that was IN FLIGHT does not resume. Resuming the clock fixes the
19
+ // arithmetic but says nothing about the semantics: an action dispatched
20
+ // moments before hibernation is not still dispatched hours later, and the
21
+ // window in which the world might have echoed our own words has closed.
22
+ // Leaving these behind is worse under a resumed clock than under a reset
23
+ // one, because they now look plausibly RECENT — an awaiting intent would be
24
+ // reconciled as a genuine timeout, teaching the mind that reaching that
25
+ // person does not work, when all that happened is that it slept.
26
+ //
27
+ // This module owns (2): what the agency considers in-flight, and therefore what
28
+ // must not cross the boundary. It is deliberately a short, explicit list rather
29
+ // than a heuristic — anything the agency stamps with a tick and expects to
30
+ // resolve within a few of them belongs here.
31
+ // ─────────────────────────────────────────────────────────────
32
+
33
+ import { CONSEQUENCE_TYPE } from '#agency/consequence'
34
+
35
+ /** Entity type of a committed action awaiting the world's answer. */
36
+ const INTENT_TYPE = 'agency.intent'
37
+
38
+ /**
39
+ * Ids of the restored entities that represent work in flight when the mind went
40
+ * to sleep. Pure — the caller drops them.
41
+ *
42
+ * • `agency.intent` with status 'awaiting' — dispatched, never answered. It is
43
+ * ABANDONED, not failed: sleeping is not the world declining to answer, and
44
+ * recording a failure here would teach reafference a lesson about the process
45
+ * lifecycle rather than about the action.
46
+ * • `agency.consequence` — the expected sensory footprint of an act, used both
47
+ * to recognise our own echo (P2) and to damp repeating ourselves (P5). Its
48
+ * TTL is a handful of ticks; across a sleep the echo will never arrive, and a
49
+ * stale one both mis-attributes genuine replies as self-caused and suppresses
50
+ * contact that should now be free to happen.
51
+ *
52
+ * A `selected` intent is deliberately NOT cleared: it has not been dispatched, so
53
+ * it is still an intention the mind holds rather than an action in flight.
54
+ */
55
+ export function inFlightOnRestore(
56
+ entities: ReadonlyMap<string, { type: string; metadata?: Record<string, unknown> }>,
57
+ ): string[] {
58
+ const drop: string[] = []
59
+
60
+ for( const [ id, e ] of entities ){
61
+ if( e.type === CONSEQUENCE_TYPE ){ drop.push( id ); continue }
62
+ if( e.type === INTENT_TYPE && e.metadata?.['status'] === 'awaiting') drop.push( id )
63
+ }
64
+
65
+ return drop
66
+ }
@@ -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
 
@@ -13,6 +13,8 @@
13
13
  // + driveUrgency
14
14
  // + habitStrength·W (additive habit bonus)
15
15
  // + planBias·W (top-down prior)
16
+ // + willBias·W (deliberate volition)
17
+ // + socialPrior·W (learned read on WHO)
16
18
  // − cost
17
19
  // − inhibition
18
20
  // − risk
@@ -47,9 +49,48 @@ export interface ScoreWeights {
47
49
  habit: number
48
50
  /** Top-down planning prior — how strongly an executing plan biases the competition. */
49
51
  plan: number
52
+ /**
53
+ * Volitional prior — how strongly a DELIBERATE executive decision biases the
54
+ * competition. Peer of `plan`: a plan's frontier is a standing prior, a willed act
55
+ * is a prior formed this cycle. Strong enough that no habit (≤1) can out-weigh a
56
+ * confident decision, weak enough that inhibition, threat, or a genuinely more
57
+ * pressing affordance still can — willing is not a rubber stamp.
58
+ */
59
+ will: number
60
+ /**
61
+ * How strongly the mind's LEARNED read on an addressee biases acting toward them.
62
+ * Peer of `goal` in magnitude: who someone has shown themselves to be should weigh
63
+ * about as much as a goal pointed at them. Signed — a person the mind has come to
64
+ * trust pulls it toward contact, one who never answers pulls it away — and it
65
+ * reaches zero for anyone unknown, so a mind that knows no one scores as before.
66
+ */
67
+ social: number
50
68
  cost: number
51
69
  inhib: number
52
70
  risk: number
71
+ /**
72
+ * How strongly an act's own live footprint damps doing it again (EXAFFERENCE
73
+ * P5). Satiation, not a lock: it decays with the descriptor, so the pull
74
+ * returns once the window in which the world could have answered has passed.
75
+ * Peer of `will` in magnitude — a freshly-delivered message should roughly
76
+ * cancel the volitional prior that produced it, and no more, so a genuinely
77
+ * pressing reason to speak again still wins.
78
+ */
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
53
94
  }
54
95
 
55
96
  export const DEFAULT_WEIGHTS: ScoreWeights = {
@@ -59,9 +100,13 @@ export const DEFAULT_WEIGHTS: ScoreWeights = {
59
100
  drive: 0.25,
60
101
  habit: 0.20,
61
102
  plan: 0.30,
103
+ will: 0.30,
104
+ social: 0.30,
62
105
  cost: 0.20,
63
106
  inhib: 0.30,
64
107
  risk: 0.20,
108
+ repeat: 0.30,
109
+ settled: 0.30,
65
110
  }
66
111
 
67
112
  /**
@@ -154,9 +199,13 @@ export function scoreAffordance(
154
199
  + w.drive * driveUrgency( a, bias )
155
200
  + w.habit * a.habitStrength
156
201
  + w.plan * ( a.planBias ?? 0 )
202
+ + w.will * ( a.willBias ?? 0 )
203
+ + w.social * ( a.socialPrior ?? 0 )
157
204
  - w.cost * a.cost
158
205
  - w.inhib * bias.inhibition
159
206
  - w.risk * risk( a, bias )
207
+ - w.repeat * ( a.justEnacted ?? 0 )
208
+ + w.settled * ( a.settled ?? 0 )
160
209
  )
161
210
  // POLICY_REAFFERENCE P2 — policy availability damps a POSITIVE activation only,
162
211
  // never flipping its sign: a refused ability competes weakly (so it is rarely