@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,204 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/llm/routing.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// MODEL_ROUTING W2 — per-call model selection.
|
|
6
|
+
//
|
|
7
|
+
// A Will runs one LLMDirector, and every call site shares it: the master's
|
|
8
|
+
// decision, each facet, the propose pass, the rolling summariser, the identity
|
|
9
|
+
// guard. They are not the same cognitive act, and they need not be the same
|
|
10
|
+
// inference. This module lets a host decide which model serves which call,
|
|
11
|
+
// without the engine learning anything it should not know.
|
|
12
|
+
//
|
|
13
|
+
// WHAT THIS IS NOT. This is a mechanism, never a policy. The engine may know
|
|
14
|
+
// that a call is routine or consequential — that is a cognitive fact it already
|
|
15
|
+
// computes (`LLMCallMeta.demand`). It must never know who is paying, what plan
|
|
16
|
+
// they are on, or what anything costs us. A router that needs commercial
|
|
17
|
+
// information is the host's to write; the seam below carries none of it.
|
|
18
|
+
//
|
|
19
|
+
// DETERMINISM CONTRACT. A router is an external oracle, exactly like the LLM
|
|
20
|
+
// itself and the policy arbiter. Its choice is already captured: the completion
|
|
21
|
+
// tape records `provider` and `model` on every call, and replay re-feeds the
|
|
22
|
+
// recorded completion rather than re-deciding. So replay never consults a
|
|
23
|
+
// router, and a run replays byte-for-byte whether the router is absent,
|
|
24
|
+
// present, or since reconfigured.
|
|
25
|
+
//
|
|
26
|
+
// SCOPE. A router may read the call meta and its own configuration, and nothing
|
|
27
|
+
// else. It must not reach into simulation state — that is what keeps this
|
|
28
|
+
// module below cognition (src/llm imports no cognition behaviour) and keeps
|
|
29
|
+
// routing from becoming a hidden input to the mind.
|
|
30
|
+
// ─────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
import { logger } from '#core/logger'
|
|
33
|
+
import type { LLMProvider, LLMCallMeta } from '#llm/index'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Where a single call should go. Every field except `model` falls back to the
|
|
37
|
+
* Will's default when omitted.
|
|
38
|
+
*/
|
|
39
|
+
export interface ModelRoute {
|
|
40
|
+
/**
|
|
41
|
+
* Omit to keep the Will's default provider and change only the model — the
|
|
42
|
+
* common "same vendor, different model for this kind of work" route, and what
|
|
43
|
+
* the per-role model map compiles to (a role has never had a provider of its
|
|
44
|
+
* own). Name a provider to cross vendors; it must appear in `llm.providers`
|
|
45
|
+
* or the route falls back to the default.
|
|
46
|
+
*/
|
|
47
|
+
provider?: LLMProvider
|
|
48
|
+
model: string
|
|
49
|
+
/** Override the provider's API base (self-hosted / OpenAI-compatible servers). */
|
|
50
|
+
baseUrl?: string
|
|
51
|
+
/** Override the output-token ceiling for this call. */
|
|
52
|
+
maxOutputTokens?: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Chooses a model for a call.
|
|
57
|
+
*
|
|
58
|
+
* Returning `null` means "no opinion" — the Will's default model is used. A
|
|
59
|
+
* router should return `null` rather than guess when it does not recognise a
|
|
60
|
+
* call: falling back is always safe, and a wrong route is not.
|
|
61
|
+
*/
|
|
62
|
+
export interface ModelRouter {
|
|
63
|
+
/** Stable identifier, recorded alongside routing telemetry. */
|
|
64
|
+
readonly name: string
|
|
65
|
+
route( meta: LLMCallMeta ): ModelRoute | null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The default. Has no opinion about anything, allocates nothing.
|
|
70
|
+
*
|
|
71
|
+
* A Will running this must be byte-identical to one built before the routing
|
|
72
|
+
* seam existed — that property is asserted by test, and it is what lets this
|
|
73
|
+
* ship dark.
|
|
74
|
+
*/
|
|
75
|
+
export const NULL_ROUTER: ModelRouter = {
|
|
76
|
+
name: 'null',
|
|
77
|
+
route(): ModelRoute | null { return null },
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** True when the router is the no-op default (used to skip the seam entirely). */
|
|
81
|
+
export function isNullRouter( router: ModelRouter | null | undefined ): boolean {
|
|
82
|
+
return !router || router === NULL_ROUTER
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Reference implementation ──────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One entry in a {@link TableRouter}'s table. All present conditions must match
|
|
89
|
+
* (logical AND); an absent condition matches anything.
|
|
90
|
+
*/
|
|
91
|
+
export interface RoutingRule {
|
|
92
|
+
/**
|
|
93
|
+
* Match `LLMCallMeta.category` exactly (e.g. 'executive', 'summarizer').
|
|
94
|
+
*
|
|
95
|
+
* The axes are typed rather than free strings so a rule that names a bucket
|
|
96
|
+
* the engine never emits fails to compile instead of silently never matching
|
|
97
|
+
* — a routing table's worst failure is the rule that looks right and is dead.
|
|
98
|
+
*/
|
|
99
|
+
category?: LLMCallMeta['category']
|
|
100
|
+
/** Match `LLMCallMeta.attribute` exactly (e.g. 'master', 'facet', 'guard'). */
|
|
101
|
+
attribute?: LLMCallMeta['attribute']
|
|
102
|
+
/** Match `LLMCallMeta.process` exactly (e.g. 'decision', 'ideation'). */
|
|
103
|
+
process?: LLMCallMeta['process']
|
|
104
|
+
/** Match `LLMCallMeta.function` exactly (e.g. 'decision', 'consolidation'). */
|
|
105
|
+
function?: LLMCallMeta['function']
|
|
106
|
+
/**
|
|
107
|
+
* Inclusive lower bound on `LLMCallMeta.demand`. A call with no demand
|
|
108
|
+
* reported never matches a rule that sets this — absent means unknown, and
|
|
109
|
+
* unknown must not be treated as zero.
|
|
110
|
+
*/
|
|
111
|
+
minDemand?: number
|
|
112
|
+
/** Exclusive upper bound on `LLMCallMeta.demand`. Same absence rule. */
|
|
113
|
+
maxDemand?: number
|
|
114
|
+
/** Where a matching call goes. */
|
|
115
|
+
route: ModelRoute
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A worked example of the seam: first matching rule wins, otherwise no opinion.
|
|
120
|
+
*
|
|
121
|
+
* This ships so that the interface has a reference implementation and so that
|
|
122
|
+
* hosts have something to copy — it is deliberately dumb. It is not a routing
|
|
123
|
+
* strategy, and the engine ships no table of its own: what belongs where is the
|
|
124
|
+
* host's decision, expressed as configuration.
|
|
125
|
+
*
|
|
126
|
+
* Rules are evaluated in order, so put specific rules before general ones.
|
|
127
|
+
*/
|
|
128
|
+
export class TableRouter implements ModelRouter {
|
|
129
|
+
readonly name: string
|
|
130
|
+
private readonly _rules: readonly RoutingRule[]
|
|
131
|
+
|
|
132
|
+
constructor( rules: readonly RoutingRule[], name = 'table' ){
|
|
133
|
+
this._rules = [ ...rules ]
|
|
134
|
+
this.name = name
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
route( meta: LLMCallMeta ): ModelRoute | null {
|
|
138
|
+
for( const rule of this._rules ){
|
|
139
|
+
if( matches( rule, meta ) ) return rule.route
|
|
140
|
+
}
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Ask each router in turn; the first with an opinion wins.
|
|
147
|
+
*
|
|
148
|
+
* This exists because a Will can have two sources of routing at once: the
|
|
149
|
+
* host's own router, and the one compiled from its per-role model map. Order
|
|
150
|
+
* expresses precedence — an explicit router is consulted before the role map,
|
|
151
|
+
* which is the precedence those two mechanisms already had when roles were
|
|
152
|
+
* served by separate directors.
|
|
153
|
+
*
|
|
154
|
+
* A throwing link is skipped, not propagated. The links are independent
|
|
155
|
+
* decisions, and one broken router must not take a working one down with it —
|
|
156
|
+
* that would silently demote every role-mapped call to the default model.
|
|
157
|
+
*/
|
|
158
|
+
export function chainRouters( ...routers: ( ModelRouter | null | undefined )[] ): ModelRouter {
|
|
159
|
+
const chain = routers.filter( ( r ): r is ModelRouter => !isNullRouter( r ) )
|
|
160
|
+
if( chain.length === 0 ) return NULL_ROUTER
|
|
161
|
+
if( chain.length === 1 ) return chain[ 0 ]!
|
|
162
|
+
|
|
163
|
+
const warned = new Set<string>()
|
|
164
|
+
return {
|
|
165
|
+
name: chain.map( r => r.name ).join('>'),
|
|
166
|
+
route( meta: LLMCallMeta ): ModelRoute | null {
|
|
167
|
+
for( const router of chain ){
|
|
168
|
+
try {
|
|
169
|
+
const hit = router.route( meta )
|
|
170
|
+
if( hit ) return hit
|
|
171
|
+
}
|
|
172
|
+
catch( err ){
|
|
173
|
+
if( !warned.has( router.name ) ){
|
|
174
|
+
warned.add( router.name )
|
|
175
|
+
logger.warn(`[llm.routing] router "${router.name}" threw — skipping it: ${( err as Error ).message}`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function matches( rule: RoutingRule, meta: LLMCallMeta ): boolean {
|
|
185
|
+
if( rule.category !== undefined && rule.category !== meta.category ) return false
|
|
186
|
+
if( rule.attribute !== undefined && rule.attribute !== meta.attribute ) return false
|
|
187
|
+
// `process` split out of `function` (ideation vs decision). It was declarable on a
|
|
188
|
+
// rule from the moment the axis existed but never tested here, so a rule that named
|
|
189
|
+
// it matched every process — the opposite of the AND semantics every other axis has.
|
|
190
|
+
if( rule.process !== undefined && rule.process !== meta.process ) return false
|
|
191
|
+
if( rule.function !== undefined && rule.function !== meta.function ) return false
|
|
192
|
+
|
|
193
|
+
// Absent demand is UNKNOWN, not zero: a demand-bounded rule cannot claim a
|
|
194
|
+
// call whose demand was never measured.
|
|
195
|
+
const bounded = rule.minDemand !== undefined || rule.maxDemand !== undefined
|
|
196
|
+
if( bounded ){
|
|
197
|
+
const d = meta.demand
|
|
198
|
+
if( typeof d !== 'number' || Number.isNaN( d ) ) return false
|
|
199
|
+
if( rule.minDemand !== undefined && d < rule.minDemand ) return false
|
|
200
|
+
if( rule.maxDemand !== undefined && d >= rule.maxDemand ) return false
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return true
|
|
204
|
+
}
|
package/src/llm/summarizer.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { logger } from '#core/logger'
|
|
19
19
|
import type { LLMDirector } from '#llm/index'
|
|
20
|
+
import { BACKGROUND_DEMAND } from '#llm/index'
|
|
20
21
|
|
|
21
22
|
export interface SummarizerConfig {
|
|
22
23
|
/** How many executive calls between summarization runs. Default: 10 */
|
|
@@ -148,7 +149,10 @@ export class ExecutiveSummarizer {
|
|
|
148
149
|
userMessage,
|
|
149
150
|
this._callCount as any,
|
|
150
151
|
undefined,
|
|
151
|
-
|
|
152
|
+
// MODEL_ROUTING W0 — compression is background work at a constant low
|
|
153
|
+
// demand: distilling excerpts is the same job whether the mind is calm
|
|
154
|
+
// or in crisis, so there is no honest per-tick measure to forward here.
|
|
155
|
+
{ category: 'summarizer', attribute: 'memory', process: 'cog', function: 'consolidation', demand: BACKGROUND_DEMAND }
|
|
152
156
|
)
|
|
153
157
|
|
|
154
158
|
if( result.text ){
|
|
@@ -34,6 +34,25 @@ export const REPLY_TEXT_TAG = 'REPLY_TEXT'
|
|
|
34
34
|
export const REPLY_TEXT_OPEN = `[${REPLY_TEXT_TAG}]`
|
|
35
35
|
export const REPLY_TEXT_CLOSE = `[/${REPLY_TEXT_TAG}]`
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The block a facet uses to say it has decided NOT to speak, and why.
|
|
39
|
+
*
|
|
40
|
+
* "Omit [REPLY_TEXT] to stay silent" was stated in the prompt and nowhere else —
|
|
41
|
+
* a rule with no mechanism behind it. Live, an outreach facet read that it had
|
|
42
|
+
* three unanswered messages outstanding, correctly concluded there was nothing new
|
|
43
|
+
* to say, and — having no other block to put that sentence in — wrote it into
|
|
44
|
+
* REPLY_TEXT. "— nothing new to say to FKEM. Three messages unanswered is enough."
|
|
45
|
+
* was delivered to FKEM, in the third person, about him.
|
|
46
|
+
*
|
|
47
|
+
* The prompt already warned against exactly that. Warning was not enough, because
|
|
48
|
+
* the pressure is structural: a mind that decides not to speak still has something
|
|
49
|
+
* to say about the decision, and REPLY_TEXT was the only place to say it. This is
|
|
50
|
+
* the place. Content here is recorded and never sent.
|
|
51
|
+
*/
|
|
52
|
+
export const NO_MESSAGE_TAG = 'NO_MESSAGE'
|
|
53
|
+
export const NO_MESSAGE_OPEN = `[${NO_MESSAGE_TAG}]`
|
|
54
|
+
export const NO_MESSAGE_CLOSE = `[/${NO_MESSAGE_TAG}]`
|
|
55
|
+
|
|
37
56
|
/** Wrap a reply body in the block markers (the mock's emission shape). */
|
|
38
57
|
export function wrapReplyText( body: string ): string {
|
|
39
58
|
return [ REPLY_TEXT_OPEN, body, REPLY_TEXT_CLOSE ].join('\n')
|
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 ─────────────────────────────────
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
import type { WillConfig } from '#stem/mind'
|
|
18
18
|
import type { StateSnapshot } from '#stem/index'
|
|
19
|
+
import type { TokenTracker } from '#cognition/utilities/token.tracker'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The Will's token tracker, for cost display. Held here rather than passed in
|
|
23
|
+
* because printStatus is registered as a TickListener, whose third parameter is
|
|
24
|
+
* the outbox. Cost is no longer a state metric (W8c) — it lives on the tracker.
|
|
25
|
+
*/
|
|
26
|
+
let _tracker: TokenTracker | undefined
|
|
19
27
|
import { WillStem } from '#stem/index'
|
|
20
28
|
|
|
21
29
|
// ── Env config ────────────────────────────────────────────────
|
|
@@ -88,14 +96,15 @@ function printStatus( snapshot: StateSnapshot, tick: number ): void {
|
|
|
88
96
|
` entities=${entities}`
|
|
89
97
|
)
|
|
90
98
|
|
|
91
|
-
// LLM stats every LOG_INTERVAL
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
99
|
+
// LLM stats every LOG_INTERVAL. Tokens come from state; cost comes from the
|
|
100
|
+
// tracker — dollars are host accounting and no longer live in state (W8c).
|
|
101
|
+
const totalCalls = snapshot.metrics.get('llm.total_calls') ?? 0
|
|
102
|
+
const cost = _tracker
|
|
103
|
+
? ` total_cost=$${_tracker.totalCostUsd.toFixed( 4 )}`
|
|
104
|
+
: ''
|
|
95
105
|
|
|
96
106
|
console.log(
|
|
97
|
-
` [llm]
|
|
98
|
-
` tick_cost=$${costThisTick}` +
|
|
107
|
+
` [llm]${cost}` +
|
|
99
108
|
` calls=${totalCalls}` +
|
|
100
109
|
` prompt=${snapshot.metrics.get('llm.prompt_tokens_total') ?? 0}` +
|
|
101
110
|
` completion=${snapshot.metrics.get('llm.completion_tokens_total') ?? 0}`
|
|
@@ -141,6 +150,9 @@ async function main(): Promise<void> {
|
|
|
141
150
|
console.log('[init] Assembling mind...')
|
|
142
151
|
await manager.createWill( willConfig )
|
|
143
152
|
|
|
153
|
+
// Cost is no longer a state metric (W8c) — read it off the Will's tracker.
|
|
154
|
+
_tracker = manager.getWillCognition( WILL_ID ).tokenTracker
|
|
155
|
+
|
|
144
156
|
manager.addTickListener( WILL_ID, printStatus )
|
|
145
157
|
|
|
146
158
|
// Graceful shutdown
|