@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
@@ -37,6 +37,9 @@ import type { CognitiveEventSchema } from '#cognition/schema.registry'
37
37
  import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
38
38
  import type { Percept } from '#senses/index'
39
39
  import { GenerativeModel } from '#cognition/generative.model'
40
+ import {
41
+ isReferentId, mintReferentId, canonicalOf, withHandle, type Handle,
42
+ } from '#cognition/social.identity'
40
43
  import { readEffectiveParams } from '#cognition/persona.prior'
41
44
 
42
45
  export interface KnownEntityTrackerConfig {
@@ -71,6 +74,31 @@ export interface KnownEntity {
71
74
  lastSeenTick: Tick
72
75
  /** 0–1: how identified/coherent this referent is (a name + repeated encounters raise it). */
73
76
  resolutionConfidence: number
77
+ /**
78
+ * The ways this referent has been reachable, with the circumstances.
79
+ *
80
+ * Distinct from an ALIAS, and the distinction is load-bearing: an alias is
81
+ * another NAME for the same referent (a transport user id), a handle is a
82
+ * PLACE it can be reached (a thread, a room). One person has one identity and
83
+ * several rooms, and conflating them is what made "which room should I say
84
+ * this in?" unaskable.
85
+ */
86
+ handles: Handle[]
87
+ /**
88
+ * Referents this one MIGHT be the same someone as, unresolved.
89
+ *
90
+ * A blocked merge used to vanish. `_recognise` will only absorb a THIN handle
91
+ * into an established relationship — rightly, because fusing two real people
92
+ * who share a name is the dangerous direction — so once the same human was
93
+ * well-established on two channels they stayed two people permanently, and
94
+ * nothing anywhere recorded the near-miss.
95
+ *
96
+ * A person does not silently fail here. They NOTICE — "hang on, is this the
97
+ * same Mara?" — and then resolve it by asking. So the doubt is kept, shown, and
98
+ * left for the mind to settle. Deliberately not a merge: this is a question,
99
+ * and the answer is the mind's to give.
100
+ */
101
+ suspectedSameAs?: string[]
74
102
  }
75
103
 
76
104
  /** Percept domains whose entities are minds. Everything else defaults to a thing. */
@@ -80,11 +108,33 @@ const SENTIENT_DOMAINS = new Set<string>([ 'audition' ])
80
108
  // specific "get to know them" pull; it subsides once resolution reaches RESOLVED.
81
109
  const CURIOUS_FAMILIARITY = 0.5
82
110
  const CURIOUS_RESOLUTION = 0.4
83
- const CURIOUS_RESOLVED = 0.6
111
+ /**
112
+ * Resolved enough to stop wondering — the point the curiosity goal completes at.
113
+ *
114
+ * Exported because the AffordanceSynthesizer offers `inspect` against referents
115
+ * BELOW it. Two readers, one constant, deliberately: if the act that satisfies
116
+ * curiosity and the goal that measures it disagreed about when something is
117
+ * known, a mind could keep being offered a look at what it had already placed —
118
+ * or stop being offered one while the goal it holds is still open.
119
+ */
120
+ export const CURIOUS_RESOLVED = 0.6
84
121
 
85
122
  // Forgetting (Phase 4). (Reliability rate is now a Channel-A developable field — analytical.)
86
123
  const FORGET_FLOOR = 0.02 // below this familiarity, an unidentified blip is forgotten
87
124
 
125
+ /**
126
+ * Familiarity decay for a referent the mind has NOT resolved — no name, barely
127
+ * identified. Two orders of magnitude faster than for a known someone, because
128
+ * one rate cannot serve both: a face glimpsed once in a crowd should fade in
129
+ * minutes, and a colleague should not.
130
+ *
131
+ * The forgetting gate already drew exactly this line (`!d.name &&
132
+ * resolutionConfidence < CURIOUS_RESOLUTION`); only the rate did not, so slowing
133
+ * decay enough for relationships to exist would have left every blip resident
134
+ * forever. Same value the single shared rate used to have.
135
+ */
136
+ const BLIP_DECAY_RATE = 0.005
137
+
88
138
  // Recognition (Phase 5) — guards against conflating two *different* people who share a
89
139
  // name. Only recognise a still-thin handle (≤) into a known person, and never when both
90
140
  // were active at the same time (two people talking at once ⇒ distinct, not one on two
@@ -109,18 +159,52 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
109
159
  private _restored = false
110
160
 
111
161
  // Buffered from senses.*.percept events; drained each tick in react().
112
- private _pendingEncounters: Array<{ keid: string; domain: string; name?: string }> = []
162
+ private _pendingEncounters: Array<{
163
+ keid: string; domain: string; name?: string
164
+ /** The room this encounter happened in, and whether it was a private one. */
165
+ thread?: string; direct?: boolean
166
+ /** What that room is called, where the channel offered a label. */
167
+ threadName?: string
168
+ }> = []
113
169
  // Buffered from known.entity.learned (the conscious / reasoning write-path, Phase 2.2).
114
- private _pendingConscious: Array<{ keid: string; name?: string; feeling?: number }> = []
170
+ private _pendingConscious: Array<{
171
+ keid: string; name?: string; feeling?: number
172
+ /** "This is the same someone as that." The mind settling an identity itself. */
173
+ sameAs?: string
174
+ }> = []
115
175
  // Buffered from action.outcome — the reliability track-record signal (Phase 4).
116
176
  private _pendingOutcomes: Array<{ keid: string; signal: number }> = []
117
177
 
178
+ /**
179
+ * Aliases minted this tick, awaiting persistence. `_getOrCreate` runs deep inside
180
+ * the drain loops with no access to the command list, and an alias that lives
181
+ * only in memory is one the mind forgets on restart — every transport address
182
+ * would mint a SECOND anchor next boot and the person would fork in two.
183
+ */
184
+ private _mintedAliases: Array<{ alias: string; canonical: string }> = []
185
+
118
186
  private _bus: CognitiveBus | null = null
119
187
  private readonly _model = new GenerativeModel()
120
188
 
121
189
  constructor( config: KnownEntityTrackerConfig = {} ){
122
190
  this._growthRate = config.familiarityGrowthRate ?? 0.15
123
- this._decayRate = config.familiarityDecayRate ?? 0.005
191
+ // 0.005/tick spends the entire scale in 200 ticks — under four minutes at the
192
+ // 1s tick a hosted Will actually runs on. Familiarity could therefore never
193
+ // accumulate for a mind whose encounters are minutes apart, and it did not:
194
+ // measured 0.00 on a fresh mind after 5 encounters, and 0.00 on a mind that
195
+ // had banked 71. A permanently-zero signal is worse than a missing one — it
196
+ // feeds the curiosity-to-resolve drive (which therefore never fired) and the
197
+ // pruning order, both of which read it as "a total stranger".
198
+ //
199
+ // Mere exposure fades over days, not minutes. At 0.00002 a full scale takes
200
+ // ~50k ticks (~14h at 1s), so someone met this morning is still familiar
201
+ // tonight and a stranger from last month has genuinely faded.
202
+ //
203
+ // The deeper flaw stays: this is per-TICK, so a psychological timescale is
204
+ // pinned to a host's tick rate. `react()` already receives the real delta —
205
+ // expressing decay in experienced time is the actual fix, and it belongs with
206
+ // the other per-tick decays rather than in this one engine alone.
207
+ this._decayRate = config.familiarityDecayRate ?? 0.00002
124
208
  this._curiosityGain = config.curiosityGain ?? 1.0
125
209
  this._reliabilityRate = config.reliabilityRate ?? 0.2
126
210
  this._maxTracked = config.maxTracked ?? 50
@@ -150,9 +234,9 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
150
234
 
151
235
  // Conscious learning (the reasoning write-path): a learned name + felt valence.
152
236
  if( e.type === 'known.entity.learned'){
153
- const u = e.payload as { keid?: string; name?: string; feeling?: number }
237
+ const u = e.payload as { keid?: string; name?: string; feeling?: number; sameAs?: string }
154
238
  if( u?.keid && u.keid !== 'agent-self')
155
- this._pendingConscious.push({ keid: u.keid, name: u.name, feeling: u.feeling })
239
+ this._pendingConscious.push({ keid: u.keid, name: u.name, feeling: u.feeling, sameAs: u.sameAs })
156
240
  return
157
241
  }
158
242
 
@@ -170,10 +254,19 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
170
254
  if( !keid || keid === 'agent-self') return
171
255
 
172
256
  // A channel-supplied display name rides on the raw input (e.g. TextMessage.speakerName).
173
- const raw = p?.raw as { speakerName?: unknown } | undefined
257
+ // A channel supplies the display name AND the room, both on the raw input.
258
+ // `direct` is the one bit the Discord edge computed (`isDM`) and threw away
259
+ // before the mind could see it — the single fact that decides whether a room
260
+ // is the right place for a given utterance.
261
+ const raw = p?.raw as { speakerName?: unknown; threadId?: unknown; direct?: unknown; threadName?: unknown } | undefined
174
262
  const name = typeof raw?.speakerName === 'string' ? raw.speakerName : undefined
263
+ const thread = typeof raw?.threadId === 'string' ? raw.threadId : undefined
264
+ const direct = typeof raw?.direct === 'boolean' ? raw.direct : undefined
265
+ // What the room is CALLED. A place has had a dossier since 0.9.0 and no way
266
+ // to be named, so every room the mind knew rendered as "something".
267
+ const threadName = typeof raw?.threadName === 'string' ? raw.threadName : undefined
175
268
 
176
- this._pendingEncounters.push({ keid, domain: p!.domain, name })
269
+ this._pendingEncounters.push({ keid, domain: p!.domain, name, thread, direct, threadName })
177
270
  }
178
271
 
179
272
  snapshot(): Record<string, unknown> {
@@ -200,8 +293,10 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
200
293
 
201
294
  // Familiarity fades a little each tick — absence dims the sense of an entity.
202
295
  for( const d of this._dossiers.values() )
203
- if( d.familiarity > 0 )
204
- d.familiarity = Math.max( 0, d.familiarity - this._decayRate )
296
+ if( d.familiarity > 0 ){
297
+ const unresolved = !d.name && d.resolutionConfidence < CURIOUS_RESOLUTION
298
+ d.familiarity = Math.max( 0, d.familiarity - ( unresolved ? BLIP_DECAY_RATE : this._decayRate ) )
299
+ }
205
300
 
206
301
  // Drain encounters — each percept is an exposure that re-warms familiarity.
207
302
  let touched = false
@@ -210,6 +305,42 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
210
305
  d.encounterCount += 1
211
306
  d.familiarity = Math.min( 1, d.familiarity + this._growthRate * ( 1 - d.familiarity ) )
212
307
  d.lastSeenTick = tick
308
+ // Where this happened is part of what happened. Merged, not appended:
309
+ // meeting someone again in a room already known is news about that room,
310
+ // not a new way to reach them.
311
+ if( enc.thread ){
312
+ d.handles = withHandle( d.handles, {
313
+ keid: enc.thread,
314
+ kind: enc.direct === true ? 'dm' : enc.direct === false ? 'room' : 'unknown',
315
+ lastSeenTick: tick,
316
+ } as Handle )
317
+
318
+ // The ROOM is a referent too, and gets a dossier of its own.
319
+ //
320
+ // `kind: 'thing'` has existed since this tracker shipped and nothing had
321
+ // ever created one — SENTIENT_DOMAINS admits only audition, so the seat
322
+ // for a non-person was built and left empty. A place earns exactly the
323
+ // same things a person does: familiarity with the room, a felt valence
324
+ // toward it, and a reliability that means something real — whether the
325
+ // mind gets answered there. A shared room nobody replies in is a fact
326
+ // about the ROOM as much as about the people in it, and until now there
327
+ // was nowhere to put it.
328
+ //
329
+ // Private threads are deliberately excluded: a DM is not a place, it is
330
+ // the person. Giving it a dossier would double every someone.
331
+ if( enc.direct === false ){
332
+ const place = this._getOrCreate( enc.thread, 'place', tick )
333
+ place.encounterCount += 1
334
+ place.familiarity = Math.min( 1, place.familiarity + this._growthRate * ( 1 - place.familiarity ) )
335
+ place.lastSeenTick = tick
336
+ // A room learns its name the same way a person does — from what the
337
+ // channel offers, and only while it has none. Re-taken every encounter
338
+ // it would churn the cached prompt on a rename; more to the point, a
339
+ // name the mind already holds is the mind's, not the platform's.
340
+ if( enc.threadName && !place.name ) place.name = enc.threadName
341
+ place.resolutionConfidence = this._resolution( place )
342
+ }
343
+ }
213
344
  if( enc.name && !d.name ) d.name = enc.name // learn a name the channel offers
214
345
  d.resolutionConfidence = this._resolution( d )
215
346
  touched = true
@@ -219,6 +350,16 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
219
350
  // toward, not slammed). A known someone can exist here with no perceptual encounter.
220
351
  for( const u of this._pendingConscious.splice( 0 ) ){
221
352
  const d = this._getOrCreate( u.keid, 'audition', tick ) // a known *someone*
353
+
354
+ // "These two are the same person." The mind settling a doubt the heuristic
355
+ // was not entitled to settle — it has reasons a name-match does not, usually
356
+ // because somebody just told it. Allowed to fuse two ESTABLISHED referents,
357
+ // which is exactly what `_recognise` refuses to do on its own.
358
+ if( u.sameAs ){
359
+ const other = this._dossiers.get( canonicalOf( this._aliases, u.sameAs ) )
360
+ if( other && other.keid !== d.keid ) this._fuse( d, other, commands )
361
+ }
362
+
222
363
  if( u.name ) d.name = u.name
223
364
  if( u.feeling != null )
224
365
  d.valence = Math.max( -1, Math.min( 1, d.valence + 0.5 * ( u.feeling - d.valence ) ) )
@@ -230,7 +371,12 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
230
371
  // performs when acted on (general: a tool, a place, or a person). Only updates an
231
372
  // already-known referent (acting on something you've never perceived doesn't conjure it).
232
373
  for( const o of this._pendingOutcomes.splice( 0 ) ){
233
- const d = this._dossiers.get( o.keid )
374
+ // Through the alias table: an `action.outcome` names its target the way the
375
+ // ACTOR did — a transport address, or whatever the host called it — and the
376
+ // dossier lives under the anchor. Reading `_dossiers` raw here silently
377
+ // dropped every outcome once addresses became aliases, so a referent could
378
+ // never earn or lose reliability again.
379
+ const d = this._dossiers.get( canonicalOf( this._aliases, o.keid ) )
234
380
  if( !d ) continue
235
381
  d.reliability = Math.max( 0, Math.min( 1, d.reliability + this._reliabilityRate * ( o.signal - d.reliability ) ) )
236
382
  touched = true
@@ -255,6 +401,11 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
255
401
  commands.delete!.push(`ke-${d.keid}`)
256
402
  }
257
403
 
404
+ // Persist anchors minted this tick BEFORE the dossiers that depend on them.
405
+ for( const { alias, canonical } of this._mintedAliases.splice( 0 ) )
406
+ commands.set!.push({ id: `kea-${ alias }`, type: 'known-entity-alias',
407
+ metadata: { aliasKeid: alias, canonicalKeid: canonical } })
408
+
258
409
  // Persist dossiers (the perceptual layer of the known-entity node).
259
410
  for( const d of this._dossiers.values() ){
260
411
  if( d.encounterCount === 0 && !d.name ) continue // keep a named-but-unseen someone
@@ -271,6 +422,8 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
271
422
  encounterCount: d.encounterCount,
272
423
  lastSeenTick: d.lastSeenTick,
273
424
  resolutionConfidence: d.resolutionConfidence,
425
+ handles: d.handles,
426
+ ...( d.suspectedSameAs?.length ? { suspectedSameAs: d.suspectedSameAs } : {} ),
274
427
  },
275
428
  })
276
429
  }
@@ -329,10 +482,66 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
329
482
  // ── Public API ───────────────────────────────────────────
330
483
 
331
484
  /** The dossier for a referent, if the Will has one. */
332
- getDossier( keid: string ): KnownEntity | undefined { return this._dossiers.get( keid ) }
485
+ /**
486
+ * The dossier for anything that names this referent — its anchor, or any address
487
+ * it has been met at.
488
+ *
489
+ * Alias-aware because a caller has no business knowing the anchor. This read
490
+ * `_dossiers.get(keid)` raw, so the moment addresses became aliases of a minted
491
+ * anchor, every caller holding a transport id got `undefined` — the mind would
492
+ * have looked up someone it knows perfectly well and found a stranger. The
493
+ * existing Phase-4 tests caught it, which is exactly what they are for.
494
+ */
495
+ getDossier( keid: string ): KnownEntity | undefined {
496
+ return this._dossiers.get( canonicalOf( this._aliases, keid ) )
497
+ }
333
498
 
334
499
  // ── Internal ─────────────────────────────────────────────
335
500
 
501
+ /**
502
+ * Absorb `alias` into `canon` — one someone where there were two.
503
+ *
504
+ * Shared by the recognition heuristic and by the mind's own `sameAs` verdict,
505
+ * because "these are the same person" must mean the same thing whichever
506
+ * concluded it. The only difference is who is ENTITLED to conclude it: the
507
+ * heuristic will not absorb an established relationship, the mind may, because
508
+ * it has reasons a name-match does not — usually that somebody just told it.
509
+ *
510
+ * Routes MOVE. This used to delete the absorbed dossier and keep only a
511
+ * redirect, so the mind concluded "same person" and in the same breath threw
512
+ * away the second way to reach them.
513
+ */
514
+ private _fuse( canon: KnownEntity, alias: KnownEntity, commands: StateCommands ): void {
515
+ canon.encounterCount += alias.encounterCount
516
+ canon.familiarity = Math.max( canon.familiarity, alias.familiarity )
517
+ canon.resolutionConfidence = Math.max( canon.resolutionConfidence, alias.resolutionConfidence )
518
+ canon.lastSeenTick = Math.max( canon.lastSeenTick as unknown as number, alias.lastSeenTick as unknown as number ) as unknown as Tick
519
+ canon.valence = ( canon.valence + alias.valence ) / 2
520
+ canon.reliability = ( canon.reliability + alias.reliability ) / 2
521
+ canon.name ??= alias.name
522
+ for( const h of alias.handles ) canon.handles = withHandle( canon.handles, h )
523
+
524
+ // The doubt is answered — on both sides, and about each other only. A
525
+ // suspicion left standing after the fusion would have the mind keep wondering
526
+ // about a question it has already settled.
527
+ const settled = ( held: string[] | undefined ): string[] | undefined => {
528
+ const out = ( held ?? [] ).filter( k => k !== alias.keid && k !== canon.keid )
529
+ return out.length > 0 ? out : undefined
530
+ }
531
+ canon.suspectedSameAs = settled([ ...( canon.suspectedSameAs ?? [] ), ...( alias.suspectedSameAs ?? [] ) ])
532
+
533
+ // Every address that pointed at the absorbed referent must now point here, or
534
+ // the next message from one of them mints the person all over again.
535
+ for( const [ a, c ] of this._aliases )
536
+ if( c === alias.keid ) this._aliases.set( a, canon.keid )
537
+
538
+ this._dossiers.delete( alias.keid )
539
+ this._aliases.set( alias.keid, canon.keid )
540
+ commands.delete!.push(`ke-${ alias.keid }`)
541
+ commands.set!.push({ id: `kea-${ alias.keid }`, type: 'known-entity-alias',
542
+ metadata: { aliasKeid: alias.keid, canonicalKeid: canon.keid } })
543
+ }
544
+
336
545
  /** Resolution confidence: a learned name plus repeated encounters identify a referent. */
337
546
  private _resolution( d: KnownEntity ): number {
338
547
  return Math.min( 1, ( d.name ? 0.4 : 0 ) + Math.min( 0.6, d.encounterCount * 0.05 ) )
@@ -361,34 +570,61 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
361
570
  // handle is already an established relationship, or if both were active concurrently
362
571
  // (two interlocutors at once ⇒ distinct). A same-name match alone is too weak.
363
572
  const gap = Math.abs( ( canon.lastSeenTick as unknown as number ) - ( alias.lastSeenTick as unknown as number ) )
364
- if( alias.encounterCount >= RECOGNITION_MERGE_MAX_ENCOUNTERS || gap < RECOGNITION_CONCURRENCY_WINDOW )
573
+
574
+ // Concurrency is evidence AGAINST: two people talking at once are two
575
+ // people, and there is nothing to wonder about. Say nothing.
576
+ if( gap < RECOGNITION_CONCURRENCY_WINDOW ) continue
577
+
578
+ // Establishment is merely insufficient evidence FOR. The two may well be
579
+ // one someone; the heuristic is just not entitled to decide it, because
580
+ // absorbing an established relationship would take a real person's
581
+ // history with them. So the mind is told, and gets to settle it itself —
582
+ // usually the way anyone would, by asking.
583
+ if( alias.encounterCount >= RECOGNITION_MERGE_MAX_ENCOUNTERS ){
584
+ for( const [ a, b ] of [ [ canon, alias ], [ alias, canon ] ] as const ){
585
+ const held = a.suspectedSameAs ?? []
586
+ if( !held.includes( b.keid ) ) a.suspectedSameAs = [ ...held, b.keid ].sort()
587
+ }
588
+ merged = true // the doubt is a change worth persisting
365
589
  continue
590
+ }
366
591
 
367
- canon.encounterCount += alias.encounterCount
368
- canon.familiarity = Math.max( canon.familiarity, alias.familiarity )
369
- canon.resolutionConfidence = Math.max( canon.resolutionConfidence, alias.resolutionConfidence )
370
- canon.lastSeenTick = Math.max( canon.lastSeenTick as unknown as number, alias.lastSeenTick as unknown as number ) as unknown as Tick
371
- canon.valence = ( canon.valence + alias.valence ) / 2
372
- canon.reliability = ( canon.reliability + alias.reliability ) / 2
373
-
374
- this._dossiers.delete( alias.keid )
375
- this._aliases.set( alias.keid, canon.keid )
376
- commands.delete!.push(`ke-${alias.keid}`)
377
- commands.set!.push({ id: `kea-${alias.keid}`, type: 'known-entity-alias',
378
- metadata: { aliasKeid: alias.keid, canonicalKeid: canon.keid } })
592
+ this._fuse( canon, alias, commands )
379
593
  merged = true
380
594
  }
381
595
  }
382
596
  return merged
383
597
  }
384
598
 
599
+ /**
600
+ * The dossier for a referent, minting its anchor the first time it is met.
601
+ *
602
+ * A transport address arriving here (`discord:1019…`) is not an identity, it is
603
+ * a way the world happened to name someone. So it becomes an ALIAS of a fresh
604
+ * `ke:` anchor, and the dossier lives under the anchor. Everything downstream —
605
+ * reputation, theory-of-mind, attachment, goals, the PMA — keeps working
606
+ * untouched: it still sees one opaque string per referent, which is simply no
607
+ * longer a route. The same human met later on another channel resolves to this
608
+ * same anchor instead of becoming a second person nobody could connect.
609
+ */
385
610
  private _getOrCreate( keid: string, domain: string, tick: Tick ): KnownEntity {
386
- keid = this._aliases.get( keid ) ?? keid // a recognised alias lands on the canonical dossier
611
+ keid = canonicalOf( this._aliases, keid ) // an alias lands on the canonical dossier
387
612
  const existing = this._dossiers.get( keid )
388
613
  if( existing ) return existing
389
614
 
615
+ // Mint the anchor for a transport address seen for the first time. Deterministic
616
+ // from that address, so a replay of a recorded run mints the same id (R2).
617
+ let anchor = keid
618
+ if( !isReferentId( keid ) ){
619
+ anchor = mintReferentId( keid )
620
+ this._aliases.set( keid, anchor )
621
+ this._mintedAliases.push({ alias: keid, canonical: anchor })
622
+ const already = this._dossiers.get( anchor )
623
+ if( already ) return already
624
+ }
625
+
390
626
  const d: KnownEntity = {
391
- keid,
627
+ keid: anchor,
392
628
  kind: SENTIENT_DOMAINS.has( domain ) ? 'sentient' : 'thing',
393
629
  familiarity: 0,
394
630
  valence: 0,
@@ -396,15 +632,35 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
396
632
  encounterCount: 0,
397
633
  lastSeenTick: tick,
398
634
  resolutionConfidence: 0,
635
+ handles: [],
399
636
  }
400
- this._dossiers.set( keid, d )
637
+ this._dossiers.set( anchor, d )
401
638
  return d
402
639
  }
403
640
 
404
641
  /** Keep the most-familiar dossiers; absence-faded acquaintances fall away (forgetting). */
642
+ /**
643
+ * Forget the least-held referents when over capacity.
644
+ *
645
+ * Ranked by more than exposure, deliberately. This sorted on `familiarity`
646
+ * alone, which is MERE EXPOSURE — and now that a referent need not be a person
647
+ * (a document, a repo, a room), things get far more exposure than people do. A
648
+ * mind that touched sixty files would have evicted a colleague it speaks to
649
+ * weekly in favour of a config file it opened a lot, silently, taking that
650
+ * person's reputation, theory-of-mind model and attachment bond with it.
651
+ *
652
+ * So a referent the mind has actually got to know is stickier than one it has
653
+ * merely seen often: knowing their NAME is the single strongest signal (it is
654
+ * what distinguishes a someone from a blip), then how resolved the referent is,
655
+ * then exposure. Nothing here is about being a person — a named, well-resolved
656
+ * document outranks a glimpsed stranger, which is correct.
657
+ */
405
658
  private _prune(): void {
406
659
  if( this._dossiers.size <= this._maxTracked ) return
407
- const sorted = [ ...this._dossiers.values() ].sort( ( a, b ) => b.familiarity - a.familiarity )
660
+ const hold = ( d: KnownEntity ): number =>
661
+ ( d.name ? 1 : 0 ) + d.resolutionConfidence + d.familiarity
662
+ const sorted = [ ...this._dossiers.values() ]
663
+ .sort( ( a, b ) => hold( b ) - hold( a ) || ( a.keid < b.keid ? -1 : 1 ) )
408
664
  for( const d of sorted.slice( this._maxTracked ) ) this._dossiers.delete( d.keid )
409
665
  }
410
666
 
@@ -430,6 +686,8 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
430
686
  familiarity: ( m['familiarity'] as number ) ?? 0,
431
687
  valence: ( m['valence'] as number ) ?? 0,
432
688
  reliability: ( m['reliability'] as number ) ?? 0.5,
689
+ handles: Array.isArray( m['handles'] ) ? ( m['handles'] as Handle[] ) : [],
690
+ ...( Array.isArray( m['suspectedSameAs'] ) ? { suspectedSameAs: m['suspectedSameAs'] as string[] } : {} ),
433
691
  encounterCount: ( m['encounterCount'] as number ) ?? 0,
434
692
  lastSeenTick: ( m['lastSeenTick'] as Tick ) ?? ( 0 as unknown as Tick ),
435
693
  resolutionConfidence: ( m['resolutionConfidence'] as number ) ?? 0,
@@ -308,8 +308,13 @@ export class MoralEvaluator implements SimulationEngine, CognitiveEngine {
308
308
  })
309
309
  }
310
310
 
311
- // Own actions that could be violations
312
- if( entity.type === 'action.own' || entity.type === 'decision.record'){
311
+ // Own actions that could be violations. `decision.record` is what carries
312
+ // them (InhibitionController writes it); an `action.own` alternative was
313
+ // scanned alongside it and never written by anything (#114). Unlike the
314
+ // social/threat host seams, this is an INTERNAL record of the mind's own
315
+ // conduct — not something a host supplies — so the dead half is removed
316
+ // rather than documented.
317
+ if( entity.type === 'decision.record'){
313
318
  const
314
319
  outcome = entity.metadata?.outcome as string ?? '',
315
320
  harmful = entity.metadata?.harmful === true,
@@ -386,7 +391,7 @@ export class MoralEvaluator implements SimulationEngine, CognitiveEngine {
386
391
  let actionCount = 0
387
392
 
388
393
  for( const entity of state.entities.values() ){
389
- if( entity.type !== 'action.own' && entity.type !== 'decision.record') continue
394
+ if( entity.type !== 'decision.record') continue
390
395
 
391
396
  const
392
397
  virtuous = entity.metadata?.virtuous === true,
@@ -135,6 +135,39 @@ const CONSCIENTIOUSNESS_MORAL_GAIN = -0.2 // −moral.eventThreshold per unit
135
135
  // native scales — see the selector's effectiveSwitchCost. Lower gain to match the smaller base.
136
136
  const CONSCIENTIOUSNESS_SELECTOR_FOCUS_GAIN = 0.1 // +action-selector.switchCost per unit conscientiousness (resists action-interruption)
137
137
 
138
+ // Attentional BREADTH — how many focused facets this mind holds at once
139
+ // (engine-config-executive.maxFacets, the FacetSupervisor's ceiling). Two opposing
140
+ // dispositional pulls on one param, the pattern rules 17/17b already use: an open
141
+ // mind spreads itself across more at once, a conscientious one narrows to fewer and
142
+ // sees them through. Scaled against a base of 10, so a strongly-expressed trait moves
143
+ // the ceiling by a few threads, not by an order of magnitude — and consolidatePrior's
144
+ // per-step and cumulative caps bound it regardless.
145
+ const OPENNESS_BREADTH_GAIN = 4 // +executive.maxFacets per unit openness (hold more at once)
146
+ const CONSCIENTIOUSNESS_BREADTH_GAIN = -3 // −executive.maxFacets per unit conscientiousness (fewer, seen through)
147
+
148
+ // The same breadth disposition at the ATTENDING level — how many things the mind
149
+ // holds in view at once (engine-config-attention.maxFoci, the allocator's slot cap),
150
+ // as opposed to how many threads it keeps open (maxFacets above). One disposition,
151
+ // two owners — rules 28/28b's pattern. Scaled against a base of 4, so the same trait
152
+ // moves both levels proportionally rather than pulling them apart.
153
+ const OPENNESS_FOCI_GAIN = 1.5 // +attention.maxFoci per unit openness
154
+ const CONSCIENTIOUSNESS_FOCI_GAIN = -1.2 // −attention.maxFoci per unit conscientiousness
155
+
156
+ // How long the Will sits with something it has already said before saying it again
157
+ // (action-selector.repeatDamping, EXAFFERENCE P5). Agreeableness gives people room;
158
+ // demonstrated persistence chases the answer. Scaled against a base of 0.30.
159
+ const AGREEABLENESS_PATIENCE_GAIN = 0.12 // +repeatDamping per unit agreeableness
160
+ const PERSISTENCE_FOLLOWUP_GAIN = -0.10 // −repeatDamping per unit persistence
161
+ // The same disposition on the WINDOW rather than the strength: how long what it
162
+ // said stands before coming back to it. Scaled against a base of 60 ticks.
163
+ const AGREEABLENESS_WINDOW_GAIN = 24 // +repeatWindowTicks per unit agreeableness
164
+ const PERSISTENCE_WINDOW_GAIN = -20 // −repeatWindowTicks per unit persistence
165
+ // Who the mind is drawn toward. Agreeableness leans into reciprocity (toward the
166
+ // people who answer); demonstrated persistence leans the other way, and can carry
167
+ // this SIGNED weight negative — a mind that reaches hardest for the silence.
168
+ const AGREEABLENESS_RECIPROCITY_GAIN = 0.12 // +action-selector.socialWeight per unit agreeableness
169
+ const PERSISTENCE_RECIPROCITY_GAIN = -0.14 // −action-selector.socialWeight per unit persistence
170
+
138
171
  // Emotional stability (formed from observed affect dynamics, not task success) develops
139
172
  // the affect *reactivity gain* down: a steadier Will lets frustration snowball into
140
173
  // chronic irritability more slowly. Distinct axis from resilience — resilience tunes how
@@ -346,6 +379,11 @@ export class PersonaConsolidator implements SimulationEngine, CognitiveEngine {
346
379
  [ 'persona.executive.impulsivity_delta', next.priors[ 'engine-config-executive' ]?.impulsivity ?? 0 ],
347
380
  [ 'persona.frustration.irritability_rate_delta', next.priors[ 'engine-config-frustration' ]?.irritabilityRate ?? 0 ],
348
381
  [ 'persona.executive.deliberate_threshold_delta', next.priors[ 'engine-config-executive' ]?.deliberateThreshold ?? 0 ],
382
+ [ 'persona.executive.max_facets_delta', next.priors[ 'engine-config-executive' ]?.maxFacets ?? 0 ],
383
+ [ 'persona.attention.max_foci_delta', next.priors[ 'engine-config-attention' ]?.maxFoci ?? 0 ],
384
+ [ 'persona.selector.repeat_damping_delta', next.priors[ 'engine-config-action-selector' ]?.repeatDamping ?? 0 ],
385
+ [ 'persona.selector.repeat_window_delta', next.priors[ 'engine-config-action-selector' ]?.repeatWindowTicks ?? 0 ],
386
+ [ 'persona.selector.social_weight_delta', next.priors[ 'engine-config-action-selector' ]?.socialWeight ?? 0 ],
349
387
  [ 'persona.reward.social_weight_delta', next.priors[ 'engine-config-reward' ]?.socialWeight ?? 0 ],
350
388
  [ 'persona.frustration.anger_reactivity_delta', next.priors[ 'engine-config-frustration' ]?.angerReactivity ?? 0 ],
351
389
  [ 'persona.novelty.significance_threshold_delta', next.priors[ 'engine-config-novelty' ]?.significanceThreshold ?? 0 ],
@@ -730,6 +768,109 @@ export class PersonaConsolidator implements SimulationEngine, CognitiveEngine {
730
768
  engineConfigId: 'engine-config-task-switcher',
731
769
  param: 'baseSwitchCost'
732
770
  },
771
+ // 27c. Attentional BREADTH — how many things this mind holds at once (the
772
+ // FacetSupervisor's facet ceiling). Openness widens it; conscientiousness
773
+ // narrows it. Two opposing pulls on one param (rules 17/17b's pattern), so a
774
+ // Will that is both lands somewhere of its own rather than at either extreme.
775
+ // This is what makes "I can hold five conversations" or "I do one thing at a
776
+ // time" a fact about the person instead of a constant — the ceiling is the
777
+ // persona's; spare attention only scales the live allowance within it.
778
+ {
779
+ magnitude: openDev,
780
+ threshold: GRIT_THRESHOLD,
781
+ gain: OPENNESS_BREADTH_GAIN,
782
+ engineConfigId: 'engine-config-executive',
783
+ param: 'maxFacets'
784
+ },
785
+ {
786
+ magnitude: conscDev,
787
+ threshold: GRIT_THRESHOLD,
788
+ gain: CONSCIENTIOUSNESS_BREADTH_GAIN,
789
+ engineConfigId: 'engine-config-executive',
790
+ param: 'maxFacets'
791
+ },
792
+ // 27d. The same breadth disposition one level down: how many things the mind
793
+ // holds IN VIEW at once (the allocator's `maxFoci` slots), as against how
794
+ // many threads it keeps OPEN (27c). A reasoning facet now competes for
795
+ // these slots like any percept, so the two levels are one economy — a Will
796
+ // can be in ten conversations while attending to two, and which two is
797
+ // settled by salience against everything else it could be noticing.
798
+ // `maxFoci` was already config-mirrored and read through the persona-prior;
799
+ // it just had no description and no edge, so it could never actually move.
800
+ {
801
+ magnitude: openDev,
802
+ threshold: GRIT_THRESHOLD,
803
+ gain: OPENNESS_FOCI_GAIN,
804
+ engineConfigId: 'engine-config-attention',
805
+ param: 'maxFoci'
806
+ },
807
+ {
808
+ magnitude: conscDev,
809
+ threshold: GRIT_THRESHOLD,
810
+ gain: CONSCIENTIOUSNESS_FOCI_GAIN,
811
+ engineConfigId: 'engine-config-attention',
812
+ param: 'maxFoci'
813
+ },
814
+ // 27e. How long the Will lets its own words stand before repeating them. A
815
+ // delivered act leaves a live footprint (EXAFFERENCE P5) that damps redoing
816
+ // it; how HARD it damps is a disposition. An agreeable Will gives people
817
+ // room after saying its piece; a persistent one chases the answer sooner.
818
+ // Opposing pulls on one param, same as 17/17b. This is the trait behind
819
+ // "she messaged me the same thing three times" — the mechanism stops the
820
+ // loop, this decides where in the range between patient and dogged she sits.
821
+ {
822
+ magnitude: agreeableDev,
823
+ threshold: GRIT_THRESHOLD,
824
+ gain: AGREEABLENESS_PATIENCE_GAIN,
825
+ engineConfigId: 'engine-config-action-selector',
826
+ param: 'repeatDamping'
827
+ },
828
+ {
829
+ magnitude: persistDev,
830
+ threshold: GRIT_THRESHOLD,
831
+ gain: PERSISTENCE_FOLLOWUP_GAIN,
832
+ engineConfigId: 'engine-config-action-selector',
833
+ param: 'repeatDamping'
834
+ },
835
+ // 27f. The same patience on the satiation WINDOW rather than its strength —
836
+ // 27e decides how hard having spoken damps speaking again, this decides
837
+ // for how long. Kept separate because a mind can be quick to repeat but
838
+ // only briefly, or slow to repeat but for a long time.
839
+ {
840
+ magnitude: agreeableDev,
841
+ threshold: GRIT_THRESHOLD,
842
+ gain: AGREEABLENESS_WINDOW_GAIN,
843
+ engineConfigId: 'engine-config-action-selector',
844
+ param: 'repeatWindowTicks'
845
+ },
846
+ {
847
+ magnitude: persistDev,
848
+ threshold: GRIT_THRESHOLD,
849
+ gain: PERSISTENCE_WINDOW_GAIN,
850
+ engineConfigId: 'engine-config-action-selector',
851
+ param: 'repeatWindowTicks'
852
+ },
853
+ // 27g. Who the Will is drawn toward. `socialWeight` scales its learned read on
854
+ // a person in the action competition, and it is the one weight left
855
+ // deliberately SIGNED: agreeableness leans into reciprocity (toward the
856
+ // people who answer), demonstrated persistence leans against it and can
857
+ // carry the weight negative — a mind that reaches hardest for the silence
858
+ // precisely because it is silent. Both are coherent colleagues, so the
859
+ // container declines to pick and lets the persona land where it lands.
860
+ {
861
+ magnitude: agreeableDev,
862
+ threshold: GRIT_THRESHOLD,
863
+ gain: AGREEABLENESS_RECIPROCITY_GAIN,
864
+ engineConfigId: 'engine-config-action-selector',
865
+ param: 'socialWeight'
866
+ },
867
+ {
868
+ magnitude: persistDev,
869
+ threshold: GRIT_THRESHOLD,
870
+ gain: PERSISTENCE_RECIPROCITY_GAIN,
871
+ engineConfigId: 'engine-config-action-selector',
872
+ param: 'socialWeight'
873
+ },
733
874
  // 28b. Same disposition, the AGENCY selector's owner (R2): a conscientious Will also
734
875
  // resists having an in-flight action preempted — raise the selector's switch-cost
735
876
  // hysteresis. Shares the conscientiousness driver with rule 28 (one switch-resistance