@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
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,12 +214,36 @@ 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 ){
|
|
123
229
|
if( e.type !== CONSEQUENCE_TYPE ) continue
|
|
124
230
|
const d = readConsequence( e.metadata )
|
|
125
|
-
if( d
|
|
231
|
+
if( !d ) continue
|
|
232
|
+
// A descriptor stamped LATER than now came from a previous session: descriptors
|
|
233
|
+
// snapshot with the state, and the tick counter restarts at 1 on wake. Without
|
|
234
|
+
// this the expiry test (`tick < expiresAt`) reads every restored descriptor as
|
|
235
|
+
// freshly live — measured: a Will woke at tick 1 holding three reach-out
|
|
236
|
+
// descriptors stamped 575–596 with expiry 605–626, so for the WHOLE session it
|
|
237
|
+
// believed it had just messaged that person moments ago. It damped every
|
|
238
|
+
// reach-out to them and delivered nothing, and the P2 matcher would equally
|
|
239
|
+
// have attenuated their genuine replies as its own echo.
|
|
240
|
+
if( d.tick > tick ) continue
|
|
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 )
|
|
126
247
|
}
|
|
127
248
|
return out.sort( ( a, b ) => ( a.intentId < b.intentId ? -1 : a.intentId > b.intentId ? 1 : 0 ) )
|
|
128
249
|
}
|
|
@@ -148,6 +269,240 @@ export function matchConsequenceText(
|
|
|
148
269
|
return null
|
|
149
270
|
}
|
|
150
271
|
|
|
272
|
+
// ── P5: the act's own footprint, felt as satiation ───────────────────────────
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* How much of an act's own footprint is still live for this (schema, target),
|
|
276
|
+
* 1 → 0 as the descriptor ages out. 0 when nothing matching is in flight.
|
|
277
|
+
*
|
|
278
|
+
* This is the consumer #112 always described and the descriptors never had. A
|
|
279
|
+
* delivered message wrote its footprint here and nothing ever read it back, so
|
|
280
|
+
* the standing `ideomotor.intent` that produced it kept re-winning the
|
|
281
|
+
* competition and the same words went out again — observed three times, ~21
|
|
282
|
+
* ticks apart, to the same person, while the mind believed each was the first.
|
|
283
|
+
*
|
|
284
|
+
* Deliberately a decaying quantity, not a lock. Right after acting it is ~1 and
|
|
285
|
+
* the pull to repeat is strongly damped; as the window in which the world could
|
|
286
|
+
* still answer runs out it returns to 0 and the appetite comes back on its own.
|
|
287
|
+
* That is a refractory period, not a rule: the mind may still repeat itself if
|
|
288
|
+
* something has become pressing enough to out-compete the damping, which is
|
|
289
|
+
* exactly what a person does when the silence starts to matter.
|
|
290
|
+
*
|
|
291
|
+
* Matches on (schema, target) rather than on the words, because at the moment
|
|
292
|
+
* the competition runs the words do not exist yet — a facet authors them later.
|
|
293
|
+
* "I have just reached out to this person and do not yet know how it landed" is
|
|
294
|
+
* the honest predicate, and it is the one that governs whether to speak again.
|
|
295
|
+
*/
|
|
296
|
+
export function enactionFootprint(
|
|
297
|
+
descriptors: readonly ConsequenceDescriptor[],
|
|
298
|
+
schema: string,
|
|
299
|
+
targetEntityId: string | undefined,
|
|
300
|
+
tick: Tick,
|
|
301
|
+
windowTicks: number = CONSEQUENCE_TTL_TICKS,
|
|
302
|
+
/**
|
|
303
|
+
* Tick of the last thing SAID to each entity, from `conversation.sent`.
|
|
304
|
+
*
|
|
305
|
+
* Descriptors cannot carry satiation on their own: the executor deletes them at
|
|
306
|
+
* `tick >= expiresAt` (motor.schema.executor's TTL sweep), so nothing survives
|
|
307
|
+
* past the echo window and a satiation window longer than it was silently a
|
|
308
|
+
* no-op. Observed live: the same relay delivered twice with `justEnacted` never
|
|
309
|
+
* once reaching the competition.
|
|
310
|
+
*
|
|
311
|
+
* `conversation.sent` is the durable record of having spoken — written by
|
|
312
|
+
* ProactiveCommunicator at push time, snapshotted with the state, and already
|
|
313
|
+
* what discharges an undertaking. Keyed by person rather than by schema on
|
|
314
|
+
* purpose: having just spoken to someone damps speaking to them again, however
|
|
315
|
+
* that speaking happens to be schematised.
|
|
316
|
+
*/
|
|
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>,
|
|
375
|
+
): number {
|
|
376
|
+
if( windowTicks <= 0 ) return 0
|
|
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
|
+
|
|
413
|
+
let strongest = 0
|
|
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
|
+
|
|
421
|
+
const spoken = spokenAt?.get( targetEntityId )
|
|
422
|
+
if( spoken !== undefined ){
|
|
423
|
+
const remaining = ( windowTicks - ( tick - spoken ) ) / windowTicks
|
|
424
|
+
if( remaining > strongest ) strongest = remaining
|
|
425
|
+
}
|
|
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
|
+
|
|
435
|
+
for( const d of descriptors ){
|
|
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
|
|
439
|
+
// Measured from when the act happened, against the SATIATION window — not from
|
|
440
|
+
// the descriptor's own expiry, which is the echo window and a different
|
|
441
|
+
// question. Conflating them meant "how long before I say this again" was
|
|
442
|
+
// pinned to "how long until the world's reply could still arrive", and the
|
|
443
|
+
// second is necessarily short. Observed: re-deliveries landing ~25 ticks
|
|
444
|
+
// apart against a 30-tick echo TTL, so the damping had decayed to almost
|
|
445
|
+
// nothing exactly when the next impulse arrived.
|
|
446
|
+
const elapsed = tick - d.tick
|
|
447
|
+
const remaining = ( windowTicks - elapsed ) / windowTicks
|
|
448
|
+
if( remaining > strongest ) strongest = remaining
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return strongest < 0 ? 0 : strongest > 1 ? 1 : strongest
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* When the mind last SAID something to each person, from the durable
|
|
456
|
+
* `conversation.sent` records. Feeds `enactionFootprint`'s satiation.
|
|
457
|
+
*
|
|
458
|
+
* Unlike consequence descriptors these are never swept, so satiation can run on
|
|
459
|
+
* whatever window the persona wants rather than being capped by the echo TTL.
|
|
460
|
+
*/
|
|
461
|
+
export function spokenAtByEntity(
|
|
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,
|
|
472
|
+
): Map<string, number> {
|
|
473
|
+
const out = new Map<string, number>()
|
|
474
|
+
|
|
475
|
+
for( const [ , e ] of entities ){
|
|
476
|
+
if( e.type !== 'conversation.sent') continue
|
|
477
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
478
|
+
const target = typeof m['targetEntityId'] === 'string' ? m['targetEntityId'] as string : undefined
|
|
479
|
+
if( !target ) continue
|
|
480
|
+
// `metadata.tick` where the writer set one, else the entity's own tick, which
|
|
481
|
+
// StateManager.setEntity stamps from the sim clock on every write. The fallback
|
|
482
|
+
// matters: not every path that records having spoken fills the metadata field,
|
|
483
|
+
// and defaulting to 0 made every record look infinitely old.
|
|
484
|
+
// `updatedAtTick` is the field `StateManager.setEntity` actually stamps.
|
|
485
|
+
// This read `e.tick`, which does not exist on SimulationEntity — so the
|
|
486
|
+
// fallback the comment above describes has never once fired, and any writer
|
|
487
|
+
// that omitted `metadata.tick` was treated as having spoken at tick 0, i.e.
|
|
488
|
+
// infinitely long ago, i.e. not satiating at all.
|
|
489
|
+
const at = typeof m['tick'] === 'number' ? m['tick'] as number
|
|
490
|
+
: typeof e.updatedAtTick === 'number' ? e.updatedAtTick
|
|
491
|
+
: typeof e.tick === 'number' ? e.tick
|
|
492
|
+
: 0
|
|
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 )
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return out
|
|
504
|
+
}
|
|
505
|
+
|
|
151
506
|
// ── ACP-P1: entity correspondence (ACTION_CONDITIONED_PREDICTION §2) ─────────
|
|
152
507
|
|
|
153
508
|
/** Salience multiplier for an entity-correspondence match — gentler than a
|