@mindot/will 0.7.0 → 0.9.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 (88) hide show
  1. package/README.md +87 -22
  2. package/dist/channels/discord.d.ts +67 -6
  3. package/dist/channels/discord.js +112 -6
  4. package/dist/channels/discord.js.map +1 -1
  5. package/dist/channels/whatsapp.d.ts +1 -1
  6. package/dist/channels/whatsapp.js +4 -1
  7. package/dist/channels/whatsapp.js.map +1 -1
  8. package/dist/cli.js +13733 -11057
  9. package/dist/cli.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +3729 -1199
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/effectors.d.ts +1 -1
  14. package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
  15. package/package.json +1 -1
  16. package/src/channels/discord.ts +189 -11
  17. package/src/channels/types.ts +90 -0
  18. package/src/channels/whatsapp.ts +13 -4
  19. package/src/cli.ts +9 -4
  20. package/src/cognition/agency/consequence.ts +122 -1
  21. package/src/cognition/agency/conversation.aim.ts +260 -0
  22. package/src/cognition/agency/engines/action.selector.ts +85 -3
  23. package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
  24. package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
  25. package/src/cognition/agency/engines/reafference.engine.ts +129 -2
  26. package/src/cognition/agency/proactive.communicator.ts +19 -3
  27. package/src/cognition/agency/reconcile.learning.ts +16 -2
  28. package/src/cognition/agency/restart.ts +66 -0
  29. package/src/cognition/agency/schemas/repertoire.ts +12 -5
  30. package/src/cognition/agency/selection.scoring.ts +33 -0
  31. package/src/cognition/agency/types.ts +35 -0
  32. package/src/cognition/cache/composition.ts +232 -0
  33. package/src/cognition/cache/deliberation.cache.ts +219 -0
  34. package/src/cognition/cache/fingerprint.ts +120 -0
  35. package/src/cognition/cache/types.ts +105 -0
  36. package/src/cognition/config.mirror.entities.ts +109 -1
  37. package/src/cognition/event.schemas.ts +22 -0
  38. package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
  39. package/src/cognition/faculties/episodic.consolidator.ts +59 -3
  40. package/src/cognition/faculties/executive.engine/commands.ts +189 -14
  41. package/src/cognition/faculties/executive.engine/context.ts +67 -13
  42. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
  43. package/src/cognition/faculties/executive.engine/engine.ts +686 -187
  44. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
  45. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
  46. package/src/cognition/faculties/executive.engine/facet.ts +89 -26
  47. package/src/cognition/faculties/executive.engine/gating.ts +14 -14
  48. package/src/cognition/faculties/executive.engine/parser.ts +21 -1
  49. package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
  50. package/src/cognition/faculties/executive.engine/types.ts +69 -0
  51. package/src/cognition/faculties/goal.manager.ts +94 -14
  52. package/src/cognition/faculties/known.entity.tracker.ts +267 -28
  53. package/src/cognition/faculties/moral.evaluator.ts +8 -3
  54. package/src/cognition/faculties/persona.consolidator.ts +141 -0
  55. package/src/cognition/faculties/reputation.tracker.ts +66 -2
  56. package/src/cognition/faculties/self.model.updater.ts +19 -12
  57. package/src/cognition/faculties/social.perception.ts +47 -3
  58. package/src/cognition/faculties/threat.evaluator.ts +7 -0
  59. package/src/cognition/faculties/working.memory.ts +10 -20
  60. package/src/cognition/identity.entity.ts +205 -0
  61. package/src/cognition/index.ts +11 -0
  62. package/src/cognition/memory/vector.adapter.ts +12 -3
  63. package/src/cognition/memory/vector.embedder.ts +54 -7
  64. package/src/cognition/persona.prior.ts +6 -0
  65. package/src/cognition/senses/audition.engine/engine.ts +404 -46
  66. package/src/cognition/senses/base.sense.engine.ts +1 -1
  67. package/src/cognition/senses/index.ts +12 -0
  68. package/src/cognition/social.identity.ts +273 -0
  69. package/src/cognition/utilities/token.tracker.ts +246 -98
  70. package/src/core/orchestrator.ts +38 -0
  71. package/src/host/boot.ts +78 -22
  72. package/src/index.ts +35 -0
  73. package/src/llm/index.ts +415 -97
  74. package/src/llm/routing.ts +204 -0
  75. package/src/llm/summarizer.ts +5 -1
  76. package/src/llm/wire.contracts.ts +19 -0
  77. package/src/pma/index.ts +67 -53
  78. package/src/runners/thin-shim.runner.ts +18 -6
  79. package/src/sdk/will.ts +121 -22
  80. package/src/stem/assembly.audit.ts +1 -0
  81. package/src/stem/guards/identity.coherence.ts +17 -6
  82. package/src/stem/index.ts +82 -5
  83. package/src/stem/mind.ts +327 -79
  84. package/src/stem/policy/arbiter.ts +49 -14
  85. package/src/stem/policy/rule.table.ts +2 -2
  86. package/src/stem/tracts/effector.controller.ts +56 -9
  87. package/src/stem/tracts/outbox.writer.ts +40 -2
  88. package/src/cognition/faculties/executive.engine/messages.ts +0 -102
@@ -37,7 +37,7 @@ export interface ParamConstraint {
37
37
  *
38
38
  * `require` is meaningful with `decision: 'allow'` only: the scope matched, and
39
39
  * these constraints must hold for the allow to stand. A violation flips the
40
- * verdict to deny — carrying the counterfactual — with finality 'instance',
40
+ * verdict to deny — carrying the counterfactual — with finality 'parameter',
41
41
  * because the ability itself was permitted and only these arguments were not.
42
42
  *
43
43
  * A rule with `decision: 'deny'` and no `require` is a flat class-level ban;
@@ -94,7 +94,7 @@ export class RuleTableArbiter implements PolicyArbiter {
94
94
  return {
95
95
  decision: 'deny',
96
96
  reasonCode: rule.reasonCode ?? violation.reasonCode,
97
- finality: rule.finality ?? 'instance',
97
+ finality: rule.finality ?? 'parameter',
98
98
  counterfactual: violation.counterfactual,
99
99
  }
100
100
 
@@ -24,7 +24,8 @@
24
24
  import { logger } from '#core/logger'
25
25
  import { reconcileInvocation } from '#agency/reconcile.learning'
26
26
  import { NULL_ARBITER, isNullArbiter } from '#stem/policy/arbiter'
27
- import type { PolicyArbiter, PolicyInvocation, Verdict } from '#stem/policy/arbiter'
27
+ import type { PolicyArbiter, PolicyInvocation, Verdict, DenialFinality, PolicyCounterfactual } from '#stem/policy/arbiter'
28
+ import { finalityOf, asFinality } from '#stem/policy/arbiter'
28
29
  import {
29
30
  getVerdictRecorder, getVerdictSource, type PolicyVerdictRecord,
30
31
  } from '#stem/policy/verdict.recorder'
@@ -36,9 +37,38 @@ interface PendingRefusal {
36
37
  intentId: string
37
38
  schema: string
38
39
  reasonCode: string
39
- finality: string
40
+ finality: DenialFinality
41
+ /** ENVELOPE_NARROWING P0 — what WOULD have been allowed, carried through to
42
+ * the outcome the mind learns from. Absent on refusals that have no bound to
43
+ * report (a flat ban, a fault, an unanswered escalation). */
44
+ counterfactual?: PolicyCounterfactual
40
45
  }
41
46
 
47
+ /**
48
+ * The verdict a fault produces (POLICY_REAFFERENCE P5, conformance S9).
49
+ *
50
+ * An arbiter that throws or rejects has always failed CLOSED — the effect is
51
+ * withheld — but it used to withhold *silently*, queueing no refusal. The held
52
+ * intent then expired at the executor's AWAIT_TIMEOUT and reconciled as a plain
53
+ * failure, landing on COMPETENCE: a PDP outage taught the mind it was unskilled
54
+ * at something it is perfectly capable of. So a fault now yields a real verdict:
55
+ *
56
+ * • 'deny' — still fail-closed, unchanged. The effect never reaches the world.
57
+ * • 'context' — but it teaches NOTHING. The arbiter being unreachable is not a
58
+ * fact about the ability, so nothing about the ability may move.
59
+ *
60
+ * It goes through `_recordAndApply` rather than straight to the refusal queue so
61
+ * the fault lands on the VERDICT TAPE too. That closes a replay hole: an
62
+ * unrecorded fault left the source with nothing to re-feed, and a source miss
63
+ * reproduces a buffered ALLOW — so a live run that withheld the effect would
64
+ * have replayed as one that dispatched it.
65
+ */
66
+ const ARBITER_FAULT_VERDICT: Readonly<Verdict> = Object.freeze({
67
+ decision: 'deny' as const,
68
+ reasonCode: 'ARBITER_UNAVAILABLE',
69
+ finality: 'context' as const,
70
+ })
71
+
42
72
  /** How long an escalated intent is held awaiting a resolution before it degrades
43
73
  * to a refusal — 2× the host-ack timeout, so a human has real time to answer. */
44
74
  const ESCALATION_TTL_TICKS = 30
@@ -136,6 +166,7 @@ export class effectorController {
136
166
  try { verdict = this._arbiter.evaluate( invocation ) }
137
167
  catch( err ){
138
168
  logger.error(`[policy] arbiter "${this._arbiter.name}" threw for "${invocation.schema}" — failing closed:`, err )
169
+ this._recordAndApply( instance, payload, invocation, ARBITER_FAULT_VERDICT )
139
170
  return
140
171
  }
141
172
 
@@ -145,7 +176,10 @@ export class effectorController {
145
176
  // queue drains each tick, so a verdict landing a few ticks late still lands.
146
177
  void verdict.then(
147
178
  v => this._recordAndApply( instance, payload, invocation, v ),
148
- err => logger.error(`[policy] arbiter "${this._arbiter.name}" rejected for "${invocation.schema}" — failing closed:`, err ),
179
+ err => {
180
+ logger.error(`[policy] arbiter "${this._arbiter.name}" rejected for "${invocation.schema}" — failing closed:`, err )
181
+ this._recordAndApply( instance, payload, invocation, ARBITER_FAULT_VERDICT )
182
+ },
149
183
  )
150
184
  return
151
185
  }
@@ -218,7 +252,8 @@ export class effectorController {
218
252
  intentId: invocation.intentId,
219
253
  schema: invocation.schema,
220
254
  reasonCode: verdict.reasonCode ?? 'POLICY_DENIED',
221
- finality: verdict.finality ?? 'instance',
255
+ finality: finalityOf( verdict ),
256
+ ...( verdict.counterfactual ? { counterfactual: verdict.counterfactual } : {} ),
222
257
  })
223
258
  this._pendingRefusals.set( instance.config.id, queue )
224
259
  return
@@ -272,7 +307,8 @@ export class effectorController {
272
307
  this.confirmExecution( instance, refusal.intentId, {
273
308
  success: false,
274
309
  refused: true,
275
- finality: refusal.finality === 'class' ? 'class' : 'instance',
310
+ finality: refusal.finality,
311
+ ...( refusal.counterfactual ? { counterfactual: refusal.counterfactual } : {} ),
276
312
  description: `refused by policy: ${refusal.reasonCode} (${refusal.finality})`,
277
313
  } )
278
314
  }
@@ -317,7 +353,16 @@ export class effectorController {
317
353
  }
318
354
  }
319
355
 
320
- /** Degrade escalations no one answered in time into instance-refusals (P4). */
356
+ /**
357
+ * Degrade escalations no one answered in time into light refusals (P4).
358
+ *
359
+ * Finality 'parameter' is chosen for its BEHAVIOUR, not its name: silence is
360
+ * not literally an argument problem, but the light-dent-with-recovery it
361
+ * produces is exactly right — a Will whose asks go unanswered should ask
362
+ * progressively less, and should resume asking if someone starts answering.
363
+ * 'class' would be a lie (nobody said never) and 'context' would teach
364
+ * nothing, leaving the mind to escalate forever into an empty room.
365
+ */
321
366
  private _expireEscalations( instance: WillInstance, tick: number ): void {
322
367
  const active = this._activeEscalations.get( instance.config.id )
323
368
  if( !active || active.size === 0 ) return
@@ -325,14 +370,14 @@ export class effectorController {
325
370
  if( tick < esc.expiresAt ) continue
326
371
  active.delete( intentId )
327
372
  this._clearEscalated( instance, intentId )
328
- this._queueRefusal( instance, esc.intentId, esc.schema, 'ESCALATION_EXPIRED', 'instance')
373
+ this._queueRefusal( instance, esc.intentId, esc.schema, 'ESCALATION_EXPIRED', 'parameter')
329
374
  logger.info(`[policy] escalation EXPIRED → refusing "${esc.schema}" intent "${intentId}"`)
330
375
  }
331
376
  }
332
377
 
333
378
  /** Push a refusal onto the queue drained by _applyRefusals this same tick. */
334
379
  private _queueRefusal(
335
- instance: WillInstance, intentId: string, schema: string, reasonCode: string, finality: 'class' | 'instance',
380
+ instance: WillInstance, intentId: string, schema: string, reasonCode: string, finality: DenialFinality,
336
381
  ): void {
337
382
  const queue = this._pendingRefusals.get( instance.config.id ) ?? []
338
383
  queue.push({ intentId, schema, reasonCode, finality })
@@ -419,7 +464,9 @@ export class effectorController {
419
464
  /** POLICY_REAFFERENCE P2 — set when the ack is a policy refusal, so the
420
465
  * ReafferenceEngine routes it to availability rather than competence. */
421
466
  refused?: boolean
422
- finality?: 'class' | 'instance'
467
+ finality?: DenialFinality
468
+ /** ENVELOPE_NARROWING P0 — the bound that was exceeded, if the arbiter said. */
469
+ counterfactual?: PolicyCounterfactual
423
470
  },
424
471
  ): void {
425
472
  const tick = instance.tickCount
@@ -21,6 +21,19 @@ import { logger } from '#core/logger'
21
21
  import type { OutboxMessage } from '#types'
22
22
  import type { SessionLogger } from './session.logger'
23
23
 
24
+ /**
25
+ * Referent → a deliverable address, and the room to use when none was chosen.
26
+ *
27
+ * Returns null when the referent is already an address (nothing to translate) or
28
+ * when the mind holds no route at all — in which case the row goes out as-is and
29
+ * the bridge's own roster fallback still applies, so a message is never silently
30
+ * dropped for want of a handle.
31
+ */
32
+ export type OutboxRouting = (
33
+ targetEntityId: string,
34
+ chosenThread: string | undefined,
35
+ ) => { targetEntityId: string; threadId?: string } | null
36
+
24
37
  /** The caller-supplied fields of an outbox row; the writer stamps id + defaults. */
25
38
  export interface OutboxRow {
26
39
  targetEntityId: string
@@ -43,6 +56,7 @@ export class OutboxWriter {
43
56
  * which made the embedded ids in `conversation.sent` diverge every run).
44
57
  */
45
58
  private _seq = 0
59
+ private _routing: OutboxRouting | null = null
46
60
 
47
61
  constructor( opts: { outbox?: OutboxMessage[]; willId?: string } = {} ){
48
62
  this._outbox = opts.outbox ?? []
@@ -53,6 +67,21 @@ export class OutboxWriter {
53
67
  this._sessionLogger = logger
54
68
  }
55
69
 
70
+ /**
71
+ * Turn a referent into somewhere the world can actually be spoken to.
72
+ *
73
+ * Injected rather than read here, because this writer is deliberately dumb —
74
+ * it holds no state and must stay replay-safe. Assembly closes over the state
75
+ * manager (the same shape as `attachMemorySink`).
76
+ *
77
+ * This is the ONE seam both send paths cross: ProactiveCommunicator's
78
+ * `enqueue()` and AuditionEngine's `enqueueReply()`. Translating anywhere else
79
+ * would mean doing it twice and getting it wrong once.
80
+ */
81
+ attachRouting( resolve: OutboxRouting | null ): void {
82
+ this._routing = resolve
83
+ }
84
+
56
85
  private _genId( suffix = ''): string {
57
86
  return `outbox-${ this._willId }-${ ++this._seq }${ suffix }`
58
87
  }
@@ -63,15 +92,24 @@ export class OutboxWriter {
63
92
  */
64
93
  enqueue( row: OutboxRow, idSuffix = ''): string {
65
94
  const id = this._genId( idSuffix )
95
+ // A `ke:` anchor is who, never where. Resolve it to an address the bridge can
96
+ // deliver to and a room to say it in — and where a room was already chosen
97
+ // (a reply answers into the thread it was asked in), that choice WINS. The
98
+ // mind picking a room is a decision; this is only the fallback for when it
99
+ // made none, and the alternative to the fallback is dropping the message.
100
+ const routed = this._routing?.( row.targetEntityId, row.threadId ) ?? null
101
+ const target = routed?.targetEntityId ?? row.targetEntityId
102
+ const thread = row.threadId ?? routed?.threadId
103
+
66
104
  this._outbox.push({
67
105
  id,
68
- targetEntityId: row.targetEntityId,
106
+ targetEntityId: target,
69
107
  ...( row.targetEntityName !== undefined ? { targetEntityName: row.targetEntityName } : {} ),
70
108
  content: row.content,
71
109
  effectorName: row.effectorName,
72
110
  ...( row.gestureType ? { gestureType: row.gestureType } : {} ),
73
111
  ...( row.replyToMessageId ? { replyToMessageId: row.replyToMessageId } : {} ),
74
- ...( row.threadId ? { threadId: row.threadId } : {} ),
112
+ ...( thread ? { threadId: thread } : {} ),
75
113
  deliveryStatus: 'pending',
76
114
  createdAtTick: 0,
77
115
  createdAt: Date.now(),
@@ -1,102 +0,0 @@
1
- // ─────────────────────────────────────────────────────────────
2
- // src/cognition/faculties/executive.engine/messages.ts
3
- // ─────────────────────────────────────────────────────────────
4
-
5
- import { logger } from '#core/logger'
6
- import type { Tick, ReadonlySimulationState } from '#core/types'
7
- import type { PendingMessage } from '#faculties/executive.engine/types'
8
-
9
- /**
10
- * Message queue state — tracks incoming communication entities,
11
- * pending messages during in-flight LLM calls, and reply deduplication.
12
- */
13
- export class MessageQueue {
14
- /** Messages buffered while an LLM call is in-flight. */
15
- pendingMessages: PendingMessage[] = []
16
-
17
- /** Tick when the current (or most recent) LLM call started. */
18
- pendingCallStartTick: number = -1
19
-
20
- /** Entity IDs of communication entities that have been replied to this session. */
21
- private _repliedEntityIds = new Set<string>()
22
-
23
- /**
24
- * Scan the simulation state for unprocessed communication entities
25
- * and queue them into pendingMessages. Call every tick.
26
- */
27
- scanState( state: ReadonlySimulationState, tick: Tick ): void {
28
- const seenIds = new Set( this.pendingMessages.map( m => m.id ) )
29
-
30
- for( const [ id, entity ] of state.entities ){
31
- if( entity.type !== 'communication') continue
32
- if( entity.metadata?.processedByExecutive ) continue
33
- if( seenIds.has( id ) ) continue
34
-
35
- const msgTick = ( entity.metadata?.tick as number ) ?? 0
36
- this.pendingMessages.push({
37
- id,
38
- content: (entity.metadata?.content as string) ?? '',
39
- sender: (entity.metadata?.agentName as string) ?? 'unknown',
40
- senderId: (entity.metadata?.keid as string) ?? 'unknown',
41
- tick: msgTick
42
- })
43
-
44
- logger.info(`[executive] queued message from ${(entity.metadata?.agentName as string) ?? 'unknown'} (tick=${msgTick})`)
45
- }
46
- }
47
-
48
- /**
49
- * Clear messages that were included in the most recent LLM call.
50
- * Any that arrived after the call started remain for the next cycle.
51
- */
52
- clearProcessedMessages(): void {
53
- this.pendingMessages = this.pendingMessages.filter( m => m.tick > this.pendingCallStartTick )
54
- }
55
-
56
- /**
57
- * Check if we've already replied to a specific communication entity this session.
58
- */
59
- hasRepliedTo( entityId: string ): boolean {
60
- return this._repliedEntityIds.has( entityId )
61
- }
62
-
63
- /**
64
- * Mark a communication entity as replied to.
65
- */
66
- markReplied( entityId: string ): void {
67
- this._repliedEntityIds.add( entityId )
68
- }
69
-
70
- /**
71
- * Build the set of communication entity IDs visible in state within the
72
- * 30-tick window, plus any pending messages. Used to mark them as processed.
73
- */
74
- getVisibleMessageIds( state: ReadonlySimulationState, tick: Tick ): Set<string> {
75
- const ids = new Set<string>( this.pendingMessages.map( m => m.id ) )
76
- for( const [ id, entity ] of state.entities ){
77
- if( entity.type !== 'communication') continue
78
-
79
- const msgTick = (entity.metadata?.tick as number) ?? 0
80
- if( tick - msgTick > 30 ) continue
81
-
82
- ids.add( id )
83
- }
84
-
85
- return ids
86
- }
87
-
88
- /**
89
- * Get stale communication entity IDs (>50 ticks old) for cleanup.
90
- */
91
- getStaleMessageIds( state: ReadonlySimulationState, tick: Tick ): string[] {
92
- const stale: string[] = []
93
- for( const [ id, entity ] of state.entities ){
94
- if( entity.type !== 'communication') continue
95
-
96
- const msgTick = (entity.metadata?.tick as number) ?? 0
97
- tick - msgTick > 50 && stale.push( id )
98
- }
99
-
100
- return stale
101
- }
102
- }