@mindot/will 0.9.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 (110) hide show
  1. package/README.md +15 -11
  2. package/dist/index.d.ts +188 -141
  3. package/dist/index.js +16660 -15206
  4. package/dist/index.js.map +1 -1
  5. package/dist/{channels → surface/channels}/discord.d.ts +50 -3
  6. package/dist/{channels → surface/channels}/discord.js +102 -7
  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 +7 -4
  10. package/dist/surface/channels/whatsapp.js.map +1 -0
  11. package/dist/{cli.js → surface/cli.js} +4406 -2904
  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-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
  17. package/package.json +11 -12
  18. package/src/cognition/agency/consequence.ts +237 -3
  19. package/src/cognition/agency/conversation.aim.ts +32 -0
  20. package/src/cognition/agency/engines/action.selector.ts +59 -3
  21. package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
  22. package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
  23. package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
  24. package/src/cognition/agency/engines/reafference.engine.ts +43 -4
  25. package/src/cognition/agency/execution.primitives.ts +17 -4
  26. package/src/cognition/agency/reconcile.learning.ts +5 -4
  27. package/src/cognition/agency/schemas/innate.ts +96 -1
  28. package/src/cognition/agency/schemas/repertoire.ts +19 -1
  29. package/src/cognition/agency/selection.scoring.ts +16 -0
  30. package/src/cognition/agency/settlement.ts +203 -0
  31. package/src/cognition/agency/types.ts +38 -1
  32. package/src/cognition/config.mirror.entities.ts +0 -1
  33. package/src/cognition/event.schemas.ts +0 -8
  34. package/src/cognition/faculties/affective.blender.ts +21 -4
  35. package/src/cognition/faculties/circadian.oscillator.ts +36 -7
  36. package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
  37. package/src/cognition/faculties/executive.engine/context.ts +41 -35
  38. package/src/cognition/faculties/executive.engine/engine.ts +270 -113
  39. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
  40. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
  41. package/src/cognition/faculties/executive.engine/parser.ts +100 -38
  42. package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
  43. package/src/cognition/faculties/executive.engine/types.ts +18 -15
  44. package/src/cognition/faculties/exteroception.ts +99 -98
  45. package/src/cognition/faculties/goal.manager.ts +50 -2
  46. package/src/cognition/faculties/known.entity.tracker.ts +22 -3
  47. package/src/cognition/faculties/planning.engine/engine.ts +4 -4
  48. package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
  49. package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
  50. package/src/cognition/faculties/working.memory.ts +6 -1
  51. package/src/cognition/instruction.handler.ts +1 -1
  52. package/src/cognition/memory/vector.embedder.ts +1 -3
  53. package/src/cognition/percept.entity.ts +122 -0
  54. package/src/cognition/sense.boundary.ts +176 -0
  55. package/src/cognition/senses/audition.engine/engine.ts +97 -16
  56. package/src/cognition/senses/base.sense.engine.ts +104 -6
  57. package/src/cognition/senses/index.ts +71 -13
  58. package/src/cognition/senses/provenance.ts +128 -0
  59. package/src/cognition/senses/somatosensation.engine.ts +117 -9
  60. package/src/cognition/social.identity.ts +23 -0
  61. package/src/cognition/utilities/token.tracker.ts +36 -5
  62. package/src/core/orchestrator.ts +13 -0
  63. package/src/index.ts +39 -5
  64. package/src/llm/gate.ts +48 -0
  65. package/src/llm/index.ts +102 -25
  66. package/src/llm/wire.contracts.ts +38 -0
  67. package/src/pma/index.ts +1 -1
  68. package/src/stem/index.ts +92 -21
  69. package/src/stem/mind.ts +26 -2
  70. package/src/stem/policy/arbiter.ts +10 -0
  71. package/src/stem/profiles/built-in.ts +7 -0
  72. package/src/{profiles → stem/profiles}/companion.ts +1 -1
  73. package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
  74. package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
  75. package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
  76. package/src/{profiles → stem/profiles}/index.ts +1 -1
  77. package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
  78. package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
  79. package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
  80. package/src/stem/tracts/effector/types.ts +110 -0
  81. package/src/stem/tracts/effector.controller.ts +112 -401
  82. package/src/stem/tracts/outbox.controller.ts +30 -17
  83. package/src/stem/tracts/sensory.controller.ts +6 -6
  84. package/src/stem/tracts/session.logger.ts +6 -1
  85. package/src/stem/tracts/transport/types.ts +20 -1
  86. package/src/stem/tracts/transport.controller.ts +33 -9
  87. package/src/{channels → surface/channels}/discord.ts +224 -7
  88. package/src/{channels → surface/channels}/roster.ts +1 -1
  89. package/src/{channels → surface/channels}/types.ts +1 -1
  90. package/src/{channels → surface/channels}/whatsapp.ts +9 -6
  91. package/src/{cli.ts → surface/cli.ts} +6 -6
  92. package/src/{host → surface/host}/boot.ts +3 -3
  93. package/src/{host → surface/host}/utterances.ts +2 -2
  94. package/src/{mcp → surface/mcp}/effectors.ts +19 -6
  95. package/src/{mcp → surface/mcp}/server.ts +19 -5
  96. package/src/{sdk → surface/sdk}/will.ts +156 -28
  97. package/src/{serve → surface/serve}/server.ts +11 -4
  98. package/src/types.ts +24 -3
  99. package/dist/channels/discord.js.map +0 -1
  100. package/dist/channels/whatsapp.js.map +0 -1
  101. package/dist/cli.js.map +0 -1
  102. package/dist/mcp/effectors.js.map +0 -1
  103. package/src/extensions/livestream.ext.ts +0 -570
  104. package/src/extensions/time.ext.ts +0 -339
  105. package/src/profiles/built-in.ts +0 -7
  106. package/src/runners/coherence.runner.ts +0 -49
  107. package/src/runners/outreach.runner.ts +0 -155
  108. package/src/runners/social.runner.ts +0 -179
  109. package/src/runners/thin-shim.runner.ts +0 -205
  110. /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
@@ -0,0 +1,128 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/senses/provenance.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Signal provenance — the boundary vocabulary for "whose doing was this?"
7
+ * (SIGNAL_BOUNDARY P0a).
8
+ *
9
+ * Its own module, not part of `senses/index`, for one concrete reason:
10
+ * `BaseSenseEngine` needs the VALUE `provenanceOf` at the emit chokepoint, and
11
+ * `senses/index` re-exports `BaseSenseEngine` — importing it from there would
12
+ * be a runtime import cycle rather than the erased type-only one that was there
13
+ * before. `senses/index` re-exports everything here, so the public path is
14
+ * unchanged.
15
+ */
16
+
17
+ /**
18
+ * Whether a signal arriving at the senses was caused by this mind's own
19
+ * efference (SIGNAL_BOUNDARY P0a).
20
+ *
21
+ * • 'exafferent' — the world did this. Nothing of mine preceded it.
22
+ * • 'reafferent' — I did this, and I am now sensing the consequence.
23
+ * • 'unknown' — the source cannot say.
24
+ *
25
+ * There is no fourth, silent state — see `SensorySignal`.
26
+ *
27
+ * ONE CONCEPT, TWO WAYS OF ARRIVING AT IT. This type is shared with the percept
28
+ * entities `exteroception` and `outbox.controller` write, which used the same
29
+ * two spellings before it existed (EXAFFERENCE P2/P3), and it is deliberately
30
+ * not "asserted only":
31
+ *
32
+ * • At the WORLD door, provenance is INFERRED and that is correct — the mind
33
+ * holds both sides. `Exteroception` matches an observed change against its
34
+ * own live consequence descriptors (`matchConsequenceText` /
35
+ * `matchConsequenceEntity`) and tags the hit `'reafferent'`.
36
+ * • At the SENSE door, provenance must be ASSERTED — there is nothing to
37
+ * match against. A host's Discord message and the mind's own echo of it are
38
+ * identical from the inside; only the host can tell them apart.
39
+ *
40
+ * What the two share is the consequence, and it is load-bearing:
41
+ * `action.selector`'s rupture gate counts only `'exafferent'` percepts, so
42
+ * anything tagged (or defaulted) as mine can never rupture a commitment.
43
+ */
44
+ export type SignalProvenance = 'exafferent' | 'reafferent' | 'unknown'
45
+
46
+ /**
47
+ * What EVERY sensory signal carries, whatever kind it is — the provenance stamp.
48
+ *
49
+ * It lives on the input rather than being derived downstream because only the
50
+ * host knows. A Discord bridge knows that the message it just received is the
51
+ * webhook echo of the message the mind sent; nothing inside the mind can tell
52
+ * that from a stranger saying the same words. So the boundary asserts it, and
53
+ * the mind trusts the assertion.
54
+ *
55
+ * WHY `provenance` IS REQUIRED AND HAS NO DEFAULT. It shipped optional first,
56
+ * defaulting to `'exafferent'`, and that was wrong for a reason worth keeping
57
+ * written down. This codebase makes a field optional when absence is the ONLY
58
+ * way to say a third thing: `speakerName?` (no string means "no name learned"),
59
+ * `direct?` (no boolean means "the channel did not say"). Provenance already
60
+ * HAS its third thing — `'unknown'`. So optionality bought a fourth state that
61
+ * was behaviourally identical to `'exafferent'` and epistemically its opposite:
62
+ * a claim that the world did this, made by nobody. Worse, it was lossy in the
63
+ * one direction that costs — a reader could never separate "the host asserts
64
+ * exafference" from "the host said nothing", which want different treatment
65
+ * downstream, and `'unknown'` exists precisely to keep them apart.
66
+ *
67
+ * The related-but-different precedent is `asFinality()`. That one DOES default,
68
+ * because it normalizes an `unknown` read back off entity metadata or a host
69
+ * ack, where the type system cannot help and something must be chosen. Here the
70
+ * type system can help, so it does. Should provenance ever need reading off a
71
+ * tape, that reader gets its own `asFinality`-shaped normalizer, and the
72
+ * direction of ITS fallback is `'exafferent'`: a percept wrongly marked as mine
73
+ * is attenuated and can never rupture a commitment, so a mind that mislabels
74
+ * the world as its own doing goes quiet about real events. Erring the other way
75
+ * costs only a little redundant salience.
76
+ *
77
+ * `sourceIntentId` stays optional by the same rule that made `provenance`
78
+ * required: there is no id meaning "no intent", so absence is the only way to
79
+ * say it, and it is meaningless unless `provenance` is `'reafferent'`.
80
+ */
81
+ export interface SensorySignal {
82
+ /** Mine, the world's, or honestly unknown. Asserted by the host — never inferred. */
83
+ provenance: SignalProvenance
84
+ /**
85
+ * The `agency.intent` id this is the consequence of. Only meaningful when
86
+ * `provenance` is `'reafferent'`; the correlation handle a later mechanism
87
+ * needs to ask *"is this the echo of that?"* (`ACT_EXPECTATIONS`).
88
+ */
89
+ sourceIntentId?: string
90
+ }
91
+
92
+ /**
93
+ * A percept as a sense engine BUILDS it — provenance omitted, because it is not
94
+ * the engine's to write. `publishPercept()` applies the host's assertion, so
95
+ * this type is what makes laundering a compile error rather than a runtime one.
96
+ */
97
+ export type Transduced<P extends { provenance: SignalProvenance }> =
98
+ Omit<P, 'provenance' | 'sourceIntentId'>
99
+
100
+ /**
101
+ * Normalize an UNTYPED provenance — one arriving over a protocol the type
102
+ * system cannot police: an MCP tool call, an HTTP body, a wire envelope, a
103
+ * value read back off a snapshot.
104
+ *
105
+ * This is the `asFinality()` situation, and the ONLY situation in which
106
+ * provenance has a default. Inside the package the field is required, because
107
+ * there the compiler can make a producer answer. At an untyped boundary it
108
+ * cannot, something must be chosen, and every such read goes through here
109
+ * rather than comparing string literals at the call site.
110
+ *
111
+ * THE DEFAULT IS 'exafferent', AND NEVER 'reafferent'. A percept wrongly marked
112
+ * as mine is attenuated on the exafference path and can never rupture a
113
+ * commitment — so a mind that mislabels the world as its own doing goes quiet
114
+ * about real events, which is the failure that matters. Erring the other way
115
+ * costs only a little redundant salience. 'reafferent' is a claim only a caller
116
+ * that actually knows can make.
117
+ *
118
+ * Note it does NOT fall back to 'unknown', which would be the tidy-looking
119
+ * choice. 'unknown' is an assertion too — "I looked and I cannot tell" — and a
120
+ * caller that simply did not send the field has not looked. Where the ABSENCE
121
+ * is structural rather than a caller's omission (a wire envelope with no such
122
+ * field), say 'unknown' at that site explicitly instead of routing through here.
123
+ */
124
+ export function asProvenance( raw: unknown ): SignalProvenance {
125
+ return raw === 'reafferent' ? 'reafferent'
126
+ : raw === 'unknown' ? 'unknown'
127
+ : 'exafferent'
128
+ }
@@ -3,21 +3,129 @@
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
5
  /**
6
- * SomatosensationEngine [SHELL] touch / physical-world interaction.
6
+ * SomatosensationEngine — the sense that feels things happen TO the mind.
7
7
  *
8
- * Handles webhook events, system signals, and external API callbacks Will's
9
- * "touch", its awareness of interaction with external systems. Future: route
10
- * incoming webhooks as percepts into ThreatEvaluator / RewardEvaluator.
8
+ * Webhooks, system signals, external callbacks: not something said, not
9
+ * something seen, but the world touching the mind directly. It was the first
10
+ * shell to be implemented because the wake event needed a door — SIGNAL_BOUNDARY
11
+ * P1 — and a hand-written percept in `stem/index.ts` was the bypass that proved
12
+ * the door was missing.
11
13
  *
12
- * Currently a structural stub `_perceive()` (inherited from ShellSenseEngine)
13
- * logs a warning and returns. The base handles gate / filter / publish / boilerplate.
14
+ * WHAT MAKES THIS A SENSE RATHER THAN A HELPER. It does nothing the other senses
15
+ * do not: transduce an input into a `Percept` with a salience and a summary, and
16
+ * hand it to `publishPercept`, which stamps provenance from the host's assertion
17
+ * and lays down the trace. Everything downstream — the rupture gate, working
18
+ * memory, the executive prompt, novelty — receives it because it is a percept,
19
+ * not because anyone wired those five places to a wake event.
20
+ *
21
+ * That is the whole point of a door: the wake event stops being special.
14
22
  */
15
23
 
16
- import type { SensoryInput } from '#senses/index'
17
- import { ShellSenseEngine } from '#senses/base.sense.engine'
24
+ import type { SensoryInput, SystemSignal, WebhookEvent, Percept, Transduced } from '#senses/index'
25
+ import { BaseSenseEngine } from '#senses/base.sense.engine'
26
+ import { PERCEPT_SUMMARY_CAP } from '#cognition/percept.entity'
27
+
28
+ /**
29
+ * How loud a system signal is by default.
30
+ *
31
+ * A signal arrives because something HAPPENED to the mind — a host woke it, a
32
+ * webhook fired — so it outranks the ambient world-change percepts
33
+ * `exteroception` produces at 0.3, and clears `action.selector`'s rupture gate
34
+ * (0.4). A host that knows better says so with `salience` on the input.
35
+ */
36
+ export const SYSTEM_SIGNAL_SALIENCE = 0.75
18
37
 
19
- export class SomatosensationEngine extends ShellSenseEngine {
38
+ export class SomatosensationEngine extends BaseSenseEngine {
20
39
  readonly name = 'somatosensation-engine'
21
40
  readonly domain = 'somatosensation' as const
22
41
  protected readonly acceptedKinds = new Set<SensoryInput['kind']>( [ 'webhook', 'system' ] )
42
+
43
+ protected async _perceive( input: SensoryInput ): Promise<void> {
44
+ const percept: Transduced<Percept> = input.kind === 'system'
45
+ ? this._fromSignal( input as SystemSignal )
46
+ : this._fromWebhook( input as WebhookEvent )
47
+
48
+ this.publishPercept( percept, input )
49
+ }
50
+
51
+ private _fromSignal( s: SystemSignal ): Transduced<Percept> {
52
+ return {
53
+ domain: this.domain,
54
+ // The signal IS the source: nothing in the world sent it, the substrate did.
55
+ sourceEntityId: `system:${ s.signal }`,
56
+ timestamp: 0,
57
+ salience: salienceOf( s.data, SYSTEM_SIGNAL_SALIENCE ),
58
+ summary: labelFor( s.signal, s.data ),
59
+ ...( s.data !== undefined && s.data !== null ? { data: s.data } : {} ),
60
+ raw: s,
61
+ }
62
+ }
63
+
64
+ private _fromWebhook( w: WebhookEvent ): Transduced<Percept> {
65
+ return {
66
+ domain: this.domain,
67
+ sourceEntityId: `webhook:${ w.source }`,
68
+ timestamp: 0,
69
+ salience: salienceOf( w.payload, SYSTEM_SIGNAL_SALIENCE ),
70
+ summary: labelFor( w.source, w.payload ),
71
+ ...( w.payload !== undefined && w.payload !== null ? { data: w.payload } : {} ),
72
+ raw: w,
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * A LABEL for a signal — what arrived, not what it means.
79
+ *
80
+ * The mind makes the meaning; this only has to make the percept legible enough
81
+ * to be noticed and connected to its data. A HOST IS NEVER ASKED FOR PROSE:
82
+ * demanding a sentence from a robot's control layer puts the mind's own work on
83
+ * the wrong side of the integration boundary, and a lidar driver has no business
84
+ * describing what a scan implies.
85
+ *
86
+ * The signal's own NAME is the hint, and it is free — `discord_server_snapshot`,
87
+ * `WAKE`, `lidar.scan` already say what kind of thing this is. A host that
88
+ * happens to have words may put a `summary` on its data and they are used
89
+ * instead; it is an option, never an obligation.
90
+ *
91
+ * Bounded by `PERCEPT_SUMMARY_CAP` because the ENGINE writes it. The data it
92
+ * labels is beside it, whole and uncapped.
93
+ */
94
+ function labelFor( signal: string, data: unknown ): string {
95
+ const words = hostWords( data )
96
+ if( words ) return words.length > PERCEPT_SUMMARY_CAP
97
+ ? `${ words.slice( 0, PERCEPT_SUMMARY_CAP - 1 ) }\u2026` : words
98
+
99
+ const rendered = compact( data )
100
+ const label = rendered ? `${ signal }: ${ rendered }` : `Something happened: ${ signal }.`
101
+ return label.length > PERCEPT_SUMMARY_CAP
102
+ ? `${ label.slice( 0, PERCEPT_SUMMARY_CAP - 1 ) }\u2026` : label
103
+ }
104
+
105
+ /** A host's own words, if it chose to offer any. Optional, never required. */
106
+ function hostWords( data: unknown ): string | undefined {
107
+ if( typeof data === 'string') return data.length > 0 ? data : undefined
108
+ if( typeof data === 'object' && data !== null && !Array.isArray( data ) ){
109
+ const s = ( data as Record<string, unknown> )['summary']
110
+ if( typeof s === 'string' && s.length > 0 ) return s
111
+ }
112
+ return undefined
113
+ }
114
+
115
+ /** A glance at the shape, for the label only. The whole thing rides in `data`. */
116
+ function compact( data: unknown ): string | undefined {
117
+ if( data === undefined || data === null ) return undefined
118
+ if( typeof data !== 'object') return String( data )
119
+ try {
120
+ const json = JSON.stringify( data )
121
+ return json === '{}' || json === '[]' ? undefined : json
122
+ }
123
+ catch { return undefined } // circular — the label falls back to the name
124
+ }
125
+
126
+ /** Same door for salience: the host may say, otherwise the default stands. */
127
+ function salienceOf( data: unknown, fallback: number ): number {
128
+ if( typeof data !== 'object' || data === null || Array.isArray( data ) ) return fallback
129
+ const s = ( data as Record<string, unknown> )['salience']
130
+ return typeof s === 'number' && Number.isFinite( s ) ? Math.max( 0, Math.min( 1, s ) ) : fallback
23
131
  }
@@ -232,6 +232,29 @@ export function handlesOf( entities: ReadonlyMap<string, EntityLike>, referentId
232
232
  return []
233
233
  }
234
234
 
235
+ /**
236
+ * Every address the world knows this referent by.
237
+ *
238
+ * The inverse of the alias table: an anchor in, the transport ids that resolve to
239
+ * it out. An anchor is deliberately opaque and deliberately not an address —
240
+ * `ke:1sqlkux` is who someone IS — so anything that must actually reach them in
241
+ * the world has to come back through here.
242
+ *
243
+ * Two callers now, which is why it is a function and not inline: the outbox, so a
244
+ * message addressed to a referent can be delivered, and the agency's dispatch, so
245
+ * a host asked to act on a referent knows which of its own ids that is. Both are
246
+ * the mind reaching OUT; nothing here lets a surface name someone to the mind.
247
+ *
248
+ * Sorted, so a recorded run and its replay produce the same list (R2).
249
+ */
250
+ export function addressesOf( entities: ReadonlyMap<string, EntityLike>, referentId: string ): string[] {
251
+ const aliases = readAliases( entities )
252
+ return [ ...aliases.entries() ]
253
+ .filter( ( [ , canonical ] ) => canonical === referentId )
254
+ .map( ( [ alias ] ) => alias )
255
+ .sort()
256
+ }
257
+
235
258
  /**
236
259
  * The route to use when the mind has expressed no preference.
237
260
  *
@@ -77,9 +77,24 @@ export type LLMCallFunction =
77
77
  export type TokenLedgerRecord = Record<string, unknown>
78
78
  export type TokenRecordListener = ( record: TokenLedgerRecord ) => void
79
79
 
80
- // ── Prompt-cache pricing (Anthropic) ──────────────────────
80
+ // ── Prompt-cache pricing ──────────────────────────────────
81
81
  // `input_tokens` in the API usage already EXCLUDES cached tokens, so the full
82
- // input cost is: fresh input ×1 + cache reads ×0.1 + cache writes ×1.25.
82
+ // input cost is: fresh input ×1 + cache reads + cache writes.
83
+ //
84
+ // These two ratios are ANTHROPIC's, and they are the last prices this engine
85
+ // ships. That is the inconsistency they encode: the module refuses to bake in
86
+ // an input or output rate on the explicit grounds that "prices belong to the
87
+ // host — they change on a vendor's schedule, differ per account, and are ~0 for
88
+ // a self-hosted model", and then hardcodes two more prices because they happen
89
+ // to be expressible as a ratio.
90
+ //
91
+ // They are wrong the moment a host is not on Anthropic. Z.ai charges $0.26/M
92
+ // for cached input against $1.40/M fresh — a ratio of 0.186, not 0.10 — so a
93
+ // COO's cache line came out 46% under. Its cache WRITES are free outright,
94
+ // which 1.25 cannot express at all.
95
+ //
96
+ // So they are now a FALLBACK, used only when the host's `ModelPrice` does not
97
+ // state the real rate. A host that says nothing gets exactly the old numbers.
83
98
  const CACHE_READ_MULT = 0.1
84
99
  const CACHE_WRITE_MULT = 1.25
85
100
 
@@ -98,7 +113,21 @@ function normalizeModelKey( model: string ): string {
98
113
  return m.replace( /[-@]\d{6,8}$/, '') // drop trailing -YYYYMMDD date stamp
99
114
  }
100
115
  /** USD per 1M tokens for one model. */
101
- export interface ModelPrice { input: number; output: number }
116
+ export interface ModelPrice {
117
+ input: number
118
+ output: number
119
+ /**
120
+ * USD per 1M cache-READ tokens. Absent ⇒ `input × 0.1` (Anthropic's ratio).
121
+ *
122
+ * State it whenever the host is not Anthropic. `0` is meaningful and honoured
123
+ * — some providers do not charge for cache reads at all — so this is read as
124
+ * "absent", not "falsy".
125
+ */
126
+ cachedInput?: number
127
+ /** USD per 1M cache-WRITE tokens. Absent ⇒ `input × 1.25`. `0` is honoured:
128
+ * Z.ai's cache storage is free, which no multiple of input can express. */
129
+ cacheWrite?: number
130
+ }
102
131
 
103
132
  /**
104
133
  * Host-supplied prices, keyed by model id. Matching is exact first, then
@@ -334,8 +363,10 @@ export class TokenTracker implements SimulationEngine {
334
363
  const costUsd = pricing
335
364
  ? ( usage.promptTokens / 1_000_000 ) * pricing.input +
336
365
  ( usage.completionTokens / 1_000_000 ) * pricing.output +
337
- ( cacheRead / 1_000_000 ) * pricing.input * CACHE_READ_MULT +
338
- ( cacheWrite / 1_000_000 ) * pricing.input * CACHE_WRITE_MULT
366
+ // `?? fallback` rather than `||`: a stated 0 is a real rate (free cache
367
+ // reads / free storage) and must not silently become the Anthropic ratio.
368
+ ( cacheRead / 1_000_000 ) * ( pricing.cachedInput ?? pricing.input * CACHE_READ_MULT ) +
369
+ ( cacheWrite / 1_000_000 ) * ( pricing.cacheWrite ?? pricing.input * CACHE_WRITE_MULT )
339
370
  : 0
340
371
 
341
372
  const full: TokenUsage = {
@@ -35,6 +35,19 @@ export interface EngineResult {
35
35
  export interface SimulationEngine {
36
36
  readonly name: string
37
37
 
38
+ /**
39
+ * Entity types this engine writes as part of the MIND'S OWN operation — its
40
+ * bookkeeping, not the world's furniture. Declared here, they fall inside the
41
+ * sense boundary and the outward senses stop re-perceiving them (see
42
+ * `#cognition/sense.boundary`).
43
+ *
44
+ * Optional and silent by default, because the default is the right one for a
45
+ * host engine: an engine that maintains rooms, documents or sensor readings
46
+ * wants those perceived. Declare only what the mind should not encounter as an
47
+ * event in its world.
48
+ */
49
+ readonly writes?: readonly string[]
50
+
38
51
  /**
39
52
  * Called once per tick with a frozen read-only snapshot.
40
53
  * Engines must NOT mutate state directly — return commands instead.
package/src/index.ts CHANGED
@@ -87,8 +87,8 @@ export {
87
87
  } from '#cognition/memory/index'
88
88
 
89
89
  // Profile registry — built-in profiles are auto-registered when this package is imported
90
- // (mind.ts imports '#profiles/built-in' as a side effect).
91
- export { resolveProfile, listProfiles, type WorldProfile } from '#profiles/index'
90
+ // (mind.ts imports '#stem/profiles/built-in' as a side effect).
91
+ export { resolveProfile, listProfiles, type WorldProfile } from '#stem/profiles/index'
92
92
 
93
93
  // PMA (Personal Mind Archive) snapshot types
94
94
  export type {
@@ -109,14 +109,20 @@ export type {
109
109
  PMAProbe,
110
110
  } from '#pma/eval'
111
111
 
112
- // Sensory input types — used by callers of WillManager.ingestText()
112
+ // Sensory input types — used by callers of WillManager.senseText()
113
113
  export type { TextMessage, VoiceChunk, SensoryInput } from '#senses/index'
114
+ // Provenance — whose doing a signal was. Required on every signal, because
115
+ // nothing inside the mind can tell its own echo from a stranger saying the same
116
+ // words: only the host knows, so the host says. `asProvenance` is for untyped
117
+ // ingress only (a tool call, an HTTP body) — the one place a default exists.
118
+ export type { SignalProvenance, SensorySignal, Transduced } from '#senses/provenance'
119
+ export { asProvenance } from '#senses/provenance'
114
120
 
115
121
  // ── SDK facade — the ergonomic embedding API (recommended entry point) ──
116
122
  // `Will.create()` wraps WillStem in the shape a developer expects: on('message'),
117
123
  // effector(name, handler), say(), state(), hibernate()/wake(). Drop to `.stem`
118
124
  // for the full contract. See src/sdk/will.ts + examples/effectors.ts.
119
- export { Will } from '#sdk/will'
125
+ export { Will } from '#surface/sdk/will'
120
126
  export type {
121
127
  CreateWillOptions,
122
128
  Stimulus,
@@ -128,5 +134,33 @@ export type {
128
134
  EffectorResult,
129
135
  EffectorSpec,
130
136
  EffectorEntry,
131
- } from '#sdk/will'
137
+ } from '#surface/sdk/will'
132
138
  export type { SchemaPrecondition, EffectorDeclaration } from '#agency/types'
139
+
140
+ // ── Policy — the PDP/PEP boundary (POLICY_REAFFERENCE) ──────────────────
141
+ // A host installs a PolicyArbiter via `WillStem.setArbiter()` / `Will.setArbiter()`
142
+ // to gate host-owned effector invocations before they reach the world: allow,
143
+ // deny (with `finality` — class/parameter/context, see arbiter.ts), or escalate
144
+ // (hold the intent, voice the ask, resolve via `resolveEscalation()`). No arbiter
145
+ // installed ⇒ byte-identical to a Will with no policy layer. `RuleTableArbiter`
146
+ // is the local reference PDP — pure and declarative, sufficient for static
147
+ // schema/target/parameter rules; a host needing live world state (session/DB
148
+ // lookups) implements `PolicyArbiter` directly instead.
149
+ export {
150
+ NULL_ARBITER,
151
+ isNullArbiter,
152
+ finalityOf,
153
+ asFinality,
154
+ type PolicyArbiter,
155
+ type PolicyDecision,
156
+ type DenialFinality,
157
+ type PolicyCounterfactual,
158
+ type Verdict,
159
+ type PolicyInvocation,
160
+ } from '#stem/policy/arbiter'
161
+ export {
162
+ RuleTableArbiter,
163
+ type PolicyRule,
164
+ type ParamConstraint,
165
+ type RuleTableOptions,
166
+ } from '#stem/policy/rule.table'
package/src/llm/gate.ts CHANGED
@@ -19,11 +19,14 @@
19
19
  *
20
20
  * Configured via env vars:
21
21
  * WILL_LLM_CONCURRENCY max simultaneous LLM calls (default 2)
22
+ * WILL_LLM_RESPONSIVE_CONCURRENCY slots reserved for calls someone outside
23
+ * the mind is waiting on (default 1)
22
24
  * WILL_LLM_MAX_RETRIES retries before giving up on a 429 (default 4)
23
25
  * WILL_LLM_RETRY_BASE_MS first retry wait, doubles each attempt (default 2000)
24
26
  */
25
27
 
26
28
  import { logger } from '#core/logger'
29
+ import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
27
30
 
28
31
  const MAX_CONCURRENT = parseInt( process.env.WILL_LLM_CONCURRENCY ?? '2')
29
32
 
@@ -76,6 +79,51 @@ export class LLMSemaphore {
76
79
 
77
80
  export const llmGate = new LLMSemaphore( MAX_CONCURRENT )
78
81
 
82
+ // ── The responsive lane ───────────────────────────────────────
83
+
84
+ /**
85
+ * A second gate, for calls someone OUTSIDE the mind is waiting on.
86
+ *
87
+ * `WILL_LLM_CONCURRENCY` ships with the comment "the minimum is 3: orbital,
88
+ * conversation, summary" — an intended allocation that one shared semaphore
89
+ * cannot enforce. Three slots means any three callers, and a mind deliberating
90
+ * at ~1,100 output tokens a call keeps all three warm indefinitely.
91
+ *
92
+ * Measured on a live COO over 7 hours: 29 of 45 replies began at or past the
93
+ * limit, and the worst spent 117 of its 131 seconds waiting for a slot rather
94
+ * than generating — 545 tokens at 4.2 tok/s against a provider that does ~39.
95
+ * From outside, that is a mind that read your message and said nothing.
96
+ *
97
+ * The distinction drawn here is NOT that conversation matters more. It is that
98
+ * something outside the mind is blocked on this call — a property of the call
99
+ * itself, which is why the predicate is named for the property and not for a
100
+ * list of blessed functions. Rumination can wait its turn. A person waiting for
101
+ * an answer cannot tell a mind that is thinking from one that is gone.
102
+ */
103
+ const RESPONSIVE_CONCURRENCY = parseInt( process.env.WILL_LLM_RESPONSIVE_CONCURRENCY ?? '1')
104
+
105
+ export const responsiveGate = new LLMSemaphore( RESPONSIVE_CONCURRENCY )
106
+
107
+ /**
108
+ * The cognitive functions something outside the mind is blocked on.
109
+ *
110
+ * `outreach` is deliberately absent: the mind started that one, and nobody is
111
+ * sitting on the other end waiting. Only work that a waiting party is already
112
+ * owed belongs in the reserved lane — widen this and the reservation stops
113
+ * meaning anything.
114
+ */
115
+ const AWAITED_OUTSIDE: ReadonlySet<LLMCallFunction> = new Set([ 'conversation' ])
116
+
117
+ /** Whether someone outside the mind is blocked on a call of this function. */
118
+ export function isAwaitedOutside( fn: LLMCallFunction | undefined ): boolean {
119
+ return fn !== undefined && AWAITED_OUTSIDE.has( fn )
120
+ }
121
+
122
+ /** The lane a call of this function belongs in. */
123
+ export function gateFor( fn: LLMCallFunction | undefined ): LLMSemaphore {
124
+ return isAwaitedOutside( fn ) ? responsiveGate : llmGate
125
+ }
126
+
79
127
  // ── Rate-limit detection ──────────────────────────────────────
80
128
 
81
129
  export function isRateLimitError( err: unknown ): boolean {