@mindot/will 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +3174 -867
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3236 -1042
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-cS6k4uiJ.d.ts → will-DbDj_TEH.d.ts} +752 -17
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +83 -2
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +117 -0
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -0
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +552 -131
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +167 -19
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -2
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +58 -5
- package/src/core/orchestrator.ts +38 -0
- package/src/llm/index.ts +25 -8
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/sdk/will.ts +39 -6
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +79 -2
- package/src/stem/mind.ts +172 -55
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
package/src/pma/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { readFileSync, existsSync } from 'node:fs'
|
|
|
32
32
|
import { join } from 'node:path'
|
|
33
33
|
|
|
34
34
|
import type { SimulationState } from '#core/types'
|
|
35
|
+
import { mergeEngineConfig } from '#cognition/config.mirror.entities'
|
|
35
36
|
import type { DefaultSimulation } from '#core/simulation'
|
|
36
37
|
import type { Cognition } from '#types'
|
|
37
38
|
import type {
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
loadCompetence,
|
|
46
47
|
type CompetenceSnapshot,
|
|
47
48
|
} from '#agency/competence.codec'
|
|
49
|
+
import { mergeIdentity, composeIdentityPrompt, readPersona, IDENTITY_ENTITY_ID } from '#cognition/identity.entity'
|
|
48
50
|
|
|
49
51
|
// ── Schema version ─────────────────────────────────────────────
|
|
50
52
|
// Bump this when any field is removed or semantically changed.
|
|
@@ -361,7 +363,12 @@ export class PMADistiller {
|
|
|
361
363
|
if( entity.type === 'will.identity'){
|
|
362
364
|
const m = entity.metadata ?? {}
|
|
363
365
|
return {
|
|
364
|
-
|
|
366
|
+
// The TENANT's text only. Capturing the composed `prompt` baked the
|
|
367
|
+
// container's WILL_CORE_PREAMBLE into the artifact, so a woken mind
|
|
368
|
+
// recited whichever build's preamble had distilled it — forever, and
|
|
369
|
+
// invisibly. `readPersona` falls back to stripping it out of a composed
|
|
370
|
+
// prompt, so artifacts written before the split load clean too.
|
|
371
|
+
prompt: readPersona( m ),
|
|
365
372
|
values: ( m['values'] as string[] ) ?? [],
|
|
366
373
|
traits: ( m['traits'] as Record<string, number> ) ?? {},
|
|
367
374
|
traitStats: ( m['traitStats'] as PMAIdentity['traitStats'] ) ?? undefined,
|
|
@@ -792,22 +799,31 @@ export class PMALoader {
|
|
|
792
799
|
const sm = simulation.stateManager
|
|
793
800
|
|
|
794
801
|
// ── 1. Identity (with enhanced fields) ────────────────────
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
802
|
+
// MERGING (see cognition/identity.entity). An artifact carries what the mind
|
|
803
|
+
// LEARNED about itself; it does not carry the mind's name, because the name is
|
|
804
|
+
// supplied at boot by whoever is renting the container. This used to replace
|
|
805
|
+
// the entity outright, so `_seedIdentity`'s `name` — written moments earlier
|
|
806
|
+
// in the same wake — was erased before the first tick, every single wake.
|
|
807
|
+
// Recomposed, never restored verbatim: `pma.identity.prompt` is layer 2 (the
|
|
808
|
+
// persona), and layer 1 comes from the build that is running NOW. This is what
|
|
809
|
+
// makes a preamble fix reach minds that already exist.
|
|
810
|
+
const environment = sm.getEntity( IDENTITY_ENTITY_ID )?.metadata?.['environment']
|
|
811
|
+
// Through `readPersona`, NOT raw. An artifact written before the layers were
|
|
812
|
+
// split stores the COMPOSED prompt, so composing over it again gives the mind
|
|
813
|
+
// its own architecture twice. Putting this fallback only in the distiller was
|
|
814
|
+
// not enough — the loader is what every existing artifact goes through.
|
|
815
|
+
const persona = readPersona({ prompt: pma.identity.prompt })
|
|
816
|
+
mergeIdentity( sm, {
|
|
817
|
+
persona,
|
|
818
|
+
prompt: composeIdentityPrompt( persona, typeof environment === 'string' ? environment : undefined ),
|
|
819
|
+
values: pma.identity.values,
|
|
820
|
+
traits: pma.identity.traits,
|
|
821
|
+
traitStats: pma.identity.traitStats, // restore the Will's own norm (graded salience B/C)
|
|
822
|
+
version: pma.identity.version,
|
|
823
|
+
style: pma.identity.style,
|
|
824
|
+
socialOrientation: pma.identity.socialOrientation,
|
|
825
|
+
trustPropensity: pma.identity.trustPropensity,
|
|
826
|
+
memoryPersistence: pma.identity.memoryPersistence,
|
|
811
827
|
})
|
|
812
828
|
|
|
813
829
|
// ── 1b. Persona (learned self-tuning from the metacognition cycle) ──
|
|
@@ -889,55 +905,53 @@ export class PMALoader {
|
|
|
889
905
|
const temperamentValence = pma.emotionalBaseline.temperamentValence
|
|
890
906
|
const reactivity = pma.emotionalBaseline.reactivity
|
|
891
907
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
inertia: 1 - reactivity,
|
|
901
|
-
temperamentValence,
|
|
902
|
-
},
|
|
908
|
+
// MERGE, never replace — same trap as engine-config-executive. The mirror
|
|
909
|
+
// seeds `emitBlendEvents` here and the PMA carries only `inertia`, so a
|
|
910
|
+
// whole-entity write silently dropped it for every restored Will.
|
|
911
|
+
mergeEngineConfig( sm, {
|
|
912
|
+
id: 'engine-config-affective-blender', engine: 'affective-blender',
|
|
913
|
+
params: {
|
|
914
|
+
inertia: 1 - reactivity,
|
|
915
|
+
temperamentValence,
|
|
903
916
|
},
|
|
904
|
-
})
|
|
917
|
+
}, 'incoming')
|
|
905
918
|
|
|
906
919
|
// ── 6. Behavioral parameters ──────────────────────────────
|
|
907
920
|
if( pma.behavioral.riskTolerance !== undefined ||
|
|
908
921
|
pma.behavioral.explorationRate !== undefined ||
|
|
909
922
|
pma.behavioral.impulsivity !== undefined ){
|
|
910
923
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
924
|
+
// MERGE onto the seeded mirror, never replace it.
|
|
925
|
+
//
|
|
926
|
+
// createWill() seeds engine-config-executive first (executiveInterval,
|
|
927
|
+
// cooldownTicks, deliberateThreshold, maxFacets); loadPMA() runs after. A
|
|
928
|
+
// whole-entity write here dropped every param the PMA does not carry — so
|
|
929
|
+
// for every Will restored from an artifact (i.e. every production Will), the
|
|
930
|
+
// executive's dual-process gate lost its base, `readBaseParams` returned
|
|
931
|
+
// nothing for `deliberateThreshold`, and consolidatePrior skipped its
|
|
932
|
+
// adjustments outright: the analytical/decisiveness edges that are supposed
|
|
933
|
+
// to develop how readily this mind stops to think had no base to move.
|
|
934
|
+
mergeEngineConfig( sm, {
|
|
935
|
+
id: 'engine-config-executive', engine: 'executive',
|
|
936
|
+
params: {
|
|
937
|
+
riskTolerance: pma.behavioral.riskTolerance ?? 0.5,
|
|
938
|
+
explorationRate: pma.behavioral.explorationRate ?? 0.3,
|
|
939
|
+
impulsivity: pma.behavioral.impulsivity ?? 0.3,
|
|
923
940
|
},
|
|
924
|
-
})
|
|
941
|
+
}, 'incoming')
|
|
925
942
|
}
|
|
926
943
|
|
|
927
944
|
// Configure memory persistence for ForgettingCurve
|
|
928
945
|
if( pma.identity.memoryPersistence !== undefined ){
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
params: {
|
|
937
|
-
baseForgettingRate: 1 - ( pma.identity.memoryPersistence * 0.7 ),
|
|
938
|
-
},
|
|
946
|
+
// MERGE — the mirror seeds emotionProtection / pruningThreshold /
|
|
947
|
+
// maxPrunePerTick here and the PMA carries only baseForgettingRate, so a
|
|
948
|
+
// whole-entity write dropped three params on every restore.
|
|
949
|
+
mergeEngineConfig( sm, {
|
|
950
|
+
id: 'engine-config-forgetting', engine: 'forgetting-curve',
|
|
951
|
+
params: {
|
|
952
|
+
baseForgettingRate: 1 - ( pma.identity.memoryPersistence * 0.7 ),
|
|
939
953
|
},
|
|
940
|
-
})
|
|
954
|
+
}, 'incoming')
|
|
941
955
|
}
|
|
942
956
|
|
|
943
957
|
// ── 7. Relationship stubs ─────────────────────────────────
|
package/src/sdk/will.ts
CHANGED
|
@@ -56,6 +56,8 @@ export interface Stimulus {
|
|
|
56
56
|
speaker?: string
|
|
57
57
|
/** Conversation/thread id (default = `from`). */
|
|
58
58
|
thread?: string
|
|
59
|
+
/** True when `thread` is private — just this someone and the Will. See TextMessage.direct. */
|
|
60
|
+
direct?: boolean
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/** A message the Will emitted to someone. */
|
|
@@ -66,6 +68,20 @@ export interface WillMessage {
|
|
|
66
68
|
content: string
|
|
67
69
|
/** Entity id the Will addressed (the speaker you used in say()/tell(), or a bond). */
|
|
68
70
|
to: string
|
|
71
|
+
/**
|
|
72
|
+
* The conversation this belongs to — the `thread` from the `perceive()` that
|
|
73
|
+
* prompted it. Absent when the Will spoke unprompted, which genuinely has no
|
|
74
|
+
* thread.
|
|
75
|
+
*
|
|
76
|
+
* WHERE, not just to whom. The engine knew this the whole way down —
|
|
77
|
+
* `OutboxMessage.threadId` carries it — and the projection dropped it here, so
|
|
78
|
+
* a channel adapter had nothing to answer INTO and had to guess from a roster.
|
|
79
|
+
* Observed live: a DM arrived on `discord:1532693…`, she answered it correctly
|
|
80
|
+
* and in seconds, and the reply went to the shared server channel because that
|
|
81
|
+
* was the last room the roster had seen this person in. From the operator's
|
|
82
|
+
* side she had simply ignored him.
|
|
83
|
+
*/
|
|
84
|
+
thread?: string
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
/**
|
|
@@ -319,17 +335,29 @@ export class Will {
|
|
|
319
335
|
pma: PMASnapshot,
|
|
320
336
|
opts: Omit<CreateWillOptions, 'identity'> & { identity?: Partial<WillIdentity> },
|
|
321
337
|
): Promise<Will> {
|
|
322
|
-
|
|
338
|
+
// Waking IS being the same mind, so the id continues from the artifact unless the
|
|
339
|
+
// caller overrides it. The id is the path key for everything durable that lives
|
|
340
|
+
// OUTSIDE the artifact — `data/wills/<id>/vector_index`, snapshots, session logs.
|
|
341
|
+
// Minting a fresh `name-<random>` here gave a woken mind a brand-new, empty vector
|
|
342
|
+
// store every boot: identity/beliefs/goals returned (those are in the artifact) while
|
|
343
|
+
// episodic recall came back permanently empty, and the orphaned index was left behind
|
|
344
|
+
// on disk. Observed: four boots, four `lora-*` directories, four 4KB indexes, and a
|
|
345
|
+
// mind that concluded at 100% confidence that its own channel might not be viable
|
|
346
|
+
// because it could not recall a conversation it had just had.
|
|
347
|
+
const id = opts.id ?? pma.willId ?? `${slug( opts.name )}-${Math.random().toString( 36 ).slice( 2, 8 )}`
|
|
323
348
|
const stem = new WillStem()
|
|
324
349
|
const will = new Will( stem, id, opts.name )
|
|
325
350
|
|
|
326
351
|
for( const [ name, entry ] of Object.entries( opts.effectors ?? {} ) )
|
|
327
352
|
will._register( name, entry )
|
|
328
353
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
const config = will._buildConfig( id, { ...opts, identity: { prompt: '', ...opts.identity } } )
|
|
355
|
+
// Say out loud that this identity is a placeholder. Otherwise the creation
|
|
356
|
+
// guard warns about the empty persona we just constructed on purpose — three
|
|
357
|
+
// alarms per wake, drowning the real check that runs when the artifact loads.
|
|
358
|
+
config.identityFromArtifact = true
|
|
359
|
+
|
|
360
|
+
await stem.createWill( config, true /* startPaused — load the artifact before the first tick */ )
|
|
333
361
|
stem.loadPMA( id, pma )
|
|
334
362
|
stem.resumeWill( id )
|
|
335
363
|
will._attach()
|
|
@@ -358,6 +386,8 @@ export class Will {
|
|
|
358
386
|
// the Will knows the person as "someone" until a real one is learned. (The live
|
|
359
387
|
// conversation focus still falls back to the entity id for its Speaker line.)
|
|
360
388
|
...( stimulus.speaker ? { speakerName: stimulus.speaker } : {} ),
|
|
389
|
+
// Omitted rather than defaulted: an unknown room is not known to be public.
|
|
390
|
+
...( stimulus.direct !== undefined ? { direct: stimulus.direct } : {} ),
|
|
361
391
|
} )
|
|
362
392
|
}
|
|
363
393
|
|
|
@@ -561,7 +591,10 @@ export class Will {
|
|
|
561
591
|
this._unsub = this.stem.addTickListener( this.id, ( _snapshot, _tick, outbox, invocations ) => {
|
|
562
592
|
// Outbound messages → event + auto delivery-ack.
|
|
563
593
|
for( const msg of outbox ){
|
|
564
|
-
this._emitMessage( {
|
|
594
|
+
this._emitMessage( {
|
|
595
|
+
id: msg.id, content: msg.content, to: msg.targetEntityId,
|
|
596
|
+
...( msg.threadId ? { thread: msg.threadId } : {} ),
|
|
597
|
+
} )
|
|
565
598
|
try { this.stem.confirmMessageDelivery( this.id, msg.id, true ) } catch { /* best-effort */ }
|
|
566
599
|
}
|
|
567
600
|
// Effector invocations → project the motor act, then run the handler → ack.
|
|
@@ -86,3 +86,4 @@ export function auditAssemblyWiring( engines: readonly SimulationEngine[] ): Wir
|
|
|
86
86
|
export function wiringKeys( records: WiringRecord[], status: WiringRecord['status'] ): string[] {
|
|
87
87
|
return records.filter( r => r.status === status ).map( r => `${r.engine}.${r.method}`).sort()
|
|
88
88
|
}
|
|
89
|
+
|
|
@@ -51,7 +51,7 @@ export interface IdentityReviewer {
|
|
|
51
51
|
// MODEL_ROUTING W0 — a one-shot classification at creation time; constant low
|
|
52
52
|
// demand (structurally background, and it runs before there is a mind whose
|
|
53
53
|
// state could modulate it).
|
|
54
|
-
const COHERENCE_META: LLMCallMeta = { category: 'identity-guard', attribute: 'guard', function: 'identity-coherence', demand: BACKGROUND_DEMAND }
|
|
54
|
+
const COHERENCE_META: LLMCallMeta = { category: 'identity-guard', attribute: 'guard', process: 'cog', function: 'identity-coherence', demand: BACKGROUND_DEMAND }
|
|
55
55
|
|
|
56
56
|
const VALID_KINDS = new Set<CoherenceIssue['kind']>( [ 'contradiction', 'false-capability', 'injection', 'incoherence', 'other' ] )
|
|
57
57
|
|
package/src/stem/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type { ExecutiveOutputFull } from '#faculties/executive.engine'
|
|
|
23
23
|
import type { Cognition, OutboxMessage, effectorInvocation, WorldInterface } from '#types'
|
|
24
24
|
import type { TextMessage, SensoryInput } from '#senses/index'
|
|
25
25
|
import type { ActivityEvent, ActivityEventHandler } from '#cognition/faculties/planning.engine/engine'
|
|
26
|
-
import { assembleMind, type WillConfig } from '#stem/mind'
|
|
26
|
+
import { assembleMind, backfillEngineConfigs, resolveExecutiveInterval, type WillConfig } from '#stem/mind'
|
|
27
27
|
import { reviewIdentityCoherence as runCoherenceReview, type CoherenceInput, type CoherenceResult } from '#stem/guards/identity.coherence'
|
|
28
28
|
import { SessionLogger } from '#stem/tracts/session.logger'
|
|
29
29
|
import { fileLoggingEnabled } from '#stem/tracts/transport/stream.transport'
|
|
@@ -39,10 +39,13 @@ import { InboundQueue } from '#stem/tracts/inbound.queue'
|
|
|
39
39
|
import type { ExternalTransport } from '#stem/tracts/transport'
|
|
40
40
|
import { effectorController } from '#stem/tracts/effector.controller'
|
|
41
41
|
import { externalSchemas } from '#agency/schemas/external'
|
|
42
|
+
import { inFlightOnRestore } from '#agency/restart'
|
|
43
|
+
import { buildEngineConfigEntities } from '#cognition/config.mirror.entities'
|
|
42
44
|
import type { EffectorDeclaration } from '#agency/types'
|
|
43
45
|
import { SensoryController } from '#stem/tracts/sensory.controller'
|
|
44
46
|
import { BiographyWriter } from '#stem/tracts/biography.writer'
|
|
45
47
|
import { HealthReporter } from '#stem/tracts/health.reporter'
|
|
48
|
+
import { mergeIdentity } from '#cognition/identity.entity'
|
|
46
49
|
|
|
47
50
|
// ── Types ─────────────────────────────────────────────────────
|
|
48
51
|
|
|
@@ -224,8 +227,66 @@ export class WillStem {
|
|
|
224
227
|
try {
|
|
225
228
|
const previousState = await simulation.snapshotManager.loadLatestFromStorage()
|
|
226
229
|
if( previousState ){
|
|
230
|
+
// Work that was in flight when the mind slept does not resume — see
|
|
231
|
+
// agency/restart.ts. Dropped BEFORE restore so it never enters live
|
|
232
|
+
// state at all, rather than being swept on some later tick.
|
|
233
|
+
const inFlight = inFlightOnRestore( previousState.entities )
|
|
234
|
+
for( const id of inFlight ) previousState.entities.delete( id )
|
|
235
|
+
|
|
227
236
|
simulation.stateManager.restore( previousState, { entities: true, metrics: false } )
|
|
228
|
-
|
|
237
|
+
|
|
238
|
+
// TIME MUST NOT GO BACKWARDS.
|
|
239
|
+
//
|
|
240
|
+
// Entities come back stamped with the tick they were written at, and
|
|
241
|
+
// the orchestrator overwrites the StateManager's tick from the CLOCK
|
|
242
|
+
// every tick (`_clock.tick()` → `updateClock`) — so restoring the
|
|
243
|
+
// manager's tick alone is undone immediately. The clock is the source
|
|
244
|
+
// of truth and has to resume too.
|
|
245
|
+
//
|
|
246
|
+
// Without this, every `tick - stampedTick` in the codebase computed a
|
|
247
|
+
// negative age. Measured: an awaiting intent read `-589 ticks`, so it
|
|
248
|
+
// could never time out, and the selector's staleness decay inverted
|
|
249
|
+
// into amplification (`1 - (-39 × 0.5)` = 20.6×) — a 0.47 incumbent
|
|
250
|
+
// scoring 9.74, unpreemptable, holding the channel against every other
|
|
251
|
+
// contact indefinitely and across restarts. It also means tick-stamped
|
|
252
|
+
// entity ids (`affordance-${tick}-…`, `agency-outcome-${tick}-…`) stop
|
|
253
|
+
// colliding with a previous session's.
|
|
254
|
+
simulation.clock.setTick( previousState.tick )
|
|
255
|
+
|
|
256
|
+
// The restore above replaced the entity map wholesale, including the
|
|
257
|
+
// engine-config mirror `assembleMind` had just seeded — so a Will woke
|
|
258
|
+
// with whatever config it FIRST hibernated under, and every tunable
|
|
259
|
+
// added since was unreachable to it. Restored values win; only params it
|
|
260
|
+
// has never seen are added.
|
|
261
|
+
backfillEngineConfigs( simulation, buildEngineConfigEntities( config, resolveExecutiveInterval( config ) ) )
|
|
262
|
+
|
|
263
|
+
// The same wholesale replacement takes `identity-self` — so the name
|
|
264
|
+
// `_seedIdentity` wrote moments ago is replaced by whatever the snapshot
|
|
265
|
+
// carries, which for any mind hibernated before the merging writer
|
|
266
|
+
// existed is NO NAME AT ALL.
|
|
267
|
+
//
|
|
268
|
+
// Caught only by booting: making every writer merge is necessary and was
|
|
269
|
+
// not sufficient, because restore is not a writer — it is the whole map
|
|
270
|
+
// arriving at once, and it lands between `_seedIdentity` and `loadPMA`.
|
|
271
|
+
// A live Will woke from a repaired build and still told her operator "my
|
|
272
|
+
// self-model says I'm Will".
|
|
273
|
+
//
|
|
274
|
+
// ONLY the name is re-asserted. Everything else on this entity — prompt,
|
|
275
|
+
// values, traits, traitStats, style — is the mind's own accumulated
|
|
276
|
+
// self-knowledge and must come from the snapshot, not from boot config.
|
|
277
|
+
// The name is the one field the container supplies and the tenant never
|
|
278
|
+
// learns, which is exactly why it is the one field a restore may not eat.
|
|
279
|
+
if( config.name ){
|
|
280
|
+
const restored = mergeIdentity( simulation.stateManager, { name: config.name } )
|
|
281
|
+
if( restored.length )
|
|
282
|
+
logger.info(`[WillStem] identity-self: re-asserted name '${config.name}' after restore`)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
logger.info(
|
|
286
|
+
`[WillStem] Restored snapshot for ${config.id} — ${previousState.entities.size} entities loaded, ` +
|
|
287
|
+
`resuming at tick ${previousState.tick}` +
|
|
288
|
+
( inFlight.length ? ` (dropped ${inFlight.length} in-flight)` : '')
|
|
289
|
+
)
|
|
229
290
|
}
|
|
230
291
|
}
|
|
231
292
|
catch( err ){ logger.warn(`[WillStem] Snapshot restore failed for ${config.id} — starting fresh:`, err ) }
|
|
@@ -459,6 +520,12 @@ export class WillStem {
|
|
|
459
520
|
instance.simulation.snapshotManager.persistNow( pauseState )
|
|
460
521
|
.catch( err => logger.error(`[WillStem] snapshot persist failed on pause (${id}):`, err ))
|
|
461
522
|
|
|
523
|
+
// The vector index lives outside the snapshot and only ever persisted itself from
|
|
524
|
+
// a debounce timer nothing awaited — so it died with the process, and every
|
|
525
|
+
// restart cold-started with an EMPTY index no matter how much was consolidated.
|
|
526
|
+
instance.cognition.vectorMemory?.persist()
|
|
527
|
+
.catch( ( err: unknown ) => logger.error(`[WillStem] vector index persist failed on pause (${id}):`, err ))
|
|
528
|
+
|
|
462
529
|
this._biography.writeSessionSummary( instance )
|
|
463
530
|
this._biography.writeEmotionalBiographySummary( instance )
|
|
464
531
|
instance.sessionLogger?.close()
|
|
@@ -574,6 +641,16 @@ export class WillStem {
|
|
|
574
641
|
instance.simulation.stateManager.applyCommands( flushCmds )
|
|
575
642
|
const archiveState = instance.simulation.stateManager.snapshot()
|
|
576
643
|
await instance.simulation.snapshotManager.persistNow( archiveState )
|
|
644
|
+
|
|
645
|
+
// …and the vector index, which is NOT part of that snapshot. Awaited here (unlike
|
|
646
|
+
// the pause path) because the process usually exits straight after this: a
|
|
647
|
+
// fire-and-forget write would simply be lost, which is exactly how a mind ended a
|
|
648
|
+
// session having consolidated episodes and left a 198-byte index on disk.
|
|
649
|
+
// Indexing runs in the background (it must not stall the tick loop), so drain it
|
|
650
|
+
// first — otherwise the last episodes consolidated are still mid-embed when the
|
|
651
|
+
// index is written and are absent from the file this session leaves behind.
|
|
652
|
+
await instance.cognition.episodicConsolidator.flushIndexing()
|
|
653
|
+
await instance.cognition.vectorMemory?.persist()
|
|
577
654
|
}
|
|
578
655
|
|
|
579
656
|
// Session-biography writers (behavioral + emotional) extracted to
|