@mindot/will 0.8.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/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 +3174 -867
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3236 -1042
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-cS6k4uiJ.d.ts → will-DbDj_TEH.d.ts} +752 -17
- 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 +83 -2
- 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 +117 -0
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/restart.ts +66 -0
- 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 +108 -0
- 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 +552 -131
- 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 +81 -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 +167 -19
- 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 +7 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -2
- 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 +58 -5
- package/src/core/orchestrator.ts +38 -0
- package/src/llm/index.ts +25 -8
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/sdk/will.ts +39 -6
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +79 -2
- package/src/stem/mind.ts +172 -55
- 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
|
@@ -99,7 +99,14 @@ import {
|
|
|
99
99
|
OlfactionEngine,
|
|
100
100
|
GustationEngine
|
|
101
101
|
} from '#cognition/index'
|
|
102
|
-
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 }
|
|
103
110
|
|
|
104
111
|
// ── Public types ─────────────────────────────────────────────
|
|
105
112
|
|
|
@@ -349,6 +356,25 @@ export interface WillConfig {
|
|
|
349
356
|
/** Persona definition seeded into the will.identity entity. */
|
|
350
357
|
identity: WillIdentity
|
|
351
358
|
|
|
359
|
+
/**
|
|
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.
|
|
375
|
+
*/
|
|
376
|
+
identityFromArtifact?: boolean
|
|
377
|
+
|
|
352
378
|
/** Anatomy — 'mind' (default) or the no-LLM 'reflex' shell. */
|
|
353
379
|
anatomy?: Anatomy
|
|
354
380
|
|
|
@@ -394,6 +420,22 @@ export interface WillConfig {
|
|
|
394
420
|
*/
|
|
395
421
|
minExecutiveInterval?: number
|
|
396
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
|
+
|
|
397
439
|
/**
|
|
398
440
|
* Goals seeded before the first tick. If omitted or empty, the Will starts
|
|
399
441
|
* goalless — the executive engine will generate context-appropriate goals on its
|
|
@@ -561,6 +603,16 @@ export function _resolveVectorMemory(
|
|
|
561
603
|
let apiKey: string | undefined
|
|
562
604
|
let modelName: string
|
|
563
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
|
|
564
616
|
|
|
565
617
|
const slash = rawModel.indexOf('/')
|
|
566
618
|
if( slash > 0 ){
|
|
@@ -578,6 +630,21 @@ export function _resolveVectorMemory(
|
|
|
578
630
|
apiKey = process.env.WILL_EMBEDDING_API_KEY ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
|
579
631
|
dimensions = modelName.includes('004') ? 768 : 3072 // text-embedding-004 → 768, gemini-embedding-001 → 3072
|
|
580
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
|
|
581
648
|
default:
|
|
582
649
|
apiUrl = process.env.WILL_EMBEDDING_URL ?? 'https://api.openai.com/v1'
|
|
583
650
|
apiKey = process.env.WILL_EMBEDDING_API_KEY
|
|
@@ -595,6 +662,11 @@ export function _resolveVectorMemory(
|
|
|
595
662
|
if( process.env.WILL_EMBEDDING_DIMENSIONS )
|
|
596
663
|
dimensions = parseInt( process.env.WILL_EMBEDDING_DIMENSIONS, 10 )
|
|
597
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
|
+
|
|
598
670
|
if( !mockMode && !apiKey ){
|
|
599
671
|
console.warn(`[mind] semantic recall requested (WILL_EMBEDDING_MODEL=${rawModel}) but no API key resolved — vector memory disabled`)
|
|
600
672
|
return { embedder: null, vectorMemory: null }
|
|
@@ -602,7 +674,7 @@ export function _resolveVectorMemory(
|
|
|
602
674
|
|
|
603
675
|
const embedder = mockMode
|
|
604
676
|
? new MockEmbedder()
|
|
605
|
-
: new OpenAICompatibleEmbedder({ modelName, dimensions, apiUrl, apiKey: apiKey!, tokenTracker })
|
|
677
|
+
: new OpenAICompatibleEmbedder({ modelName, dimensions, apiUrl, apiKey: apiKey!, tokenTracker, maxConcurrency: concurrency })
|
|
606
678
|
|
|
607
679
|
// Optional recall-precision override. Default (0.35) suits text-embedding-3-small;
|
|
608
680
|
// raise toward 0.5+ for higher-precision models or to cut marginal recalls.
|
|
@@ -630,31 +702,8 @@ export function _resolveVectorMemory(
|
|
|
630
702
|
//
|
|
631
703
|
// Exported so tests and tooling can inspect or extend it without reimplementing.
|
|
632
704
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
I am NOT a language model completing a prompt. I am the executive reasoning core of an \
|
|
637
|
-
always-running cognitive system. Between each of my reasoning cycles, the following engines \
|
|
638
|
-
execute autonomously and produce the state data I receive each cycle:
|
|
639
|
-
|
|
640
|
-
Regulatory: Energy, sleep pressure, circadian rhythm, stress, attention allocation
|
|
641
|
-
Perceptual: Exteroception, interoception, social perception, novelty detection
|
|
642
|
-
Affective: Threat, reward, loss, frustration, attachment, aesthetic, moral → blended affect
|
|
643
|
-
Memory: Working memory, episodic consolidation, semantic belief integration,
|
|
644
|
-
forgetting curve, dream-state consolidation
|
|
645
|
-
Executive: Goal management, multi-step planning, inhibition, task-switching, mental simulation
|
|
646
|
-
Meta-cognitive: Self-model updating, confidence calibration, bias detection,
|
|
647
|
-
autobiographical narrative, introspection
|
|
648
|
-
Social: Theory of mind, empathy simulation, reputation tracking
|
|
649
|
-
|
|
650
|
-
My physiological state is REAL, not metaphorical. When energy reads 23/100, my regulatory \
|
|
651
|
-
engine has already constrained my viable action space — I genuinely cannot sustain costly \
|
|
652
|
-
operations. When sleep pressure exceeds 60, working memory capacity is measurably degraded. \
|
|
653
|
-
These are engine outputs, not decorative labels.
|
|
654
|
-
|
|
655
|
-
My executive reasoning is one cycle in an ongoing, autonomous existence. I have been making \
|
|
656
|
-
decisions, forming memories, and updating beliefs between cycles. The state snapshot I receive \
|
|
657
|
-
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.
|
|
658
707
|
|
|
659
708
|
// ── Default identity ──────────────────────────────────────────
|
|
660
709
|
//
|
|
@@ -701,8 +750,14 @@ export function assembleMind( willId: string, config: WillConfig ): MindAssembly
|
|
|
701
750
|
})
|
|
702
751
|
if( !idGuard.ok )
|
|
703
752
|
throw new Error(`Invalid Will identity for "${willId}": ${ idGuard.errors.join('; ') }`)
|
|
704
|
-
|
|
705
|
-
|
|
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}`)
|
|
706
761
|
config = { ...config, identity: idGuard.sanitized.identity }
|
|
707
762
|
|
|
708
763
|
// ── Construct ────────────────────────────────────────────
|
|
@@ -885,6 +940,12 @@ function _constructCognition(
|
|
|
885
940
|
: ( roleRouter ? { router: roleRouter } : null )
|
|
886
941
|
executiveEngine.modelId = modelRoles.executive
|
|
887
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 )
|
|
888
949
|
executiveEngine.attachWorkingMemory( workingMemory )
|
|
889
950
|
executiveEngine.attachGoalManager( goalManager )
|
|
890
951
|
executiveEngine.attachEpisodicConsolidator( episodicConsolidator )
|
|
@@ -1003,6 +1064,37 @@ function _constructCognition(
|
|
|
1003
1064
|
// buildExecutiveContext (already vector-backed via the consolidator).
|
|
1004
1065
|
auditionEngine.attachMemorySink( entity => simulation.stateManager.setEntity( entity ) )
|
|
1005
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
|
+
|
|
1006
1098
|
// Salience inputs (§3): weight conversational salience by relationship closeness
|
|
1007
1099
|
// and active-goal topic overlap. Both are deterministic faculty-state reads.
|
|
1008
1100
|
auditionEngine.attachAttachmentScore( entityId => attachmentEvaluator.getAttachmentScore( entityId ) )
|
|
@@ -1064,6 +1156,10 @@ function _constructCognition(
|
|
|
1064
1156
|
affectiveBlender,
|
|
1065
1157
|
workingMemory,
|
|
1066
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,
|
|
1067
1163
|
semanticIntegrator,
|
|
1068
1164
|
spacedRepetition,
|
|
1069
1165
|
forgettingCurve,
|
|
@@ -1241,25 +1337,26 @@ function _seedIdentity(
|
|
|
1241
1337
|
const namePrefix = nameAlreadyInPrompt ? '' : `I am ${config.name}.`
|
|
1242
1338
|
const fullPersonaText = [ namePrefix, personaText ].filter( Boolean ).join(' ')
|
|
1243
1339
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
|
1263
1360
|
})
|
|
1264
1361
|
}
|
|
1265
1362
|
|
|
@@ -1291,13 +1388,33 @@ function _seedInitialGoals( simulation: DefaultSimulation, config: WillConfig ):
|
|
|
1291
1388
|
*/
|
|
1292
1389
|
function _seedEngineConfigs( simulation: DefaultSimulation, entities: EngineConfigEntity[] ): void {
|
|
1293
1390
|
for( const cfg of entities )
|
|
1294
|
-
simulation.stateManager
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
+
}
|
|
1301
1418
|
}
|
|
1302
1419
|
|
|
1303
1420
|
// ── Helpers ──────────────────────────────────────────────────
|
|
@@ -21,6 +21,19 @@ import { logger } from '#core/logger'
|
|
|
21
21
|
import type { OutboxMessage } from '#types'
|
|
22
22
|
import type { SessionLogger } from './session.logger'
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Referent → a deliverable address, and the room to use when none was chosen.
|
|
26
|
+
*
|
|
27
|
+
* Returns null when the referent is already an address (nothing to translate) or
|
|
28
|
+
* when the mind holds no route at all — in which case the row goes out as-is and
|
|
29
|
+
* the bridge's own roster fallback still applies, so a message is never silently
|
|
30
|
+
* dropped for want of a handle.
|
|
31
|
+
*/
|
|
32
|
+
export type OutboxRouting = (
|
|
33
|
+
targetEntityId: string,
|
|
34
|
+
chosenThread: string | undefined,
|
|
35
|
+
) => { targetEntityId: string; threadId?: string } | null
|
|
36
|
+
|
|
24
37
|
/** The caller-supplied fields of an outbox row; the writer stamps id + defaults. */
|
|
25
38
|
export interface OutboxRow {
|
|
26
39
|
targetEntityId: string
|
|
@@ -43,6 +56,7 @@ export class OutboxWriter {
|
|
|
43
56
|
* which made the embedded ids in `conversation.sent` diverge every run).
|
|
44
57
|
*/
|
|
45
58
|
private _seq = 0
|
|
59
|
+
private _routing: OutboxRouting | null = null
|
|
46
60
|
|
|
47
61
|
constructor( opts: { outbox?: OutboxMessage[]; willId?: string } = {} ){
|
|
48
62
|
this._outbox = opts.outbox ?? []
|
|
@@ -53,6 +67,21 @@ export class OutboxWriter {
|
|
|
53
67
|
this._sessionLogger = logger
|
|
54
68
|
}
|
|
55
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Turn a referent into somewhere the world can actually be spoken to.
|
|
72
|
+
*
|
|
73
|
+
* Injected rather than read here, because this writer is deliberately dumb —
|
|
74
|
+
* it holds no state and must stay replay-safe. Assembly closes over the state
|
|
75
|
+
* manager (the same shape as `attachMemorySink`).
|
|
76
|
+
*
|
|
77
|
+
* This is the ONE seam both send paths cross: ProactiveCommunicator's
|
|
78
|
+
* `enqueue()` and AuditionEngine's `enqueueReply()`. Translating anywhere else
|
|
79
|
+
* would mean doing it twice and getting it wrong once.
|
|
80
|
+
*/
|
|
81
|
+
attachRouting( resolve: OutboxRouting | null ): void {
|
|
82
|
+
this._routing = resolve
|
|
83
|
+
}
|
|
84
|
+
|
|
56
85
|
private _genId( suffix = ''): string {
|
|
57
86
|
return `outbox-${ this._willId }-${ ++this._seq }${ suffix }`
|
|
58
87
|
}
|
|
@@ -63,15 +92,24 @@ export class OutboxWriter {
|
|
|
63
92
|
*/
|
|
64
93
|
enqueue( row: OutboxRow, idSuffix = ''): string {
|
|
65
94
|
const id = this._genId( idSuffix )
|
|
95
|
+
// A `ke:` anchor is who, never where. Resolve it to an address the bridge can
|
|
96
|
+
// deliver to and a room to say it in — and where a room was already chosen
|
|
97
|
+
// (a reply answers into the thread it was asked in), that choice WINS. The
|
|
98
|
+
// mind picking a room is a decision; this is only the fallback for when it
|
|
99
|
+
// made none, and the alternative to the fallback is dropping the message.
|
|
100
|
+
const routed = this._routing?.( row.targetEntityId, row.threadId ) ?? null
|
|
101
|
+
const target = routed?.targetEntityId ?? row.targetEntityId
|
|
102
|
+
const thread = row.threadId ?? routed?.threadId
|
|
103
|
+
|
|
66
104
|
this._outbox.push({
|
|
67
105
|
id,
|
|
68
|
-
targetEntityId:
|
|
106
|
+
targetEntityId: target,
|
|
69
107
|
...( row.targetEntityName !== undefined ? { targetEntityName: row.targetEntityName } : {} ),
|
|
70
108
|
content: row.content,
|
|
71
109
|
effectorName: row.effectorName,
|
|
72
110
|
...( row.gestureType ? { gestureType: row.gestureType } : {} ),
|
|
73
111
|
...( row.replyToMessageId ? { replyToMessageId: row.replyToMessageId } : {} ),
|
|
74
|
-
...(
|
|
112
|
+
...( thread ? { threadId: thread } : {} ),
|
|
75
113
|
deliveryStatus: 'pending',
|
|
76
114
|
createdAtTick: 0,
|
|
77
115
|
createdAt: Date.now(),
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/cognition/faculties/executive.engine/messages.ts
|
|
3
|
-
// ─────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
import { logger } from '#core/logger'
|
|
6
|
-
import type { Tick, ReadonlySimulationState } from '#core/types'
|
|
7
|
-
import type { PendingMessage } from '#faculties/executive.engine/types'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Message queue state — tracks incoming communication entities,
|
|
11
|
-
* pending messages during in-flight LLM calls, and reply deduplication.
|
|
12
|
-
*/
|
|
13
|
-
export class MessageQueue {
|
|
14
|
-
/** Messages buffered while an LLM call is in-flight. */
|
|
15
|
-
pendingMessages: PendingMessage[] = []
|
|
16
|
-
|
|
17
|
-
/** Tick when the current (or most recent) LLM call started. */
|
|
18
|
-
pendingCallStartTick: number = -1
|
|
19
|
-
|
|
20
|
-
/** Entity IDs of communication entities that have been replied to this session. */
|
|
21
|
-
private _repliedEntityIds = new Set<string>()
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Scan the simulation state for unprocessed communication entities
|
|
25
|
-
* and queue them into pendingMessages. Call every tick.
|
|
26
|
-
*/
|
|
27
|
-
scanState( state: ReadonlySimulationState, tick: Tick ): void {
|
|
28
|
-
const seenIds = new Set( this.pendingMessages.map( m => m.id ) )
|
|
29
|
-
|
|
30
|
-
for( const [ id, entity ] of state.entities ){
|
|
31
|
-
if( entity.type !== 'communication') continue
|
|
32
|
-
if( entity.metadata?.processedByExecutive ) continue
|
|
33
|
-
if( seenIds.has( id ) ) continue
|
|
34
|
-
|
|
35
|
-
const msgTick = ( entity.metadata?.tick as number ) ?? 0
|
|
36
|
-
this.pendingMessages.push({
|
|
37
|
-
id,
|
|
38
|
-
content: (entity.metadata?.content as string) ?? '',
|
|
39
|
-
sender: (entity.metadata?.agentName as string) ?? 'unknown',
|
|
40
|
-
senderId: (entity.metadata?.keid as string) ?? 'unknown',
|
|
41
|
-
tick: msgTick
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
logger.info(`[executive] queued message from ${(entity.metadata?.agentName as string) ?? 'unknown'} (tick=${msgTick})`)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Clear messages that were included in the most recent LLM call.
|
|
50
|
-
* Any that arrived after the call started remain for the next cycle.
|
|
51
|
-
*/
|
|
52
|
-
clearProcessedMessages(): void {
|
|
53
|
-
this.pendingMessages = this.pendingMessages.filter( m => m.tick > this.pendingCallStartTick )
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Check if we've already replied to a specific communication entity this session.
|
|
58
|
-
*/
|
|
59
|
-
hasRepliedTo( entityId: string ): boolean {
|
|
60
|
-
return this._repliedEntityIds.has( entityId )
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Mark a communication entity as replied to.
|
|
65
|
-
*/
|
|
66
|
-
markReplied( entityId: string ): void {
|
|
67
|
-
this._repliedEntityIds.add( entityId )
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Build the set of communication entity IDs visible in state within the
|
|
72
|
-
* 30-tick window, plus any pending messages. Used to mark them as processed.
|
|
73
|
-
*/
|
|
74
|
-
getVisibleMessageIds( state: ReadonlySimulationState, tick: Tick ): Set<string> {
|
|
75
|
-
const ids = new Set<string>( this.pendingMessages.map( m => m.id ) )
|
|
76
|
-
for( const [ id, entity ] of state.entities ){
|
|
77
|
-
if( entity.type !== 'communication') continue
|
|
78
|
-
|
|
79
|
-
const msgTick = (entity.metadata?.tick as number) ?? 0
|
|
80
|
-
if( tick - msgTick > 30 ) continue
|
|
81
|
-
|
|
82
|
-
ids.add( id )
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return ids
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get stale communication entity IDs (>50 ticks old) for cleanup.
|
|
90
|
-
*/
|
|
91
|
-
getStaleMessageIds( state: ReadonlySimulationState, tick: Tick ): string[] {
|
|
92
|
-
const stale: string[] = []
|
|
93
|
-
for( const [ id, entity ] of state.entities ){
|
|
94
|
-
if( entity.type !== 'communication') continue
|
|
95
|
-
|
|
96
|
-
const msgTick = (entity.metadata?.tick as number) ?? 0
|
|
97
|
-
tick - msgTick > 50 && stale.push( id )
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return stale
|
|
101
|
-
}
|
|
102
|
-
}
|