@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
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { logger } from '#core/logger'
|
|
26
26
|
import type {
|
|
27
|
-
Duration, Tick, SimulationContext,
|
|
27
|
+
Duration, Tick, SimulationContext, SimulationEvent,
|
|
28
28
|
ReadonlySimulationState, StateCommands, EntityInput,
|
|
29
29
|
} from '#core/types'
|
|
30
30
|
import type { CognitiveBus } from '#cognition/bus'
|
|
@@ -39,10 +39,21 @@ import type { ActionRequest, ActionResult } from '#types'
|
|
|
39
39
|
import { INNATE_SCHEMAS } from '#agency/schemas/innate'
|
|
40
40
|
import { enact, type Enaction } from '#agency/execution.primitives'
|
|
41
41
|
import { revokedIntentIds, revocationId, staleRevocationIds } from '#agency/revocation'
|
|
42
|
+
import { addressesOf } from '#cognition/social.identity'
|
|
43
|
+
import { lastAnsweredByEntity } from '#agency/conversation.aim'
|
|
42
44
|
import {
|
|
43
|
-
CONSEQUENCE_TYPE, CONSEQUENCE_TTL_TICKS,
|
|
44
|
-
consequenceEntity, fnv1a, paramsKey,
|
|
45
|
-
|
|
45
|
+
CONSEQUENCE_TYPE, CONSEQUENCE_TTL_TICKS, ENACTED_TYPE,
|
|
46
|
+
consequenceEntity, enactedEntity, fnv1a, paramsKey, spokenAtByEntity } from '#agency/consequence'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* How long a durable enaction record is kept. Generous against the satiation
|
|
50
|
+
* window (`repeatWindowTicks`, 60 by default) rather than equal to it, because
|
|
51
|
+
* the window is persona-tunable and read in the synthesizer, not here — a
|
|
52
|
+
* retention shorter than some tenant's window would silently reintroduce the
|
|
53
|
+
* very gap this record exists to close. Past this the satiation term is 0
|
|
54
|
+
* regardless, so keeping it longer buys nothing.
|
|
55
|
+
*/
|
|
56
|
+
const ENACTED_RETENTION_TICKS = 600
|
|
46
57
|
|
|
47
58
|
/** Ticks an async (communicate/external) intent may stay 'awaiting' before it is
|
|
48
59
|
* abandoned. Exported: the ReafferenceEngine's sensory-confirmation path (P5)
|
|
@@ -78,9 +89,41 @@ interface Intent {
|
|
|
78
89
|
evokedBy?: string
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
/**
|
|
93
|
+
* What an authoring pass produced.
|
|
94
|
+
*
|
|
95
|
+
* `bubbles` empty is AMBIGUOUS on its own — it is also what a timed-out facet, a
|
|
96
|
+
* full facet budget, and a pass deferring to one already in flight all return.
|
|
97
|
+
* `answered` is what disambiguates it: whether a facet actually reasoned and came
|
|
98
|
+
* back. An empty answer IS an answer; an empty non-answer means the pass never
|
|
99
|
+
* happened and is worth asking for again.
|
|
100
|
+
*/
|
|
101
|
+
export interface OutreachResult {
|
|
102
|
+
bubbles: string[]
|
|
103
|
+
/** The mind considered speaking and declared silence — the strongest answer. */
|
|
104
|
+
withheld?: boolean
|
|
105
|
+
/**
|
|
106
|
+
* A facet reasoned and its decision came back, whatever it held. Absent means
|
|
107
|
+
* no pass ran at all (no executive, budget full, deferring to one in flight,
|
|
108
|
+
* timed out, threw) — only THAT is worth asking again for.
|
|
109
|
+
*
|
|
110
|
+
* Without this the caller could not tell the two apart, so it re-asked on
|
|
111
|
+
* every tick. Observed live: nineteen authoring passes for one outreach, each
|
|
112
|
+
* returning an empty answer, none of them delivering a word, and the person
|
|
113
|
+
* she had decided to contact never heard from her.
|
|
114
|
+
*/
|
|
115
|
+
answered?: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
81
118
|
/** Authors the words for a self-initiated communicate the agency selected (no inbound triggered it). */
|
|
82
119
|
export interface OutreachAuthor {
|
|
83
|
-
|
|
120
|
+
/** An array return is still honoured — it reads as "no words, and I am not saying why". */
|
|
121
|
+
authorOutreach( entityId: string, entityName: string, gist?: string ): Promise<string[] | OutreachResult>
|
|
122
|
+
/**
|
|
123
|
+
* True while a turn with this person is still resolving — they spoke and the
|
|
124
|
+
* reply has not landed yet. Optional: an author that cannot say is not blocking.
|
|
125
|
+
*/
|
|
126
|
+
isSpeakingTo?( entityId: string ): boolean
|
|
84
127
|
}
|
|
85
128
|
|
|
86
129
|
export class MotorSchemaExecutor implements CognitiveEngine {
|
|
@@ -108,6 +151,30 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
108
151
|
*/
|
|
109
152
|
private _authoring = new Set<string>()
|
|
110
153
|
private _authored = new Map<string, string[]>()
|
|
154
|
+
/**
|
|
155
|
+
* Intents whose facet considered speaking and chose NOT to.
|
|
156
|
+
*
|
|
157
|
+
* A declined outreach used to resolve by rotting: no words arrived, so the
|
|
158
|
+
* intent sat 'awaiting' until AWAIT_TIMEOUT abandoned it as a FAILURE — and
|
|
159
|
+
* reafference folded that into `reach-out`'s competence. The mind was learning
|
|
160
|
+
* it is bad at speaking from the times it decided not to speak. Live, a COO
|
|
161
|
+
* declining correctly ("nothing new to add — a sixth message would repeat")
|
|
162
|
+
* took a competence hit for the judgement.
|
|
163
|
+
*
|
|
164
|
+
* Silence is an ANSWER, not the absence of one. Held here so the sweep can
|
|
165
|
+
* resolve it as what it is: the intent is freed, and nothing is taught about
|
|
166
|
+
* an ability that was never in question.
|
|
167
|
+
*/
|
|
168
|
+
private _withheld = new Map<string, string>()
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The tick at which each held intent's words were REQUESTED from a facet.
|
|
172
|
+
*
|
|
173
|
+
* Authoring is off-tick and can take 10–30s of real time. In that window the
|
|
174
|
+
* situation the words were composed for can move — and until now nothing
|
|
175
|
+
* looked. See `situationMoved`.
|
|
176
|
+
*/
|
|
177
|
+
private _composedAt = new Map<string, Tick>()
|
|
111
178
|
|
|
112
179
|
constructor( schemas: MotorSchema[] = INNATE_SCHEMAS ){
|
|
113
180
|
for( const s of schemas ) this._schemas.set( s.id, s )
|
|
@@ -136,6 +203,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
136
203
|
{ type: 'agency.enacted', version: 1, validate: () => null },
|
|
137
204
|
{ type: 'agency.invocation', version: 1, validate: () => null },
|
|
138
205
|
{ type: 'agency.communicate', version: 1, validate: () => null },
|
|
206
|
+
{ type: 'action.withheld', version: 1, validate: () => null },
|
|
139
207
|
]
|
|
140
208
|
}
|
|
141
209
|
subscribes(): string[] { return [] }
|
|
@@ -144,7 +212,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
144
212
|
* The executor is plan-agnostic. A plan does NOT dispatch steps here — it biases
|
|
145
213
|
* the affordance competition (see PLANNING_AS_PRIOR_TODO.md), so a plan-driven
|
|
146
214
|
* action reaches the executor as an ordinary committed `agency.intent` the
|
|
147
|
-
* selector won. That intent already carries planId/stepId
|
|
215
|
+
* selector won. That intent already carries its planId/stepId link (stamped by
|
|
148
216
|
* the selector from the winning affordance); `_emitActionOutcome` threads it back
|
|
149
217
|
* out, which is how the PlanningEngine advances. Nothing plan-specific here.
|
|
150
218
|
*/
|
|
@@ -161,6 +229,10 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
161
229
|
const set: EntityInput[] = []
|
|
162
230
|
const del: string[] = []
|
|
163
231
|
const metrics: Array<[ string, number ]> = []
|
|
232
|
+
// Dispatch announcements bound for the SIMULATION bus — the only one the host
|
|
233
|
+
// hears. See _emitDispatch: the cognitive copy reaches the mind's own
|
|
234
|
+
// faculties and reaches no host at all.
|
|
235
|
+
const events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = []
|
|
164
236
|
|
|
165
237
|
const energy = state.metrics.get('energy.level') ?? 100
|
|
166
238
|
const stress = state.metrics.get('stress.load' ) ?? 0
|
|
@@ -173,6 +245,22 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
173
245
|
if( tick >= num( e.metadata?.['expiresAt'], 0 ) ) del.push( id )
|
|
174
246
|
}
|
|
175
247
|
|
|
248
|
+
// ── Expire durable enaction records ──────────────────────────
|
|
249
|
+
// One per (schema, target), refreshed in place, so the live set is bounded
|
|
250
|
+
// by pairs actually enacted rather than by enactions — but a Will that meets
|
|
251
|
+
// many people would still accrete one per person per schema forever, and the
|
|
252
|
+
// soak test asserts entities plateau. Dropped once no satiation window could
|
|
253
|
+
// still be reading them: past ENACTED_RETENTION_TICKS the term is 0 anyway,
|
|
254
|
+
// so the record is indistinguishable from its own absence.
|
|
255
|
+
for( const [ id, e ] of state.entities ){
|
|
256
|
+
if( e.type !== ENACTED_TYPE ) continue
|
|
257
|
+
const at = num( e.metadata?.['tick'], 0 )
|
|
258
|
+
// A record stamped later than now is a restored one from a previous
|
|
259
|
+
// session (the tick counter restarts on wake) — same trap `liveConsequences`
|
|
260
|
+
// documents. Drop it rather than let it read as "just now" all session.
|
|
261
|
+
if( at > tick || tick - at > ENACTED_RETENTION_TICKS ) del.push( id )
|
|
262
|
+
}
|
|
263
|
+
|
|
176
264
|
// ── Revocation tombstones (EXAFFERENCE P4) ───────────────────
|
|
177
265
|
// Reap expired tombstones (intent vanished otherwise), and collect live ones
|
|
178
266
|
// so the selected-processing loop can refuse the half-race case: Deliberation
|
|
@@ -217,6 +305,10 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
217
305
|
// that will never be said — drop them so the map cannot grow without bound.
|
|
218
306
|
for( const id of this._authored.keys() )
|
|
219
307
|
if( !state.entities.has( id ) ) this._authored.delete( id )
|
|
308
|
+
for( const id of this._withheld.keys() )
|
|
309
|
+
if( !state.entities.has( id ) ) this._withheld.delete( id )
|
|
310
|
+
for( const id of this._composedAt.keys() )
|
|
311
|
+
if( !state.entities.has( id ) ) this._composedAt.delete( id )
|
|
220
312
|
|
|
221
313
|
// ── Timeout stranded async intents ───────────────────────────
|
|
222
314
|
// An 'awaiting' intent whose host/delivery never returned would block the
|
|
@@ -233,6 +325,48 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
233
325
|
// pause is bounded, not open-ended: `authorOutreach` always settles (its own 60s
|
|
234
326
|
// timeout resolves empty), so `_authoring` always clears and the clock resumes.
|
|
235
327
|
if( this._authoring.has( id ) ) continue
|
|
328
|
+
// The facet was asked and chose silence. Resolve it as that rather than
|
|
329
|
+
// letting the clock abandon it as a failure — see `_withheld`.
|
|
330
|
+
if( this._withheld.has( id ) ){
|
|
331
|
+
const intent = readIntent( id, e.metadata )
|
|
332
|
+
const why = this._withheld.get( id ) ?? 'I considered speaking and chose not to.'
|
|
333
|
+
set.push({
|
|
334
|
+
id: `agency-outcome-${ tick }-${ id }`,
|
|
335
|
+
type: 'agency.outcome',
|
|
336
|
+
metadata: {
|
|
337
|
+
schema: intent.schema, intentId: id,
|
|
338
|
+
targetEntityId: intent.targetEntityId,
|
|
339
|
+
withheld: true,
|
|
340
|
+
description: why,
|
|
341
|
+
mode: 'communicate', tick,
|
|
342
|
+
...( intent.planId ? { planId: intent.planId } : {} ),
|
|
343
|
+
...( intent.planStepId ? { stepId: intent.planStepId } : {} ),
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
del.push( id )
|
|
347
|
+
this._withheld.delete( id )
|
|
348
|
+
metrics.push([ 'agency.communicate.withheld', 1 ])
|
|
349
|
+
// A DISTINCT event, never `action.outcome`: six faculties learn from that
|
|
350
|
+
// one's `success`, and a withheld turn published as `success: false`
|
|
351
|
+
// teaches the mind it is bad at speaking from the times it decided not to
|
|
352
|
+
// speak — the exact regression #123 exists to prevent. This one is read
|
|
353
|
+
// only by the executive's record of what it did.
|
|
354
|
+
if( this._bus ){
|
|
355
|
+
try {
|
|
356
|
+
this._bus.publish({
|
|
357
|
+
type: 'action.withheld', version: 1, sourceEngine: this.name,
|
|
358
|
+
salience: 0.4,
|
|
359
|
+
payload: {
|
|
360
|
+
actionType: intent.schema, targetEntityId: intent.targetEntityId,
|
|
361
|
+
description: why, tick,
|
|
362
|
+
...( intent.planId ? { planId: intent.planId } : {} ),
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
catch { /* unregistered schema is telemetry-only */ }
|
|
367
|
+
}
|
|
368
|
+
continue
|
|
369
|
+
}
|
|
236
370
|
// POLICY_REAFFERENCE P4 — an escalated intent is HELD: the stem owns its
|
|
237
371
|
// lifecycle (extended TTL → approve/deny/expire), so the executor must not
|
|
238
372
|
// time it out at AWAIT_TIMEOUT and reconcile it as a phantom failure.
|
|
@@ -278,7 +412,8 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
278
412
|
del.push( id )
|
|
279
413
|
this._emitEnacted( intent, timedOut, predicted, tick )
|
|
280
414
|
if( intent.planId && intent.planStepId )
|
|
281
|
-
this._emitActionOutcome( intent, false, 0, 1, tick
|
|
415
|
+
this._emitActionOutcome( intent, false, 0, 1, tick,
|
|
416
|
+
`No answer came back — I gave up waiting after ${ tick - dispatchedAt } ticks.` )
|
|
282
417
|
logger.info(`[motor] ⏱ "${ intent.schema }" timed out after ${ tick - dispatchedAt } ticks`)
|
|
283
418
|
}
|
|
284
419
|
|
|
@@ -321,7 +456,8 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
321
456
|
enactedCount++
|
|
322
457
|
this._emitEnacted( intent, enaction, predicted, tick )
|
|
323
458
|
this._emitActionOutcome( intent, enaction.success, enaction.outcomeQuality,
|
|
324
|
-
clamp01( Math.abs( predicted.expectedReward - enaction.outcomeQuality ) ), tick
|
|
459
|
+
clamp01( Math.abs( predicted.expectedReward - enaction.outcomeQuality ) ), tick,
|
|
460
|
+
enaction.description )
|
|
325
461
|
|
|
326
462
|
// If this was a macro step, advance (or finalize) its parent.
|
|
327
463
|
if( intent.parentIntentId )
|
|
@@ -360,6 +496,13 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
360
496
|
set.push( consequenceEntity({
|
|
361
497
|
intentId: id, schema: intent.schema,
|
|
362
498
|
mode: enaction.mode === 'communicate' ? 'communicate' : 'external',
|
|
499
|
+
// A communicate with no words yet has not happened. The footprint is
|
|
500
|
+
// still written (P1/P2 want it the moment the words land), but it must
|
|
501
|
+
// not satiate — attempting to speak is not speaking. See
|
|
502
|
+
// ConsequenceDescriptor.pending. An EXTERNAL dispatch IS the act: the
|
|
503
|
+
// host is doing it now, and not asking twice while waiting is exactly
|
|
504
|
+
// what satiation is for.
|
|
505
|
+
...( enaction.mode === 'communicate' && !awaitingText ? { pending: true } : {} ),
|
|
363
506
|
...( enaction.mode === 'communicate'
|
|
364
507
|
? { effector: COMM_SCHEMA_TO_EFFECTOR[ intent.schema ] ?? intent.schema }
|
|
365
508
|
: {} ),
|
|
@@ -368,7 +511,16 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
368
511
|
paramsHash: fnv1a( paramsKey( intent.parameters ) ),
|
|
369
512
|
expiresAt: tick + CONSEQUENCE_TTL_TICKS, tick,
|
|
370
513
|
}) )
|
|
371
|
-
|
|
514
|
+
// The durable half, for an act with an object that is not speech.
|
|
515
|
+
// Speech has `conversation.sent` and objectless acts have
|
|
516
|
+
// `LearnedSkill.lastEnactedTick`; this is the peer those two left out,
|
|
517
|
+
// and without it satiation expired with the descriptor at the ECHO
|
|
518
|
+
// window rather than lasting the satiation window. Written at the same
|
|
519
|
+
// moment as the descriptor — the dispatch IS the act for an external
|
|
520
|
+
// effector — and keyed per (schema, target) so it refreshes in place.
|
|
521
|
+
if( intent.targetEntityId && enaction.mode !== 'communicate')
|
|
522
|
+
set.push( enactedEntity( intent.schema, intent.targetEntityId, tick ) )
|
|
523
|
+
events.push( ...this._emitDispatch( intent, enaction.mode, tick, state ) )
|
|
372
524
|
metrics.push([ enaction.mode === 'communicate'
|
|
373
525
|
? 'agency.communicate.dispatched'
|
|
374
526
|
: 'agency.invocation.dispatched', 1 ])
|
|
@@ -377,7 +529,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
377
529
|
}
|
|
378
530
|
|
|
379
531
|
metrics.push([ 'agency.executor.enacted', enactedCount ])
|
|
380
|
-
return { commands: { set, delete: del, metrics } }
|
|
532
|
+
return { commands: { set, delete: del, metrics }, ...( events.length > 0 ? { events } : {} ) }
|
|
381
533
|
}
|
|
382
534
|
|
|
383
535
|
// ── composite machinery ──────────────────────────────────────
|
|
@@ -453,7 +605,8 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
453
605
|
this._emitEnacted( compIntent, compEnaction,
|
|
454
606
|
{ expectedReward: compIntent.expectedReward, expectedValence: compIntent.expectedValence }, tick )
|
|
455
607
|
this._emitActionOutcome( compIntent, true, avgQuality,
|
|
456
|
-
clamp01( Math.abs( compIntent.expectedReward - avgQuality ) ), tick
|
|
608
|
+
clamp01( Math.abs( compIntent.expectedReward - avgQuality ) ), tick,
|
|
609
|
+
compEnaction.description )
|
|
457
610
|
}
|
|
458
611
|
|
|
459
612
|
private _subIntent(
|
|
@@ -516,17 +669,63 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
516
669
|
return true
|
|
517
670
|
}
|
|
518
671
|
|
|
672
|
+
// ── Am I mid-sentence with them? ─────────────────────────────
|
|
673
|
+
// A reply and a self-initiated message are two paths to the same person, and
|
|
674
|
+
// neither can see the other. Observed live: a conversation facet answered a
|
|
675
|
+
// question and the agency delivered a proactive message to the same human in
|
|
676
|
+
// the SAME millisecond — one mind speaking to one person twice at once.
|
|
677
|
+
//
|
|
678
|
+
// Holding costs a tick and the words keep. If what they just said changes
|
|
679
|
+
// what I meant to say, `situationMoved` catches that on the way back round,
|
|
680
|
+
// which is the outcome we want anyway.
|
|
681
|
+
if( intent.targetEntityId && this._author?.isSpeakingTo?.( intent.targetEntityId ) ){
|
|
682
|
+
logger.debug(`[motor] holding "${ intent.schema }" — a turn with ${ intent.targetEntityId } is in flight`)
|
|
683
|
+
metrics.push([ 'agency.communicate.mid_turn', 1 ])
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
|
|
519
687
|
// Content authored upstream when present (host / host-facet), else words a facet
|
|
520
688
|
// authored off-tick and landed since a previous tick asked for them. Neither ⇒
|
|
521
689
|
// request authoring (never awaited here — see `_authoring`) and hold 'awaiting'.
|
|
522
690
|
const authored = str( intent.parameters['content'] ) ?? firstMessage( intent.parameters['messages'] )
|
|
691
|
+
const fromFacet = !authored
|
|
523
692
|
let bubbles: string[] = authored ? [ authored ] : ( this._authored.get( id ) ?? [] )
|
|
524
693
|
this._authored.delete( id )
|
|
525
694
|
if( bubbles.length === 0 ){
|
|
526
|
-
this._requestAuthoring( id, intent )
|
|
695
|
+
this._requestAuthoring( id, intent, tick )
|
|
527
696
|
return false // nothing to send yet → await
|
|
528
697
|
}
|
|
529
698
|
|
|
699
|
+
// ── Is this still what I want to say? ─────────────────────────
|
|
700
|
+
// Words a facet composed off-tick were formed against the situation as it
|
|
701
|
+
// stood when they were asked for. Delivering them into a situation that has
|
|
702
|
+
// since moved is not the act that was decided on — it is an older act
|
|
703
|
+
// arriving late wearing the present's clothes.
|
|
704
|
+
//
|
|
705
|
+
// Live: a COO committed to outreach, was told "I need my full attention on
|
|
706
|
+
// that right now, will brief you later", acknowledged that correctly — and
|
|
707
|
+
// 41 seconds later delivered the three-bubble message composed beforehand,
|
|
708
|
+
// asking for a brain-dump. Same shape at 3am with a colleague who had just
|
|
709
|
+
// said goodnight: "Night. Talk soon." followed one second later by two
|
|
710
|
+
// unprompted messages. Satiation cannot damp either, because satiation gates
|
|
711
|
+
// SELECTION and these were already selected. Nothing looked at the door.
|
|
712
|
+
//
|
|
713
|
+
// Only facet-composed words are checked. Content supplied upstream
|
|
714
|
+
// (`parameters.content`) came from the host or a host-facet with the present
|
|
715
|
+
// situation in hand, and a reply never reaches this path at all — it is
|
|
716
|
+
// delivered by the audition facet through the outbox.
|
|
717
|
+
const moved = fromFacet
|
|
718
|
+
? situationMoved( state, intent.targetEntityId, this._composedAt.get( id ) )
|
|
719
|
+
: null
|
|
720
|
+
if( moved ){
|
|
721
|
+
this._withheld.set( id, moved )
|
|
722
|
+
this._composedAt.delete( id )
|
|
723
|
+
logger.debug(`[motor] "${ intent.schema }" withheld — ${ moved }`)
|
|
724
|
+
metrics.push([ 'agency.communicate.stale', 1 ])
|
|
725
|
+
return false // resolved as withheld by the sweep: freed, and nothing taught
|
|
726
|
+
}
|
|
727
|
+
this._composedAt.delete( id )
|
|
728
|
+
|
|
530
729
|
const request: ActionRequest = {
|
|
531
730
|
effector,
|
|
532
731
|
parameters: { ...intent.parameters, messages: bubbles },
|
|
@@ -567,7 +766,8 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
567
766
|
|
|
568
767
|
this._emitEnacted( intent, out, predicted, tick )
|
|
569
768
|
this._emitActionOutcome( intent, result.success, result.feedback.outcomeQuality,
|
|
570
|
-
clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick
|
|
769
|
+
clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick,
|
|
770
|
+
out.description )
|
|
571
771
|
metrics.push([ 'agency.communicate.delivered', 1 ])
|
|
572
772
|
return true
|
|
573
773
|
}
|
|
@@ -578,16 +778,46 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
578
778
|
* per intent — a request already in flight is not duplicated, so the intent may sit
|
|
579
779
|
* 'awaiting' across many ticks with exactly one LLM call behind it.
|
|
580
780
|
*/
|
|
581
|
-
private _requestAuthoring( id: string, intent: Intent ): void {
|
|
781
|
+
private _requestAuthoring( id: string, intent: Intent, tick: Tick ): void {
|
|
582
782
|
if( !this._author || this._authoring.has( id ) ) return
|
|
583
783
|
|
|
584
784
|
const name = str( intent.parameters['targetEntityName'] ) ?? intent.targetEntityId ?? 'them'
|
|
585
785
|
this._authoring.add( id )
|
|
786
|
+
this._composedAt.set( id, tick )
|
|
586
787
|
void this._author
|
|
587
788
|
.authorOutreach( intent.targetEntityId ?? '', name, str( intent.parameters['gist'] ) )
|
|
588
|
-
.then(
|
|
589
|
-
|
|
590
|
-
|
|
789
|
+
.then( result => {
|
|
790
|
+
const bubbles = Array.isArray( result ) ? result : result.bubbles
|
|
791
|
+
const declared = !Array.isArray( result ) && result.withheld === true
|
|
792
|
+
const answered = !Array.isArray( result ) && result.answered === true
|
|
793
|
+
|
|
794
|
+
if( bubbles.length > 0 ){ this._authored.set( id, bubbles ); return }
|
|
795
|
+
|
|
796
|
+
// Not a warning: the facet was asked and answered. Choosing not to speak
|
|
797
|
+
// is a decision the mind is entitled to make.
|
|
798
|
+
if( declared ){
|
|
799
|
+
this._withheld.set( id, 'I considered speaking and chose not to.')
|
|
800
|
+
logger.debug(`[motor] "${ intent.schema }" withheld — the facet chose silence`)
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
// Asked, reasoned, and came back with nothing — neither words nor a
|
|
804
|
+
// declared silence. That is still an answer, and re-asking puts the same
|
|
805
|
+
// question to the same mind against the same situation, so it comes back
|
|
806
|
+
// the same way. Nineteen times, in the run this exists to prevent.
|
|
807
|
+
//
|
|
808
|
+
// Resolved as withheld rather than failed on purpose: nothing was said,
|
|
809
|
+
// and nothing should be taught about being bad at speaking — the #123
|
|
810
|
+
// regression. The reason is worded for what actually happened.
|
|
811
|
+
if( answered ){
|
|
812
|
+
this._withheld.set( id, 'I turned it over and came back with no words.')
|
|
813
|
+
logger.info(`[motor] "${ intent.schema }" withheld — the pass produced no words`)
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// No pass ran at all — a dead author, a full budget, or one deferring to
|
|
818
|
+
// a pass already in flight. Keep holding: the clock is the right judge of
|
|
819
|
+
// those, and the deferring case wants to come back round.
|
|
820
|
+
logger.warn(`[motor] outreach authoring never ran for "${ intent.schema }"`)
|
|
591
821
|
} )
|
|
592
822
|
.catch( err => { logger.warn(`[motor] outreach authoring failed: ${ errMsg( err ) }`) } )
|
|
593
823
|
.finally( () => { this._authoring.delete( id ) } )
|
|
@@ -649,6 +879,14 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
649
879
|
|
|
650
880
|
private _emitActionOutcome(
|
|
651
881
|
intent: Intent, success: boolean, outcomeQuality: number, surprise: number, tick: Tick,
|
|
882
|
+
/**
|
|
883
|
+
* What happened, in words. This payload builds `action.record`, which the
|
|
884
|
+
* prompt renders as `## What Became Of What I Did` — and until now this method
|
|
885
|
+
* published no description at all, so that section showed the action's NAME
|
|
886
|
+
* and nothing else. Sixty-five lookups rendered as sixty-five lines saying
|
|
887
|
+
* `discord_lookup_member` and never once what was found.
|
|
888
|
+
*/
|
|
889
|
+
description?: string,
|
|
652
890
|
): void {
|
|
653
891
|
if( !this._bus ) return
|
|
654
892
|
try {
|
|
@@ -659,6 +897,7 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
659
897
|
actionType: intent.schema,
|
|
660
898
|
domain: intent.schema,
|
|
661
899
|
confidence: intent.expectedReward,
|
|
900
|
+
...( description ? { description } : {} ),
|
|
662
901
|
success,
|
|
663
902
|
outcomeQuality,
|
|
664
903
|
surprise,
|
|
@@ -672,21 +911,67 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
672
911
|
catch( err ){ logger.warn(`[motor] action outcome publish failed: ${ errMsg( err ) }`) }
|
|
673
912
|
}
|
|
674
913
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
914
|
+
/**
|
|
915
|
+
* Announce a dispatched act to its TWO audiences, which live on two buses.
|
|
916
|
+
*
|
|
917
|
+
* The mind's own faculties appraise it — affective.blender, stress.regulator
|
|
918
|
+
* and attention.allocator all subscribe to `agency.invocation` on the
|
|
919
|
+
* CognitiveBus, the internal fabric. But the act is host-owned, and the HOST
|
|
920
|
+
* hears only `simulation.eventBus`: `WillStem` buffers an invocation for
|
|
921
|
+
* delivery from `eventBus.subscribeAll`, and that is the sole path by which an
|
|
922
|
+
* external effector ever reaches a handler.
|
|
923
|
+
*
|
|
924
|
+
* Only the cognitive half was ever published. The two buses have no bridge, so
|
|
925
|
+
* `bufferInvocation` was subscribed to a bus that has never carried the event
|
|
926
|
+
* — measured on a live boot: `agency.invocation.dispatched` incremented,
|
|
927
|
+
* `pendingEffectorInvocations` stayed empty, the intent held `awaiting`, and
|
|
928
|
+
* fifteen ticks later `[motor] ⏱ "inspect" timed out`. **No host-owned
|
|
929
|
+
* effector invocation has ever been delivered.** Communication is unaffected —
|
|
930
|
+
* the outbox is a separate mechanism, which is why a Will could always speak.
|
|
931
|
+
*
|
|
932
|
+
* Both ends were unit-tested and the crossing was not: `policy.*.test.ts` calls
|
|
933
|
+
* `bufferInvocation` directly, which is true about the controller and silent
|
|
934
|
+
* about whether anything reaches it. Same shape as the affordance-field hop.
|
|
935
|
+
*/
|
|
936
|
+
private _emitDispatch(
|
|
937
|
+
intent: Intent, mode: 'communicate' | 'external', tick: Tick,
|
|
938
|
+
state?: ReadonlySimulationState,
|
|
939
|
+
): Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> {
|
|
940
|
+
const payload = this._dispatchPayload( intent, tick, state )
|
|
941
|
+
const type = mode === 'communicate' ? 'agency.communicate' : 'agency.invocation'
|
|
942
|
+
|
|
943
|
+
if( this._bus )
|
|
944
|
+
try {
|
|
945
|
+
this._bus.publish({ type, version: 1, sourceEngine: this.name, salience: 0.6, payload })
|
|
946
|
+
}
|
|
947
|
+
catch( err ){ logger.warn(`[motor] dispatch publish failed: ${ errMsg( err ) }`) }
|
|
948
|
+
|
|
949
|
+
// The observable bus — where the host is listening.
|
|
950
|
+
return [ { type, source: this.name, payload } ]
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private _dispatchPayload(
|
|
954
|
+
intent: Intent, tick: Tick, state?: ReadonlySimulationState,
|
|
955
|
+
): Record<string, unknown> {
|
|
956
|
+
return {
|
|
682
957
|
schema: intent.schema, intentId: intent.id,
|
|
683
958
|
targetEntityId: intent.targetEntityId, parameters: intent.parameters, tick,
|
|
684
959
|
// The ability's declared meaning, carried to the host handler.
|
|
685
960
|
description: this._resolve( intent.schema )?.description,
|
|
686
|
-
|
|
687
|
-
|
|
961
|
+
// Where in the world this referent is, so a host can act on it at all.
|
|
962
|
+
//
|
|
963
|
+
// The target is an ANCHOR (`ke:1sqlkux`) — 0.9.0 made identity opaque and
|
|
964
|
+
// separate from address on purpose. A bridge holds channel ids and knows
|
|
965
|
+
// nothing of anchors, so an invocation naming only the referent is one no
|
|
966
|
+
// surface can serve. The outbox hit this first and solved it the same way
|
|
967
|
+
// (mind.ts attachRouting): resolve inside, where the alias table lives.
|
|
968
|
+
//
|
|
969
|
+
// Outbound only. This is the mind telling the world which of its own
|
|
970
|
+
// handles it means — the reverse direction, a surface writing an address
|
|
971
|
+
// into the mind, is what perception is for.
|
|
972
|
+
...( state && intent.targetEntityId
|
|
973
|
+
? { targetAddresses: addressesOf( state.entities, intent.targetEntityId ) } : {} ),
|
|
688
974
|
}
|
|
689
|
-
catch( err ){ logger.warn(`[motor] dispatch publish failed: ${ errMsg( err ) }`) }
|
|
690
975
|
}
|
|
691
976
|
}
|
|
692
977
|
|
|
@@ -750,3 +1035,53 @@ function clamp01( n: number ): number {
|
|
|
750
1035
|
function errMsg( err: unknown ): string {
|
|
751
1036
|
return err instanceof Error ? err.message : String( err )
|
|
752
1037
|
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Has the situation these words were composed for moved on?
|
|
1041
|
+
*
|
|
1042
|
+
* Returns the mind's own account of what changed, or null if the moment still
|
|
1043
|
+
* stands. The account is what the withheld outcome records, so the mind's
|
|
1044
|
+
* history says why it did not speak rather than merely that it didn't.
|
|
1045
|
+
*
|
|
1046
|
+
* The evidence is `conversation.sent` — the durable record of having spoken,
|
|
1047
|
+
* written by every path that speaks (ProactiveCommunicator, the audition facet,
|
|
1048
|
+
* the outbox). If I have said something to this person SINCE these words were
|
|
1049
|
+
* asked for, then these are not my current words to them: I have already
|
|
1050
|
+
* responded to whatever moved in between, and this is an older turn arriving on
|
|
1051
|
+
* top of a newer one.
|
|
1052
|
+
*
|
|
1053
|
+
* And `answeredAt` — when they last spoke to ME. This arm was missing at first,
|
|
1054
|
+
* excluded on the argument that `conversation.received` is a one-shot entity
|
|
1055
|
+
* swept on the tick it is scanned so nothing durable records the other
|
|
1056
|
+
* direction. That was wrong: `answeredAt` is the durable half, folded onto the
|
|
1057
|
+
* mind's own sent turns, and it is what renders "they answered" in the prompt.
|
|
1058
|
+
*
|
|
1059
|
+
* The gap was the COMMON case, not an edge. Live, a COO delivered a pre-composed
|
|
1060
|
+
* agenda message ("I need a list of what's actively in flight…") two seconds
|
|
1061
|
+
* after the person changed the subject, having said nothing in between — so the
|
|
1062
|
+
* stale words arrived BEFORE her real reply and the spoken-since arm had not
|
|
1063
|
+
* fired yet. The two signals were assumed to coincide and do not: whoever moved
|
|
1064
|
+
* the situation, it moved.
|
|
1065
|
+
*
|
|
1066
|
+
* A missing `composedAt` means the words were never requested through
|
|
1067
|
+
* `_requestAuthoring` — nothing is known about when they were formed, so nothing
|
|
1068
|
+
* is claimed and they go out. Silence beats a guess here: withholding on absent
|
|
1069
|
+
* evidence would mute a mind for reasons it could not name.
|
|
1070
|
+
*/
|
|
1071
|
+
export function situationMoved(
|
|
1072
|
+
state: ReadonlySimulationState,
|
|
1073
|
+
targetEntityId: string | undefined,
|
|
1074
|
+
composedAt: Tick | undefined,
|
|
1075
|
+
): string | null {
|
|
1076
|
+
if( composedAt === undefined || !targetEntityId ) return null
|
|
1077
|
+
|
|
1078
|
+
const heard = lastAnsweredByEntity( state.entities as never ).get( targetEntityId )
|
|
1079
|
+
if( heard !== undefined && heard > composedAt )
|
|
1080
|
+
return 'They said something after I composed this, so it is an answer to a moment that has passed.'
|
|
1081
|
+
|
|
1082
|
+
const spoke = spokenAtByEntity( state.entities as never ).get( targetEntityId )
|
|
1083
|
+
if( spoke !== undefined && spoke > composedAt )
|
|
1084
|
+
return 'I had already spoken to them since composing this, so it was no longer what I had to say.'
|
|
1085
|
+
|
|
1086
|
+
return null
|
|
1087
|
+
}
|