@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
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindot/will",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"author": "Fabrice <fabrice8@github.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/mindot-ai/will.git"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"will": "dist/cli.js"
|
|
10
|
+
"will": "dist/surface/cli.js"
|
|
11
11
|
},
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"devDependencies": {
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
"types": "./dist/index.d.ts"
|
|
33
33
|
},
|
|
34
34
|
"./mcp": {
|
|
35
|
-
"import": "./dist/mcp/effectors.js",
|
|
36
|
-
"types": "./dist/mcp/effectors.d.ts"
|
|
35
|
+
"import": "./dist/surface/mcp/effectors.js",
|
|
36
|
+
"types": "./dist/surface/mcp/effectors.d.ts"
|
|
37
37
|
},
|
|
38
38
|
"./discord": {
|
|
39
|
-
"import": "./dist/channels/discord.js",
|
|
40
|
-
"types": "./dist/channels/discord.d.ts"
|
|
39
|
+
"import": "./dist/surface/channels/discord.js",
|
|
40
|
+
"types": "./dist/surface/channels/discord.d.ts"
|
|
41
41
|
},
|
|
42
42
|
"./whatsapp": {
|
|
43
|
-
"import": "./dist/channels/whatsapp.js",
|
|
44
|
-
"types": "./dist/channels/whatsapp.d.ts"
|
|
43
|
+
"import": "./dist/surface/channels/whatsapp.js",
|
|
44
|
+
"types": "./dist/surface/channels/whatsapp.d.ts"
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"bugs": {
|
|
@@ -78,11 +78,10 @@
|
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "tsup",
|
|
80
80
|
"build:types": "tsc --project tsconfig.build.json",
|
|
81
|
-
"dev": "bun --env-file=.env run
|
|
81
|
+
"dev": "bun --env-file=.env run examples/thin-shim.ts",
|
|
82
82
|
"dev:build": "tsup --watch",
|
|
83
|
-
"run:social": "bun --env-file=.env run
|
|
84
|
-
"run:outreach": "bun --env-file=.env run
|
|
85
|
-
"start": "bun --env-file=.env run src/runners/thin-shim.runner.ts",
|
|
83
|
+
"run:social": "bun --env-file=.env run examples/social.ts",
|
|
84
|
+
"run:outreach": "bun --env-file=.env run examples/outreach.ts",
|
|
86
85
|
"typecheck": "tsc --noEmit",
|
|
87
86
|
"lint": "tsc --noEmit",
|
|
88
87
|
"prepublishOnly": "bun run typecheck && bun test && bun run build",
|
|
@@ -52,6 +52,26 @@ export interface ConsequenceDescriptor {
|
|
|
52
52
|
text?: string
|
|
53
53
|
/** FNV-1a over the canonicalized parameters (async dispatch). */
|
|
54
54
|
paramsHash?: number
|
|
55
|
+
/**
|
|
56
|
+
* Dispatched but NOT YET PERFORMED — a communicate held awaiting words that do
|
|
57
|
+
* not exist yet.
|
|
58
|
+
*
|
|
59
|
+
* The footprint is written when an intent goes to the async hold, which for an
|
|
60
|
+
* EXTERNAL effector is the act itself (the host is doing it now, and asking
|
|
61
|
+
* twice while waiting is the thing satiation should prevent). For a communicate
|
|
62
|
+
* it is not: the facet has not authored anything, and the mind has said nothing.
|
|
63
|
+
*
|
|
64
|
+
* Counting it as satiation meant ATTEMPTING to speak silenced the mind exactly
|
|
65
|
+
* as hard as speaking. Dormant until `justEnacted` first reached the
|
|
66
|
+
* competition, then immediately visible: a live COO willed `reach-out` 63 times
|
|
67
|
+
* in four minutes, delivered NOTHING, and 18 of those lost to `justEnacted`
|
|
68
|
+
* readings of 0.93–0.98 against a mind that had not spoken once that run. Each
|
|
69
|
+
* attempt wrote a fresh footprint, so the damping never decayed.
|
|
70
|
+
*
|
|
71
|
+
* P2's echo matching is unaffected — a pending descriptor carries no text to
|
|
72
|
+
* match on, which is the same fact from the other side.
|
|
73
|
+
*/
|
|
74
|
+
pending?: boolean
|
|
55
75
|
expiresAt: Tick
|
|
56
76
|
tick: Tick
|
|
57
77
|
}
|
|
@@ -90,6 +110,80 @@ export function consequenceEntity( d: ConsequenceDescriptor ): EntityInput {
|
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
112
|
|
|
113
|
+
// ── The durable enaction record — satiation's third source ───────────────────
|
|
114
|
+
//
|
|
115
|
+
// Descriptors are swept at the ECHO window (`CONSEQUENCE_TTL_TICKS`, 30) because
|
|
116
|
+
// that answers "could the world still be replying to me". Satiation asks a
|
|
117
|
+
// different and longer question (`repeatWindowTicks`, 60 by default), so the
|
|
118
|
+
// second half of the satiation window had no carrier at all and silently damped
|
|
119
|
+
// nothing.
|
|
120
|
+
//
|
|
121
|
+
// The codebase already hit this exact wall twice and closed it twice:
|
|
122
|
+
// • speaking → `conversation.sent`, durable, read as `spokenAt`
|
|
123
|
+
// • objectless → `LearnedSkill.lastEnactedTick`, durable, read as `selfEnactedAt`
|
|
124
|
+
//
|
|
125
|
+
// The gap left was an act WITH an object that is NOT speech — `inspect`, and
|
|
126
|
+
// every entity-bound host effector. `lastEnactedTick` cannot serve it: it is
|
|
127
|
+
// per-schema, so "I just looked up Alice" would damp looking up Bob, which is
|
|
128
|
+
// why the objectless branch is the only place it is consulted. So this is the
|
|
129
|
+
// missing peer: per (schema, target), durable, on the satiation window.
|
|
130
|
+
//
|
|
131
|
+
// Measured on a live COO before this existed: entity-bound acts carried
|
|
132
|
+
// satiation in 40–46% of competitions against 100% for objectless ones, and she
|
|
133
|
+
// re-enacted `discord_lookup_member` toward the same person 33 times in seven
|
|
134
|
+
// minutes.
|
|
135
|
+
export const ENACTED_TYPE = 'agency.enacted'
|
|
136
|
+
|
|
137
|
+
/** Stable id — one record per (schema, target), refreshed in place, so the set is
|
|
138
|
+
* bounded by the pairs actually enacted rather than growing per enaction. */
|
|
139
|
+
export function enactedId( schema: string, targetEntityId: string ): string {
|
|
140
|
+
return `agency-enacted-${ schema }-${ targetEntityId }`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Build/refresh the durable "I did this to them, then" record. */
|
|
144
|
+
export function enactedEntity( schema: string, targetEntityId: string, tick: Tick ): EntityInput {
|
|
145
|
+
return {
|
|
146
|
+
id: enactedId( schema, targetEntityId ),
|
|
147
|
+
type: ENACTED_TYPE,
|
|
148
|
+
metadata: { schema, targetEntityId, tick },
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* When this mind last enacted each (schema, target) pair. Keyed
|
|
154
|
+
* `schema\u0000target` — NUL, because neither half can contain it, so a pair
|
|
155
|
+
* cannot be forged by a schema id that happens to contain the separator.
|
|
156
|
+
*/
|
|
157
|
+
export function enactedAtBySchemaTarget(
|
|
158
|
+
entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
|
|
159
|
+
/**
|
|
160
|
+
* alias id → anchor id, when the caller has an alias table.
|
|
161
|
+
*
|
|
162
|
+
* A FUNCTION rather than the table itself because `social.identity` already
|
|
163
|
+
* imports `fnv1a` from this file, so importing `canonicalOf` back would close a
|
|
164
|
+
* cycle. The caller owns the id space and hands down the resolution; absent, ids
|
|
165
|
+
* are used as they were written, which is exactly the previous behaviour.
|
|
166
|
+
*/
|
|
167
|
+
canon?: ( id: string ) => string,
|
|
168
|
+
): Map<string, number> {
|
|
169
|
+
const out = new Map<string, number>()
|
|
170
|
+
for( const [ , e ] of entities ){
|
|
171
|
+
if( e.type !== ENACTED_TYPE ) continue
|
|
172
|
+
const meta = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
173
|
+
const schema = typeof meta['schema'] === 'string' ? meta['schema'] as string : undefined
|
|
174
|
+
const target = typeof meta['targetEntityId'] === 'string' ? meta['targetEntityId'] as string : undefined
|
|
175
|
+
const tick = typeof meta['tick'] === 'number' ? meta['tick'] as number : undefined
|
|
176
|
+
if( !schema || !target || tick === undefined ) continue
|
|
177
|
+
out.set( enactedKey( schema, canon ? canon( target ) : target ), tick )
|
|
178
|
+
}
|
|
179
|
+
return out
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The map key for a (schema, target) pair. */
|
|
183
|
+
export function enactedKey( schema: string, targetEntityId: string ): string {
|
|
184
|
+
return `${ schema }\u0000${ targetEntityId }`
|
|
185
|
+
}
|
|
186
|
+
|
|
93
187
|
/** Read a descriptor back off entity metadata (P2 matcher + tests). */
|
|
94
188
|
export function readConsequence(
|
|
95
189
|
m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined,
|
|
@@ -106,6 +200,9 @@ export function readConsequence(
|
|
|
106
200
|
textHash: typeof meta['textHash'] === 'number' ? meta['textHash'] as number : undefined,
|
|
107
201
|
text: typeof meta['text'] === 'string' ? meta['text'] as string : undefined,
|
|
108
202
|
paramsHash: typeof meta['paramsHash'] === 'number' ? meta['paramsHash'] as number : undefined,
|
|
203
|
+
// Decoded, not just written — a field only one side knows about is the shape
|
|
204
|
+
// of defect this codebase has hit five times now.
|
|
205
|
+
pending: meta['pending'] === true ? true : undefined,
|
|
109
206
|
expiresAt: typeof meta['expiresAt'] === 'number' ? meta['expiresAt'] as number : 0,
|
|
110
207
|
tick: typeof meta['tick'] === 'number' ? meta['tick'] as number : 0,
|
|
111
208
|
}
|
|
@@ -117,6 +214,15 @@ export function readConsequence(
|
|
|
117
214
|
export function liveConsequences(
|
|
118
215
|
entities: ReadonlyMap<string, { type: string; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
|
|
119
216
|
tick: Tick,
|
|
217
|
+
/**
|
|
218
|
+
* alias id → anchor id, when the caller has an alias table.
|
|
219
|
+
*
|
|
220
|
+
* A FUNCTION rather than the table itself because `social.identity` already
|
|
221
|
+
* imports `fnv1a` from this file, so importing `canonicalOf` back would close a
|
|
222
|
+
* cycle. The caller owns the id space and hands down the resolution; absent, ids
|
|
223
|
+
* are used as they were written, which is exactly the previous behaviour.
|
|
224
|
+
*/
|
|
225
|
+
canon?: ( id: string ) => string,
|
|
120
226
|
): ConsequenceDescriptor[] {
|
|
121
227
|
const out: ConsequenceDescriptor[] = []
|
|
122
228
|
for( const [ , e ] of entities ){
|
|
@@ -132,7 +238,12 @@ export function liveConsequences(
|
|
|
132
238
|
// reach-out to them and delivered nothing, and the P2 matcher would equally
|
|
133
239
|
// have attenuated their genuine replies as its own echo.
|
|
134
240
|
if( d.tick > tick ) continue
|
|
135
|
-
if( tick < d.expiresAt )
|
|
241
|
+
if( tick < d.expiresAt )
|
|
242
|
+
// Canonical, so a descriptor written against a transport id and a candidate
|
|
243
|
+
// aimed at the anchor are recognised as the same act toward the same person.
|
|
244
|
+
out.push( canon && d.targetEntityId
|
|
245
|
+
? { ...d, targetEntityId: canon( d.targetEntityId ) }
|
|
246
|
+
: d )
|
|
136
247
|
}
|
|
137
248
|
return out.sort( ( a, b ) => ( a.intentId < b.intentId ? -1 : a.intentId > b.intentId ? 1 : 0 ) )
|
|
138
249
|
}
|
|
@@ -204,20 +315,127 @@ export function enactionFootprint(
|
|
|
204
315
|
* that speaking happens to be schematised.
|
|
205
316
|
*/
|
|
206
317
|
spokenAt?: ReadonlyMap<string, number>,
|
|
318
|
+
/**
|
|
319
|
+
* Tick this schema was last enacted — `LearnedSkill.lastEnactedTick`, used for
|
|
320
|
+
* an act with NO OBJECT, where "I have just done this" is the whole of the fact
|
|
321
|
+
* and there is no target to key on.
|
|
322
|
+
*
|
|
323
|
+
* Without it the innate floor could not satiate at all. Every objectless stance
|
|
324
|
+
* returned here at the first line, so `repeat` — the largest damping weight in
|
|
325
|
+
* the competition at 0.30 — was structurally zero for `orient · attend · rest ·
|
|
326
|
+
* withdraw · reflect · wait · express`, while `habit` (+0.20) outweighed the
|
|
327
|
+
* novelty it spends (−0.10). A stance that won once therefore won harder next
|
|
328
|
+
* tick, forever. Measured: `express` at habit 1.0 taking 144 of 145 decisions
|
|
329
|
+
* across 300 quiet ticks, with nothing in the mind able to represent that it had
|
|
330
|
+
* just done it.
|
|
331
|
+
*
|
|
332
|
+
* The skill record is the right source for the same reason `conversation.sent`
|
|
333
|
+
* is for speaking: descriptors are swept at the echo TTL, and `agency.outcome`
|
|
334
|
+
* is consumed and deleted the tick after it is written, so neither survives a
|
|
335
|
+
* satiation window. `lastEnactedTick` is durable and snapshotted.
|
|
336
|
+
*/
|
|
337
|
+
selfEnactedAt?: number,
|
|
338
|
+
/**
|
|
339
|
+
* Tick this mind last SPOKE — to anyone. Supplied only for a speaking act, and
|
|
340
|
+
* satiating over the shorter echo window rather than the full repeat window.
|
|
341
|
+
*
|
|
342
|
+
* Satiation was keyed entirely by listener, so "I have already said this" had
|
|
343
|
+
* no bearing once the listener changed. Live, a COO drafted one scoping
|
|
344
|
+
* outline and put substantially the same thing into three destinations inside
|
|
345
|
+
* three minutes — general, a DM, a second channel — with nothing damping it,
|
|
346
|
+
* because each was a different key. She could SEE it: `## What I've Said
|
|
347
|
+
* Lately` listed her own turns across all targets and she posted again 31
|
|
348
|
+
* seconds later. The record was there; the pull had no opposing force.
|
|
349
|
+
*
|
|
350
|
+
* Deliberately WEAKER than the per-person arm, via the shorter window: telling
|
|
351
|
+
* a second person something is legitimate and must stay cheap, while saying it
|
|
352
|
+
* again to someone who already heard it should not be. And deliberately from
|
|
353
|
+
* `conversation.sent` — what was actually SAID — not from the schema's
|
|
354
|
+
* `lastEnactedTick`, which also counts attempts that authored no words and
|
|
355
|
+
* would let a run of declined outreach satiate the mind into silence.
|
|
356
|
+
*
|
|
357
|
+
* Replies are unaffected: an answer to an inbound message is delivered by the
|
|
358
|
+
* audition facet through the outbox and never enters this competition. This
|
|
359
|
+
* damps only self-initiated outreach — which is exactly what broadcasting is.
|
|
360
|
+
*/
|
|
361
|
+
spokeAnywhereAt?: number,
|
|
362
|
+
/**
|
|
363
|
+
* Tick this (schema, target) pair was last enacted, from the durable
|
|
364
|
+
* `agency.enacted` records — the peer of `spokenAt` for an act that has an
|
|
365
|
+
* object but is not speech (`inspect`, every entity-bound host effector).
|
|
366
|
+
*
|
|
367
|
+
* Without it those acts carried satiation only while a consequence descriptor
|
|
368
|
+
* survived, i.e. the echo window, so the back half of the satiation window
|
|
369
|
+
* damped nothing — measured at 40–46% coverage against 100% for objectless
|
|
370
|
+
* acts, with a live COO re-looking-up the same person 33 times in 7 minutes.
|
|
371
|
+
* Keyed by PAIR, not by schema, so damping "look up Alice" leaves "look up
|
|
372
|
+
* Bob" untouched — which is precisely why `selfEnactedAt` cannot serve here.
|
|
373
|
+
*/
|
|
374
|
+
enactedAt?: ReadonlyMap<string, number>,
|
|
207
375
|
): number {
|
|
208
|
-
if( !targetEntityId ) return 0
|
|
209
376
|
if( windowTicks <= 0 ) return 0
|
|
210
377
|
|
|
378
|
+
if( !targetEntityId ){
|
|
379
|
+
// BOTH arms, for the same reason an act with an object has both.
|
|
380
|
+
//
|
|
381
|
+
// `selfEnactedAt` is a skill record, and a skill record is written when the
|
|
382
|
+
// outcome is RECONCILED — for a host effector that is an async ack landing
|
|
383
|
+
// some ticks after the act. In the window between dispatching and learning,
|
|
384
|
+
// satiation read the PREVIOUS enaction and damped almost nothing, while the
|
|
385
|
+
// descriptor saying "I did this on the tick just gone" sat one branch away,
|
|
386
|
+
// written at dispatch and never read: the objectless case returned before
|
|
387
|
+
// the loop that reads it.
|
|
388
|
+
//
|
|
389
|
+
// Measured live on a COO: `discord_server_snapshot` — objectless, cheap,
|
|
390
|
+
// always succeeds — repeated at a gap of THREE ticks in 26 of 47 intervals,
|
|
391
|
+
// while every entity-bound effector in the same run sat at 30–60 under the
|
|
392
|
+
// same weights. Those were not damped harder, they were damped SOONER,
|
|
393
|
+
// because a descriptor exists from the enacting tick and a skill record does
|
|
394
|
+
// not. Half the repeats were slipping through the reconciliation window.
|
|
395
|
+
let strongest = selfEnactedAt === undefined
|
|
396
|
+
? 0
|
|
397
|
+
: ( windowTicks - ( tick - selfEnactedAt ) ) / windowTicks
|
|
398
|
+
|
|
399
|
+
for( const d of descriptors ){
|
|
400
|
+
// Objectless descriptors only. A `look up Alice` must never satiate the
|
|
401
|
+
// objectless form of the same schema — that is the asymmetry `enactedAt`
|
|
402
|
+
// is keyed by PAIR to avoid, and it would read as the mind refusing to do
|
|
403
|
+
// a thing in general because it once did it to someone.
|
|
404
|
+
if( d.schema !== schema || d.targetEntityId !== undefined ) continue
|
|
405
|
+
if( d.pending ) continue
|
|
406
|
+
const remaining = ( windowTicks - ( tick - d.tick ) ) / windowTicks
|
|
407
|
+
if( remaining > strongest ) strongest = remaining
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return strongest < 0 ? 0 : strongest > 1 ? 1 : strongest
|
|
411
|
+
}
|
|
412
|
+
|
|
211
413
|
let strongest = 0
|
|
212
414
|
|
|
415
|
+
const enacted = enactedAt?.get( enactedKey( schema, targetEntityId ) )
|
|
416
|
+
if( enacted !== undefined ){
|
|
417
|
+
const remaining = ( windowTicks - ( tick - enacted ) ) / windowTicks
|
|
418
|
+
if( remaining > strongest ) strongest = remaining
|
|
419
|
+
}
|
|
420
|
+
|
|
213
421
|
const spoken = spokenAt?.get( targetEntityId )
|
|
214
422
|
if( spoken !== undefined ){
|
|
215
423
|
const remaining = ( windowTicks - ( tick - spoken ) ) / windowTicks
|
|
216
424
|
if( remaining > strongest ) strongest = remaining
|
|
217
425
|
}
|
|
218
426
|
|
|
427
|
+
// Having just spoken AT ALL — the arm that is not about the listener. Over the
|
|
428
|
+
// echo window, so it is real but short: a second person is cheap to tell, a
|
|
429
|
+
// third destination in ninety seconds is not. See `spokeAnywhereAt`.
|
|
430
|
+
if( spokeAnywhereAt !== undefined ){
|
|
431
|
+
const remaining = ( CONSEQUENCE_TTL_TICKS - ( tick - spokeAnywhereAt ) ) / CONSEQUENCE_TTL_TICKS
|
|
432
|
+
if( remaining > strongest ) strongest = remaining
|
|
433
|
+
}
|
|
434
|
+
|
|
219
435
|
for( const d of descriptors ){
|
|
220
436
|
if( d.schema !== schema || d.targetEntityId !== targetEntityId ) continue
|
|
437
|
+
// You cannot be satiated by something you have not done. See `pending`.
|
|
438
|
+
if( d.pending ) continue
|
|
221
439
|
// Measured from when the act happened, against the SATIATION window — not from
|
|
222
440
|
// the descriptor's own expiry, which is the echo window and a different
|
|
223
441
|
// question. Conflating them meant "how long before I say this again" was
|
|
@@ -242,6 +460,15 @@ export function enactionFootprint(
|
|
|
242
460
|
*/
|
|
243
461
|
export function spokenAtByEntity(
|
|
244
462
|
entities: ReadonlyMap<string, { type: string; updatedAtTick?: number; tick?: number; metadata?: ReadonlyMap<string, unknown> | Record<string, unknown> }>,
|
|
463
|
+
/**
|
|
464
|
+
* alias id → anchor id, when the caller has an alias table.
|
|
465
|
+
*
|
|
466
|
+
* A FUNCTION rather than the table itself because `social.identity` already
|
|
467
|
+
* imports `fnv1a` from this file, so importing `canonicalOf` back would close a
|
|
468
|
+
* cycle. The caller owns the id space and hands down the resolution; absent, ids
|
|
469
|
+
* are used as they were written, which is exactly the previous behaviour.
|
|
470
|
+
*/
|
|
471
|
+
canon?: ( id: string ) => string,
|
|
245
472
|
): Map<string, number> {
|
|
246
473
|
const out = new Map<string, number>()
|
|
247
474
|
|
|
@@ -263,7 +490,14 @@ export function spokenAtByEntity(
|
|
|
263
490
|
: typeof e.updatedAtTick === 'number' ? e.updatedAtTick
|
|
264
491
|
: typeof e.tick === 'number' ? e.tick
|
|
265
492
|
: 0
|
|
266
|
-
|
|
493
|
+
// Through the alias table, for the reason `readSpokenTurns` states plainly:
|
|
494
|
+
// a REPLY is addressed to the transport id the percept arrived with
|
|
495
|
+
// (`discord:1019…`) while a PROACTIVE message is addressed to the anchor the
|
|
496
|
+
// executive resolved (`ke:…`), and they are the same someone. The PROMPT half
|
|
497
|
+
// resolved that from the start; this half, the one that damps, never did — so
|
|
498
|
+
// having just answered a person did not damp reaching out to them.
|
|
499
|
+
const key = canon ? canon( target ) : target
|
|
500
|
+
if( at > ( out.get( key ) ?? -Infinity ) ) out.set( key, at )
|
|
267
501
|
}
|
|
268
502
|
|
|
269
503
|
return out
|
|
@@ -187,6 +187,38 @@ export function lastHeardByEntity( entities: ReadonlyMap<string, EntityLike> ):
|
|
|
187
187
|
/** When someone last spoke to us, AND what they said. */
|
|
188
188
|
export interface Heard { tick: Tick; preview: string }
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* When each person last spoke to the mind — the DURABLE version.
|
|
192
|
+
*
|
|
193
|
+
* `lastHeardByEntity` reads `conversation.received`, which lives exactly one
|
|
194
|
+
* tick: SocialPerception sweeps it as a one-shot event. So it answers "did
|
|
195
|
+
* someone speak to me THIS tick", never "when did they last speak to me".
|
|
196
|
+
*
|
|
197
|
+
* `answeredAt` is the durable half of the same fact, folded onto the mind's own
|
|
198
|
+
* `conversation.sent` turns by `resolveReplyExpectations` — it is what renders
|
|
199
|
+
* "they answered" in the prompt, and it survives snapshots.
|
|
200
|
+
*
|
|
201
|
+
* Written for the delivery-time re-check (`situationMoved`). That check first
|
|
202
|
+
* shipped keyed only on the mind having SPOKEN since composing, on the argument
|
|
203
|
+
* that nothing durable recorded the other direction. That was wrong — this is
|
|
204
|
+
* it — and the gap was the common case: live, a COO delivered a pre-composed
|
|
205
|
+
* agenda message two seconds after the person changed the subject, having said
|
|
206
|
+
* nothing in between, so the stale words arrived BEFORE her real reply and
|
|
207
|
+
* nothing was looking.
|
|
208
|
+
*/
|
|
209
|
+
export function lastAnsweredByEntity( entities: ReadonlyMap<string, EntityLike> ): Map<string, Tick> {
|
|
210
|
+
const out = new Map<string, Tick>()
|
|
211
|
+
for( const [ , e ] of entities ){
|
|
212
|
+
if( e.type !== SENT_TYPE ) continue
|
|
213
|
+
const m = meta( e )
|
|
214
|
+
const target = str( m['targetEntityId'] )
|
|
215
|
+
const at = num( m['answeredAt'] )
|
|
216
|
+
if( !target || at === undefined ) continue
|
|
217
|
+
if( at > ( out.get( target ) ?? -Infinity ) ) out.set( target, at as Tick )
|
|
218
|
+
}
|
|
219
|
+
return out
|
|
220
|
+
}
|
|
221
|
+
|
|
190
222
|
/** A turn still in the air: said, not acknowledged-only, and not yet answered. */
|
|
191
223
|
export function isOpen( t: SpokenTurn ): boolean {
|
|
192
224
|
return !t.isAck && t.answeredAt === undefined
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from '#agency/selection.scoring'
|
|
38
38
|
import { readEffectiveParams, readPersonaPrior } from '#cognition/persona.prior'
|
|
39
39
|
import { RUPTURE_REVOKE_GATE, revocationEntity } from '#agency/revocation'
|
|
40
|
+
import { liveSettlements, settlementId } from '#agency/settlement'
|
|
40
41
|
import { liveConsequences, matchConsequenceText } from '#agency/consequence'
|
|
41
42
|
import { asFinality } from '#stem/policy/arbiter'
|
|
42
43
|
|
|
@@ -285,8 +286,23 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
285
286
|
catch( err ){ logger.warn(`[selector] rupture publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
// A verdict is held only while the situation that produced it stands. A hard
|
|
290
|
+
// rupture means the world just contradicted the premises the mind decided
|
|
291
|
+
// on, so every standing settlement lapses and those questions are genuinely
|
|
292
|
+
// open again — the same reasoning that revokes a commitment still being
|
|
293
|
+
// weighed, applied to the ones already settled.
|
|
294
|
+
//
|
|
295
|
+
// Deliberately NOT conditioned on something being deliberated right now: the
|
|
296
|
+
// entire point of a settlement is that it outlives the intent that formed
|
|
297
|
+
// it, so it has to be able to lapse without one.
|
|
298
|
+
const settlementDrops = rupture >= RUPTURE_REVOKE_GATE
|
|
299
|
+
? liveSettlements( state.entities, tick ).map( s => settlementId( s.schema, s.targetEntityId ) )
|
|
300
|
+
: []
|
|
301
|
+
const dropSettled = settlementDrops.length > 0 ? { delete: settlementDrops } : {}
|
|
302
|
+
|
|
288
303
|
const busy = ( n: number ): EngineResult => ({
|
|
289
304
|
commands: {
|
|
305
|
+
...dropSettled,
|
|
290
306
|
metrics: [
|
|
291
307
|
[ 'agency.field.eligible', n ],
|
|
292
308
|
[ 'agency.selection.busy', 1 ],
|
|
@@ -323,6 +339,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
323
339
|
return {
|
|
324
340
|
commands: {
|
|
325
341
|
set: [ revocationEntity( deliberating.id, deliberating.schema, revRupture, tick ) ],
|
|
342
|
+
...dropSettled,
|
|
326
343
|
metrics: [
|
|
327
344
|
[ 'agency.field.eligible', eligible.length ],
|
|
328
345
|
[ 'agency.selection.busy', 1 ],
|
|
@@ -340,6 +357,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
340
357
|
if( eligible.length === 0 )
|
|
341
358
|
return {
|
|
342
359
|
commands: {
|
|
360
|
+
...dropSettled,
|
|
343
361
|
metrics: [
|
|
344
362
|
[ 'agency.field.eligible', 0 ],
|
|
345
363
|
[ 'agency.selection.busy',( awaiting || composite ) ? 1 : 0 ],
|
|
@@ -544,7 +562,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
544
562
|
// happens: an intention that has been acted on is no longer an intention, and
|
|
545
563
|
// nothing was deleting these. See MotorSchemaExecutor._dischargeWill.
|
|
546
564
|
...( winner.affordance.evokedBy ? { evokedBy: winner.affordance.evokedBy } : {} ),
|
|
547
|
-
//
|
|
565
|
+
// The plan link (when a plan's frontier-step prior won the competition) —
|
|
548
566
|
// flows through the executor's action.outcome so the PlanningEngine advances.
|
|
549
567
|
...( winner.affordance.planId ? { planId: winner.affordance.planId } : {} ),
|
|
550
568
|
...( winner.affordance.stepId ? { stepId: winner.affordance.stepId } : {} ),
|
|
@@ -628,7 +646,9 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
628
646
|
set: compositeTombstone
|
|
629
647
|
? [ intent, revocationEntity( compositeTombstone, compositeFrom ?? '', rupture, tick ) ]
|
|
630
648
|
: [ intent ],
|
|
631
|
-
...( preemptDelete
|
|
649
|
+
...( preemptDelete || settlementDrops.length > 0
|
|
650
|
+
? { delete: [ ...( preemptDelete ? [ preemptDelete ] : [] ), ...settlementDrops ] }
|
|
651
|
+
: {} ),
|
|
632
652
|
metrics: [
|
|
633
653
|
[ 'agency.field.eligible', eligible.length ],
|
|
634
654
|
[ 'agency.selection.busy', 0 ],
|
|
@@ -685,6 +705,14 @@ function computeRupture(
|
|
|
685
705
|
for( const e of state.entities.values() ){
|
|
686
706
|
if( e.type !== 'percept') continue
|
|
687
707
|
const m = e.metadata
|
|
708
|
+
// Deliberately NOT asProvenance(). Absence is load-bearing here and only
|
|
709
|
+
// here: an UNTAGGED percept is excluded from rupture, and three writers
|
|
710
|
+
// are untagged — `escalation.buffer` (x2) and the wake percept in
|
|
711
|
+
// `stem/index.ts`. Normalizing would default them to 'exafferent' and make
|
|
712
|
+
// them rupture-eligible. That is a real behaviour change, and probably the
|
|
713
|
+
// right one — a mind waking after hours offline currently cannot be
|
|
714
|
+
// ruptured by that fact — but it belongs to the phase that tags those
|
|
715
|
+
// writers, with tests. See .TODO/SIGNAL_BOUNDARY.md.
|
|
688
716
|
if( str( m?.['provenance'] ) !== 'exafferent') continue
|
|
689
717
|
const pTick = num( m?.['tick'], -1 )
|
|
690
718
|
if( pTick < 0 || tick - pTick > RUPTURE_WINDOW_TICKS ) continue
|
|
@@ -792,7 +820,31 @@ function buildBias( state: ReadonlySimulationState ): BiasContext {
|
|
|
792
820
|
|
|
793
821
|
// ─── entity decoding ─────────────────────────────────────────────────────────
|
|
794
822
|
|
|
795
|
-
|
|
823
|
+
/**
|
|
824
|
+
* Decode an `affordance` entity back into the thing the competition scores.
|
|
825
|
+
*
|
|
826
|
+
* This is the ONE hop between synthesis and selection, and three fields did not
|
|
827
|
+
* cross it — each the payload of a mechanism that therefore never ran:
|
|
828
|
+
*
|
|
829
|
+
* • `justEnacted` — `scoreAffordance` subtracts `w.repeat × justEnacted`, the
|
|
830
|
+
* largest damping weight in the competition (0.30). Undefined here meant ×0
|
|
831
|
+
* always, so no act has ever been damped for having just been done. EXAFFERENCE
|
|
832
|
+
* P5 and the `spokenAt` durability fix both landed on a field nothing read.
|
|
833
|
+
* • `availability` — POLICY_REAFFERENCE P2 damps a refused ability so it competes
|
|
834
|
+
* weakly instead of vanishing. `a.availability ?? 1` read the fallback forever.
|
|
835
|
+
* • `settled` — a verdict System 2 already reached. Same weight class as
|
|
836
|
+
* `justEnacted` and the same failure if dropped: the mind re-deliberates a
|
|
837
|
+
* question it has answered, forever. Added WITH its decoder rather than
|
|
838
|
+
* after someone notices the mechanism is dark.
|
|
839
|
+
* • `description` — the schema's MEANING. The selector snapshots it onto
|
|
840
|
+
* `metadata.candidates` and the DeliberationEngine renders it as `— <what>`,
|
|
841
|
+
* so the candidate list the mind reasons over listed acts with no meanings.
|
|
842
|
+
*
|
|
843
|
+
* Exported for the round-trip guard: what synthesis writes, selection must read.
|
|
844
|
+
* Four fields in this arc have been carried most of the way and lost at the last
|
|
845
|
+
* hop, so the property is pinned rather than re-checked by eye.
|
|
846
|
+
*/
|
|
847
|
+
export function readAffordance( id: string, m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined ): Affordance {
|
|
796
848
|
const meta = ( m ?? {} ) as Record<string, unknown>
|
|
797
849
|
return {
|
|
798
850
|
id,
|
|
@@ -810,6 +862,10 @@ function readAffordance( id: string, m: ReadonlyMap<string, unknown> | Record<st
|
|
|
810
862
|
planBias: typeof meta['planBias'] === 'number' ? ( meta['planBias'] as number ) : undefined,
|
|
811
863
|
willBias: typeof meta['willBias'] === 'number' ? ( meta['willBias'] as number ) : undefined,
|
|
812
864
|
socialPrior: typeof meta['socialPrior'] === 'number' ? ( meta['socialPrior'] as number ) : undefined,
|
|
865
|
+
justEnacted: typeof meta['justEnacted'] === 'number' ? ( meta['justEnacted'] as number ) : undefined,
|
|
866
|
+
settled: typeof meta['settled'] === 'number' ? ( meta['settled'] as number ) : undefined,
|
|
867
|
+
availability: typeof meta['availability'] === 'number' ? ( meta['availability'] as number ) : undefined,
|
|
868
|
+
description: str( meta['description'] ),
|
|
813
869
|
planId: str( meta['planId'] ),
|
|
814
870
|
stepId: str( meta['stepId'] ),
|
|
815
871
|
tick: num( meta['tick'], 0 ),
|