@mindot/will 0.8.0 → 0.10.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 +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +6985 -3337
- package/dist/index.js.map +1 -1
- package/dist/surface/channels/discord.d.ts +177 -0
- package/dist/surface/channels/discord.js +394 -0
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +11 -5
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +5382 -1573
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +356 -1
- package/src/cognition/agency/conversation.aim.ts +292 -0
- package/src/cognition/agency/engines/action.selector.ts +142 -5
- package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
- package/src/cognition/agency/engines/reafference.engine.ts +160 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +49 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +73 -1
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -1
- package/src/cognition/event.schemas.ts +22 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +106 -46
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +714 -136
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +120 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
- package/src/cognition/faculties/executive.engine/types.ts +84 -12
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +144 -16
- package/src/cognition/faculties/known.entity.tracker.ts +287 -29
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- 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 +16 -21
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -4
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +492 -53
- package/src/cognition/senses/base.sense.engine.ts +105 -7
- package/src/cognition/senses/index.ts +83 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +296 -0
- package/src/cognition/utilities/token.tracker.ts +94 -10
- package/src/core/orchestrator.ts +51 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +127 -33
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +57 -0
- package/src/pma/index.ts +68 -54
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +171 -23
- package/src/stem/mind.ts +198 -57
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/surface/channels/discord.ts +609 -0
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/surface/channels/types.ts +136 -0
- package/src/{channels → surface/channels}/whatsapp.ts +22 -10
- package/src/{cli.ts → surface/cli.ts} +14 -9
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +194 -33
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.d.ts +0 -69
- package/dist/channels/discord.js +0 -193
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/channels/discord.ts +0 -214
- package/src/channels/types.ts +0 -46
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
package/src/llm/gate.ts
CHANGED
|
@@ -19,11 +19,14 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Configured via env vars:
|
|
21
21
|
* WILL_LLM_CONCURRENCY max simultaneous LLM calls (default 2)
|
|
22
|
+
* WILL_LLM_RESPONSIVE_CONCURRENCY slots reserved for calls someone outside
|
|
23
|
+
* the mind is waiting on (default 1)
|
|
22
24
|
* WILL_LLM_MAX_RETRIES retries before giving up on a 429 (default 4)
|
|
23
25
|
* WILL_LLM_RETRY_BASE_MS first retry wait, doubles each attempt (default 2000)
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import { logger } from '#core/logger'
|
|
29
|
+
import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
|
|
27
30
|
|
|
28
31
|
const MAX_CONCURRENT = parseInt( process.env.WILL_LLM_CONCURRENCY ?? '2')
|
|
29
32
|
|
|
@@ -76,6 +79,51 @@ export class LLMSemaphore {
|
|
|
76
79
|
|
|
77
80
|
export const llmGate = new LLMSemaphore( MAX_CONCURRENT )
|
|
78
81
|
|
|
82
|
+
// ── The responsive lane ───────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A second gate, for calls someone OUTSIDE the mind is waiting on.
|
|
86
|
+
*
|
|
87
|
+
* `WILL_LLM_CONCURRENCY` ships with the comment "the minimum is 3: orbital,
|
|
88
|
+
* conversation, summary" — an intended allocation that one shared semaphore
|
|
89
|
+
* cannot enforce. Three slots means any three callers, and a mind deliberating
|
|
90
|
+
* at ~1,100 output tokens a call keeps all three warm indefinitely.
|
|
91
|
+
*
|
|
92
|
+
* Measured on a live COO over 7 hours: 29 of 45 replies began at or past the
|
|
93
|
+
* limit, and the worst spent 117 of its 131 seconds waiting for a slot rather
|
|
94
|
+
* than generating — 545 tokens at 4.2 tok/s against a provider that does ~39.
|
|
95
|
+
* From outside, that is a mind that read your message and said nothing.
|
|
96
|
+
*
|
|
97
|
+
* The distinction drawn here is NOT that conversation matters more. It is that
|
|
98
|
+
* something outside the mind is blocked on this call — a property of the call
|
|
99
|
+
* itself, which is why the predicate is named for the property and not for a
|
|
100
|
+
* list of blessed functions. Rumination can wait its turn. A person waiting for
|
|
101
|
+
* an answer cannot tell a mind that is thinking from one that is gone.
|
|
102
|
+
*/
|
|
103
|
+
const RESPONSIVE_CONCURRENCY = parseInt( process.env.WILL_LLM_RESPONSIVE_CONCURRENCY ?? '1')
|
|
104
|
+
|
|
105
|
+
export const responsiveGate = new LLMSemaphore( RESPONSIVE_CONCURRENCY )
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The cognitive functions something outside the mind is blocked on.
|
|
109
|
+
*
|
|
110
|
+
* `outreach` is deliberately absent: the mind started that one, and nobody is
|
|
111
|
+
* sitting on the other end waiting. Only work that a waiting party is already
|
|
112
|
+
* owed belongs in the reserved lane — widen this and the reservation stops
|
|
113
|
+
* meaning anything.
|
|
114
|
+
*/
|
|
115
|
+
const AWAITED_OUTSIDE: ReadonlySet<LLMCallFunction> = new Set([ 'conversation' ])
|
|
116
|
+
|
|
117
|
+
/** Whether someone outside the mind is blocked on a call of this function. */
|
|
118
|
+
export function isAwaitedOutside( fn: LLMCallFunction | undefined ): boolean {
|
|
119
|
+
return fn !== undefined && AWAITED_OUTSIDE.has( fn )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The lane a call of this function belongs in. */
|
|
123
|
+
export function gateFor( fn: LLMCallFunction | undefined ): LLMSemaphore {
|
|
124
|
+
return isAwaitedOutside( fn ) ? responsiveGate : llmGate
|
|
125
|
+
}
|
|
126
|
+
|
|
79
127
|
// ── Rate-limit detection ──────────────────────────────────────
|
|
80
128
|
|
|
81
129
|
export function isRateLimitError( err: unknown ): boolean {
|
package/src/llm/index.ts
CHANGED
|
@@ -8,11 +8,12 @@ import type { SessionLogger } from '#stem/tracts/session.logger'
|
|
|
8
8
|
import { writeFileSync, mkdirSync } from 'node:fs'
|
|
9
9
|
import type {
|
|
10
10
|
TokenTracker, LLMCallCategory, LLMCallAttribute, LLMCallFunction,
|
|
11
|
+
LLMCallProcess,
|
|
11
12
|
} from '#cognition/utilities/token.tracker'
|
|
12
13
|
import { type ModelRouter, isNullRouter } from '#llm/routing'
|
|
13
14
|
import { getCompletionRecorder, getCompletionSource } from '#core/completion.recorder'
|
|
14
15
|
import type { LLMCompletionRecord } from '#core/completion.recorder'
|
|
15
|
-
import { withGate } from '#llm/gate'
|
|
16
|
+
import { withGate, gateFor } from '#llm/gate'
|
|
16
17
|
import { matchConversationFocus, wrapReplyText } from '#llm/wire.contracts'
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -263,6 +264,48 @@ export interface LLMCallResult {
|
|
|
263
264
|
cacheWriteTok?: number
|
|
264
265
|
}
|
|
265
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The usage block on an Anthropic-wire SSE event. Every field is optional
|
|
269
|
+
* because which of them a host populates — and on which event — varies: see
|
|
270
|
+
* `takeUsage` in `_callAnthropicStream`.
|
|
271
|
+
*/
|
|
272
|
+
export interface StreamUsage {
|
|
273
|
+
input_tokens?: number
|
|
274
|
+
output_tokens?: number
|
|
275
|
+
cache_read_input_tokens?: number
|
|
276
|
+
cache_creation_input_tokens?: number
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Running token totals accumulated across a stream's usage events. */
|
|
280
|
+
export interface StreamTokens {
|
|
281
|
+
inputTok: number
|
|
282
|
+
outputTok: number
|
|
283
|
+
cacheReadTok: number
|
|
284
|
+
cacheWriteTok: number
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Fold one SSE usage block into the running totals.
|
|
289
|
+
*
|
|
290
|
+
* Which event carries the real numbers is host-specific. Real Anthropic puts
|
|
291
|
+
* the input side on `message_start` and only `output_tokens` on `message_delta`.
|
|
292
|
+
* Z.ai (glm) sends `message_start` with every field zeroed and reports input,
|
|
293
|
+
* output and cache together on the final `message_delta`. Reading input from
|
|
294
|
+
* `message_start` alone recorded 0 input and 0 cache for every GLM call ever
|
|
295
|
+
* made — 441,896 output tokens against 0 input across one 7-hour COO run.
|
|
296
|
+
*
|
|
297
|
+
* So: fold from wherever it arrives, and let a later non-zero reading win. A
|
|
298
|
+
* zero never overwrites a figure already in hand, and a real figure always
|
|
299
|
+
* replaces a placeholder — which makes the fold correct under either ordering.
|
|
300
|
+
*/
|
|
301
|
+
export function foldStreamUsage( acc: StreamTokens, u: StreamUsage ): StreamTokens {
|
|
302
|
+
if( u.input_tokens ) acc.inputTok = u.input_tokens
|
|
303
|
+
if( u.output_tokens ) acc.outputTok = u.output_tokens
|
|
304
|
+
if( u.cache_read_input_tokens ) acc.cacheReadTok = u.cache_read_input_tokens
|
|
305
|
+
if( u.cache_creation_input_tokens ) acc.cacheWriteTok = u.cache_creation_input_tokens
|
|
306
|
+
return acc
|
|
307
|
+
}
|
|
308
|
+
|
|
266
309
|
/**
|
|
267
310
|
* Cost-attribution metadata for a single LLM call. The same director instance is
|
|
268
311
|
* shared by the master executive, every facet (conversation/planning/outreach),
|
|
@@ -275,6 +318,8 @@ export interface LLMCallMeta {
|
|
|
275
318
|
/** The actor/subsystem doing the work. */
|
|
276
319
|
attribute: LLMCallAttribute
|
|
277
320
|
/** The specific cognitive function. */
|
|
321
|
+
process: LLMCallProcess
|
|
322
|
+
/** The specific cognitive function. */
|
|
278
323
|
function: LLMCallFunction
|
|
279
324
|
/** Optional specific id or namespace: facet id, entity id, model name. */
|
|
280
325
|
scope?: string
|
|
@@ -284,13 +329,23 @@ export interface LLMCallMeta {
|
|
|
284
329
|
* How much this call demands, 0..1 — MODEL_ROUTING W0.
|
|
285
330
|
*
|
|
286
331
|
* A *cognitive* measure, never a commercial one: it says how consequential or
|
|
287
|
-
* uncertain this moment is, never who is paying for it.
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* the
|
|
292
|
-
*
|
|
293
|
-
*
|
|
332
|
+
* uncertain this moment is, never who is paying for it.
|
|
333
|
+
*
|
|
334
|
+
* What actually reports it today:
|
|
335
|
+
* · the master and every facet — including the deliberation facet — forward
|
|
336
|
+
* `effortScore`, the a-priori effort gate (uncertainty, prior confidence,
|
|
337
|
+
* novelty, a pending reply, stress load);
|
|
338
|
+
* · structurally background work (summarising, the identity guard) reports a
|
|
339
|
+
* low constant, because it is background whether the mind is calm or in
|
|
340
|
+
* crisis.
|
|
341
|
+
*
|
|
342
|
+
* Note what is NOT here: agency's `stakes( winner, bias )` — the consequence
|
|
343
|
+
* of the specific choice under contest — is computed in
|
|
344
|
+
* `agency/selection.scoring.ts` and never reaches a call. So a deliberation
|
|
345
|
+
* facet currently reports the tick's general effort, not the stakes of the
|
|
346
|
+
* choice it is deliberating. Carrying it is MODEL_ROUTING W1, still open.
|
|
347
|
+
* This comment previously claimed deliberation passed those stakes; it does
|
|
348
|
+
* not, and a doc comment that ships to npm is a bad place to be aspirational.
|
|
294
349
|
*
|
|
295
350
|
* Absent means UNKNOWN, not zero: a consumer must fall back to its default
|
|
296
351
|
* rather than treat a missing value as "cheapest possible".
|
|
@@ -312,7 +367,7 @@ export const BACKGROUND_DEMAND = 0.1
|
|
|
312
367
|
export const ESCALATION_DEMAND = 0.7
|
|
313
368
|
|
|
314
369
|
/** Default attribution when a caller does not tag itself (back-compat). */
|
|
315
|
-
const DEFAULT_CALL_META: LLMCallMeta = { category: 'executive', attribute: 'master',
|
|
370
|
+
const DEFAULT_CALL_META: LLMCallMeta = { category: 'executive', attribute: 'master', process: 'decision', function: '-' }
|
|
316
371
|
|
|
317
372
|
/**
|
|
318
373
|
* Fill in wire and base URL, or say clearly what is missing.
|
|
@@ -606,6 +661,9 @@ export class LLMDirector {
|
|
|
606
661
|
// reached from several vendors at very different prices.
|
|
607
662
|
model: ep.model,
|
|
608
663
|
provider: ep.provider,
|
|
664
|
+
// The router's own input, kept alongside its output. Without this the
|
|
665
|
+
// ledger records WHERE a call went and never WHY.
|
|
666
|
+
demand: meta.demand,
|
|
609
667
|
promptTokens: result.inputTok,
|
|
610
668
|
completionTokens: result.outputTok,
|
|
611
669
|
totalTokens: result.inputTok + result.outputTok,
|
|
@@ -613,6 +671,7 @@ export class LLMDirector {
|
|
|
613
671
|
cacheWriteTokens: result.cacheWriteTok,
|
|
614
672
|
category: meta.category,
|
|
615
673
|
attribute: meta.attribute,
|
|
674
|
+
process: meta.process,
|
|
616
675
|
function: meta.function,
|
|
617
676
|
scope: meta.scope,
|
|
618
677
|
label: meta.label,
|
|
@@ -686,11 +745,24 @@ export class LLMDirector {
|
|
|
686
745
|
onChunk: ( chunk: string ) => void,
|
|
687
746
|
temperature?: number,
|
|
688
747
|
): Promise<LLMCallResult> {
|
|
689
|
-
//
|
|
690
|
-
// truncate a healthy
|
|
691
|
-
//
|
|
748
|
+
// IDLE deadline, not a whole-response one. A hard cap on the total stream
|
|
749
|
+
// would truncate a healthy long generation; no cap at all lets a stalled
|
|
750
|
+
// socket hang forever. A live generation keeps emitting and a dead one goes
|
|
751
|
+
// quiet, so the clock is restarted by every chunk that arrives and fires
|
|
752
|
+
// only after `_timeoutMs` of silence — the first byte included.
|
|
753
|
+
//
|
|
754
|
+
// It used to be cleared the moment the response headers arrived, which left
|
|
755
|
+
// the read loop with no deadline at all. A mid-stream stall then never
|
|
756
|
+
// settled, and the reasoning promise it belonged to stayed in the executive's
|
|
757
|
+
// `_pending` map forever: `hasPendingWork` gated every cycle and the master
|
|
758
|
+
// seat went silent for the full 600-tick prune horizon. Observed live as ten
|
|
759
|
+
// minutes of a mind not deliberating, announced by nothing.
|
|
692
760
|
const controller = new AbortController()
|
|
693
|
-
|
|
761
|
+
let timer = setTimeout( () => controller.abort(), this._timeoutMs )
|
|
762
|
+
const restartDeadline = (): void => {
|
|
763
|
+
clearTimeout( timer )
|
|
764
|
+
timer = setTimeout( () => controller.abort(), this._timeoutMs )
|
|
765
|
+
}
|
|
694
766
|
|
|
695
767
|
let res: Response
|
|
696
768
|
try {
|
|
@@ -715,24 +787,23 @@ export class LLMDirector {
|
|
|
715
787
|
throw err
|
|
716
788
|
}
|
|
717
789
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if( !res.ok )
|
|
790
|
+
if( !res.ok ){
|
|
791
|
+
clearTimeout( timer )
|
|
721
792
|
throw new Error(`Anthropic stream ${res.status}: ${( await res.text() ).slice(0, 300)}`)
|
|
793
|
+
}
|
|
722
794
|
|
|
723
795
|
const reader = res.body!.getReader()
|
|
724
796
|
const decoder = new TextDecoder()
|
|
725
797
|
let buffer = ''
|
|
726
|
-
let fullText
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
let cacheReadTok = 0
|
|
730
|
-
let cacheWriteTok = 0
|
|
798
|
+
let fullText = ''
|
|
799
|
+
const tokens: StreamTokens = {
|
|
800
|
+
inputTok: 0, outputTok: 0, cacheReadTok: 0, cacheWriteTok: 0 }
|
|
731
801
|
|
|
732
802
|
try {
|
|
733
803
|
while( true ){
|
|
734
804
|
const { done, value } = await reader.read()
|
|
735
805
|
if( done ) break
|
|
806
|
+
restartDeadline()
|
|
736
807
|
buffer += decoder.decode( value, { stream: true })
|
|
737
808
|
|
|
738
809
|
const lines = buffer.split('\n')
|
|
@@ -747,32 +818,52 @@ export class LLMDirector {
|
|
|
747
818
|
const ev = JSON.parse( raw ) as {
|
|
748
819
|
type: string
|
|
749
820
|
delta?: { type: string; text?: string; stop_reason?: string }
|
|
750
|
-
message?: { usage?:
|
|
751
|
-
usage?:
|
|
821
|
+
message?: { usage?: StreamUsage }
|
|
822
|
+
usage?: StreamUsage
|
|
752
823
|
}
|
|
753
824
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
825
|
+
// Usage arrives in different events depending on the host. Real
|
|
826
|
+
// Anthropic reports the input side up front on `message_start` and
|
|
827
|
+
// only `output_tokens` on `message_delta`. Anthropic-WIRE hosts do
|
|
828
|
+
// not all follow that: Z.ai (glm) sends `message_start` with zeroed
|
|
829
|
+
// placeholders and puts the true figures — input, output AND cache —
|
|
830
|
+
// on the final `message_delta`. Reading the input side from
|
|
831
|
+
// `message_start` alone therefore recorded 0 input and 0 cache for
|
|
832
|
+
// every GLM call ever made, while output logged correctly: 441,896
|
|
833
|
+
// output tokens against 0 input across one 7-hour COO run, which is
|
|
834
|
+
// not a possible shape for a conversation.
|
|
835
|
+
//
|
|
836
|
+
// So take usage wherever it appears and let a later non-zero reading
|
|
837
|
+
// win — a zero never overwrites a figure already in hand, and a real
|
|
838
|
+
// figure always replaces a placeholder.
|
|
839
|
+
if( ev.type === 'message_start' && ev.message?.usage )
|
|
840
|
+
foldStreamUsage( tokens, ev.message.usage )
|
|
759
841
|
else if( ev.type === 'content_block_delta' && ev.delta?.text ){
|
|
760
842
|
fullText += ev.delta.text
|
|
761
843
|
onChunk( ev.delta.text )
|
|
762
844
|
}
|
|
763
|
-
else if( ev.type === 'message_delta' && ev.usage
|
|
764
|
-
|
|
765
|
-
}
|
|
845
|
+
else if( ev.type === 'message_delta' && ev.usage )
|
|
846
|
+
foldStreamUsage( tokens, ev.usage )
|
|
766
847
|
}
|
|
767
848
|
catch { /* ignore malformed events */ }
|
|
768
849
|
}
|
|
769
850
|
}
|
|
770
851
|
}
|
|
852
|
+
catch( err ){
|
|
853
|
+
// An abort here is the idle deadline, not a caller cancelling: nothing
|
|
854
|
+
// arrived for `_timeoutMs` while the stream was still open. Naming it is
|
|
855
|
+
// the whole point — a stall that throws is a pass that failed, and a pass
|
|
856
|
+
// that failed settles its promise and frees the seat.
|
|
857
|
+
if( controller.signal.aborted )
|
|
858
|
+
throw new Error(`LLM stream to ${ep.provider} stalled — no data for ${this._timeoutMs}ms`)
|
|
859
|
+
throw err
|
|
860
|
+
}
|
|
771
861
|
finally {
|
|
862
|
+
clearTimeout( timer )
|
|
772
863
|
reader.releaseLock()
|
|
773
864
|
}
|
|
774
865
|
|
|
775
|
-
return { text: fullText,
|
|
866
|
+
return { text: fullText, ...tokens }
|
|
776
867
|
}
|
|
777
868
|
|
|
778
869
|
/**
|
|
@@ -814,7 +905,10 @@ export class LLMDirector {
|
|
|
814
905
|
() => ep.wire === 'anthropic'
|
|
815
906
|
? this._callAnthropicStream( ep, systemPrompt, userMessage, () => {}, temperature )
|
|
816
907
|
: this._callProvider( ep, systemPrompt, userMessage, temperature ),
|
|
817
|
-
|
|
908
|
+
`executive/direct:${ meta.function }`,
|
|
909
|
+
// A reply to a waiting person takes the reserved lane; everything the
|
|
910
|
+
// mind is doing for itself shares the general one. See `gateFor`.
|
|
911
|
+
gateFor( meta.function ),
|
|
818
912
|
)
|
|
819
913
|
|
|
820
914
|
// Record token usage + cost into this Will's injected tracker (R4), tagged
|
package/src/llm/routing.ts
CHANGED
|
@@ -99,6 +99,8 @@ export interface RoutingRule {
|
|
|
99
99
|
category?: LLMCallMeta['category']
|
|
100
100
|
/** Match `LLMCallMeta.attribute` exactly (e.g. 'master', 'facet', 'guard'). */
|
|
101
101
|
attribute?: LLMCallMeta['attribute']
|
|
102
|
+
/** Match `LLMCallMeta.process` exactly (e.g. 'decision', 'ideation'). */
|
|
103
|
+
process?: LLMCallMeta['process']
|
|
102
104
|
/** Match `LLMCallMeta.function` exactly (e.g. 'decision', 'consolidation'). */
|
|
103
105
|
function?: LLMCallMeta['function']
|
|
104
106
|
/**
|
|
@@ -182,6 +184,10 @@ export function chainRouters( ...routers: ( ModelRouter | null | undefined )[] )
|
|
|
182
184
|
function matches( rule: RoutingRule, meta: LLMCallMeta ): boolean {
|
|
183
185
|
if( rule.category !== undefined && rule.category !== meta.category ) return false
|
|
184
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
|
|
185
191
|
if( rule.function !== undefined && rule.function !== meta.function ) return false
|
|
186
192
|
|
|
187
193
|
// Absent demand is UNKNOWN, not zero: a demand-bounded rule cannot claim a
|
package/src/llm/summarizer.ts
CHANGED
|
@@ -152,7 +152,7 @@ export class ExecutiveSummarizer {
|
|
|
152
152
|
// MODEL_ROUTING W0 — compression is background work at a constant low
|
|
153
153
|
// demand: distilling excerpts is the same job whether the mind is calm
|
|
154
154
|
// or in crisis, so there is no honest per-tick measure to forward here.
|
|
155
|
-
{ category: 'summarizer', attribute: 'memory', function: 'consolidation', demand: BACKGROUND_DEMAND }
|
|
155
|
+
{ category: 'summarizer', attribute: 'memory', process: 'cog', function: 'consolidation', demand: BACKGROUND_DEMAND }
|
|
156
156
|
)
|
|
157
157
|
|
|
158
158
|
if( result.text ){
|
|
@@ -34,11 +34,68 @@ 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')
|
|
40
59
|
}
|
|
41
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Every marker that is PROTOCOL rather than content.
|
|
63
|
+
*
|
|
64
|
+
* Named explicitly rather than matched as `[ANYTHING]`, because bracketed text is
|
|
65
|
+
* ordinary in real messages — "[1]", "[see §4.4]", "[REDACTED]" are things a mind
|
|
66
|
+
* may legitimately say, and a greedy strip would eat them.
|
|
67
|
+
*/
|
|
68
|
+
export const PROTOCOL_TAGS: readonly string[] = [
|
|
69
|
+
REPLY_TEXT_TAG, NO_MESSAGE_TAG, 'INTROSPECTION', 'NARRATIVE', 'SELF_OBS',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remove any protocol marker that survived inside extracted content.
|
|
74
|
+
*
|
|
75
|
+
* `extractTextBlock` slices from the first `[TAG]` to the first `[/TAG]` after
|
|
76
|
+
* it, so a STRAY second opener inside the body is carried out as content — and
|
|
77
|
+
* the bubble splitter, seeing a line of its own, delivers it as a message.
|
|
78
|
+
*
|
|
79
|
+
* Live: a COO's reply to a technical document went out as four substantive
|
|
80
|
+
* bubbles followed by a fifth reading exactly `[REPLY_TEXT]`. The person got a
|
|
81
|
+
* message whose entire content was the name of the slot it should have filled.
|
|
82
|
+
*
|
|
83
|
+
* These tokens can never be legitimate content — they are the wire, not the
|
|
84
|
+
* words — so stripping them is not censorship of anything the mind meant. A line
|
|
85
|
+
* left empty by the removal is dropped so it cannot become an empty bubble.
|
|
86
|
+
*/
|
|
87
|
+
export function stripProtocolMarkers( text: string ): string {
|
|
88
|
+
let out = text
|
|
89
|
+
for( const tag of PROTOCOL_TAGS )
|
|
90
|
+
out = out.split(`[${ tag }]`).join('').split(`[/${ tag }]`).join('')
|
|
91
|
+
|
|
92
|
+
return out
|
|
93
|
+
.split('\n')
|
|
94
|
+
.filter( ( line, i, all ) => line.trim() !== '' || ( i > 0 && i < all.length - 1 && all[ i - 1 ]?.trim() !== '') )
|
|
95
|
+
.join('\n')
|
|
96
|
+
.trim()
|
|
97
|
+
}
|
|
98
|
+
|
|
42
99
|
// ── Conversation-facet focus — render ↔ match pair ────────────
|
|
43
100
|
// The AuditionEngine RENDERS these lines into the facet focus; the mock LLM
|
|
44
101
|
// MATCHES them to detect "this call is a conversation turn" and synthesize a
|
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.
|
|
@@ -280,7 +282,7 @@ export class PMADistiller {
|
|
|
280
282
|
* @param willId The Will's ID
|
|
281
283
|
* @param willName The Will's display name
|
|
282
284
|
* @param state Current simulation state (from stateManager.snapshot())
|
|
283
|
-
* @param sessionId Active session ID — recorded as
|
|
285
|
+
* @param sessionId Active session ID — recorded on the snapshot as its origin
|
|
284
286
|
* @param dataDir Root data dir (defaults to WILL_DATA_DIR env or './data')
|
|
285
287
|
* @param repertoire The agency competence layer (in-memory manager). When
|
|
286
288
|
* provided, the Will's learned skills + composite schemas are
|
|
@@ -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 ─────────────────────────────────
|
|
@@ -86,3 +86,4 @@ export function auditAssemblyWiring( engines: readonly SimulationEngine[] ): Wir
|
|
|
86
86
|
export function wiringKeys( records: WiringRecord[], status: WiringRecord['status'] ): string[] {
|
|
87
87
|
return records.filter( r => r.status === status ).map( r => `${r.engine}.${r.method}`).sort()
|
|
88
88
|
}
|
|
89
|
+
|
|
@@ -51,7 +51,7 @@ export interface IdentityReviewer {
|
|
|
51
51
|
// MODEL_ROUTING W0 — a one-shot classification at creation time; constant low
|
|
52
52
|
// demand (structurally background, and it runs before there is a mind whose
|
|
53
53
|
// state could modulate it).
|
|
54
|
-
const COHERENCE_META: LLMCallMeta = { category: 'identity-guard', attribute: 'guard', function: 'identity-coherence', demand: BACKGROUND_DEMAND }
|
|
54
|
+
const COHERENCE_META: LLMCallMeta = { category: 'identity-guard', attribute: 'guard', process: 'cog', function: 'identity-coherence', demand: BACKGROUND_DEMAND }
|
|
55
55
|
|
|
56
56
|
const VALID_KINDS = new Set<CoherenceIssue['kind']>( [ 'contradiction', 'false-capability', 'injection', 'incoherence', 'other' ] )
|
|
57
57
|
|