@mindot/will 0.5.0 → 0.7.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 (136) hide show
  1. package/dist/channels/discord.d.ts +1 -1
  2. package/dist/channels/discord.js.map +1 -1
  3. package/dist/channels/whatsapp.d.ts +1 -1
  4. package/dist/channels/whatsapp.js.map +1 -1
  5. package/dist/cli.js +998 -59
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +998 -59
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/effectors.d.ts +1 -1
  11. package/dist/mcp/effectors.js.map +1 -1
  12. package/dist/{will-BDq-TMQr.d.ts → will-DAW0l-lY.d.ts} +90 -10
  13. package/package.json +1 -1
  14. package/src/channels/discord.ts +11 -11
  15. package/src/channels/roster.ts +1 -1
  16. package/src/channels/types.ts +1 -1
  17. package/src/channels/whatsapp.ts +25 -25
  18. package/src/cli.ts +20 -20
  19. package/src/cognition/acp.ts +28 -0
  20. package/src/cognition/agency/consequence.ts +174 -0
  21. package/src/cognition/agency/engines/action.selector.ts +333 -48
  22. package/src/cognition/agency/engines/affordance.synthesizer.ts +35 -16
  23. package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
  24. package/src/cognition/agency/engines/motor.schema.executor.ts +90 -14
  25. package/src/cognition/agency/engines/reafference.engine.ts +152 -23
  26. package/src/cognition/agency/execution.primitives.ts +13 -13
  27. package/src/cognition/agency/proactive.communicator.ts +1 -1
  28. package/src/cognition/agency/reconcile.learning.ts +10 -1
  29. package/src/cognition/agency/revocation.ts +79 -0
  30. package/src/cognition/agency/schemas/repertoire.ts +109 -9
  31. package/src/cognition/agency/selection.scoring.ts +32 -10
  32. package/src/cognition/agency/types.ts +9 -0
  33. package/src/cognition/bus.ts +2 -2
  34. package/src/cognition/completion.inbox.ts +1 -1
  35. package/src/cognition/config.mirror.entities.ts +5 -5
  36. package/src/cognition/event.log.ts +2 -2
  37. package/src/cognition/event.schemas.ts +67 -67
  38. package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
  39. package/src/cognition/faculties/affective.blender.ts +36 -4
  40. package/src/cognition/faculties/attachment.evaluator.ts +6 -6
  41. package/src/cognition/faculties/attention.allocator.ts +63 -8
  42. package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
  43. package/src/cognition/faculties/bias.detector.ts +7 -7
  44. package/src/cognition/faculties/circadian.oscillator.ts +3 -3
  45. package/src/cognition/faculties/confidence.calibrator.ts +7 -7
  46. package/src/cognition/faculties/dream.simulator.ts +5 -5
  47. package/src/cognition/faculties/empathy.simulator.ts +7 -7
  48. package/src/cognition/faculties/energy.regulator.ts +4 -4
  49. package/src/cognition/faculties/episodic.consolidator.ts +6 -6
  50. package/src/cognition/faculties/executive.engine/commands.ts +6 -6
  51. package/src/cognition/faculties/executive.engine/context.ts +42 -42
  52. package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
  53. package/src/cognition/faculties/executive.engine/engine.ts +25 -25
  54. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
  55. package/src/cognition/faculties/executive.engine/facet.ts +18 -18
  56. package/src/cognition/faculties/executive.engine/gating.ts +2 -2
  57. package/src/cognition/faculties/executive.engine/messages.ts +3 -3
  58. package/src/cognition/faculties/executive.engine/parser.ts +11 -11
  59. package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
  60. package/src/cognition/faculties/exteroception.ts +99 -5
  61. package/src/cognition/faculties/forgetting.curve.ts +3 -3
  62. package/src/cognition/faculties/frustration.evaluator.ts +6 -6
  63. package/src/cognition/faculties/goal.manager.ts +15 -15
  64. package/src/cognition/faculties/inhibition.controller.ts +3 -3
  65. package/src/cognition/faculties/interoception.ts +3 -3
  66. package/src/cognition/faculties/introspection.engine.ts +3 -3
  67. package/src/cognition/faculties/known.entity.tracker.ts +15 -15
  68. package/src/cognition/faculties/loss.evaluator.ts +2 -2
  69. package/src/cognition/faculties/moral.evaluator.ts +4 -4
  70. package/src/cognition/faculties/novelty.detector.ts +4 -4
  71. package/src/cognition/faculties/persona.consolidator.ts +12 -12
  72. package/src/cognition/faculties/planning.engine/engine.ts +47 -47
  73. package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
  74. package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
  75. package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
  76. package/src/cognition/faculties/reputation.tracker.ts +6 -6
  77. package/src/cognition/faculties/reward.evaluator.ts +4 -4
  78. package/src/cognition/faculties/self.model.updater.ts +8 -8
  79. package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
  80. package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
  81. package/src/cognition/faculties/social.perception.ts +3 -3
  82. package/src/cognition/faculties/spaced.repetition.ts +2 -2
  83. package/src/cognition/faculties/stress.regulator.ts +27 -4
  84. package/src/cognition/faculties/task.switcher.ts +11 -6
  85. package/src/cognition/faculties/theory.of.mind.ts +6 -6
  86. package/src/cognition/faculties/threat.evaluator.ts +3 -3
  87. package/src/cognition/faculties/working.memory.ts +8 -8
  88. package/src/cognition/generative.model.ts +2 -2
  89. package/src/cognition/memory/vector.adapter.ts +5 -5
  90. package/src/cognition/memory/vector.content.ts +4 -4
  91. package/src/cognition/memory/vector.embedder.ts +9 -9
  92. package/src/cognition/persona.prior.ts +1 -1
  93. package/src/cognition/schema.registry.ts +2 -2
  94. package/src/cognition/senses/audition.engine/engine.ts +2 -2
  95. package/src/cognition/senses/base.sense.engine.ts +1 -1
  96. package/src/cognition/utilities/token.tracker.ts +5 -5
  97. package/src/core/abstracts.ts +1 -1
  98. package/src/core/completion.recorder.ts +2 -2
  99. package/src/core/serialization.ts +3 -3
  100. package/src/core/snapshot.manager.ts +1 -1
  101. package/src/core/state.manager.ts +1 -1
  102. package/src/core/utils.ts +1 -1
  103. package/src/host/boot.ts +30 -30
  104. package/src/host/utterances.ts +1 -1
  105. package/src/llm/index.ts +9 -9
  106. package/src/llm/summarizer.ts +4 -4
  107. package/src/llm/wire.contracts.ts +1 -1
  108. package/src/mcp/effectors.ts +10 -10
  109. package/src/mcp/server.ts +11 -11
  110. package/src/pma/eval.ts +1 -1
  111. package/src/pma/index.ts +21 -21
  112. package/src/runners/outreach.runner.ts +3 -3
  113. package/src/runners/social.runner.ts +1 -1
  114. package/src/runners/thin-shim.runner.ts +1 -1
  115. package/src/sdk/will.ts +5 -5
  116. package/src/serve/server.ts +12 -12
  117. package/src/stem/assembly.audit.ts +3 -3
  118. package/src/stem/guards/identity.coherence.ts +10 -10
  119. package/src/stem/guards/identity.guard.ts +22 -22
  120. package/src/stem/index.ts +26 -11
  121. package/src/stem/mind.ts +12 -12
  122. package/src/stem/policy/arbiter.ts +136 -0
  123. package/src/stem/policy/rule.table.ts +172 -0
  124. package/src/stem/policy/verdict.recorder.ts +0 -0
  125. package/src/stem/tracts/biography.writer.ts +3 -3
  126. package/src/stem/tracts/effector.controller.ts +383 -4
  127. package/src/stem/tracts/health.reporter.ts +1 -1
  128. package/src/stem/tracts/outbox.controller.ts +4 -0
  129. package/src/stem/tracts/outbox.writer.ts +4 -4
  130. package/src/stem/tracts/pma.controller.ts +1 -1
  131. package/src/stem/tracts/replay.controller.ts +10 -10
  132. package/src/stem/tracts/sensory.controller.ts +2 -2
  133. package/src/stem/tracts/session.logger.ts +4 -4
  134. package/src/stem/tracts/transport/socketio.transport.ts +7 -7
  135. package/src/stem/tracts/transport/stream.transport.ts +2 -2
  136. package/src/stem/tracts/transport.controller.ts +15 -15
@@ -28,10 +28,39 @@ import type { CognitiveBus, CognitiveEvent } from '#cognition/bus'
28
28
  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
- import { schemaEntityId } from '#agency/schemas/repertoire'
31
+ import { schemaEntityId, availabilityEntityId } 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,102 @@ 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>()
92
126
  for( const [ id, e ] of state.entities ){
93
- if( e.type !== 'agency.outcome' ) continue
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 }>()
134
+ for( const [ id, e ] of state.entities ){
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
+ let refused = 0
194
+ for( const { id, meta: m, fromState } of outcomes ){
95
195
  const schema = str( m['schema'] )
96
- if( !schema ){ del.push( id ); continue }
196
+ if( !schema ){ if( fromState ) del.push( id ); continue }
197
+
198
+ // POLICY_REAFFERENCE P2 — a refusal is NOT a failure. Route it to the
199
+ // availability layer and stop: it must never touch LearnedSkill (value,
200
+ // habit, param priors), or the Will learns it is unskilled at something it
201
+ // is merely forbidden to do. The awaiting intent is still freed, and a
202
+ // refused plan step is signalled unsuccessful so the plan doesn't hang.
203
+ if( m['refused'] === true ){
204
+ const finality = str( m['finality'] ) === 'class' ? 'class' : 'instance'
205
+ this._repertoire.recordRefusal( schema, finality, tick )
206
+ if( fromState ) del.push( id )
207
+ const refusedIntent = str( m['intentId'] )
208
+ if( refusedIntent ) del.push( refusedIntent )
209
+ const refusedPlan = str( m['planId'] )
210
+ if( refusedPlan ) this._emitPlanOutcome( refusedPlan, str( m['stepId'] ), schema, false, 0, 0, tick )
211
+ refused++
212
+ continue
213
+ }
97
214
 
98
215
  const { skill, proceduralized } = this._repertoire.recordOutcome({
99
216
  schema,
@@ -105,10 +222,11 @@ export class ReafferenceEngine implements CognitiveEngine {
105
222
  })
106
223
 
107
224
  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.
225
+ if( fromState ) del.push( id ) // real outcome consumed (synthetic ones were never in state)
226
+ // Host-ack reconciliation OR sensory confirmation: the outcome carries the
227
+ // awaiting intent's id — free it so the serial Will can act again. The
228
+ // executor's own sync outcomes already deleted their intent, so this is a
229
+ // harmless no-op there.
112
230
  const intentId = str( m['intentId'] )
113
231
  if( intentId ) del.push( intentId )
114
232
  updates++
@@ -131,23 +249,27 @@ export class ReafferenceEngine implements CognitiveEngine {
131
249
 
132
250
  if( proceduralized ){
133
251
  this._emitProceduralized( skill, tick )
134
- logger.info( `[reafference] "${ schema }" proceduralized (habit ${ skill.habitStrength.toFixed( 2 ) })` )
252
+ logger.info(`[reafference] "${ schema }" proceduralized (habit ${ skill.habitStrength.toFixed( 2 ) })`)
135
253
  }
136
254
  }
137
255
 
138
- // ── 2. Forgetting curve over the competence layer ────────────
256
+ // ── 2. Forgetting curve over the competence layer + availability recovery ──
139
257
  const dropped = this._repertoire.decay( tick )
140
- for( const id of dropped ){
141
- del.push( `agency-skill-${ id }` )
258
+ for( const id of dropped.skills ){
259
+ del.push(`agency-skill-${ id }`)
142
260
  del.push( schemaEntityId( id ) ) // composite mirror (harmless no-op for primitives)
143
261
  }
262
+ for( const id of dropped.availability )
263
+ del.push( availabilityEntityId( id ) ) // fully-recovered ⇒ remove the mirror
144
264
 
145
- // ── 3. Mirror learned composite templates ────────────────────
265
+ // ── 3. Mirror learned composite templates + availability ─────
146
266
  // Skills become `agency.skill` (above); the invented composite *definitions*
147
267
  // must travel too, or a snapshot/restore brings back a skill whose schema is
148
268
  // gone and the executor can't expand it. Idempotent re-write each tick, like
149
269
  // GoalManager._persistGoals. Empty until a composite is actually learned.
150
- for( const e of this._repertoire.compositeEntities() ) set.push( e )
270
+ for( const e of this._repertoire.compositeEntities() ) set.push( e )
271
+ // Availability entries (P2) mirror the same way — empty until a refusal lands.
272
+ for( const e of this._repertoire.availabilityEntities() ) set.push( e )
151
273
 
152
274
  // ── 4. Telemetry ─────────────────────────────────────────────
153
275
  const skills = this._repertoire.skills()
@@ -157,7 +279,11 @@ export class ReafferenceEngine implements CognitiveEngine {
157
279
  [ 'agency.discovered.count', discovered ],
158
280
  [ 'agency.skill.count', skills.size ],
159
281
  [ 'agency.habitual.count', habitual ],
282
+ [ 'agency.sensory.confirmed', sensory ],
160
283
  )
284
+ // Only emit the refusal metric when it fired — a never-refused Will writes
285
+ // nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
286
+ if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
161
287
 
162
288
  return { commands: { set, delete: del, metrics } }
163
289
  }
@@ -171,7 +297,7 @@ export class ReafferenceEngine implements CognitiveEngine {
171
297
  payload: { schema: skill.schema, habitStrength: skill.habitStrength, tick },
172
298
  })
173
299
  }
174
- catch( err ){ logger.warn( `[reafference] publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
300
+ catch( err ){ logger.warn(`[reafference] publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
175
301
  }
176
302
 
177
303
  /**
@@ -197,7 +323,7 @@ export class ReafferenceEngine implements CognitiveEngine {
197
323
  },
198
324
  })
199
325
  }
200
- catch( err ){ logger.warn( `[reafference] plan outcome publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
326
+ catch( err ){ logger.warn(`[reafference] plan outcome publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
201
327
  }
202
328
 
203
329
  private _emitDiscovered( schema: string, tick: Tick ): void {
@@ -208,7 +334,7 @@ export class ReafferenceEngine implements CognitiveEngine {
208
334
  salience: 0.5, payload: { schema, tick },
209
335
  })
210
336
  }
211
- catch( err ){ logger.warn( `[reafference] discovered publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
337
+ catch( err ){ logger.warn(`[reafference] discovered publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
212
338
  }
213
339
  }
214
340
 
@@ -239,3 +365,6 @@ function str( v: unknown ): string | undefined {
239
365
  function num( v: unknown, fallback: number ): number {
240
366
  return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
241
367
  }
368
+ function clamp01( n: number ): number {
369
+ return n < 0 ? 0 : n > 1 ? 1 : n
370
+ }
@@ -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.
@@ -24,6 +24,14 @@ export interface HostAckResult {
24
24
  /** −1..1 felt valence of the outcome. */
25
25
  valence?: number
26
26
  description?: string
27
+ /**
28
+ * POLICY_REAFFERENCE P2 — this ack is a policy REFUSAL, not a world failure.
29
+ * The ReafferenceEngine routes it to the availability layer (NOT competence):
30
+ * a refusal must never teach the Will it is unskilled at something it is merely
31
+ * forbidden to do. `finality` decides how hard availability is cut.
32
+ */
33
+ refused?: boolean
34
+ finality?: 'class' | 'instance'
27
35
  }
28
36
 
29
37
  /**
@@ -67,10 +75,11 @@ export function reconcileInvocation(
67
75
  predictedReward: predicted.reward,
68
76
  predictedValence: predicted.valence,
69
77
  surprise,
70
- description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.' ),
78
+ description: result.description ?? ( result.success ? 'The world confirmed the action.' : 'The world rejected the action.'),
71
79
  mode: 'external',
72
80
  tick,
73
81
  reconciled: true,
82
+ ...( result.refused ? { refused: true, finality: result.finality ?? 'instance' } : {} ),
74
83
  ...( provenance.planId ? { planId: provenance.planId } : {} ),
75
84
  ...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
76
85
  },
@@ -0,0 +1,79 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/revocation.ts — commitment-revocation tombstones
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // EXAFFERENCE P4. When the world ruptures hard enough (rupture ≥
6
+ // RUPTURE_REVOKE_GATE), the ActionSelector doesn't just soften its switch cost
7
+ // (P3) — it *lets go* of a commitment it was still weighing: a `deliberating`
8
+ // intent. But the selector cannot delete that intent directly. Registration
9
+ // order is Selector → Deliberation → Executor, and each engine reads the frozen
10
+ // tick-start snapshot, so a same-tick selector-delete + deliberation-set on the
11
+ // same intent applies set-after-delete → the intent is RESURRECTED as `selected`
12
+ // and enacted despite revocation (the P0 audit's resurrection race).
13
+ //
14
+ // The tombstone breaks that race by construction: the selector writes a SEPARATE
15
+ // `agency.revocation` entity keyed by the intent id (no same-entity write). One
16
+ // tick later both honorers see it from frozen state:
17
+ // • the Deliberation engine skips + deletes tombstoned `deliberating` intents;
18
+ // • the Executor refuses to enact a tombstoned `selected` intent (the half-race
19
+ // where deliberation committed `selected` the same tick the tombstone landed).
20
+ // Revocation commits NO successor — the field re-forms and next tick selects.
21
+ // A short TTL reaps a tombstone whose intent already vanished for another reason.
22
+ // ─────────────────────────────────────────────────────────────
23
+
24
+ import type { EntityInput, Tick } from '#core/types'
25
+
26
+ export const REVOCATION_TYPE = 'agency.revocation'
27
+
28
+ /** Rupture at/above which a still-deliberating commitment is revoked (rarer than
29
+ * P3's recruitment-grade softening — letting go is a bigger step than easing up). */
30
+ export const RUPTURE_REVOKE_GATE = 0.7
31
+
32
+ /** Safety reap: a tombstone whose intent disappeared otherwise dies after this. */
33
+ export const REVOCATION_TTL_TICKS = 5
34
+
35
+ export function revocationId( intentId: string ): string {
36
+ return `agency-revocation-${ intentId }`
37
+ }
38
+
39
+ /** Build the tombstone for a revoked intent (keyed by intent id — one per intent). */
40
+ export function revocationEntity(
41
+ intentId: string, schema: string, rupture: number, tick: Tick,
42
+ ): EntityInput {
43
+ return {
44
+ id: revocationId( intentId ),
45
+ type: REVOCATION_TYPE,
46
+ metadata: { intentId, schema, rupture, tick, expiresAt: tick + REVOCATION_TTL_TICKS },
47
+ }
48
+ }
49
+
50
+ /** Intent ids under a live (unexpired) revocation tombstone, from frozen state. */
51
+ export function revokedIntentIds(
52
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
53
+ tick: Tick,
54
+ ): Set<string> {
55
+ const out = new Set<string>()
56
+ for( const [ , e ] of entities ){
57
+ if( e.type !== REVOCATION_TYPE ) continue
58
+ const m = ( e.metadata ?? {} ) as Record<string, unknown>
59
+ const intentId = typeof m['intentId'] === 'string' ? m['intentId'] as string : undefined
60
+ const expiresAt = typeof m['expiresAt'] === 'number' ? m['expiresAt'] as number : 0
61
+ if( intentId && tick < expiresAt ) out.add( intentId )
62
+ }
63
+ return out
64
+ }
65
+
66
+ /** Ids of expired tombstones to sweep (TTL reap for intents that vanished otherwise). */
67
+ export function staleRevocationIds(
68
+ entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
69
+ tick: Tick,
70
+ ): string[] {
71
+ const stale: string[] = []
72
+ for( const [ id, e ] of entities ){
73
+ if( e.type !== REVOCATION_TYPE ) continue
74
+ const m = ( e.metadata ?? {} ) as Record<string, unknown>
75
+ const expiresAt = typeof m['expiresAt'] === 'number' ? m['expiresAt'] as number : 0
76
+ if( tick >= expiresAt ) stale.push( id )
77
+ }
78
+ return stale
79
+ }
@@ -39,6 +39,23 @@ const IDLE_TICKS = 200 // ticks of disuse before forgetting starts
39
39
  const DECAY_RATE = 0.02 // habit lost per decay application
40
40
  const DROP_HABIT = 0.05 // below this (and learned) the skill is forgotten
41
41
 
42
+ // ── availability layer (POLICY_REAFFERENCE P2) ────────────────
43
+ // Availability is NOT competence. It answers "may I use this schema", learned
44
+ // from policy refusals, and is kept strictly apart from LearnedSkill so a
45
+ // refusal never teaches the Will it is *unskilled* at something it is merely
46
+ // *forbidden* to do. A `class` refusal ("never, under this policy") drives
47
+ // availability down hard; an `instance` refusal ("not with those parameters")
48
+ // dents it lightly — the Will should keep reaching for the ability, just not
49
+ // that way. Recovery is slow but real, so a policy change is re-discoverable:
50
+ // availability never floors at zero, and it climbs back toward 1 with disuse of
51
+ // the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
52
+ // narrowing is a follow-up that belongs at selection time, not fielding time.
53
+ const AVAIL_DROP_CLASS = 0.50 // multiplicative cut on a class-final refusal
54
+ const AVAIL_DROP_INSTANCE = 0.12 // lighter cut on an instance-final refusal
55
+ const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
56
+ const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
57
+ const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
58
+
42
59
  export interface OutcomeObservation {
43
60
  schema: string
44
61
  success: boolean
@@ -54,6 +71,9 @@ export class SchemaRepertoire {
54
71
  private _skills = new Map<string, LearnedSkill>()
55
72
  /** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
56
73
  private _learned = new Set<string>()
74
+ /** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
75
+ * a refusal lands — a never-refused Will writes nothing here (byte-identical). */
76
+ private _availability = new Map<string, { value: number; lastRefusedTick: number }>()
57
77
 
58
78
  constructor( seed: MotorSchema[] = INNATE_SCHEMAS ){
59
79
  for( const s of seed ) this._templates.set( s.id, s )
@@ -85,6 +105,32 @@ export class SchemaRepertoire {
85
105
  skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
86
106
  getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
87
107
 
108
+ // ── availability (P2) ─────────────────────────────────────────
109
+ availability(): ReadonlyMap<string, { value: number; lastRefusedTick: number }> { return this._availability }
110
+
111
+ /**
112
+ * How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
113
+ * (fully available — the common case). This is the ONLY value the
114
+ * AffordanceSynthesizer reads; it never touches competence.
115
+ */
116
+ availabilityOf( schema: string ): number {
117
+ return this._availability.get( schema )?.value ?? 1
118
+ }
119
+
120
+ /**
121
+ * Fold a policy refusal into the availability layer (NOT competence). A
122
+ * `class` refusal cuts availability hard; an `instance` refusal dents it
123
+ * lightly. Multiplicative so repeated refusals compound toward — but never
124
+ * reach — zero, keeping re-probe alive.
125
+ */
126
+ recordRefusal( schema: string, finality: 'class' | 'instance', tick: number ): number {
127
+ const prev = this._availability.get( schema )?.value ?? 1
128
+ const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_INSTANCE
129
+ const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
130
+ this._availability.set( schema, { value, lastRefusedTick: tick } )
131
+ return value
132
+ }
133
+
88
134
  /**
89
135
  * Fold one outcome into the schema's learned skill. Returns the updated skill
90
136
  * and whether it just crossed the proceduralization threshold this update.
@@ -118,12 +164,14 @@ export class SchemaRepertoire {
118
164
  }
119
165
 
120
166
  /**
121
- * Forgetting curve over the competence layer. Skills unused for IDLE_TICKS
122
- * lose habit; learned composites that fall below DROP_HABIT are dropped
123
- * entirely (template + skill). Returns the schema ids that were forgotten.
167
+ * Forgetting curve over the competence layer, plus availability recovery.
168
+ * Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
169
+ * are dropped entirely (template + skill). Availability entries climb back
170
+ * toward 1 and are dropped once fully recovered. Returns the ids that were
171
+ * removed from each layer so their mirrored state entities can be deleted.
124
172
  */
125
- decay( tick: number ): string[] {
126
- const dropped: string[] = []
173
+ decay( tick: number ): { skills: string[]; availability: string[] } {
174
+ const skills: string[] = []
127
175
  for( const [ id, skill ] of this._skills ){
128
176
  if( tick - skill.lastEnactedTick <= IDLE_TICKS ) continue
129
177
 
@@ -132,12 +180,23 @@ export class SchemaRepertoire {
132
180
  this._skills.delete( id )
133
181
  this._templates.delete( id )
134
182
  this._learned.delete( id )
135
- dropped.push( id )
183
+ skills.push( id )
136
184
  continue
137
185
  }
138
186
  this._skills.set( id, { ...skill, habitStrength } )
139
187
  }
140
- return dropped
188
+
189
+ // Availability recovery (P2): each entry climbs slowly back toward 1, so a
190
+ // policy change is re-discoverable. A fully-recovered entry is dropped, so a
191
+ // Will that was refused long ago returns to the byte-identical quiet path.
192
+ const availability: string[] = []
193
+ for( const [ id, avail ] of this._availability ){
194
+ const value = avail.value + AVAIL_RECOVERY * ( 1 - avail.value )
195
+ if( value >= AVAIL_RECOVERED ){ this._availability.delete( id ); availability.push( id ) }
196
+ else this._availability.set( id, { ...avail, value } )
197
+ }
198
+
199
+ return { skills, availability }
141
200
  }
142
201
 
143
202
  // ── PMA portability (Phase 6 reads these) ─────────────────────
@@ -167,7 +226,7 @@ export class SchemaRepertoire {
167
226
  const out: EntityInput[] = []
168
227
  for( const id of this._learned ){
169
228
  const s = this._templates.get( id )
170
- if( s && s.kind === 'composite' ) out.push( schemaEntity( s ) )
229
+ if( s && s.kind === 'composite') out.push( schemaEntity( s ) )
171
230
  }
172
231
  return out
173
232
  }
@@ -188,6 +247,30 @@ export class SchemaRepertoire {
188
247
  this._learned.add( s.id )
189
248
  }
190
249
  }
250
+
251
+ /** Availability ledger encoded as `agency.availability` state entities (P2).
252
+ * Empty until a refusal lands, so the quiet path writes nothing. */
253
+ availabilityEntities(): EntityInput[] {
254
+ const out: EntityInput[] = []
255
+ for( const [ schema, a ] of this._availability )
256
+ out.push( availabilityEntity( schema, a.value, a.lastRefusedTick ) )
257
+ return out
258
+ }
259
+
260
+ /** Rehydrate the availability ledger from state after a restore. Idempotent;
261
+ * keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
262
+ restoreAvailability( entities: ReadonlySimulationState['entities'] ): void {
263
+ for( const e of entities.values() ){
264
+ if( e.type !== AVAILABILITY_ENTITY_TYPE ) continue
265
+ const m = ( e.metadata ?? {} ) as Record<string, unknown>
266
+ const schema = typeof m['schema'] === 'string' ? m['schema'] : ''
267
+ if( !schema ) continue
268
+ const value = typeof m['value'] === 'number' ? m['value'] : 1
269
+ const tick = typeof m['lastRefusedTick'] === 'number' ? m['lastRefusedTick'] : 0
270
+ const prev = this._availability.get( schema )
271
+ if( !prev || value < prev.value ) this._availability.set( schema, { value, lastRefusedTick: tick } )
272
+ }
273
+ }
191
274
  }
192
275
 
193
276
  // ─── helpers ─────────────────────────────────────────────────────────────────
@@ -209,6 +292,23 @@ function clamp01( n: number ): number {
209
292
  return n < 0 ? 0 : n > 1 ? 1 : n
210
293
  }
211
294
 
295
+ // ─── availability ⇄ state-entity codec (P2) ──────────────────────────────────
296
+
297
+ /** State-entity type for a mirrored availability entry. */
298
+ export const AVAILABILITY_ENTITY_TYPE = 'agency.availability'
299
+
300
+ /** Stable entity id for a schema's availability mirror (idempotent re-writes). */
301
+ export function availabilityEntityId( schema: string ): string { return `agency-availability-${ schema }` }
302
+
303
+ /** Encode one availability entry as a state entity. */
304
+ function availabilityEntity( schema: string, value: number, lastRefusedTick: number ): EntityInput {
305
+ return {
306
+ id: availabilityEntityId( schema ),
307
+ type: AVAILABILITY_ENTITY_TYPE,
308
+ metadata: { schema, value, lastRefusedTick },
309
+ }
310
+ }
311
+
212
312
  // ─── composite ⇄ state-entity codec (snapshot/replay) ────────────────────────
213
313
 
214
314
  /** State-entity type for a mirrored composite template. */
@@ -242,7 +342,7 @@ function readSchema( m: Record<string, unknown> | undefined ): MotorSchema | und
242
342
  const meta = m ?? {}
243
343
  const id = typeof meta['id'] === 'string' ? meta['id'] as string : undefined
244
344
  const kind = meta['kind']
245
- if( !id || ( kind !== 'composite' && kind !== 'primitive' ) ) return undefined
345
+ if( !id || ( kind !== 'composite' && kind !== 'primitive') ) return undefined
246
346
  return {
247
347
  id,
248
348
  kind,