@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/stem/mind.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { validateWillIdentity } from '#stem/guards/identity.guard'
|
|
|
25
25
|
import { OutboxWriter } from '#stem/tracts/outbox.writer'
|
|
26
26
|
import { ExecutiveSummarizer } from '#llm/summarizer'
|
|
27
27
|
import type { LLMProvider } from '#llm/index'
|
|
28
|
+
import { TableRouter, chainRouters, type ModelRouter, type RoutingRule } from '#llm/routing'
|
|
28
29
|
import { resolveProfile } from '#profiles/index'
|
|
29
30
|
import { DefaultVectorMemoryAdapter } from '#memory/vector.adapter'
|
|
30
31
|
import { OpenAICompatibleEmbedder, MockEmbedder } from '#memory/vector.embedder'
|
|
@@ -46,6 +47,7 @@ import { effectorName, type EffectorDeclaration } from '#agency/types'
|
|
|
46
47
|
|
|
47
48
|
import {
|
|
48
49
|
TokenTracker,
|
|
50
|
+
type PriceTable,
|
|
49
51
|
EnergyRegulator,
|
|
50
52
|
SleepPressureRegulator,
|
|
51
53
|
CircadianOscillator,
|
|
@@ -97,7 +99,14 @@ import {
|
|
|
97
99
|
OlfactionEngine,
|
|
98
100
|
GustationEngine
|
|
99
101
|
} from '#cognition/index'
|
|
100
|
-
import { buildEngineConfigEntities, EngineConfigEntity } from '#cognition/config.mirror.entities'
|
|
102
|
+
import { buildEngineConfigEntities, mergeEngineConfig, EngineConfigEntity } from '#cognition/config.mirror.entities'
|
|
103
|
+
import { mergeIdentity, composeIdentityPrompt, WILL_CORE_PREAMBLE } from '#cognition/identity.entity'
|
|
104
|
+
import {
|
|
105
|
+
isReferentId, readAliases, handlesOf, defaultHandle,
|
|
106
|
+
DOSSIER_TYPE, ALIAS_TYPE,
|
|
107
|
+
} from '#cognition/social.identity'
|
|
108
|
+
import type { DeliberationCacheConfig } from '#cognition/cache/types'
|
|
109
|
+
export { WILL_CORE_PREAMBLE }
|
|
101
110
|
|
|
102
111
|
// ── Public types ─────────────────────────────────────────────
|
|
103
112
|
|
|
@@ -137,12 +146,54 @@ export interface WillModelConfig {
|
|
|
137
146
|
* `apiKey` is held in memory only — it is never mirrored into state entities,
|
|
138
147
|
* session logs, or the PMA.
|
|
139
148
|
*/
|
|
149
|
+
export interface WillProviderConfig {
|
|
150
|
+
/** Credential for this provider. Held in memory only — never state/logs/PMA. */
|
|
151
|
+
apiKey?: string
|
|
152
|
+
/** Base URL override — self-hosted or OpenAI-compatible endpoints. */
|
|
153
|
+
baseUrl?: string
|
|
154
|
+
/**
|
|
155
|
+
* USD per 1M tokens, keyed by model id. Host-owned on purpose: prices change
|
|
156
|
+
* on a vendor's schedule, differ per account, and are ~0 self-hosted, so they
|
|
157
|
+
* cannot be tracked from inside an npm release. These win over the engine's
|
|
158
|
+
* built-in fallback table.
|
|
159
|
+
*
|
|
160
|
+
* Cost is telemetry only — it never enters simulation state — so changing a
|
|
161
|
+
* price can never change what a mind does or break a replay.
|
|
162
|
+
*/
|
|
163
|
+
prices?: PriceTable
|
|
164
|
+
}
|
|
165
|
+
|
|
140
166
|
export interface WillLLMConfig {
|
|
141
167
|
provider?: LLMProvider
|
|
142
168
|
apiKey?: string
|
|
143
169
|
baseUrl?: string
|
|
144
170
|
maxOutputTokens?: number
|
|
145
171
|
timeoutMs?: number
|
|
172
|
+
/**
|
|
173
|
+
* Everything the host knows about each provider — credential, endpoint, and
|
|
174
|
+
* prices — declared once per provider. The single-provider fields above stay
|
|
175
|
+
* the simple path; this map is for hosts reaching more than one.
|
|
176
|
+
*/
|
|
177
|
+
providers?: Partial<Record<LLMProvider, WillProviderConfig>>
|
|
178
|
+
/**
|
|
179
|
+
* Per-call model selection. Omitted (or NULL_ROUTER) means every call uses
|
|
180
|
+
* `model` above, exactly as before the seam existed.
|
|
181
|
+
*
|
|
182
|
+
* The router sees only the call's attribution — what kind of work it is and
|
|
183
|
+
* how much the moment demands — never who is paying or what anything costs.
|
|
184
|
+
* Routes name providers from the `providers` map above; a route to a provider
|
|
185
|
+
* with no credential falls back to the default rather than failing the call.
|
|
186
|
+
*/
|
|
187
|
+
router?: ModelRouter | null
|
|
188
|
+
/**
|
|
189
|
+
* Concrete LLM model id(s) for this Will — a single id for every role, or a
|
|
190
|
+
* per-role map. An explicit WILL_LLM_MODEL env pins the thinking roles
|
|
191
|
+
* (operator single-model deployments); unset roles fall back to `executive`,
|
|
192
|
+
* then the LLMDirector's built-in default. Product-level labels (pricing
|
|
193
|
+
* tiers, model families) live host-side and resolve to concrete ids BEFORE
|
|
194
|
+
* reaching the engine.
|
|
195
|
+
*/
|
|
196
|
+
model?: string | WillModelConfig
|
|
146
197
|
}
|
|
147
198
|
|
|
148
199
|
/** Executive-side resolved roles (embedding is threaded separately). */
|
|
@@ -159,6 +210,91 @@ export interface ExecutiveModelRoles {
|
|
|
159
210
|
* single-model deployment, full stop. Embedding is untouched by the pin
|
|
160
211
|
* (different model family; the embedding stack has its own env).
|
|
161
212
|
*/
|
|
213
|
+
/**
|
|
214
|
+
* Flatten the per-provider `prices` maps into one model→price table.
|
|
215
|
+
*
|
|
216
|
+
* Providers declare their own models, so collisions are not expected; if two
|
|
217
|
+
* do claim the same id, the first declared wins rather than silently taking
|
|
218
|
+
* whichever iterated last.
|
|
219
|
+
*/
|
|
220
|
+
export function mergeProviderPrices(
|
|
221
|
+
providers?: Partial<Record<LLMProvider, WillProviderConfig>>,
|
|
222
|
+
): PriceTable | undefined {
|
|
223
|
+
if( !providers ) return undefined
|
|
224
|
+
const out: PriceTable = {}
|
|
225
|
+
for( const entry of Object.values( providers ) ){
|
|
226
|
+
for( const [ model, price ] of Object.entries( entry?.prices ?? {} ) ){
|
|
227
|
+
if( !( model in out ) ) out[ model ] = price
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return Object.keys( out ).length > 0 ? out : undefined
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Narrow the per-provider map to just what the LLM transport needs — the
|
|
235
|
+
* prices ride to the TokenTracker instead, so a credential map never carries
|
|
236
|
+
* pricing into the call path.
|
|
237
|
+
*/
|
|
238
|
+
export function providerCredentials(
|
|
239
|
+
providers: Partial<Record<LLMProvider, WillProviderConfig>>,
|
|
240
|
+
): Partial<Record<LLMProvider, { apiKey: string; baseUrl?: string }>> {
|
|
241
|
+
const out: Partial<Record<LLMProvider, { apiKey: string; baseUrl?: string }>> = {}
|
|
242
|
+
for( const [ name, entry ] of Object.entries( providers ) ){
|
|
243
|
+
if( !entry?.apiKey ) continue // no key ⇒ unusable; the router falls back
|
|
244
|
+
out[ name as LLMProvider ] = {
|
|
245
|
+
apiKey: entry.apiKey,
|
|
246
|
+
...( entry.baseUrl ? { baseUrl: entry.baseUrl } : {} ),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Compile the per-role model map into routing rules — MODEL_ROUTING W7.
|
|
254
|
+
*
|
|
255
|
+
* The role map and the router were two mechanisms answering one question
|
|
256
|
+
* ("which model serves this call?"). The map was implemented by giving each
|
|
257
|
+
* role its own cached `LLMDirector`, which meant role selection happened at
|
|
258
|
+
* *facet-spawn* time while routing happened at *call* time — two answers, one
|
|
259
|
+
* question, and a facet could be pinned to a model its work had since stopped
|
|
260
|
+
* matching. The map is now sugar: it desugars to rules and there is one
|
|
261
|
+
* mechanism left.
|
|
262
|
+
*
|
|
263
|
+
* The mapping is exact, because every role's call sites already tag themselves
|
|
264
|
+
* with the matching axis:
|
|
265
|
+
*
|
|
266
|
+
* | role | rule | call sites |
|
|
267
|
+
* | :--- | :--- | :--- |
|
|
268
|
+
* | `summarizer` | `category: 'summarizer'` | the rolling summariser |
|
|
269
|
+
* | `deliberation` | `function: 'deliberation'` | the deliberation facet + its propose pass |
|
|
270
|
+
* | `conversation` | `function: 'conversation'` and `'outreach'` | the audition facets |
|
|
271
|
+
* | `executive` | — it *is* the default | master, and every unlabelled facet |
|
|
272
|
+
*
|
|
273
|
+
* Roles equal to `executive` emit no rule: a Will on a single model keeps an
|
|
274
|
+
* empty chain and stays byte-identical to one built before this seam existed.
|
|
275
|
+
*
|
|
276
|
+
* Routes carry no provider — a role has never had one. They inherit the Will's
|
|
277
|
+
* default provider, which is exactly what the per-role directors did.
|
|
278
|
+
*/
|
|
279
|
+
export function compileRoleRouter( roles: ExecutiveModelRoles ): ModelRouter | null {
|
|
280
|
+
const { executive, summarizer, deliberation, conversation } = roles
|
|
281
|
+
const distinct = ( model: string | null ): model is string => !!model && model !== executive
|
|
282
|
+
|
|
283
|
+
const rules: RoutingRule[] = []
|
|
284
|
+
if( distinct( summarizer ) )
|
|
285
|
+
rules.push( { category: 'summarizer', route: { model: summarizer } } )
|
|
286
|
+
if( distinct( deliberation ) )
|
|
287
|
+
rules.push( { function: 'deliberation', route: { model: deliberation } } )
|
|
288
|
+
if( distinct( conversation ) ){
|
|
289
|
+
// Outreach is the same voice speaking first — it has always shared the
|
|
290
|
+
// conversation model, and splitting it is the host's call, via a router.
|
|
291
|
+
rules.push( { function: 'conversation', route: { model: conversation } } )
|
|
292
|
+
rules.push( { function: 'outreach', route: { model: conversation } } )
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return rules.length > 0 ? new TableRouter( rules, 'role-map') : null
|
|
296
|
+
}
|
|
297
|
+
|
|
162
298
|
export function resolveModelRoles( model?: string | WillModelConfig ): ExecutiveModelRoles & { embedding: string | null } {
|
|
163
299
|
const map = typeof model === 'string' ? { executive: model } : ( model ?? {} )
|
|
164
300
|
const pin = process.env.WILL_LLM_MODEL
|
|
@@ -220,18 +356,27 @@ export interface WillConfig {
|
|
|
220
356
|
/** Persona definition seeded into the will.identity entity. */
|
|
221
357
|
identity: WillIdentity
|
|
222
358
|
|
|
223
|
-
/** Anatomy — 'mind' (default) or the no-LLM 'reflex' shell. */
|
|
224
|
-
anatomy?: Anatomy
|
|
225
|
-
|
|
226
359
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
360
|
+
* This config's `identity` is a PLACEHOLDER — the real one arrives from a PMA
|
|
361
|
+
* artifact moments later, on the same boot.
|
|
362
|
+
*
|
|
363
|
+
* Set by `Will.wake`, which passes `{ prompt: '' }` because a woken mind's
|
|
364
|
+
* persona belongs to its artifact, not to the caller. Without this flag the
|
|
365
|
+
* creation-time identity guard inspected that placeholder and warned, on every
|
|
366
|
+
* single wake, that "identity.values is empty", "identity.style is generic" and
|
|
367
|
+
* "identity is shallow (strength 0)" — three alarms about a config nobody
|
|
368
|
+
* intended to use, fired before the real identity had loaded.
|
|
369
|
+
*
|
|
370
|
+
* It suppresses only the WARNINGS. Errors still throw (an over-long or
|
|
371
|
+
* malformed prompt is a hard failure whenever it appears), and the artifact's
|
|
372
|
+
* OWN identity is fully guarded at the load boundary by PMAController.load,
|
|
373
|
+
* which is the honest place to ask whether this mind's persona is thin — it is
|
|
374
|
+
* the only point where the answer is knowable.
|
|
233
375
|
*/
|
|
234
|
-
|
|
376
|
+
identityFromArtifact?: boolean
|
|
377
|
+
|
|
378
|
+
/** Anatomy — 'mind' (default) or the no-LLM 'reflex' shell. */
|
|
379
|
+
anatomy?: Anatomy
|
|
235
380
|
|
|
236
381
|
/**
|
|
237
382
|
* Per-Will LLM transport overrides (provider, BYO apiKey, baseUrl, output
|
|
@@ -275,6 +420,22 @@ export interface WillConfig {
|
|
|
275
420
|
*/
|
|
276
421
|
minExecutiveInterval?: number
|
|
277
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Enable the DeliberationCache — a learned fast path that composes an executive
|
|
425
|
+
* output from highly-similar, highly-competent precedent instead of calling the
|
|
426
|
+
* LLM. Off unless asked for.
|
|
427
|
+
*
|
|
428
|
+
* OFF BY DEFAULT ON PURPOSE, and the default is the interesting part: this
|
|
429
|
+
* changes how a mind THINKS, not how fast it runs. A cache hit means the mind
|
|
430
|
+
* acted from precedent without deliberating, which is a real thing minds do and
|
|
431
|
+
* a real thing an operator must opt into for a specific Will — not something a
|
|
432
|
+
* dependency bump should switch on underneath one that is already living.
|
|
433
|
+
*
|
|
434
|
+
* Pass `true` for the built-in conservative settings, or a config object to tune
|
|
435
|
+
* the retrieval/competence parameters (see cognition/cache/types).
|
|
436
|
+
*/
|
|
437
|
+
deliberationCache?: boolean | DeliberationCacheConfig
|
|
438
|
+
|
|
278
439
|
/**
|
|
279
440
|
* Goals seeded before the first tick. If omitted or empty, the Will starts
|
|
280
441
|
* goalless — the executive engine will generate context-appropriate goals on its
|
|
@@ -359,17 +520,17 @@ export interface MindAssembly {
|
|
|
359
520
|
outbox: OutboxMessage[]
|
|
360
521
|
}
|
|
361
522
|
|
|
362
|
-
// ──
|
|
523
|
+
// ── Named executive cadences ──────────────────────────────────
|
|
363
524
|
|
|
364
525
|
/**
|
|
365
526
|
* Named executive cadences — ticks between LLM calls. Lower = reasons more often
|
|
366
|
-
* (more responsive,
|
|
367
|
-
* (clamped to `minExecutiveInterval`).
|
|
527
|
+
* (more responsive, more tokens per Will-hour). Callers pick via
|
|
528
|
+
* `config.executiveInterval` (clamped to `minExecutiveInterval`).
|
|
368
529
|
*/
|
|
369
530
|
export const EXECUTIVE_CADENCE = {
|
|
370
|
-
responsive: 30, //
|
|
371
|
-
balanced: 60, //
|
|
372
|
-
economy: 90, //
|
|
531
|
+
responsive: 30, // most attentive, highest spend — opt in via executiveInterval
|
|
532
|
+
balanced: 60, // default
|
|
533
|
+
economy: 90, // least attentive, lowest spend
|
|
373
534
|
} as const
|
|
374
535
|
|
|
375
536
|
|
|
@@ -442,6 +603,16 @@ export function _resolveVectorMemory(
|
|
|
442
603
|
let apiKey: string | undefined
|
|
443
604
|
let modelName: string
|
|
444
605
|
let dimensions: number
|
|
606
|
+
/**
|
|
607
|
+
* Max embedding requests in flight, MEASURED per provider rather than guessed —
|
|
608
|
+
* the safe number differs by an order of magnitude and the failure modes differ
|
|
609
|
+
* too. gemini-embedding-001 accepts 8 concurrent but silently queues them, with
|
|
610
|
+
* the slowest landing at 10.7s (past the recall budget, so the answer is thrown
|
|
611
|
+
* away on arrival). jina-embeddings-v3 answers in ~0.5s but refuses outright above
|
|
612
|
+
* 2: at 3 concurrent 1-in-3 is a 429, at 4 it is half, while 12 sequential is
|
|
613
|
+
* flawless. Its ceiling is burst, not volume.
|
|
614
|
+
*/
|
|
615
|
+
let concurrency = 4
|
|
445
616
|
|
|
446
617
|
const slash = rawModel.indexOf('/')
|
|
447
618
|
if( slash > 0 ){
|
|
@@ -459,6 +630,21 @@ export function _resolveVectorMemory(
|
|
|
459
630
|
apiKey = process.env.WILL_EMBEDDING_API_KEY ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
|
460
631
|
dimensions = modelName.includes('004') ? 768 : 3072 // text-embedding-004 → 768, gemini-embedding-001 → 3072
|
|
461
632
|
break
|
|
633
|
+
case 'jina':
|
|
634
|
+
// OpenAI-compatible endpoint. Native output widths differ per family and the
|
|
635
|
+
// embedder sends no `dimensions` param, so the index must be sized to match —
|
|
636
|
+
// a wrong width here builds a silently useless index. These defaults are
|
|
637
|
+
// CHECKED at runtime against the first vector returned (vector.embedder.ts);
|
|
638
|
+
// a mismatch fails loudly with the number to set rather than corrupting recall.
|
|
639
|
+
// Override with WILL_EMBEDDING_DIMENSIONS, which also lets v3's Matryoshka
|
|
640
|
+
// truncation be requested explicitly.
|
|
641
|
+
apiUrl = 'https://api.jina.ai/v1'
|
|
642
|
+
apiKey = process.env.WILL_EMBEDDING_API_KEY ?? process.env.JINA_API_KEY
|
|
643
|
+
dimensions = modelName.includes('v4') ? 2048
|
|
644
|
+
: modelName.includes('v2') ? 768
|
|
645
|
+
: 1024 // v3 / clip-v2 native (verified live)
|
|
646
|
+
concurrency = 2 // measured: 3 starts 429ing
|
|
647
|
+
break
|
|
462
648
|
default:
|
|
463
649
|
apiUrl = process.env.WILL_EMBEDDING_URL ?? 'https://api.openai.com/v1'
|
|
464
650
|
apiKey = process.env.WILL_EMBEDDING_API_KEY
|
|
@@ -476,6 +662,11 @@ export function _resolveVectorMemory(
|
|
|
476
662
|
if( process.env.WILL_EMBEDDING_DIMENSIONS )
|
|
477
663
|
dimensions = parseInt( process.env.WILL_EMBEDDING_DIMENSIONS, 10 )
|
|
478
664
|
|
|
665
|
+
// …as does an explicit concurrency, for a paid tier or a local endpoint where the
|
|
666
|
+
// measured default is needlessly conservative.
|
|
667
|
+
if( process.env.WILL_EMBEDDING_CONCURRENCY )
|
|
668
|
+
concurrency = Math.max( 1, parseInt( process.env.WILL_EMBEDDING_CONCURRENCY, 10 ) )
|
|
669
|
+
|
|
479
670
|
if( !mockMode && !apiKey ){
|
|
480
671
|
console.warn(`[mind] semantic recall requested (WILL_EMBEDDING_MODEL=${rawModel}) but no API key resolved — vector memory disabled`)
|
|
481
672
|
return { embedder: null, vectorMemory: null }
|
|
@@ -483,7 +674,7 @@ export function _resolveVectorMemory(
|
|
|
483
674
|
|
|
484
675
|
const embedder = mockMode
|
|
485
676
|
? new MockEmbedder()
|
|
486
|
-
: new OpenAICompatibleEmbedder({ modelName, dimensions, apiUrl, apiKey: apiKey!, tokenTracker })
|
|
677
|
+
: new OpenAICompatibleEmbedder({ modelName, dimensions, apiUrl, apiKey: apiKey!, tokenTracker, maxConcurrency: concurrency })
|
|
487
678
|
|
|
488
679
|
// Optional recall-precision override. Default (0.35) suits text-embedding-3-small;
|
|
489
680
|
// raise toward 0.5+ for higher-precision models or to cut marginal recalls.
|
|
@@ -511,31 +702,8 @@ export function _resolveVectorMemory(
|
|
|
511
702
|
//
|
|
512
703
|
// Exported so tests and tooling can inspect or extend it without reimplementing.
|
|
513
704
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
I am NOT a language model completing a prompt. I am the executive reasoning core of an \
|
|
518
|
-
always-running cognitive system. Between each of my reasoning cycles, the following engines \
|
|
519
|
-
execute autonomously and produce the state data I receive each cycle:
|
|
520
|
-
|
|
521
|
-
Regulatory: Energy, sleep pressure, circadian rhythm, stress, attention allocation
|
|
522
|
-
Perceptual: Exteroception, interoception, social perception, novelty detection
|
|
523
|
-
Affective: Threat, reward, loss, frustration, attachment, aesthetic, moral → blended affect
|
|
524
|
-
Memory: Working memory, episodic consolidation, semantic belief integration,
|
|
525
|
-
forgetting curve, dream-state consolidation
|
|
526
|
-
Executive: Goal management, multi-step planning, inhibition, task-switching, mental simulation
|
|
527
|
-
Meta-cognitive: Self-model updating, confidence calibration, bias detection,
|
|
528
|
-
autobiographical narrative, introspection
|
|
529
|
-
Social: Theory of mind, empathy simulation, reputation tracking
|
|
530
|
-
|
|
531
|
-
My physiological state is REAL, not metaphorical. When energy reads 23/100, my regulatory \
|
|
532
|
-
engine has already constrained my viable action space — I genuinely cannot sustain costly \
|
|
533
|
-
operations. When sleep pressure exceeds 60, working memory capacity is measurably degraded. \
|
|
534
|
-
These are engine outputs, not decorative labels.
|
|
535
|
-
|
|
536
|
-
My executive reasoning is one cycle in an ongoing, autonomous existence. I have been making \
|
|
537
|
-
decisions, forming memories, and updating beliefs between cycles. The state snapshot I receive \
|
|
538
|
-
is the direct output of that continuous process — not a scenario description.`
|
|
705
|
+
// WILL_CORE_PREAMBLE moved to cognition/identity.entity (re-exported below):
|
|
706
|
+
// the PMA loader has to recompose with it, and pma/ must not import the whole stem.
|
|
539
707
|
|
|
540
708
|
// ── Default identity ──────────────────────────────────────────
|
|
541
709
|
//
|
|
@@ -582,8 +750,14 @@ export function assembleMind( willId: string, config: WillConfig ): MindAssembly
|
|
|
582
750
|
})
|
|
583
751
|
if( !idGuard.ok )
|
|
584
752
|
throw new Error(`Invalid Will identity for "${willId}": ${ idGuard.errors.join('; ') }`)
|
|
585
|
-
|
|
586
|
-
|
|
753
|
+
// Warnings only when this identity is the one that will actually be used. On a
|
|
754
|
+
// wake it is a placeholder (see WillConfig.identityFromArtifact) and the real
|
|
755
|
+
// persona is guarded at the PMA load boundary instead.
|
|
756
|
+
if( config.identityFromArtifact )
|
|
757
|
+
logger.debug(`[identity-guard] ${willId}: identity deferred to artifact — guarded at PMA load`)
|
|
758
|
+
else
|
|
759
|
+
for( const w of idGuard.warnings )
|
|
760
|
+
logger.warn(`[identity-guard] ${willId}: ${w}`)
|
|
587
761
|
config = { ...config, identity: idGuard.sanitized.identity }
|
|
588
762
|
|
|
589
763
|
// ── Construct ────────────────────────────────────────────
|
|
@@ -666,6 +840,10 @@ function _constructCognition(
|
|
|
666
840
|
// (attachTokenTracker), so usage/cost never conflates across Wills and
|
|
667
841
|
// parallel runs stay isolated.
|
|
668
842
|
const tokenTracker = new TokenTracker({
|
|
843
|
+
// Host prices, flattened from the per-provider map. Cost is telemetry only
|
|
844
|
+
// (it never enters state), so this can differ run to run without touching
|
|
845
|
+
// determinism.
|
|
846
|
+
prices: mergeProviderPrices( config.llm?.providers ),
|
|
669
847
|
emitCostEvents: true,
|
|
670
848
|
costWarningThresholdUsd: 0.02,
|
|
671
849
|
willId,
|
|
@@ -708,7 +886,7 @@ function _constructCognition(
|
|
|
708
886
|
|
|
709
887
|
// Per-Will, per-ROLE models (env WILL_LLM_MODEL pins all thinking roles —
|
|
710
888
|
// operator single-model deployments). No tier vocabulary inside the engine.
|
|
711
|
-
const modelRoles = resolveModelRoles( config.model )
|
|
889
|
+
const modelRoles = resolveModelRoles( config.llm?.model )
|
|
712
890
|
|
|
713
891
|
const { embedder, vectorMemory } = _resolveVectorMemory( willId, randomSeed, config.vectorMemoryAdapter, config.disableVectorMemory, tokenTracker, config.testMode, modelRoles.embedding ?? undefined )
|
|
714
892
|
const episodicConsolidator = new EpisodicConsolidator( vectorMemory ? { vectorMemory, ...(embedder ? { embedder } : {}) } : {} )
|
|
@@ -746,14 +924,28 @@ function _constructCognition(
|
|
|
746
924
|
const executiveEngine = new ExecutiveEngine({ executiveInterval, cooldownTicks: 5 })
|
|
747
925
|
|
|
748
926
|
executiveEngine.willId = willId
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
927
|
+
// Narrow the host's per-provider map to credentials for the call path; the
|
|
928
|
+
// prices from that same map went to the TokenTracker above.
|
|
929
|
+
// W7 — the per-role model map compiles into routing rules and joins the
|
|
930
|
+
// host's own router in one chain, so the engine receives a single answer to
|
|
931
|
+
// "which model serves this call?" instead of two. The host's router leads:
|
|
932
|
+
// that is the precedence the two mechanisms already had.
|
|
933
|
+
const roleRouter = compileRoleRouter( modelRoles )
|
|
934
|
+
executiveEngine.llm = config.llm
|
|
935
|
+
? {
|
|
936
|
+
...config.llm,
|
|
937
|
+
...( config.llm.providers ? { credentials: providerCredentials( config.llm.providers ) } : {} ),
|
|
938
|
+
router: chainRouters( config.llm.router, roleRouter ),
|
|
939
|
+
}
|
|
940
|
+
: ( roleRouter ? { router: roleRouter } : null )
|
|
941
|
+
executiveEngine.modelId = modelRoles.executive
|
|
756
942
|
if( config.testMode ) executiveEngine.setTestMode( true )
|
|
943
|
+
// The learned fast path, when this Will has been given one. Built, tested and
|
|
944
|
+
// snapshot-safe, but it had no caller at all — so it shipped in the bundle as
|
|
945
|
+
// code no mind could ever reach. A capability with no way in is indistinguishable
|
|
946
|
+
// from a missing one.
|
|
947
|
+
if( config.deliberationCache )
|
|
948
|
+
executiveEngine.enableCache( config.deliberationCache === true ? undefined : config.deliberationCache )
|
|
757
949
|
executiveEngine.attachWorkingMemory( workingMemory )
|
|
758
950
|
executiveEngine.attachGoalManager( goalManager )
|
|
759
951
|
executiveEngine.attachEpisodicConsolidator( episodicConsolidator )
|
|
@@ -872,6 +1064,37 @@ function _constructCognition(
|
|
|
872
1064
|
// buildExecutiveContext (already vector-backed via the consolidator).
|
|
873
1065
|
auditionEngine.attachMemorySink( entity => simulation.stateManager.setEntity( entity ) )
|
|
874
1066
|
|
|
1067
|
+
// Referent → address + room. Closes over the state manager because the writer
|
|
1068
|
+
// is deliberately stateless; this is the one seam both send paths cross, so the
|
|
1069
|
+
// translation happens once rather than in each of them.
|
|
1070
|
+
outboxWriter.attachRouting( ( targetEntityId, chosenThread ) => {
|
|
1071
|
+
if( !isReferentId( targetEntityId ) ) return null // already an address
|
|
1072
|
+
|
|
1073
|
+
// Through the O(1) type index rather than the whole entity map: this runs on
|
|
1074
|
+
// every outbound message, and the two types it needs are both indexed.
|
|
1075
|
+
const entities = new Map(
|
|
1076
|
+
[ ...simulation.stateManager.getEntitiesByType( DOSSIER_TYPE ),
|
|
1077
|
+
...simulation.stateManager.getEntitiesByType( ALIAS_TYPE ) ]
|
|
1078
|
+
.map( e => [ e.id, e ] as const ),
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
// An anchor cannot be delivered to. Find an address the world knows them by —
|
|
1082
|
+
// preferring one on the same platform as the room already chosen, so a reply
|
|
1083
|
+
// in a Discord thread is not addressed to a WhatsApp handle.
|
|
1084
|
+
const aliases = readAliases( entities )
|
|
1085
|
+
const scheme = chosenThread?.split(':')[0]
|
|
1086
|
+
const addresses = [ ...aliases.entries() ]
|
|
1087
|
+
.filter( ( [ , canonical ] ) => canonical === targetEntityId )
|
|
1088
|
+
.map( ( [ alias ] ) => alias )
|
|
1089
|
+
.sort()
|
|
1090
|
+
const address = addresses.find( a => scheme && a.startsWith(`${ scheme }:`) ) ?? addresses[0]
|
|
1091
|
+
if( !address ) return null // nothing known — let the bridge's roster try
|
|
1092
|
+
|
|
1093
|
+
// Only a fallback: a chosen room always wins upstream (see enqueue).
|
|
1094
|
+
const room = defaultHandle( handlesOf( entities, targetEntityId ) )
|
|
1095
|
+
return { targetEntityId: address, ...( room ? { threadId: room.keid } : {} ) }
|
|
1096
|
+
} )
|
|
1097
|
+
|
|
875
1098
|
// Salience inputs (§3): weight conversational salience by relationship closeness
|
|
876
1099
|
// and active-goal topic overlap. Both are deterministic faculty-state reads.
|
|
877
1100
|
auditionEngine.attachAttachmentScore( entityId => attachmentEvaluator.getAttachmentScore( entityId ) )
|
|
@@ -933,6 +1156,10 @@ function _constructCognition(
|
|
|
933
1156
|
affectiveBlender,
|
|
934
1157
|
workingMemory,
|
|
935
1158
|
episodicConsolidator,
|
|
1159
|
+
// Exposed so shutdown can FLUSH it. The adapter only ever persisted itself from
|
|
1160
|
+
// a 5s debounce timer that no shutdown path awaited, so the index died with the
|
|
1161
|
+
// process — see WillStem.archiveWill.
|
|
1162
|
+
vectorMemory,
|
|
936
1163
|
semanticIntegrator,
|
|
937
1164
|
spacedRepetition,
|
|
938
1165
|
forgettingCurve,
|
|
@@ -1110,25 +1337,26 @@ function _seedIdentity(
|
|
|
1110
1337
|
const namePrefix = nameAlreadyInPrompt ? '' : `I am ${config.name}.`
|
|
1111
1338
|
const fullPersonaText = [ namePrefix, personaText ].filter( Boolean ).join(' ')
|
|
1112
1339
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1340
|
+
// Composed for the prompt, but the persona is ALSO stored on its own below —
|
|
1341
|
+
// see cognition/identity.entity. Storing only the composed string is what let
|
|
1342
|
+
// the distiller capture the container's preamble into a tenant's artifact.
|
|
1343
|
+
const prompt = composeIdentityPrompt( fullPersonaText, profileContext )
|
|
1344
|
+
|
|
1345
|
+
// The one place `name` is ever written. Every other writer merges (see
|
|
1346
|
+
// cognition/identity.entity), so from here on the mind's name can only be
|
|
1347
|
+
// changed on purpose — never dropped as a side effect of revising something else.
|
|
1348
|
+
mergeIdentity( simulation.stateManager, {
|
|
1349
|
+
name: config.name, // canonical persona name — single source of truth
|
|
1350
|
+
prompt,
|
|
1351
|
+
// Layer 2 alone — what the artifact will carry. `prompt` is the composed
|
|
1352
|
+
// view for the prompt factory and is recomposed from THIS on every load, so
|
|
1353
|
+
// a woken mind always gets the current build's preamble.
|
|
1354
|
+
persona: fullPersonaText,
|
|
1355
|
+
...( profileContext ? { environment: profileContext } : {} ),
|
|
1356
|
+
values: identity.values,
|
|
1357
|
+
traits: identity.traits,
|
|
1358
|
+
style: identity.style,
|
|
1359
|
+
version: 1
|
|
1132
1360
|
})
|
|
1133
1361
|
}
|
|
1134
1362
|
|
|
@@ -1160,13 +1388,33 @@ function _seedInitialGoals( simulation: DefaultSimulation, config: WillConfig ):
|
|
|
1160
1388
|
*/
|
|
1161
1389
|
function _seedEngineConfigs( simulation: DefaultSimulation, entities: EngineConfigEntity[] ): void {
|
|
1162
1390
|
for( const cfg of entities )
|
|
1163
|
-
simulation.stateManager
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1391
|
+
mergeEngineConfig( simulation.stateManager, cfg, 'incoming')
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Fill in engine-config params a restored Will has never seen, WITHOUT touching
|
|
1396
|
+
* the ones it has.
|
|
1397
|
+
*
|
|
1398
|
+
* Seeding runs inside `assembleMind`; the snapshot restore runs after it and
|
|
1399
|
+
* replaces the entity map wholesale, so a Will woke with whatever config it first
|
|
1400
|
+
* hibernated under — for good. Every tunable added after a tenant's first run was
|
|
1401
|
+
* therefore unreachable by that tenant: measured on a live Will, three params
|
|
1402
|
+
* shipped that day (`repeatDamping`, `repeatWindowTicks`, `socialWeight`) were
|
|
1403
|
+
* simply absent from its restored `engine-config-action-selector`, so the code
|
|
1404
|
+
* reading them silently fell back to defaults and the features did nothing.
|
|
1405
|
+
*
|
|
1406
|
+
* A container has to be able to ship a new capability to a tenant already living
|
|
1407
|
+
* in it. Restored values WIN — they carry PMA seeding and whatever the persona has
|
|
1408
|
+
* learned — and only genuinely missing keys are added.
|
|
1409
|
+
*/
|
|
1410
|
+
export function backfillEngineConfigs( simulation: DefaultSimulation, entities: EngineConfigEntity[] ): void {
|
|
1411
|
+
for( const cfg of entities ){
|
|
1412
|
+
// 'existing' — state is the authority here. It carries PMA seeding and
|
|
1413
|
+
// whatever the persona has learned, so only genuinely missing keys are added.
|
|
1414
|
+
const added = mergeEngineConfig( simulation.stateManager, cfg, 'existing')
|
|
1415
|
+
if( added.length > 0 )
|
|
1416
|
+
logger.info(`[WillStem] ${cfg.id}: added ${added.length} new param(s) — ${added.join(', ')}`)
|
|
1417
|
+
}
|
|
1170
1418
|
}
|
|
1171
1419
|
|
|
1172
1420
|
// ── Helpers ──────────────────────────────────────────────────
|
|
@@ -33,15 +33,28 @@
|
|
|
33
33
|
export type PolicyDecision = 'allow' | 'deny' | 'escalate'
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* WHY this denial is final — the distinction that makes a refusal learnable
|
|
37
|
+
* rather than a wall to re-probe forever. Each value selects a different
|
|
38
|
+
* cognitive fate; they are not degrees of one severity.
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* • 'class' — the ACTION ITSELF is never permitted. Suppress the
|
|
41
|
+
* affordance hard, erase any learned envelope, and let go of
|
|
42
|
+
* a commitment currently deliberating toward it.
|
|
43
|
+
* • 'parameter' — the action is fine; THESE ARGUMENTS were not (bound
|
|
44
|
+
* exceeded, wrong target). Narrow the envelope the Will
|
|
45
|
+
* reaches for; the ability stays.
|
|
46
|
+
* • 'context' — the refusal was NOT ABOUT THE ACTION at all (tainted
|
|
47
|
+
* context, unavailable dependency). Touch nothing: no
|
|
48
|
+
* availability delta, no envelope, no competence.
|
|
49
|
+
*
|
|
50
|
+
* POLICY_REAFFERENCE P5 widened this from 'class' | 'instance' after the HELM
|
|
51
|
+
* joint RFC ("Denials That Teach") identified that an instance-scoped refusal
|
|
52
|
+
* splits in two, and that the two halves demand opposite responses. These are
|
|
53
|
+
* OUR names for the distinctions, deliberately not HELM's wire spellings — see
|
|
54
|
+
* the naming-boundary note in .TODO/POLICY_REAFFERENCE.md. A provider adapter
|
|
55
|
+
* translates; this interface stays vendor-neutral.
|
|
43
56
|
*/
|
|
44
|
-
export type DenialFinality = 'class' | '
|
|
57
|
+
export type DenialFinality = 'class' | 'parameter' | 'context'
|
|
45
58
|
|
|
46
59
|
/**
|
|
47
60
|
* The nearest allowed envelope — what WOULD have been permitted. Structured so
|
|
@@ -64,9 +77,8 @@ export interface Verdict {
|
|
|
64
77
|
decision: PolicyDecision
|
|
65
78
|
/** Stable machine-readable code, e.g. 'TARGET_NOT_ALLOWED'. Never prose. */
|
|
66
79
|
reasonCode?: string
|
|
67
|
-
/** Meaningful on 'deny' only. Absent ⇒ treat as '
|
|
68
|
-
*
|
|
69
|
-
* delete an ability from the Will's reach). */
|
|
80
|
+
/** Meaningful on 'deny' only. Absent ⇒ treat as 'parameter' — see
|
|
81
|
+
* `asFinality` for why that, and not 'context', is the safe default. */
|
|
70
82
|
finality?: DenialFinality
|
|
71
83
|
counterfactual?: PolicyCounterfactual
|
|
72
84
|
/** Free-text for logs and host UX. NEVER parsed by cognition. */
|
|
@@ -127,10 +139,33 @@ export function isNullArbiter( arbiter: PolicyArbiter | null | undefined ): bool
|
|
|
127
139
|
return !arbiter || arbiter === NULL_ARBITER
|
|
128
140
|
}
|
|
129
141
|
|
|
142
|
+
/** Normalize a denial's finality. Absent ⇒ 'parameter' (see `asFinality`). */
|
|
143
|
+
export function finalityOf( verdict: Verdict ): DenialFinality {
|
|
144
|
+
return asFinality( verdict.finality )
|
|
145
|
+
}
|
|
146
|
+
|
|
130
147
|
/**
|
|
131
|
-
* Normalize
|
|
132
|
-
*
|
|
148
|
+
* Normalize an UNTYPED finality — one read back off entity metadata, a verdict
|
|
149
|
+
* tape, or a host ack, where the type system cannot help.
|
|
150
|
+
*
|
|
151
|
+
* Every such read goes through here rather than comparing string literals at
|
|
152
|
+
* the call site: the enum is a moving target (it widened once for the HELM
|
|
153
|
+
* joint RFC and may again), and a hand-written `=== 'class'` scattered through
|
|
154
|
+
* cognition is a mis-route that still typechecks.
|
|
155
|
+
*
|
|
156
|
+
* THE DEFAULT IS 'parameter', AND NEVER 'context'. The intuitive reading — an
|
|
157
|
+
* unlabelled denial should do the LEAST, so default to the fate that touches
|
|
158
|
+
* nothing — is wrong, and dangerously so. 'context' means *the mind learns
|
|
159
|
+
* nothing from this denial*, so it re-probes the same wall forever: the exact
|
|
160
|
+
* failure this whole epoch exists to fix, silently re-enabled for any provider
|
|
161
|
+
* that doesn't tag its refusals. 'context' is a claim only a provider that
|
|
162
|
+
* actually knows can make — it must be ASSERTED, never defaulted to.
|
|
163
|
+
* 'parameter' narrows without deleting, which is the honest conservative
|
|
164
|
+
* reading and preserves pre-P5 behaviour for an untagged denial exactly.
|
|
165
|
+
*
|
|
166
|
+
* Legacy 'instance' (the pre-P5 spelling) normalizes to 'parameter' by the same
|
|
167
|
+
* fallback, so tapes and snapshots written before the split replay unchanged.
|
|
133
168
|
*/
|
|
134
|
-
export function
|
|
135
|
-
return
|
|
169
|
+
export function asFinality( raw: unknown ): DenialFinality {
|
|
170
|
+
return raw === 'class' ? 'class' : raw === 'context' ? 'context' : 'parameter'
|
|
136
171
|
}
|