@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
package/src/sdk/will.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import { WillStem } from '#stem/index'
|
|
31
31
|
import type { WillConfig, WillIdentity, Anatomy, InitialGoal, WillModelConfig, WillLLMConfig } from '#stem/mind'
|
|
32
|
+
import { PROVIDER_KEY_ENV, providerKeyFromEnv, type LLMProvider } from '#llm/index'
|
|
32
33
|
import type { PMASnapshot } from '#pma/index'
|
|
33
34
|
import type { effectorInvocation } from '#types'
|
|
34
35
|
import type { EffectorDeclaration, SchemaPrecondition } from '#agency/types'
|
|
@@ -55,6 +56,8 @@ export interface Stimulus {
|
|
|
55
56
|
speaker?: string
|
|
56
57
|
/** Conversation/thread id (default = `from`). */
|
|
57
58
|
thread?: string
|
|
59
|
+
/** True when `thread` is private — just this someone and the Will. See TextMessage.direct. */
|
|
60
|
+
direct?: boolean
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/** A message the Will emitted to someone. */
|
|
@@ -65,6 +68,20 @@ export interface WillMessage {
|
|
|
65
68
|
content: string
|
|
66
69
|
/** Entity id the Will addressed (the speaker you used in say()/tell(), or a bond). */
|
|
67
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
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
/**
|
|
@@ -165,20 +182,42 @@ export interface CreateWillOptions {
|
|
|
165
182
|
anatomy?: Anatomy
|
|
166
183
|
/** Concrete LLM model id, or a per-role map ({ executive, summarizer?,
|
|
167
184
|
* deliberation?, embedding? } — unset thinking roles fall back to executive).
|
|
168
|
-
* Unset → env / provider default.
|
|
185
|
+
* Unset → env / provider default.
|
|
186
|
+
* @deprecated Pass `llmConfig: { model }` instead — model and transport are
|
|
187
|
+
* one concern. Still honoured; an explicit `llmConfig.model` wins. */
|
|
169
188
|
model?: string | WillModelConfig
|
|
170
|
-
/** Per-Will LLM
|
|
189
|
+
/** Per-Will LLM config: provider, model(s), BYO apiKey, baseUrl, caps.
|
|
171
190
|
* Unset fields fall back to WILL_LLM_* envs. apiKey stays in memory only.
|
|
172
|
-
* (Named llmConfig because `llm` is the
|
|
191
|
+
* (Named llmConfig because `llm` is the provider MODE switch.) */
|
|
173
192
|
llmConfig?: WillLLMConfig
|
|
174
193
|
/**
|
|
175
|
-
* LLM mode
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
194
|
+
* LLM mode — which provider the executive speaks to.
|
|
195
|
+
*
|
|
196
|
+
* 'mock' (the default when no key is present) runs a deterministic canned
|
|
197
|
+
* executive: zero keys, zero cost. Every other value names a provider and
|
|
198
|
+
* needs its key, either the provider's own env below or the
|
|
199
|
+
* provider-agnostic WILL_LLM_API_KEY:
|
|
200
|
+
*
|
|
201
|
+
* anthropic ANTHROPIC_API_KEY Claude, native Messages wire
|
|
202
|
+
* glm ZAI_API_KEY Z.ai GLM, Anthropic-compatible wire
|
|
203
|
+
* openai OPENAI_API_KEY OpenAI wire
|
|
204
|
+
* google GOOGLE_API_KEY | GEMINI_API_KEY native Gemini wire
|
|
205
|
+
* deepseek DEEPSEEK_API_KEY OpenAI wire
|
|
206
|
+
* moonshot MOONSHOT_API_KEY Kimi — OpenAI wire
|
|
207
|
+
* qwen DASHSCOPE_API_KEY Alibaba Model Studio — OpenAI wire
|
|
208
|
+
* xai XAI_API_KEY Grok — OpenAI wire
|
|
209
|
+
* minimax MINIMAX_API_KEY OpenAI wire
|
|
210
|
+
* mistral MISTRAL_API_KEY OpenAI wire
|
|
211
|
+
* ollama · vllm local; no key, set `llm` explicitly
|
|
212
|
+
*
|
|
213
|
+
* Any other string works too — it just has to declare its `wire` and
|
|
214
|
+
* `baseUrl` on `llmConfig.providers`. Naming the vendor rather than
|
|
215
|
+
* borrowing `openai` because it speaks that wire is what keeps the
|
|
216
|
+
* completion tape and the cost breakdown honest.
|
|
217
|
+
*
|
|
218
|
+
* Omit to auto-detect from whichever key is set.
|
|
180
219
|
*/
|
|
181
|
-
llm?: 'mock' |
|
|
220
|
+
llm?: 'mock' | LLMProvider
|
|
182
221
|
/**
|
|
183
222
|
* Abilities the Will can choose to enact. `name → handler`, or
|
|
184
223
|
* `name → { handler, description?, cost?, valence?, preconditions? }` to seed
|
|
@@ -213,6 +252,41 @@ const AFFECT_EPSILON = 0.02
|
|
|
213
252
|
|
|
214
253
|
// ── The facade ────────────────────────────────────────────────
|
|
215
254
|
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Which provider to talk to when the caller did not say.
|
|
258
|
+
*
|
|
259
|
+
* Checks the provider-agnostic key first (it means "I configured this
|
|
260
|
+
* deliberately"), then each provider's conventional env. The order among
|
|
261
|
+
* providers is detection precedence when several keys happen to be present —
|
|
262
|
+
* it is not a ranking, and no provider here is more supported than another.
|
|
263
|
+
*/
|
|
264
|
+
export function detectProvider(): 'mock' | LLMProvider {
|
|
265
|
+
// A provider-agnostic key says nothing about who to send it to. Guessing
|
|
266
|
+
// here is how a key meant for one vendor ends up at another; the guess used
|
|
267
|
+
// to be 'anthropic' unconditionally.
|
|
268
|
+
if( process.env.WILL_LLM_API_KEY ){
|
|
269
|
+
const provider = process.env.WILL_LLM_PROVIDER
|
|
270
|
+
if( !provider )
|
|
271
|
+
throw new Error(
|
|
272
|
+
'WILL_LLM_API_KEY is set but WILL_LLM_PROVIDER is not — there is no way ' +
|
|
273
|
+
'to tell which provider that key belongs to. Set WILL_LLM_PROVIDER, or ' +
|
|
274
|
+
'use a provider-specific key (ANTHROPIC_API_KEY, ZAI_API_KEY, …).'
|
|
275
|
+
)
|
|
276
|
+
return provider as LLMProvider
|
|
277
|
+
}
|
|
278
|
+
// A provider-specific key IS the explicit statement — no guess involved.
|
|
279
|
+
// Order is precedence when several are present, and is append-only: moving an
|
|
280
|
+
// entry silently changes which vendor an existing environment talks to.
|
|
281
|
+
//
|
|
282
|
+
// Read through providerKeyFromEnv rather than raw truthiness, so "is a key
|
|
283
|
+
// set?" and "what is the key?" cannot disagree — a blank `XAI_API_KEY=` would
|
|
284
|
+
// otherwise select xai here and then supply nothing to call it with.
|
|
285
|
+
for( const provider of Object.keys( PROVIDER_KEY_ENV ) )
|
|
286
|
+
if( providerKeyFromEnv( provider ) ) return provider
|
|
287
|
+
return 'mock'
|
|
288
|
+
}
|
|
289
|
+
|
|
216
290
|
export class Will {
|
|
217
291
|
/** The underlying WillStem — drop here for the full contract. */
|
|
218
292
|
readonly stem: WillStem
|
|
@@ -261,17 +335,29 @@ export class Will {
|
|
|
261
335
|
pma: PMASnapshot,
|
|
262
336
|
opts: Omit<CreateWillOptions, 'identity'> & { identity?: Partial<WillIdentity> },
|
|
263
337
|
): Promise<Will> {
|
|
264
|
-
|
|
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 )}`
|
|
265
348
|
const stem = new WillStem()
|
|
266
349
|
const will = new Will( stem, id, opts.name )
|
|
267
350
|
|
|
268
351
|
for( const [ name, entry ] of Object.entries( opts.effectors ?? {} ) )
|
|
269
352
|
will._register( name, entry )
|
|
270
353
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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 */ )
|
|
275
361
|
stem.loadPMA( id, pma )
|
|
276
362
|
stem.resumeWill( id )
|
|
277
363
|
will._attach()
|
|
@@ -300,6 +386,8 @@ export class Will {
|
|
|
300
386
|
// the Will knows the person as "someone" until a real one is learned. (The live
|
|
301
387
|
// conversation focus still falls back to the entity id for its Speaker line.)
|
|
302
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 } : {} ),
|
|
303
391
|
} )
|
|
304
392
|
}
|
|
305
393
|
|
|
@@ -462,13 +550,22 @@ export class Will {
|
|
|
462
550
|
|
|
463
551
|
private _buildConfig( id: string, opts: CreateWillOptions ): WillConfig {
|
|
464
552
|
// Auto-detect from whichever provider key is present; explicit `llm` wins.
|
|
465
|
-
|
|
466
|
-
|
|
553
|
+
// Order is detection precedence when several keys are set, not a
|
|
554
|
+
// recommendation — every provider here is a first-class target.
|
|
555
|
+
const mode = opts.llm ?? detectProvider()
|
|
467
556
|
const useMock = mode === 'mock'
|
|
468
|
-
// `llm
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
557
|
+
// `llm` selects the provider; an explicit llmConfig.provider still wins.
|
|
558
|
+
// Model rides with the transport: `llmConfig.model` is canonical, the
|
|
559
|
+
// top-level `opts.model` is the deprecated spelling of the same thing.
|
|
560
|
+
const llmConfig: WillLLMConfig | undefined = useMock && !opts.llmConfig && opts.model === undefined
|
|
561
|
+
? undefined
|
|
562
|
+
: {
|
|
563
|
+
...( mode !== 'mock' ? { provider: mode } : {} ),
|
|
564
|
+
...opts.llmConfig,
|
|
565
|
+
...( opts.llmConfig?.model !== undefined ? { model: opts.llmConfig.model }
|
|
566
|
+
: opts.model !== undefined ? { model: opts.model }
|
|
567
|
+
: {} ),
|
|
568
|
+
}
|
|
472
569
|
return {
|
|
473
570
|
id, name: opts.name,
|
|
474
571
|
identity: {
|
|
@@ -478,7 +575,6 @@ export class Will {
|
|
|
478
575
|
style: opts.identity.style ?? '',
|
|
479
576
|
},
|
|
480
577
|
anatomy: opts.anatomy ?? 'mind',
|
|
481
|
-
model: opts.model,
|
|
482
578
|
llm: llmConfig,
|
|
483
579
|
testMode: useMock,
|
|
484
580
|
persistentMemory: opts.persist ?? false,
|
|
@@ -495,7 +591,10 @@ export class Will {
|
|
|
495
591
|
this._unsub = this.stem.addTickListener( this.id, ( _snapshot, _tick, outbox, invocations ) => {
|
|
496
592
|
// Outbound messages → event + auto delivery-ack.
|
|
497
593
|
for( const msg of outbox ){
|
|
498
|
-
this._emitMessage( {
|
|
594
|
+
this._emitMessage( {
|
|
595
|
+
id: msg.id, content: msg.content, to: msg.targetEntityId,
|
|
596
|
+
...( msg.threadId ? { thread: msg.threadId } : {} ),
|
|
597
|
+
} )
|
|
499
598
|
try { this.stem.confirmMessageDelivery( this.id, msg.id, true ) } catch { /* best-effort */ }
|
|
500
599
|
}
|
|
501
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
|
+
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// ─────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
20
|
import type { WillIdentity } from '#stem/mind'
|
|
21
|
-
import { LLMDirector,
|
|
21
|
+
import { LLMDirector, BACKGROUND_DEMAND, type LLMProvider, type LLMCallMeta } from '#llm/index'
|
|
22
22
|
import type { TokenTracker } from '#cognition/utilities/token.tracker'
|
|
23
23
|
|
|
24
24
|
export interface CoherenceIssue {
|
|
@@ -48,7 +48,10 @@ export interface IdentityReviewer {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** Attribution tag for the one creation-time coherence-review call. */
|
|
51
|
-
|
|
51
|
+
// MODEL_ROUTING W0 — a one-shot classification at creation time; constant low
|
|
52
|
+
// demand (structurally background, and it runs before there is a mind whose
|
|
53
|
+
// state could modulate it).
|
|
54
|
+
const COHERENCE_META: LLMCallMeta = { category: 'identity-guard', attribute: 'guard', process: 'cog', function: 'identity-coherence', demand: BACKGROUND_DEMAND }
|
|
52
55
|
|
|
53
56
|
const VALID_KINDS = new Set<CoherenceIssue['kind']>( [ 'contradiction', 'false-capability', 'injection', 'incoherence', 'other' ] )
|
|
54
57
|
|
|
@@ -112,13 +115,21 @@ export async function reviewIdentityCoherence(
|
|
|
112
115
|
input: CoherenceInput,
|
|
113
116
|
opts: { willId?: string; tokenTracker?: TokenTracker | null } = {},
|
|
114
117
|
): Promise<CoherenceResult> {
|
|
115
|
-
|
|
118
|
+
// No defaults: an unconfigured environment cannot review a persona, and
|
|
119
|
+
// silently reviewing it with somebody else's model is worse than not running.
|
|
120
|
+
const provider = process.env.WILL_LLM_PROVIDER
|
|
121
|
+
const model = process.env.WILL_LLM_MODEL
|
|
122
|
+
if( !provider || !model )
|
|
123
|
+
return { ok: true, ran: false, issues: [], raw: 'review skipped: WILL_LLM_PROVIDER / WILL_LLM_MODEL not set' }
|
|
124
|
+
|
|
116
125
|
const director = new LLMDirector({
|
|
117
126
|
willId: opts.willId ?? 'identity-coherence',
|
|
118
|
-
model
|
|
127
|
+
model,
|
|
119
128
|
maxOutputTokens: 512,
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
// Provider-agnostic key only — the old chain ended at ANTHROPIC_API_KEY,
|
|
130
|
+
// so a Will pointed at another vendor would have sent it an Anthropic key.
|
|
131
|
+
apiKey: process.env.WILL_LLM_API_KEY ?? '',
|
|
132
|
+
provider: provider as LLMProvider,
|
|
122
133
|
sessionLogger: null,
|
|
123
134
|
baseUrl: process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
|
|
124
135
|
// When a per-Will tracker is supplied, the creation-time review records under
|
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
|
|
|
@@ -95,7 +98,7 @@ export interface WillSummary {
|
|
|
95
98
|
createdAt: Date
|
|
96
99
|
lastTickAt: Date | null
|
|
97
100
|
anatomy: WillConfig['anatomy']
|
|
98
|
-
model: WillConfig['model']
|
|
101
|
+
model: NonNullable<WillConfig['llm']>['model']
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// Re-export WillConfig so the API layer only imports from manager
|
|
@@ -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 ) }
|
|
@@ -289,7 +350,7 @@ export class WillStem {
|
|
|
289
350
|
willId: config.id,
|
|
290
351
|
willName: config.name,
|
|
291
352
|
anatomy: config.anatomy ?? 'mind',
|
|
292
|
-
model: config.model ?? null,
|
|
353
|
+
model: config.llm?.model ?? null,
|
|
293
354
|
startedAt: new Date().toISOString(),
|
|
294
355
|
})
|
|
295
356
|
|
|
@@ -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
|
|
@@ -942,7 +1019,7 @@ export class WillStem {
|
|
|
942
1019
|
createdAt: inst.createdAt,
|
|
943
1020
|
lastTickAt: inst.lastTickAt,
|
|
944
1021
|
anatomy: inst.config.anatomy ?? 'mind',
|
|
945
|
-
model: inst.config.model,
|
|
1022
|
+
model: inst.config.llm?.model,
|
|
946
1023
|
}))
|
|
947
1024
|
}
|
|
948
1025
|
|