@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
|
@@ -24,7 +24,8 @@ import type {
|
|
|
24
24
|
SimulationContext,
|
|
25
25
|
ReadonlySimulationState,
|
|
26
26
|
ReasoningFootprint,
|
|
27
|
-
StateCommands
|
|
27
|
+
StateCommands,
|
|
28
|
+
EntityInput
|
|
28
29
|
} from '#core/types'
|
|
29
30
|
import { AsyncEngine } from '#core/async.engine'
|
|
30
31
|
import type { IntermediateStream } from '#core/async.engine'
|
|
@@ -42,6 +43,9 @@ import { GenerativeModel } from '#cognition/generative.model'
|
|
|
42
43
|
import { ExecutiveSummarizer } from '#llm/summarizer'
|
|
43
44
|
import { ExecutiveFacet, type ExecutiveFacetHandle } from '#faculties/executive.engine/facet'
|
|
44
45
|
import type { ExecutiveOutputFull, IdeationCandidate } from '#faculties/executive.engine/types'
|
|
46
|
+
import { DeliberationCache } from '#cognition/cache/deliberation.cache'
|
|
47
|
+
import { extractFingerprint } from '#cognition/cache/fingerprint'
|
|
48
|
+
import type { DeliberationCacheConfig, DeliberationCacheSnapshot } from '#cognition/cache/types'
|
|
45
49
|
import {
|
|
46
50
|
PromptFactory,
|
|
47
51
|
type PromptDependencies,
|
|
@@ -68,14 +72,13 @@ import { buildFallbackOutput, parseResponse } from '#faculties/executive.engine/
|
|
|
68
72
|
import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
|
|
69
73
|
import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
|
|
70
74
|
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
71
|
-
import { MessageQueue } from '#faculties/executive.engine/messages'
|
|
72
75
|
import {
|
|
73
76
|
buildStateCommands,
|
|
74
77
|
publishCognitiveEvents,
|
|
75
78
|
type CommandDependencies
|
|
76
79
|
} from '#faculties/executive.engine/commands'
|
|
77
80
|
import { DeferredEffectQueue } from '#faculties/executive.engine/deferred.effects'
|
|
78
|
-
import { EscalationBuffer } from '#faculties/executive.engine/escalation.buffer'
|
|
81
|
+
import { EscalationBuffer, type HandoffBody } from '#faculties/executive.engine/escalation.buffer'
|
|
79
82
|
import { FacetSupervisor } from '#faculties/executive.engine/facet.supervisor'
|
|
80
83
|
import type { EngineResult } from '#core/orchestrator'
|
|
81
84
|
import type { Duration } from '#core/types'
|
|
@@ -84,6 +87,17 @@ import { wallClock } from '#core/wall.clock'
|
|
|
84
87
|
// Re-export for compatibility
|
|
85
88
|
export { ExecutiveFacet, type ExecutiveFacetHandle }
|
|
86
89
|
|
|
90
|
+
/**
|
|
91
|
+
* How hard a reasoning facet pulls on attention, as an `attention.demand` urgency.
|
|
92
|
+
*
|
|
93
|
+
* Deliberately below 1: holding a live conversation is a real claim on attention,
|
|
94
|
+
* but the allocator sorts candidates into `maxFoci` slots by salience, and a facet
|
|
95
|
+
* that always outranked everything would starve perception — the mind would stop
|
|
96
|
+
* noticing the world whenever it was talking. At 0.7 an urgent percept still wins
|
|
97
|
+
* a slot; routine traffic does not.
|
|
98
|
+
*/
|
|
99
|
+
const FACET_ATTENTION_URGENCY = 0.7
|
|
100
|
+
|
|
87
101
|
// ── Engine config ───────────────────────────────────────────
|
|
88
102
|
|
|
89
103
|
export interface ExecutiveEngineConfig {
|
|
@@ -131,7 +145,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
131
145
|
private _testMode = false
|
|
132
146
|
|
|
133
147
|
// ── Message queue ──────────────────────────────────────────
|
|
134
|
-
private _messageQueue = new MessageQueue()
|
|
135
148
|
|
|
136
149
|
// ── Action diversity tracking ──────────────────────────────
|
|
137
150
|
private _recentActionTypes: string[] = []
|
|
@@ -146,6 +159,15 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
146
159
|
private _lastExecutiveOutput: ExecutiveOutputFull | null = null
|
|
147
160
|
private _lastExecutiveTick: number = -100
|
|
148
161
|
|
|
162
|
+
// ── DeliberationCache (optional fast path) ─────────────────
|
|
163
|
+
private _cache: DeliberationCache | null = null
|
|
164
|
+
private _cacheRestored = false
|
|
165
|
+
private _pendingVerify: { fingerprint: Float32Array; cachedActionTypes: string[] } | null = null
|
|
166
|
+
// Last cache outcome this cycle, surfaced as metrics/events from the committed path.
|
|
167
|
+
private _lastCacheHit = false
|
|
168
|
+
private _lastCacheConfidence = 0
|
|
169
|
+
private _lastCacheNeighborCount = 0
|
|
170
|
+
|
|
149
171
|
// ── Injected dependencies ──────────────────────────────────
|
|
150
172
|
private _willId: string | null = null
|
|
151
173
|
/**
|
|
@@ -175,7 +197,15 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
175
197
|
// engine keeps only the bus-subscription guard, since those subscriptions
|
|
176
198
|
// feed the gating salience buffer and are shared with the escalation path.
|
|
177
199
|
private readonly _facetSupervisor = new FacetSupervisor()
|
|
178
|
-
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Who each live facet is engaged with, learned from `executive.facet.sync`.
|
|
203
|
+
* Keyed by facetId; the last sync wins. Rendered into the master's own prompt so
|
|
204
|
+
* the singular seat can reason across its conversations "as if they were sitting
|
|
205
|
+
* at the same table" — which it cannot do while it only knows facet numbers.
|
|
206
|
+
* Stale entries age out on read (see _activeConversations).
|
|
207
|
+
*/
|
|
208
|
+
private _facetSubjects = new Map<string, { entityId: string; name?: string; tick: number }>()
|
|
179
209
|
|
|
180
210
|
// ── Cognitive models ───────────────────────────────────────
|
|
181
211
|
private readonly _model = new GenerativeModel()
|
|
@@ -187,6 +217,17 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
187
217
|
// ── Last state reference (for onReasoningComplete and facets) ─
|
|
188
218
|
private _lastStateRef: ReadonlySimulationState | null = null
|
|
189
219
|
|
|
220
|
+
/**
|
|
221
|
+
* The tick currently being processed, refreshed every react() — distinct from
|
|
222
|
+
* `_lastStateRef` (which tracks the REASONING tick and must not move under
|
|
223
|
+
* onReasoningComplete) and from `_lastExecutiveTick` (the last cycle that ran).
|
|
224
|
+
*
|
|
225
|
+
* Off-tick arrivals — a facet handoff, in particular — need to be stamped with
|
|
226
|
+
* when they actually happened. Using `_lastExecutiveTick` for that dated them to
|
|
227
|
+
* the last master cycle, which can be hundreds of ticks behind.
|
|
228
|
+
*/
|
|
229
|
+
private _currentTick = 0
|
|
230
|
+
|
|
190
231
|
// ── Deferred manager side-effects (FN11) ───────────────────
|
|
191
232
|
// Commit-gated queue for the mirroring manager writes returned by
|
|
192
233
|
// buildStateCommands. The queue runs them only after the orchestrator
|
|
@@ -231,8 +272,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
231
272
|
goallessTickCount: 0,
|
|
232
273
|
lowValenceTickCount: 0
|
|
233
274
|
}
|
|
234
|
-
|
|
235
|
-
this._ensureFacetSyncSubscription()
|
|
236
275
|
}
|
|
237
276
|
|
|
238
277
|
// ── Dependency injection ───────────────────────────────────
|
|
@@ -256,7 +295,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
256
295
|
/** Called by CognitiveOrchestrator.addEngine() — injects the shared bus. */
|
|
257
296
|
attachBus( bus: CognitiveBus ): void {
|
|
258
297
|
this._bus = bus
|
|
259
|
-
this._ensureFacetSyncSubscription()
|
|
260
298
|
}
|
|
261
299
|
|
|
262
300
|
/**
|
|
@@ -393,12 +431,22 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
393
431
|
* spawn site sets a focus whose `function` matches its role — so the routed
|
|
394
432
|
* answer is the same one, decided later and from the work itself.
|
|
395
433
|
*/
|
|
396
|
-
spawnFacet(
|
|
434
|
+
spawnFacet(
|
|
435
|
+
role?: 'deliberation' | 'conversation' | 'outreach' | 'supervision',
|
|
436
|
+
/**
|
|
437
|
+
* What this facet is FOR — see `FacetSpawnDeps.key`. Two spawns with the same
|
|
438
|
+
* key get the same facet, so callers no longer each invent their own dedup
|
|
439
|
+
* (and `authorOutreach`, which had none, no longer opens a rival facet on a
|
|
440
|
+
* person the mind is already talking to).
|
|
441
|
+
*/
|
|
442
|
+
key?: string,
|
|
443
|
+
): { attention: 'available' | 'full', handle?: ExecutiveFacetHandle } {
|
|
397
444
|
void role
|
|
398
445
|
// Delegate to FacetSupervisor (R5-g-3), passing the current engine
|
|
399
446
|
// attachments. The supervisor owns the registry + attention budget and
|
|
400
447
|
// performs the throw-checks on bus / director / state ref.
|
|
401
448
|
return this._facetSupervisor.spawn( {
|
|
449
|
+
...( key ? { key } : {} ),
|
|
402
450
|
bus: this._bus,
|
|
403
451
|
llmDirector: this._llmDirector,
|
|
404
452
|
stateRef: this._lastStateRef,
|
|
@@ -416,6 +464,14 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
416
464
|
} )
|
|
417
465
|
}
|
|
418
466
|
|
|
467
|
+
/**
|
|
468
|
+
* The facet already attending to `key`, if one is open — without spawning.
|
|
469
|
+
* See FacetSupervisor.handleFor.
|
|
470
|
+
*/
|
|
471
|
+
facetFor( key: string ): ExecutiveFacetHandle | undefined {
|
|
472
|
+
return this._facetSupervisor.handleFor( key )
|
|
473
|
+
}
|
|
474
|
+
|
|
419
475
|
// ── CognitiveEngine interface ──────────────────────────────
|
|
420
476
|
|
|
421
477
|
subscribes(): string[] { return [ '*' ] }
|
|
@@ -432,14 +488,28 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
432
488
|
onCognitiveEvent( event: CognitiveEvent ): StateCommands | void {
|
|
433
489
|
if( event.sourceEngine === this.name ) return
|
|
434
490
|
|
|
435
|
-
//
|
|
436
|
-
// One facet per ~0.3 free capacity units, floor at 1
|
|
491
|
+
// Spare attention scales the facet allowance within the persona's ceiling.
|
|
437
492
|
if( event.type === 'attention.state.changed'){
|
|
438
493
|
const p = event.payload as { freeFraction: number }
|
|
439
494
|
this._facetSupervisor.setAttentionState( p.freeFraction )
|
|
440
495
|
return
|
|
441
496
|
}
|
|
442
497
|
|
|
498
|
+
// The facet legs. These MUST be dispatched here rather than through their own
|
|
499
|
+
// `bus.subscribe` calls: the bus keeps one subscription per engineId, and the
|
|
500
|
+
// orchestrator's `subscribe( engine.name, engine.subscribes(), … )` runs after
|
|
501
|
+
// `attachBus`, so anything registered separately is silently replaced. This
|
|
502
|
+
// `['*']` subscription is the engine's only live one.
|
|
503
|
+
if( event.type === 'executive.facet.sync'){
|
|
504
|
+
this._onFacetSync( event )
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if( event.type === 'executive.facet.handoff'){
|
|
509
|
+
this._onFacetHandoff( event )
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
443
513
|
// GWT salience-based buffer
|
|
444
514
|
( event.salience ?? 0 ) >= WORKSPACE_THRESHOLD
|
|
445
515
|
&& this._gatingState.salienceBuffer.push( {
|
|
@@ -462,6 +532,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
462
532
|
state: ReadonlySimulationState,
|
|
463
533
|
context: SimulationContext,
|
|
464
534
|
): Promise<EngineResult> {
|
|
535
|
+
this._currentTick = tick as unknown as number
|
|
465
536
|
this._deferred.flush( state, tick as unknown as number )
|
|
466
537
|
this._deferred.markReactTick( tick as unknown as number )
|
|
467
538
|
// Per-tick facet pump: refresh every facet's state ref to THIS tick's frozen
|
|
@@ -469,7 +540,77 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
469
540
|
// Sits at a fixed point in the serial engine order — the issue-side twin of
|
|
470
541
|
// the CompletionInbox drain in Phase 2. See .TODO/FACET_REPLAY_DETERMINISM.md.
|
|
471
542
|
this._facetSupervisor.pump( state )
|
|
472
|
-
|
|
543
|
+
|
|
544
|
+
const result = await super.react( delta, tick, state, context )
|
|
545
|
+
|
|
546
|
+
// Reasoning facets occupy attention. Merged into whatever this tick already
|
|
547
|
+
// produced — AsyncEngine.react returns commands on every tick, reasoning or not,
|
|
548
|
+
// so the allocator sees the cost land the tick it is incurred.
|
|
549
|
+
const focus = this._facetAttentionDemands( state, tick as unknown as number )
|
|
550
|
+
if( focus.set.length || focus.delete.length ){
|
|
551
|
+
result.commands ??= {}
|
|
552
|
+
result.commands.set = [ ...( result.commands.set ?? [] ), ...focus.set ]
|
|
553
|
+
result.commands.delete = [ ...( result.commands.delete ?? [] ), ...focus.delete ]
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return result
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* What the mind is attending to because a facet is reasoning about it, as
|
|
561
|
+
* `attention.demand` entities the AttentionAllocator allocates real capacity
|
|
562
|
+
* against (`_extractSalienceSignals` reads this type; `costPerFocus` is then
|
|
563
|
+
* charged against the same 100-unit budget as perceptual foci).
|
|
564
|
+
*
|
|
565
|
+
* This closes a loop that was open in one direction only: the allocator's
|
|
566
|
+
* `freeFraction` scaled the facet budget, but facets never appeared in
|
|
567
|
+
* `_activeFocus`, so holding three conversations reported exactly as much spare
|
|
568
|
+
* attention as holding none. The budget was being scaled by a signal blind to the
|
|
569
|
+
* thing it was bounding.
|
|
570
|
+
*
|
|
571
|
+
* `urgency` sits below 1 on purpose: a live conversation is a genuine claim on
|
|
572
|
+
* attention but must not automatically outrank every percept — the allocator sorts
|
|
573
|
+
* candidates by salience into `maxFoci` slots, and a facet that always won would
|
|
574
|
+
* starve perception. Only BUSY facets are charged; an open-but-quiet thread is one
|
|
575
|
+
* the mind is in, not one it is attending to.
|
|
576
|
+
*/
|
|
577
|
+
private _facetAttentionDemands(
|
|
578
|
+
state: ReadonlySimulationState,
|
|
579
|
+
tick: number,
|
|
580
|
+
): { set: EntityInput[]; delete: string[] } {
|
|
581
|
+
const busy = this._facetSupervisor.busyFacetIds()
|
|
582
|
+
const set: EntityInput[] = []
|
|
583
|
+
const live = new Set<string>()
|
|
584
|
+
|
|
585
|
+
for( const facetId of busy ){
|
|
586
|
+
const id = `facet-attending-${facetId}`
|
|
587
|
+
live.add( id )
|
|
588
|
+
const subject = this._facetSubjects.get( facetId )
|
|
589
|
+
set.push({
|
|
590
|
+
id,
|
|
591
|
+
type: 'attention.demand',
|
|
592
|
+
metadata: {
|
|
593
|
+
urgency: FACET_ATTENTION_URGENCY,
|
|
594
|
+
source: 'executive-facet',
|
|
595
|
+
facetId,
|
|
596
|
+
...( subject?.entityId ? { subjectEntityId: subject.entityId } : {} ),
|
|
597
|
+
...( subject?.name ? { subjectName: subject.name } : {} ),
|
|
598
|
+
// NOT `generatesGoal` — GoalManager turns flagged demands into goals, and
|
|
599
|
+
// "I am talking to someone" is a state, not something to pursue.
|
|
600
|
+
tick,
|
|
601
|
+
},
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Release the attention a facet held once it stops reasoning (or is reaped).
|
|
606
|
+
const del: string[] = []
|
|
607
|
+
for( const [ id, e ] of state.entities )
|
|
608
|
+
if( e.type === 'attention.demand'
|
|
609
|
+
&& ( e.metadata as Record<string, unknown> | undefined )?.['source'] === 'executive-facet'
|
|
610
|
+
&& !live.has( id ) )
|
|
611
|
+
del.push( id )
|
|
612
|
+
|
|
613
|
+
return { set, delete: del }
|
|
473
614
|
}
|
|
474
615
|
|
|
475
616
|
protected override shouldAct(
|
|
@@ -483,6 +624,12 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
483
624
|
this._summarizerRestored = true
|
|
484
625
|
}
|
|
485
626
|
|
|
627
|
+
// Restore the deliberation cache from state on first tick (parallels the summary)
|
|
628
|
+
if( this._cache && !this._cacheRestored ){
|
|
629
|
+
this._restoreDeliberationCache( state )
|
|
630
|
+
this._cacheRestored = true
|
|
631
|
+
}
|
|
632
|
+
|
|
486
633
|
// Read runtime config overrides from state
|
|
487
634
|
const rtConfig = readRuntimeConfig( state, {
|
|
488
635
|
executiveInterval: this._executiveInterval,
|
|
@@ -522,7 +669,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
522
669
|
// Evaluate gating
|
|
523
670
|
const gatingDeps: GatingDependencies = {
|
|
524
671
|
generativeModel: this._generativeModel,
|
|
525
|
-
pendingMessages: this._messageQueue.pendingMessages,
|
|
526
672
|
hasPendingWork: this.hasPendingWork
|
|
527
673
|
}
|
|
528
674
|
|
|
@@ -564,7 +710,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
564
710
|
stream: IntermediateStream,
|
|
565
711
|
): Promise<unknown> {
|
|
566
712
|
this._lastStateRef = state
|
|
567
|
-
this._messageQueue.pendingCallStartTick = state.tick
|
|
568
713
|
|
|
569
714
|
// Mark which buffer entries this cycle consumes. Anything pushed after this
|
|
570
715
|
// point arrives during the LLM call and was never part of the broadcast, so
|
|
@@ -574,6 +719,47 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
574
719
|
// Update all facets with the latest state reference
|
|
575
720
|
this._facetSupervisor.broadcastStateRef( state )
|
|
576
721
|
|
|
722
|
+
// ── DeliberationCache fast path (optional) ───────────────
|
|
723
|
+
// A learned interpolation of past executive outputs. When a highly similar,
|
|
724
|
+
// highly competent precedent exists we skip the LLM entirely. R2-safe: the
|
|
725
|
+
// fingerprint, retrieval and composition are pure functions of the frozen
|
|
726
|
+
// state and the cache's own snapshotted contents. The mandatory bookkeeping
|
|
727
|
+
// above (buffer marking, facet broadcast) has already run, so a cache hit and
|
|
728
|
+
// a slow tick leave the facet/buffer discipline in the same state.
|
|
729
|
+
this._pendingVerify = null
|
|
730
|
+
if( this._cache ){
|
|
731
|
+
const cacheTick = footprint.tickObserved as unknown as number
|
|
732
|
+
this._cache.decay()
|
|
733
|
+
const fp = extractFingerprint( state )
|
|
734
|
+
const cacheResult = this._cache.retrieve( fp, cacheTick )
|
|
735
|
+
this._lastCacheHit = cacheResult.hit
|
|
736
|
+
this._lastCacheConfidence = cacheResult.confidence
|
|
737
|
+
this._lastCacheNeighborCount = cacheResult.neighbors.length
|
|
738
|
+
|
|
739
|
+
if( cacheResult.hit && cacheResult.output ){
|
|
740
|
+
if( !this._cache.shouldVerify() ){
|
|
741
|
+
// FAST PATH — return the composed output; this tick spends no tokens.
|
|
742
|
+
stream.report('executive_complete', {
|
|
743
|
+
actionCount: cacheResult.output.actions.length,
|
|
744
|
+
planCount: 0,
|
|
745
|
+
newBeliefCount: cacheResult.output.newBeliefs?.length ?? 0,
|
|
746
|
+
hasIntrospection: false,
|
|
747
|
+
hasNarrative: false,
|
|
748
|
+
} )
|
|
749
|
+
logger.info(
|
|
750
|
+
`[executive] ⚡ cache hit tick=${cacheTick} ` +
|
|
751
|
+
`ρ=${cacheResult.confidence.toFixed( 3 )} neighbors=${cacheResult.neighbors.length}`
|
|
752
|
+
)
|
|
753
|
+
return cacheResult.output
|
|
754
|
+
}
|
|
755
|
+
// Hit selected for verification — run the LLM and score the cache against it.
|
|
756
|
+
this._pendingVerify = {
|
|
757
|
+
fingerprint: fp,
|
|
758
|
+
cachedActionTypes: cacheResult.output.actions.map( a => a.type ),
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
577
763
|
// Build executive context using PromptFactory's helper
|
|
578
764
|
const execContext = await PromptFactory.buildFreshContext( {
|
|
579
765
|
workingMemory: this._workingMemory,
|
|
@@ -607,7 +793,13 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
607
793
|
priorConfidence: this._lastExecutiveOutput?.confidence ?? 0.5,
|
|
608
794
|
novelty: state.metrics.get('perception.novelty') ?? 0,
|
|
609
795
|
stressLoad: state.metrics.get('stress.load') ?? 0,
|
|
610
|
-
|
|
796
|
+
// Something a conversation surfaced is waiting on the master and has not
|
|
797
|
+
// been dealt with. This used to read the pending-message queue, which was
|
|
798
|
+
// never filled — so the effort gate's `pending_reply` term was structurally
|
|
799
|
+
// zero for every master that has ever run. Escalations are the master's real
|
|
800
|
+
// version of "someone is waiting on me": the facets do the replying, the
|
|
801
|
+
// master owns what they escalate.
|
|
802
|
+
hasPendingMessage: this._escalations.size > 0,
|
|
611
803
|
}, deliberateThreshold )
|
|
612
804
|
stream.report('process_selected', {
|
|
613
805
|
process: processSelection.process,
|
|
@@ -650,11 +842,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
650
842
|
state,
|
|
651
843
|
qualityModulation,
|
|
652
844
|
epistemicUncertainty,
|
|
653
|
-
|
|
654
|
-
focus,
|
|
845
|
+
focus,
|
|
655
846
|
deps: promptDeps,
|
|
656
847
|
recentActionTypes: [ ...this._recentActionTypes ],
|
|
657
848
|
mode: 'master',
|
|
849
|
+
activeConversations: this._activeConversations(),
|
|
658
850
|
outputFormat: PromptFactory.buildIdeationFormatInstruction(),
|
|
659
851
|
} )
|
|
660
852
|
// Propose temperature scales with the creativity trait (TODO #4): a creative Will
|
|
@@ -668,7 +860,13 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
668
860
|
ideationUserMessage,
|
|
669
861
|
tick: state.tick,
|
|
670
862
|
proposeTemperature,
|
|
671
|
-
meta: {
|
|
863
|
+
meta: {
|
|
864
|
+
category: 'executive',
|
|
865
|
+
attribute: 'master',
|
|
866
|
+
process: 'ideation',
|
|
867
|
+
function: '-',
|
|
868
|
+
demand: processSelection.effortScore
|
|
869
|
+
},
|
|
672
870
|
} )
|
|
673
871
|
logger.info(
|
|
674
872
|
`[executive] ◆ deliberate propose tick=${state.tick} ` +
|
|
@@ -688,11 +886,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
688
886
|
state,
|
|
689
887
|
qualityModulation,
|
|
690
888
|
epistemicUncertainty,
|
|
691
|
-
pendingMessages: [ ...this._messageQueue.pendingMessages ],
|
|
692
889
|
focus,
|
|
693
890
|
deps: promptDeps,
|
|
694
891
|
recentActionTypes: [ ...this._recentActionTypes ],
|
|
695
892
|
mode: 'master',
|
|
893
|
+
activeConversations: this._activeConversations(),
|
|
696
894
|
ideationCandidates
|
|
697
895
|
} )
|
|
698
896
|
|
|
@@ -706,7 +904,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
706
904
|
// D2: context counts for per-tick cognitive state snapshot
|
|
707
905
|
workingMemoryItems: execContext.workingMemory.length,
|
|
708
906
|
goalCount: execContext.goals.length,
|
|
709
|
-
pendingMessages: this._messageQueue.pendingMessages.length,
|
|
710
907
|
beliefCount: execContext.beliefs.length,
|
|
711
908
|
beliefsOmitted: execContext.beliefsOmitted,
|
|
712
909
|
promptPath: this._llmDirector?.writeDebugPrompt( state.tick, systemPrompt, userMessage ) ?? ''
|
|
@@ -724,7 +921,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
724
921
|
// MODEL_ROUTING W0 — the effort gate already weighed this tick's demand
|
|
725
922
|
// (uncertainty, prior confidence, novelty, a pending reply, stress load);
|
|
726
923
|
// forward it rather than inventing a second measure of the same thing.
|
|
727
|
-
const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master',
|
|
924
|
+
const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master', process: 'decision', function: '-', demand: processSelection.effortScore }
|
|
728
925
|
const result = this._chunkBroadcaster
|
|
729
926
|
? await this._llmDirector.callStream( systemPrompt, userMessage, state.tick, this._chunkBroadcaster, undefined, masterMeta )
|
|
730
927
|
: await this._llmDirector.call( systemPrompt, userMessage, state.tick, undefined, masterMeta )
|
|
@@ -811,6 +1008,23 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
811
1008
|
hasNarrative: !!executiveOutput.narrative
|
|
812
1009
|
} )
|
|
813
1010
|
|
|
1011
|
+
// ── DeliberationCache learning (slow path) ───────────────
|
|
1012
|
+
// extractFingerprint is pure over the frozen `state`, so recomputing here
|
|
1013
|
+
// yields the identical vector used on entry — no need to thread it through.
|
|
1014
|
+
if( this._cache ){
|
|
1015
|
+
const learnTick = footprint.tickObserved as unknown as number
|
|
1016
|
+
if( this._pendingVerify ){
|
|
1017
|
+
const match = this._actionTypesMatch(
|
|
1018
|
+
this._pendingVerify.cachedActionTypes,
|
|
1019
|
+
executiveOutput.actions.map( a => a.type ),
|
|
1020
|
+
)
|
|
1021
|
+
this._cache.updateCompetence( this._pendingVerify.fingerprint, match ? 1 : 0, learnTick )
|
|
1022
|
+
this._pendingVerify = null
|
|
1023
|
+
} else {
|
|
1024
|
+
this._cache.learn( extractFingerprint( state ), executiveOutput, learnTick )
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
814
1028
|
return executiveOutput
|
|
815
1029
|
}
|
|
816
1030
|
|
|
@@ -889,7 +1103,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
889
1103
|
)
|
|
890
1104
|
|
|
891
1105
|
// ── Flush pending escalation percepts ──────────────────────
|
|
892
|
-
// Convert buffered
|
|
1106
|
+
// Convert buffered executive.facet.handoff events into high-salience
|
|
893
1107
|
// percept entities so Exteroception surfaces them as
|
|
894
1108
|
// "## Percepts (What I Notice)" on the NEXT master cycle.
|
|
895
1109
|
// The master sees them as environmental signals — not as messages to reply to.
|
|
@@ -976,12 +1190,54 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
976
1190
|
} )
|
|
977
1191
|
|
|
978
1192
|
// Clear processed messages
|
|
979
|
-
this._messageQueue.clearProcessedMessages()
|
|
980
1193
|
|
|
981
|
-
// Merge escalation percepts into final commands
|
|
982
|
-
|
|
1194
|
+
// Merge escalation percepts into final commands, and retire the ones already
|
|
1195
|
+
// honoured. See _dischargedUndertakings for why this is not optional.
|
|
1196
|
+
const { keep, discharge } = this._reconcileUndertakings( escalationPercepts, this._lastStateRef! )
|
|
1197
|
+
if( keep.length ){
|
|
983
1198
|
commands.set ??= []
|
|
984
|
-
commands.set.push( ...
|
|
1199
|
+
commands.set.push( ...keep )
|
|
1200
|
+
}
|
|
1201
|
+
if( discharge.length ){
|
|
1202
|
+
commands.delete ??= []
|
|
1203
|
+
commands.delete.push( ...discharge )
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// ── Persist deliberation cache (mirrors the rolling summary) ──
|
|
1207
|
+
// Written each executive cycle so the learned patterns survive snapshot/
|
|
1208
|
+
// restore (R2). Bounded by maxPatterns; the stored output objects are only
|
|
1209
|
+
// ever read back, so state's deep-freeze of them is harmless.
|
|
1210
|
+
if( this._cache ){
|
|
1211
|
+
commands.set ??= []
|
|
1212
|
+
commands.set.push( {
|
|
1213
|
+
id: 'executive-deliberation-cache',
|
|
1214
|
+
type: 'executive.cache',
|
|
1215
|
+
metadata: { snapshot: this._cache.snapshot() },
|
|
1216
|
+
} )
|
|
1217
|
+
|
|
1218
|
+
// Surface cache state the R2-safe way: metrics ride the returned
|
|
1219
|
+
// StateCommands (never a frozen-state mutation), and hit/miss events
|
|
1220
|
+
// publish from this committed path so faculties can react to how
|
|
1221
|
+
// automatic the Will is becoming.
|
|
1222
|
+
const total = this._cache.hitCount + this._cache.missCount
|
|
1223
|
+
commands.metrics ??= []
|
|
1224
|
+
commands.metrics.push(
|
|
1225
|
+
[ 'cache.hit', this._lastCacheHit ? 1 : 0 ],
|
|
1226
|
+
[ 'cache.confidence', this._lastCacheConfidence ],
|
|
1227
|
+
[ 'cache.hit_rate', total > 0 ? this._cache.hitCount / total : 0 ],
|
|
1228
|
+
[ 'cache.size', this._cache.size ],
|
|
1229
|
+
)
|
|
1230
|
+
this._bus?.publish( this._lastCacheHit
|
|
1231
|
+
? {
|
|
1232
|
+
type: 'cache.hit', version: 1, sourceEngine: this.name,
|
|
1233
|
+
salience: this._lastCacheConfidence,
|
|
1234
|
+
payload: { confidence: this._lastCacheConfidence, neighborCount: this._lastCacheNeighborCount },
|
|
1235
|
+
}
|
|
1236
|
+
: {
|
|
1237
|
+
type: 'cache.miss', version: 1, sourceEngine: this.name,
|
|
1238
|
+
salience: 0.3,
|
|
1239
|
+
payload: { confidence: this._lastCacheConfidence },
|
|
1240
|
+
} )
|
|
985
1241
|
}
|
|
986
1242
|
|
|
987
1243
|
// Track entities modified
|
|
@@ -1001,122 +1257,287 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1001
1257
|
|
|
1002
1258
|
// ── Private helpers ────────────────────────────────────────
|
|
1003
1259
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1260
|
+
/**
|
|
1261
|
+
* The people the mind is in conversation with right now, newest first.
|
|
1262
|
+
*
|
|
1263
|
+
* Pruned against the supervisor's live facets on every read: a reaped facet is a
|
|
1264
|
+
* conversation that has ended, and a master that still believes it is mid-thread
|
|
1265
|
+
* with someone reasons about a table that is no longer there.
|
|
1266
|
+
*/
|
|
1267
|
+
private _activeConversations(): { entityId: string; name?: string; sinceTick: number }[] {
|
|
1268
|
+
const live = this._facetSupervisor.liveFacetIds()
|
|
1269
|
+
for( const id of [ ...this._facetSubjects.keys() ] )
|
|
1270
|
+
if( !live.has( id ) ) this._facetSubjects.delete( id )
|
|
1271
|
+
|
|
1272
|
+
return [ ...this._facetSubjects.values() ]
|
|
1273
|
+
.sort( ( a, b ) => b.tick - a.tick )
|
|
1274
|
+
.map( s => ({ entityId: s.entityId, ...( s.name ? { name: s.name } : {} ), sinceTick: s.tick }) )
|
|
1275
|
+
}
|
|
1021
1276
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Facet sync — remember WHO each facet is with, and wake the master.
|
|
1279
|
+
*
|
|
1280
|
+
* Reached from `onCognitiveEvent`, NOT from its own `bus.subscribe`. The bus
|
|
1281
|
+
* stores one subscription per engineId (`_subscriptions.set( engineId, … )`),
|
|
1282
|
+
* so a second `subscribe(this.name, …)` silently REPLACES the first — and the
|
|
1283
|
+
* orchestrator registers `subscribe( engine.name, engine.subscribes(), … )`
|
|
1284
|
+
* after `attachBus`, which replaced everything registered here. Two dedicated
|
|
1285
|
+
* handlers used to be installed at this point; the second overwrote the first
|
|
1286
|
+
* and the orchestrator then overwrote that, so neither ever ran. The escalation
|
|
1287
|
+
* leg had been dead in production for its whole life: a facet could escalate,
|
|
1288
|
+
* the audition engine published, and nothing was listening.
|
|
1289
|
+
*/
|
|
1290
|
+
/**
|
|
1291
|
+
* Retire undertakings the mind has already honoured, and refuse to restate one
|
|
1292
|
+
* it is already carrying.
|
|
1293
|
+
*
|
|
1294
|
+
* An undertaking percept says, in the first person, "I said I would reach X and
|
|
1295
|
+
* nothing has gone to them yet". That sentence has to stop being true at some
|
|
1296
|
+
* point, and nothing made it stop. Measured on a live Will: SEVEN of them
|
|
1297
|
+
* accumulated in state, every one still asserting nothing had been sent, while
|
|
1298
|
+
* a `conversation.sent` to that person sat right beside them. She read seven
|
|
1299
|
+
* standing unfulfilled promises every cycle and dutifully sent the same message
|
|
1300
|
+
* again, five times in five minutes and once more in the next session — the
|
|
1301
|
+
* percept meant to stop her forgetting a promise was making her unable to
|
|
1302
|
+
* believe she had kept it.
|
|
1303
|
+
*
|
|
1304
|
+
* Discharged by EVIDENCE, not by a timer: a `conversation.sent` to that target,
|
|
1305
|
+
* written no earlier than the undertaking, means the contact happened. That
|
|
1306
|
+
* record is durable and snapshots with the state, so the discharge survives a
|
|
1307
|
+
* restart exactly as the promise does — which the tick-scoped satiation in
|
|
1308
|
+
* `enactionFootprint` deliberately cannot.
|
|
1309
|
+
*
|
|
1310
|
+
* It stays a decision, not an erasure. Retiring the percept removes the standing
|
|
1311
|
+
* claim that the words are unsent; whether to say more to that person is then an
|
|
1312
|
+
* ordinary competition like any other.
|
|
1313
|
+
*/
|
|
1314
|
+
private _reconcileUndertakings(
|
|
1315
|
+
incoming: EntityInput[],
|
|
1316
|
+
state: ReadonlySimulationState,
|
|
1317
|
+
): { keep: EntityInput[]; discharge: string[] } {
|
|
1318
|
+
/** Latest tick at which anything was sent to each target. */
|
|
1319
|
+
const contacted = new Map<string, number>()
|
|
1320
|
+
for( const [ , e ] of state.entities ){
|
|
1321
|
+
if( e.type !== 'conversation.sent') continue
|
|
1322
|
+
const m = e.metadata as Record<string, unknown> | undefined
|
|
1323
|
+
const target = typeof m?.['targetEntityId'] === 'string' ? m['targetEntityId'] as string : undefined
|
|
1324
|
+
if( !target ) continue
|
|
1325
|
+
// metadata.tick where set, else the entity's own sim-clock tick. Reading
|
|
1326
|
+
// metadata alone defaulted every record to 0, so `contacted >= madeAt` was
|
|
1327
|
+
// false for any promise made after tick 0 — this discharge never once fired.
|
|
1328
|
+
const at = typeof m?.['tick'] === 'number' ? m['tick'] as number
|
|
1329
|
+
: typeof ( e as { tick?: number } ).tick === 'number' ? ( e as { tick?: number } ).tick as number
|
|
1330
|
+
: 0
|
|
1331
|
+
contacted.set( target, Math.max( contacted.get( target ) ?? 0, at ) )
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/** An undertaking is spent once we have reached its target since making it. */
|
|
1335
|
+
const honoured = ( target: string | undefined, madeAt: number ): boolean =>
|
|
1336
|
+
!!target && ( contacted.get( target ) ?? -1 ) >= madeAt
|
|
1337
|
+
|
|
1338
|
+
const undertakingOf = ( m: Record<string, unknown> | undefined ) =>
|
|
1339
|
+
m?.['category'] === 'undertaking'
|
|
1340
|
+
? {
|
|
1341
|
+
target: typeof m['undertakingTarget'] === 'string' ? m['undertakingTarget'] as string : undefined,
|
|
1342
|
+
madeAt: typeof m['tick'] === 'number' ? m['tick'] as number : 0,
|
|
1032
1343
|
}
|
|
1344
|
+
: undefined
|
|
1345
|
+
|
|
1346
|
+
const discharge: string[] = []
|
|
1347
|
+
/** Targets we are already carrying a live, unhonoured undertaking toward. */
|
|
1348
|
+
const carrying = new Set<string>()
|
|
1349
|
+
|
|
1350
|
+
for( const [ id, e ] of state.entities ){
|
|
1351
|
+
if( e.type !== 'percept') continue
|
|
1352
|
+
const u = undertakingOf( e.metadata as Record<string, unknown> | undefined )
|
|
1353
|
+
if( !u ) continue
|
|
1354
|
+
|
|
1355
|
+
// No target ⇒ it predates the field this reconciliation keys on, so it can
|
|
1356
|
+
// never be matched against a contact and would assert "nothing has gone to
|
|
1357
|
+
// them yet" for the rest of the mind's life. Seven of these were found in a
|
|
1358
|
+
// live snapshot, each one re-read every cycle. Undischargeable is strictly
|
|
1359
|
+
// worse than gone.
|
|
1360
|
+
if( !u.target ){ discharge.push( id ); continue }
|
|
1361
|
+
|
|
1362
|
+
if( honoured( u.target, u.madeAt ) ) discharge.push( id )
|
|
1363
|
+
else carrying.add( u.target )
|
|
1364
|
+
}
|
|
1033
1365
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1366
|
+
// Drop an incoming restatement of something already honoured or already held.
|
|
1367
|
+
// Duplicates were how seven of these piled up: each master cycle drained a
|
|
1368
|
+
// fresh copy of a promise that could never be marked kept.
|
|
1369
|
+
const keep = incoming.filter( p => {
|
|
1370
|
+
const u = undertakingOf( p.metadata as Record<string, unknown> | undefined )
|
|
1371
|
+
if( !u?.target ) return true
|
|
1372
|
+
if( honoured( u.target, u.madeAt ) ) return false
|
|
1373
|
+
if( carrying.has( u.target ) ) return false
|
|
1374
|
+
carrying.add( u.target )
|
|
1375
|
+
return true
|
|
1376
|
+
} )
|
|
1038
1377
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1378
|
+
if( discharge.length )
|
|
1379
|
+
logger.info(`[executive] ${ discharge.length } undertaking(s) discharged — the contact was made`)
|
|
1380
|
+
|
|
1381
|
+
return { keep, discharge }
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
private _onFacetSync( event: CognitiveEvent ): void {
|
|
1385
|
+
const payload = event.payload as {
|
|
1386
|
+
facetId?: string
|
|
1387
|
+
reasoning?: string
|
|
1388
|
+
confidence?: number
|
|
1389
|
+
tick?: number
|
|
1390
|
+
subjectEntityId?: string
|
|
1391
|
+
subjectName?: string
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// Remember WHO this facet is with, not just that it reported.
|
|
1395
|
+
if( payload.facetId && payload.subjectEntityId )
|
|
1396
|
+
this._facetSubjects.set( payload.facetId, {
|
|
1397
|
+
entityId: payload.subjectEntityId,
|
|
1398
|
+
...( payload.subjectName ? { name: payload.subjectName } : {} ),
|
|
1399
|
+
tick: payload.tick ?? this._lastExecutiveTick ?? 0,
|
|
1400
|
+
})
|
|
1401
|
+
|
|
1402
|
+
// Unconditional (not gated on WORKSPACE_THRESHOLD like ordinary traffic):
|
|
1403
|
+
// a facet reporting back is the master's own attention returning, and the
|
|
1404
|
+
// point of the spike is that it re-evaluates rather than waiting out its
|
|
1405
|
+
// interval.
|
|
1406
|
+
this._gatingState.salienceBuffer.push({
|
|
1407
|
+
event,
|
|
1408
|
+
tick: payload.tick ?? event.logicalTime ?? 0,
|
|
1409
|
+
})
|
|
1410
|
+
|
|
1411
|
+
logger.info(
|
|
1412
|
+
`[executive] master received facet sync from ${payload.facetId}` +
|
|
1413
|
+
( payload.subjectName || payload.subjectEntityId
|
|
1414
|
+
? ` (with ${payload.subjectName ?? payload.subjectEntityId})` : '') +
|
|
1415
|
+
` (confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1044
1416
|
)
|
|
1417
|
+
}
|
|
1045
1418
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1419
|
+
/**
|
|
1420
|
+
* A focused part of me surfaced something the singular seat owns — work to plan
|
|
1421
|
+
* (`escalation`) or an intention toward a third party (`undertaking`).
|
|
1422
|
+
*
|
|
1423
|
+
* ONE handler for every facet type. This was `_onAuditionTaskSignal`, listening
|
|
1424
|
+
* on a topic named for one sense engine and typed with one sense engine's nouns
|
|
1425
|
+
* (`entityId`, `threadId`), which meant a planning, supervision or deliberation
|
|
1426
|
+
* facet had no way to hand anything up at all. See EscalationBuffer for the full
|
|
1427
|
+
* rationale; new kinds go in `HandoffBody`, not in a new topic and a new handler
|
|
1428
|
+
* beside this one.
|
|
1429
|
+
*
|
|
1430
|
+
* Master stays out of the reply path entirely:
|
|
1431
|
+
* • The facet has already said (or will say) whatever the person in front of
|
|
1432
|
+
* it needed to hear.
|
|
1433
|
+
* • The master's job is purely cognitive: create a [PLANS] block, update
|
|
1434
|
+
* goals, reflect, or decide whether it still means to make that contact.
|
|
1435
|
+
* Any follow-up communication flows through the agency competition —
|
|
1436
|
+
* NEVER via [REPLY].
|
|
1437
|
+
*
|
|
1438
|
+
* Buffered rather than written directly: state is read-only here, so
|
|
1439
|
+
* `EscalationBuffer.drainToPercepts()` emits it as a StateCommand on the next
|
|
1440
|
+
* master cycle, where Exteroception surfaces it under "## Percepts (What I Notice)".
|
|
1441
|
+
*/
|
|
1442
|
+
private _onFacetHandoff( event: CognitiveEvent ): void {
|
|
1443
|
+
const payload = event.payload as {
|
|
1444
|
+
facetId?: string
|
|
1445
|
+
subjectEntityId?: string
|
|
1446
|
+
subjectName?: string
|
|
1447
|
+
threadId?: string
|
|
1448
|
+
confidence?: number
|
|
1449
|
+
tick?: number
|
|
1450
|
+
body: HandoffBody
|
|
1451
|
+
}
|
|
1452
|
+
if( !payload?.body?.kind ) return
|
|
1453
|
+
|
|
1454
|
+
// NOW, not the last time the master happened to run.
|
|
1059
1455
|
//
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
(
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1456
|
+
// This was `this._lastExecutiveTick`, so an undertaking formed at tick 900 was
|
|
1457
|
+
// stamped 780 if that was the last master cycle. `_reconcileUndertakings`
|
|
1458
|
+
// discharges on "contacted at or after `madeAt`", which means a message sent at
|
|
1459
|
+
// tick 800 — BEFORE the promise existed — retired it. Over-discharge is the safe
|
|
1460
|
+
// direction, which is why it never surfaced as a symptom, but it made the promise
|
|
1461
|
+
// unfalsifiable: it could be marked kept by something that happened first.
|
|
1462
|
+
const tick = payload.tick ?? this._currentTick
|
|
1463
|
+
|
|
1464
|
+
this._escalations.push({
|
|
1465
|
+
tick,
|
|
1466
|
+
...( payload.facetId ? { facetId: payload.facetId } : {} ),
|
|
1467
|
+
...( payload.subjectEntityId ? { subjectEntityId: payload.subjectEntityId } : {} ),
|
|
1468
|
+
...( payload.subjectName ? { subjectName: payload.subjectName } : {} ),
|
|
1469
|
+
...( payload.threadId ? { threadId: payload.threadId } : {} ),
|
|
1470
|
+
body: payload.body.kind === 'undertaking'
|
|
1471
|
+
? { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) }
|
|
1472
|
+
: { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) },
|
|
1473
|
+
})
|
|
1474
|
+
|
|
1475
|
+
// Spike the salience buffer so the master fires soon rather than waiting for
|
|
1476
|
+
// the next scheduled interval. The master must NOT be handed the inbound as a
|
|
1477
|
+
// message to answer — that would make it produce a [REPLY], duplicating the
|
|
1478
|
+
// facet's and breaking the facet/master boundary. (The queue that once carried
|
|
1479
|
+
// inbound this way was removed in #114: nothing ever filled it.)
|
|
1480
|
+
this._gatingState.salienceBuffer.push({ event, tick })
|
|
1481
|
+
|
|
1482
|
+
const from = payload.subjectName ?? payload.subjectEntityId ?? payload.facetId ?? 'a focus'
|
|
1483
|
+
logger.info(
|
|
1484
|
+
payload.body.kind === 'undertaking'
|
|
1485
|
+
? `[executive] master queued undertaking from ${from} ` +
|
|
1486
|
+
`→ reach ${payload.body.target} (confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1487
|
+
: `[executive] master queued escalation percept from ${from} ` +
|
|
1488
|
+
`(confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1489
|
+
)
|
|
1490
|
+
}
|
|
1077
1491
|
|
|
1078
|
-
|
|
1079
|
-
// "## Percepts (What I Notice)" — master sees it as an
|
|
1080
|
-
// environmental signal prompting cognitive work, not a reply.
|
|
1081
|
-
if( this._lastStateRef ){
|
|
1082
|
-
// State is read-only here; we can't write directly.
|
|
1083
|
-
// Instead, buffer the escalation for injection on next tick.
|
|
1084
|
-
// The master's shouldAct() will fire due to the salience spike below.
|
|
1085
|
-
// The actual percept write happens in onReasoningComplete() via
|
|
1086
|
-
// a synthetic percept we store here and emit as a StateCommand.
|
|
1087
|
-
this._escalations.push({
|
|
1088
|
-
entityId: payload.entityId,
|
|
1089
|
-
threadId: payload.threadId,
|
|
1090
|
-
reasoning: payload.reasoning.slice( 0, 400 ),
|
|
1091
|
-
tick: this._lastExecutiveTick ?? 0,
|
|
1092
|
-
})
|
|
1093
|
-
}
|
|
1492
|
+
// ── DeliberationCache wiring ───────────────────────────────
|
|
1094
1493
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
type: 'audition.task.signal',
|
|
1100
|
-
version: 1,
|
|
1101
|
-
sequenceNumber: 1,
|
|
1102
|
-
sourceEngine: 'audition-engine',
|
|
1103
|
-
salience: event.salience ?? 0.9,
|
|
1104
|
-
payload,
|
|
1105
|
-
wallTime: wallClock(), // telemetry field; logicalTime carries the deterministic clock
|
|
1106
|
-
logicalTime: this._lastExecutiveTick ?? 0
|
|
1107
|
-
}
|
|
1494
|
+
/** Enable the deliberation cache (off by default). Call during mind assembly. */
|
|
1495
|
+
enableCache( config?: DeliberationCacheConfig ): void {
|
|
1496
|
+
this._cache = new DeliberationCache( config )
|
|
1497
|
+
}
|
|
1108
1498
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
tick: this._lastExecutiveTick ?? 0
|
|
1112
|
-
} )
|
|
1499
|
+
/** True when the cache is active. */
|
|
1500
|
+
get cacheEnabled(): boolean { return this._cache !== null }
|
|
1113
1501
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1502
|
+
/** Telemetry snapshot for harnesses / eval. Null when disabled. */
|
|
1503
|
+
cacheStats(): { size: number; hits: number; misses: number } | null {
|
|
1504
|
+
if( !this._cache ) return null
|
|
1505
|
+
return { size: this._cache.size, hits: this._cache.hitCount, misses: this._cache.missCount }
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Reafference hook — update cache competence from a confirmed action outcome.
|
|
1510
|
+
* Optional, layered on top of the inline verify loop. Reward follows the
|
|
1511
|
+
* research sketch: mean of (action succeeded, stress relief, goal progress).
|
|
1512
|
+
*/
|
|
1513
|
+
onActionOutcome(
|
|
1514
|
+
state: ReadonlySimulationState,
|
|
1515
|
+
tick: Tick,
|
|
1516
|
+
success: boolean,
|
|
1517
|
+
stressDelta: number,
|
|
1518
|
+
goalProgressDelta: number,
|
|
1519
|
+
): void {
|
|
1520
|
+
if( !this._cache ) return
|
|
1521
|
+
const reward = (
|
|
1522
|
+
( success ? 1 : 0 ) +
|
|
1523
|
+
( 1 - Math.max( 0, Math.min( 1, stressDelta ) ) ) +
|
|
1524
|
+
Math.max( 0, Math.min( 1, goalProgressDelta ) )
|
|
1525
|
+
) / 3
|
|
1526
|
+
this._cache.updateCompetence( extractFingerprint( state ), reward, tick )
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
private _actionTypesMatch( a: string[], b: string[] ): boolean {
|
|
1530
|
+
if( a.length !== b.length ) return false
|
|
1531
|
+
for( let i = 0; i < a.length; i++ ) if( a[ i ] !== b[ i ] ) return false
|
|
1532
|
+
return true
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
private _restoreDeliberationCache( state: ReadonlySimulationState ): void {
|
|
1536
|
+
if( !this._cache ) return
|
|
1537
|
+
const entity = state.entities.get('executive-deliberation-cache')
|
|
1538
|
+
if( !entity ) return
|
|
1539
|
+
const snap = entity.metadata?.[ 'snapshot' ] as DeliberationCacheSnapshot | undefined
|
|
1540
|
+
if( snap ) this._cache.restore( snap )
|
|
1120
1541
|
}
|
|
1121
1542
|
|
|
1122
1543
|
private _restoreSummarizer( state: ReadonlySimulationState ): void {
|