@mindot/will 0.7.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/README.md +87 -22
- 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 +13733 -11057
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3729 -1199
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
- 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 +85 -3
- 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 +129 -2
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +16 -2
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/repertoire.ts +12 -5
- 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 +109 -1
- 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 +686 -187
- 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 +89 -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 +169 -20
- 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 +11 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +54 -7
- 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 +246 -98
- package/src/core/orchestrator.ts +38 -0
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +415 -97
- package/src/llm/routing.ts +204 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +121 -22
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +82 -5
- package/src/stem/mind.ts +327 -79
- package/src/stem/policy/arbiter.ts +49 -14
- package/src/stem/policy/rule.table.ts +2 -2
- package/src/stem/tracts/effector.controller.ts +56 -9
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/cache/types.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DeliberationCache — types and contracts.
|
|
7
|
+
*
|
|
8
|
+
* The cache stores past executive outputs keyed by a deterministic
|
|
9
|
+
* cognitive fingerprint. It is pure, deterministic, and R2-safe:
|
|
10
|
+
* the same state + same history ⇒ same retrieval + same composition.
|
|
11
|
+
*
|
|
12
|
+
* Scope note: Phase 1 caches the ACTIONS block only. The composed output
|
|
13
|
+
* is a valid `ExecutiveOutputFull` carrying the three required fields
|
|
14
|
+
* (actions, reasoning, confidence) plus whatever optional blocks the
|
|
15
|
+
* enabled scopes cover. Everything else stays undefined and the existing
|
|
16
|
+
* downstream (`buildStateCommands`) treats it as "nothing to do", which is
|
|
17
|
+
* exactly the intended Phase-1 behaviour.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExecutiveOutputFull } from '#faculties/executive.engine/types'
|
|
21
|
+
|
|
22
|
+
/** Which blocks of the executive output the cache may synthesise. */
|
|
23
|
+
export type CacheScope =
|
|
24
|
+
| 'actions'
|
|
25
|
+
| 'goals'
|
|
26
|
+
| 'beliefs'
|
|
27
|
+
|
|
28
|
+
export interface DeliberationCacheConfig {
|
|
29
|
+
/** Maximum patterns to retain. Lowest (competence × recency) evicted when full. */
|
|
30
|
+
maxPatterns?: number
|
|
31
|
+
/** Neighbors retrieved for composition. */
|
|
32
|
+
k?: number
|
|
33
|
+
/** Minimum similarity for a stored pattern to count as a neighbor. */
|
|
34
|
+
minSimilarity?: number
|
|
35
|
+
/** Confidence threshold θ — cache hit requires ρ ≥ θ. Start conservative. */
|
|
36
|
+
theta?: number
|
|
37
|
+
/** Temperature for softmax weights over neighbors. */
|
|
38
|
+
tau?: number
|
|
39
|
+
/** Learning rate (EMA) for competence updates. */
|
|
40
|
+
eta?: number
|
|
41
|
+
/** Competence decay per executive cycle (applied via decay()). */
|
|
42
|
+
decayPerCycle?: number
|
|
43
|
+
/** Verify 1-in-N cache hits against the LLM (0 = never). */
|
|
44
|
+
verifyEveryNHits?: number
|
|
45
|
+
/** Which output blocks to synthesise. Phase 1 default: ['actions']. */
|
|
46
|
+
scopes?: CacheScope[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A stored pattern in the cache. */
|
|
50
|
+
export interface CachePattern {
|
|
51
|
+
/** Deterministic fingerprint vector (length FINGERPRINT_DIM). */
|
|
52
|
+
fingerprint: Float32Array
|
|
53
|
+
/** The executive output produced for this fingerprint (verbatim from the LLM). */
|
|
54
|
+
output: ExecutiveOutputFull
|
|
55
|
+
/** Competence score (0–1), learned from reafference. */
|
|
56
|
+
competence: number
|
|
57
|
+
/** Tick when stored. */
|
|
58
|
+
storedAtTick: number
|
|
59
|
+
/** Number of times this pattern won a retrieval. */
|
|
60
|
+
retrievalCount: number
|
|
61
|
+
/** Number of retrievals that were followed by a positive reafference. */
|
|
62
|
+
successCount: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A neighbor plus its similarity to the query fingerprint. */
|
|
66
|
+
export interface ScoredNeighbor {
|
|
67
|
+
pattern: CachePattern
|
|
68
|
+
/** Cosine similarity of this pattern's fingerprint to the query. */
|
|
69
|
+
similarity: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Result of a cache retrieve (+ compose when hit). */
|
|
73
|
+
export interface CacheResult {
|
|
74
|
+
/** The composed output — only meaningful when `hit` is true. */
|
|
75
|
+
output: ExecutiveOutputFull | null
|
|
76
|
+
/** Confidence ρ = max_i (competence_i × sim_i). Used for gating. */
|
|
77
|
+
confidence: number
|
|
78
|
+
/** The scored neighbors that were considered. */
|
|
79
|
+
neighbors: ScoredNeighbor[]
|
|
80
|
+
/** Whether this was a cache hit (confidence ≥ θ). */
|
|
81
|
+
hit: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Serialisable snapshot for deterministic persistence / restore.
|
|
86
|
+
*
|
|
87
|
+
* Persistence follows the same pattern as the rolling summarizer: the engine
|
|
88
|
+
* writes this into a state entity (`executive-deliberation-cache`) via
|
|
89
|
+
* StateCommands and rehydrates it on the first tick. Float arrays are stored
|
|
90
|
+
* as plain number[] so they survive JSON round-tripping through state.
|
|
91
|
+
*/
|
|
92
|
+
export interface DeliberationCacheSnapshot {
|
|
93
|
+
version: number
|
|
94
|
+
patterns: Array<{
|
|
95
|
+
fingerprint: number[]
|
|
96
|
+
output: ExecutiveOutputFull
|
|
97
|
+
competence: number
|
|
98
|
+
storedAtTick: number
|
|
99
|
+
retrievalCount: number
|
|
100
|
+
successCount: number
|
|
101
|
+
}>
|
|
102
|
+
hitCount: number
|
|
103
|
+
missCount: number
|
|
104
|
+
verifyCounter: number
|
|
105
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// reason about its own operational parameters.
|
|
14
14
|
|
|
15
15
|
import { WillConfig } from '#stem/mind'
|
|
16
|
+
import type { StateManager } from '#core/state.manager'
|
|
16
17
|
|
|
17
18
|
export interface EngineConfigEntity {
|
|
18
19
|
id: string
|
|
@@ -34,7 +35,7 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
34
35
|
engine: 'system',
|
|
35
36
|
params: {
|
|
36
37
|
anatomy: config.anatomy ?? 'mind',
|
|
37
|
-
model: config.model ?? '',
|
|
38
|
+
model: config.llm?.model ?? '',
|
|
38
39
|
tickIntervalMs: config.tickIntervalMs ?? 1000
|
|
39
40
|
}
|
|
40
41
|
},
|
|
@@ -316,6 +317,24 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
316
317
|
// DOWN from demonstrated `analytical` disposition via the persona-prior mirror,
|
|
317
318
|
// so a more analytical Will deliberates more readily; this is the baseline.
|
|
318
319
|
deliberateThreshold: 0.5,
|
|
320
|
+
// How many focused facets this Will can hold at once before spawning starts
|
|
321
|
+
// evicting (FacetSupervisor). A structural ceiling, not the live budget:
|
|
322
|
+
// attention scales the allowance *within* it each tick, so a tired or loaded
|
|
323
|
+
// mind narrows on its own. The metacog loop develops it via the persona-prior
|
|
324
|
+
// (openness widens, conscientiousness narrows), which is what makes "how many
|
|
325
|
+
// things I can hold at once" a property of this person rather than a constant.
|
|
326
|
+
maxFacets: 10,
|
|
327
|
+
// How long a QUIET thread stays open before the mind considers it finished
|
|
328
|
+
// (FacetSupervisor idle reaper). The sibling of maxFacets — that one is how
|
|
329
|
+
// many threads at once, this one is how long each survives a silence — and
|
|
330
|
+
// it was the only number in the economy no personality could move.
|
|
331
|
+
//
|
|
332
|
+
// ~30 minutes at a typical tick rate. It was hardcoded at 50 ticks, which is
|
|
333
|
+
// THIRTY SECONDS: every pause longer than a person taking a moment to type
|
|
334
|
+
// destroyed the conversation, and the reply landed on a facet that had never
|
|
335
|
+
// heard of them. Generous is safe — maxFacets + eviction bound the population;
|
|
336
|
+
// this only decides when silence means "over".
|
|
337
|
+
facetIdleTtlTicks: 3000,
|
|
319
338
|
},
|
|
320
339
|
},
|
|
321
340
|
{
|
|
@@ -366,6 +385,35 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
366
385
|
switchCost: 0.15,
|
|
367
386
|
riskWeight: 0.20,
|
|
368
387
|
noveltyWeight: 0.10,
|
|
388
|
+
// How hard an act's own live footprint damps doing it again (EXAFFERENCE
|
|
389
|
+
// P5) — how long this mind sits with something it has already said before
|
|
390
|
+
// saying it again. Agreeableness develops it up, demonstrated persistence
|
|
391
|
+
// down, so "gives people room" vs "chases an answer" is a trait rather
|
|
392
|
+
// than a constant.
|
|
393
|
+
repeatDamping: 0.30,
|
|
394
|
+
// Ticks an act keeps satiating the urge to repeat it. Separate from the
|
|
395
|
+
// consequence TTL on purpose: that one is "how long until the world's echo
|
|
396
|
+
// could still arrive" (short, and about perception), this is "how long
|
|
397
|
+
// before saying it again feels right" (a disposition). Same two traits as
|
|
398
|
+
// repeatDamping move it — patience lengthens, persistence shortens.
|
|
399
|
+
repeatWindowTicks: 60,
|
|
400
|
+
// Ticks before a silence starts to mean something — how long this mind
|
|
401
|
+
// gives someone to get back to it before it counts the turn unanswered
|
|
402
|
+
// and learns from that (conversation.aim / ReafferenceEngine).
|
|
403
|
+
//
|
|
404
|
+
// Lives beside repeatWindowTicks rather than in a config of its own
|
|
405
|
+
// because they are two readings of ONE disposition, and splitting them
|
|
406
|
+
// would let a mind tune itself into contradiction — coming back to
|
|
407
|
+
// something in 20 ticks while still calling the silence too fresh to
|
|
408
|
+
// count. Long relative to its neighbours by design: repeatWindowTicks
|
|
409
|
+
// asks "how long before saying it again feels right", this asks "how long
|
|
410
|
+
// before I take not hearing back as information", and at 1s/tick that is
|
|
411
|
+
// four minutes of a real person's time, not one.
|
|
412
|
+
replyWindowTicks: 240,
|
|
413
|
+
// How much a learned read on someone biases acting toward them. SIGNED and
|
|
414
|
+
// unclamped: a warm mind leans toward whoever answers, a dogged one chases
|
|
415
|
+
// the silence. The container will not choose between those.
|
|
416
|
+
socialWeight: 0.30,
|
|
369
417
|
},
|
|
370
418
|
},
|
|
371
419
|
|
|
@@ -473,3 +521,63 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
473
521
|
},
|
|
474
522
|
]
|
|
475
523
|
}
|
|
524
|
+
|
|
525
|
+
// ── The single writer ─────────────────────────────────────────
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Write an `engine.config` entity — MERGING, always. The only sanctioned way to
|
|
529
|
+
* write one; `tests/unit/config.mirror.writer.test.ts` fails on a raw
|
|
530
|
+
* `setEntity({ type: 'engine.config' })` anywhere else.
|
|
531
|
+
*
|
|
532
|
+
* Every whole-entity write to one of these has silently dropped params, three
|
|
533
|
+
* times in one day and each in a different place:
|
|
534
|
+
*
|
|
535
|
+
* • PMALoader replaced `engine-config-executive` with the three behavioural
|
|
536
|
+
* params a PMA carries, dropping `deliberateThreshold` — so `readBaseParams`
|
|
537
|
+
* returned nothing for it and `consolidatePrior` skipped the analytical and
|
|
538
|
+
* decisiveness edges outright, for every Will ever restored from an artifact.
|
|
539
|
+
* • The same loader dropped `emitBlendEvents` from the blender and three params
|
|
540
|
+
* from forgetting.
|
|
541
|
+
* • Snapshot restore replaced the whole mirror, so a Will woke with the config
|
|
542
|
+
* it FIRST hibernated under and could never receive a param added later —
|
|
543
|
+
* `maxFacets` and `deliberateThreshold` were inert on a live Will for its
|
|
544
|
+
* entire life.
|
|
545
|
+
*
|
|
546
|
+
* `precedence` says which side wins on a key both hold. Neither ever drops a key.
|
|
547
|
+
*
|
|
548
|
+
* 'incoming' — the caller is the authority (boot seed; a PMA supplying the
|
|
549
|
+
* tenant's own dispositions). Keys it does not mention survive.
|
|
550
|
+
* 'existing' — state is the authority (post-restore backfill). Only genuinely
|
|
551
|
+
* missing keys are added, so learned and PMA'd values are safe.
|
|
552
|
+
*
|
|
553
|
+
* Returns the keys it actually added or changed, for the caller to log.
|
|
554
|
+
*/
|
|
555
|
+
export function mergeEngineConfig(
|
|
556
|
+
store: StateManager,
|
|
557
|
+
cfg: EngineConfigEntity,
|
|
558
|
+
precedence: 'incoming' | 'existing' = 'incoming',
|
|
559
|
+
): string[] {
|
|
560
|
+
const existing = store.getEntity( cfg.id )
|
|
561
|
+
const current = ( existing?.metadata as { params?: Record<string, unknown> } | undefined )?.params ?? {}
|
|
562
|
+
|
|
563
|
+
const params = precedence === 'existing'
|
|
564
|
+
? { ...cfg.params, ...current } // state wins; fills only what is missing
|
|
565
|
+
: { ...current, ...cfg.params } // caller wins; keeps everything else
|
|
566
|
+
|
|
567
|
+
const changed = Object.keys( params ).filter( k => params[ k ] !== current[ k ] )
|
|
568
|
+
if( existing && changed.length === 0 ) return []
|
|
569
|
+
|
|
570
|
+
// No timestamps: StateManager.setEntity is the single place they are stamped,
|
|
571
|
+
// and it sources them from the SIM clock so entity times replay identically
|
|
572
|
+
// (R2). It also preserves an existing `createdAt`. The write sites this
|
|
573
|
+
// replaced all passed `Date.now()`, which was both redundant and a real
|
|
574
|
+
// determinism hole — the guard test caught it the moment the code moved into
|
|
575
|
+
// `cognition/`, where wall-clock reads are banned.
|
|
576
|
+
store.setEntity({
|
|
577
|
+
id: cfg.id,
|
|
578
|
+
type: 'engine.config',
|
|
579
|
+
metadata: { engine: cfg.engine, params },
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
return changed
|
|
583
|
+
}
|
|
@@ -566,3 +566,25 @@ globalSchemaRegistry.register({
|
|
|
566
566
|
return hasNum( p, 'count')
|
|
567
567
|
},
|
|
568
568
|
})
|
|
569
|
+
|
|
570
|
+
// ── Deliberation cache (fast-path telemetry) ─────────────────
|
|
571
|
+
// Published from the ExecutiveEngine's committed path (onReasoningComplete),
|
|
572
|
+
// never from inside the pure cache. Lets faculties like the PersonaConsolidator
|
|
573
|
+
// react to how automatic the Will is becoming (e.g. a high hit rate could lower
|
|
574
|
+
// the deliberate-effort threshold).
|
|
575
|
+
|
|
576
|
+
globalSchemaRegistry.register({
|
|
577
|
+
type: 'cache.hit', version: 1,
|
|
578
|
+
validate( p ){
|
|
579
|
+
if( !isObj(p) ) return 'payload must be object'
|
|
580
|
+
return hasNum( p, 'confidence') ?? hasNum( p, 'neighborCount')
|
|
581
|
+
},
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
globalSchemaRegistry.register({
|
|
585
|
+
type: 'cache.miss', version: 1,
|
|
586
|
+
validate( p ){
|
|
587
|
+
if( !isObj(p) ) return 'payload must be object'
|
|
588
|
+
return hasNum( p, 'confidence')
|
|
589
|
+
},
|
|
590
|
+
})
|
|
@@ -23,6 +23,7 @@ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
|
23
23
|
import { GenerativeModel } from '#cognition/generative.model'
|
|
24
24
|
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
25
25
|
import { ExecutiveEngine } from '#faculties/executive.engine'
|
|
26
|
+
import { identityCommand } from '#cognition/identity.entity'
|
|
26
27
|
|
|
27
28
|
export interface AutobiographicalNarratorConfig {
|
|
28
29
|
minIntervalTicks?: number
|
|
@@ -171,16 +172,10 @@ export class AutobiographicalNarrator implements SimulationEngine, CognitiveEngi
|
|
|
171
172
|
for( const { key: trait, value: delta } of executiveOutput.identityUpdates.traits )
|
|
172
173
|
updatedTraits[ trait ] = Math.max( 0, Math.min( 1, ( updatedTraits[ trait ] ?? 0.5 ) + delta ) )
|
|
173
174
|
|
|
174
|
-
commands.set!.push({
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
metadata: {
|
|
179
|
-
...existingIdentity.metadata,
|
|
180
|
-
traits: updatedTraits,
|
|
181
|
-
version: ( ( existingIdentity.metadata?.version as number ) ?? 1 ) + 1,
|
|
182
|
-
},
|
|
183
|
-
})
|
|
175
|
+
commands.set!.push( identityCommand( state, {
|
|
176
|
+
traits: updatedTraits,
|
|
177
|
+
version: ( ( existingIdentity.metadata?.version as number ) ?? 1 ) + 1,
|
|
178
|
+
} ) )
|
|
184
179
|
}
|
|
185
180
|
}
|
|
186
181
|
|
|
@@ -114,6 +114,13 @@ export class EpisodicConsolidator implements SimulationEngine, CognitiveEngine {
|
|
|
114
114
|
|
|
115
115
|
// Vector memory integration
|
|
116
116
|
private _vectorMemory: VectorMemoryAdapter | null = null
|
|
117
|
+
/**
|
|
118
|
+
* In-flight background indexing. Indexing is deliberately not awaited inside
|
|
119
|
+
* react() (a rate-limit retry chain would stall the whole tick loop), so this is
|
|
120
|
+
* the handle for the two callers that genuinely must wait for it: shutdown,
|
|
121
|
+
* before persisting the index, and tests asserting on it.
|
|
122
|
+
*/
|
|
123
|
+
private _indexing: Promise<void> = Promise.resolve()
|
|
117
124
|
private _embedder: EmbeddingProvider | null = null
|
|
118
125
|
private _autoIndex: boolean
|
|
119
126
|
|
|
@@ -278,7 +285,35 @@ export class EpisodicConsolidator implements SimulationEngine, CognitiveEngine {
|
|
|
278
285
|
content: ep.content
|
|
279
286
|
} ) )
|
|
280
287
|
|
|
281
|
-
|
|
288
|
+
// Indexing is BEST-EFFORT, exactly like semanticQuery. It calls an embedding
|
|
289
|
+
// provider, so a rate limit or an outage is an ordinary condition, not a
|
|
290
|
+
// cognitive fault: the episode is already consolidated and in the store, and
|
|
291
|
+
// only its vector is missing. Letting that throw took the whole engine down
|
|
292
|
+
// mid-tick — observed live as `Engine "episodic-consolidator" threw at tick
|
|
293
|
+
// 107: Embedding failed: 429`, which also aborted the rest of this react()
|
|
294
|
+
// (store sync, forgetting-curve decay) for that tick.
|
|
295
|
+
//
|
|
296
|
+
// The episode itself is NOT lost: it is in `_store` and is written to state by
|
|
297
|
+
// the periodic full-store sync below, so it survives snapshot/restore. Only its
|
|
298
|
+
// VECTOR is missing, which costs semantic recall of that episode for the rest of
|
|
299
|
+
// the session — `rebuildFromStore()` re-indexes the whole store on a later
|
|
300
|
+
// restore that finds no persisted index. Nothing re-indexes it in-session; that
|
|
301
|
+
// is a known gap, not a claim that this self-heals.
|
|
302
|
+
// NOT awaited. Nothing in this tick reads the vector index, and the embedding
|
|
303
|
+
// call is a network round-trip behind a rate-limit gate whose retry chain runs
|
|
304
|
+
// ~60s (4+8+16+32s) before giving up. Awaiting it made a single 429 stall the
|
|
305
|
+
// WHOLE TICK LOOP for a minute — measured: ticks 3→4 and 4→5 took 64.9s and
|
|
306
|
+
// 63.5s, one per deferred batch. That is not merely slow: `AWAIT_TIMEOUT` and
|
|
307
|
+
// every other agency deadline are denominated in TICKS, so a 60s tick silently
|
|
308
|
+
// turned a 15-tick timeout into 15 minutes, left one communicate intent stuck
|
|
309
|
+
// 'awaiting', and — because the selector is serial — blocked every subsequent
|
|
310
|
+
// action. 45 executive decisions produced 1 intent and 0 delivered messages.
|
|
311
|
+
this._indexing = this._vectorMemory.indexBatch( episodesWithContent ).catch( ( err: unknown ) => {
|
|
312
|
+
logger.warn(
|
|
313
|
+
`[EpisodicConsolidator] indexing deferred for ${ newEpisodes.length } episode(s) — ` +
|
|
314
|
+
`${ err instanceof Error ? err.message : String( err ) }`
|
|
315
|
+
)
|
|
316
|
+
} )
|
|
282
317
|
}
|
|
283
318
|
|
|
284
319
|
// 5. Periodic full-store sync — captures activationStrength decay (forgetting curve),
|
|
@@ -385,6 +420,9 @@ export class EpisodicConsolidator implements SimulationEngine, CognitiveEngine {
|
|
|
385
420
|
* Other metadata narrowing (sourceType / tags) remains the caller's job on the
|
|
386
421
|
* returned episodes (which carry all metadata).
|
|
387
422
|
*/
|
|
423
|
+
/** Await any background indexing still in flight. Shutdown and tests only. */
|
|
424
|
+
async flushIndexing(): Promise<void> { await this._indexing }
|
|
425
|
+
|
|
388
426
|
async semanticQuery(
|
|
389
427
|
query: unknown,
|
|
390
428
|
filters?: {
|
|
@@ -417,10 +455,18 @@ export class EpisodicConsolidator implements SimulationEngine, CognitiveEngine {
|
|
|
417
455
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
418
456
|
const timedOut = Symbol('recall-timeout')
|
|
419
457
|
|
|
458
|
+
// The search keeps running after the race is lost — its own rate-limit retry
|
|
459
|
+
// chain can outlive this timeout by a minute. Its rejection is caught HERE
|
|
460
|
+
// rather than left to the race: a promise that loses a Promise.race still
|
|
461
|
+
// settles, and an unhandled 429 rejection surfaces as a process-level warning
|
|
462
|
+
// (or a crash, depending on host) long after the recall it belonged to gave up.
|
|
420
463
|
const results = await Promise.race( [
|
|
421
464
|
this._vectorMemory.search( query, {
|
|
422
465
|
maxResults: fetch,
|
|
423
466
|
minSimilarity: filters?.minSimilarity,
|
|
467
|
+
} ).catch( ( err: unknown ) => {
|
|
468
|
+
logger.warn(`[EpisodicConsolidator] recall search failed — ${ err instanceof Error ? err.message : String( err ) }`)
|
|
469
|
+
return []
|
|
424
470
|
} ),
|
|
425
471
|
new Promise<typeof timedOut>( resolve => { timer = setTimeout( () => resolve( timedOut ), timeoutMs ) } ),
|
|
426
472
|
] ).finally( () => clearTimeout( timer ) )
|
|
@@ -658,8 +704,18 @@ export class EpisodicConsolidator implements SimulationEngine, CognitiveEngine {
|
|
|
658
704
|
if( this._vectorMemory ){
|
|
659
705
|
await this._vectorMemory.load()
|
|
660
706
|
if( this._vectorMemory.size === 0 && this._store.length > 0 ){
|
|
661
|
-
|
|
662
|
-
|
|
707
|
+
// Re-embedding the whole store is a bulk network operation, and this runs on
|
|
708
|
+
// the FIRST TICK after restore. Awaited, it held tick 1 for 66.7s against a
|
|
709
|
+
// 500ms budget and threw on a rate limit — a mind spending its first waking
|
|
710
|
+
// minute frozen, with every tick-denominated deadline stretched around it.
|
|
711
|
+
// Detached: recall degrades to "not yet indexed" until it lands, which is the
|
|
712
|
+
// same best-effort contract semanticQuery already has. `flushIndexing()` is
|
|
713
|
+
// what shutdown drains, so a rebuild in flight is still written out.
|
|
714
|
+
this._indexing = this._vectorMemory.rebuildFromStore( this._store )
|
|
715
|
+
.then( () => { logger.info(`[episodic] vector index rebuilt with ${ this._store.length } episodes`) } )
|
|
716
|
+
.catch( ( err: unknown ) => {
|
|
717
|
+
logger.warn(`[episodic] vector index rebuild deferred — ${ err instanceof Error ? err.message : String( err ) }`)
|
|
718
|
+
} )
|
|
663
719
|
} else if( this._vectorMemory.size > 0 ){
|
|
664
720
|
logger.info(`[episodic] vector index loaded from disk (${this._vectorMemory.size} entries)`)
|
|
665
721
|
}
|