@mindot/will 0.4.0 → 0.6.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 (135) hide show
  1. package/README.md +32 -12
  2. package/dist/channels/discord.d.ts +2 -11
  3. package/dist/channels/discord.js.map +1 -1
  4. package/dist/channels/whatsapp.d.ts +72 -0
  5. package/dist/channels/whatsapp.js +252 -0
  6. package/dist/channels/whatsapp.js.map +1 -0
  7. package/dist/cli.js +837 -91
  8. package/dist/cli.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +620 -79
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp/effectors.d.ts +1 -1
  13. package/dist/mcp/effectors.js.map +1 -1
  14. package/dist/types-E9-HV-SW.d.ts +11 -0
  15. package/dist/{will-D-slky1N.d.ts → will-Bikuk4s2.d.ts} +56 -12
  16. package/package.json +12 -3
  17. package/src/channels/discord.ts +11 -11
  18. package/src/channels/roster.ts +1 -1
  19. package/src/channels/types.ts +1 -1
  20. package/src/channels/whatsapp.ts +318 -0
  21. package/src/cli.ts +46 -23
  22. package/src/cognition/acp.ts +28 -0
  23. package/src/cognition/agency/consequence.ts +174 -0
  24. package/src/cognition/agency/engines/action.selector.ts +301 -48
  25. package/src/cognition/agency/engines/affordance.synthesizer.ts +26 -16
  26. package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
  27. package/src/cognition/agency/engines/motor.schema.executor.ts +86 -14
  28. package/src/cognition/agency/engines/reafference.engine.ts +122 -18
  29. package/src/cognition/agency/execution.primitives.ts +13 -13
  30. package/src/cognition/agency/proactive.communicator.ts +1 -1
  31. package/src/cognition/agency/reconcile.learning.ts +1 -1
  32. package/src/cognition/agency/revocation.ts +79 -0
  33. package/src/cognition/agency/schemas/repertoire.ts +2 -2
  34. package/src/cognition/agency/selection.scoring.ts +25 -9
  35. package/src/cognition/bus.ts +2 -2
  36. package/src/cognition/completion.inbox.ts +1 -1
  37. package/src/cognition/config.mirror.entities.ts +5 -5
  38. package/src/cognition/event.log.ts +2 -2
  39. package/src/cognition/event.schemas.ts +67 -67
  40. package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
  41. package/src/cognition/faculties/affective.blender.ts +36 -4
  42. package/src/cognition/faculties/attachment.evaluator.ts +6 -6
  43. package/src/cognition/faculties/attention.allocator.ts +63 -8
  44. package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
  45. package/src/cognition/faculties/bias.detector.ts +7 -7
  46. package/src/cognition/faculties/circadian.oscillator.ts +3 -3
  47. package/src/cognition/faculties/confidence.calibrator.ts +7 -7
  48. package/src/cognition/faculties/dream.simulator.ts +5 -5
  49. package/src/cognition/faculties/empathy.simulator.ts +7 -7
  50. package/src/cognition/faculties/energy.regulator.ts +4 -4
  51. package/src/cognition/faculties/episodic.consolidator.ts +6 -6
  52. package/src/cognition/faculties/executive.engine/commands.ts +6 -6
  53. package/src/cognition/faculties/executive.engine/context.ts +42 -42
  54. package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
  55. package/src/cognition/faculties/executive.engine/engine.ts +27 -26
  56. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
  57. package/src/cognition/faculties/executive.engine/facet.ts +18 -18
  58. package/src/cognition/faculties/executive.engine/gating.ts +2 -2
  59. package/src/cognition/faculties/executive.engine/messages.ts +3 -3
  60. package/src/cognition/faculties/executive.engine/parser.ts +11 -11
  61. package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
  62. package/src/cognition/faculties/exteroception.ts +99 -5
  63. package/src/cognition/faculties/forgetting.curve.ts +3 -3
  64. package/src/cognition/faculties/frustration.evaluator.ts +6 -6
  65. package/src/cognition/faculties/goal.manager.ts +15 -15
  66. package/src/cognition/faculties/inhibition.controller.ts +3 -3
  67. package/src/cognition/faculties/interoception.ts +3 -3
  68. package/src/cognition/faculties/introspection.engine.ts +3 -3
  69. package/src/cognition/faculties/known.entity.tracker.ts +15 -15
  70. package/src/cognition/faculties/loss.evaluator.ts +2 -2
  71. package/src/cognition/faculties/moral.evaluator.ts +4 -4
  72. package/src/cognition/faculties/novelty.detector.ts +4 -4
  73. package/src/cognition/faculties/persona.consolidator.ts +12 -12
  74. package/src/cognition/faculties/planning.engine/engine.ts +47 -47
  75. package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
  76. package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
  77. package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
  78. package/src/cognition/faculties/reputation.tracker.ts +6 -6
  79. package/src/cognition/faculties/reward.evaluator.ts +4 -4
  80. package/src/cognition/faculties/self.model.updater.ts +8 -8
  81. package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
  83. package/src/cognition/faculties/social.perception.ts +3 -3
  84. package/src/cognition/faculties/spaced.repetition.ts +2 -2
  85. package/src/cognition/faculties/stress.regulator.ts +27 -4
  86. package/src/cognition/faculties/task.switcher.ts +11 -6
  87. package/src/cognition/faculties/theory.of.mind.ts +6 -6
  88. package/src/cognition/faculties/threat.evaluator.ts +3 -3
  89. package/src/cognition/faculties/working.memory.ts +8 -8
  90. package/src/cognition/generative.model.ts +2 -2
  91. package/src/cognition/memory/vector.adapter.ts +5 -5
  92. package/src/cognition/memory/vector.content.ts +4 -4
  93. package/src/cognition/memory/vector.embedder.ts +9 -9
  94. package/src/cognition/persona.prior.ts +1 -1
  95. package/src/cognition/schema.registry.ts +2 -2
  96. package/src/cognition/senses/audition.engine/engine.ts +2 -2
  97. package/src/cognition/senses/base.sense.engine.ts +1 -1
  98. package/src/cognition/utilities/token.tracker.ts +11 -5
  99. package/src/core/abstracts.ts +1 -1
  100. package/src/core/completion.recorder.ts +2 -2
  101. package/src/core/serialization.ts +3 -3
  102. package/src/core/snapshot.manager.ts +1 -1
  103. package/src/core/state.manager.ts +1 -1
  104. package/src/core/utils.ts +1 -1
  105. package/src/host/boot.ts +59 -37
  106. package/src/host/utterances.ts +1 -1
  107. package/src/llm/index.ts +84 -34
  108. package/src/llm/summarizer.ts +4 -4
  109. package/src/llm/wire.contracts.ts +1 -1
  110. package/src/mcp/effectors.ts +10 -10
  111. package/src/mcp/server.ts +11 -11
  112. package/src/pma/eval.ts +1 -1
  113. package/src/pma/index.ts +21 -21
  114. package/src/runners/outreach.runner.ts +3 -3
  115. package/src/runners/social.runner.ts +1 -1
  116. package/src/runners/thin-shim.runner.ts +1 -1
  117. package/src/sdk/will.ts +20 -11
  118. package/src/serve/server.ts +12 -12
  119. package/src/stem/assembly.audit.ts +3 -3
  120. package/src/stem/guards/identity.coherence.ts +13 -12
  121. package/src/stem/guards/identity.guard.ts +22 -22
  122. package/src/stem/index.ts +11 -11
  123. package/src/stem/mind.ts +12 -12
  124. package/src/stem/tracts/biography.writer.ts +3 -3
  125. package/src/stem/tracts/effector.controller.ts +4 -4
  126. package/src/stem/tracts/health.reporter.ts +1 -1
  127. package/src/stem/tracts/outbox.controller.ts +4 -0
  128. package/src/stem/tracts/outbox.writer.ts +4 -4
  129. package/src/stem/tracts/pma.controller.ts +1 -1
  130. package/src/stem/tracts/replay.controller.ts +10 -10
  131. package/src/stem/tracts/sensory.controller.ts +2 -2
  132. package/src/stem/tracts/session.logger.ts +4 -4
  133. package/src/stem/tracts/transport/socketio.transport.ts +7 -7
  134. package/src/stem/tracts/transport/stream.transport.ts +2 -2
  135. package/src/stem/tracts/transport.controller.ts +15 -15
@@ -30,6 +30,7 @@ import type {
30
30
  import type { CognitiveBus } from '#cognition/bus'
31
31
  import type { CognitiveEngine, EngineResult } from '#cognition/types'
32
32
  import type { CognitiveEventSchema } from '#cognition/schema.registry'
33
+ import { revokedIntentIds, revocationId } from '#agency/revocation'
33
34
 
34
35
  // ── Minimal structural view of the executive's facet machinery ───────────────
35
36
  // Kept structural (not an import of the executive engine) so the agency module
@@ -42,7 +43,7 @@ interface DeliberationFacetHandle {
42
43
  destroy(): void
43
44
  }
44
45
  export interface DeliberationFacetProvider {
45
- spawnFacet( role?: 'deliberation' ): { attention: 'available' | 'full'; handle?: DeliberationFacetHandle }
46
+ spawnFacet( role?: 'deliberation'): { attention: 'available' | 'full'; handle?: DeliberationFacetHandle }
46
47
  }
47
48
 
48
49
  interface Candidate {
@@ -92,15 +93,23 @@ export class DeliberationEngine implements CognitiveEngine {
92
93
  state: ReadonlySimulationState,
93
94
  _context: SimulationContext,
94
95
  ): Promise<EngineResult> {
96
+ // Honor commitment revocations first (EXAFFERENCE P4): a `deliberating`
97
+ // intent under a live tombstone is dropped — the world ruptured hard enough
98
+ // to let go of it — and the tombstone with it. We never deliberate a
99
+ // revoked intent.
100
+ const revoked = revokedIntentIds( state.entities, tick )
101
+ const del: string[] = []
102
+
95
103
  // One deliberation per tick (the serial bottleneck).
96
104
  let target: { id: string; meta: Record<string, unknown> } | null = null
97
105
  for( const [ id, e ] of state.entities ){
98
- if( e.type !== 'agency.intent' ) continue
99
- if( str( e.metadata?.['status'] ) !== 'deliberating' ) continue
106
+ if( e.type !== 'agency.intent') continue
107
+ if( str( e.metadata?.['status'] ) !== 'deliberating') continue
108
+ if( revoked.has( id ) ){ del.push( id, revocationId( id ) ); continue } // revoked → drop
100
109
  const meta = ( e.metadata ?? {} ) as Record<string, unknown>
101
110
  if( !target || id < target.id ) target = { id, meta } // stable pick
102
111
  }
103
- if( !target ) return { commands: {} }
112
+ if( !target ) return del.length > 0 ? { commands: { delete: del } } : { commands: {} }
104
113
 
105
114
  const { id, meta } = target
106
115
  const provisional = str( meta['schema'] ) ?? 'wait'
@@ -108,7 +117,8 @@ export class DeliberationEngine implements CognitiveEngine {
108
117
 
109
118
  // No executive → confirm the substrate's winner (graceful System-1).
110
119
  if( !this._provider )
111
- return { commands: { set: [ this._commit( id, meta, provisional, candidates, 'no-executive' ) ] } }
120
+ return { commands: { set: [ this._commit( id, meta, provisional, candidates, 'no-executive') ],
121
+ ...( del.length > 0 ? { delete: del } : {} ) } }
112
122
 
113
123
  // Deliberate through a UNIFIED facet (same persona/context as the master).
114
124
  const chosen = await this._deliberate( state, candidates, provisional, meta )
@@ -126,7 +136,8 @@ export class DeliberationEngine implements CognitiveEngine {
126
136
 
127
137
  return {
128
138
  commands: {
129
- set: [ this._commit( id, meta, chosen, candidates, 'facet' ) ],
139
+ set: [ this._commit( id, meta, chosen, candidates, 'facet') ],
140
+ ...( del.length > 0 ? { delete: del } : {} ),
130
141
  metrics: [ [ 'agency.deliberation.count', 1 ] ],
131
142
  },
132
143
  }
@@ -145,7 +156,7 @@ export class DeliberationEngine implements CognitiveEngine {
145
156
  if( !this._handle ){
146
157
  const spawned = this._provider!.spawnFacet('deliberation')
147
158
  if( spawned.attention === 'full' || !spawned.handle ){
148
- logger.info( '[deliberation] facet budget full — confirming substrate winner' )
159
+ logger.info('[deliberation] facet budget full — confirming substrate winner')
149
160
  return provisional
150
161
  }
151
162
  this._handle = spawned.handle
@@ -170,7 +181,7 @@ export class DeliberationEngine implements CognitiveEngine {
170
181
  // Director/state not ready (e.g. executive hasn't run yet) or LLM failure →
171
182
  // fall back to the substrate winner. Drop a possibly-dead handle.
172
183
  this._handle = null
173
- logger.warn( `[deliberation] facet unavailable, confirming winner: ${ err instanceof Error ? err.message : String( err ) }` )
184
+ logger.warn(`[deliberation] facet unavailable, confirming winner: ${ err instanceof Error ? err.message : String( err ) }`)
174
185
  return provisional
175
186
  }
176
187
  }
@@ -208,10 +219,13 @@ export class DeliberationEngine implements CognitiveEngine {
208
219
  // Channel B: if this choice arose by interrupting a pending action, name it so the
209
220
  // facet owns the interruption in-character rather than reasoning in a vacuum.
210
221
  const preemptedFrom = str( meta['preemptedFrom'] )
211
- if( preemptedFrom )
212
- lines.push( `I just broke off a pending action ("${ preemptedFrom }") because something more pressing pulled at me. Decide what to do now:` )
222
+ const revokedBy = str( meta['revokedBy'] )
223
+ if( revokedBy )
224
+ lines.push(`Something in my situation just shifted and I let go of what I was weighing ("${ revokedBy }"). Decide afresh what to do now:`)
225
+ else if( preemptedFrom )
226
+ lines.push(`I just broke off a pending action ("${ preemptedFrom }") because something more pressing pulled at me. Decide what to do now:`)
213
227
  else
214
- lines.push( 'My automatic action-selection was uncertain. Candidate actions:' )
228
+ lines.push('My automatic action-selection was uncertain. Candidate actions:')
215
229
  candidates.forEach( ( c, i ) => {
216
230
  const to = c.targetEntityId ? ` toward ${ c.targetEntityId }` : ''
217
231
  // The ability's meaning, so the facet weighs what each option IS FOR rather
@@ -220,9 +234,9 @@ export class DeliberationEngine implements CognitiveEngine {
220
234
  // Channel B: name the plan link so the facet chooses as the self pursuing it,
221
235
  // not blindly among labels ("this one is the next step of the plan I'm on").
222
236
  const plan = c.fromPlan ? " (my current plan's next step)" : ''
223
- lines.push( `${ i + 1 }. ${ c.schema }${ to }${ what }${ plan }` )
237
+ lines.push(`${ i + 1 }. ${ c.schema }${ to }${ what }${ plan }`)
224
238
  })
225
- return lines.join( '\n' )
239
+ return lines.join('\n')
226
240
  }
227
241
  }
228
242
 
@@ -230,7 +244,7 @@ export class DeliberationEngine implements CognitiveEngine {
230
244
 
231
245
  /** Pull the chosen schema from a facet decision, validated against the candidates. */
232
246
  function extractChosen( decision: unknown, candidates: Candidate[] ): string | undefined {
233
- if( !decision || typeof decision !== 'object' ) return undefined
247
+ if( !decision || typeof decision !== 'object') return undefined
234
248
  const actions = ( decision as Record<string, unknown> )['actions']
235
249
  if( !Array.isArray( actions ) ) return undefined
236
250
  const valid = new Set( candidates.map( c => c.schema ) )
@@ -38,9 +38,16 @@ import type { AccessGrants } from '#agency/access.grants'
38
38
  import type { ActionRequest, ActionResult } from '#types'
39
39
  import { INNATE_SCHEMAS } from '#agency/schemas/innate'
40
40
  import { enact, type Enaction } from '#agency/execution.primitives'
41
+ import { revokedIntentIds, revocationId, staleRevocationIds } from '#agency/revocation'
42
+ import {
43
+ CONSEQUENCE_TYPE, CONSEQUENCE_TTL_TICKS,
44
+ consequenceEntity, fnv1a, paramsKey,
45
+ } from '#agency/consequence'
41
46
 
42
- /** Ticks an async (communicate/external) intent may stay 'awaiting' before it is abandoned. */
43
- const AWAIT_TIMEOUT = 15
47
+ /** Ticks an async (communicate/external) intent may stay 'awaiting' before it is
48
+ * abandoned. Exported: the ReafferenceEngine's sensory-confirmation path (P5)
49
+ * stands down at this boundary so the executor's timeout-failure wins. */
50
+ export const AWAIT_TIMEOUT = 15
44
51
 
45
52
  /** Map a communicate schema to the ProactiveCommunicator effector + the permission name. */
46
53
  const COMM_SCHEMA_TO_EFFECTOR: Record<string, string> = {
@@ -134,15 +141,44 @@ export class MotorSchemaExecutor implements CognitiveEngine {
134
141
  const del: string[] = []
135
142
  const metrics: Array<[ string, number ]> = []
136
143
 
137
- const energy = state.metrics.get( 'energy.level' ) ?? 100
138
- const stress = state.metrics.get( 'stress.load' ) ?? 0
144
+ const energy = state.metrics.get('energy.level') ?? 100
145
+ const stress = state.metrics.get('stress.load' ) ?? 0
146
+
147
+ // ── Expire consequence descriptors (EXAFFERENCE P1) ──────────
148
+ // Descriptors deliberately outlive intent resolution (the sensory echo of a
149
+ // reconciled action still arrives); their only cleanup is this TTL sweep.
150
+ for( const [ id, e ] of state.entities ){
151
+ if( e.type !== CONSEQUENCE_TYPE ) continue
152
+ if( tick >= num( e.metadata?.['expiresAt'], 0 ) ) del.push( id )
153
+ }
154
+
155
+ // ── Revocation tombstones (EXAFFERENCE P4) ───────────────────
156
+ // Reap expired tombstones (intent vanished otherwise), and collect live ones
157
+ // so the selected-processing loop can refuse the half-race case: Deliberation
158
+ // committed `selected` the same tick the tombstone was written (both read the
159
+ // pre-tombstone snapshot), so the intent surfaces here still needing to die.
160
+ del.push( ...staleRevocationIds( state.entities, tick ) )
161
+ const revoked = revokedIntentIds( state.entities, tick )
162
+
163
+ // A tombstoned intent dies whatever its status — including an 'expanding'
164
+ // composite parent preempted mid-routine (registry #4) — and takes its
165
+ // sub-intents with it, in any state ('selected' queued by the executor's
166
+ // own in-tick advance, or 'awaiting' a host). The selected-loop guard below
167
+ // additionally prevents enacting any of them this tick.
168
+ if( revoked.size > 0 )
169
+ for( const [ id, e ] of state.entities ){
170
+ if( e.type !== 'agency.intent') continue
171
+ const parentId = str( e.metadata?.['parentIntentId'] )
172
+ if( revoked.has( id ) ) del.push( id, revocationId( id ) )
173
+ else if( parentId && revoked.has( parentId ) ) del.push( id )
174
+ }
139
175
 
140
176
  // ── Timeout stranded async intents ───────────────────────────
141
177
  // An 'awaiting' intent whose host/delivery never returned would block the
142
178
  // serial Will forever. After AWAIT_TIMEOUT ticks, abandon it as a failed
143
179
  // outcome (which also teaches reafference the action is unreliable here).
144
180
  for( const [ id, e ] of state.entities ){
145
- if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting' ) continue
181
+ if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
146
182
  const dispatchedAt = num( e.metadata?.['dispatchedAt'], tick )
147
183
  if( tick - dispatchedAt < AWAIT_TIMEOUT ) continue
148
184
 
@@ -160,17 +196,22 @@ export class MotorSchemaExecutor implements CognitiveEngine {
160
196
  this._emitEnacted( intent, timedOut, predicted, tick )
161
197
  if( intent.planId && intent.planStepId )
162
198
  this._emitActionOutcome( intent, false, 0, 1, tick )
163
- logger.info( `[motor] ⏱ "${ intent.schema }" timed out after ${ tick - dispatchedAt } ticks` )
199
+ logger.info(`[motor] ⏱ "${ intent.schema }" timed out after ${ tick - dispatchedAt } ticks`)
164
200
  }
165
201
 
166
202
  // Process committed intents in a stable order (determinism / replay).
167
203
  const selected = [ ...state.entities.entries() ]
168
- .filter( ( [ , e ] ) => e.type === 'agency.intent' && str( e.metadata?.['status'] ) === 'selected' )
204
+ .filter( ( [ , e ] ) => e.type === 'agency.intent' && str( e.metadata?.['status'] ) === 'selected')
169
205
  .sort( ( a, b ) => ( a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0 ) )
170
206
 
171
207
  let enactedCount = 0
172
208
 
173
209
  for( const [ id, e ] of selected ){
210
+ // Revoked mid-commit (P4), or a sub of a preempted composite (#4) — refuse
211
+ // to enact; the sweep above already queued the deletes.
212
+ if( revoked.has( id ) ) continue
213
+ const parentOf = str( e.metadata?.['parentIntentId'] )
214
+ if( parentOf && revoked.has( parentOf ) ) continue
174
215
  const intent = readIntent( id, e.metadata )
175
216
  const schema = this._resolve( intent.schema )
176
217
 
@@ -190,7 +231,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
190
231
  : { mode: 'external', success: true, outcomeQuality: 0.5, valence: 0,
191
232
  description: `Unknown schema "${ intent.schema }" routed to the host.` }
192
233
 
193
- if( enaction.mode === 'sync' ){
234
+ if( enaction.mode === 'sync'){
194
235
  set.push( outcomeEntity( tick, intent, enaction, predicted ) )
195
236
  del.push( id )
196
237
  if( enaction.metricDeltas ) metrics.push( ...enaction.metricDeltas )
@@ -224,6 +265,26 @@ export class MotorSchemaExecutor implements CognitiveEngine {
224
265
  predictedValence: predicted.expectedValence,
225
266
  },
226
267
  })
268
+ // EXAFFERENCE P1 — the dispatched action's expected sensory footprint,
269
+ // so the world's answer can be recognized as ours when it arrives
270
+ // through the senses (P2). Host acks correlate by intent id already.
271
+ // For a communicate whose words are authored, also carry the text so an
272
+ // echo of them can be matched (P2) and, if the intent is still awaiting,
273
+ // confirm it through the senses (P5) instead of timing out as a failure.
274
+ const awaitingText = enaction.mode === 'communicate'
275
+ ? ( str( intent.parameters['content'] ) ?? firstMessage( intent.parameters['messages'] ) )
276
+ : undefined
277
+ set.push( consequenceEntity({
278
+ intentId: id, schema: intent.schema,
279
+ mode: enaction.mode === 'communicate' ? 'communicate' : 'external',
280
+ ...( enaction.mode === 'communicate'
281
+ ? { effector: COMM_SCHEMA_TO_EFFECTOR[ intent.schema ] ?? intent.schema }
282
+ : {} ),
283
+ ...( intent.targetEntityId ? { targetEntityId: intent.targetEntityId } : {} ),
284
+ ...( awaitingText ? { text: awaitingText, textHash: fnv1a( awaitingText ) } : {} ),
285
+ paramsHash: fnv1a( paramsKey( intent.parameters ) ),
286
+ expiresAt: tick + CONSEQUENCE_TTL_TICKS, tick,
287
+ }) )
227
288
  this._emitDispatch( intent, enaction.mode, tick )
228
289
  metrics.push([ enaction.mode === 'communicate'
229
290
  ? 'agency.communicate.dispatched'
@@ -253,7 +314,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
253
314
  metadata: { ...meta, status: 'expanding', steps, cursor: 0, accumQuality: 0, accumValence: 0, completed: 0 },
254
315
  })
255
316
  set.push( this._subIntent( parentId, intent.targetEntityId, intent.parameters, steps[0]!, 0, tick ) )
256
- logger.info( `[motor] composite "${ intent.schema }" → ${ steps.length } steps` )
317
+ logger.info(`[motor] composite "${ intent.schema }" → ${ steps.length } steps`)
257
318
  }
258
319
 
259
320
  private _advance(
@@ -380,7 +441,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
380
441
  if( bubbles.length === 0 && this._author ){
381
442
  const name = str( intent.parameters['targetEntityName'] ) ?? intent.targetEntityId ?? 'them'
382
443
  try { bubbles = await this._author.authorOutreach( intent.targetEntityId ?? '', name, str( intent.parameters['gist'] ) ) }
383
- catch( err ){ logger.warn( `[motor] outreach authoring failed: ${ errMsg( err ) }` ) }
444
+ catch( err ){ logger.warn(`[motor] outreach authoring failed: ${ errMsg( err ) }`) }
384
445
  }
385
446
  if( bubbles.length === 0 ) return false // nothing authored to send → await
386
447
 
@@ -394,13 +455,24 @@ export class MotorSchemaExecutor implements CognitiveEngine {
394
455
  }
395
456
  let result: ActionResult
396
457
  try { result = await this._comms.executeAction( request, state ) }
397
- catch( err ){ logger.warn( `[motor] communicate delivery failed: ${ errMsg( err ) }` ); return false }
458
+ catch( err ){ logger.warn(`[motor] communicate delivery failed: ${ errMsg( err ) }`); return false }
398
459
 
399
460
  if( result.commands.set?.length ) set.push( ...result.commands.set )
400
461
  const out: Enaction = { mode: 'communicate', success: result.success,
401
462
  outcomeQuality: result.feedback.outcomeQuality, valence: 0, description: result.description }
402
463
  set.push( outcomeEntity( tick, intent, out, predicted ) )
403
464
  del.push( id )
465
+ // EXAFFERENCE P1 — the delivered words' sensory footprint: the channel echo
466
+ // or a quote-back of this exact text should read as ours, not as the world
467
+ // surprising us (P2 matches on the content hash).
468
+ if( result.success )
469
+ set.push( consequenceEntity({
470
+ intentId: id, schema: intent.schema, mode: 'communicate', effector,
471
+ ...( intent.targetEntityId ? { targetEntityId: intent.targetEntityId } : {} ),
472
+ textHash: fnv1a( bubbles.join('\n') ),
473
+ text: bubbles.join('\n'),
474
+ expiresAt: tick + CONSEQUENCE_TTL_TICKS, tick,
475
+ }) )
404
476
  this._emitEnacted( intent, out, predicted, tick )
405
477
  this._emitActionOutcome( intent, result.success, result.feedback.outcomeQuality,
406
478
  clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick )
@@ -420,7 +492,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
420
492
  payload: { schema: intent.schema, success: enaction.success, outcomeQuality: enaction.outcomeQuality, surprise, tick },
421
493
  })
422
494
  }
423
- catch( err ){ logger.warn( `[motor] enacted publish failed: ${ errMsg( err ) }` ) }
495
+ catch( err ){ logger.warn(`[motor] enacted publish failed: ${ errMsg( err ) }`) }
424
496
  }
425
497
 
426
498
  /**
@@ -452,7 +524,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
452
524
  },
453
525
  })
454
526
  }
455
- catch( err ){ logger.warn( `[motor] action outcome publish failed: ${ errMsg( err ) }` ) }
527
+ catch( err ){ logger.warn(`[motor] action outcome publish failed: ${ errMsg( err ) }`) }
456
528
  }
457
529
 
458
530
  private _emitDispatch( intent: Intent, mode: 'communicate' | 'external', tick: Tick ): void {
@@ -469,7 +541,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
469
541
  },
470
542
  })
471
543
  }
472
- catch( err ){ logger.warn( `[motor] dispatch publish failed: ${ errMsg( err ) }` ) }
544
+ catch( err ){ logger.warn(`[motor] dispatch publish failed: ${ errMsg( err ) }`) }
473
545
  }
474
546
  }
475
547
 
@@ -29,9 +29,38 @@ import type { CognitiveEngine, EngineResult } from '#cognition/types'
29
29
  import type { CognitiveEventSchema } from '#cognition/schema.registry'
30
30
  import type { SchemaRepertoire } from '#agency/schemas/repertoire'
31
31
  import { schemaEntityId } from '#agency/schemas/repertoire'
32
+ import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
32
33
 
33
34
  const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
34
35
 
36
+ /**
37
+ * EXAFFERENCE P5 — soft outcome quality for a purely *sensory* confirmation: the
38
+ * world echoed our action back (a reafferent percept), but no host ack graded it.
39
+ * A modest positive — "it manifested" — below a host ack's 0.8, enough to free the
40
+ * awaiting intent and accrue competence instead of letting it time out as a failure.
41
+ */
42
+ const SENSORY_SOFT_QUALITY = 0.6
43
+
44
+ /**
45
+ * Registry #5 — the affect→percept valence seam, consumed. The echo now carries
46
+ * how the mind FEELS about what came back (Exteroception stamps it), so a
47
+ * sensory confirmation is graded rather than flat: quality = the soft base
48
+ * shifted by that valence within a bounded band.
49
+ *
50
+ * Two spans, because the two valence sources are not equal evidence:
51
+ * • `'entity'` — the KnownEntityTracker's felt valence toward this very thing.
52
+ * Real appraisal of the outcome ⇒ the wider span.
53
+ * • `'ambient'` — the mind's mood at perception time. Weak context, NOT a
54
+ * judgement of this action ⇒ a narrow span, so a bad mood cannot teach the
55
+ * Will that a working skill failed.
56
+ *
57
+ * Neutral (or absent) valence reproduces the pre-seam 0.6 exactly.
58
+ */
59
+ const SENSORY_VALENCE_SPAN_ENTITY = 0.25
60
+ const SENSORY_VALENCE_SPAN_AMBIENT = 0.10
61
+ /** Below this the world's reflection reads as a failed outcome, not a success. */
62
+ const SENSORY_SUCCESS_FLOOR = 0.5
63
+
35
64
  export class ReafferenceEngine implements CognitiveEngine {
36
65
  readonly name = 'reafference'
37
66
 
@@ -56,11 +85,11 @@ export class ReafferenceEngine implements CognitiveEngine {
56
85
  /** Creation seam: register a composite proposed by the executive/deliberation facet. */
57
86
  subscribes(): string[] { return [ 'agency.composite.proposed' ] }
58
87
  onCognitiveEvent( e: CognitiveEvent ): void {
59
- if( e.type !== 'agency.composite.proposed' ) return
88
+ if( e.type !== 'agency.composite.proposed') return
60
89
  const p = e.payload as { id?: string; name?: string; composedOf?: unknown; tags?: unknown; cost?: number }
61
90
  const id = p.id ?? p.name
62
91
  const steps = Array.isArray( p.composedOf )
63
- ? ( p.composedOf as unknown[] ).filter( ( s ): s is string => typeof s === 'string' )
92
+ ? ( p.composedOf as unknown[] ).filter( ( s ): s is string => typeof s === 'string')
64
93
  : []
65
94
  if( !id || steps.length < 2 ) return // a composite needs a name + ≥2 sub-schemas
66
95
 
@@ -68,9 +97,9 @@ export class ReafferenceEngine implements CognitiveEngine {
68
97
  id, kind: 'composite', source: 'repertoire', binds: 'none',
69
98
  cost: typeof p.cost === 'number' ? p.cost : 0.1,
70
99
  composedOf: steps,
71
- tags: Array.isArray( p.tags ) ? ( p.tags as unknown[] ).filter( ( t ): t is string => typeof t === 'string' ) : [ 'composite' ],
100
+ tags: Array.isArray( p.tags ) ? ( p.tags as unknown[] ).filter( ( t ): t is string => typeof t === 'string') : [ 'composite' ],
72
101
  })
73
- logger.info( `[reafference] registered proposed composite "${ id }" (${ steps.join( ' → ' ) })` )
102
+ logger.info(`[reafference] registered proposed composite "${ id }" (${ steps.join(' → ') })`)
74
103
  }
75
104
  snapshot(): Record<string, unknown> {
76
105
  return { skills: this._repertoire.skills().size }
@@ -86,14 +115,84 @@ export class ReafferenceEngine implements CognitiveEngine {
86
115
  const del: string[] = []
87
116
  const metrics: Array<[ string, number ]> = []
88
117
 
89
- // ── 1. Fold each new outcome into its skill ──────────────────
90
- let updates = 0
91
- let discovered = 0
118
+ // ── 0. Gather outcomes: real ones from state + synthesized sensory ones ──
119
+ // A `reafferent` percept (P2) carrying a `sourceIntentId` is the world echoing
120
+ // our own action back. If that intent is still `awaiting` and no outcome graded
121
+ // it this tick (a host ack / sync / timeout — those WIN), synthesize a soft
122
+ // outcome so the skill learns and the intent is freed through the senses rather
123
+ // than timing out as a failure (EXAFFERENCE P5). Ack-less awaiting intents only.
124
+ const outcomes: Array<{ id: string; meta: Record<string, unknown>; fromState: boolean }> = []
125
+ const gradedIntentIds = new Set<string>()
126
+ for( const [ id, e ] of state.entities ){
127
+ if( e.type !== 'agency.outcome') continue
128
+ const m = ( e.metadata ?? {} ) as Record<string, unknown>
129
+ outcomes.push({ id, meta: m, fromState: true })
130
+ const iid = str( m['intentId'] ); if( iid ) gradedIntentIds.add( iid )
131
+ }
132
+
133
+ const awaiting = new Map<string, { schema: string; predictedReward: number; predictedValence: number }>()
92
134
  for( const [ id, e ] of state.entities ){
93
- if( e.type !== 'agency.outcome' ) continue
135
+ if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
136
+ const m = ( e.metadata ?? {} ) as Record<string, unknown>
137
+ // Timeout boundary: at tick - dispatchedAt ≥ AWAIT_TIMEOUT the executor
138
+ // (which ran earlier this tick, off the same frozen snapshot) is writing
139
+ // its timeout-failure outcome right now — stand down so one enaction is
140
+ // never scored twice (soft success here + failure next tick).
141
+ if( tick - num( m['dispatchedAt'], tick ) >= AWAIT_TIMEOUT ) continue
142
+ awaiting.set( id, {
143
+ schema: str( m['schema'] ) ?? '',
144
+ predictedReward: num( m['predictedReward'], 0.5 ),
145
+ predictedValence: num( m['predictedValence'], 0 ),
146
+ })
147
+ }
148
+
149
+ let sensory = 0
150
+ const sensedIntentIds = new Set<string>()
151
+ for( const [ , e ] of state.entities ){
152
+ if( e.type !== 'percept') continue
94
153
  const m = ( e.metadata ?? {} ) as Record<string, unknown>
154
+ if( str( m['provenance'] ) !== 'reafferent') continue
155
+ const iid = str( m['sourceIntentId'] )
156
+ if( !iid || gradedIntentIds.has( iid ) || sensedIntentIds.has( iid ) ) continue
157
+ const aw = awaiting.get( iid )
158
+ if( !aw || !aw.schema ) continue // only ack-less awaiting intents
159
+ sensedIntentIds.add( iid )
160
+ sensory++
161
+
162
+ // Registry #5: grade the confirmation by how the echo FELT, within a band
163
+ // whose width reflects how much that valence really says about THIS action.
164
+ const feltRaw = m['valence']
165
+ const felt = typeof feltRaw === 'number' && Number.isFinite( feltRaw ) ? feltRaw : undefined
166
+ const span = str( m['valenceSource'] ) === 'entity'
167
+ ? SENSORY_VALENCE_SPAN_ENTITY
168
+ : SENSORY_VALENCE_SPAN_AMBIENT
169
+ const quality = felt === undefined
170
+ ? SENSORY_SOFT_QUALITY
171
+ : clamp01( SENSORY_SOFT_QUALITY + Math.max( -1, Math.min( 1, felt ) ) * span )
172
+
173
+ outcomes.push({ id: `agency-outcome-${ tick }-${ iid }-sensory`, fromState: false, meta: {
174
+ schema: aw.schema,
175
+ intentId: iid,
176
+ success: quality >= SENSORY_SUCCESS_FLOOR,
177
+ outcomeQuality: quality,
178
+ valence: felt ?? aw.predictedValence,
179
+ predictedReward: aw.predictedReward,
180
+ predictedValence: aw.predictedValence,
181
+ surprise: clamp01( Math.abs( aw.predictedReward - quality ) ),
182
+ mode: 'external',
183
+ reconciled: true,
184
+ sensory: true,
185
+ ...( felt !== undefined ? { sensoryValence: felt, valenceSource: str( m['valenceSource'] ) } : {} ),
186
+ tick,
187
+ } })
188
+ }
189
+
190
+ // ── 1. Fold each outcome into its skill ──────────────────────
191
+ let updates = 0
192
+ let discovered = 0
193
+ for( const { id, meta: m, fromState } of outcomes ){
95
194
  const schema = str( m['schema'] )
96
- if( !schema ){ del.push( id ); continue }
195
+ if( !schema ){ if( fromState ) del.push( id ); continue }
97
196
 
98
197
  const { skill, proceduralized } = this._repertoire.recordOutcome({
99
198
  schema,
@@ -105,10 +204,11 @@ export class ReafferenceEngine implements CognitiveEngine {
105
204
  })
106
205
 
107
206
  set.push( skillEntity( skill ) )
108
- del.push( id ) // outcome consumed
109
- // Host-ack reconciliation: a reconciled outcome carries the awaiting intent's
110
- // id — free it so the serial Will can act again. The executor's own sync
111
- // outcomes already deleted their intent, so this is a harmless no-op there.
207
+ if( fromState ) del.push( id ) // real outcome consumed (synthetic ones were never in state)
208
+ // Host-ack reconciliation OR sensory confirmation: the outcome carries the
209
+ // awaiting intent's id — free it so the serial Will can act again. The
210
+ // executor's own sync outcomes already deleted their intent, so this is a
211
+ // harmless no-op there.
112
212
  const intentId = str( m['intentId'] )
113
213
  if( intentId ) del.push( intentId )
114
214
  updates++
@@ -131,14 +231,14 @@ export class ReafferenceEngine implements CognitiveEngine {
131
231
 
132
232
  if( proceduralized ){
133
233
  this._emitProceduralized( skill, tick )
134
- logger.info( `[reafference] "${ schema }" proceduralized (habit ${ skill.habitStrength.toFixed( 2 ) })` )
234
+ logger.info(`[reafference] "${ schema }" proceduralized (habit ${ skill.habitStrength.toFixed( 2 ) })`)
135
235
  }
136
236
  }
137
237
 
138
238
  // ── 2. Forgetting curve over the competence layer ────────────
139
239
  const dropped = this._repertoire.decay( tick )
140
240
  for( const id of dropped ){
141
- del.push( `agency-skill-${ id }` )
241
+ del.push(`agency-skill-${ id }`)
142
242
  del.push( schemaEntityId( id ) ) // composite mirror (harmless no-op for primitives)
143
243
  }
144
244
 
@@ -157,6 +257,7 @@ export class ReafferenceEngine implements CognitiveEngine {
157
257
  [ 'agency.discovered.count', discovered ],
158
258
  [ 'agency.skill.count', skills.size ],
159
259
  [ 'agency.habitual.count', habitual ],
260
+ [ 'agency.sensory.confirmed', sensory ],
160
261
  )
161
262
 
162
263
  return { commands: { set, delete: del, metrics } }
@@ -171,7 +272,7 @@ export class ReafferenceEngine implements CognitiveEngine {
171
272
  payload: { schema: skill.schema, habitStrength: skill.habitStrength, tick },
172
273
  })
173
274
  }
174
- catch( err ){ logger.warn( `[reafference] publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
275
+ catch( err ){ logger.warn(`[reafference] publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
175
276
  }
176
277
 
177
278
  /**
@@ -197,7 +298,7 @@ export class ReafferenceEngine implements CognitiveEngine {
197
298
  },
198
299
  })
199
300
  }
200
- catch( err ){ logger.warn( `[reafference] plan outcome publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
301
+ catch( err ){ logger.warn(`[reafference] plan outcome publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
201
302
  }
202
303
 
203
304
  private _emitDiscovered( schema: string, tick: Tick ): void {
@@ -208,7 +309,7 @@ export class ReafferenceEngine implements CognitiveEngine {
208
309
  salience: 0.5, payload: { schema, tick },
209
310
  })
210
311
  }
211
- catch( err ){ logger.warn( `[reafference] discovered publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
312
+ catch( err ){ logger.warn(`[reafference] discovered publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
212
313
  }
213
314
  }
214
315
 
@@ -239,3 +340,6 @@ function str( v: unknown ): string | undefined {
239
340
  function num( v: unknown, fallback: number ): number {
240
341
  return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
241
342
  }
343
+ function clamp01( n: number ): number {
344
+ return n < 0 ? 0 : n > 1 ? 1 : n
345
+ }
@@ -46,8 +46,8 @@ const COMM_SCHEMAS = new Set([ 'reach-out', 'talk', 'text', 'broadcast', 'gestur
46
46
 
47
47
  /** Classify how a primitive schema executes. */
48
48
  export function modeOf( schema: MotorSchema ): EnactionMode {
49
- if( schema.tags?.includes( 'external' ) ) return 'external'
50
- if( schema.tags?.includes( 'communication' ) || COMM_SCHEMAS.has( schema.id ) ) return 'communicate'
49
+ if( schema.tags?.includes('external') ) return 'external'
50
+ if( schema.tags?.includes('communication') || COMM_SCHEMAS.has( schema.id ) ) return 'communicate'
51
51
  return 'sync'
52
52
  }
53
53
 
@@ -55,7 +55,7 @@ export function modeOf( schema: MotorSchema ): EnactionMode {
55
55
  export function enact( ctx: EnactionContext ): Enaction {
56
56
  const mode = modeOf( ctx.schema )
57
57
 
58
- if( mode === 'communicate' ){
58
+ if( mode === 'communicate'){
59
59
  const name = str( ctx.parameters['targetEntityName'] ) ?? ctx.targetEntityId ?? 'them'
60
60
  return {
61
61
  mode, success: true, outcomeQuality: 0.7, valence: 0.1,
@@ -63,7 +63,7 @@ export function enact( ctx: EnactionContext ): Enaction {
63
63
  }
64
64
  }
65
65
 
66
- if( mode === 'external' )
66
+ if( mode === 'external')
67
67
  return {
68
68
  mode, success: true, outcomeQuality: 0.5, valence: 0,
69
69
  description: `"${ ctx.schema.id }" dispatched to the host; awaiting the world's reply.`,
@@ -82,25 +82,25 @@ function syncStance( ctx: EnactionContext ): Enaction {
82
82
  switch( schema.id ){
83
83
  case 'rest':
84
84
  // More restorative the more depleted the Will was.
85
- return sync( 0.5 + ( 1 - e01 ) * 0.4, 0.15, 'I let myself recover; the pressure eases a little.' )
85
+ return sync( 0.5 + ( 1 - e01 ) * 0.4, 0.15, 'I let myself recover; the pressure eases a little.')
86
86
  case 'withdraw':
87
- return sync( 0.5 + s01 * 0.3, 0.05 + s01 * 0.1, 'I pull back from the press of things; the world quietens.' )
87
+ return sync( 0.5 + s01 * 0.3, 0.05 + s01 * 0.1, 'I pull back from the press of things; the world quietens.')
88
88
  case 'reflect':
89
- return sync( 0.6, 0.05, 'I turn inward; patterns from recent events settle into place.' )
89
+ return sync( 0.6, 0.05, 'I turn inward; patterns from recent events settle into place.')
90
90
  case 'attend':
91
- return sync( 0.6, 0.0, 'I concentrate, mobilizing more of my attention.' )
91
+ return sync( 0.6, 0.0, 'I concentrate, mobilizing more of my attention.')
92
92
  case 'orient':
93
- return sync( 0.5, 0.0, 'My awareness sweeps the situation, taking its measure.' )
93
+ return sync( 0.5, 0.0, 'My awareness sweeps the situation, taking its measure.')
94
94
  case 'wait':
95
- return sync( 0.5, 0.0, 'I let time pass; regulatory processes continue their quiet work.' )
95
+ return sync( 0.5, 0.0, 'I let time pass; regulatory processes continue their quiet work.')
96
96
  case 'express':
97
- return sync( 0.6, 0.1, 'My inner state becomes outwardly visible.' )
97
+ return sync( 0.6, 0.1, 'My inner state becomes outwardly visible.')
98
98
  case 'inspect': {
99
99
  const focus = str( parameters['focus'] ) ?? 'it'
100
- return sync( 0.65, 0.05, `I examine ${ focus } closely; more of its detail resolves.` )
100
+ return sync( 0.65, 0.05, `I examine ${ focus } closely; more of its detail resolves.`)
101
101
  }
102
102
  default:
103
- return sync( 0.5, 0.0, `I enact ${ schema.id }.` )
103
+ return sync( 0.5, 0.0, `I enact ${ schema.id }.`)
104
104
  }
105
105
  }
106
106
 
@@ -203,7 +203,7 @@ export class ProactiveCommunicator {
203
203
  content: bubble,
204
204
  effectorName,
205
205
  replyToMessageId,
206
- }, `-${ i }` ) )
206
+ }, `-${ i }`) )
207
207
  })
208
208
 
209
209
  // Write the intent record at push time so context-builder can see what we said.
@@ -67,7 +67,7 @@ export function reconcileInvocation(
67
67
  predictedReward: predicted.reward,
68
68
  predictedValence: predicted.valence,
69
69
  surprise,
70
- description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.' ),
70
+ description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.'),
71
71
  mode: 'external',
72
72
  tick,
73
73
  reconciled: true,