@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.
Files changed (88) hide show
  1. package/README.md +87 -22
  2. package/dist/channels/discord.d.ts +67 -6
  3. package/dist/channels/discord.js +112 -6
  4. package/dist/channels/discord.js.map +1 -1
  5. package/dist/channels/whatsapp.d.ts +1 -1
  6. package/dist/channels/whatsapp.js +4 -1
  7. package/dist/channels/whatsapp.js.map +1 -1
  8. package/dist/cli.js +13733 -11057
  9. package/dist/cli.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +3729 -1199
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/effectors.d.ts +1 -1
  14. package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
  15. package/package.json +1 -1
  16. package/src/channels/discord.ts +189 -11
  17. package/src/channels/types.ts +90 -0
  18. package/src/channels/whatsapp.ts +13 -4
  19. package/src/cli.ts +9 -4
  20. package/src/cognition/agency/consequence.ts +122 -1
  21. package/src/cognition/agency/conversation.aim.ts +260 -0
  22. package/src/cognition/agency/engines/action.selector.ts +85 -3
  23. package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
  24. package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
  25. package/src/cognition/agency/engines/reafference.engine.ts +129 -2
  26. package/src/cognition/agency/proactive.communicator.ts +19 -3
  27. package/src/cognition/agency/reconcile.learning.ts +16 -2
  28. package/src/cognition/agency/restart.ts +66 -0
  29. package/src/cognition/agency/schemas/repertoire.ts +12 -5
  30. package/src/cognition/agency/selection.scoring.ts +33 -0
  31. package/src/cognition/agency/types.ts +35 -0
  32. package/src/cognition/cache/composition.ts +232 -0
  33. package/src/cognition/cache/deliberation.cache.ts +219 -0
  34. package/src/cognition/cache/fingerprint.ts +120 -0
  35. package/src/cognition/cache/types.ts +105 -0
  36. package/src/cognition/config.mirror.entities.ts +109 -1
  37. package/src/cognition/event.schemas.ts +22 -0
  38. package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
  39. package/src/cognition/faculties/episodic.consolidator.ts +59 -3
  40. package/src/cognition/faculties/executive.engine/commands.ts +189 -14
  41. package/src/cognition/faculties/executive.engine/context.ts +67 -13
  42. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
  43. package/src/cognition/faculties/executive.engine/engine.ts +686 -187
  44. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
  45. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
  46. package/src/cognition/faculties/executive.engine/facet.ts +89 -26
  47. package/src/cognition/faculties/executive.engine/gating.ts +14 -14
  48. package/src/cognition/faculties/executive.engine/parser.ts +21 -1
  49. package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
  50. package/src/cognition/faculties/executive.engine/types.ts +69 -0
  51. package/src/cognition/faculties/goal.manager.ts +94 -14
  52. package/src/cognition/faculties/known.entity.tracker.ts +267 -28
  53. package/src/cognition/faculties/moral.evaluator.ts +8 -3
  54. package/src/cognition/faculties/persona.consolidator.ts +141 -0
  55. package/src/cognition/faculties/reputation.tracker.ts +66 -2
  56. package/src/cognition/faculties/self.model.updater.ts +19 -12
  57. package/src/cognition/faculties/social.perception.ts +47 -3
  58. package/src/cognition/faculties/threat.evaluator.ts +7 -0
  59. package/src/cognition/faculties/working.memory.ts +10 -20
  60. package/src/cognition/identity.entity.ts +205 -0
  61. package/src/cognition/index.ts +11 -0
  62. package/src/cognition/memory/vector.adapter.ts +12 -3
  63. package/src/cognition/memory/vector.embedder.ts +54 -7
  64. package/src/cognition/persona.prior.ts +6 -0
  65. package/src/cognition/senses/audition.engine/engine.ts +404 -46
  66. package/src/cognition/senses/base.sense.engine.ts +1 -1
  67. package/src/cognition/senses/index.ts +12 -0
  68. package/src/cognition/social.identity.ts +273 -0
  69. package/src/cognition/utilities/token.tracker.ts +246 -98
  70. package/src/core/orchestrator.ts +38 -0
  71. package/src/host/boot.ts +78 -22
  72. package/src/index.ts +35 -0
  73. package/src/llm/index.ts +415 -97
  74. package/src/llm/routing.ts +204 -0
  75. package/src/llm/summarizer.ts +5 -1
  76. package/src/llm/wire.contracts.ts +19 -0
  77. package/src/pma/index.ts +67 -53
  78. package/src/runners/thin-shim.runner.ts +18 -6
  79. package/src/sdk/will.ts +121 -22
  80. package/src/stem/assembly.audit.ts +1 -0
  81. package/src/stem/guards/identity.coherence.ts +17 -6
  82. package/src/stem/index.ts +82 -5
  83. package/src/stem/mind.ts +327 -79
  84. package/src/stem/policy/arbiter.ts +49 -14
  85. package/src/stem/policy/rule.table.ts +2 -2
  86. package/src/stem/tracts/effector.controller.ts +56 -9
  87. package/src/stem/tracts/outbox.writer.ts +40 -2
  88. 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
- id: 'identity-self',
176
- type: 'will.identity',
177
- createdAt: existingIdentity.createdAt,
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
- await this._vectorMemory.indexBatch( episodesWithContent )
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
- await this._vectorMemory.rebuildFromStore( this._store )
662
- logger.info(`[episodic] vector index rebuilt with ${this._store.length} episodes`)
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
  }