@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
@@ -1,5 +1,5 @@
1
1
  // ─────────────────────────────────────────────────────────────
2
- // src/sdk/will.ts — the ergonomic facade
2
+ // src/surface/sdk/will.ts — the ergonomic facade
3
3
  // ─────────────────────────────────────────────────────────────
4
4
  //
5
5
  // `WillStem` is the full, powerful contract (tick listeners, the outbox drain,
@@ -9,11 +9,11 @@
9
9
  // const will = await Will.create({ name: 'Aria', identity: {...} })
10
10
  // will.on('message', m => console.log(m.content))
11
11
  // will.effector('search_docs', async a => await myDb.search(a.query))
12
- // await will.perceive({ from: 'ada', text: 'What should we work on?' })
12
+ // await will.sense({ from: 'ada', text: 'What should we work on?', provenance: 'exafferent' })
13
13
  // const reply = await will.nextUtterance({ to: 'ada' }) // WillMessage | null
14
14
  // const pma = await will.save() // non-destructive; keeps ticking
15
15
  //
16
- // A Will is a *subject*, not a function: you `perceive` stimuli to it and observe
16
+ // A Will is a *subject*, not a function: you `sense` stimuli to it and observe
17
17
  // its *projections* (message / effector / emotion / state) — you never `await`
18
18
  // a computed return. `nextUtterance` is a thin, honest adapter for callers that
19
19
  // want a reply-shaped await; `null` means the Will chose silence, not an error.
@@ -33,12 +33,14 @@ import { PROVIDER_KEY_ENV, providerKeyFromEnv, type LLMProvider } from '#llm/ind
33
33
  import type { PMASnapshot } from '#pma/index'
34
34
  import type { effectorInvocation } from '#types'
35
35
  import type { EffectorDeclaration, SchemaPrecondition } from '#agency/types'
36
+ import type { PolicyArbiter } from '#stem/policy/arbiter'
37
+ import type { SignalProvenance } from '#senses/provenance'
36
38
 
37
39
  // ── Public surface ────────────────────────────────────────────
38
40
 
39
41
  /**
40
42
  * A stimulus entering the Will's sensory field. A Will is a subject, not a
41
- * function: you don't *call* it with input and await a return — you `perceive`
43
+ * function: you don't *call* it with input and await a return — you `sense`
42
44
  * something to it, and it *may* project a response later (see `nextUtterance`),
43
45
  * coloured by its current state. Silence is a valid, meaningful outcome.
44
46
  */
@@ -54,8 +56,39 @@ export interface Stimulus {
54
56
  * name, the Will knows the person as "someone" until it learns one.
55
57
  */
56
58
  speaker?: string
59
+ /**
60
+ * Whose doing this was: `'exafferent'` (the world), `'reafferent'` (the Will's
61
+ * own act, coming back), or `'unknown'` (you cannot tell).
62
+ *
63
+ * REQUIRED, as of P3. Only you can answer it — nothing inside the mind can
64
+ * tell the echo of its own utterance from a stranger saying the same words,
65
+ * which is why this is asserted at the boundary and never inferred behind it.
66
+ * Most inbound traffic is `'exafferent'`; use `say()`/`tell()`, whose verbs
67
+ * already make that claim, when that is all you mean. Reach for `'reafferent'`
68
+ * when you are feeding back the result of something the Will did — an
69
+ * ability's output, a webhook fired by its own write, a platform echo of a
70
+ * message it sent — and pass `sourceIntentId` if you have it.
71
+ *
72
+ * It was optional for exactly one epoch, defaulting to `'exafferent'`, so that
73
+ * a host migrated ONCE — at P3, alongside the `perceive()` → `sense()` rename
74
+ * — rather than twice. That default was the last surviving instance of the
75
+ * four-state hole the internal types exist to forbid: an omission silently
76
+ * became a claim nobody made. It is gone.
77
+ *
78
+ * If you genuinely cannot tell, say `'unknown'`. That is a different statement
79
+ * from `'exafferent'` and the mind treats it as one: the rupture gate in
80
+ * `action.selector` counts only `'exafferent'` percepts, so a mislabelled echo
81
+ * can interrupt a mind's train of thought with its own words.
82
+ */
83
+ provenance: SignalProvenance
84
+ /** The intent whose enaction caused this, when `provenance` is `'reafferent'`. */
85
+ sourceIntentId?: string
57
86
  /** Conversation/thread id (default = `from`). */
58
87
  thread?: string
88
+ /** True when `thread` is private — just this someone and the Will. See TextMessage.direct. */
89
+ direct?: boolean
90
+ /** What the room is called, e.g. `#general`. A label, not an address. See TextMessage.threadName. */
91
+ threadName?: string
59
92
  }
60
93
 
61
94
  /** A message the Will emitted to someone. */
@@ -66,6 +99,20 @@ export interface WillMessage {
66
99
  content: string
67
100
  /** Entity id the Will addressed (the speaker you used in say()/tell(), or a bond). */
68
101
  to: string
102
+ /**
103
+ * The conversation this belongs to — the `thread` from the `sense()` that
104
+ * prompted it. Absent when the Will spoke unprompted, which genuinely has no
105
+ * thread.
106
+ *
107
+ * WHERE, not just to whom. The engine knew this the whole way down —
108
+ * `OutboxMessage.threadId` carries it — and the projection dropped it here, so
109
+ * a channel adapter had nothing to answer INTO and had to guess from a roster.
110
+ * Observed live: a DM arrived on `discord:1532693…`, she answered it correctly
111
+ * and in seconds, and the reply went to the shared server channel because that
112
+ * was the last room the roster had seen this person in. From the operator's
113
+ * side she had simply ignored him.
114
+ */
115
+ thread?: string
69
116
  }
70
117
 
71
118
  /**
@@ -97,14 +144,75 @@ export interface WillAffect {
97
144
  */
98
145
  export type EffectorResult = string | {
99
146
  success: boolean
147
+ /** How the act WENT — its fate. What the Will learns competence from. */
100
148
  description: string
149
+ /**
150
+ * What the act REVEALED — new information about the world (SIGNAL_BOUNDARY P2).
151
+ *
152
+ * Return it and the Will *perceives* it: it arrives as a reafferent percept
153
+ * tied to the act by `sourceIntentId`, so it is remembered and recallable,
154
+ * not merely learned from. A lookup, a listing, a snapshot has one; a kick or
155
+ * a warning does not — those only have a fate.
156
+ *
157
+ * return { success: true,
158
+ * description: 'Looked up Ada.', // how it went
159
+ * observation: 'Ada joined 3 months ago, …' } // what I found
160
+ *
161
+ * Any shape — a string, a record, a list — and carried WHOLE.
162
+ *
163
+ * SEND WHAT YOU HAVE, NOT WHAT IT MEANS. You are not asked to summarise, and
164
+ * you should not: making meaning by connecting pieces of information is the
165
+ * mind's entire job, and a host that hands over a conclusion has done that
166
+ * work on the wrong side of the boundary. A robot's vision layer reports
167
+ * `{ object: 'ball', confidence: 0.9, bbox: […] }`; it has no business
168
+ * deciding whether that is worth reacting to.
169
+ *
170
+ * The mind labels it with the ability's own name, and sees the data itself in
171
+ * its percepts — state, working memory and the prompt all carry it. If you
172
+ * happen to have a one-line `summary` on your object it is used as the label,
173
+ * but that is a convenience and never a requirement.
174
+ *
175
+ * SIZE IS YOURS TO JUDGE, AND NOTHING TRUNCATES IT. What you send lands in
176
+ * the mind's percepts and, briefly, its prompt — so a very large payload
177
+ * costs tokens on the ticks it is alive. The engine will not second-guess you
178
+ * by cutting it: a cap here decides for a mind how much of an answer it may
179
+ * have, and that decision is not the engine's to make. Send the record; send
180
+ * the field you would want it to notice.
181
+ *
182
+ * Before this, a host with facts to hand back had to return the ack AND call
183
+ * `sense()` separately — two calls for one act, and the second one had to
184
+ * pretend somebody had spoken.
185
+ */
186
+ observation?: unknown
101
187
  metrics?: Record<string, number>
102
188
  }
103
189
 
104
190
  /** Your implementation of an ability the Will can choose to use. */
105
191
  export type EffectorHandler = (
106
192
  args: Record<string, unknown>,
107
- ctx: { reasoning: string; targetEntityId?: string; description?: string },
193
+ ctx: {
194
+ reasoning: string
195
+ /**
196
+ * The `agency.intent` this handler is running under — the correlation handle
197
+ * the Will will match an ack to (SIGNAL_BOUNDARY P1).
198
+ *
199
+ * Pass it as `sourceIntentId` on any `sense()` you make from inside a
200
+ * handler, and the resulting percept is a *reafference* the mind can tie back
201
+ * to the act that caused it, rather than an unexplained arrival. Before this
202
+ * existed, `discord_inspect_channel` had to say so in English — a bracketed
203
+ * `[I looked into #general: …]` that only the LLM could read — because the
204
+ * fact had nowhere structural to live.
205
+ */
206
+ intentId: string
207
+ targetEntityId?: string
208
+ /**
209
+ * The addresses this host knows `targetEntityId` by — a channel id, a user id.
210
+ * `targetEntityId` itself is an opaque anchor (who something IS); these are
211
+ * where to find it. Resolved inside the Will, where the alias table lives.
212
+ */
213
+ targetAddresses?: readonly string[]
214
+ description?: string
215
+ },
108
216
  ) => EffectorResult | Promise<EffectorResult>
109
217
 
110
218
  /**
@@ -164,12 +272,6 @@ export interface CreateWillOptions {
164
272
  identity: Partial<WillIdentity> & { prompt: string }
165
273
  /** 'mind' (default: the whole architecture) | 'reflex' (no-LLM shell). */
166
274
  anatomy?: Anatomy
167
- /** Concrete LLM model id, or a per-role map ({ executive, summarizer?,
168
- * deliberation?, embedding? } — unset thinking roles fall back to executive).
169
- * Unset → env / provider default.
170
- * @deprecated Pass `llmConfig: { model }` instead — model and transport are
171
- * one concern. Still honoured; an explicit `llmConfig.model` wins. */
172
- model?: string | WillModelConfig
173
275
  /** Per-Will LLM config: provider, model(s), BYO apiKey, baseUrl, caps.
174
276
  * Unset fields fall back to WILL_LLM_* envs. apiKey stays in memory only.
175
277
  * (Named llmConfig because `llm` is the provider MODE switch.) */
@@ -319,17 +421,29 @@ export class Will {
319
421
  pma: PMASnapshot,
320
422
  opts: Omit<CreateWillOptions, 'identity'> & { identity?: Partial<WillIdentity> },
321
423
  ): Promise<Will> {
322
- const id = opts.id ?? `${slug( opts.name )}-${Math.random().toString( 36 ).slice( 2, 8 )}`
424
+ // Waking IS being the same mind, so the id continues from the artifact unless the
425
+ // caller overrides it. The id is the path key for everything durable that lives
426
+ // OUTSIDE the artifact — `data/wills/<id>/vector_index`, snapshots, session logs.
427
+ // Minting a fresh `name-<random>` here gave a woken mind a brand-new, empty vector
428
+ // store every boot: identity/beliefs/goals returned (those are in the artifact) while
429
+ // episodic recall came back permanently empty, and the orphaned index was left behind
430
+ // on disk. Observed: four boots, four `lora-*` directories, four 4KB indexes, and a
431
+ // mind that concluded at 100% confidence that its own channel might not be viable
432
+ // because it could not recall a conversation it had just had.
433
+ const id = opts.id ?? pma.willId ?? `${slug( opts.name )}-${Math.random().toString( 36 ).slice( 2, 8 )}`
323
434
  const stem = new WillStem()
324
435
  const will = new Will( stem, id, opts.name )
325
436
 
326
437
  for( const [ name, entry ] of Object.entries( opts.effectors ?? {} ) )
327
438
  will._register( name, entry )
328
439
 
329
- await stem.createWill(
330
- will._buildConfig( id, { ...opts, identity: { prompt: '', ...opts.identity } } ),
331
- true /* startPaused load the artifact before the first tick */,
332
- )
440
+ const config = will._buildConfig( id, { ...opts, identity: { prompt: '', ...opts.identity } } )
441
+ // Say out loud that this identity is a placeholder. Otherwise the creation
442
+ // guard warns about the empty persona we just constructed on purpose — three
443
+ // alarms per wake, drowning the real check that runs when the artifact loads.
444
+ config.identityFromArtifact = true
445
+
446
+ await stem.createWill( config, true /* startPaused — load the artifact before the first tick */ )
333
447
  stem.loadPMA( id, pma )
334
448
  stem.resumeWill( id )
335
449
  will._attach()
@@ -344,10 +458,18 @@ export class Will {
344
458
  * *delivered*, NOT once the Will has responded: a response (if any) is a
345
459
  * projection that arrives later on the `message` event, or via
346
460
  * `nextUtterance()`. The Will may also stay silent — that is not an error.
461
+ *
462
+ * NAMED `sense`, NOT `perceive` (SIGNAL_BOUNDARY P3). What happens here is
463
+ * *transduction*: a signal crosses into the mind. PERCEPTION is what a sense
464
+ * engine does afterwards — audition runs, judges salience, and produces a
465
+ * `Percept`, which may not resemble what arrived and may not happen at all
466
+ * (a gated sense drops it). Calling the door `perceive` said the caller had
467
+ * already done the mind's work, and it misled every reader of this flow,
468
+ * including the first draft of the epoch that renamed it.
347
469
  */
348
- async perceive( stimulus: Stimulus ): Promise<void> {
470
+ async sense( stimulus: Stimulus ): Promise<void> {
349
471
  const from = stimulus.from ?? 'user'
350
- await this.stem.ingestText( this.id, {
472
+ await this.stem.senseText( this.id, {
351
473
  kind: 'text',
352
474
  entityId: from,
353
475
  threadId: stimulus.thread ?? from,
@@ -358,17 +480,34 @@ export class Will {
358
480
  // the Will knows the person as "someone" until a real one is learned. (The live
359
481
  // conversation focus still falls back to the entity id for its Speaker line.)
360
482
  ...( stimulus.speaker ? { speakerName: stimulus.speaker } : {} ),
483
+ // Omitted rather than defaulted: an unknown room is not known to be public.
484
+ ...( stimulus.direct !== undefined ? { direct: stimulus.direct } : {} ),
485
+ // Omitted when the channel does not know — a room with no name stays
486
+ // unnamed, the same way a person does, rather than being labelled with its id.
487
+ ...( stimulus.threadName ? { threadName: stimulus.threadName } : {} ),
488
+ // No default. `Stimulus.provenance` is required now, so there is nothing
489
+ // left to fall back to — the four-state hole is closed at every door into
490
+ // this mind, which was the point of the epoch.
491
+ provenance: stimulus.provenance,
492
+ ...( stimulus.sourceIntentId ? { sourceIntentId: stimulus.sourceIntentId } : {} ),
361
493
  } )
362
494
  }
363
495
 
364
- /** Perceive from the default user. Sugar over `perceive`. */
496
+ /**
497
+ * Sense from the default user. Sugar over `sense`.
498
+ *
499
+ * Supplies `provenance: 'exafferent'` — that is not a default sneaking back
500
+ * in, it is what the verb MEANS. "Say" is somebody speaking to the Will; a
501
+ * caller who wants to feed back the Will's own act reaches for `sense` and
502
+ * says so. The assertion lives in the function name.
503
+ */
365
504
  async say( text: string ): Promise<void> {
366
- return this.perceive( { text, from: 'user' } )
505
+ return this.sense( { text, from: 'user', provenance: 'exafferent' } )
367
506
  }
368
507
 
369
- /** Perceive from a specific interlocutor (multi-party). Sugar over `perceive`. */
508
+ /** Sense from a specific interlocutor (multi-party). Sugar over `sense` — see `say` on provenance. */
370
509
  async tell( entityId: string, speakerName: string, text: string ): Promise<void> {
371
- return this.perceive( { text, from: entityId, speaker: speakerName } )
510
+ return this.sense( { text, from: entityId, speaker: speakerName, provenance: 'exafferent' } )
372
511
  }
373
512
 
374
513
  /**
@@ -378,7 +517,7 @@ export class Will {
378
517
  * 5000). `null` is a real outcome — the Will chose not to speak — not a
379
518
  * failure. Pass `to` to only accept an utterance addressed to that entity.
380
519
  *
381
- * await will.perceive( { from: 'ada', text: 'Hi!' } )
520
+ * await will.sense( { from: 'ada', text: 'Hi!', provenance: 'exafferent' } )
382
521
  * const reply = await will.nextUtterance( { to: 'ada', within: 3000 } )
383
522
  * // reply is a WillMessage, or null if Ada got the silent treatment.
384
523
  */
@@ -485,6 +624,19 @@ export class Will {
485
624
  pause(): void { this.stem.pauseWill( this.id ) }
486
625
  resume(): void { this.stem.resumeWill( this.id ) }
487
626
 
627
+
628
+ /**
629
+ * Install the Policy Decision Point consulted before every effector
630
+ * invocation this Will hands to the host (POLICY_REAFFERENCE P0). `null`
631
+ * restores the no-op default. See `WillStem.setArbiter` for the one scoping
632
+ * caveat (per-stem, not per-Will id) — irrelevant here since `Will.create()`
633
+ * gives this instance its own dedicated stem.
634
+ */
635
+ setArbiter( arbiter: PolicyArbiter | null ): this {
636
+ this.stem.setArbiter( arbiter )
637
+ return this
638
+ }
639
+
488
640
  /**
489
641
  * Checkpoint the living mind into a portable PMA artifact — NON-destructive.
490
642
  * The Will keeps ticking; the snapshot is a point-in-time copy you can archive
@@ -525,16 +677,14 @@ export class Will {
525
677
  const mode = opts.llm ?? detectProvider()
526
678
  const useMock = mode === 'mock'
527
679
  // `llm` selects the provider; an explicit llmConfig.provider still wins.
528
- // Model rides with the transport: `llmConfig.model` is canonical, the
529
- // top-level `opts.model` is the deprecated spelling of the same thing.
530
- const llmConfig: WillLLMConfig | undefined = useMock && !opts.llmConfig && opts.model === undefined
680
+ // Model rides with the transport `llmConfig.model`, and only that. The
681
+ // top-level `model` spelling is gone: two ways to say one thing is how a
682
+ // config grows a precedence rule nobody can remember.
683
+ const llmConfig: WillLLMConfig | undefined = useMock && !opts.llmConfig
531
684
  ? undefined
532
685
  : {
533
686
  ...( mode !== 'mock' ? { provider: mode } : {} ),
534
687
  ...opts.llmConfig,
535
- ...( opts.llmConfig?.model !== undefined ? { model: opts.llmConfig.model }
536
- : opts.model !== undefined ? { model: opts.model }
537
- : {} ),
538
688
  }
539
689
  return {
540
690
  id, name: opts.name,
@@ -561,7 +711,10 @@ export class Will {
561
711
  this._unsub = this.stem.addTickListener( this.id, ( _snapshot, _tick, outbox, invocations ) => {
562
712
  // Outbound messages → event + auto delivery-ack.
563
713
  for( const msg of outbox ){
564
- this._emitMessage( { id: msg.id, content: msg.content, to: msg.targetEntityId } )
714
+ this._emitMessage( {
715
+ id: msg.id, content: msg.content, to: msg.targetEntityId,
716
+ ...( msg.threadId ? { thread: msg.threadId } : {} ),
717
+ } )
565
718
  try { this.stem.confirmMessageDelivery( this.id, msg.id, true ) } catch { /* best-effort */ }
566
719
  }
567
720
  // Effector invocations → project the motor act, then run the handler → ack.
@@ -589,7 +742,7 @@ export class Will {
589
742
  if( !handler ){
590
743
  // The Will chose an effector we have no handler for — report it as failed so
591
744
  // the reafference loop learns it doesn't work, rather than hanging on the await.
592
- this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, {
745
+ this.stem.confirmEffectorExecution( this.id, inv.intentId, {
593
746
  success: false, description: `No handler registered for effector "${inv.effectorName}"`,
594
747
  } )
595
748
  return
@@ -598,15 +751,23 @@ export class Will {
598
751
  try {
599
752
  const raw = await handler( inv.parameters, {
600
753
  reasoning: inv.reasoning,
754
+ // The correlation handle, so a handler that feeds its own result back in
755
+ // can say WHICH act caused it. This line used to read
756
+ // `intentId: inv.decisionRecordId` — the translation that proved the
757
+ // wire name was already wrong.
758
+ intentId: inv.intentId,
601
759
  targetEntityId: inv.targetEntityId,
760
+ // Which of the host's own ids that referent is — without it a handler
761
+ // gets an opaque anchor and nothing it can look up.
762
+ ...( inv.targetAddresses?.length ? { targetAddresses: inv.targetAddresses } : {} ),
602
763
  ...( inv.description ? { description: inv.description } : {} ),
603
764
  } )
604
765
  const result = typeof raw === 'string' ? { success: true, description: raw } : raw
605
- this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, result )
766
+ this.stem.confirmEffectorExecution( this.id, inv.intentId, result )
606
767
  }
607
768
  catch( err ){
608
769
  this._emitError( err instanceof Error ? err : new Error( String( err ) ) )
609
- this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, {
770
+ this.stem.confirmEffectorExecution( this.id, inv.intentId, {
610
771
  success: false, description: `Effector "${inv.effectorName}" threw: ${( err as Error ).message}`,
611
772
  } )
612
773
  }
@@ -1,5 +1,5 @@
1
1
  // ─────────────────────────────────────────────────────────────
2
- // src/serve/server.ts — a Will, exposed over plain HTTP (the sidecar)
2
+ // src/surface/serve/server.ts — a Will, exposed over plain HTTP (the sidecar)
3
3
  // ─────────────────────────────────────────────────────────────
4
4
  //
5
5
  // `will serve` for hosts that aren't Node and aren't MCP clients — a Python
@@ -23,8 +23,9 @@
23
23
  import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http'
24
24
  import { mkdirSync, writeFileSync } from 'node:fs'
25
25
  import { dirname } from 'node:path'
26
- import type { Will } from '#sdk/will'
27
- import { UtteranceTap } from '#root/host/utterances'
26
+ import type { Will } from '#surface/sdk/will'
27
+ import { asProvenance } from '#senses/provenance'
28
+ import { UtteranceTap } from '#surface/host/utterances'
28
29
 
29
30
  export interface WillHttpOptions {
30
31
  /** Where POST /save writes the PMA artifact. */
@@ -98,10 +99,16 @@ export function buildWillHttpServer( will: Will, opts: WillHttpOptions = {} ): S
98
99
  const body = await readJsonBody( req )
99
100
  const text = typeof body.text === 'string' ? body.text : ''
100
101
  if( !text ) return json( res, 400, { error: 'text is required' } )
101
- await will.perceive( {
102
+ await will.sense( {
102
103
  text,
104
+ // Untyped ingress — a JSON body cannot be type-checked, and a client
105
+ // that predates the field has not claimed anything. asProvenance()
106
+ // owns the direction; see its comment for why 'exafferent' and not
107
+ // the tidier-looking 'unknown'.
108
+ provenance: asProvenance( body.provenance ),
103
109
  ...( typeof body.from === 'string' ? { from: body.from } : {} ),
104
110
  ...( typeof body.speaker === 'string' ? { speaker: body.speaker } : {} ),
111
+ ...( typeof body.sourceIntentId === 'string' ? { sourceIntentId: body.sourceIntentId } : {} ),
105
112
  } )
106
113
  // 202: delivered into the sensory field — NOT answered. A response, if
107
114
  // any, arrives on /utterances or /next-utterance; silence is valid.
package/src/types.ts CHANGED
@@ -22,12 +22,33 @@ export interface effectorInvocation {
22
22
  id: string
23
23
  /** Correlation handle — the awaiting `agency.intent` id. Echo it when POSTing to
24
24
  * `POST /v1/wills/:id/effectors/invoked/ack`; the Will reconciles the result onto
25
- * that intent. (Field name kept for wire-contract stability; no longer a
26
- * `decision.record` id since the agency cutover.) */
27
- decisionRecordId: string
25
+ * that intent.
26
+ *
27
+ * Called `decisionRecordId` until the aliases came out. It stopped being a
28
+ * `decision.record` id at the agency cutover and was kept anyway "for
29
+ * wire-contract stability" — so the wire was stable and wrong, naming a
30
+ * record type that no longer exists, while the SDK translated it to
31
+ * `intentId` one hop later for the handler. Stability that preserves a false
32
+ * name is preserving the wrong thing. */
33
+ intentId: string
28
34
  effectorName: string
29
35
  parameters: Record<string, unknown>
30
36
  targetEntityId: string | undefined
37
+ /**
38
+ * The addresses the world knows `targetEntityId` by.
39
+ *
40
+ * `targetEntityId` is an ANCHOR (`ke:1sqlkux`) — since 0.9.0 identity is opaque
41
+ * and deliberately not an address, because who someone is and where to find them
42
+ * are different facts. A host holds channel ids and knows nothing of anchors, so
43
+ * an invocation naming only the referent is one no surface can act on. Resolved
44
+ * inside, where the alias table lives, exactly as the outbox resolves a message
45
+ * addressed to a referent.
46
+ *
47
+ * Outbound only: this is the mind saying which of its own handles it means.
48
+ * Nothing here lets a surface name someone TO the mind — that is what perception
49
+ * is for.
50
+ */
51
+ targetAddresses?: readonly string[]
31
52
  reasoning: string
32
53
  /** The ability's declared meaning (from its EffectorDeclaration), when present. */
33
54
  description?: string
@@ -1,69 +0,0 @@
1
- import { W as Will } from '../will-cS6k4uiJ.js';
2
- import { C as ChannelBridge } from '../types-E9-HV-SW.js';
3
-
4
- interface DiscordLikeChannel {
5
- send(content: string): Promise<unknown>;
6
- sendTyping?(): Promise<unknown>;
7
- }
8
- interface DiscordLikeMessage {
9
- content: string;
10
- cleanContent?: string;
11
- channelId: string;
12
- guildId?: string | null;
13
- author: {
14
- id: string;
15
- bot?: boolean;
16
- username?: string;
17
- displayName?: string;
18
- };
19
- member?: {
20
- displayName?: string;
21
- } | null;
22
- mentions?: {
23
- has(userId: string): boolean;
24
- };
25
- channel: DiscordLikeChannel;
26
- }
27
- interface DiscordLikeClient {
28
- user: {
29
- id: string;
30
- setPresence?(p: unknown): void;
31
- } | null;
32
- /** discord.js ≥14.22; polled so we needn't subscribe to the deprecated `ready`. */
33
- isReady?(): boolean;
34
- on(event: 'messageCreate', fn: (m: DiscordLikeMessage) => void): unknown;
35
- once(event: string, fn: () => void): unknown;
36
- login(token: string): Promise<unknown>;
37
- destroy(): Promise<unknown> | void;
38
- channels: {
39
- fetch(id: string): Promise<unknown>;
40
- };
41
- users: {
42
- fetch(id: string): Promise<{
43
- send(content: string): Promise<unknown>;
44
- }>;
45
- };
46
- }
47
- interface DiscordBridgeOptions {
48
- /** Bot token (Discord developer portal). Unused when `client` is injected pre-logged-in. */
49
- token?: string;
50
- /** Channel ids the Will inhabits. Unset = every channel it can see. */
51
- channels?: string[];
52
- /** Perceive guild messages only when the Will is @mentioned (DMs always perceived). */
53
- mentionOnly?: boolean;
54
- /** Fallback channel for utterances with no reachable addressee. */
55
- homeChannelId?: string;
56
- /** Roster path (default: ./.will/<willId>.discord.json). */
57
- rosterPath?: string;
58
- /** Test / power-user seam: bring your own client; discord.js is never imported. */
59
- client?: DiscordLikeClient;
60
- log?: (msg: string) => void;
61
- }
62
- /**
63
- * Connect a Will to Discord. Resolves once the bridge is live (logged in and
64
- * relaying). Close it via the returned `ChannelBridge.close()` — the Will
65
- * itself is not stopped; it simply loses this surface.
66
- */
67
- declare function connectDiscord(will: Will, opts: DiscordBridgeOptions): Promise<ChannelBridge>;
68
-
69
- export { type DiscordBridgeOptions, type DiscordLikeChannel, type DiscordLikeClient, type DiscordLikeMessage, connectDiscord };