@mindot/will 0.7.0 → 0.8.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 +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/cli.js +10823 -10454
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +562 -226
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-DAW0l-lY.d.ts → will-cS6k4uiJ.d.ts} +470 -84
- package/package.json +1 -1
- package/src/cognition/agency/engines/action.selector.ts +2 -1
- package/src/cognition/agency/engines/reafference.engine.ts +12 -2
- package/src/cognition/agency/reconcile.learning.ts +16 -2
- package/src/cognition/agency/schemas/repertoire.ts +12 -5
- package/src/cognition/config.mirror.entities.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +136 -58
- package/src/cognition/faculties/executive.engine/facet.ts +10 -2
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +2 -1
- package/src/cognition/index.ts +4 -0
- package/src/cognition/memory/vector.embedder.ts +9 -5
- package/src/cognition/utilities/token.tracker.ts +191 -96
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +397 -96
- package/src/llm/routing.ts +198 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +82 -16
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +3 -3
- package/src/stem/mind.ts +155 -24
- 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/package.json
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { readEffectiveParams, readPersonaPrior } from '#cognition/persona.prior'
|
|
39
39
|
import { RUPTURE_REVOKE_GATE, revocationEntity } from '#agency/revocation'
|
|
40
40
|
import { liveConsequences, matchConsequenceText } from '#agency/consequence'
|
|
41
|
+
import { asFinality } from '#stem/policy/arbiter'
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Activation margin (winner − runner-up) below which the choice is "contested" —
|
|
@@ -657,7 +658,7 @@ function refusedClassSchemas( state: ReadonlySimulationState ): Set<string> {
|
|
|
657
658
|
for( const e of state.entities.values() ){
|
|
658
659
|
if( e.type !== 'agency.outcome') continue
|
|
659
660
|
const m = e.metadata
|
|
660
|
-
if( m?.['refused'] !== true ||
|
|
661
|
+
if( m?.['refused'] !== true || asFinality( m?.['finality'] ) !== 'class') continue
|
|
661
662
|
const schema = str( m?.['schema'] )
|
|
662
663
|
if( schema ) out.add( schema )
|
|
663
664
|
}
|
|
@@ -29,6 +29,7 @@ import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
|
29
29
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
30
30
|
import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
31
31
|
import { schemaEntityId, availabilityEntityId } from '#agency/schemas/repertoire'
|
|
32
|
+
import { asFinality } from '#stem/policy/arbiter'
|
|
32
33
|
import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
|
|
33
34
|
|
|
34
35
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
@@ -201,8 +202,17 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
201
202
|
// is merely forbidden to do. The awaiting intent is still freed, and a
|
|
202
203
|
// refused plan step is signalled unsuccessful so the plan doesn't hang.
|
|
203
204
|
if( m['refused'] === true ){
|
|
204
|
-
const finality =
|
|
205
|
-
|
|
205
|
+
const finality = asFinality( m['finality'] )
|
|
206
|
+
|
|
207
|
+
// P5 — 'context' means the refusal was NOT ABOUT THE ACTION (tainted
|
|
208
|
+
// context, an unavailable dependency, the arbiter itself down). Denting
|
|
209
|
+
// availability here would teach a lesson the policy never taught:
|
|
210
|
+
// "reach for this less", when nothing about the ability was the problem.
|
|
211
|
+
// So the intent is freed and the plan step signalled — the mind must not
|
|
212
|
+
// hang — and NOTHING else is written. Not competence, not availability.
|
|
213
|
+
if( finality !== 'context')
|
|
214
|
+
this._repertoire.recordRefusal( schema, finality, tick )
|
|
215
|
+
|
|
206
216
|
if( fromState ) del.push( id )
|
|
207
217
|
const refusedIntent = str( m['intentId'] )
|
|
208
218
|
if( refusedIntent ) del.push( refusedIntent )
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// ─────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
import type { EntityInput, Tick } from '#core/types'
|
|
19
|
+
import type { DenialFinality, PolicyCounterfactual } from '#stem/policy/arbiter'
|
|
19
20
|
|
|
20
21
|
export interface HostAckResult {
|
|
21
22
|
success: boolean
|
|
@@ -31,7 +32,17 @@ export interface HostAckResult {
|
|
|
31
32
|
* forbidden to do. `finality` decides how hard availability is cut.
|
|
32
33
|
*/
|
|
33
34
|
refused?: boolean
|
|
34
|
-
finality?:
|
|
35
|
+
finality?: DenialFinality
|
|
36
|
+
/**
|
|
37
|
+
* ENVELOPE_NARROWING P0 — what WOULD have been permitted, e.g.
|
|
38
|
+
* `{ field: 'amount', requested: 500, allowed: 100 }`.
|
|
39
|
+
*
|
|
40
|
+
* Carried onto the outcome so the tape and the thing the mind actually learns
|
|
41
|
+
* from agree about what happened. Until ENVELOPE_NARROWING P1 lands nothing
|
|
42
|
+
* READS it — that fold is blocked upstream on whether a scalar `allowed` is a
|
|
43
|
+
* ceiling or a floor — but dropping it here is what made the two disagree.
|
|
44
|
+
*/
|
|
45
|
+
counterfactual?: PolicyCounterfactual
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
@@ -79,7 +90,10 @@ export function reconcileInvocation(
|
|
|
79
90
|
mode: 'external',
|
|
80
91
|
tick,
|
|
81
92
|
reconciled: true,
|
|
82
|
-
...( result.refused ? { refused: true, finality: result.finality ?? '
|
|
93
|
+
...( result.refused ? { refused: true, finality: result.finality ?? 'parameter' } : {} ),
|
|
94
|
+
// Only when the arbiter actually reported a bound — a refusal without one
|
|
95
|
+
// writes no key at all, so the quiet path is unchanged.
|
|
96
|
+
...( result.refused && result.counterfactual ? { counterfactual: result.counterfactual } : {} ),
|
|
83
97
|
...( provenance.planId ? { planId: provenance.planId } : {} ),
|
|
84
98
|
...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
|
|
85
99
|
},
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import type { MotorSchema, LearnedSkill } from '#agency/types'
|
|
28
28
|
import type { EntityInput, ReadonlySimulationState } from '#core/types'
|
|
29
|
+
import type { DenialFinality } from '#stem/policy/arbiter'
|
|
29
30
|
import { INNATE_SCHEMAS } from '#agency/schemas/innate'
|
|
30
31
|
|
|
31
32
|
const VALUE_ALPHA = 0.2 // value EMA rate
|
|
@@ -50,8 +51,8 @@ const DROP_HABIT = 0.05 // below this (and learned) the skill is forgott
|
|
|
50
51
|
// availability never floors at zero, and it climbs back toward 1 with disuse of
|
|
51
52
|
// the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
|
|
52
53
|
// narrowing is a follow-up that belongs at selection time, not fielding time.
|
|
53
|
-
const AVAIL_DROP_CLASS
|
|
54
|
-
const
|
|
54
|
+
const AVAIL_DROP_CLASS = 0.50 // multiplicative cut on a class-final refusal
|
|
55
|
+
const AVAIL_DROP_PARAMETER = 0.12 // lighter cut when only the arguments were refused
|
|
55
56
|
const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
|
|
56
57
|
const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
|
|
57
58
|
const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
|
|
@@ -119,13 +120,19 @@ export class SchemaRepertoire {
|
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
122
|
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
122
|
-
* `class` refusal cuts availability hard;
|
|
123
|
+
* `class` refusal cuts availability hard; a `parameter` refusal dents it
|
|
123
124
|
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
124
125
|
* reach — zero, keeping re-probe alive.
|
|
126
|
+
*
|
|
127
|
+
* `context` is EXCLUDED FROM THE SIGNATURE, not handled inside: a refusal
|
|
128
|
+
* that was not about the action must never reach the availability layer at
|
|
129
|
+
* all, and making that a type error rather than a convention means a future
|
|
130
|
+
* caller cannot quietly re-introduce the dent. The routing decision lives in
|
|
131
|
+
* the ReafferenceEngine's refused branch (P5).
|
|
125
132
|
*/
|
|
126
|
-
recordRefusal( schema: string, finality:
|
|
133
|
+
recordRefusal( schema: string, finality: Exclude<DenialFinality, 'context'>, tick: number ): number {
|
|
127
134
|
const prev = this._availability.get( schema )?.value ?? 1
|
|
128
|
-
const drop = finality === 'class' ? AVAIL_DROP_CLASS :
|
|
135
|
+
const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_PARAMETER
|
|
129
136
|
const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
|
|
130
137
|
this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
131
138
|
return value
|
|
@@ -34,7 +34,7 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
34
34
|
engine: 'system',
|
|
35
35
|
params: {
|
|
36
36
|
anatomy: config.anatomy ?? 'mind',
|
|
37
|
-
model: config.model ?? '',
|
|
37
|
+
model: config.llm?.model ?? '',
|
|
38
38
|
tickIntervalMs: config.tickIntervalMs ?? 1000
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -60,7 +60,10 @@ import {
|
|
|
60
60
|
type GatingState
|
|
61
61
|
} from '#faculties/executive.engine/gating'
|
|
62
62
|
import { LLMDirector } from '#llm/index'
|
|
63
|
-
import {
|
|
63
|
+
import { MOCK_PROVIDER, MOCK_MODEL } from '#llm/index'
|
|
64
|
+
import { getCompletionSource } from '#core/completion.recorder'
|
|
65
|
+
import { providerKeyFromEnv, type LLMProvider, type LLMCallMeta, type ProviderCredential, type LLMWire } from '#llm/index'
|
|
66
|
+
import type { ModelRouter } from '#llm/routing'
|
|
64
67
|
import { buildFallbackOutput, parseResponse } from '#faculties/executive.engine/parser'
|
|
65
68
|
import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
|
|
66
69
|
import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
|
|
@@ -145,14 +148,14 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
145
148
|
|
|
146
149
|
// ── Injected dependencies ──────────────────────────────────
|
|
147
150
|
private _willId: string | null = null
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
/**
|
|
152
|
+
* The Will's default model (config.model's `executive` role, resolved in
|
|
153
|
+
* mind.ts). Every other role reaches its model through the router — see
|
|
154
|
+
* `compileRoleRouter`.
|
|
155
|
+
*/
|
|
156
|
+
private _modelId: string | null = null
|
|
151
157
|
/** Per-Will LLM transport overrides (config.llm) — env fallbacks apply per field. */
|
|
152
|
-
private _llm: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number } | null = null
|
|
153
|
-
/** One director per distinct model — same config, different model. Shared
|
|
154
|
-
* tracker/recorder/willId, so ledger attribution and replay hold per role. */
|
|
155
|
-
private _directorCache = new Map<string, LLMDirector>()
|
|
158
|
+
private _llm: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number; credentials?: Partial<Record<string, ProviderCredential>>; router?: ModelRouter | null; wire?: LLMWire } | null = null
|
|
156
159
|
private _workingMemory: WorkingMemory | null = null
|
|
157
160
|
private _goalManager: GoalManager | null = null
|
|
158
161
|
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
@@ -267,13 +270,18 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
267
270
|
|
|
268
271
|
set willId( willId: string ){ this._willId = willId }
|
|
269
272
|
|
|
270
|
-
/**
|
|
271
|
-
|
|
272
|
-
|
|
273
|
+
/**
|
|
274
|
+
* The Will's default model. Set before the first tick.
|
|
275
|
+
*
|
|
276
|
+
* This replaced a four-role map (W7): the other roles are routing rules now,
|
|
277
|
+
* compiled in mind.ts, so the engine holds one model and one router rather
|
|
278
|
+
* than a model per role plus a router.
|
|
279
|
+
*/
|
|
280
|
+
set modelId( id: string | null ){ this._modelId = id }
|
|
281
|
+
get modelId(): string | null { return this._modelId }
|
|
282
|
+
|
|
273
283
|
/** Per-Will LLM transport overrides (config.llm). Set before the first tick. */
|
|
274
|
-
set llm( c: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number } | null ){ this._llm = c }
|
|
275
|
-
/** The executive-role model id (back-compat read). */
|
|
276
|
-
get modelId(): string | null { return this._models.executive }
|
|
284
|
+
set llm( c: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number; credentials?: Partial<Record<string, ProviderCredential>>; router?: ModelRouter | null; wire?: LLMWire } | null ){ this._llm = c }
|
|
277
285
|
|
|
278
286
|
// ── Public surface ─────────────────────────────────────────
|
|
279
287
|
|
|
@@ -301,48 +309,98 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
301
309
|
* and subscribe() to receive facet decisions.
|
|
302
310
|
*/
|
|
303
311
|
/** Get-or-create the director for a model id (shared config, per-Will). */
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return
|
|
312
|
+
/**
|
|
313
|
+
* The provider, from config or environment — never guessed.
|
|
314
|
+
*
|
|
315
|
+
* This used to default to 'anthropic', which is how a Will configured for one
|
|
316
|
+
* vendor could quietly talk to another. An unset provider is a configuration
|
|
317
|
+
* error, and saying so at construction is far cheaper than a 401 mid-tick.
|
|
318
|
+
*/
|
|
319
|
+
private _requireProvider(): LLMProvider {
|
|
320
|
+
const provider = this._llm?.provider ?? process.env.WILL_LLM_PROVIDER
|
|
321
|
+
?? ( this._noLiveCalls() ? MOCK_PROVIDER : undefined )
|
|
322
|
+
if( !provider )
|
|
323
|
+
throw new Error(
|
|
324
|
+
'No LLM provider configured. Set one on the Will (llm.provider) or in ' +
|
|
325
|
+
'the environment (WILL_LLM_PROVIDER) — the engine carries no default.'
|
|
326
|
+
)
|
|
327
|
+
return provider as LLMProvider
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* True when this Will cannot make a live call, so provider/model are not
|
|
332
|
+
* required: mock mode, or a replay re-feeding recorded completions.
|
|
333
|
+
*/
|
|
334
|
+
private _noLiveCalls(): boolean {
|
|
335
|
+
return this._testMode || ( !!this._willId && !!getCompletionSource( this._willId ) )
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Build this Will's one and only director.
|
|
340
|
+
*
|
|
341
|
+
* There used to be a cache of them, keyed by model, because the per-role
|
|
342
|
+
* model map had no other way to make a role use a different model. Routing
|
|
343
|
+
* gave it one — the role map now compiles to rules (see `compileRoleRouter`)
|
|
344
|
+
* and a single director resolves every call's endpoint per call. That is also
|
|
345
|
+
* strictly more faithful: a facet follows the work it is doing rather than
|
|
346
|
+
* whatever role it happened to be spawned under.
|
|
347
|
+
*/
|
|
348
|
+
private _buildDirector( model: string ): LLMDirector {
|
|
349
|
+
// Resolved first: the key fallback below is keyed by it.
|
|
350
|
+
const provider = this._requireProvider()
|
|
351
|
+
|
|
352
|
+
// Per-Will transport overrides first (BYO keys), env per field otherwise.
|
|
353
|
+
return new LLMDirector( {
|
|
354
|
+
willId: this._willId!,
|
|
355
|
+
model,
|
|
356
|
+
maxOutputTokens: this._llm?.maxOutputTokens ?? parseInt( process.env.WILL_MAX_OUTPUT_TOKENS ?? '8096'),
|
|
357
|
+
// Config, then the provider-agnostic env, then THIS provider's own env.
|
|
358
|
+
// The last step is not the fallback W9 removed: that one ended at
|
|
359
|
+
// ANTHROPIC_API_KEY for every provider, so a Will pointed elsewhere sent
|
|
360
|
+
// Anthropic's key to a stranger. This one can only ever read the key
|
|
361
|
+
// belonging to the provider actually configured.
|
|
362
|
+
apiKey: this._llm?.apiKey ?? process.env.WILL_LLM_API_KEY ?? providerKeyFromEnv( provider ) ?? '',
|
|
363
|
+
provider,
|
|
364
|
+
// Optional base-URL override (e.g. Ollama / Azure / self-hosted). Unset →
|
|
365
|
+
// the director uses the provider's official endpoint.
|
|
366
|
+
baseUrl: this._llm?.baseUrl ?? process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
|
|
367
|
+
timeoutMs: this._llm?.timeoutMs ?? ( process.env.WILL_LLM_TIMEOUT_MS ? parseInt( process.env.WILL_LLM_TIMEOUT_MS ) : undefined ),
|
|
368
|
+
sessionLogger: this._sessionLogger,
|
|
369
|
+
mock: this._testMode,
|
|
370
|
+
// Inject the per-Will tracker (R4) so live calls record usage here, not
|
|
371
|
+
// through a process global. null is fine — the director skips recording.
|
|
372
|
+
tokenTracker: this._tokenTracker,
|
|
373
|
+
// Credentials for routed calls, narrowed by the stem from the host's
|
|
374
|
+
// per-provider map. Prices from that same map ride to the TokenTracker
|
|
375
|
+
// instead, so nothing carries pricing into the call path.
|
|
376
|
+
...( this._llm?.credentials ? { credentials: this._llm.credentials } : {} ),
|
|
377
|
+
// Per-call model selection — the host's router chained with the rules
|
|
378
|
+
// compiled from the per-role model map.
|
|
379
|
+
...( this._llm?.router ? { router: this._llm.router } : {} ),
|
|
380
|
+
// Dialect for the default provider — required for anything outside the
|
|
381
|
+
// known set, so the engine never guesses how to talk to an endpoint.
|
|
382
|
+
...( this._llm?.wire ? { wire: this._llm.wire } : {} ),
|
|
383
|
+
} )
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Spawn a facet.
|
|
388
|
+
*
|
|
389
|
+
* `role` declares the facet's intent at the call site. It no longer selects a
|
|
390
|
+
* model: that used to happen here, pinning a facet for life to whatever role
|
|
391
|
+
* it was spawned under, and it now happens per call from the focus function
|
|
392
|
+
* the caller sets immediately afterwards (W7). The two always agreed — every
|
|
393
|
+
* spawn site sets a focus whose `function` matches its role — so the routed
|
|
394
|
+
* answer is the same one, decided later and from the work itself.
|
|
395
|
+
*/
|
|
330
396
|
spawnFacet( role?: 'deliberation' | 'conversation' | 'outreach' | 'supervision'): { attention: 'available' | 'full', handle?: ExecutiveFacetHandle } {
|
|
397
|
+
void role
|
|
331
398
|
// Delegate to FacetSupervisor (R5-g-3), passing the current engine
|
|
332
399
|
// attachments. The supervisor owns the registry + attention budget and
|
|
333
400
|
// performs the throw-checks on bus / director / state ref.
|
|
334
|
-
// A role with its own configured model gets that role's director; every
|
|
335
|
-
// other facet shares the executive's (one self, role-appropriate depth).
|
|
336
|
-
// Outreach speaks with the conversation voice; supervision thinks with the
|
|
337
|
-
// executive's depth.
|
|
338
|
-
const roleModel =
|
|
339
|
-
role === 'deliberation' ? this._models.deliberation :
|
|
340
|
-
role === 'conversation' || role === 'outreach' ? this._models.conversation :
|
|
341
|
-
null
|
|
342
|
-
const director = roleModel && this._llmDirector ? this._directorFor( roleModel ) : this._llmDirector
|
|
343
401
|
return this._facetSupervisor.spawn( {
|
|
344
402
|
bus: this._bus,
|
|
345
|
-
llmDirector:
|
|
403
|
+
llmDirector: this._llmDirector,
|
|
346
404
|
stateRef: this._lastStateRef,
|
|
347
405
|
willId: this._willId,
|
|
348
406
|
inbox: this._inbox,
|
|
@@ -433,15 +491,32 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
433
491
|
this._gatingState.executiveInterval = rtConfig.executiveInterval
|
|
434
492
|
this._gatingState.cooldownTicks = rtConfig.cooldownTicks
|
|
435
493
|
|
|
436
|
-
// Initialize LLM
|
|
437
|
-
//
|
|
494
|
+
// Initialize the LLM director if not yet done (requires willId). One per
|
|
495
|
+
// Will — every role reaches its model through the router.
|
|
438
496
|
if( !this._llmDirector && this._willId ){
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
|
|
497
|
+
// No default model. The engine used to fall back to a Claude id for every
|
|
498
|
+
// provider but GLM, which meant a misconfigured Will asked the wrong
|
|
499
|
+
// vendor for the wrong model and failed at the first tick with a 404
|
|
500
|
+
// instead of at construction with a sentence.
|
|
501
|
+
//
|
|
502
|
+
// Two cases legitimately have no credentials and are exempt: a test-mode
|
|
503
|
+
// Will (never reaches a network — demanding a key would break the whole
|
|
504
|
+
// point of the no-key quickstart) and a replay (completions are re-fed
|
|
505
|
+
// from the tape, which carries the model that actually served them). The
|
|
506
|
+
// sentinel is what a mock run records, so the tape still says plainly
|
|
507
|
+
// that nothing real answered.
|
|
508
|
+
const defaultModel = this._modelId ?? process.env.WILL_LLM_MODEL
|
|
509
|
+
?? ( this._noLiveCalls() ? MOCK_MODEL : undefined )
|
|
510
|
+
if( !defaultModel )
|
|
511
|
+
throw new Error(
|
|
512
|
+
'No LLM model configured. Set one on the Will (llm.model) or in the ' +
|
|
513
|
+
'environment (WILL_LLM_MODEL) — the engine carries no default.'
|
|
514
|
+
)
|
|
515
|
+
this._llmDirector = this._buildDirector( defaultModel )
|
|
516
|
+
// The summarizer shares that director. Its calls tag themselves
|
|
517
|
+
// `category: 'summarizer'`, which is what a configured summarizer role
|
|
518
|
+
// compiles to — so it still gets its own model, chosen per call.
|
|
519
|
+
this._summarizer?.attachLLMDirector( this._llmDirector )
|
|
445
520
|
}
|
|
446
521
|
|
|
447
522
|
// Evaluate gating
|
|
@@ -593,7 +668,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
593
668
|
ideationUserMessage,
|
|
594
669
|
tick: state.tick,
|
|
595
670
|
proposeTemperature,
|
|
596
|
-
meta: { category: 'executive', attribute: 'master', function: 'ideation' },
|
|
671
|
+
meta: { category: 'executive', attribute: 'master', function: 'ideation', demand: processSelection.effortScore },
|
|
597
672
|
} )
|
|
598
673
|
logger.info(
|
|
599
674
|
`[executive] ◆ deliberate propose tick=${state.tick} ` +
|
|
@@ -646,7 +721,10 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
646
721
|
|
|
647
722
|
try {
|
|
648
723
|
// Use streaming call when clients are connected (F3); fall back to regular call.
|
|
649
|
-
|
|
724
|
+
// MODEL_ROUTING W0 — the effort gate already weighed this tick's demand
|
|
725
|
+
// (uncertainty, prior confidence, novelty, a pending reply, stress load);
|
|
726
|
+
// forward it rather than inventing a second measure of the same thing.
|
|
727
|
+
const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master', function: 'decision', demand: processSelection.effortScore }
|
|
650
728
|
const result = this._chunkBroadcaster
|
|
651
729
|
? await this._llmDirector.callStream( systemPrompt, userMessage, state.tick, this._chunkBroadcaster, undefined, masterMeta )
|
|
652
730
|
: await this._llmDirector.call( systemPrompt, userMessage, state.tick, undefined, masterMeta )
|
|
@@ -424,7 +424,7 @@ export class ExecutiveFacet {
|
|
|
424
424
|
ideationUserMessage,
|
|
425
425
|
tick: currentState.tick,
|
|
426
426
|
proposeTemperature,
|
|
427
|
-
meta: { category: 'executive', attribute: 'facet', function: this._currentFocus?.function ?? 'ideation', scope: this.facetId },
|
|
427
|
+
meta: { category: 'executive', attribute: 'facet', function: this._currentFocus?.function ?? 'ideation', scope: this.facetId, demand: processSelection.effortScore },
|
|
428
428
|
} )
|
|
429
429
|
logger.info(
|
|
430
430
|
`[executive.facet] ${this.facetId} ◆ deliberate propose tick=${currentState.tick} ` +
|
|
@@ -465,11 +465,19 @@ export class ExecutiveFacet {
|
|
|
465
465
|
// Use streaming call when a per-facet chunk handler is registered —
|
|
466
466
|
// this enables entity-scoped token delivery (e.g. AuditionEngine SSE).
|
|
467
467
|
// Falls back to regular call when no handler is present.
|
|
468
|
+
// MODEL_ROUTING W0 — same effort gate as master, so a facet's demand is
|
|
469
|
+
// measured on the identical scale (a live message awaiting reply is this
|
|
470
|
+
// focus's stakes-bearing moment, and already weighs into the score).
|
|
468
471
|
const facetMeta: LLMCallMeta = {
|
|
469
472
|
category: 'executive',
|
|
470
473
|
attribute: 'facet',
|
|
471
|
-
function
|
|
474
|
+
// A focus that declares no function is making its decision call, the
|
|
475
|
+
// facet's analogue of master's 'decision'. This previously fell back to
|
|
476
|
+
// 'facet' — an *attribute* value, which quietly created a bogus bucket
|
|
477
|
+
// in the by-function cost breakdown. The typed axes caught it.
|
|
478
|
+
function: this._currentFocus?.function ?? 'decision',
|
|
472
479
|
scope: this.facetId,
|
|
480
|
+
demand: processSelection.effortScore,
|
|
473
481
|
}
|
|
474
482
|
const result = this._chunkHandler
|
|
475
483
|
? await this._llmDirector.callStream( systemPrompt, userMessage, currentState.tick, this._chunkHandler, undefined, facetMeta )
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import type { ReadonlySimulationState } from '#core/types'
|
|
48
|
+
import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
|
|
48
49
|
import type { ExecutiveSummarizer } from '#llm/summarizer'
|
|
49
50
|
import type { ExecutiveContext, PendingMessage, IdeationCandidate } from '#faculties/executive.engine/types'
|
|
50
51
|
import { buildExecutiveContext, type ContextDependencies } from '#faculties/executive.engine/context'
|
|
@@ -158,7 +159,7 @@ export interface FocusSection {
|
|
|
158
159
|
* into the facet's LLM calls as `LLMCallMeta.function` so the TokenTracker can
|
|
159
160
|
* break spend down per facet type. Defaults to 'facet' when unset.
|
|
160
161
|
*/
|
|
161
|
-
function?:
|
|
162
|
+
function?: LLMCallFunction
|
|
162
163
|
/**
|
|
163
164
|
* Optional: Custom output format to append instead of the standard executive format.
|
|
164
165
|
* Pass via PromptBuildOptions.outputFormat when building the user message.
|
package/src/cognition/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
type TokenUsage,
|
|
10
10
|
type TokenLedgerRecord,
|
|
11
11
|
type RecordUsageInput,
|
|
12
|
+
type PriceTable,
|
|
13
|
+
type ModelPrice,
|
|
12
14
|
} from '#cognition/utilities/token.tracker'
|
|
13
15
|
|
|
14
16
|
import { EnergyRegulator, type EnergyRegulatorConfig } from '#faculties/energy.regulator'
|
|
@@ -84,6 +86,8 @@ export {
|
|
|
84
86
|
type TokenUsage,
|
|
85
87
|
type TokenLedgerRecord,
|
|
86
88
|
type RecordUsageInput,
|
|
89
|
+
type PriceTable,
|
|
90
|
+
type ModelPrice,
|
|
87
91
|
|
|
88
92
|
// ── Regulatory Engines ─────────────────────────────────────
|
|
89
93
|
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { TokenTracker } from '#cognition/utilities/token.tracker'
|
|
15
|
+
import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
|
|
16
|
+
|
|
17
|
+
/** Embedding is only ever a read or a write. */
|
|
18
|
+
export type EmbedFunction = Extract<LLMCallFunction, 'recall' | 'index'>
|
|
15
19
|
|
|
16
20
|
export interface EmbeddingProvider {
|
|
17
21
|
readonly modelName: string
|
|
@@ -55,7 +59,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
55
59
|
/**
|
|
56
60
|
* Per-Will token tracker. When provided, each embedding call records its
|
|
57
61
|
* input-token usage under the 'embedding' category so memory-vector spend is
|
|
58
|
-
* visible alongside LLM spend instead of being a silent
|
|
62
|
+
* visible alongside LLM spend instead of being a silent cost leak.
|
|
59
63
|
*/
|
|
60
64
|
tokenTracker?: TokenTracker | null
|
|
61
65
|
} ){
|
|
@@ -68,7 +72,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
68
72
|
this._tokenTracker = config.tokenTracker ?? null
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
async embed( content: unknown, fn:
|
|
75
|
+
async embed( content: unknown, fn: EmbedFunction = 'recall'): Promise<number[]> {
|
|
72
76
|
let response: Response
|
|
73
77
|
try {
|
|
74
78
|
response = await fetch(`${this._apiUrl}/embeddings`, {
|
|
@@ -128,7 +132,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
128
132
|
return embedding
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
async embedBatch( contents: unknown[], fn:
|
|
135
|
+
async embedBatch( contents: unknown[], fn: EmbedFunction = 'index'): Promise<number[][]> {
|
|
132
136
|
// Bounded fan-out: cap concurrent requests at _maxConcurrency instead of
|
|
133
137
|
// firing all of them at once (FN16), while preserving input order.
|
|
134
138
|
const results: number[][] = new Array( contents.length )
|
|
@@ -170,7 +174,7 @@ export class MockEmbedder implements EmbeddingProvider {
|
|
|
170
174
|
this._seed = seed
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
async embed( content: unknown, _fn:
|
|
177
|
+
async embed( content: unknown, _fn: EmbedFunction = 'recall'): Promise<number[]> {
|
|
174
178
|
const str = typeof content === 'string' ? content : JSON.stringify( content )
|
|
175
179
|
const hash = this._hashString( str )
|
|
176
180
|
const embedding: number[] = []
|
|
@@ -184,7 +188,7 @@ export class MockEmbedder implements EmbeddingProvider {
|
|
|
184
188
|
return embedding
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
async embedBatch( contents: unknown[], fn:
|
|
191
|
+
async embedBatch( contents: unknown[], fn: EmbedFunction = 'index'): Promise<number[][]> {
|
|
188
192
|
return Promise.all( contents.map( c => this.embed( c, fn ) ) )
|
|
189
193
|
}
|
|
190
194
|
|