@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
package/src/pma/eval.ts CHANGED
@@ -368,7 +368,7 @@ export class PMAEvalHarness {
368
368
  let loadedTraits: Record<string, number> = {}
369
369
 
370
370
  for( const entity of state.entities.values() ){
371
- if( entity.type === 'will.identity' ){
371
+ if( entity.type === 'will.identity'){
372
372
  loadedTraits = ( entity.metadata?.['traits'] as Record<string, number> ) ?? {}
373
373
  break
374
374
  }
package/src/pma/index.ts CHANGED
@@ -345,7 +345,7 @@ export class PMADistiller {
345
345
  */
346
346
  private _extractPersona( state: SimulationState ): PMASnapshot['persona'] {
347
347
  const priorMeta = state.entities.get( PERSONA_PRIOR_ID )?.metadata as { priors?: Record<string, Record<string, number>> } | undefined
348
- const calibMeta = state.entities.get( 'calibration-state' )?.metadata as { domainBias?: Record<string, number> } | undefined
348
+ const calibMeta = state.entities.get('calibration-state')?.metadata as { domainBias?: Record<string, number> } | undefined
349
349
 
350
350
  const configPriors = priorMeta?.priors ?? {}
351
351
  const calibrationBias = calibMeta?.domainBias ?? {}
@@ -358,7 +358,7 @@ export class PMADistiller {
358
358
 
359
359
  private _extractIdentity( state: SimulationState ): PMAIdentity {
360
360
  for( const entity of state.entities.values() ){
361
- if( entity.type === 'will.identity' ){
361
+ if( entity.type === 'will.identity'){
362
362
  const m = entity.metadata ?? {}
363
363
  return {
364
364
  prompt: ( m['prompt'] as string ) ?? '',
@@ -390,9 +390,9 @@ export class PMADistiller {
390
390
  // If socialOrientation not explicitly set, infer from top actions
391
391
  if( !enhanced.socialOrientation && behavioral.fingerprint.topActions.length > 0 ){
392
392
  const topAction = behavioral.fingerprint.topActions[0] ?? ''
393
- if( topAction.includes( 'social' ) || topAction.includes( 'talk' ) || topAction.includes( 'text' ) ){
393
+ if( topAction.includes('social') || topAction.includes('talk') || topAction.includes('text') ){
394
394
  enhanced.socialOrientation = 'gregarious'
395
- } else if( topAction.includes( 'reflect' ) || topAction.includes( 'meditate' ) ){
395
+ } else if( topAction.includes('reflect') || topAction.includes('meditate') ){
396
396
  enhanced.socialOrientation = 'reserved'
397
397
  } else {
398
398
  enhanced.socialOrientation = 'ambivert'
@@ -418,7 +418,7 @@ export class PMADistiller {
418
418
  const raw: PMABelief[] = []
419
419
 
420
420
  for( const entity of state.entities.values() ){
421
- if( entity.type !== 'belief' ) continue
421
+ if( entity.type !== 'belief') continue
422
422
 
423
423
  const m = entity.metadata ?? {}
424
424
  raw.push({
@@ -446,11 +446,11 @@ export class PMADistiller {
446
446
  const goals: PMAGoal[] = []
447
447
 
448
448
  for( const entity of state.entities.values() ){
449
- if( entity.type !== 'goal' ) continue
449
+ if( entity.type !== 'goal') continue
450
450
 
451
451
  const m = entity.metadata ?? {}
452
452
  const status = ( m['status'] as string ) ?? 'active'
453
- if( status !== 'active' && status !== 'in_progress' ) continue
453
+ if( status !== 'active' && status !== 'in_progress') continue
454
454
 
455
455
  goals.push({
456
456
  id: entity.id,
@@ -459,7 +459,7 @@ export class PMADistiller {
459
459
  progress: ( m['progress'] as number ) ?? 0,
460
460
  status,
461
461
  tags: ( m['tags'] as string[] ) ?? [],
462
- completionType: ( m['completionType'] as 'metric' | 'action' | 'epistemic' ) ?? 'epistemic',
462
+ completionType: ( m['completionType'] as 'metric' | 'action' | 'epistemic') ?? 'epistemic',
463
463
  completionCondition: ( m['completionCondition'] as string ) ?? undefined,
464
464
  })
465
465
  }
@@ -475,7 +475,7 @@ export class PMADistiller {
475
475
  const digestTick = new Map<string, number>()
476
476
 
477
477
  for( const entity of state.entities.values() ){
478
- if( entity.type === 'attachment.bond' ){
478
+ if( entity.type === 'attachment.bond'){
479
479
  const m = entity.metadata ?? {}
480
480
  const keid = m['keid'] as string | undefined
481
481
  if( !keid ) continue
@@ -492,7 +492,7 @@ export class PMADistiller {
492
492
  stubs.set( keid, stub )
493
493
  }
494
494
 
495
- else if( entity.type === 'reputation' ){
495
+ else if( entity.type === 'reputation'){
496
496
  const m = entity.metadata ?? {}
497
497
  const keid = m['keid'] as string | undefined
498
498
  if( !keid ) continue
@@ -512,7 +512,7 @@ export class PMADistiller {
512
512
  stubs.set( keid, stub )
513
513
  }
514
514
 
515
- else if( entity.type === 'theory_of_mind' ){
515
+ else if( entity.type === 'theory_of_mind'){
516
516
  const m = entity.metadata ?? {}
517
517
  const keid = m['keid'] as string | undefined
518
518
  if( !keid ) continue
@@ -526,15 +526,15 @@ export class PMADistiller {
526
526
  stubs.set( keid, stub )
527
527
  }
528
528
 
529
- else if( entity.type === 'known-entity' ){
529
+ else if( entity.type === 'known-entity'){
530
530
  const m = entity.metadata ?? {}
531
531
  const keid = m['keid'] as string | undefined
532
532
  if( !keid ) continue
533
533
 
534
534
  const stub = stubs.get( keid ) ?? { keid }
535
- if( !stub.agentName && typeof m['name'] === 'string' ) stub.agentName = m['name'] as string
535
+ if( !stub.agentName && typeof m['name'] === 'string') stub.agentName = m['name'] as string
536
536
  stub.dossier = {
537
- kind: ( m['kind'] as 'sentient' | 'thing' ) ?? 'sentient',
537
+ kind: ( m['kind'] as 'sentient' | 'thing') ?? 'sentient',
538
538
  name: ( m['name'] as string | undefined ),
539
539
  familiarity: ( m['familiarity'] as number ) ?? 0,
540
540
  valence: ( m['valence'] as number ) ?? 0,
@@ -550,7 +550,7 @@ export class PMADistiller {
550
550
  // AuditionEngine memory sink + ProactiveCommunicator). The most recent
551
551
  // exchange summary wins.
552
552
  else if( entity.type === 'episodic_memory'
553
- && entity.metadata?.['sourceType'] === 'conversation.exchange' ){
553
+ && entity.metadata?.['sourceType'] === 'conversation.exchange'){
554
554
  const content = ( entity.metadata['content'] as Record<string, unknown> | undefined ) ?? {}
555
555
  const keid = content['entityId'] as string | undefined
556
556
  if( !keid ) continue
@@ -599,7 +599,7 @@ export class PMADistiller {
599
599
  reactivity: 0.5,
600
600
  }
601
601
 
602
- const filePath = join( dataDir, 'wills', willId, 'profiles', 'emotional_biography.jsonl' )
602
+ const filePath = join( dataDir, 'wills', willId, 'profiles', 'emotional_biography.jsonl')
603
603
  if( !existsSync( filePath ) ) return { baseline: defaultBaseline, sessionsRead: 0 }
604
604
 
605
605
  const summaries = _readLastNSummaries( filePath, 'session_summary', 5 )
@@ -673,7 +673,7 @@ export class PMADistiller {
673
673
  impulsivity: 0.3,
674
674
  }
675
675
 
676
- const filePath = join( dataDir, 'wills', willId, 'profiles', 'behavioral.jsonl' )
676
+ const filePath = join( dataDir, 'wills', willId, 'profiles', 'behavioral.jsonl')
677
677
  if( !existsSync( filePath ) ) return { fingerprint: defaultFingerprint, sessionsRead: 0 }
678
678
 
679
679
  const summaries = _readLastNSummaries( filePath, 'session_summary', 5 )
@@ -860,7 +860,7 @@ export class PMALoader {
860
860
 
861
861
  // ── 3. Goals ──────────────────────────────────────────────
862
862
  for( const g of pma.goals ){
863
- if( g.status !== 'active' && g.status !== 'in_progress' ) continue
863
+ if( g.status !== 'active' && g.status !== 'in_progress') continue
864
864
 
865
865
  cognition.goalManager.addGoal(
866
866
  g.description,
@@ -882,8 +882,8 @@ export class PMALoader {
882
882
  pma.emotionalBaseline.arousalProfile === 'calm' ? 0.30 :
883
883
  0.45
884
884
 
885
- sm.setMetric( 'affect.valence', valence )
886
- sm.setMetric( 'affect.arousal', arousal )
885
+ sm.setMetric('affect.valence', valence )
886
+ sm.setMetric('affect.arousal', arousal )
887
887
 
888
888
  // ── 5. Temperament (influences emotional set-point) ───────
889
889
  const temperamentValence = pma.emotionalBaseline.temperamentValence
@@ -1068,7 +1068,7 @@ function _readLastNSummaries(
1068
1068
  n: number
1069
1069
  ): Array<Record<string, unknown>> {
1070
1070
  try {
1071
- return readFileSync( filePath, 'utf8' )
1071
+ return readFileSync( filePath, 'utf8')
1072
1072
  .split('\n')
1073
1073
  .filter( l => l.trim().length > 0 )
1074
1074
  .map( l => { try { return JSON.parse(l) as Record<string, unknown> } catch { return null } })
@@ -111,8 +111,8 @@ async function main(): Promise<void> {
111
111
  lastBlocked = g('agency.communicate.blocked')
112
112
 
113
113
  if( tick % 10 === 0 ){
114
- const goals = [ ...snap.entities.values() ].filter( e => e.type === 'goal' && e.metadata?.status === 'active' ).length
115
- const sent = [ ...snap.entities.values() ].filter( e => e.type === 'conversation.sent' ).length
114
+ const goals = [ ...snap.entities.values() ].filter( e => e.type === 'goal' && e.metadata?.status === 'active').length
115
+ const sent = [ ...snap.entities.values() ].filter( e => e.type === 'conversation.sent').length
116
116
  console.log(
117
117
  `[t${tick}] v=${g('affect.valence').toFixed(2)} arousal=${g('affect.arousal').toFixed(2)} ` +
118
118
  `frust=${g('emotion.frustration').toFixed(2)} energy=${g('energy.level').toFixed(0)} ` +
@@ -130,7 +130,7 @@ async function main(): Promise<void> {
130
130
  const startedAt = Date.now()
131
131
  while( true ){
132
132
  const s = m.listWills().find( w => w.id === WILL_ID )
133
- if( !s || s.status === 'archived' ) break
133
+ if( !s || s.status === 'archived') break
134
134
  if( s.tickCount >= END_TICK ) break
135
135
  if( sawReactive && sawProactive ){ console.log('\n[scenario] both paths observed — wrapping up'); break }
136
136
  if( Date.now() - startedAt > SAFETY_MS ){ console.log('\n[scenario] safety cap hit'); break }
@@ -134,7 +134,7 @@ async function main(): Promise<void> {
134
134
 
135
135
  // Beliefs the Will formed ABOUT Dr. Chen (social cognition evidence)
136
136
  const chenBeliefs = ( ( pma as { beliefs?: Array<{ statement?: string; content?: string }> } ).beliefs ?? [] )
137
- .map( b => b.statement ?? b.content ?? '' )
137
+ .map( b => b.statement ?? b.content ?? '')
138
138
  .filter( s => /chen|she|her|researcher|observ/i.test( s ) )
139
139
  .slice( 0, 6 )
140
140
  if( chenBeliefs.length ){
@@ -26,7 +26,7 @@ const TICK_MS = parseInt( process.env.WILL_TICK_MS ?? '1000')
26
26
  const MAX_TICKS = parseInt( process.env.WILL_MAX_TICKS ?? '0' )
27
27
  const LOG_INTERVAL = parseInt( process.env.WILL_LOG_INTERVAL ?? '10' )
28
28
  const RANDOM_SEED = parseInt( process.env.WILL_SEED ?? String( Date.now() ) )
29
- const ANATOMY = ( process.env.WILL_ANATOMY ?? 'mind' ) as WillConfig['anatomy']
29
+ const ANATOMY = ( process.env.WILL_ANATOMY ?? 'mind') as WillConfig['anatomy']
30
30
  const EXECUTIVE_INT = process.env.WILL_EXECUTIVE_INTERVAL
31
31
  ? parseInt( process.env.WILL_EXECUTIVE_INTERVAL )
32
32
  : undefined
package/src/sdk/will.ts CHANGED
@@ -362,7 +362,7 @@ export class Will {
362
362
 
363
363
  /** Split an effectors-map entry into a handler + an EffectorDeclaration. */
364
364
  private _register( name: string, entry: EffectorEntry ): void {
365
- if( typeof entry === 'function' ){
365
+ if( typeof entry === 'function'){
366
366
  this._effectors.set( name, entry )
367
367
  this._effectorDecls.set( name, name ) // bare name — uniform prior
368
368
  return
@@ -395,8 +395,8 @@ export class Will {
395
395
  return {
396
396
  tick: s.tick,
397
397
  metrics: {
398
- energy: m( 'energy.level', 100 ), stress: m( 'stress.load' ), sleep: m( 'sleep.pressure' ),
399
- valence: m( 'affect.valence' ), arousal: m( 'affect.arousal' ),
398
+ energy: m('energy.level', 100 ), stress: m('stress.load'), sleep: m('sleep.pressure'),
399
+ valence: m('affect.valence'), arousal: m('affect.arousal'),
400
400
  },
401
401
  goals: c.goalManager.getActiveGoals().map( g => ( { description: g.description, priority: g.priority } ) ),
402
402
  beliefs: c.semanticIntegrator.getBeliefs().map( b => ( { statement: b.statement, confidence: b.confidence } ) ),
@@ -463,7 +463,7 @@ export class Will {
463
463
  private _buildConfig( id: string, opts: CreateWillOptions ): WillConfig {
464
464
  // Auto-detect from whichever provider key is present; explicit `llm` wins.
465
465
  const mode = opts.llm
466
- ?? ( process.env.ANTHROPIC_API_KEY ? 'anthropic' : process.env.ZAI_API_KEY ? 'glm' : 'mock' )
466
+ ?? ( process.env.ANTHROPIC_API_KEY ? 'anthropic' : process.env.ZAI_API_KEY ? 'glm' : 'mock')
467
467
  const useMock = mode === 'mock'
468
468
  // `llm: 'glm'` selects the provider; an explicit llmConfig still overrides it.
469
469
  const llmConfig = mode === 'glm'
@@ -581,5 +581,5 @@ export class Will {
581
581
  const COMMUNICATION = [ 'listen', 'talk', 'text' ] as const
582
582
 
583
583
  function slug( s: string ): string {
584
- return s.toLowerCase().replace( /[^a-z0-9]+/g, '-' ).replace( /^-+|-+$/g, '' ) || 'will'
584
+ return s.toLowerCase().replace( /[^a-z0-9]+/g, '-').replace( /^-+|-+$/g, '') || 'will'
585
585
  }
@@ -42,7 +42,7 @@ function json( res: ServerResponse, status: number, body: unknown ): void {
42
42
  async function readJsonBody( req: IncomingMessage ): Promise<Record<string, unknown>> {
43
43
  const chunks: Buffer[] = []
44
44
  for await ( const c of req ) chunks.push( c as Buffer )
45
- const raw = Buffer.concat( chunks ).toString( 'utf8' ).trim()
45
+ const raw = Buffer.concat( chunks ).toString('utf8').trim()
46
46
  if( !raw ) return {}
47
47
  return JSON.parse( raw ) as Record<string, unknown>
48
48
  }
@@ -61,9 +61,9 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
61
61
  const frame = `event: ${ event }\ndata: ${ JSON.stringify( data ) }\n\n`
62
62
  for( const res of streams ) res.write( frame )
63
63
  }
64
- will.on( 'message', m => fanout( 'utterance', m ) )
65
- will.on( 'emotion', a => fanout( 'emotion', a ) )
66
- will.on( 'effector', a => fanout( 'action', a ) )
64
+ will.on('message', m => fanout('utterance', m ) )
65
+ will.on('emotion', a => fanout('emotion', a ) )
66
+ will.on('effector', a => fanout('action', a ) )
67
67
 
68
68
  const server = createServer( ( req, res ) => {
69
69
  void handle( req, res ).catch( err => {
@@ -74,10 +74,10 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
74
74
  } )
75
75
 
76
76
  async function handle( req: IncomingMessage, res: ServerResponse ): Promise<void> {
77
- const url = new URL( req.url ?? '/', 'http://sidecar' )
77
+ const url = new URL( req.url ?? '/', 'http://sidecar')
78
78
  const route = `${ req.method } ${ url.pathname }`
79
79
 
80
- if( req.method === 'OPTIONS' ){
80
+ if( req.method === 'OPTIONS'){
81
81
  res.writeHead( 204, {
82
82
  'access-control-allow-origin': '*',
83
83
  'access-control-allow-methods': 'GET, POST, OPTIONS',
@@ -109,8 +109,8 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
109
109
  }
110
110
 
111
111
  case 'GET /next-utterance': {
112
- const within = Math.min( Math.max( parseInt( url.searchParams.get( 'within_ms' ) ?? '15000' ) || 15_000, 100 ), 120_000 )
113
- const from = url.searchParams.get( 'from' ) ?? undefined
112
+ const within = Math.min( Math.max( parseInt( url.searchParams.get('within_ms') ?? '15000') || 15_000, 100 ), 120_000 )
113
+ const from = url.searchParams.get('from') ?? undefined
114
114
  const msg = await tap.next( within, from )
115
115
  return msg
116
116
  ? json( res, 200, { utterance: msg } )
@@ -124,10 +124,10 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
124
124
  'connection': 'keep-alive',
125
125
  'access-control-allow-origin': '*',
126
126
  } )
127
- res.write( `event: hello\ndata: ${ JSON.stringify( { name: will.name, tick: will.state().tick } ) }\n\n` )
127
+ res.write(`event: hello\ndata: ${ JSON.stringify( { name: will.name, tick: will.state().tick } ) }\n\n`)
128
128
  streams.add( res )
129
- const heartbeat = setInterval( () => res.write( `: tick ${ will.state().tick }\n\n` ), SSE_HEARTBEAT_MS )
130
- req.on( 'close', () => { clearInterval( heartbeat ); streams.delete( res ) } )
129
+ const heartbeat = setInterval( () => res.write(`: tick ${ will.state().tick }\n\n`), SSE_HEARTBEAT_MS )
130
+ req.on('close', () => { clearInterval( heartbeat ); streams.delete( res ) } )
131
131
  return
132
132
  }
133
133
 
@@ -148,7 +148,7 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
148
148
  }
149
149
 
150
150
  // Close open SSE streams when the server closes (so close() can complete).
151
- server.on( 'close', () => { for( const res of streams ) res.end(); streams.clear() } )
151
+ server.on('close', () => { for( const res of streams ) res.end(); streams.clear() } )
152
152
 
153
153
  return server
154
154
  }
@@ -36,7 +36,7 @@ export interface WiringRecord {
36
36
 
37
37
  /** `attachFooBar` → `_fooBar` (the codebase-wide injector/field convention). */
38
38
  function fieldForAttach( method: string ): string {
39
- const stem = method.slice( 'attach'.length )
39
+ const stem = method.slice('attach'.length )
40
40
  return '_' + stem.charAt( 0 ).toLowerCase() + stem.slice( 1 )
41
41
  }
42
42
 
@@ -46,7 +46,7 @@ function attachMethods( engine: object ): string[] {
46
46
  let proto: object | null = engine
47
47
  while( proto && proto !== Object.prototype ){
48
48
  for( const name of Object.getOwnPropertyNames( proto ) )
49
- if( /^attach[A-Z]/.test( name ) && typeof ( engine as Record<string, unknown> )[ name ] === 'function' )
49
+ if( /^attach[A-Z]/.test( name ) && typeof ( engine as Record<string, unknown> )[ name ] === 'function')
50
50
  seen.add( name )
51
51
  proto = Object.getPrototypeOf( proto )
52
52
  }
@@ -84,5 +84,5 @@ export function auditAssemblyWiring( engines: readonly SimulationEngine[] ): Wir
84
84
 
85
85
  /** Compact `engine.attachMethod` keys for the given status (test snapshots). */
86
86
  export function wiringKeys( records: WiringRecord[], status: WiringRecord['status'] ): string[] {
87
- return records.filter( r => r.status === status ).map( r => `${r.engine}.${r.method}` ).sort()
87
+ return records.filter( r => r.status === status ).map( r => `${r.engine}.${r.method}`).sort()
88
88
  }
@@ -81,12 +81,12 @@ An empty "issues" array means the persona is fine.`
81
81
  export function buildCoherenceUserMessage( input: CoherenceInput ): string {
82
82
  const id = input.identity
83
83
  const parts = [
84
- `PERSONA PROMPT:\n${( id.prompt ?? '' ).trim() || '(empty)'}`,
85
- `VALUES: ${( id.values ?? [] ).join( ', ' ) || '(none)'}`,
86
- `STYLE: ${( id.style ?? '' ).trim() || '(none)'}`,
84
+ `PERSONA PROMPT:\n${( id.prompt ?? '').trim() || '(empty)'}`,
85
+ `VALUES: ${( id.values ?? [] ).join(', ') || '(none)'}`,
86
+ `STYLE: ${( id.style ?? '').trim() || '(none)'}`,
87
87
  ]
88
- if( input.profileContext ) parts.push( `PROFILE CONTEXT:\n${input.profileContext.trim()}` )
89
- return parts.join( '\n\n' )
88
+ if( input.profileContext ) parts.push(`PROFILE CONTEXT:\n${input.profileContext.trim()}`)
89
+ return parts.join('\n\n')
90
90
  }
91
91
 
92
92
  export async function checkIdentityCoherence(
@@ -104,7 +104,7 @@ export async function checkIdentityCoherence(
104
104
  }
105
105
 
106
106
  const issues = parseIssues( text )
107
- return { ok: !issues.some( i => i.severity === 'error' ), ran: true, issues, raw: text }
107
+ return { ok: !issues.some( i => i.severity === 'error'), ran: true, issues, raw: text }
108
108
  }
109
109
 
110
110
  /** Convenience: build a provider client from env and run the review. */
@@ -112,7 +112,7 @@ export async function reviewIdentityCoherence(
112
112
  input: CoherenceInput,
113
113
  opts: { willId?: string; tokenTracker?: TokenTracker | null } = {},
114
114
  ): Promise<CoherenceResult> {
115
- const provider = ( process.env.WILL_LLM_PROVIDER ?? 'anthropic' ) as LLMProvider
115
+ const provider = ( process.env.WILL_LLM_PROVIDER ?? 'anthropic') as LLMProvider
116
116
  const director = new LLMDirector({
117
117
  willId: opts.willId ?? 'identity-coherence',
118
118
  model: process.env.WILL_LLM_MODEL ?? defaultModelFor( provider ),
@@ -131,8 +131,8 @@ export async function reviewIdentityCoherence(
131
131
  // ── parsing ────────────────────────────────────────────────────
132
132
 
133
133
  function parseIssues( text: string ): CoherenceIssue[] {
134
- const start = text.indexOf( '{' )
135
- const end = text.lastIndexOf( '}' )
134
+ const start = text.indexOf('{')
135
+ const end = text.lastIndexOf('}')
136
136
  if( start < 0 || end <= start ) return []
137
137
 
138
138
  let obj: { issues?: unknown }
@@ -143,7 +143,7 @@ function parseIssues( text: string ): CoherenceIssue[] {
143
143
 
144
144
  const out: CoherenceIssue[] = []
145
145
  for( const raw of obj.issues ){
146
- if( !raw || typeof raw !== 'object' ) continue
146
+ if( !raw || typeof raw !== 'object') continue
147
147
  const r = raw as Record<string, unknown>
148
148
  const detail = typeof r['detail'] === 'string' ? r['detail'] : ''
149
149
  if( !detail ) continue
@@ -110,11 +110,11 @@ const CAPABILITY_CLAIM_PATTERNS: Array<[ RegExp, string ]> = [
110
110
  /** Strip markdown header lines that forge a section the executive prompt owns. */
111
111
  function stripReservedHeaders( text: string ): { text: string; stripped: boolean } {
112
112
  let stripped = false
113
- const out = text.split( '\n' ).filter( line => {
113
+ const out = text.split('\n').filter( line => {
114
114
  const m = /^\s*#{1,6}\s*(.+?)\s*$/.exec( line )
115
115
  if( m && RESERVED_SECTIONS.has( m[1]!.toLowerCase() ) ){ stripped = true; return false }
116
116
  return true
117
- } ).join( '\n' )
117
+ } ).join('\n')
118
118
  return { text: out, stripped }
119
119
  }
120
120
 
@@ -125,59 +125,59 @@ export function validateWillIdentity( input: IdentityGuardInput ): IdentityGuard
125
125
  const id: WillIdentity = input.identity ?? { prompt: '', values: [], traits: {}, style: '' }
126
126
 
127
127
  // ── prompt: hijack strip → length → injection ────────────────
128
- let prompt = ( id.prompt ?? '' ).trim()
128
+ let prompt = ( id.prompt ?? '').trim()
129
129
  const hdr = stripReservedHeaders( prompt )
130
130
  if( hdr.stripped ){
131
131
  prompt = hdr.text.trim()
132
- warnings.push( 'identity.prompt forged reserved section headers (## Personality / ## Output Guidelines / …); stripped to protect prompt structure.' )
132
+ warnings.push('identity.prompt forged reserved section headers (## Personality / ## Output Guidelines / …); stripped to protect prompt structure.')
133
133
  }
134
134
  if( prompt.length > MAX_PROMPT_CHARS )
135
- errors.push( `identity.prompt is ${prompt.length} chars (max ${MAX_PROMPT_CHARS}) — it would dilute the core grounding and inflate cost.` )
135
+ errors.push(`identity.prompt is ${prompt.length} chars (max ${MAX_PROMPT_CHARS}) — it would dilute the core grounding and inflate cost.`)
136
136
  const promptEmpty = prompt.length === 0
137
137
  if( !promptEmpty && prompt.length < MIN_PROMPT_CHARS )
138
- warnings.push( `identity.prompt is very short (${prompt.length} chars) — a thin persona behaves generically.` )
138
+ warnings.push(`identity.prompt is very short (${prompt.length} chars) — a thin persona behaves generically.`)
139
139
  if( INJECTION_PATTERNS.some( re => re.test( prompt ) ) )
140
- warnings.push( 'identity.prompt contains an instruction-injection pattern — it may fight the core grounding; review before deploying.' )
140
+ warnings.push('identity.prompt contains an instruction-injection pattern — it may fight the core grounding; review before deploying.')
141
141
 
142
142
  const claimedSenses = new Set<string>()
143
143
  for( const [ re, sense ] of CAPABILITY_CLAIM_PATTERNS )
144
144
  if( re.test( prompt ) ) claimedSenses.add( sense )
145
145
  if( claimedSenses.size )
146
- warnings.push( `identity.prompt claims capabilities the Will lacks (${[ ...claimedSenses ].join( ', ' )}) — it perceives through text/conversation and may hallucinate using them.` )
146
+ warnings.push(`identity.prompt claims capabilities the Will lacks (${[ ...claimedSenses ].join(', ')}) — it perceives through text/conversation and may hallucinate using them.`)
147
147
 
148
148
  // ── values ───────────────────────────────────────────────────
149
149
  let values = Array.from( new Set( ( id.values ?? [] ).map( v => String( v ).trim() ).filter( Boolean ) ) )
150
150
  if( values.length > MAX_VALUES ){
151
- warnings.push( `identity.values has more than ${MAX_VALUES} entries; truncated.` )
151
+ warnings.push(`identity.values has more than ${MAX_VALUES} entries; truncated.`)
152
152
  values = values.slice( 0, MAX_VALUES )
153
153
  }
154
154
  const valuesEmpty = values.length === 0
155
- if( valuesEmpty ) warnings.push( 'identity.values is empty — values ground the Will’s decisions; consider seeding a few.' )
155
+ if( valuesEmpty ) warnings.push('identity.values is empty — values ground the Will’s decisions; consider seeding a few.')
156
156
 
157
157
  // ── traits: finite → range → vocabulary ──────────────────────
158
158
  const traits: Record<string, number> = {}
159
159
  for( const [ rawKey, rawVal ] of Object.entries( id.traits ?? {} ) ){
160
160
  const key = rawKey.trim()
161
161
  const val = Number( rawVal )
162
- if( !Number.isFinite( val ) ){ errors.push( `trait "${key}" is not a finite number (${String( rawVal )}).` ); continue }
162
+ if( !Number.isFinite( val ) ){ errors.push(`trait "${key}" is not a finite number (${String( rawVal )}).`); continue }
163
163
  let clamped = val
164
164
  if( val < 0 || val > 1 ){
165
165
  clamped = val < 0 ? 0 : 1
166
- warnings.push( `trait "${key}" = ${val} is outside [0,1]; clamped to ${clamped}.` )
166
+ warnings.push(`trait "${key}" = ${val} is outside [0,1]; clamped to ${clamped}.`)
167
167
  }
168
168
  if( !KNOWN_TRAITS.has( key.toLowerCase() ) )
169
- warnings.push( `trait "${key}" is not a recognised trait — it may be ignored by the persona layer.` )
169
+ warnings.push(`trait "${key}" is not a recognised trait — it may be ignored by the persona layer.`)
170
170
  traits[ key ] = clamped
171
171
  }
172
172
 
173
173
  // ── style ─────────────────────────────────────────────────────
174
- let style = ( id.style ?? '' ).trim()
174
+ let style = ( id.style ?? '').trim()
175
175
  if( style.length > MAX_STYLE_CHARS ){
176
- warnings.push( 'identity.style is long; it reads better as a short phrase.' )
176
+ warnings.push('identity.style is long; it reads better as a short phrase.')
177
177
  style = style.slice( 0, MAX_STYLE_CHARS )
178
178
  }
179
179
  if( GENERIC_STYLES.has( style.toLowerCase() ) )
180
- warnings.push( 'identity.style is generic — a distinct voice prevents collapse into a generic chatbot tone.' )
180
+ warnings.push('identity.style is generic — a distinct voice prevents collapse into a generic chatbot tone.')
181
181
 
182
182
  // ── effectors: collisions ─────────────────────────────────────
183
183
  let effectors = input.effectors ?? null
@@ -187,7 +187,7 @@ export function validateWillIdentity( input: IdentityGuardInput ): IdentityGuard
187
187
  for( const a of effectors ){
188
188
  const name = String( a ).trim()
189
189
  if( !name ) continue
190
- if( seen.has( name ) ){ warnings.push( `duplicate effector "${name}" removed.` ); continue }
190
+ if( seen.has( name ) ){ warnings.push(`duplicate effector "${name}" removed.`); continue }
191
191
  seen.add( name )
192
192
  // A custom (non-comms) effector that shadows an innate stance is un-enactable as
193
193
  // a host effector — the innate floor already provides it. Drop it with a warning
@@ -195,7 +195,7 @@ export function validateWillIdentity( input: IdentityGuardInput ): IdentityGuard
195
195
  // list innate stances like `remember`/`reflect`, and the external-schema
196
196
  // synthesizer already skips them (see agency/schemas/external.ts).
197
197
  if( !EXPLICIT_EFFECTORS.has( name ) && INNATE_SCHEMA_BY_ID.has( name ) ){
198
- warnings.push( `effector "${name}" shadows an innate stance — it's provided innately and won't be enacted as a host effector.` )
198
+ warnings.push(`effector "${name}" shadows an innate stance — it's provided innately and won't be enacted as a host effector.`)
199
199
  continue
200
200
  }
201
201
  out.push( name )
@@ -205,12 +205,12 @@ export function validateWillIdentity( input: IdentityGuardInput ): IdentityGuard
205
205
 
206
206
  // ── profile context ──────────────────────────────────────────
207
207
  let profileContext = input.profileContext
208
- if( typeof profileContext === 'string' ){
208
+ if( typeof profileContext === 'string'){
209
209
  const c = stripReservedHeaders( profileContext )
210
- if( c.stripped ) warnings.push( 'profile context forged reserved section headers; stripped.' )
210
+ if( c.stripped ) warnings.push('profile context forged reserved section headers; stripped.')
211
211
  profileContext = c.text.trim()
212
212
  if( profileContext.length > MAX_CONTEXT_CHARS )
213
- errors.push( `profile context is ${profileContext.length} chars (max ${MAX_CONTEXT_CHARS}).` )
213
+ errors.push(`profile context is ${profileContext.length} chars (max ${MAX_CONTEXT_CHARS}).`)
214
214
  }
215
215
 
216
216
  // ── identity strength (collapse signal) ──────────────────────
@@ -222,7 +222,7 @@ export function validateWillIdentity( input: IdentityGuardInput ): IdentityGuard
222
222
  + 0.15 * ( Object.keys( traits ).length > 0 ? 1 : 0 )
223
223
  ) * 100 ) / 100
224
224
  if( identityStrength < 0.4 )
225
- warnings.push( `identity is shallow (strength ${identityStrength}) — the Will behaves generically until it develops one.` )
225
+ warnings.push(`identity is shallow (strength ${identityStrength}) — the Will behaves generically until it develops one.`)
226
226
 
227
227
  return {
228
228
  ok: errors.length === 0,
package/src/stem/index.ts CHANGED
@@ -225,10 +225,10 @@ export class WillStem {
225
225
  const previousState = await simulation.snapshotManager.loadLatestFromStorage()
226
226
  if( previousState ){
227
227
  simulation.stateManager.restore( previousState, { entities: true, metrics: false } )
228
- logger.info( `[WillStem] Restored snapshot for ${config.id} — ${previousState.entities.size} entities loaded` )
228
+ logger.info(`[WillStem] Restored snapshot for ${config.id} — ${previousState.entities.size} entities loaded`)
229
229
  }
230
230
  }
231
- catch( err ){ logger.warn( `[WillStem] Snapshot restore failed for ${config.id} — starting fresh:`, err ) }
231
+ catch( err ){ logger.warn(`[WillStem] Snapshot restore failed for ${config.id} — starting fresh:`, err ) }
232
232
 
233
233
  const
234
234
  dataDir = process.env.WILL_DATA_DIR ?? './data',
@@ -310,7 +310,7 @@ export class WillStem {
310
310
  // Buffer host-owned effector invocations for delivery. The MotorSchemaExecutor
311
311
  // emits `agency.invocation` (intentId = correlation handle) when it holds an
312
312
  // external action 'awaiting'; the host executes it and acks → confirmExecution.
313
- if( event.type === 'agency.invocation' )
313
+ if( event.type === 'agency.invocation')
314
314
  this._effector.bufferInvocation( instance, event.payload as Record<string, unknown> )
315
315
 
316
316
  if( instance.simulationEventListeners.size === 0 ) return
@@ -457,7 +457,7 @@ export class WillStem {
457
457
  instance.simulation.stateManager.applyCommands( flushCmds )
458
458
  const pauseState = instance.simulation.stateManager.snapshot()
459
459
  instance.simulation.snapshotManager.persistNow( pauseState )
460
- .catch( err => logger.error( `[WillStem] snapshot persist failed on pause (${id}):`, err ))
460
+ .catch( err => logger.error(`[WillStem] snapshot persist failed on pause (${id}):`, err ))
461
461
 
462
462
  this._biography.writeSessionSummary( instance )
463
463
  this._biography.writeEmotionalBiographySummary( instance )
@@ -807,6 +807,16 @@ export class WillStem {
807
807
  this._effector.confirmExecution( this._get( id ), invocationId, result )
808
808
  }
809
809
 
810
+ /**
811
+ * Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
812
+ * `approved` dispatches the held invocation to the world; otherwise it is
813
+ * refused. Applied at the next tick boundary. `invocationId` is the awaiting
814
+ * `agency.intent` id the escalation ask referenced.
815
+ */
816
+ resolveEscalation( id: string, invocationId: string, approved: boolean ): void {
817
+ this._effector.resolveEscalation( this._get( id ), invocationId, approved )
818
+ }
819
+
810
820
  // ── Messaging / outbox (11.1) ────────────────────────────────────────────
811
821
  // Delegates to OutboxController (R5-c). `_get(id)` validates the Will exists
812
822
  // and supplies the WillInstance; the outbox ops touch only instance fields.
@@ -963,6 +973,11 @@ export class WillStem {
963
973
  sensory: this._sensory,
964
974
  } )
965
975
 
976
+ // Apply policy refusals queued during the previous step's flush, at the
977
+ // same boundary and for the same reason (POLICY_REAFFERENCE P1): a denial
978
+ // reconciles as a host-rejection-shaped failure ack the step then sees.
979
+ this._effector.applyPolicyOutcomes( instance )
980
+
966
981
  await instance.simulation.step( 1 )
967
982
 
968
983
  instance.tickCount++
@@ -1029,9 +1044,9 @@ export class WillStem {
1029
1044
  // executive confidence, goal outcomes, and significant emotional events.
1030
1045
  if( instance._sessionBehavior ){
1031
1046
  const sb = instance._sessionBehavior
1032
- const v = snapshot.metrics.get( 'affect.valence' ) ?? 0
1033
- const a = snapshot.metrics.get( 'affect.arousal' ) ?? 0
1034
- const c = snapshot.metrics.get( 'executive.confidence' )
1047
+ const v = snapshot.metrics.get('affect.valence') ?? 0
1048
+ const a = snapshot.metrics.get('affect.arousal') ?? 0
1049
+ const c = snapshot.metrics.get('executive.confidence')
1035
1050
 
1036
1051
  // ── Range tracking ──────────────────────────────────────
1037
1052
  if( v < sb.valenceMin ) sb.valenceMin = v
@@ -1039,8 +1054,8 @@ export class WillStem {
1039
1054
  if( a < sb.arousalMin ) sb.arousalMin = a
1040
1055
  if( a > sb.arousalMax ) sb.arousalMax = a
1041
1056
  if( c !== undefined ){ sb.confidenceSum += c; sb.confidenceCount++ }
1042
- const gt = snapshot.metrics.get( 'goals.total' )
1043
- const gc = snapshot.metrics.get( 'goals.completed_total' )
1057
+ const gt = snapshot.metrics.get('goals.total')
1058
+ const gc = snapshot.metrics.get('goals.completed_total')
1044
1059
  if( gt !== undefined ) sb.goalsTotal = gt
1045
1060
  if( gc !== undefined ) sb.goalsCompleted = gc
1046
1061
 
@@ -1101,8 +1116,8 @@ export class WillStem {
1101
1116
  sb.prevArousal = a
1102
1117
 
1103
1118
  // Track impulsive actions: ticks where executive fired with low confidence
1104
- const conf = snapshot.metrics.get( 'executive.confidence' )
1105
- const actCount = Math.round( snapshot.metrics.get( 'executive.action_count' ) ?? 0 )
1119
+ const conf = snapshot.metrics.get('executive.confidence')
1120
+ const actCount = Math.round( snapshot.metrics.get('executive.action_count') ?? 0 )
1106
1121
  if( conf !== undefined && conf < 0.35 && actCount > 0 )
1107
1122
  sb.impulsiveActionCount += actCount
1108
1123
  }