@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.
- package/README.md +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +16660 -15206
- package/dist/index.js.map +1 -1
- package/dist/{channels → surface/channels}/discord.d.ts +50 -3
- package/dist/{channels → surface/channels}/discord.js +102 -7
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +7 -4
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +4406 -2904
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +237 -3
- package/src/cognition/agency/conversation.aim.ts +32 -0
- package/src/cognition/agency/engines/action.selector.ts +59 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
- package/src/cognition/agency/engines/reafference.engine.ts +43 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +16 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +38 -1
- package/src/cognition/config.mirror.entities.ts +0 -1
- package/src/cognition/event.schemas.ts +0 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/context.ts +41 -35
- package/src/cognition/faculties/executive.engine/engine.ts +270 -113
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
- package/src/cognition/faculties/executive.engine/parser.ts +100 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
- package/src/cognition/faculties/executive.engine/types.ts +18 -15
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +50 -2
- package/src/cognition/faculties/known.entity.tracker.ts +22 -3
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/working.memory.ts +6 -1
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.embedder.ts +1 -3
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +97 -16
- package/src/cognition/senses/base.sense.engine.ts +104 -6
- package/src/cognition/senses/index.ts +71 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +23 -0
- package/src/cognition/utilities/token.tracker.ts +36 -5
- package/src/core/orchestrator.ts +13 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +102 -25
- package/src/llm/wire.contracts.ts +38 -0
- package/src/pma/index.ts +1 -1
- package/src/stem/index.ts +92 -21
- package/src/stem/mind.ts +26 -2
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/{channels → surface/channels}/discord.ts +224 -7
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/{channels → surface/channels}/types.ts +1 -1
- package/src/{channels → surface/channels}/whatsapp.ts +9 -6
- package/src/{cli.ts → surface/cli.ts} +6 -6
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +156 -28
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /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
|
|
6
|
+
* SomatosensationEngine — the sense that feels things happen TO the mind.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
338
|
-
|
|
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 = {
|
package/src/core/orchestrator.ts
CHANGED
|
@@ -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.
|
|
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 {
|