@mindot/will 0.8.0 → 0.9.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/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +3174 -867
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3236 -1042
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-cS6k4uiJ.d.ts → will-DbDj_TEH.d.ts} +752 -17
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +83 -2
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +117 -0
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- 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 -0
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +552 -131
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -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 +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +167 -19
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- 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 +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -2
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +58 -5
- package/src/core/orchestrator.ts +38 -0
- package/src/llm/index.ts +25 -8
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/sdk/will.ts +39 -6
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +79 -2
- package/src/stem/mind.ts +172 -55
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -71,6 +71,11 @@ interface Intent {
|
|
|
71
71
|
*/
|
|
72
72
|
planId?: string
|
|
73
73
|
planStepId?: string
|
|
74
|
+
/**
|
|
75
|
+
* The entity that evoked this — for an ideomotor winner, the `ideomotor.intent`
|
|
76
|
+
* the executive wrote. Read so enaction can retire it (see _dischargeWill).
|
|
77
|
+
*/
|
|
78
|
+
evokedBy?: string
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
/** Authors the words for a self-initiated communicate the agency selected (no inbound triggered it). */
|
|
@@ -88,6 +93,22 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
88
93
|
private _grants: AccessGrants | null = null
|
|
89
94
|
private _bus: CognitiveBus | null = null
|
|
90
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Two-phase outreach authoring. A facet cannot be awaited from inside a tick:
|
|
98
|
+
* `ExecutiveFacet.report()` only QUEUES in tick-discipline mode and the reasoning
|
|
99
|
+
* launches from `pump()`, which the ExecutiveEngine calls once per tick — so an
|
|
100
|
+
* in-tick `await` blocks the very loop that would produce the answer. Observed
|
|
101
|
+
* live: a 61s freeze of the whole mind inside one tick, then an empty result.
|
|
102
|
+
* (It passes unit tests because bare facets have no inbox and author inline.)
|
|
103
|
+
*
|
|
104
|
+
* So `_deliver` REQUESTS words and returns false (the intent holds 'awaiting'),
|
|
105
|
+
* the facet answers off-tick, and a later tick delivers. Process-local by design:
|
|
106
|
+
* an authoring call in flight cannot survive a restart, and the intent would
|
|
107
|
+
* simply re-request.
|
|
108
|
+
*/
|
|
109
|
+
private _authoring = new Set<string>()
|
|
110
|
+
private _authored = new Map<string, string[]>()
|
|
111
|
+
|
|
91
112
|
constructor( schemas: MotorSchema[] = INNATE_SCHEMAS ){
|
|
92
113
|
for( const s of schemas ) this._schemas.set( s.id, s )
|
|
93
114
|
}
|
|
@@ -173,18 +194,76 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
173
194
|
else if( parentId && revoked.has( parentId ) ) del.push( id )
|
|
174
195
|
}
|
|
175
196
|
|
|
197
|
+
// ── Words that landed off-tick → deliver now ─────────────────
|
|
198
|
+
// Second half of two-phase authoring: a previous tick asked a facet for the words
|
|
199
|
+
// and held the intent 'awaiting'; the facet answered between ticks. Runs BEFORE
|
|
200
|
+
// the timeout sweep so words arriving on the same tick the clock would expire are
|
|
201
|
+
// still spoken rather than discarded.
|
|
202
|
+
const spokeThisTick = new Set<string>()
|
|
203
|
+
for( const [ id, e ] of state.entities ){
|
|
204
|
+
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
205
|
+
if( !this._authored.has( id ) ) continue
|
|
206
|
+
|
|
207
|
+
const intent = readIntent( id, e.metadata )
|
|
208
|
+
const predicted: EfferenceCopy = {
|
|
209
|
+
expectedReward: num( e.metadata?.['predictedReward'], intent.expectedReward ),
|
|
210
|
+
expectedValence: num( e.metadata?.['predictedValence'], intent.expectedValence ),
|
|
211
|
+
}
|
|
212
|
+
if( await this._deliver( id, intent, predicted, state, tick, set, del, metrics ) )
|
|
213
|
+
spokeThisTick.add( id )
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Words whose intent no longer exists (revoked, timed out, superseded) are words
|
|
217
|
+
// that will never be said — drop them so the map cannot grow without bound.
|
|
218
|
+
for( const id of this._authored.keys() )
|
|
219
|
+
if( !state.entities.has( id ) ) this._authored.delete( id )
|
|
220
|
+
|
|
176
221
|
// ── Timeout stranded async intents ───────────────────────────
|
|
177
222
|
// An 'awaiting' intent whose host/delivery never returned would block the
|
|
178
223
|
// serial Will forever. After AWAIT_TIMEOUT ticks, abandon it as a failed
|
|
179
224
|
// outcome (which also teaches reafference the action is unreliable here).
|
|
180
225
|
for( const [ id, e ] of state.entities ){
|
|
181
226
|
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
227
|
+
// Already delivered above — its outcome and deletion are queued for this tick.
|
|
228
|
+
if( spokeThisTick.has( id ) ) continue
|
|
229
|
+
// Words are being authored right now. The clock PAUSES rather than extends: a
|
|
230
|
+
// facet LLM call is 10–30s of latency we do not control, and any fixed budget
|
|
231
|
+
// large enough to cover a slow one is also large enough to strand a dead one.
|
|
232
|
+
// While a call is genuinely in flight the world has not failed to answer. The
|
|
233
|
+
// pause is bounded, not open-ended: `authorOutreach` always settles (its own 60s
|
|
234
|
+
// timeout resolves empty), so `_authoring` always clears and the clock resumes.
|
|
235
|
+
if( this._authoring.has( id ) ) continue
|
|
182
236
|
// POLICY_REAFFERENCE P4 — an escalated intent is HELD: the stem owns its
|
|
183
237
|
// lifecycle (extended TTL → approve/deny/expire), so the executor must not
|
|
184
238
|
// time it out at AWAIT_TIMEOUT and reconcile it as a phantom failure.
|
|
185
239
|
if( e.metadata?.['escalated'] === true ) continue
|
|
186
240
|
const dispatchedAt = num( e.metadata?.['dispatchedAt'], tick )
|
|
187
|
-
|
|
241
|
+
const age = tick - dispatchedAt
|
|
242
|
+
|
|
243
|
+
// Dispatched LATER than now ⇒ it was in flight when the mind went to sleep.
|
|
244
|
+
// Intents snapshot with the state and the tick counter restarts at 1 on wake,
|
|
245
|
+
// so a restored `awaiting` intent has a NEGATIVE age — and every guard here
|
|
246
|
+
// inverts on it: `age < AWAIT_TIMEOUT` is trivially true for -589, so it never
|
|
247
|
+
// timed out, and the selector's staleness went negative, INFLATING the
|
|
248
|
+
// incumbent's strength instead of decaying it. Measured on a live Will:
|
|
249
|
+
// incumbent 9.742 against challengers of 0.52, unpreemptable and immortal.
|
|
250
|
+
// One person's stale intent held the channel and every attempt to contact
|
|
251
|
+
// anyone else was refused, indefinitely, across restarts.
|
|
252
|
+
//
|
|
253
|
+
// Cleared WITHOUT a failure outcome: hibernating is not the world declining
|
|
254
|
+
// to answer. Reconciling it as a timeout would teach reafference that
|
|
255
|
+
// reaching that person does not work, which is a lesson about the process
|
|
256
|
+
// lifecycle, not about them.
|
|
257
|
+
if( age < 0 ){
|
|
258
|
+
del.push( id )
|
|
259
|
+
logger.info(
|
|
260
|
+
`[motor] cleared "${ str( e.metadata?.['schema'] ) ?? 'intent' }" left awaiting ` +
|
|
261
|
+
`across a restart (dispatched at tick ${ dispatchedAt }, now ${ tick })`
|
|
262
|
+
)
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if( age < AWAIT_TIMEOUT ) continue
|
|
188
267
|
|
|
189
268
|
const intent = readIntent( id, e.metadata )
|
|
190
269
|
const predicted: EfferenceCopy = {
|
|
@@ -437,17 +516,16 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
437
516
|
return true
|
|
438
517
|
}
|
|
439
518
|
|
|
440
|
-
// Content authored upstream when present (host / host-facet)
|
|
441
|
-
//
|
|
442
|
-
//
|
|
519
|
+
// Content authored upstream when present (host / host-facet), else words a facet
|
|
520
|
+
// authored off-tick and landed since a previous tick asked for them. Neither ⇒
|
|
521
|
+
// request authoring (never awaited here — see `_authoring`) and hold 'awaiting'.
|
|
443
522
|
const authored = str( intent.parameters['content'] ) ?? firstMessage( intent.parameters['messages'] )
|
|
444
|
-
let bubbles: string[] = authored ? [ authored ] : []
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
523
|
+
let bubbles: string[] = authored ? [ authored ] : ( this._authored.get( id ) ?? [] )
|
|
524
|
+
this._authored.delete( id )
|
|
525
|
+
if( bubbles.length === 0 ){
|
|
526
|
+
this._requestAuthoring( id, intent )
|
|
527
|
+
return false // nothing to send yet → await
|
|
449
528
|
}
|
|
450
|
-
if( bubbles.length === 0 ) return false // nothing authored to send → await
|
|
451
529
|
|
|
452
530
|
const request: ActionRequest = {
|
|
453
531
|
effector,
|
|
@@ -477,6 +555,16 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
477
555
|
text: bubbles.join('\n'),
|
|
478
556
|
expiresAt: tick + CONSEQUENCE_TTL_TICKS, tick,
|
|
479
557
|
}) )
|
|
558
|
+
// The words are out. Whatever willed them is DONE being an intention.
|
|
559
|
+
//
|
|
560
|
+
// Discharged here rather than at dispatch on purpose: `_deliver` holds the
|
|
561
|
+
// intent 'awaiting' while a facet authors, and that authoring can time out or
|
|
562
|
+
// return nothing. Clearing the will at dispatch would lose the intention
|
|
563
|
+
// silently — the mind would have decided to say something and simply never
|
|
564
|
+
// have, which is the failure `commands.ts` calls "the whole intention
|
|
565
|
+
// evaporated without a trace". Enaction is the discharge; a request is not.
|
|
566
|
+
if( result.success ) this._dischargeWill( intent, del )
|
|
567
|
+
|
|
480
568
|
this._emitEnacted( intent, out, predicted, tick )
|
|
481
569
|
this._emitActionOutcome( intent, result.success, result.feedback.outcomeQuality,
|
|
482
570
|
clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick )
|
|
@@ -484,6 +572,27 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
484
572
|
return true
|
|
485
573
|
}
|
|
486
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Ask a facet for the words, off-tick. Fire-and-forget on purpose: awaiting this
|
|
577
|
+
* from inside `react()` deadlocks the tick loop against the facet pump. Idempotent
|
|
578
|
+
* per intent — a request already in flight is not duplicated, so the intent may sit
|
|
579
|
+
* 'awaiting' across many ticks with exactly one LLM call behind it.
|
|
580
|
+
*/
|
|
581
|
+
private _requestAuthoring( id: string, intent: Intent ): void {
|
|
582
|
+
if( !this._author || this._authoring.has( id ) ) return
|
|
583
|
+
|
|
584
|
+
const name = str( intent.parameters['targetEntityName'] ) ?? intent.targetEntityId ?? 'them'
|
|
585
|
+
this._authoring.add( id )
|
|
586
|
+
void this._author
|
|
587
|
+
.authorOutreach( intent.targetEntityId ?? '', name, str( intent.parameters['gist'] ) )
|
|
588
|
+
.then( bubbles => {
|
|
589
|
+
if( bubbles.length > 0 ) this._authored.set( id, bubbles )
|
|
590
|
+
else logger.warn(`[motor] outreach authoring returned nothing for "${ intent.schema }"`)
|
|
591
|
+
} )
|
|
592
|
+
.catch( err => { logger.warn(`[motor] outreach authoring failed: ${ errMsg( err ) }`) } )
|
|
593
|
+
.finally( () => { this._authoring.delete( id ) } )
|
|
594
|
+
}
|
|
595
|
+
|
|
487
596
|
// ── bus emission ─────────────────────────────────────────────
|
|
488
597
|
|
|
489
598
|
private _emitEnacted( intent: Intent, enaction: Enaction, predicted: EfferenceCopy, tick: Tick ): void {
|
|
@@ -506,6 +615,38 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
506
615
|
* RewardEvaluator reads it as a reward signal. `confidence` carries the agency's
|
|
507
616
|
* own forward-model prior so calibration has a real prediction to score.
|
|
508
617
|
*/
|
|
618
|
+
/**
|
|
619
|
+
* Retire the `ideomotor.intent` that produced this act.
|
|
620
|
+
*
|
|
621
|
+
* Nothing deleted these. They were cleared only when the executive next ran and
|
|
622
|
+
* declined to name the same action again — and the executive runs on an interval,
|
|
623
|
+
* so between cycles a willed reach-out STOOD in state, was rebuilt into an
|
|
624
|
+
* affordance every single tick, and competed every single tick. Observed as
|
|
625
|
+
* dozens of identical lines:
|
|
626
|
+
*
|
|
627
|
+
* [selector] willed reach-out → … NOT selected: 0.297 < inspect… 0.340
|
|
628
|
+
*
|
|
629
|
+
* losing by four thousandths, over and over, until it won — twice. Fabrice got
|
|
630
|
+
* the same message byte-for-byte 25 ticks apart, two outbox ids.
|
|
631
|
+
*
|
|
632
|
+
* `justEnacted` was built to hold this line and cannot: it is a DECAYING
|
|
633
|
+
* quantity, capped at `repeatDamping` (0.30), and a standing intent outlasts it
|
|
634
|
+
* by construction. Damping a permanent pull only ever delays it. So the intention
|
|
635
|
+
* is discharged by being acted on, which is what an intention is — you meant to
|
|
636
|
+
* tell someone something, you told them, and it is finished. If the mind still
|
|
637
|
+
* wants to say more, the next executive cycle forms a new one, now seeing "I said
|
|
638
|
+
* this 25 ticks ago and have had no answer" in front of it.
|
|
639
|
+
*
|
|
640
|
+
* Satiation stays exactly as it was, and still earns its keep: it damps saying
|
|
641
|
+
* the same thing again for reasons that did NOT come from a standing will.
|
|
642
|
+
*/
|
|
643
|
+
private _dischargeWill( intent: Intent, del: string[] ): void {
|
|
644
|
+
const willId = intent.evokedBy
|
|
645
|
+
if( !willId || !willId.startsWith('ideomotor-') ) return
|
|
646
|
+
del.push( willId )
|
|
647
|
+
logger.info(`[motor] discharged the will behind "${ intent.schema }" (${ willId })`)
|
|
648
|
+
}
|
|
649
|
+
|
|
509
650
|
private _emitActionOutcome(
|
|
510
651
|
intent: Intent, success: boolean, outcomeQuality: number, surprise: number, tick: Tick,
|
|
511
652
|
): void {
|
|
@@ -588,6 +729,7 @@ function readIntent( id: string, m: ReadonlyMap<string, unknown> | Record<string
|
|
|
588
729
|
stepIndex: typeof meta['stepIndex'] === 'number' ? ( meta['stepIndex'] as number ) : undefined,
|
|
589
730
|
planId: str( meta['planId'] ),
|
|
590
731
|
planStepId: str( meta['stepId'] ),
|
|
732
|
+
evokedBy: str( meta['evokedBy'] ),
|
|
591
733
|
}
|
|
592
734
|
}
|
|
593
735
|
|
|
@@ -31,6 +31,10 @@ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
|
31
31
|
import { schemaEntityId, availabilityEntityId } from '#agency/schemas/repertoire'
|
|
32
32
|
import { asFinality } from '#stem/policy/arbiter'
|
|
33
33
|
import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
|
|
34
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
35
|
+
import {
|
|
36
|
+
SENT_TYPE, DEFAULT_REPLY_WINDOW_TICKS, resolveReplyExpectations,
|
|
37
|
+
} from '#agency/conversation.aim'
|
|
34
38
|
|
|
35
39
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
36
40
|
|
|
@@ -81,6 +85,11 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
81
85
|
// action.outcome the PlanningEngine advances on. (The executor is the emitter
|
|
82
86
|
// for sync/timeout outcomes; this is its async counterpart — one emitter each.)
|
|
83
87
|
{ type: 'action.outcome', version: 1, validate: () => null },
|
|
88
|
+
// Whether a person answers the mind when it speaks to them. Distinct from
|
|
89
|
+
// `interaction.occurred`, which reports something someone DID — a silence is
|
|
90
|
+
// not an act and so can never appear there, which is why "they never answer
|
|
91
|
+
// me" was unlearnable despite `socialStanding` being built to carry it.
|
|
92
|
+
{ type: 'social.responsiveness', version: 1, validate: () => null },
|
|
84
93
|
]
|
|
85
94
|
}
|
|
86
95
|
/** Creation seam: register a composite proposed by the executive/deliberation facet. */
|
|
@@ -281,6 +290,19 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
281
290
|
// Availability entries (P2) mirror the same way — empty until a refusal lands.
|
|
282
291
|
for( const e of this._repertoire.availabilityEntities() ) set.push( e )
|
|
283
292
|
|
|
293
|
+
// ── 3.5 Did the words achieve what they were for? ────────────
|
|
294
|
+
// The motor loop above graded whether each act EXECUTED. For a communicative
|
|
295
|
+
// act that is the smaller half of the question: the outbox accepting a message
|
|
296
|
+
// is not the world answering it. Both were the same fact until now, which is
|
|
297
|
+
// how `reach-out` came to sit at 28 enactments / 28 successes while the person
|
|
298
|
+
// it kept reaching had said nothing back.
|
|
299
|
+
//
|
|
300
|
+
// Folded here rather than in a new engine because this is the same question
|
|
301
|
+
// this engine already exists to ask, one layer out — reafference over a social
|
|
302
|
+
// act instead of a motor one. The resolution itself is pure (conversation.aim);
|
|
303
|
+
// all that happens here is writing it down and saying it out loud.
|
|
304
|
+
const replied = this._resolveReplies( tick, state, set )
|
|
305
|
+
|
|
284
306
|
// ── 4. Telemetry ─────────────────────────────────────────────
|
|
285
307
|
const skills = this._repertoire.skills()
|
|
286
308
|
const habitual = [ ...skills.values() ].filter( s => s.habitStrength >= PROC_THRESHOLD ).length
|
|
@@ -291,6 +313,10 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
291
313
|
[ 'agency.habitual.count', habitual ],
|
|
292
314
|
[ 'agency.sensory.confirmed', sensory ],
|
|
293
315
|
)
|
|
316
|
+
// Quiet path stays byte-identical: a Will that has spoken to nobody, or whose
|
|
317
|
+
// every turn was answered, writes nothing here (cf. EXAFFERENCE P3).
|
|
318
|
+
if( replied.answered > 0 ) metrics.push([ 'social.answered.count', replied.answered ])
|
|
319
|
+
if( replied.unanswered > 0 ) metrics.push([ 'social.unanswered.count', replied.unanswered ])
|
|
294
320
|
// Only emit the refusal metric when it fired — a never-refused Will writes
|
|
295
321
|
// nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
|
|
296
322
|
if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
|
|
@@ -298,6 +324,97 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
298
324
|
return { commands: { set, delete: del, metrics } }
|
|
299
325
|
}
|
|
300
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Latch each open turn's fate onto its own record and announce it once.
|
|
329
|
+
*
|
|
330
|
+
* Two rules earn their keep here:
|
|
331
|
+
*
|
|
332
|
+
* • MERGE, never replace. `StateManager.setEntity` overwrites the whole entity,
|
|
333
|
+
* and a `conversation.sent` carries `outboxMessageIds` — the sole key by which
|
|
334
|
+
* a later delivery ack can find it. Rewriting the record with only the fields
|
|
335
|
+
* this method cares about would sever that, silently, for every turn that got
|
|
336
|
+
* an answer.
|
|
337
|
+
*
|
|
338
|
+
* • Latch, don't recompute. `answeredAt`/`unansweredAt` persist, so the event
|
|
339
|
+
* fires on the one tick the fact changed rather than every tick for the rest
|
|
340
|
+
* of the session — which for an unanswered turn would be thousands of
|
|
341
|
+
* identical reputation hits against one person for one silence.
|
|
342
|
+
*/
|
|
343
|
+
private _resolveReplies(
|
|
344
|
+
tick: Tick,
|
|
345
|
+
state: ReadonlySimulationState,
|
|
346
|
+
set: EntityInput[],
|
|
347
|
+
): { answered: number; unanswered: number } {
|
|
348
|
+
// A trait, not a constant: how long a silence takes to mean something differs
|
|
349
|
+
// between minds, so it tunes through the persona prior like every other
|
|
350
|
+
// developable parameter. Absent ⇒ the default ⇒ the pre-seam behaviour.
|
|
351
|
+
//
|
|
352
|
+
// Read from the SELECTOR's config despite being applied here, because it and
|
|
353
|
+
// `repeatWindowTicks` are two readings of one disposition — how long this mind
|
|
354
|
+
// sits with something it has said. Held apart, a mind could tune itself into
|
|
355
|
+
// saying a thing again while still calling the silence too fresh to count.
|
|
356
|
+
const window = Math.max(
|
|
357
|
+
1,
|
|
358
|
+
Math.round(
|
|
359
|
+
readEffectiveParams( state, 'engine-config-action-selector').replyWindowTicks
|
|
360
|
+
?? DEFAULT_REPLY_WINDOW_TICKS
|
|
361
|
+
),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const { answered, unanswered } = resolveReplyExpectations( state.entities, tick, window )
|
|
365
|
+
if( answered.length === 0 && unanswered.length === 0 ) return { answered: 0, unanswered: 0 }
|
|
366
|
+
|
|
367
|
+
const merge = ( id: string, patch: Record<string, unknown> ): void => {
|
|
368
|
+
const existing = state.entities.get( id )
|
|
369
|
+
if( !existing ) return
|
|
370
|
+
set.push({
|
|
371
|
+
id, type: SENT_TYPE,
|
|
372
|
+
metadata: { ...( existing.metadata as Record<string, unknown> ?? {} ), ...patch },
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for( const { turn, at, with: said } of answered ){
|
|
377
|
+
// Their words, not merely the fact of them — see SpokenTurn.answeredWith.
|
|
378
|
+
merge( turn.entityId, { answeredAt: at, ...( said ? { answeredWith: said } : {} ) } )
|
|
379
|
+
this._emitResponsiveness( turn.targetEntityId, true, at - turn.tick, tick )
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for( const turn of unanswered ){
|
|
383
|
+
merge( turn.entityId, { unansweredAt: tick } )
|
|
384
|
+
this._emitResponsiveness( turn.targetEntityId, false, tick - turn.tick, tick )
|
|
385
|
+
logger.info(
|
|
386
|
+
`[reafference] no answer from ${ turn.targetEntityName ?? turn.targetEntityId } ` +
|
|
387
|
+
`after ${ tick - turn.tick } ticks — "${ turn.preview.slice( 0, 60 ) }"`
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { answered: answered.length, unanswered: unanswered.length }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Publish how a person responded to being spoken to.
|
|
396
|
+
*
|
|
397
|
+
* Deliberately NOT an `interaction.occurred`: that event means "someone did
|
|
398
|
+
* something toward us" and carries a valence for what they did. A silence is
|
|
399
|
+
* nobody doing anything, and forcing it through that channel would have the
|
|
400
|
+
* ReputationTracker book a hostile *act* where there was only an absence — the
|
|
401
|
+
* mind would come to think it was being rebuffed rather than simply not
|
|
402
|
+
* answered yet. Separate signal, separate meaning, one consumer decides what
|
|
403
|
+
* either is worth.
|
|
404
|
+
*/
|
|
405
|
+
private _emitResponsiveness( keid: string, answered: boolean, waitedTicks: number, tick: Tick ): void {
|
|
406
|
+
if( !this._bus ) return
|
|
407
|
+
try {
|
|
408
|
+
this._bus.publish({
|
|
409
|
+
type: 'social.responsiveness', version: 1, sourceEngine: this.name,
|
|
410
|
+
// An answer is ordinary; being ignored is the one worth interrupting for.
|
|
411
|
+
salience: answered ? 0.3 : 0.55,
|
|
412
|
+
payload: { keid, answered, waitedTicks, tick },
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
catch( err ){ logger.warn(`[reafference] responsiveness publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
416
|
+
}
|
|
417
|
+
|
|
301
418
|
private _emitProceduralized( skill: LearnedSkill, tick: Tick ): void {
|
|
302
419
|
if( !this._bus ) return
|
|
303
420
|
try {
|
|
@@ -190,7 +190,17 @@ export class ProactiveCommunicator {
|
|
|
190
190
|
// Generate outbox message IDs first so we can embed them in conversation.sent
|
|
191
191
|
// and correlate deliveries back to this intent (TODO 11.3).
|
|
192
192
|
const originalMessage = ( request.parameters?.originalMessage as string ) ?? ''
|
|
193
|
-
|
|
193
|
+
// `decidedAt` is the enacting tick and is always set by the caller; the
|
|
194
|
+
// `parameters.tick` fallback is for hosts that build a request by hand.
|
|
195
|
+
//
|
|
196
|
+
// This defaulted to 0 for every agency-path delivery, because MotorSchemaExecutor
|
|
197
|
+
// builds `parameters` from the intent and never put a tick in it. Two
|
|
198
|
+
// consequences, both silent: every `conversation.sent` was stamped tick 0, so
|
|
199
|
+
// anything comparing against it (satiation, undertaking discharge) compared
|
|
200
|
+
// against zero and never fired; and the id `conv-sent-<target>-0` was CONSTANT,
|
|
201
|
+
// so each send to a person overwrote the record of the last one — the mind kept
|
|
202
|
+
// exactly one memory of having spoken to each person, forever.
|
|
203
|
+
const deliveryTick = ( request.parameters?.tick as number ) ?? request.decidedAt ?? 0
|
|
194
204
|
const replyToMessageId = ( request.parameters?.replyToMessageId as string ) ?? undefined
|
|
195
205
|
const isAck = ( request.parameters?.isAck as boolean ) ?? false
|
|
196
206
|
|
|
@@ -264,9 +274,15 @@ export class ProactiveCommunicator {
|
|
|
264
274
|
description: `I reach out to ${targetEntityName}: "${fullReply.slice( 0, 80 )}${fullReply.length > 80 ? '…' : ''}"`,
|
|
265
275
|
commands,
|
|
266
276
|
feedback: {
|
|
267
|
-
|
|
277
|
+
// NOT a success yet. This reports the TRANSPORT, and the act's point is to be
|
|
278
|
+
// answered — an outcome not yet known at this moment. Scoring delivery as 0.85
|
|
279
|
+
// taught the mind that reaching out works every time, including the times it
|
|
280
|
+
// was ignored, which is why it re-asked the same question 19 times in two
|
|
281
|
+
// minutes. Neutral here; the unanswered/answered signal in outreach.silence.ts
|
|
282
|
+
// is what actually moves the pull toward this person.
|
|
283
|
+
outcomeQuality: 0.5,
|
|
268
284
|
surprise: 0.15,
|
|
269
|
-
lessons: [ `My
|
|
285
|
+
lessons: [ `My words are on their way to ${targetEntityName}. Whether they land is not yet known.` ],
|
|
270
286
|
},
|
|
271
287
|
}
|
|
272
288
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/agency/restart.ts — the restart boundary
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Two different things happen when a Will wakes from a snapshot, and conflating
|
|
6
|
+
// them is what made this a recurring class of bug rather than a single one:
|
|
7
|
+
//
|
|
8
|
+
// 1. TIME must not go backwards. Entities come back stamped with the tick they
|
|
9
|
+
// were written at, so the clock has to resume from the snapshot's tick
|
|
10
|
+
// rather than restart at 0 (WillStem.createWill does this via
|
|
11
|
+
// `clock.setTick`). Otherwise every `tick - stampedTick` in the codebase —
|
|
12
|
+
// 42 comparison sites across 19 files — computes a NEGATIVE age, and the
|
|
13
|
+
// guards built on them inverted spectacularly: an awaiting intent whose age
|
|
14
|
+
// read -589 could never time out, and the selector's staleness decay
|
|
15
|
+
// `1 - staleness × STALE_DECAY` became `1 + 19.6`, AMPLIFYING an incumbent
|
|
16
|
+
// from 0.47 to 9.74 and making it permanently unpreemptable.
|
|
17
|
+
//
|
|
18
|
+
// 2. Work that was IN FLIGHT does not resume. Resuming the clock fixes the
|
|
19
|
+
// arithmetic but says nothing about the semantics: an action dispatched
|
|
20
|
+
// moments before hibernation is not still dispatched hours later, and the
|
|
21
|
+
// window in which the world might have echoed our own words has closed.
|
|
22
|
+
// Leaving these behind is worse under a resumed clock than under a reset
|
|
23
|
+
// one, because they now look plausibly RECENT — an awaiting intent would be
|
|
24
|
+
// reconciled as a genuine timeout, teaching the mind that reaching that
|
|
25
|
+
// person does not work, when all that happened is that it slept.
|
|
26
|
+
//
|
|
27
|
+
// This module owns (2): what the agency considers in-flight, and therefore what
|
|
28
|
+
// must not cross the boundary. It is deliberately a short, explicit list rather
|
|
29
|
+
// than a heuristic — anything the agency stamps with a tick and expects to
|
|
30
|
+
// resolve within a few of them belongs here.
|
|
31
|
+
// ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
import { CONSEQUENCE_TYPE } from '#agency/consequence'
|
|
34
|
+
|
|
35
|
+
/** Entity type of a committed action awaiting the world's answer. */
|
|
36
|
+
const INTENT_TYPE = 'agency.intent'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ids of the restored entities that represent work in flight when the mind went
|
|
40
|
+
* to sleep. Pure — the caller drops them.
|
|
41
|
+
*
|
|
42
|
+
* • `agency.intent` with status 'awaiting' — dispatched, never answered. It is
|
|
43
|
+
* ABANDONED, not failed: sleeping is not the world declining to answer, and
|
|
44
|
+
* recording a failure here would teach reafference a lesson about the process
|
|
45
|
+
* lifecycle rather than about the action.
|
|
46
|
+
* • `agency.consequence` — the expected sensory footprint of an act, used both
|
|
47
|
+
* to recognise our own echo (P2) and to damp repeating ourselves (P5). Its
|
|
48
|
+
* TTL is a handful of ticks; across a sleep the echo will never arrive, and a
|
|
49
|
+
* stale one both mis-attributes genuine replies as self-caused and suppresses
|
|
50
|
+
* contact that should now be free to happen.
|
|
51
|
+
*
|
|
52
|
+
* A `selected` intent is deliberately NOT cleared: it has not been dispatched, so
|
|
53
|
+
* it is still an intention the mind holds rather than an action in flight.
|
|
54
|
+
*/
|
|
55
|
+
export function inFlightOnRestore(
|
|
56
|
+
entities: ReadonlyMap<string, { type: string; metadata?: Record<string, unknown> }>,
|
|
57
|
+
): string[] {
|
|
58
|
+
const drop: string[] = []
|
|
59
|
+
|
|
60
|
+
for( const [ id, e ] of entities ){
|
|
61
|
+
if( e.type === CONSEQUENCE_TYPE ){ drop.push( id ); continue }
|
|
62
|
+
if( e.type === INTENT_TYPE && e.metadata?.['status'] === 'awaiting') drop.push( id )
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return drop
|
|
66
|
+
}
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
// + driveUrgency
|
|
14
14
|
// + habitStrength·W (additive habit bonus)
|
|
15
15
|
// + planBias·W (top-down prior)
|
|
16
|
+
// + willBias·W (deliberate volition)
|
|
17
|
+
// + socialPrior·W (learned read on WHO)
|
|
16
18
|
// − cost
|
|
17
19
|
// − inhibition
|
|
18
20
|
// − risk
|
|
@@ -47,9 +49,34 @@ export interface ScoreWeights {
|
|
|
47
49
|
habit: number
|
|
48
50
|
/** Top-down planning prior — how strongly an executing plan biases the competition. */
|
|
49
51
|
plan: number
|
|
52
|
+
/**
|
|
53
|
+
* Volitional prior — how strongly a DELIBERATE executive decision biases the
|
|
54
|
+
* competition. Peer of `plan`: a plan's frontier is a standing prior, a willed act
|
|
55
|
+
* is a prior formed this cycle. Strong enough that no habit (≤1) can out-weigh a
|
|
56
|
+
* confident decision, weak enough that inhibition, threat, or a genuinely more
|
|
57
|
+
* pressing affordance still can — willing is not a rubber stamp.
|
|
58
|
+
*/
|
|
59
|
+
will: number
|
|
60
|
+
/**
|
|
61
|
+
* How strongly the mind's LEARNED read on an addressee biases acting toward them.
|
|
62
|
+
* Peer of `goal` in magnitude: who someone has shown themselves to be should weigh
|
|
63
|
+
* about as much as a goal pointed at them. Signed — a person the mind has come to
|
|
64
|
+
* trust pulls it toward contact, one who never answers pulls it away — and it
|
|
65
|
+
* reaches zero for anyone unknown, so a mind that knows no one scores as before.
|
|
66
|
+
*/
|
|
67
|
+
social: number
|
|
50
68
|
cost: number
|
|
51
69
|
inhib: number
|
|
52
70
|
risk: number
|
|
71
|
+
/**
|
|
72
|
+
* How strongly an act's own live footprint damps doing it again (EXAFFERENCE
|
|
73
|
+
* P5). Satiation, not a lock: it decays with the descriptor, so the pull
|
|
74
|
+
* returns once the window in which the world could have answered has passed.
|
|
75
|
+
* Peer of `will` in magnitude — a freshly-delivered message should roughly
|
|
76
|
+
* cancel the volitional prior that produced it, and no more, so a genuinely
|
|
77
|
+
* pressing reason to speak again still wins.
|
|
78
|
+
*/
|
|
79
|
+
repeat: number
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
export const DEFAULT_WEIGHTS: ScoreWeights = {
|
|
@@ -59,9 +86,12 @@ export const DEFAULT_WEIGHTS: ScoreWeights = {
|
|
|
59
86
|
drive: 0.25,
|
|
60
87
|
habit: 0.20,
|
|
61
88
|
plan: 0.30,
|
|
89
|
+
will: 0.30,
|
|
90
|
+
social: 0.30,
|
|
62
91
|
cost: 0.20,
|
|
63
92
|
inhib: 0.30,
|
|
64
93
|
risk: 0.20,
|
|
94
|
+
repeat: 0.30,
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
/**
|
|
@@ -154,9 +184,12 @@ export function scoreAffordance(
|
|
|
154
184
|
+ w.drive * driveUrgency( a, bias )
|
|
155
185
|
+ w.habit * a.habitStrength
|
|
156
186
|
+ w.plan * ( a.planBias ?? 0 )
|
|
187
|
+
+ w.will * ( a.willBias ?? 0 )
|
|
188
|
+
+ w.social * ( a.socialPrior ?? 0 )
|
|
157
189
|
- w.cost * a.cost
|
|
158
190
|
- w.inhib * bias.inhibition
|
|
159
191
|
- w.risk * risk( a, bias )
|
|
192
|
+
- w.repeat * ( a.justEnacted ?? 0 )
|
|
160
193
|
)
|
|
161
194
|
// POLICY_REAFFERENCE P2 — policy availability damps a POSITIVE activation only,
|
|
162
195
|
// never flipping its sign: a refused ability competes weakly (so it is rarely
|
|
@@ -140,6 +140,29 @@ export interface Affordance {
|
|
|
140
140
|
* competition WITHOUT bypassing it (the plan biases; the field still decides).
|
|
141
141
|
*/
|
|
142
142
|
planBias?: number
|
|
143
|
+
/**
|
|
144
|
+
* Top-down volitional bias 0..1 — set when the executive DELIBERATELY willed this
|
|
145
|
+
* affordance (source 'ideomotor'), carrying the confidence it decided with. Like
|
|
146
|
+
* planBias it lifts activation without bypassing the competition: a willed act must
|
|
147
|
+
* still out-compete the field, but it no longer arrives with the same standing as an
|
|
148
|
+
* ambient possibility. Without this term the ideomotor leg created candidates the
|
|
149
|
+
* arithmetic could not tell apart from idle ones, and a cheap unconditional reflex
|
|
150
|
+
* ('express', cost 0.02) beat a deliberate 'reach-out' 176 times out of 176.
|
|
151
|
+
*/
|
|
152
|
+
willBias?: number
|
|
153
|
+
/**
|
|
154
|
+
* Learned social standing of this affordance's addressee, −1..1 (0 = neutral or
|
|
155
|
+
* unknown). Set only for an act aimed at someone. Every input is LEARNED, none
|
|
156
|
+
* hardcoded: ReputationTracker's trustworthiness — which is fed by
|
|
157
|
+
* `interaction.occurred` and so only became live once inbound conversation reached
|
|
158
|
+
* social cognition (#113) — weighted by that model's own confidence, plus the mind's
|
|
159
|
+
* current affective tone as a gentle tilt on reaching out at all.
|
|
160
|
+
*
|
|
161
|
+
* This is what makes "they never answer me" reach the competition: it arrives as a
|
|
162
|
+
* fading opinion of a person the mind formed itself and can revise, not as a damping
|
|
163
|
+
* curve applied behind its back.
|
|
164
|
+
*/
|
|
165
|
+
socialPrior?: number
|
|
143
166
|
/**
|
|
144
167
|
* Policy availability 0..1 (POLICY_REAFFERENCE P2) — learned from refusals,
|
|
145
168
|
* distinct from `available` (precondition satisfaction) and from competence.
|
|
@@ -149,6 +172,18 @@ export interface Affordance {
|
|
|
149
172
|
* gets an occasional re-probe and can climb back as availability recovers.
|
|
150
173
|
*/
|
|
151
174
|
availability?: number
|
|
175
|
+
/**
|
|
176
|
+
* How much of this act's OWN footprint is still live, 1 → 0 (EXAFFERENCE P5).
|
|
177
|
+
* Set when a consequence descriptor for the same (schema, target) has not yet
|
|
178
|
+
* expired — the mind has just done this and does not yet know how it landed.
|
|
179
|
+
*
|
|
180
|
+
* Damps the pull to do it again, and decays back to 0 on its own, so a silence
|
|
181
|
+
* that starts to matter can still out-compete it. Without this the executive's
|
|
182
|
+
* standing `ideomotor.intent` outlived the act it produced: the same two
|
|
183
|
+
* messages were delivered to the same person three times, ~21 ticks apart,
|
|
184
|
+
* each time as though it were the first.
|
|
185
|
+
*/
|
|
186
|
+
justEnacted?: number
|
|
152
187
|
/** Provenance: the plan whose frontier step projected this affordance. */
|
|
153
188
|
planId?: string
|
|
154
189
|
/** Provenance: the frontier step id — flows through to action.outcome so the plan advances. */
|