@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.
- package/README.md +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +6985 -3337
- package/dist/index.js.map +1 -1
- package/dist/surface/channels/discord.d.ts +177 -0
- package/dist/surface/channels/discord.js +394 -0
- 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 +11 -5
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +5382 -1573
- 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-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +356 -1
- package/src/cognition/agency/conversation.aim.ts +292 -0
- package/src/cognition/agency/engines/action.selector.ts +142 -5
- package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
- package/src/cognition/agency/engines/reafference.engine.ts +160 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/restart.ts +66 -0
- 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 +49 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +73 -1
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -1
- package/src/cognition/event.schemas.ts +22 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +106 -46
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +714 -136
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +120 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
- package/src/cognition/faculties/executive.engine/types.ts +84 -12
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +144 -16
- package/src/cognition/faculties/known.entity.tracker.ts +287 -29
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- 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/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +16 -21
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -4
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +492 -53
- package/src/cognition/senses/base.sense.engine.ts +105 -7
- package/src/cognition/senses/index.ts +83 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +296 -0
- package/src/cognition/utilities/token.tracker.ts +94 -10
- package/src/core/orchestrator.ts +51 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +127 -33
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +57 -0
- package/src/pma/index.ts +68 -54
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +171 -23
- package/src/stem/mind.ts +198 -57
- 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/outbox.writer.ts +40 -2
- 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/surface/channels/discord.ts +609 -0
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/surface/channels/types.ts +136 -0
- package/src/{channels → surface/channels}/whatsapp.ts +22 -10
- package/src/{cli.ts → surface/cli.ts} +14 -9
- 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 +194 -33
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.d.ts +0 -69
- package/dist/channels/discord.js +0 -193
- 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/channels/discord.ts +0 -214
- package/src/channels/types.ts +0 -46
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
- 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,296 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/social.identity.ts — what something IS, vs where to find it
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// A `keid` used to be minted by the transport: `discord:${author.id}`,
|
|
6
|
+
// `whatsapp:${userId}`. Identity WAS the address, and whichever channel spoke
|
|
7
|
+
// first won the right to name the person. Everything downstream inherited that:
|
|
8
|
+
// twenty-two files key off a keid, so the same human met on two channels was two
|
|
9
|
+
// people to the reputation tracker, the theory-of-mind model, the attachment
|
|
10
|
+
// bond and the PMA — with no way to notice, and no way to say so.
|
|
11
|
+
//
|
|
12
|
+
// It also made "how should I reach them?" unaskable. There was exactly one id
|
|
13
|
+
// and it WAS a route, so the question collapsed into a roster guess about where
|
|
14
|
+
// the person was last seen. Live, that sent a follow-up promised in a DM into a
|
|
15
|
+
// public channel, because the roster's last-seen answer is not the same question
|
|
16
|
+
// as "where did I promise this".
|
|
17
|
+
//
|
|
18
|
+
// So identity and route are separated here:
|
|
19
|
+
//
|
|
20
|
+
// ke:<opaque> — a referent. Never a route. The anchor everything hangs on.
|
|
21
|
+
// handle — a way that referent has been reachable, with the
|
|
22
|
+
// circumstances under which it worked.
|
|
23
|
+
//
|
|
24
|
+
// Deliberately NOT social-only. `keid` has always stood for *known entity* id and
|
|
25
|
+
// the dossier has carried `kind: 'sentient' | 'thing'` since it shipped; the
|
|
26
|
+
// first cut of this minted `person:` ids, which was a narrower word than the
|
|
27
|
+
// system already used. A document, a repo, a dashboard, a room each have a what
|
|
28
|
+
// and a where, and the where can change while the what stays put.
|
|
29
|
+
//
|
|
30
|
+
// This is closer to how a person actually holds it. You know someone; you know
|
|
31
|
+
// places; and separately you know where you usually find whom. Three things that
|
|
32
|
+
// compose, not one contact record — which is why a handle carries evidence
|
|
33
|
+
// (`lastAnsweredTick`) rather than a priority number somebody configured.
|
|
34
|
+
//
|
|
35
|
+
// Everything here is pure and deterministic. `mintReferentId` hashes the first
|
|
36
|
+
// transport id ever seen for that referent, so a recorded run and its replay mint
|
|
37
|
+
// the same id (R2) — no clock, no counter, no RNG.
|
|
38
|
+
// ─────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
import type { Tick } from '#core/types'
|
|
41
|
+
import { fnv1a } from '#agency/consequence'
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Marks an id as a referent rather than an address.
|
|
45
|
+
*
|
|
46
|
+
* `ke` for known-entity, which is what `keid` has always stood for — the
|
|
47
|
+
* vocabulary was never social. The first cut of this minted `person:` ids and
|
|
48
|
+
* that was a narrower word than the system uses: the dossier has carried
|
|
49
|
+
* `kind: 'sentient' | 'thing'` since it shipped, and the split here is not a
|
|
50
|
+
* social idea. It is referent vs. access path, which is how anything is held —
|
|
51
|
+
* you remember the book, and separately that it is on the shelf, on the Kindle,
|
|
52
|
+
* or at the library. A document, a repo, a dashboard, a room all have several
|
|
53
|
+
* routes that come and go while the thing itself stays put.
|
|
54
|
+
*/
|
|
55
|
+
export const REFERENT_PREFIX = 'ke:'
|
|
56
|
+
|
|
57
|
+
export const ALIAS_TYPE = 'known-entity-alias'
|
|
58
|
+
export const DOSSIER_TYPE = 'known-entity'
|
|
59
|
+
|
|
60
|
+
/** True for an anchor, false for a transport address (`discord:…`, `whatsapp:…`). */
|
|
61
|
+
export function isReferentId( id: string ): boolean {
|
|
62
|
+
return id.startsWith( REFERENT_PREFIX )
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Mint the anchor for a referent first met at `seedKeid`.
|
|
67
|
+
*
|
|
68
|
+
* Kind is deliberately NOT in the id. It lives on the dossier, because kind is
|
|
69
|
+
* LEARNED and correctable — a handle you took for a bot turns out to be a person.
|
|
70
|
+
* Baked into the id, correcting it would mean re-identifying, and every faculty
|
|
71
|
+
* keyed off that id would lose its history of them at the moment it finally
|
|
72
|
+
* understood what they were.
|
|
73
|
+
*
|
|
74
|
+
* Deterministic by construction: the same first-seen transport id always yields
|
|
75
|
+
* the same referent id, so a replay of a recorded run mints identically and the
|
|
76
|
+
* state hashes match (R2). A counter would drift the moment two runs met people
|
|
77
|
+
* in a different order; a clock or RNG would never match at all.
|
|
78
|
+
*
|
|
79
|
+
* Opaque on purpose. The moment an id is readable as `ke:discord:123`
|
|
80
|
+
* something downstream starts parsing it back into a route, and the separation
|
|
81
|
+
* this whole module exists for quietly stops holding.
|
|
82
|
+
*/
|
|
83
|
+
export function mintReferentId( seedKeid: string ): string {
|
|
84
|
+
return `${ REFERENT_PREFIX }${ fnv1a( seedKeid ).toString( 36 ) }`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A way this referent has been reachable, and what happened there.
|
|
89
|
+
*
|
|
90
|
+
* `kind` is the one fact that decides whether a room is the right place for a
|
|
91
|
+
* given utterance, and it was being computed at the Discord edge (`isDM`) and
|
|
92
|
+
* discarded before the mind could see it.
|
|
93
|
+
*
|
|
94
|
+
* `lastAnsweredTick` is evidence, not configuration — it arrives free from the
|
|
95
|
+
* `social.responsiveness` signal. It is what lets the mind prefer the DM because
|
|
96
|
+
* that is where this person actually answers, rather than because a constant in
|
|
97
|
+
* the code says DMs rank higher.
|
|
98
|
+
*/
|
|
99
|
+
export interface Handle {
|
|
100
|
+
/** The transport address — what a channel bridge can actually deliver to. */
|
|
101
|
+
keid: string
|
|
102
|
+
/**
|
|
103
|
+
* 'dm' — a private thread. 'room' — somewhere others are listening. Left open
|
|
104
|
+
* for a non-social referent, where the meaningful distinction is a different one.
|
|
105
|
+
*/
|
|
106
|
+
kind: 'dm' | 'room' | 'unknown'
|
|
107
|
+
/** The place this handle lives in, once places are dossiers of their own. */
|
|
108
|
+
place?: string
|
|
109
|
+
/** When the mind last SAID something here. */
|
|
110
|
+
lastUsedTick?: Tick
|
|
111
|
+
/** When someone last answered it here — the only evidence that this route works. */
|
|
112
|
+
lastAnsweredTick?: Tick
|
|
113
|
+
/** Free-form, so a host can mark what its own vocabulary cares about. */
|
|
114
|
+
tags?: string[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface EntityLike {
|
|
118
|
+
type: string
|
|
119
|
+
/** A frozen state entity may hand metadata over as either shape. */
|
|
120
|
+
metadata?: ReadonlyMap<string, unknown> | Record<string, unknown>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Normalize whichever shape metadata arrived in. */
|
|
124
|
+
function meta( e: EntityLike ): Record<string, unknown> {
|
|
125
|
+
const m = e.metadata
|
|
126
|
+
if( !m ) return {}
|
|
127
|
+
return m instanceof Map ? Object.fromEntries( m ) : m as Record<string, unknown>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function str( v: unknown ): string | undefined {
|
|
131
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* alias keid → canonical referent id.
|
|
136
|
+
*
|
|
137
|
+
* Every transport address a referent has been met at is an alias of its anchor,
|
|
138
|
+
* which is what lets the twenty-two keid consumers keep working untouched: they
|
|
139
|
+
* still see one opaque string per referent, it is simply no longer a route.
|
|
140
|
+
*/
|
|
141
|
+
export function readAliases( entities: ReadonlyMap<string, EntityLike> ): Map<string, string> {
|
|
142
|
+
const out = new Map<string, string>()
|
|
143
|
+
for( const [ , e ] of entities ){
|
|
144
|
+
if( e.type !== ALIAS_TYPE ) continue
|
|
145
|
+
const m = meta( e )
|
|
146
|
+
const a = str( m['aliasKeid'] )
|
|
147
|
+
const c = str( m['canonicalKeid'] )
|
|
148
|
+
if( a && c ) out.set( a, c )
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Follow an alias chain to the anchor. Cycle-safe; returns the input when unaliased. */
|
|
154
|
+
export function canonicalOf( aliases: ReadonlyMap<string, string>, keid: string ): string {
|
|
155
|
+
const seen = new Set<string>()
|
|
156
|
+
let id = keid
|
|
157
|
+
while( true ){
|
|
158
|
+
const next = aliases.get( id )
|
|
159
|
+
if( !next || next === id || seen.has( next ) ) return id
|
|
160
|
+
seen.add( id )
|
|
161
|
+
id = next
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resolve anything the mind might name — an anchor, a transport address, or a
|
|
167
|
+
* learned name — to the anchor.
|
|
168
|
+
*
|
|
169
|
+
* ONE resolver, because there were two and they disagreed:
|
|
170
|
+
* `extractKnownEntities` folded aliases (so the prompt showed one person) while
|
|
171
|
+
* `resolveKnownEntity` did not (so willing a reach-out to that same person could
|
|
172
|
+
* resolve to a keid that had been merged away, and the intention evaporated).
|
|
173
|
+
* Same question, two answers, in the same tick.
|
|
174
|
+
*
|
|
175
|
+
* Name matching stays last and exact. It is the weakest evidence here — two
|
|
176
|
+
* people genuinely can share a name — and the KnownEntityTracker's recognition
|
|
177
|
+
* pass already guards fusing them.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveKeid(
|
|
180
|
+
entities: ReadonlyMap<string, EntityLike>,
|
|
181
|
+
ref: string,
|
|
182
|
+
): string | undefined {
|
|
183
|
+
const needle = ref.trim().toLowerCase()
|
|
184
|
+
if( !needle ) return undefined
|
|
185
|
+
|
|
186
|
+
const aliases = readAliases( entities )
|
|
187
|
+
|
|
188
|
+
// An anchor or an address named directly.
|
|
189
|
+
const direct = canonicalOf( aliases, ref.trim() )
|
|
190
|
+
for( const [ , e ] of entities )
|
|
191
|
+
if( e.type === DOSSIER_TYPE && str( meta( e )['keid'] ) === direct ) return direct
|
|
192
|
+
|
|
193
|
+
// Otherwise a keid or a name, case-insensitively, in stable entity order.
|
|
194
|
+
for( const [ , e ] of entities ){
|
|
195
|
+
if( e.type !== DOSSIER_TYPE ) continue
|
|
196
|
+
const m = meta( e )
|
|
197
|
+
const keid = str( m['keid'] )
|
|
198
|
+
if( !keid ) continue
|
|
199
|
+
if( keid.toLowerCase() === needle ) return canonicalOf( aliases, keid )
|
|
200
|
+
if( str( m['name'] )?.toLowerCase() === needle ) return canonicalOf( aliases, keid )
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// A known alias whose dossier was absorbed — still a real reference to someone.
|
|
204
|
+
for( const [ alias, canon ] of aliases )
|
|
205
|
+
if( alias.toLowerCase() === needle ) return canon
|
|
206
|
+
|
|
207
|
+
return undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The name the mind has learned for this referent, or undefined — never a placeholder. */
|
|
211
|
+
export function nameOf( entities: ReadonlyMap<string, EntityLike>, referentId: string ): string | undefined {
|
|
212
|
+
for( const [ , e ] of entities ){
|
|
213
|
+
if( e.type !== DOSSIER_TYPE || str( meta( e )['keid'] ) !== referentId ) continue
|
|
214
|
+
return str( meta( e )['name'] )?.trim() || undefined
|
|
215
|
+
}
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Every route the mind holds for this referent, most recently answered first. */
|
|
220
|
+
export function handlesOf( entities: ReadonlyMap<string, EntityLike>, referentId: string ): Handle[] {
|
|
221
|
+
for( const [ , e ] of entities ){
|
|
222
|
+
if( e.type !== DOSSIER_TYPE || str( meta( e )['keid'] ) !== referentId ) continue
|
|
223
|
+
const raw = meta( e )['handles']
|
|
224
|
+
if( !Array.isArray( raw ) ) return []
|
|
225
|
+
return ( raw as Handle[] )
|
|
226
|
+
.filter( h => h && typeof h.keid === 'string' )
|
|
227
|
+
.sort( ( a, b ) =>
|
|
228
|
+
( b.lastAnsweredTick ?? -1 ) - ( a.lastAnsweredTick ?? -1 )
|
|
229
|
+
|| ( b.lastUsedTick ?? -1 ) - ( a.lastUsedTick ?? -1 )
|
|
230
|
+
|| ( a.keid < b.keid ? -1 : a.keid > b.keid ? 1 : 0 ) )
|
|
231
|
+
}
|
|
232
|
+
return []
|
|
233
|
+
}
|
|
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
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The route to use when the mind has expressed no preference.
|
|
260
|
+
*
|
|
261
|
+
* A DEFAULT, not a decision. Which room to speak in is the mind's call, made
|
|
262
|
+
* from the circumstances it can now see; this only answers "nothing was chosen
|
|
263
|
+
* and the words must still go somewhere" — the case where the alternative is
|
|
264
|
+
* dropping the message.
|
|
265
|
+
*
|
|
266
|
+
* Ordering is evidence-first: somewhere this person has actually answered beats
|
|
267
|
+
* somewhere they have not, and a DM beats a room only as a tiebreak. A live Will
|
|
268
|
+
* lost a promised follow-up into a public channel precisely because its fallback
|
|
269
|
+
* ranked "where I last saw them" above "where they talk to me".
|
|
270
|
+
*/
|
|
271
|
+
export function defaultHandle( handles: readonly Handle[] ): Handle | undefined {
|
|
272
|
+
if( handles.length === 0 ) return undefined
|
|
273
|
+
const answered = handles.filter( h => h.lastAnsweredTick !== undefined )
|
|
274
|
+
const pool = answered.length > 0 ? answered : handles
|
|
275
|
+
return pool.find( h => h.kind === 'dm') ?? pool[0]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Fold a newly-seen address into a person's handle list.
|
|
280
|
+
*
|
|
281
|
+
* Merges rather than appends: meeting someone again in a room already known is
|
|
282
|
+
* not a new way to reach them, it is news about an existing one. Pure — returns
|
|
283
|
+
* a fresh array, so a caller writing it back through `setEntity` cannot
|
|
284
|
+
* accidentally share a mutable reference with frozen state.
|
|
285
|
+
*/
|
|
286
|
+
export function withHandle( handles: readonly Handle[], next: Handle ): Handle[] {
|
|
287
|
+
const out = handles.filter( h => h.keid !== next.keid )
|
|
288
|
+
const old = handles.find( h => h.keid === next.keid )
|
|
289
|
+
out.push( old ? { ...old, ...next,
|
|
290
|
+
// Never let a fresh sighting erase evidence the old record already held.
|
|
291
|
+
lastUsedTick: next.lastUsedTick ?? old.lastUsedTick,
|
|
292
|
+
lastAnsweredTick: next.lastAnsweredTick ?? old.lastAnsweredTick,
|
|
293
|
+
tags: [ ...new Set([ ...( old.tags ?? [] ), ...( next.tags ?? [] ) ]) ],
|
|
294
|
+
} : next )
|
|
295
|
+
return out.sort( ( a, b ) => ( a.keid < b.keid ? -1 : a.keid > b.keid ? 1 : 0 ) )
|
|
296
|
+
}
|
|
@@ -54,10 +54,15 @@ export type LLMCallAttribute =
|
|
|
54
54
|
| 'memory' // consolidation / embedding
|
|
55
55
|
| 'guard' // a safety reviewer
|
|
56
56
|
|
|
57
|
-
/** The specific cognitive
|
|
58
|
-
export type
|
|
57
|
+
/** The specific cognitive process being paid for. */
|
|
58
|
+
export type LLMCallProcess =
|
|
59
|
+
| 'cog' // background cogs' call
|
|
59
60
|
| 'decision' // the master's fused decision call
|
|
60
61
|
| 'ideation' // the deliberate path's propose pass
|
|
62
|
+
|
|
63
|
+
/** The specific cognitive function being paid for. */
|
|
64
|
+
export type LLMCallFunction =
|
|
65
|
+
| '-' // no-specific function
|
|
61
66
|
| 'deliberation' // action choice under contest
|
|
62
67
|
| 'conversation' // a live reply
|
|
63
68
|
| 'outreach' // an unprompted message
|
|
@@ -72,9 +77,24 @@ export type LLMCallFunction =
|
|
|
72
77
|
export type TokenLedgerRecord = Record<string, unknown>
|
|
73
78
|
export type TokenRecordListener = ( record: TokenLedgerRecord ) => void
|
|
74
79
|
|
|
75
|
-
// ── Prompt-cache pricing
|
|
80
|
+
// ── Prompt-cache pricing ──────────────────────────────────
|
|
76
81
|
// `input_tokens` in the API usage already EXCLUDES cached tokens, so the full
|
|
77
|
-
// 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.
|
|
78
98
|
const CACHE_READ_MULT = 0.1
|
|
79
99
|
const CACHE_WRITE_MULT = 1.25
|
|
80
100
|
|
|
@@ -93,7 +113,21 @@ function normalizeModelKey( model: string ): string {
|
|
|
93
113
|
return m.replace( /[-@]\d{6,8}$/, '') // drop trailing -YYYYMMDD date stamp
|
|
94
114
|
}
|
|
95
115
|
/** USD per 1M tokens for one model. */
|
|
96
|
-
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
|
+
}
|
|
97
131
|
|
|
98
132
|
/**
|
|
99
133
|
* Host-supplied prices, keyed by model id. Matching is exact first, then
|
|
@@ -176,9 +210,25 @@ export interface TokenUsage {
|
|
|
176
210
|
*/
|
|
177
211
|
priced: boolean
|
|
178
212
|
|
|
213
|
+
/**
|
|
214
|
+
* How much this call demanded, 0..1 — the cognitive measure the router saw.
|
|
215
|
+
*
|
|
216
|
+
* Recorded so routing can be ANSWERED rather than argued. Every call computes
|
|
217
|
+
* this, routes on it, and until now threw it away — which left questions like
|
|
218
|
+
* "is deliberation being rated by the tick's mood rather than the stakes of
|
|
219
|
+
* its own choice?" with no dataset at all.
|
|
220
|
+
*
|
|
221
|
+
* Absent means UNMEASURED, never zero. It must stay nullable all the way to
|
|
222
|
+
* storage: a call that never reported demand and a call that reported 0.0 are
|
|
223
|
+
* different facts, and collapsing them would put a floor of invented
|
|
224
|
+
* confidence under exactly the analysis this exists to enable.
|
|
225
|
+
*/
|
|
226
|
+
demand?: number
|
|
227
|
+
|
|
179
228
|
// ── 5-axis cost attribution ──────────────────────────────
|
|
180
229
|
category: LLMCallCategory
|
|
181
230
|
attribute: LLMCallAttribute
|
|
231
|
+
process: LLMCallProcess
|
|
182
232
|
function: LLMCallFunction
|
|
183
233
|
/** Optional specific id or namespace: facet id, entity id, model name. */
|
|
184
234
|
scope?: string
|
|
@@ -197,9 +247,17 @@ export interface TokenUsage {
|
|
|
197
247
|
/** What callers pass to {@link TokenTracker.recordUsage} — cost and label are derived. */
|
|
198
248
|
export type RecordUsageInput = Omit<TokenUsage, 'estimatedCostUsd' | 'label' | 'priced'> & { label?: string }
|
|
199
249
|
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Compose a stable, readable label from the attribution axes.
|
|
252
|
+
*
|
|
253
|
+
* `function` carries '-' when the call has no specific one (the master's own
|
|
254
|
+
* decision/ideation passes), so it is dropped rather than rendered — a label of
|
|
255
|
+
* `executive/master/-` names nothing. Process always shows: it is what
|
|
256
|
+
* distinguishes the propose pass from the decision it feeds.
|
|
257
|
+
*/
|
|
258
|
+
function composeLabel( m: { category: LLMCallCategory; attribute: LLMCallAttribute; process: LLMCallProcess; function: LLMCallFunction; scope?: string } ): string {
|
|
259
|
+
const base = [ m.category, m.attribute, m.process, m.function === '-' ? '' : m.function ]
|
|
260
|
+
.filter( Boolean ).join('/')
|
|
203
261
|
return m.scope ? `${base}#${m.scope}` : base
|
|
204
262
|
}
|
|
205
263
|
|
|
@@ -245,6 +303,12 @@ export class TokenTracker implements SimulationEngine {
|
|
|
245
303
|
private _categoryTokens = new Map<string, { prompt: number; completion: number }>()
|
|
246
304
|
private _functionCosts = new Map<string, number>()
|
|
247
305
|
private _functionTokens = new Map<string, { prompt: number; completion: number }>()
|
|
306
|
+
// Split out of the function axis: `decision` vs `ideation` is which PROCESS ran,
|
|
307
|
+
// not which function it served. Without its own bucket the deliberate path's
|
|
308
|
+
// propose pass became invisible in cost reporting — every master call landing in
|
|
309
|
+
// the '-' function bucket regardless of whether it deliberated.
|
|
310
|
+
private _processCosts = new Map<string, number>()
|
|
311
|
+
private _processTokens = new Map<string, { prompt: number; completion: number }>()
|
|
248
312
|
// Per-provider spend. The axis a host actually reconciles against invoices —
|
|
249
313
|
// "which vendor did we pay?" is not answerable from the model id once routing
|
|
250
314
|
// can reach one model through several of them.
|
|
@@ -299,8 +363,10 @@ export class TokenTracker implements SimulationEngine {
|
|
|
299
363
|
const costUsd = pricing
|
|
300
364
|
? ( usage.promptTokens / 1_000_000 ) * pricing.input +
|
|
301
365
|
( usage.completionTokens / 1_000_000 ) * pricing.output +
|
|
302
|
-
|
|
303
|
-
|
|
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 )
|
|
304
370
|
: 0
|
|
305
371
|
|
|
306
372
|
const full: TokenUsage = {
|
|
@@ -320,6 +386,7 @@ export class TokenTracker implements SimulationEngine {
|
|
|
320
386
|
// Per-axis breakdowns — the repartition surface (category × function).
|
|
321
387
|
this._accumulate( this._categoryCosts, this._categoryTokens, full.category, full )
|
|
322
388
|
this._accumulate( this._functionCosts, this._functionTokens, full.function, full )
|
|
389
|
+
this._accumulate( this._processCosts, this._processTokens, full.process, full )
|
|
323
390
|
// Unattributed rather than guessed: a caller that did not say which
|
|
324
391
|
// provider served the call must not be silently folded into the default.
|
|
325
392
|
this._accumulate( this._providerCosts, this._providerTokens, full.provider ?? 'unattributed', full )
|
|
@@ -350,6 +417,7 @@ export class TokenTracker implements SimulationEngine {
|
|
|
350
417
|
provider: full.provider,
|
|
351
418
|
category: full.category,
|
|
352
419
|
attribute: full.attribute,
|
|
420
|
+
process: full.process,
|
|
353
421
|
function: full.function,
|
|
354
422
|
scope: full.scope,
|
|
355
423
|
label: full.label,
|
|
@@ -363,6 +431,9 @@ export class TokenTracker implements SimulationEngine {
|
|
|
363
431
|
// priced this model, NOT because the call was free — a consumer summing
|
|
364
432
|
// spend must not fold unpriced calls in as zero.
|
|
365
433
|
priced: full.priced,
|
|
434
|
+
// Undefined stays undefined — see TokenUsage.demand. A consumer that
|
|
435
|
+
// coerces this to 0 has silently invented a measurement.
|
|
436
|
+
demand: full.demand,
|
|
366
437
|
latencyMs: full.latencyMs,
|
|
367
438
|
}
|
|
368
439
|
|
|
@@ -461,6 +532,7 @@ export class TokenTracker implements SimulationEngine {
|
|
|
461
532
|
totalCost: this._totalCost,
|
|
462
533
|
categoryBreakdown: Object.fromEntries( this._categoryCosts ),
|
|
463
534
|
functionBreakdown: Object.fromEntries( this._functionCosts ),
|
|
535
|
+
processBreakdown: Object.fromEntries( this._processCosts ),
|
|
464
536
|
},
|
|
465
537
|
})
|
|
466
538
|
}
|
|
@@ -502,6 +574,16 @@ export class TokenTracker implements SimulationEngine {
|
|
|
502
574
|
return this._functionTokens
|
|
503
575
|
}
|
|
504
576
|
|
|
577
|
+
/** Cost broken down by process ('decision' | 'ideation' | 'cog'). */
|
|
578
|
+
get processBreakdown(): ReadonlyMap<string, number> {
|
|
579
|
+
return this._processCosts
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Token counts (prompt + completion) broken down by process. */
|
|
583
|
+
get processTokenBreakdown(): ReadonlyMap<string, { prompt: number; completion: number }> {
|
|
584
|
+
return this._processTokens
|
|
585
|
+
}
|
|
586
|
+
|
|
505
587
|
/**
|
|
506
588
|
* Cost broken down by provider ('anthropic' | 'glm' | 'moonshot' | …), plus
|
|
507
589
|
* an `unattributed` bucket for usage recorded without one.
|
|
@@ -554,6 +636,8 @@ export class TokenTracker implements SimulationEngine {
|
|
|
554
636
|
this._categoryTokens.clear()
|
|
555
637
|
this._functionCosts.clear()
|
|
556
638
|
this._functionTokens.clear()
|
|
639
|
+
this._processCosts.clear()
|
|
640
|
+
this._processTokens.clear()
|
|
557
641
|
this._providerCosts.clear()
|
|
558
642
|
this._providerTokens.clear()
|
|
559
643
|
this._tickCosts = []
|
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.
|
|
@@ -62,6 +75,13 @@ export interface SimulationEngine {
|
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Fraction of one tick interval a single non-async engine may consume before the
|
|
80
|
+
* orchestrator says so. Half a tick is generous for work that is meant to be
|
|
81
|
+
* in-memory, and low enough to fire long before tick-denominated deadlines drift.
|
|
82
|
+
*/
|
|
83
|
+
const SLOW_ENGINE_FRACTION = 0.5
|
|
84
|
+
|
|
65
85
|
// ── Middleware ───────────────────────────────────────────────
|
|
66
86
|
|
|
67
87
|
export type TickMiddleware = (
|
|
@@ -384,6 +404,35 @@ export class DefaultOrchestrator implements Orchestrator {
|
|
|
384
404
|
* calls _executeTick() which handles pause/stop checks internally.
|
|
385
405
|
* The orchestrator is the sole driver of ticks.
|
|
386
406
|
*/
|
|
407
|
+
/**
|
|
408
|
+
* A plain SimulationEngine's `react()` is implicitly "finish inside the tick" —
|
|
409
|
+
* only an AsyncEngine is allowed to span ticks, and it does so by LAUNCHING work
|
|
410
|
+
* and landing it later (see async.engine.ts: "react() never awaits LLM calls").
|
|
411
|
+
* Nothing enforces that on everyone else, and the failure is silent and severe:
|
|
412
|
+
* every agency deadline is denominated in TICKS, so an engine that awaits network
|
|
413
|
+
* I/O does not merely run slowly, it rescales time for the whole mind.
|
|
414
|
+
*
|
|
415
|
+
* Measured: one rate-limited embedding call awaited inside EpisodicConsolidator
|
|
416
|
+
* made two consecutive ticks take 64.9s and 63.5s. `AWAIT_TIMEOUT` — 15 ticks,
|
|
417
|
+
* normally ~15s — silently became 15 minutes, so a communicate intent sat
|
|
418
|
+
* 'awaiting' forever and the serial selector never chose anything again. 45
|
|
419
|
+
* executive decisions produced one intent and zero delivered messages, with no
|
|
420
|
+
* error anywhere. This turns that into a line in the log the first time it happens.
|
|
421
|
+
*/
|
|
422
|
+
private _warnIfSlow( engine: SimulationEngine, elapsedMs: number ): void {
|
|
423
|
+
// AsyncEngines are exempt by design — spanning ticks is their contract.
|
|
424
|
+
if( ( engine as { hasPendingWork?: unknown } ).hasPendingWork !== undefined ) return
|
|
425
|
+
|
|
426
|
+
const budget = ( this._config.tickIntervalMs ?? 1000 ) * SLOW_ENGINE_FRACTION
|
|
427
|
+
if( elapsedMs <= budget ) return
|
|
428
|
+
|
|
429
|
+
logger.warn(
|
|
430
|
+
`[Orchestrator] Engine "${ engine.name }" held the tick for ${ Math.round( elapsedMs ) }ms ` +
|
|
431
|
+
`at tick ${ this._currentTick } (budget ${ Math.round( budget ) }ms). A non-async engine must not ` +
|
|
432
|
+
`await network I/O — tick-denominated deadlines elsewhere are being stretched by this.`
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
387
436
|
private _runLoop( context: SimulationContext ): void {
|
|
388
437
|
if( this._tickTimer ) return
|
|
389
438
|
|
|
@@ -478,6 +527,7 @@ export class DefaultOrchestrator implements Orchestrator {
|
|
|
478
527
|
const allResults: EngineResult[] = []
|
|
479
528
|
|
|
480
529
|
const runEngine = async ( engine: SimulationEngine ): Promise<void> => {
|
|
530
|
+
const startedAt = wallClock()
|
|
481
531
|
try {
|
|
482
532
|
const result = await engine.react?.( this._clock.delta, this._currentTick, snapshot, engineContext )
|
|
483
533
|
if( result ) allResults.push( result )
|
|
@@ -494,6 +544,7 @@ export class DefaultOrchestrator implements Orchestrator {
|
|
|
494
544
|
)
|
|
495
545
|
}
|
|
496
546
|
}
|
|
547
|
+
finally { this._warnIfSlow( engine, wallClock() - startedAt ) }
|
|
497
548
|
}
|
|
498
549
|
|
|
499
550
|
for( const engine of this._enginesTick() )
|
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'
|