@mindot/will 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +6985 -3337
- package/dist/index.js.map +1 -1
- package/dist/surface/channels/discord.d.ts +177 -0
- package/dist/surface/channels/discord.js +394 -0
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +11 -5
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +5382 -1573
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +356 -1
- package/src/cognition/agency/conversation.aim.ts +292 -0
- package/src/cognition/agency/engines/action.selector.ts +142 -5
- package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
- package/src/cognition/agency/engines/reafference.engine.ts +160 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +49 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +73 -1
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -1
- package/src/cognition/event.schemas.ts +22 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +106 -46
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +714 -136
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +120 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
- package/src/cognition/faculties/executive.engine/types.ts +84 -12
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +144 -16
- package/src/cognition/faculties/known.entity.tracker.ts +287 -29
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +16 -21
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -4
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +492 -53
- package/src/cognition/senses/base.sense.engine.ts +105 -7
- package/src/cognition/senses/index.ts +83 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +296 -0
- package/src/cognition/utilities/token.tracker.ts +94 -10
- package/src/core/orchestrator.ts +51 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +127 -33
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +57 -0
- package/src/pma/index.ts +68 -54
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +171 -23
- package/src/stem/mind.ts +198 -57
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/surface/channels/discord.ts +609 -0
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/surface/channels/types.ts +136 -0
- package/src/{channels → surface/channels}/whatsapp.ts +22 -10
- package/src/{cli.ts → surface/cli.ts} +14 -9
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +194 -33
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.d.ts +0 -69
- package/dist/channels/discord.js +0 -193
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/channels/discord.ts +0 -214
- package/src/channels/types.ts +0 -46
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
|
@@ -18,15 +18,20 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { logger } from '#core/logger'
|
|
21
|
+
import { readAliases, canonicalOf } from '#cognition/social.identity'
|
|
21
22
|
import type { SessionLogger } from '#stem/tracts/session.logger'
|
|
22
23
|
import type {
|
|
23
24
|
Tick,
|
|
24
25
|
SimulationContext,
|
|
25
26
|
ReadonlySimulationState,
|
|
26
27
|
ReasoningFootprint,
|
|
27
|
-
StateCommands
|
|
28
|
+
StateCommands,
|
|
29
|
+
EntityInput
|
|
28
30
|
} from '#core/types'
|
|
29
31
|
import { AsyncEngine } from '#core/async.engine'
|
|
32
|
+
import {
|
|
33
|
+
actionRecordEntity, staleActionRecordIds, type ActionStatus,
|
|
34
|
+
} from '#faculties/executive.engine/action.record'
|
|
30
35
|
import type { IntermediateStream } from '#core/async.engine'
|
|
31
36
|
import type { WorkingMemory } from '#faculties/working.memory'
|
|
32
37
|
import type { GoalManager, GoalState } from '#faculties/goal.manager'
|
|
@@ -42,6 +47,9 @@ import { GenerativeModel } from '#cognition/generative.model'
|
|
|
42
47
|
import { ExecutiveSummarizer } from '#llm/summarizer'
|
|
43
48
|
import { ExecutiveFacet, type ExecutiveFacetHandle } from '#faculties/executive.engine/facet'
|
|
44
49
|
import type { ExecutiveOutputFull, IdeationCandidate } from '#faculties/executive.engine/types'
|
|
50
|
+
import { DeliberationCache } from '#cognition/cache/deliberation.cache'
|
|
51
|
+
import { extractFingerprint } from '#cognition/cache/fingerprint'
|
|
52
|
+
import type { DeliberationCacheConfig, DeliberationCacheSnapshot } from '#cognition/cache/types'
|
|
45
53
|
import {
|
|
46
54
|
PromptFactory,
|
|
47
55
|
type PromptDependencies,
|
|
@@ -68,21 +76,29 @@ import { buildFallbackOutput, parseResponse } from '#faculties/executive.engine/
|
|
|
68
76
|
import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
|
|
69
77
|
import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
|
|
70
78
|
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
71
|
-
import { MessageQueue } from '#faculties/executive.engine/messages'
|
|
72
79
|
import {
|
|
73
80
|
buildStateCommands,
|
|
74
81
|
publishCognitiveEvents,
|
|
75
82
|
type CommandDependencies
|
|
76
83
|
} from '#faculties/executive.engine/commands'
|
|
77
84
|
import { DeferredEffectQueue } from '#faculties/executive.engine/deferred.effects'
|
|
78
|
-
import { EscalationBuffer } from '#faculties/executive.engine/escalation.buffer'
|
|
85
|
+
import { EscalationBuffer, type HandoffBody } from '#faculties/executive.engine/escalation.buffer'
|
|
79
86
|
import { FacetSupervisor } from '#faculties/executive.engine/facet.supervisor'
|
|
80
87
|
import type { EngineResult } from '#core/orchestrator'
|
|
81
88
|
import type { Duration } from '#core/types'
|
|
82
89
|
import { wallClock } from '#core/wall.clock'
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* How hard a reasoning facet pulls on attention, as an `attention.demand` urgency.
|
|
94
|
+
*
|
|
95
|
+
* Deliberately below 1: holding a live conversation is a real claim on attention,
|
|
96
|
+
* but the allocator sorts candidates into `maxFoci` slots by salience, and a facet
|
|
97
|
+
* that always outranked everything would starve perception — the mind would stop
|
|
98
|
+
* noticing the world whenever it was talking. At 0.7 an urgent percept still wins
|
|
99
|
+
* a slot; routine traffic does not.
|
|
100
|
+
*/
|
|
101
|
+
const FACET_ATTENTION_URGENCY = 0.7
|
|
86
102
|
|
|
87
103
|
// ── Engine config ───────────────────────────────────────────
|
|
88
104
|
|
|
@@ -131,7 +147,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
131
147
|
private _testMode = false
|
|
132
148
|
|
|
133
149
|
// ── Message queue ──────────────────────────────────────────
|
|
134
|
-
private _messageQueue = new MessageQueue()
|
|
135
150
|
|
|
136
151
|
// ── Action diversity tracking ──────────────────────────────
|
|
137
152
|
private _recentActionTypes: string[] = []
|
|
@@ -146,6 +161,15 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
146
161
|
private _lastExecutiveOutput: ExecutiveOutputFull | null = null
|
|
147
162
|
private _lastExecutiveTick: number = -100
|
|
148
163
|
|
|
164
|
+
// ── DeliberationCache (optional fast path) ─────────────────
|
|
165
|
+
private _cache: DeliberationCache | null = null
|
|
166
|
+
private _cacheRestored = false
|
|
167
|
+
private _pendingVerify: { fingerprint: Float32Array; cachedActionTypes: string[] } | null = null
|
|
168
|
+
// Last cache outcome this cycle, surfaced as metrics/events from the committed path.
|
|
169
|
+
private _lastCacheHit = false
|
|
170
|
+
private _lastCacheConfidence = 0
|
|
171
|
+
private _lastCacheNeighborCount = 0
|
|
172
|
+
|
|
149
173
|
// ── Injected dependencies ──────────────────────────────────
|
|
150
174
|
private _willId: string | null = null
|
|
151
175
|
/**
|
|
@@ -175,7 +199,41 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
175
199
|
// engine keeps only the bus-subscription guard, since those subscriptions
|
|
176
200
|
// feed the gating salience buffer and are shared with the escalation path.
|
|
177
201
|
private readonly _facetSupervisor = new FacetSupervisor()
|
|
178
|
-
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Who each live facet is engaged with AND what it concluded there, learned from
|
|
205
|
+
* `executive.facet.sync`. Keyed by facetId; the last sync wins. Rendered into
|
|
206
|
+
* the master's own prompt so the singular seat can reason across its
|
|
207
|
+
* conversations "as if they were sitting at the same table" — which it cannot
|
|
208
|
+
* do while it only knows facet numbers. Stale entries age out on read (see
|
|
209
|
+
* _activeConversations).
|
|
210
|
+
*
|
|
211
|
+
* `concluded` IS THE RETURN LEG, and it did not exist. `executive.facet.sync`
|
|
212
|
+
* has always carried the facet's full `reasoning`; this handler received it and
|
|
213
|
+
* dropped it on the floor. So the sync was one-way: the master's thinking flows
|
|
214
|
+
* DOWN to facets as "What I've Been Turning Over", while what a facet worked out
|
|
215
|
+
* came back as identity and a salience spike — that attention had been engaged,
|
|
216
|
+
* and with whom, never what it concluded.
|
|
217
|
+
*
|
|
218
|
+
* A facet is the same mind with a focus, not a subordinate reporting in. A mind
|
|
219
|
+
* that cannot read back its own thinking from where its attention has been is
|
|
220
|
+
* split, and the prompt comment two files over already named the consequence:
|
|
221
|
+
* "telling one person it has contacted another when it has not".
|
|
222
|
+
*
|
|
223
|
+
* It is kept HERE, on the engine, rather than as a percept, because a percept is
|
|
224
|
+
* swept after 2 ticks and a working-memory item decays below the retrieval
|
|
225
|
+
* threshold in about 9 — while the master's own interval is 15. Anything routed
|
|
226
|
+
* that way is usually gone before the master next reads. This survives to the
|
|
227
|
+
* next cycle by construction.
|
|
228
|
+
*/
|
|
229
|
+
private _facetSubjects = new Map<string, {
|
|
230
|
+
entityId: string
|
|
231
|
+
name?: string
|
|
232
|
+
tick: number
|
|
233
|
+
concluded?: string
|
|
234
|
+
/** Commitments the facet declared toward a THIRD party while attending here. */
|
|
235
|
+
promised?: Array<{ what: string; target?: string; gist?: string; tick: number }>
|
|
236
|
+
}>()
|
|
179
237
|
|
|
180
238
|
// ── Cognitive models ───────────────────────────────────────
|
|
181
239
|
private readonly _model = new GenerativeModel()
|
|
@@ -187,6 +245,17 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
187
245
|
// ── Last state reference (for onReasoningComplete and facets) ─
|
|
188
246
|
private _lastStateRef: ReadonlySimulationState | null = null
|
|
189
247
|
|
|
248
|
+
/**
|
|
249
|
+
* The tick currently being processed, refreshed every react() — distinct from
|
|
250
|
+
* `_lastStateRef` (which tracks the REASONING tick and must not move under
|
|
251
|
+
* onReasoningComplete) and from `_lastExecutiveTick` (the last cycle that ran).
|
|
252
|
+
*
|
|
253
|
+
* Off-tick arrivals — a facet handoff, in particular — need to be stamped with
|
|
254
|
+
* when they actually happened. Using `_lastExecutiveTick` for that dated them to
|
|
255
|
+
* the last master cycle, which can be hundreds of ticks behind.
|
|
256
|
+
*/
|
|
257
|
+
private _currentTick = 0
|
|
258
|
+
|
|
190
259
|
// ── Deferred manager side-effects (FN11) ───────────────────
|
|
191
260
|
// Commit-gated queue for the mirroring manager writes returned by
|
|
192
261
|
// buildStateCommands. The queue runs them only after the orchestrator
|
|
@@ -206,6 +275,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
206
275
|
// See EscalationBuffer for the full rationale (R5-g-2).
|
|
207
276
|
private readonly _escalations = new EscalationBuffer()
|
|
208
277
|
|
|
278
|
+
/** First tick of an unbroken `hasPendingWork` suppression; null when the seat is free. */
|
|
279
|
+
private _mutedSince: Tick | null = null
|
|
280
|
+
/** Whether this suppression episode has already been reported. */
|
|
281
|
+
private _mutedWarned = false
|
|
282
|
+
|
|
209
283
|
/** Set/clear the chunk broadcaster (called by WillManager when SSE clients connect). */
|
|
210
284
|
setChunkBroadcaster( fn: (( chunk: string ) => void) | null ): void {
|
|
211
285
|
this._chunkBroadcaster = fn
|
|
@@ -231,8 +305,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
231
305
|
goallessTickCount: 0,
|
|
232
306
|
lowValenceTickCount: 0
|
|
233
307
|
}
|
|
234
|
-
|
|
235
|
-
this._ensureFacetSyncSubscription()
|
|
236
308
|
}
|
|
237
309
|
|
|
238
310
|
// ── Dependency injection ───────────────────────────────────
|
|
@@ -256,7 +328,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
256
328
|
/** Called by CognitiveOrchestrator.addEngine() — injects the shared bus. */
|
|
257
329
|
attachBus( bus: CognitiveBus ): void {
|
|
258
330
|
this._bus = bus
|
|
259
|
-
this._ensureFacetSyncSubscription()
|
|
260
331
|
}
|
|
261
332
|
|
|
262
333
|
/**
|
|
@@ -393,12 +464,22 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
393
464
|
* spawn site sets a focus whose `function` matches its role — so the routed
|
|
394
465
|
* answer is the same one, decided later and from the work itself.
|
|
395
466
|
*/
|
|
396
|
-
spawnFacet(
|
|
467
|
+
spawnFacet(
|
|
468
|
+
role?: 'deliberation' | 'conversation' | 'outreach' | 'supervision',
|
|
469
|
+
/**
|
|
470
|
+
* What this facet is FOR — see `FacetSpawnDeps.key`. Two spawns with the same
|
|
471
|
+
* key get the same facet, so callers no longer each invent their own dedup
|
|
472
|
+
* (and `authorOutreach`, which had none, no longer opens a rival facet on a
|
|
473
|
+
* person the mind is already talking to).
|
|
474
|
+
*/
|
|
475
|
+
key?: string,
|
|
476
|
+
): { attention: 'available' | 'full', handle?: ExecutiveFacetHandle } {
|
|
397
477
|
void role
|
|
398
478
|
// Delegate to FacetSupervisor (R5-g-3), passing the current engine
|
|
399
479
|
// attachments. The supervisor owns the registry + attention budget and
|
|
400
480
|
// performs the throw-checks on bus / director / state ref.
|
|
401
481
|
return this._facetSupervisor.spawn( {
|
|
482
|
+
...( key ? { key: this._canonicalKey( key ) } : {} ),
|
|
402
483
|
bus: this._bus,
|
|
403
484
|
llmDirector: this._llmDirector,
|
|
404
485
|
stateRef: this._lastStateRef,
|
|
@@ -416,11 +497,100 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
416
497
|
} )
|
|
417
498
|
}
|
|
418
499
|
|
|
500
|
+
/**
|
|
501
|
+
* The facet already attending to `key`, if one is open — without spawning.
|
|
502
|
+
* See FacetSupervisor.handleFor.
|
|
503
|
+
*/
|
|
504
|
+
facetFor( key: string ): ExecutiveFacetHandle | undefined {
|
|
505
|
+
// Tolerant of BOTH id spaces, canonical first.
|
|
506
|
+
//
|
|
507
|
+
// A facet registered before its anchor was visible is still that person's
|
|
508
|
+
// open thread, and answering "nobody home" would open a second one beside it
|
|
509
|
+
// — the exact failure this key exists to prevent. Two ways that happens: a
|
|
510
|
+
// first message racing the tracker's mint, and a later name-merge that
|
|
511
|
+
// re-points one anchor onto another (KNOWN_ENTITY Phase 5, which is
|
|
512
|
+
// deliberately revisable and never destructively re-keys).
|
|
513
|
+
return this._facetSupervisor.handleFor( this._canonicalKey( key ) )
|
|
514
|
+
?? this._facetSupervisor.handleFor( key )
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* A facet key in the PERSON's id space, not the address's.
|
|
519
|
+
*
|
|
520
|
+
* `FacetSpawnDeps.key` is `<role>:<entityId>`, and the two sides of that
|
|
521
|
+
* contract disagreed about which space `entityId` lives in. Audition spawns a
|
|
522
|
+
* conversation facet keyed by `percept.speakerEntityId` — the transport address
|
|
523
|
+
* the message arrived on (`discord:1019…`) — while `authorOutreach` asks for
|
|
524
|
+
* `conversation:<anchor>` (`ke:1sqlkux`), because the executive resolves a
|
|
525
|
+
* person to their anchor before willing anything at them.
|
|
526
|
+
*
|
|
527
|
+
* So the dedup this key exists for never fired for a master-willed outreach.
|
|
528
|
+
* Every one spawned a transient rival facet on someone the mind was already
|
|
529
|
+
* talking to — which is what the key was added to prevent, and what its own
|
|
530
|
+
* comment claims it does. Observed live: a reply and an unprompted second
|
|
531
|
+
* answer to the same question 27 seconds apart, the second composed by a facet
|
|
532
|
+
* that could not see the first.
|
|
533
|
+
*
|
|
534
|
+
* Safe to resolve here because KnownEntityTracker mints the anchor on FIRST
|
|
535
|
+
* sight of an address, deterministically from it (R2), so a conversation is
|
|
536
|
+
* keyed the same way from its first message onward.
|
|
537
|
+
*/
|
|
538
|
+
private _canonicalKey( key: string ): string {
|
|
539
|
+
const cut = key.indexOf(':')
|
|
540
|
+
if( cut < 0 || !this._lastStateRef ) return key
|
|
541
|
+
const id = key.slice( cut + 1 )
|
|
542
|
+
const canon = canonicalOf( readAliases( this._lastStateRef.entities ), id )
|
|
543
|
+
return canon === id ? key : `${ key.slice( 0, cut ) }:${ canon }`
|
|
544
|
+
}
|
|
545
|
+
|
|
419
546
|
// ── CognitiveEngine interface ──────────────────────────────
|
|
420
547
|
|
|
421
548
|
subscribes(): string[] { return [ '*' ] }
|
|
422
549
|
publishes(): CognitiveEventSchema[] { return [] }
|
|
423
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Fold one resolved act into the mind's record of its own doing.
|
|
553
|
+
*
|
|
554
|
+
* `withheld` is kept distinct from `failed` deliberately: the mind formed the
|
|
555
|
+
* act and chose not to complete it, which is a judgement, not an inability.
|
|
556
|
+
* Collapsing them is the #123 mistake — a COO learning it was bad at speaking
|
|
557
|
+
* from the times it decided not to speak.
|
|
558
|
+
*/
|
|
559
|
+
private _recordAction( event: CognitiveEvent ): StateCommands | void {
|
|
560
|
+
const p = event.payload as {
|
|
561
|
+
actionType?: unknown; success?: unknown; targetEntityId?: unknown
|
|
562
|
+
description?: unknown; planId?: unknown; tick?: unknown
|
|
563
|
+
} | undefined
|
|
564
|
+
const type = typeof p?.actionType === 'string' ? p.actionType : undefined
|
|
565
|
+
if( !type ) return
|
|
566
|
+
|
|
567
|
+
const status: ActionStatus = event.type === 'action.withheld' ? 'withheld'
|
|
568
|
+
: p?.success === false ? 'failed'
|
|
569
|
+
: 'completed'
|
|
570
|
+
|
|
571
|
+
const record = actionRecordEntity({
|
|
572
|
+
type, status,
|
|
573
|
+
tick: typeof p?.tick === 'number' ? p.tick : 0,
|
|
574
|
+
...( typeof p?.targetEntityId === 'string' ? { targetEntityId: p.targetEntityId } : {} ),
|
|
575
|
+
// WHOLE. 120 here, 300 at the session log and 700 at the MCP boundary were
|
|
576
|
+
// three unexamined numbers cutting the same string, and the cut was the
|
|
577
|
+
// engine deciding how much of what a host said the mind was allowed to
|
|
578
|
+
// keep. What a host sends is consumed in its entirety; what the engine
|
|
579
|
+
// itself composes it may bound (see `PERCEPT_SUMMARY_CAP`), because there
|
|
580
|
+
// it is not destroying anyone's only copy.
|
|
581
|
+
//
|
|
582
|
+
// Safe to leave unbounded only because P2 split fate from facts: this
|
|
583
|
+
// carries a FATE, which is short by nature. The answer to a lookup leaves
|
|
584
|
+
// through `observation` and reaches the mind as a percept.
|
|
585
|
+
outcome: typeof p?.description === 'string' ? p.description : '',
|
|
586
|
+
...( typeof p?.planId === 'string' ? { planId: p.planId } : {} ),
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
// Pruning happens in `react`, which has frozen state to prune against; an
|
|
590
|
+
// event handler sees only the event.
|
|
591
|
+
return { set: [ record ] }
|
|
592
|
+
}
|
|
593
|
+
|
|
424
594
|
snapshot(): Record<string, unknown> {
|
|
425
595
|
return {
|
|
426
596
|
bufferSize: this._gatingState.salienceBuffer.length,
|
|
@@ -432,14 +602,46 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
432
602
|
onCognitiveEvent( event: CognitiveEvent ): StateCommands | void {
|
|
433
603
|
if( event.sourceEngine === this.name ) return
|
|
434
604
|
|
|
435
|
-
//
|
|
436
|
-
//
|
|
605
|
+
// What became of what I did.
|
|
606
|
+
//
|
|
607
|
+
// the outcomes section has existed — section, builder and type —
|
|
608
|
+
// since it was written, fed by `context.ts` scanning `decision.record` for
|
|
609
|
+
// an `actionStatus` that nothing in the engine has ever set. It rendered 0
|
|
610
|
+
// times across every prompt a live COO received. So the mind could see what
|
|
611
|
+
// it had SAID and never what it had DONE, and its only other record of
|
|
612
|
+
// acting is `## Recent Actions`, which is built from the executive's own
|
|
613
|
+
// DECISIONS. A record of acting that contains only intentions cannot
|
|
614
|
+
// distinguish an intention from an act — and one did not, reporting a spec
|
|
615
|
+
// as drafted and sent when it had no effectors and had sent nothing.
|
|
616
|
+
//
|
|
617
|
+
// Written here because these events already arrive here (`subscribes()` is
|
|
618
|
+
// `['*']`) and the returned commands are drained onto state by the
|
|
619
|
+
// orchestrator. See `action.record.ts`.
|
|
620
|
+
if( event.type === 'action.outcome' || event.type === 'action.withheld')
|
|
621
|
+
return this._recordAction( event )
|
|
622
|
+
|
|
623
|
+
// Spare attention scales the facet allowance within the persona's ceiling.
|
|
437
624
|
if( event.type === 'attention.state.changed'){
|
|
438
625
|
const p = event.payload as { freeFraction: number }
|
|
439
626
|
this._facetSupervisor.setAttentionState( p.freeFraction )
|
|
440
627
|
return
|
|
441
628
|
}
|
|
442
629
|
|
|
630
|
+
// The facet legs. These MUST be dispatched here rather than through their own
|
|
631
|
+
// `bus.subscribe` calls: the bus keeps one subscription per engineId, and the
|
|
632
|
+
// orchestrator's `subscribe( engine.name, engine.subscribes(), … )` runs after
|
|
633
|
+
// `attachBus`, so anything registered separately is silently replaced. This
|
|
634
|
+
// `['*']` subscription is the engine's only live one.
|
|
635
|
+
if( event.type === 'executive.facet.sync'){
|
|
636
|
+
this._onFacetSync( event )
|
|
637
|
+
return
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if( event.type === 'executive.facet.handoff'){
|
|
641
|
+
this._onFacetHandoff( event )
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
443
645
|
// GWT salience-based buffer
|
|
444
646
|
( event.salience ?? 0 ) >= WORKSPACE_THRESHOLD
|
|
445
647
|
&& this._gatingState.salienceBuffer.push( {
|
|
@@ -462,6 +664,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
462
664
|
state: ReadonlySimulationState,
|
|
463
665
|
context: SimulationContext,
|
|
464
666
|
): Promise<EngineResult> {
|
|
667
|
+
this._currentTick = tick as unknown as number
|
|
465
668
|
this._deferred.flush( state, tick as unknown as number )
|
|
466
669
|
this._deferred.markReactTick( tick as unknown as number )
|
|
467
670
|
// Per-tick facet pump: refresh every facet's state ref to THIS tick's frozen
|
|
@@ -469,7 +672,85 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
469
672
|
// Sits at a fixed point in the serial engine order — the issue-side twin of
|
|
470
673
|
// the CompletionInbox drain in Phase 2. See .TODO/FACET_REPLAY_DETERMINISM.md.
|
|
471
674
|
this._facetSupervisor.pump( state )
|
|
472
|
-
|
|
675
|
+
|
|
676
|
+
const result = await super.react( delta, tick, state, context )
|
|
677
|
+
|
|
678
|
+
// Reasoning facets occupy attention. Merged into whatever this tick already
|
|
679
|
+
// produced — AsyncEngine.react returns commands on every tick, reasoning or not,
|
|
680
|
+
// so the allocator sees the cost land the tick it is incurred.
|
|
681
|
+
const focus = this._facetAttentionDemands( state, tick as unknown as number )
|
|
682
|
+
if( focus.set.length || focus.delete.length ){
|
|
683
|
+
result.commands ??= {}
|
|
684
|
+
result.commands.set = [ ...( result.commands.set ?? [] ), ...focus.set ]
|
|
685
|
+
result.commands.delete = [ ...( result.commands.delete ?? [] ), ...focus.delete ]
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Hold as many acts as the prompt shows and no more. Done here rather than
|
|
689
|
+
// in the event handler because pruning needs frozen state to prune against.
|
|
690
|
+
const stale = staleActionRecordIds( state.entities as never )
|
|
691
|
+
if( stale.length > 0 ){
|
|
692
|
+
result.commands ??= {}
|
|
693
|
+
result.commands.delete = [ ...( result.commands.delete ?? [] ), ...stale ]
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return result
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* What the mind is attending to because a facet is reasoning about it, as
|
|
701
|
+
* `attention.demand` entities the AttentionAllocator allocates real capacity
|
|
702
|
+
* against (`_extractSalienceSignals` reads this type; `costPerFocus` is then
|
|
703
|
+
* charged against the same 100-unit budget as perceptual foci).
|
|
704
|
+
*
|
|
705
|
+
* This closes a loop that was open in one direction only: the allocator's
|
|
706
|
+
* `freeFraction` scaled the facet budget, but facets never appeared in
|
|
707
|
+
* `_activeFocus`, so holding three conversations reported exactly as much spare
|
|
708
|
+
* attention as holding none. The budget was being scaled by a signal blind to the
|
|
709
|
+
* thing it was bounding.
|
|
710
|
+
*
|
|
711
|
+
* `urgency` sits below 1 on purpose: a live conversation is a genuine claim on
|
|
712
|
+
* attention but must not automatically outrank every percept — the allocator sorts
|
|
713
|
+
* candidates by salience into `maxFoci` slots, and a facet that always won would
|
|
714
|
+
* starve perception. Only BUSY facets are charged; an open-but-quiet thread is one
|
|
715
|
+
* the mind is in, not one it is attending to.
|
|
716
|
+
*/
|
|
717
|
+
private _facetAttentionDemands(
|
|
718
|
+
state: ReadonlySimulationState,
|
|
719
|
+
tick: number,
|
|
720
|
+
): { set: EntityInput[]; delete: string[] } {
|
|
721
|
+
const busy = this._facetSupervisor.busyFacetIds()
|
|
722
|
+
const set: EntityInput[] = []
|
|
723
|
+
const live = new Set<string>()
|
|
724
|
+
|
|
725
|
+
for( const facetId of busy ){
|
|
726
|
+
const id = `facet-attending-${facetId}`
|
|
727
|
+
live.add( id )
|
|
728
|
+
const subject = this._facetSubjects.get( facetId )
|
|
729
|
+
set.push({
|
|
730
|
+
id,
|
|
731
|
+
type: 'attention.demand',
|
|
732
|
+
metadata: {
|
|
733
|
+
urgency: FACET_ATTENTION_URGENCY,
|
|
734
|
+
source: 'executive-facet',
|
|
735
|
+
facetId,
|
|
736
|
+
...( subject?.entityId ? { subjectEntityId: subject.entityId } : {} ),
|
|
737
|
+
...( subject?.name ? { subjectName: subject.name } : {} ),
|
|
738
|
+
// NOT `generatesGoal` — GoalManager turns flagged demands into goals, and
|
|
739
|
+
// "I am talking to someone" is a state, not something to pursue.
|
|
740
|
+
tick,
|
|
741
|
+
},
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Release the attention a facet held once it stops reasoning (or is reaped).
|
|
746
|
+
const del: string[] = []
|
|
747
|
+
for( const [ id, e ] of state.entities )
|
|
748
|
+
if( e.type === 'attention.demand'
|
|
749
|
+
&& ( e.metadata as Record<string, unknown> | undefined )?.['source'] === 'executive-facet'
|
|
750
|
+
&& !live.has( id ) )
|
|
751
|
+
del.push( id )
|
|
752
|
+
|
|
753
|
+
return { set, delete: del }
|
|
473
754
|
}
|
|
474
755
|
|
|
475
756
|
protected override shouldAct(
|
|
@@ -483,6 +764,12 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
483
764
|
this._summarizerRestored = true
|
|
484
765
|
}
|
|
485
766
|
|
|
767
|
+
// Restore the deliberation cache from state on first tick (parallels the summary)
|
|
768
|
+
if( this._cache && !this._cacheRestored ){
|
|
769
|
+
this._restoreDeliberationCache( state )
|
|
770
|
+
this._cacheRestored = true
|
|
771
|
+
}
|
|
772
|
+
|
|
486
773
|
// Read runtime config overrides from state
|
|
487
774
|
const rtConfig = readRuntimeConfig( state, {
|
|
488
775
|
executiveInterval: this._executiveInterval,
|
|
@@ -522,7 +809,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
522
809
|
// Evaluate gating
|
|
523
810
|
const gatingDeps: GatingDependencies = {
|
|
524
811
|
generativeModel: this._generativeModel,
|
|
525
|
-
pendingMessages: this._messageQueue.pendingMessages,
|
|
526
812
|
hasPendingWork: this.hasPendingWork
|
|
527
813
|
}
|
|
528
814
|
|
|
@@ -531,6 +817,26 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
531
817
|
// Always update counters, even when not activating
|
|
532
818
|
updateGatingState( this._gatingState, state, tick, result.shouldActivate, result.cleanedBuffer )
|
|
533
819
|
|
|
820
|
+
// A reasoning pass that never settles holds this gate shut until the
|
|
821
|
+
// AsyncEngine's stale prune drops it 600 ticks later, and every one of those
|
|
822
|
+
// ticks was silent — the mind simply stopped deliberating and nothing said
|
|
823
|
+
// why. Warn ONCE, at the point a pass has outlived any healthy one; warning
|
|
824
|
+
// every tick would bury the line it exists to surface.
|
|
825
|
+
if( result.reason === 'hasPendingWork'){
|
|
826
|
+
this._mutedSince ??= tick
|
|
827
|
+
// Once per episode, not once at an exact tick: react() is not guaranteed to
|
|
828
|
+
// be called on every tick, and an `=== threshold` test that the clock steps
|
|
829
|
+
// over never fires at all — a silent warning about a silence.
|
|
830
|
+
if( !this._mutedWarned && tick - this._mutedSince >= this._executiveInterval * 3 ){
|
|
831
|
+
this._mutedWarned = true
|
|
832
|
+
logger.warn(
|
|
833
|
+
`[executive] no cycle for ${ tick - this._mutedSince } ticks — a reasoning pass ` +
|
|
834
|
+
`has not settled and is holding the seat (tick=${tick})`
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
else { this._mutedSince = null; this._mutedWarned = false }
|
|
839
|
+
|
|
534
840
|
if( result.shouldActivate )
|
|
535
841
|
logger.info(`[executive] activating — reason: ${result.reason} (tick=${tick})`)
|
|
536
842
|
|
|
@@ -564,7 +870,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
564
870
|
stream: IntermediateStream,
|
|
565
871
|
): Promise<unknown> {
|
|
566
872
|
this._lastStateRef = state
|
|
567
|
-
this._messageQueue.pendingCallStartTick = state.tick
|
|
568
873
|
|
|
569
874
|
// Mark which buffer entries this cycle consumes. Anything pushed after this
|
|
570
875
|
// point arrives during the LLM call and was never part of the broadcast, so
|
|
@@ -574,6 +879,47 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
574
879
|
// Update all facets with the latest state reference
|
|
575
880
|
this._facetSupervisor.broadcastStateRef( state )
|
|
576
881
|
|
|
882
|
+
// ── DeliberationCache fast path (optional) ───────────────
|
|
883
|
+
// A learned interpolation of past executive outputs. When a highly similar,
|
|
884
|
+
// highly competent precedent exists we skip the LLM entirely. R2-safe: the
|
|
885
|
+
// fingerprint, retrieval and composition are pure functions of the frozen
|
|
886
|
+
// state and the cache's own snapshotted contents. The mandatory bookkeeping
|
|
887
|
+
// above (buffer marking, facet broadcast) has already run, so a cache hit and
|
|
888
|
+
// a slow tick leave the facet/buffer discipline in the same state.
|
|
889
|
+
this._pendingVerify = null
|
|
890
|
+
if( this._cache ){
|
|
891
|
+
const cacheTick = footprint.tickObserved as unknown as number
|
|
892
|
+
this._cache.decay()
|
|
893
|
+
const fp = extractFingerprint( state )
|
|
894
|
+
const cacheResult = this._cache.retrieve( fp, cacheTick )
|
|
895
|
+
this._lastCacheHit = cacheResult.hit
|
|
896
|
+
this._lastCacheConfidence = cacheResult.confidence
|
|
897
|
+
this._lastCacheNeighborCount = cacheResult.neighbors.length
|
|
898
|
+
|
|
899
|
+
if( cacheResult.hit && cacheResult.output ){
|
|
900
|
+
if( !this._cache.shouldVerify() ){
|
|
901
|
+
// FAST PATH — return the composed output; this tick spends no tokens.
|
|
902
|
+
stream.report('executive_complete', {
|
|
903
|
+
actionCount: cacheResult.output.actions.length,
|
|
904
|
+
planCount: 0,
|
|
905
|
+
newBeliefCount: cacheResult.output.newBeliefs?.length ?? 0,
|
|
906
|
+
hasIntrospection: false,
|
|
907
|
+
hasNarrative: false,
|
|
908
|
+
} )
|
|
909
|
+
logger.info(
|
|
910
|
+
`[executive] ⚡ cache hit tick=${cacheTick} ` +
|
|
911
|
+
`ρ=${cacheResult.confidence.toFixed( 3 )} neighbors=${cacheResult.neighbors.length}`
|
|
912
|
+
)
|
|
913
|
+
return cacheResult.output
|
|
914
|
+
}
|
|
915
|
+
// Hit selected for verification — run the LLM and score the cache against it.
|
|
916
|
+
this._pendingVerify = {
|
|
917
|
+
fingerprint: fp,
|
|
918
|
+
cachedActionTypes: cacheResult.output.actions.map( a => a.type ),
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
577
923
|
// Build executive context using PromptFactory's helper
|
|
578
924
|
const execContext = await PromptFactory.buildFreshContext( {
|
|
579
925
|
workingMemory: this._workingMemory,
|
|
@@ -607,7 +953,13 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
607
953
|
priorConfidence: this._lastExecutiveOutput?.confidence ?? 0.5,
|
|
608
954
|
novelty: state.metrics.get('perception.novelty') ?? 0,
|
|
609
955
|
stressLoad: state.metrics.get('stress.load') ?? 0,
|
|
610
|
-
|
|
956
|
+
// Something a conversation surfaced is waiting on the master and has not
|
|
957
|
+
// been dealt with. This used to read the pending-message queue, which was
|
|
958
|
+
// never filled — so the effort gate's `pending_reply` term was structurally
|
|
959
|
+
// zero for every master that has ever run. Escalations are the master's real
|
|
960
|
+
// version of "someone is waiting on me": the facets do the replying, the
|
|
961
|
+
// master owns what they escalate.
|
|
962
|
+
hasPendingMessage: this._escalations.size > 0,
|
|
611
963
|
}, deliberateThreshold )
|
|
612
964
|
stream.report('process_selected', {
|
|
613
965
|
process: processSelection.process,
|
|
@@ -650,11 +1002,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
650
1002
|
state,
|
|
651
1003
|
qualityModulation,
|
|
652
1004
|
epistemicUncertainty,
|
|
653
|
-
|
|
654
|
-
focus,
|
|
1005
|
+
focus,
|
|
655
1006
|
deps: promptDeps,
|
|
656
1007
|
recentActionTypes: [ ...this._recentActionTypes ],
|
|
657
1008
|
mode: 'master',
|
|
1009
|
+
activeConversations: this._activeConversations(),
|
|
658
1010
|
outputFormat: PromptFactory.buildIdeationFormatInstruction(),
|
|
659
1011
|
} )
|
|
660
1012
|
// Propose temperature scales with the creativity trait (TODO #4): a creative Will
|
|
@@ -668,7 +1020,13 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
668
1020
|
ideationUserMessage,
|
|
669
1021
|
tick: state.tick,
|
|
670
1022
|
proposeTemperature,
|
|
671
|
-
meta: {
|
|
1023
|
+
meta: {
|
|
1024
|
+
category: 'executive',
|
|
1025
|
+
attribute: 'master',
|
|
1026
|
+
process: 'ideation',
|
|
1027
|
+
function: '-',
|
|
1028
|
+
demand: processSelection.effortScore
|
|
1029
|
+
},
|
|
672
1030
|
} )
|
|
673
1031
|
logger.info(
|
|
674
1032
|
`[executive] ◆ deliberate propose tick=${state.tick} ` +
|
|
@@ -688,11 +1046,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
688
1046
|
state,
|
|
689
1047
|
qualityModulation,
|
|
690
1048
|
epistemicUncertainty,
|
|
691
|
-
pendingMessages: [ ...this._messageQueue.pendingMessages ],
|
|
692
1049
|
focus,
|
|
693
1050
|
deps: promptDeps,
|
|
694
1051
|
recentActionTypes: [ ...this._recentActionTypes ],
|
|
695
1052
|
mode: 'master',
|
|
1053
|
+
activeConversations: this._activeConversations(),
|
|
696
1054
|
ideationCandidates
|
|
697
1055
|
} )
|
|
698
1056
|
|
|
@@ -706,7 +1064,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
706
1064
|
// D2: context counts for per-tick cognitive state snapshot
|
|
707
1065
|
workingMemoryItems: execContext.workingMemory.length,
|
|
708
1066
|
goalCount: execContext.goals.length,
|
|
709
|
-
pendingMessages: this._messageQueue.pendingMessages.length,
|
|
710
1067
|
beliefCount: execContext.beliefs.length,
|
|
711
1068
|
beliefsOmitted: execContext.beliefsOmitted,
|
|
712
1069
|
promptPath: this._llmDirector?.writeDebugPrompt( state.tick, systemPrompt, userMessage ) ?? ''
|
|
@@ -724,7 +1081,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
724
1081
|
// MODEL_ROUTING W0 — the effort gate already weighed this tick's demand
|
|
725
1082
|
// (uncertainty, prior confidence, novelty, a pending reply, stress load);
|
|
726
1083
|
// forward it rather than inventing a second measure of the same thing.
|
|
727
|
-
const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master',
|
|
1084
|
+
const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master', process: 'decision', function: '-', demand: processSelection.effortScore }
|
|
728
1085
|
const result = this._chunkBroadcaster
|
|
729
1086
|
? await this._llmDirector.callStream( systemPrompt, userMessage, state.tick, this._chunkBroadcaster, undefined, masterMeta )
|
|
730
1087
|
: await this._llmDirector.call( systemPrompt, userMessage, state.tick, undefined, masterMeta )
|
|
@@ -794,11 +1151,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
794
1151
|
plansCount: executiveOutput.plans?.length ?? 0,
|
|
795
1152
|
goalsNew: executiveOutput.newGoals ?? [],
|
|
796
1153
|
goalsAbandon: executiveOutput.goalsToAbandon ?? [],
|
|
797
|
-
replies: ( executiveOutput.conversationReplies ?? [] ).map( r => ( {
|
|
798
|
-
targetEntityId: r.targetEntityId,
|
|
799
|
-
targetEntityName: r.targetEntityName,
|
|
800
|
-
messages: r.messages,
|
|
801
|
-
} ) ),
|
|
802
1154
|
hasIntrospection: !!executiveOutput.introspection,
|
|
803
1155
|
hasNarrative: !!executiveOutput.narrative
|
|
804
1156
|
} )
|
|
@@ -811,6 +1163,23 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
811
1163
|
hasNarrative: !!executiveOutput.narrative
|
|
812
1164
|
} )
|
|
813
1165
|
|
|
1166
|
+
// ── DeliberationCache learning (slow path) ───────────────
|
|
1167
|
+
// extractFingerprint is pure over the frozen `state`, so recomputing here
|
|
1168
|
+
// yields the identical vector used on entry — no need to thread it through.
|
|
1169
|
+
if( this._cache ){
|
|
1170
|
+
const learnTick = footprint.tickObserved as unknown as number
|
|
1171
|
+
if( this._pendingVerify ){
|
|
1172
|
+
const match = this._actionTypesMatch(
|
|
1173
|
+
this._pendingVerify.cachedActionTypes,
|
|
1174
|
+
executiveOutput.actions.map( a => a.type ),
|
|
1175
|
+
)
|
|
1176
|
+
this._cache.updateCompetence( this._pendingVerify.fingerprint, match ? 1 : 0, learnTick )
|
|
1177
|
+
this._pendingVerify = null
|
|
1178
|
+
} else {
|
|
1179
|
+
this._cache.learn( extractFingerprint( state ), executiveOutput, learnTick )
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
814
1183
|
return executiveOutput
|
|
815
1184
|
}
|
|
816
1185
|
|
|
@@ -889,7 +1258,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
889
1258
|
)
|
|
890
1259
|
|
|
891
1260
|
// ── Flush pending escalation percepts ──────────────────────
|
|
892
|
-
// Convert buffered
|
|
1261
|
+
// Convert buffered executive.facet.handoff events into high-salience
|
|
893
1262
|
// percept entities so Exteroception surfaces them as
|
|
894
1263
|
// "## Percepts (What I Notice)" on the NEXT master cycle.
|
|
895
1264
|
// The master sees them as environmental signals — not as messages to reply to.
|
|
@@ -976,14 +1345,57 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
976
1345
|
} )
|
|
977
1346
|
|
|
978
1347
|
// Clear processed messages
|
|
979
|
-
this._messageQueue.clearProcessedMessages()
|
|
980
1348
|
|
|
981
|
-
// Merge escalation percepts into final commands
|
|
1349
|
+
// Merge escalation percepts into the final commands.
|
|
1350
|
+
//
|
|
1351
|
+
// `_reconcileUndertakings` used to sit here, discharging promises the engine
|
|
1352
|
+
// judged kept and dropping restatements. Both are gone with the undertaking
|
|
1353
|
+
// harness: a promise is no longer a percept, so there is nothing to
|
|
1354
|
+
// reconcile. What the facet declared rides the sync tract to the master's own
|
|
1355
|
+
// prompt, and what to do about it — including whether it is still worth
|
|
1356
|
+
// doing — is the mind's, through the goal machinery it already has.
|
|
982
1357
|
if( escalationPercepts.length ){
|
|
983
1358
|
commands.set ??= []
|
|
984
1359
|
commands.set.push( ...escalationPercepts )
|
|
985
1360
|
}
|
|
986
1361
|
|
|
1362
|
+
// ── Persist deliberation cache (mirrors the rolling summary) ──
|
|
1363
|
+
// Written each executive cycle so the learned patterns survive snapshot/
|
|
1364
|
+
// restore (R2). Bounded by maxPatterns; the stored output objects are only
|
|
1365
|
+
// ever read back, so state's deep-freeze of them is harmless.
|
|
1366
|
+
if( this._cache ){
|
|
1367
|
+
commands.set ??= []
|
|
1368
|
+
commands.set.push( {
|
|
1369
|
+
id: 'executive-deliberation-cache',
|
|
1370
|
+
type: 'executive.cache',
|
|
1371
|
+
metadata: { snapshot: this._cache.snapshot() },
|
|
1372
|
+
} )
|
|
1373
|
+
|
|
1374
|
+
// Surface cache state the R2-safe way: metrics ride the returned
|
|
1375
|
+
// StateCommands (never a frozen-state mutation), and hit/miss events
|
|
1376
|
+
// publish from this committed path so faculties can react to how
|
|
1377
|
+
// automatic the Will is becoming.
|
|
1378
|
+
const total = this._cache.hitCount + this._cache.missCount
|
|
1379
|
+
commands.metrics ??= []
|
|
1380
|
+
commands.metrics.push(
|
|
1381
|
+
[ 'cache.hit', this._lastCacheHit ? 1 : 0 ],
|
|
1382
|
+
[ 'cache.confidence', this._lastCacheConfidence ],
|
|
1383
|
+
[ 'cache.hit_rate', total > 0 ? this._cache.hitCount / total : 0 ],
|
|
1384
|
+
[ 'cache.size', this._cache.size ],
|
|
1385
|
+
)
|
|
1386
|
+
this._bus?.publish( this._lastCacheHit
|
|
1387
|
+
? {
|
|
1388
|
+
type: 'cache.hit', version: 1, sourceEngine: this.name,
|
|
1389
|
+
salience: this._lastCacheConfidence,
|
|
1390
|
+
payload: { confidence: this._lastCacheConfidence, neighborCount: this._lastCacheNeighborCount },
|
|
1391
|
+
}
|
|
1392
|
+
: {
|
|
1393
|
+
type: 'cache.miss', version: 1, sourceEngine: this.name,
|
|
1394
|
+
salience: 0.3,
|
|
1395
|
+
payload: { confidence: this._lastCacheConfidence },
|
|
1396
|
+
} )
|
|
1397
|
+
}
|
|
1398
|
+
|
|
987
1399
|
// Track entities modified
|
|
988
1400
|
if( commands.set?.length )
|
|
989
1401
|
for( const entity of commands.set )
|
|
@@ -1001,122 +1413,288 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1001
1413
|
|
|
1002
1414
|
// ── Private helpers ────────────────────────────────────────
|
|
1003
1415
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1416
|
+
/**
|
|
1417
|
+
* The people the mind is in conversation with right now, newest first.
|
|
1418
|
+
*
|
|
1419
|
+
* Pruned against the supervisor's live facets on every read: a reaped facet is a
|
|
1420
|
+
* conversation that has ended, and a master that still believes it is mid-thread
|
|
1421
|
+
* with someone reasons about a table that is no longer there.
|
|
1422
|
+
*/
|
|
1423
|
+
private _activeConversations(): { entityId: string; name?: string; sinceTick: number }[] {
|
|
1424
|
+
const live = this._facetSupervisor.liveFacetIds()
|
|
1425
|
+
for( const id of [ ...this._facetSubjects.keys() ] )
|
|
1426
|
+
if( !live.has( id ) ) this._facetSubjects.delete( id )
|
|
1427
|
+
|
|
1428
|
+
return [ ...this._facetSubjects.values() ]
|
|
1429
|
+
.sort( ( a, b ) => b.tick - a.tick )
|
|
1430
|
+
.map( s => ({
|
|
1431
|
+
entityId: s.entityId,
|
|
1432
|
+
...( s.name ? { name: s.name } : {} ),
|
|
1433
|
+
sinceTick: s.tick,
|
|
1434
|
+
...( s.concluded ? { concluded: s.concluded } : {} ),
|
|
1435
|
+
...( s.promised?.length ? { promised: s.promised } : {} ),
|
|
1436
|
+
}) )
|
|
1437
|
+
}
|
|
1021
1438
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1439
|
+
/**
|
|
1440
|
+
* Facet sync — remember WHO each facet is with, and wake the master.
|
|
1441
|
+
*
|
|
1442
|
+
* Reached from `onCognitiveEvent`, NOT from its own `bus.subscribe`. The bus
|
|
1443
|
+
* stores one subscription per engineId (`_subscriptions.set( engineId, … )`),
|
|
1444
|
+
* so a second `subscribe(this.name, …)` silently REPLACES the first — and the
|
|
1445
|
+
* orchestrator registers `subscribe( engine.name, engine.subscribes(), … )`
|
|
1446
|
+
* after `attachBus`, which replaced everything registered here. Two dedicated
|
|
1447
|
+
* handlers used to be installed at this point; the second overwrote the first
|
|
1448
|
+
* and the orchestrator then overwrote that, so neither ever ran. The escalation
|
|
1449
|
+
* leg had been dead in production for its whole life: a facet could escalate,
|
|
1450
|
+
* the audition engine published, and nothing was listening.
|
|
1451
|
+
*/
|
|
1452
|
+
/**
|
|
1453
|
+
* Retire undertakings the mind has already honoured, and refuse to restate one
|
|
1454
|
+
* it is already carrying.
|
|
1455
|
+
*
|
|
1456
|
+
* An undertaking percept says, in the first person, "I said I would reach X and
|
|
1457
|
+
* nothing has gone to them yet". That sentence has to stop being true at some
|
|
1458
|
+
* point, and nothing made it stop. Measured on a live Will: SEVEN of them
|
|
1459
|
+
* accumulated in state, every one still asserting nothing had been sent, while
|
|
1460
|
+
* a `conversation.sent` to that person sat right beside them. She read seven
|
|
1461
|
+
* standing unfulfilled promises every cycle and dutifully sent the same message
|
|
1462
|
+
* again, five times in five minutes and once more in the next session — the
|
|
1463
|
+
* percept meant to stop her forgetting a promise was making her unable to
|
|
1464
|
+
* believe she had kept it.
|
|
1465
|
+
*
|
|
1466
|
+
* Discharged by EVIDENCE, not by a timer: a `conversation.sent` to that target,
|
|
1467
|
+
* written no earlier than the undertaking, means the contact happened. That
|
|
1468
|
+
* record is durable and snapshots with the state, so the discharge survives a
|
|
1469
|
+
* restart exactly as the promise does — which the tick-scoped satiation in
|
|
1470
|
+
* `enactionFootprint` deliberately cannot.
|
|
1471
|
+
*
|
|
1472
|
+
* It stays a decision, not an erasure. Retiring the percept removes the standing
|
|
1473
|
+
* claim that the words are unsent; whether to say more to that person is then an
|
|
1474
|
+
* ordinary competition like any other.
|
|
1475
|
+
*/
|
|
1476
|
+
private _onFacetSync( event: CognitiveEvent ): void {
|
|
1477
|
+
const payload = event.payload as {
|
|
1478
|
+
facetId?: string
|
|
1479
|
+
reasoning?: string
|
|
1480
|
+
confidence?: number
|
|
1481
|
+
tick?: number
|
|
1482
|
+
subjectEntityId?: string
|
|
1483
|
+
subjectName?: string
|
|
1484
|
+
}
|
|
1033
1485
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1486
|
+
// Remember WHO this facet is with, and WHAT it worked out there. The second
|
|
1487
|
+
// half is the return leg: `reasoning` has always been on this payload and was
|
|
1488
|
+
// read into a local and then dropped, so the master learned that its attention
|
|
1489
|
+
// had been engaged and never what it concluded.
|
|
1490
|
+
//
|
|
1491
|
+
// Carried whole, not clipped. For the master this is the only copy — the
|
|
1492
|
+
// facet's own history is in the facet, and once that conversation ends the
|
|
1493
|
+
// thinking is gone. Live facets are few (one per open conversation), so this
|
|
1494
|
+
// is bounded by how many people the mind is talking to, not by traffic.
|
|
1495
|
+
if( payload.facetId && payload.subjectEntityId ){
|
|
1496
|
+
const prior = this._facetSubjects.get( payload.facetId )
|
|
1497
|
+
this._facetSubjects.set( payload.facetId, {
|
|
1498
|
+
entityId: payload.subjectEntityId,
|
|
1499
|
+
...( payload.subjectName ? { name: payload.subjectName } : {} ),
|
|
1500
|
+
tick: payload.tick ?? this._lastExecutiveTick ?? 0,
|
|
1501
|
+
...( payload.reasoning ? { concluded: payload.reasoning } : {} ),
|
|
1502
|
+
// Promises ride along rather than being re-derived: a commitment the facet
|
|
1503
|
+
// DECLARED is a fact, while the same thing read back out of prose is a
|
|
1504
|
+
// guess. Preserved across syncs until the master has read them.
|
|
1505
|
+
...( prior?.promised?.length ? { promised: prior.promised } : {} ),
|
|
1506
|
+
})
|
|
1507
|
+
}
|
|
1038
1508
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1509
|
+
// Unconditional (not gated on WORKSPACE_THRESHOLD like ordinary traffic):
|
|
1510
|
+
// a facet reporting back is the master's own attention returning, and the
|
|
1511
|
+
// point of the spike is that it re-evaluates rather than waiting out its
|
|
1512
|
+
// interval.
|
|
1513
|
+
this._gatingState.salienceBuffer.push({
|
|
1514
|
+
event,
|
|
1515
|
+
tick: payload.tick ?? event.logicalTime ?? 0,
|
|
1516
|
+
})
|
|
1517
|
+
|
|
1518
|
+
logger.info(
|
|
1519
|
+
`[executive] master received facet sync from ${payload.facetId}` +
|
|
1520
|
+
( payload.subjectName || payload.subjectEntityId
|
|
1521
|
+
? ` (with ${payload.subjectName ?? payload.subjectEntityId})` : '') +
|
|
1522
|
+
` (confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1044
1523
|
)
|
|
1524
|
+
}
|
|
1045
1525
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1526
|
+
/**
|
|
1527
|
+
* A focused part of me surfaced something the singular seat owns — work to plan
|
|
1528
|
+
* (`escalation`) or an intention toward a third party (`undertaking`).
|
|
1529
|
+
*
|
|
1530
|
+
* ONE handler for every facet type. This was `_onAuditionTaskSignal`, listening
|
|
1531
|
+
* on a topic named for one sense engine and typed with one sense engine's nouns
|
|
1532
|
+
* (`entityId`, `threadId`), which meant a planning, supervision or deliberation
|
|
1533
|
+
* facet had no way to hand anything up at all. See EscalationBuffer for the full
|
|
1534
|
+
* rationale; new kinds go in `HandoffBody`, not in a new topic and a new handler
|
|
1535
|
+
* beside this one.
|
|
1536
|
+
*
|
|
1537
|
+
* Master stays out of the reply path entirely:
|
|
1538
|
+
* • The facet has already said (or will say) whatever the person in front of
|
|
1539
|
+
* it needed to hear.
|
|
1540
|
+
* • The master's job is purely cognitive: create a [PLANS] block, update
|
|
1541
|
+
* goals, reflect, or decide whether it still means to make that contact.
|
|
1542
|
+
* Any follow-up communication flows through the agency competition —
|
|
1543
|
+
* NEVER via [REPLY].
|
|
1544
|
+
*
|
|
1545
|
+
* Buffered rather than written directly: state is read-only here, so
|
|
1546
|
+
* `EscalationBuffer.drainToPercepts()` emits it as a StateCommand on the next
|
|
1547
|
+
* master cycle, where Exteroception surfaces it under "## Percepts (What I Notice)".
|
|
1548
|
+
*/
|
|
1549
|
+
private _onFacetHandoff( event: CognitiveEvent ): void {
|
|
1550
|
+
const payload = event.payload as {
|
|
1551
|
+
facetId?: string
|
|
1552
|
+
subjectEntityId?: string
|
|
1553
|
+
subjectName?: string
|
|
1554
|
+
threadId?: string
|
|
1555
|
+
confidence?: number
|
|
1556
|
+
tick?: number
|
|
1557
|
+
body: HandoffBody
|
|
1558
|
+
}
|
|
1559
|
+
if( !payload?.body?.kind ) return
|
|
1560
|
+
|
|
1561
|
+
// NOW, not the last time the master happened to run.
|
|
1051
1562
|
//
|
|
1052
|
-
//
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
|
|
1563
|
+
// This was `this._lastExecutiveTick`, so an undertaking formed at tick 900 was
|
|
1564
|
+
// stamped 780 if that was the last master cycle. `_reconcileUndertakings`
|
|
1565
|
+
// discharges on "contacted at or after `madeAt`", which means a message sent at
|
|
1566
|
+
// tick 800 — BEFORE the promise existed — retired it. Over-discharge is the safe
|
|
1567
|
+
// direction, which is why it never surfaced as a symptom, but it made the promise
|
|
1568
|
+
// unfalsifiable: it could be marked kept by something that happened first.
|
|
1569
|
+
const tick = payload.tick ?? this._currentTick
|
|
1570
|
+
|
|
1571
|
+
// AN UNDERTAKING RIDES THE TRACT, NOT A HARNESS OF ITS OWN.
|
|
1059
1572
|
//
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1573
|
+
// It used to become a percept plus a standing ideomotor intent plus a bespoke
|
|
1574
|
+
// discharge rule — the engine deciding how hard a promise pulls and when it
|
|
1575
|
+
// counts as kept. That is judgement, and judgement is the mind's. What is not
|
|
1576
|
+
// the mind's to supply is the FACT crossing from where its attention was to
|
|
1577
|
+
// where its singular seat is; that crossing is anatomy.
|
|
1578
|
+
//
|
|
1579
|
+
// So it is filed with what the facet concluded and rendered to the master at
|
|
1580
|
+
// its next cycle. Reading that it promised someone something and has not done
|
|
1581
|
+
// it, the master may form a goal — which it can already do, and which the goal
|
|
1582
|
+
// machinery then lifts, plans and completes. Or it may not, and that is a
|
|
1583
|
+
// decision rather than a mechanism failing.
|
|
1584
|
+
//
|
|
1585
|
+
// The old route could not have worked anyway: a percept is swept at +2 ticks
|
|
1586
|
+
// and decays out of working memory at about +9, while the master's interval
|
|
1587
|
+
// is 15. The notice was usually gone before the seat it was addressed to ran.
|
|
1588
|
+
// Spike the salience buffer FIRST, for both kinds. The master must NOT be
|
|
1589
|
+
// handed the inbound as a message to answer — that would make it produce a
|
|
1590
|
+
// [REPLY], duplicating the facet's and breaking the facet/master boundary.
|
|
1591
|
+
// (The queue that once carried inbound this way was removed in #114: nothing
|
|
1592
|
+
// ever filled it.) Done before the undertaking branch returns, so a promise
|
|
1593
|
+
// still pulls the master's next cycle forward rather than waiting out the
|
|
1594
|
+
// interval — which matters more now that the promise is what it will read.
|
|
1595
|
+
this._gatingState.salienceBuffer.push({ event, tick })
|
|
1596
|
+
|
|
1597
|
+
const from = payload.subjectName ?? payload.subjectEntityId ?? payload.facetId ?? 'a focus'
|
|
1598
|
+
|
|
1599
|
+
if( payload.body.kind === 'undertaking'){
|
|
1600
|
+
const body = payload.body
|
|
1601
|
+
// Keyed by facet because that is what the master's view of its open threads
|
|
1602
|
+
// is keyed by. A handoff with no facet id has nowhere to file — say so
|
|
1603
|
+
// rather than dropping it quietly, which is how a promise disappears with
|
|
1604
|
+
// nobody able to tell whether it was ever made.
|
|
1605
|
+
if( !payload.facetId ){
|
|
1606
|
+
logger.warn(`[executive] commitment "${ body.what }" arrived with no facetId — nowhere to file it`)
|
|
1607
|
+
return
|
|
1608
|
+
}
|
|
1609
|
+
// Created if absent: a facet can hand something up before its first sync,
|
|
1610
|
+
// and requiring the entry to exist first made that promise vanish.
|
|
1611
|
+
const at = this._facetSubjects.get( payload.facetId ) ?? {
|
|
1612
|
+
entityId: payload.subjectEntityId ?? payload.facetId,
|
|
1613
|
+
...( payload.subjectName ? { name: payload.subjectName } : {} ),
|
|
1614
|
+
tick,
|
|
1615
|
+
}
|
|
1616
|
+
at.promised = [
|
|
1617
|
+
// Deduped on WHAT was promised, falling back to the target when two
|
|
1618
|
+
// wordings mean the same contact: restating a promise is the same
|
|
1619
|
+
// promise, and the master does not need telling twice.
|
|
1620
|
+
...( at.promised ?? [] ).filter( p =>
|
|
1621
|
+
p.what !== body.what && !( body.target !== undefined && p.target === body.target ) ),
|
|
1622
|
+
{ what: body.what,
|
|
1623
|
+
...( body.target ? { target: body.target } : {} ),
|
|
1624
|
+
...( body.gist ? { gist: body.gist } : {} ), tick },
|
|
1625
|
+
]
|
|
1626
|
+
this._facetSubjects.set( payload.facetId, at )
|
|
1627
|
+
logger.info(
|
|
1628
|
+
`[executive] filed commitment from ${from} → "${ body.what }" ` +
|
|
1629
|
+
`(it reads this at its next cycle; what to do about it is its own call)`
|
|
1630
|
+
)
|
|
1631
|
+
return
|
|
1632
|
+
}
|
|
1077
1633
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
}
|
|
1634
|
+
this._escalations.push({
|
|
1635
|
+
tick,
|
|
1636
|
+
...( payload.facetId ? { facetId: payload.facetId } : {} ),
|
|
1637
|
+
...( payload.subjectEntityId ? { subjectEntityId: payload.subjectEntityId } : {} ),
|
|
1638
|
+
...( payload.subjectName ? { subjectName: payload.subjectName } : {} ),
|
|
1639
|
+
...( payload.threadId ? { threadId: payload.threadId } : {} ),
|
|
1640
|
+
body: { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) },
|
|
1641
|
+
})
|
|
1094
1642
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
}
|
|
1643
|
+
logger.info(
|
|
1644
|
+
`[executive] master queued escalation percept from ${from} ` +
|
|
1645
|
+
`(confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1646
|
+
)
|
|
1647
|
+
}
|
|
1108
1648
|
|
|
1109
|
-
|
|
1110
|
-
event: syntheticEvent,
|
|
1111
|
-
tick: this._lastExecutiveTick ?? 0
|
|
1112
|
-
} )
|
|
1649
|
+
// ── DeliberationCache wiring ───────────────────────────────
|
|
1113
1650
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1651
|
+
/** Enable the deliberation cache (off by default). Call during mind assembly. */
|
|
1652
|
+
enableCache( config?: DeliberationCacheConfig ): void {
|
|
1653
|
+
this._cache = new DeliberationCache( config )
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/** True when the cache is active. */
|
|
1657
|
+
get cacheEnabled(): boolean { return this._cache !== null }
|
|
1658
|
+
|
|
1659
|
+
/** Telemetry snapshot for harnesses / eval. Null when disabled. */
|
|
1660
|
+
cacheStats(): { size: number; hits: number; misses: number } | null {
|
|
1661
|
+
if( !this._cache ) return null
|
|
1662
|
+
return { size: this._cache.size, hits: this._cache.hitCount, misses: this._cache.missCount }
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* Reafference hook — update cache competence from a confirmed action outcome.
|
|
1667
|
+
* Optional, layered on top of the inline verify loop. Reward follows the
|
|
1668
|
+
* research sketch: mean of (action succeeded, stress relief, goal progress).
|
|
1669
|
+
*/
|
|
1670
|
+
onActionOutcome(
|
|
1671
|
+
state: ReadonlySimulationState,
|
|
1672
|
+
tick: Tick,
|
|
1673
|
+
success: boolean,
|
|
1674
|
+
stressDelta: number,
|
|
1675
|
+
goalProgressDelta: number,
|
|
1676
|
+
): void {
|
|
1677
|
+
if( !this._cache ) return
|
|
1678
|
+
const reward = (
|
|
1679
|
+
( success ? 1 : 0 ) +
|
|
1680
|
+
( 1 - Math.max( 0, Math.min( 1, stressDelta ) ) ) +
|
|
1681
|
+
Math.max( 0, Math.min( 1, goalProgressDelta ) )
|
|
1682
|
+
) / 3
|
|
1683
|
+
this._cache.updateCompetence( extractFingerprint( state ), reward, tick )
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
private _actionTypesMatch( a: string[], b: string[] ): boolean {
|
|
1687
|
+
if( a.length !== b.length ) return false
|
|
1688
|
+
for( let i = 0; i < a.length; i++ ) if( a[ i ] !== b[ i ] ) return false
|
|
1689
|
+
return true
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
private _restoreDeliberationCache( state: ReadonlySimulationState ): void {
|
|
1693
|
+
if( !this._cache ) return
|
|
1694
|
+
const entity = state.entities.get('executive-deliberation-cache')
|
|
1695
|
+
if( !entity ) return
|
|
1696
|
+
const snap = entity.metadata?.[ 'snapshot' ] as DeliberationCacheSnapshot | undefined
|
|
1697
|
+
if( snap ) this._cache.restore( snap )
|
|
1120
1698
|
}
|
|
1121
1699
|
|
|
1122
1700
|
private _restoreSummarizer( state: ReadonlySimulationState ): void {
|