@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
|
@@ -53,6 +53,18 @@ export interface FacetReport {
|
|
|
53
53
|
contextId?: string
|
|
54
54
|
/** Optional dynamic instructions to append to the user message */
|
|
55
55
|
instructions?: string
|
|
56
|
+
/**
|
|
57
|
+
* Attend to something else for THIS report only, leaving the facet's standing
|
|
58
|
+
* focus untouched.
|
|
59
|
+
*
|
|
60
|
+
* A facet's focus was a single mutable field, so anything that wanted a live
|
|
61
|
+
* facet to consider one different thing had to `setFocus()` first — clobbering
|
|
62
|
+
* whatever the facet was already set up for, and racing with any report already
|
|
63
|
+
* queued behind it. That is why a self-initiated message to someone the mind was
|
|
64
|
+
* ALREADY talking to had to be composed by a separate, transient facet that could
|
|
65
|
+
* not see the live conversation at all.
|
|
66
|
+
*/
|
|
67
|
+
focus?: FocusSection
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
export interface FacetDecision {
|
|
@@ -63,6 +75,16 @@ export interface FacetDecision {
|
|
|
63
75
|
decision: unknown
|
|
64
76
|
reasoning: string
|
|
65
77
|
confidence: number
|
|
78
|
+
/**
|
|
79
|
+
* Sim tick this decision was reasoned at.
|
|
80
|
+
*
|
|
81
|
+
* The facet has always known it and never passed it on, so a subscriber writing
|
|
82
|
+
* state in response had no deterministic clock and reached for a process-local
|
|
83
|
+
* counter instead — which resets on restart and collides. Never wall-clock: this
|
|
84
|
+
* reaches ids that live in state, and a wall-clock id makes recorded and replayed
|
|
85
|
+
* runs diverge (R2).
|
|
86
|
+
*/
|
|
87
|
+
tick: number
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
export type FacetEventListener = ( decision: FacetDecision ) => void
|
|
@@ -134,6 +156,19 @@ export class ExecutiveFacet {
|
|
|
134
156
|
private _facetReasoningHistory: string[] = []
|
|
135
157
|
private _masterSyncHistory: string[] = []
|
|
136
158
|
|
|
159
|
+
/**
|
|
160
|
+
* This facet's own prior reasoning, for the supervisor to carry to whichever
|
|
161
|
+
* facet takes over the same keyed thread. Continuity belongs to the thread, not
|
|
162
|
+
* to the instance holding it: a facet reaped mid-conversation used to take the
|
|
163
|
+
* mind's private thinking about that person with it.
|
|
164
|
+
*/
|
|
165
|
+
get reasoningHistory(): string[] { return [ ...this._facetReasoningHistory ] }
|
|
166
|
+
|
|
167
|
+
/** Resume a thread's reasoning on spawn (supervisor-only; see FacetSpawnDeps.key). */
|
|
168
|
+
restoreReasoningHistory( history: string[] ): void {
|
|
169
|
+
this._facetReasoningHistory = [ ...history ]
|
|
170
|
+
}
|
|
171
|
+
|
|
137
172
|
/** Confidence of this facet's *previous* decision — a dual-process gate signal. */
|
|
138
173
|
private _lastConfidence = 0.5
|
|
139
174
|
|
|
@@ -260,9 +295,7 @@ export class ExecutiveFacet {
|
|
|
260
295
|
private _launchReason( report: FacetReport ): void {
|
|
261
296
|
this._inflight++
|
|
262
297
|
this._reason( report )
|
|
263
|
-
.catch( err =>
|
|
264
|
-
logger.error(`[executive.facet] ${this.facetId} reasoning error:`, err )
|
|
265
|
-
)
|
|
298
|
+
.catch( err => logger.error(`[executive.facet] ${this.facetId} reasoning error:`, err ) )
|
|
266
299
|
.finally( () => {
|
|
267
300
|
this._inflight--
|
|
268
301
|
this.markActive( ( this._currentStateRef?.tick as number ) ?? this._lastActiveTick )
|
|
@@ -322,9 +355,14 @@ export class ExecutiveFacet {
|
|
|
322
355
|
|
|
323
356
|
logger.info(`[executive.facet] ${this.facetId} synced from master (tick=${payload.tick})`)
|
|
324
357
|
|
|
325
|
-
// Store
|
|
358
|
+
// Store the wider reasoning for context in the next report().
|
|
359
|
+
//
|
|
360
|
+
// Rendered in the FIRST PERSON and with no mention of a "master": this is the
|
|
361
|
+
// same mind's thinking arriving from where the rest of its attention has been,
|
|
362
|
+
// not a report from a superior. Naming it "Master sync" gave the facet a second
|
|
363
|
+
// party to address — and it addressed it, out loud, on the outbound channel.
|
|
326
364
|
if( payload.reasoning )
|
|
327
|
-
this._masterSyncHistory.push(`[
|
|
365
|
+
this._masterSyncHistory.push(`[tick ${payload.tick}] ${payload.reasoning.slice( 0, 400 )}`)
|
|
328
366
|
|
|
329
367
|
// Keep only last 5 sync entries
|
|
330
368
|
if( this._masterSyncHistory.length > 5 )
|
|
@@ -338,7 +376,11 @@ export class ExecutiveFacet {
|
|
|
338
376
|
if( !this._currentStateRef )
|
|
339
377
|
throw new Error(`[executive.facet] ${this.facetId} no state reference available`)
|
|
340
378
|
|
|
341
|
-
if
|
|
379
|
+
// This report's focus: its own if it carried one, else the facet's standing
|
|
380
|
+
// one. A per-report focus never mutates `_currentFocus`, so a live thread can
|
|
381
|
+
// be asked to attend to one different thing and come straight back.
|
|
382
|
+
const reportFocus = report.focus ?? this._currentFocus
|
|
383
|
+
if( !reportFocus )
|
|
342
384
|
throw new Error(`[executive.facet] ${this.facetId} no focus set. Call setFocus() before report().`)
|
|
343
385
|
|
|
344
386
|
const currentState = this._currentStateRef
|
|
@@ -346,7 +388,7 @@ export class ExecutiveFacet {
|
|
|
346
388
|
// Build fresh context from current state
|
|
347
389
|
// A focus may supply a recall query (e.g. the live conversation message) to
|
|
348
390
|
// drive the single "## Relevant Memories" section — one recall surface (§5).
|
|
349
|
-
const execContext = await PromptFactory.buildFreshContext( this._contextDeps, currentState,
|
|
391
|
+
const execContext = await PromptFactory.buildFreshContext( this._contextDeps, currentState, reportFocus.recallQuery )
|
|
350
392
|
|
|
351
393
|
const qualityModulation = PromptFactory.computeQualityModulation( currentState )
|
|
352
394
|
const epistemicUncertainty = PromptFactory.computeEpistemicUncertainty( execContext, currentState )
|
|
@@ -355,10 +397,10 @@ export class ExecutiveFacet {
|
|
|
355
397
|
// the appropriate content via setFocus()
|
|
356
398
|
const focus: FocusSection = this._masterSyncHistory.length > 0
|
|
357
399
|
? {
|
|
358
|
-
...
|
|
359
|
-
content: `${
|
|
400
|
+
...reportFocus,
|
|
401
|
+
content: `${reportFocus.content}\n\n## What I've Been Turning Over\n${this._masterSyncHistory.join('\n')}`
|
|
360
402
|
}
|
|
361
|
-
:
|
|
403
|
+
: reportFocus
|
|
362
404
|
|
|
363
405
|
// Build system prompt using PromptFactory — same schema as master, [REPLY] gated out.
|
|
364
406
|
const systemPrompt = PromptFactory.buildSystemPrompt( {
|
|
@@ -377,7 +419,7 @@ export class ExecutiveFacet {
|
|
|
377
419
|
// isn't cold on each cycle. Injected before the caller's instructions so the
|
|
378
420
|
// caller content always comes last (highest recency bias from the LLM).
|
|
379
421
|
const continuityBlock = this._facetReasoningHistory.length > 0
|
|
380
|
-
? `## My
|
|
422
|
+
? `## Where My Thinking Had Got To\n${this._facetReasoningHistory.join('\n')}`
|
|
381
423
|
: ''
|
|
382
424
|
|
|
383
425
|
const reportContent = [
|
|
@@ -399,7 +441,7 @@ export class ExecutiveFacet {
|
|
|
399
441
|
stressLoad: currentState.metrics.get('stress.load') ?? 0,
|
|
400
442
|
// A facet's "stakes-bearing moment" is a live message awaiting reply (conversation
|
|
401
443
|
// facets set focus.recallQuery to it); planning/other facets rely on uncertainty.
|
|
402
|
-
hasPendingMessage: !!
|
|
444
|
+
hasPendingMessage: !!reportFocus.recallQuery,
|
|
403
445
|
}, deliberateThreshold )
|
|
404
446
|
|
|
405
447
|
let ideationCandidates: IdeationCandidate[] | undefined
|
|
@@ -410,7 +452,6 @@ export class ExecutiveFacet {
|
|
|
410
452
|
state: currentState,
|
|
411
453
|
qualityModulation,
|
|
412
454
|
epistemicUncertainty,
|
|
413
|
-
pendingMessages: [],
|
|
414
455
|
focus,
|
|
415
456
|
deps: this._promptDeps,
|
|
416
457
|
recentActionTypes: [],
|
|
@@ -424,7 +465,14 @@ export class ExecutiveFacet {
|
|
|
424
465
|
ideationUserMessage,
|
|
425
466
|
tick: currentState.tick,
|
|
426
467
|
proposeTemperature,
|
|
427
|
-
meta: {
|
|
468
|
+
meta: {
|
|
469
|
+
category: 'executive',
|
|
470
|
+
attribute: 'facet',
|
|
471
|
+
process: 'ideation',
|
|
472
|
+
function: reportFocus.function ?? '-',
|
|
473
|
+
scope: this.facetId,
|
|
474
|
+
demand: processSelection.effortScore
|
|
475
|
+
},
|
|
428
476
|
} )
|
|
429
477
|
logger.info(
|
|
430
478
|
`[executive.facet] ${this.facetId} ◆ deliberate propose tick=${currentState.tick} ` +
|
|
@@ -437,7 +485,6 @@ export class ExecutiveFacet {
|
|
|
437
485
|
state: currentState,
|
|
438
486
|
qualityModulation,
|
|
439
487
|
epistemicUncertainty,
|
|
440
|
-
pendingMessages: [],
|
|
441
488
|
focus,
|
|
442
489
|
deps: this._promptDeps,
|
|
443
490
|
recentActionTypes: [],
|
|
@@ -471,11 +518,12 @@ export class ExecutiveFacet {
|
|
|
471
518
|
const facetMeta: LLMCallMeta = {
|
|
472
519
|
category: 'executive',
|
|
473
520
|
attribute: 'facet',
|
|
521
|
+
process: 'decision',
|
|
474
522
|
// A focus that declares no function is making its decision call, the
|
|
475
523
|
// facet's analogue of master's 'decision'. This previously fell back to
|
|
476
524
|
// 'facet' — an *attribute* value, which quietly created a bogus bucket
|
|
477
525
|
// in the by-function cost breakdown. The typed axes caught it.
|
|
478
|
-
function:
|
|
526
|
+
function: reportFocus.function ?? '-',
|
|
479
527
|
scope: this.facetId,
|
|
480
528
|
demand: processSelection.effortScore,
|
|
481
529
|
}
|
|
@@ -555,7 +603,8 @@ export class ExecutiveFacet {
|
|
|
555
603
|
respondingToType: report.type,
|
|
556
604
|
decision: this._extractDecisionPayload( output, focus ),
|
|
557
605
|
reasoning: output.reasoning,
|
|
558
|
-
confidence: output.confidence
|
|
606
|
+
confidence: output.confidence,
|
|
607
|
+
tick: currentState.tick as unknown as number
|
|
559
608
|
}
|
|
560
609
|
|
|
561
610
|
// Promote subscriber-visible fields from the decision object to the event payload
|
|
@@ -566,7 +615,7 @@ export class ExecutiveFacet {
|
|
|
566
615
|
? decision.decision as Record<string, unknown>
|
|
567
616
|
: {}
|
|
568
617
|
|
|
569
|
-
this._bus.publish(
|
|
618
|
+
this._bus.publish({
|
|
570
619
|
type: 'executive.facet.progress',
|
|
571
620
|
version: 1,
|
|
572
621
|
sourceEngine: `executive-facet-${this.facetId}`,
|
|
@@ -587,7 +636,7 @@ export class ExecutiveFacet {
|
|
|
587
636
|
contextId: report.contextId,
|
|
588
637
|
tick: currentState.tick
|
|
589
638
|
}
|
|
590
|
-
}
|
|
639
|
+
})
|
|
591
640
|
|
|
592
641
|
// Conscious learning about others — route name/feeling to known.entity.tracker (the
|
|
593
642
|
// dossier owner) the same way the master does, so routine (un-escalated) conversation
|
|
@@ -595,15 +644,17 @@ export class ExecutiveFacet {
|
|
|
595
644
|
const keUpdates = dec[ 'knownEntityUpdates' ] as ExecutiveOutputFull['knownEntityUpdates'] | undefined
|
|
596
645
|
if( keUpdates )
|
|
597
646
|
for( const u of keUpdates )
|
|
598
|
-
if( u.keid && u.keid !== 'agent-self' && ( u.name || u.feeling != null ) )
|
|
599
|
-
this._bus.publish(
|
|
600
|
-
type: 'known.entity.learned',
|
|
647
|
+
if( u.keid && u.keid !== 'agent-self' && ( u.name || u.feeling != null || u.sameAs ) )
|
|
648
|
+
this._bus.publish({
|
|
649
|
+
type: 'known.entity.learned',
|
|
650
|
+
version: 1,
|
|
601
651
|
sourceEngine: `executive-facet-${this.facetId}`,
|
|
602
|
-
salience:
|
|
603
|
-
|
|
652
|
+
salience: u.sameAs ? 0.7 : 0.5,
|
|
653
|
+
payload: { keid: u.keid, name: u.name, feeling: u.feeling, sameAs: u.sameAs }
|
|
654
|
+
})
|
|
604
655
|
|
|
605
656
|
// Sync back to master
|
|
606
|
-
this._bus.publish(
|
|
657
|
+
this._bus.publish({
|
|
607
658
|
type: 'executive.facet.sync',
|
|
608
659
|
version: 1,
|
|
609
660
|
sourceEngine: `executive-facet-${this.facetId}`,
|
|
@@ -613,9 +664,13 @@ export class ExecutiveFacet {
|
|
|
613
664
|
reasoning: output.reasoning,
|
|
614
665
|
confidence: output.confidence,
|
|
615
666
|
actionTypes: output.actions.map( a => a.type ),
|
|
667
|
+
// WHO this facet is engaged with (FocusSection.subject*). The master keeps
|
|
668
|
+
// the singular seat, so it needs the person, not just the facet number.
|
|
669
|
+
...( focus.subjectEntityId ? { subjectEntityId: focus.subjectEntityId } : {} ),
|
|
670
|
+
...( focus.subjectName ? { subjectName: focus.subjectName } : {} ),
|
|
616
671
|
tick: currentState.tick
|
|
617
672
|
}
|
|
618
|
-
}
|
|
673
|
+
})
|
|
619
674
|
|
|
620
675
|
// Notify all listeners (PlanningEngine, AuditionEngine, etc.).
|
|
621
676
|
//
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// src/cognition/faculties/executive.engine/gating.ts
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
import type { PendingMessage } from '#faculties/executive.engine/types'
|
|
6
5
|
import type { Tick, ReadonlySimulationState } from '#core/types'
|
|
7
6
|
import type { GenerativeModel } from '#cognition/generative.model'
|
|
8
7
|
import {
|
|
@@ -11,23 +10,28 @@ import {
|
|
|
11
10
|
} from '#faculties/executive.engine/config'
|
|
12
11
|
import type { CognitiveEvent } from '#cognition/bus'
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Something in the world is loud enough to wake a resting mind.
|
|
15
|
+
*
|
|
16
|
+
* Percept salience is the whole test now. Two other wake sources used to sit here
|
|
17
|
+
* and neither could ever fire: an unprocessed `communication` entity (a type
|
|
18
|
+
* nothing has ever written) and a non-empty pending-message queue (never pushed
|
|
19
|
+
* to, and the current design forbids it — inbound reaches a facet, and the master
|
|
20
|
+
* learns of it through `executive.facet.handoff` → a high-salience percept, which
|
|
21
|
+
* this catches). Both removed with the queue itself (#114).
|
|
22
|
+
*/
|
|
23
|
+
function hasPendingInstructions( state: ReadonlySimulationState ): boolean {
|
|
24
|
+
for( const entity of state.entities.values() )
|
|
16
25
|
if( entity.type === 'percept' || entity.type === 'percept.social'){
|
|
17
26
|
const salience = (entity.metadata?.salience as number) ?? 0
|
|
18
27
|
if( salience > 0.7 ) return true
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
return true
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return pendingMessages.length > 0
|
|
30
|
+
return false
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export interface GatingDependencies {
|
|
29
34
|
generativeModel: GenerativeModel
|
|
30
|
-
pendingMessages: PendingMessage[]
|
|
31
35
|
hasPendingWork: boolean
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -110,7 +114,7 @@ export function evaluateGating(
|
|
|
110
114
|
if( isResting > 0 || isSleeping > 0 ){
|
|
111
115
|
const
|
|
112
116
|
significantEvent = novelty > 0.8,
|
|
113
|
-
hasPending = hasPendingInstructions( state
|
|
117
|
+
hasPending = hasPendingInstructions( state )
|
|
114
118
|
|
|
115
119
|
if( significantEvent || hasPending )
|
|
116
120
|
return {
|
|
@@ -142,10 +146,6 @@ export function evaluateGating(
|
|
|
142
146
|
if( energy < 15 )
|
|
143
147
|
return { shouldActivate: true, reason: 'energy_critical', cleanedBuffer }
|
|
144
148
|
|
|
145
|
-
// ── Pending messages — always fire ────────────────────────
|
|
146
|
-
if( deps.pendingMessages.length > 0 )
|
|
147
|
-
return { shouldActivate: true, reason: 'pending_message', cleanedBuffer }
|
|
148
|
-
|
|
149
149
|
// ── Normal interval scheduling ────────────────────────────
|
|
150
150
|
if( tick - gs.lastExecutiveTick >= gs.executiveInterval ){
|
|
151
151
|
// Prediction-error gating — uses GenerativeModel.observe() which returns { gated }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
import { logger } from '#core/logger'
|
|
6
|
-
import { REPLY_TEXT_TAG } from '#llm/wire.contracts'
|
|
6
|
+
import { REPLY_TEXT_TAG, NO_MESSAGE_TAG, NO_MESSAGE_OPEN, stripProtocolMarkers } from '#llm/wire.contracts'
|
|
7
7
|
import type { ReadonlySimulationState } from '#core/types'
|
|
8
8
|
import type { ExecutiveOutputFull, ExecutiveOutputMinimal, IdeationCandidate, IdeationOutput } from '#faculties/executive.engine/types'
|
|
9
9
|
|
|
@@ -27,7 +27,9 @@ export function parseResponse(
|
|
|
27
27
|
|
|
28
28
|
let
|
|
29
29
|
actions: Array<{ type: string; reasoning: string; expectedOutcome: string }>,
|
|
30
|
-
confidence = 0.5
|
|
30
|
+
confidence = 0.5,
|
|
31
|
+
/** The mind's own sentence, when the response parsed as JSON and carried one. */
|
|
32
|
+
stated: string | undefined
|
|
31
33
|
|
|
32
34
|
// Strategy 1: direct JSON.parse
|
|
33
35
|
try {
|
|
@@ -41,6 +43,7 @@ export function parseResponse(
|
|
|
41
43
|
|
|
42
44
|
actions = parsed.actions
|
|
43
45
|
confidence = parsed.confidence ?? 0.5
|
|
46
|
+
stated = typeof parsed.reasoning === 'string' ? parsed.reasoning : undefined
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
// Strategy 2: balanced-bracket extractor
|
|
@@ -60,14 +63,45 @@ export function parseResponse(
|
|
|
60
63
|
confidence = confidenceMatch ? parseFloat( confidenceMatch[1]! ) : 0.5
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
//
|
|
66
|
+
// Tagged blocks are scanned over the FULL text — they are documented as embedded
|
|
67
|
+
// in the reasoning field, but a model puts them wherever it likes and the scan
|
|
68
|
+
// has always been forgiving about that. Keep that surface.
|
|
64
69
|
const full = parseTaggedBlocks({ actions, reasoning: fullText, confidence }, state )
|
|
65
70
|
|
|
71
|
+
// …but `reasoning` itself is the mind's SENTENCE, not its serialization.
|
|
72
|
+
//
|
|
73
|
+
// It was `fullText` — the whole JSON blob — for every consumer downstream, and
|
|
74
|
+
// that was invisible for as long as nothing read it as prose. Three things do:
|
|
75
|
+
// the session log records it, a facet is handed the master's as "What I've Been
|
|
76
|
+
// Turning Over" (clipped to 400 chars, so facets have been reading the master's
|
|
77
|
+
// JSON truncated mid-object), and the master now reads a facet's back as what it
|
|
78
|
+
// worked out. Seen live in a prompt: `What I worked out there: { "actions": [...
|
|
79
|
+
//
|
|
80
|
+
// The scan surface and the sentence were the same string only because nothing
|
|
81
|
+
// had ever needed them to be different.
|
|
82
|
+
// Stripped of the tagged blocks, because the format tells the mind to embed them
|
|
83
|
+
// IN this field — so the sentence and the structured payload arrive in one
|
|
84
|
+
// string, and handing the whole thing back as "what I worked out" would just
|
|
85
|
+
// move the serialization problem one level in.
|
|
86
|
+
if( stated ) full.reasoning = stripTaggedBlocks( stated )
|
|
87
|
+
|
|
66
88
|
// [REPLY_TEXT] is a plain-text block that lives OUTSIDE the JSON code block.
|
|
67
89
|
// Search the full response text so we find it even when the LLM used a code fence.
|
|
68
90
|
const replyText = extractTextBlock( responseText, REPLY_TEXT_TAG )
|
|
69
91
|
if( replyText ) full.replyText = replyText
|
|
70
92
|
|
|
93
|
+
// A declared decision not to speak. Its own field rather than an empty
|
|
94
|
+
// replyText, because "I chose silence" and "the facet produced nothing" are
|
|
95
|
+
// different events: one is a decision worth recording, the other a failure worth
|
|
96
|
+
// noticing, and collapsing them hides both.
|
|
97
|
+
// Presence of the MARKER is the decision — not the content, which may legitimately
|
|
98
|
+
// be empty. `extractTextBlock` returns null for an absent tag AND for a present
|
|
99
|
+
// but empty one, so it cannot tell them apart; testing it for undefined (as this
|
|
100
|
+
// first did) is true in every case and made the mind mute on every path. The
|
|
101
|
+
// integration suite caught it; a live boot would have caught it as total silence.
|
|
102
|
+
if( responseText.includes( NO_MESSAGE_OPEN ) )
|
|
103
|
+
full.noMessage = extractTextBlock( responseText, NO_MESSAGE_TAG ) ?? '(no reason given)'
|
|
104
|
+
|
|
71
105
|
return full
|
|
72
106
|
}
|
|
73
107
|
|
|
@@ -164,6 +198,29 @@ function extractBalancedArray( text: string, key: string ): string | null {
|
|
|
164
198
|
return null
|
|
165
199
|
}
|
|
166
200
|
|
|
201
|
+
/** Every block name the reasoning field may carry, for stripping it back to prose. */
|
|
202
|
+
const TAGGED_BLOCK_NAMES = [
|
|
203
|
+
'PLANS', 'BELIEFS', 'INTROSPECTION', 'NARRATIVE', 'IDENTITY', 'KNOWN_ENTITIES',
|
|
204
|
+
'GOALS_NEW', 'GOALS_ABANDON', 'GOALS_REPRIORITIZE', 'EFFECTORS', 'SELF_OBS',
|
|
205
|
+
'SKILLS', 'REPLY_TEXT', 'ACK',
|
|
206
|
+
] as const
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The sentence, with the structured payload taken back out of it.
|
|
210
|
+
*
|
|
211
|
+
* Unclosed blocks are cut to the end of the string on purpose: a truncated
|
|
212
|
+
* `[PLANS]{…` is not prose either, and leaving it in puts half a JSON object in
|
|
213
|
+
* front of whoever reads this as the mind's thinking.
|
|
214
|
+
*/
|
|
215
|
+
export function stripTaggedBlocks( reasoning: string ): string {
|
|
216
|
+
let out = reasoning
|
|
217
|
+
for( const tag of TAGGED_BLOCK_NAMES )
|
|
218
|
+
out = out
|
|
219
|
+
.replace( new RegExp( `\\[${ tag }\\][\\s\\S]*?\\[/${ tag }\\]`, 'g'), '')
|
|
220
|
+
.replace( new RegExp( `\\[${ tag }\\][\\s\\S]*$`, 'g'), '')
|
|
221
|
+
return out.replace( /\n{3,}/g, '\n\n').trim()
|
|
222
|
+
}
|
|
223
|
+
|
|
167
224
|
/**
|
|
168
225
|
* Parse tagged JSON blocks from the reasoning text.
|
|
169
226
|
* The LLM embeds optional outputs as [TAG]...[/TAG] blocks.
|
|
@@ -193,6 +250,7 @@ function parseTaggedBlocks(
|
|
|
193
250
|
'GOALS_REPRIORITIZE',
|
|
194
251
|
'EFFECTORS',
|
|
195
252
|
'SELF_OBS',
|
|
253
|
+
'SKILLS',
|
|
196
254
|
],
|
|
197
255
|
found = taggedTypes.filter( t => text.includes(`[${t}]`) )
|
|
198
256
|
if( found.length > 0 ){
|
|
@@ -299,6 +357,13 @@ function parseTaggedBlocks(
|
|
|
299
357
|
}
|
|
300
358
|
catch { /* ignore */ }
|
|
301
359
|
|
|
360
|
+
// Named compound skills — the creation seam for learned composites (#114).
|
|
361
|
+
try {
|
|
362
|
+
const skillsData = parseJsonBlock('SKILLS') as { newSkills?: ExecutiveOutputFull['newSkills'] } | null
|
|
363
|
+
if( skillsData?.newSkills ) full.newSkills = skillsData.newSkills
|
|
364
|
+
}
|
|
365
|
+
catch { /* ignore */ }
|
|
366
|
+
|
|
302
367
|
// [ACK] and legacy [REPLY] blocks are no longer emitted by any engine.
|
|
303
368
|
// [ACK] — removed with the legacy master communication path (Phase 13.8).
|
|
304
369
|
// [REPLY] — replaced by [REPLY_TEXT] plain-text block in conversation facets.
|
|
@@ -319,7 +384,15 @@ function extractBlock( text: string, tag: string ): string | null {
|
|
|
319
384
|
* Used for [REPLY_TEXT] — content is streamed directly to the client,
|
|
320
385
|
* so it must be clean prose, not a JSON payload.
|
|
321
386
|
*/
|
|
322
|
-
|
|
387
|
+
/**
|
|
388
|
+
* Pull one plain-text block out of a response.
|
|
389
|
+
*
|
|
390
|
+
* Exported for the guard: this slice is where a stray marker survives into
|
|
391
|
+
* content, and the seam is the thing worth testing. `parseResponse` needs a full
|
|
392
|
+
* well-formed response around it, which is exactly the setup that hides a defect
|
|
393
|
+
* living in one line of string handling.
|
|
394
|
+
*/
|
|
395
|
+
export function extractTextBlock( text: string, tag: string ): string | null {
|
|
323
396
|
const open = `[${tag}]`
|
|
324
397
|
const close = `[/${tag}]`
|
|
325
398
|
const openIdx = text.indexOf( open )
|
|
@@ -330,51 +403,60 @@ function extractTextBlock( text: string, tag: string ): string | null {
|
|
|
330
403
|
? text.slice( contentStart, closeIdx )
|
|
331
404
|
: text.slice( contentStart ) // unclosed block — take everything after the open marker
|
|
332
405
|
|
|
333
|
-
|
|
406
|
+
// A stray marker INSIDE the body survives this slice — the block runs from the
|
|
407
|
+
// first open to the first close after it, and anything between is content by
|
|
408
|
+
// construction. Live, a reply went out as four substantive bubbles and a fifth
|
|
409
|
+
// reading exactly `[REPLY_TEXT]`: the person received a message whose whole
|
|
410
|
+
// content was the name of the slot it should have filled.
|
|
411
|
+
return stripProtocolMarkers( content ) || null
|
|
334
412
|
}
|
|
335
413
|
|
|
336
414
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
415
|
+
* What the executive intends when its own reasoning did not come back.
|
|
416
|
+
*
|
|
417
|
+
* Two things had to change here. Every action this produced was PROSE, not a
|
|
418
|
+
* schema: `observe`, `replenish energy`, `enter deep rest to reduce fatigue`,
|
|
419
|
+
* `calm my mind and reduce tension`, `explore`, `learn`, `express_emotion`.
|
|
420
|
+
* Only `rest` names anything the body can enact. The rest reach the agency, fail
|
|
421
|
+
* to resolve, and become an ideomotor push toward an act that does not exist —
|
|
422
|
+
* observed live as `master decided [observe] conf=0.4` four times across two
|
|
423
|
+
* boots, each one a decision that could go nowhere.
|
|
424
|
+
*
|
|
425
|
+
* And the "no urgent need" branch was INVENTION. The executive could not read
|
|
426
|
+
* its own thought, so it made one up and pushed it into the field as intention.
|
|
427
|
+
* That is the mind being told what it wants. The substrate does not need the
|
|
428
|
+
* help: the affordance field is always there, System 1 selects from it every
|
|
429
|
+
* tick without an executive, and it now carries real satiation — the monotony
|
|
430
|
+
* this used to break by cycling a hardcoded list is handled where monotony
|
|
431
|
+
* actually lives.
|
|
432
|
+
*
|
|
433
|
+
* So: reflexes stay, invention goes. Critically low energy, high sleep pressure
|
|
434
|
+
* or high stress still push a real regulatory stance, because a body in trouble
|
|
435
|
+
* should not depend on an LLM parsing correctly. Anything else yields NO action
|
|
436
|
+
* and lets the substrate decide, which is what it is for.
|
|
339
437
|
*/
|
|
340
438
|
export function buildFallbackOutput(
|
|
341
439
|
state: ReadonlySimulationState,
|
|
342
|
-
|
|
440
|
+
_recentActionTypes: string[]
|
|
343
441
|
): ExecutiveOutputFull {
|
|
344
|
-
const energy
|
|
442
|
+
const energy = state.metrics.get('energy.level') ?? 100
|
|
345
443
|
const sleepPressure = state.metrics.get('sleep.pressure') ?? 0
|
|
346
|
-
const stressLoad
|
|
347
|
-
|
|
348
|
-
let actionType = 'observe'
|
|
349
|
-
let actionReason = 'No urgent needs — observing surroundings.'
|
|
444
|
+
const stressLoad = state.metrics.get('stress.load') ?? 0
|
|
350
445
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
actionReason = 'Sleep pressure high — attempting to sleep.'
|
|
358
|
-
}
|
|
359
|
-
else if( stressLoad > 70 ){
|
|
360
|
-
actionType = 'calm my mind and reduce tension'
|
|
361
|
-
actionReason = 'Stress elevated — attempting to meditate.'
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
// Vary the fallback when low-effort actions have dominated recently
|
|
365
|
-
const monotonous = recentActionTypes.filter( t => t === 'reflect' || t === 'observe').length >= 3
|
|
366
|
-
if( monotonous) {
|
|
367
|
-
const alternatives = ['explore', 'learn', 'express_emotion', 'rest']
|
|
368
|
-
const tick = state.tick as number
|
|
369
|
-
|
|
370
|
-
actionType = alternatives[ tick % alternatives.length ]!
|
|
371
|
-
actionReason = 'Breaking monotony — choosing a varied action during LLM fallback.'
|
|
372
|
-
}
|
|
373
|
-
}
|
|
446
|
+
// Innate stances only — what the body can actually enact.
|
|
447
|
+
const reflex: { type: string; reasoning: string } | null =
|
|
448
|
+
energy < 20 ? { type: 'rest', reasoning: 'Energy critically low — I let myself recover.' }
|
|
449
|
+
: sleepPressure > 60 ? { type: 'rest', reasoning: 'Sleep pressure high — I let myself recover.' }
|
|
450
|
+
: stressLoad > 70 ? { type: 'withdraw', reasoning: 'Stress elevated — I pull back from the press of things.' }
|
|
451
|
+
: null
|
|
374
452
|
|
|
375
453
|
return {
|
|
376
|
-
actions:
|
|
377
|
-
|
|
454
|
+
actions: reflex
|
|
455
|
+
? [ { type: reflex.type, reasoning: reflex.reasoning, expectedOutcome: 'The body settles' } ]
|
|
456
|
+
: [],
|
|
457
|
+
reasoning: reflex
|
|
458
|
+
? `Heuristic fallback — my reasoning did not come back. ${ reflex.reasoning }`
|
|
459
|
+
: 'Heuristic fallback — my reasoning did not come back, and nothing about my state is pressing. I intend nothing in particular; my body goes on choosing.',
|
|
378
460
|
confidence: 0.4
|
|
379
461
|
}
|
|
380
462
|
}
|