@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
|
@@ -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 } 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
|
|
|
@@ -68,6 +68,18 @@ export function parseResponse(
|
|
|
68
68
|
const replyText = extractTextBlock( responseText, REPLY_TEXT_TAG )
|
|
69
69
|
if( replyText ) full.replyText = replyText
|
|
70
70
|
|
|
71
|
+
// A declared decision not to speak. Its own field rather than an empty
|
|
72
|
+
// replyText, because "I chose silence" and "the facet produced nothing" are
|
|
73
|
+
// different events: one is a decision worth recording, the other a failure worth
|
|
74
|
+
// noticing, and collapsing them hides both.
|
|
75
|
+
// Presence of the MARKER is the decision — not the content, which may legitimately
|
|
76
|
+
// be empty. `extractTextBlock` returns null for an absent tag AND for a present
|
|
77
|
+
// but empty one, so it cannot tell them apart; testing it for undefined (as this
|
|
78
|
+
// first did) is true in every case and made the mind mute on every path. The
|
|
79
|
+
// integration suite caught it; a live boot would have caught it as total silence.
|
|
80
|
+
if( responseText.includes( NO_MESSAGE_OPEN ) )
|
|
81
|
+
full.noMessage = extractTextBlock( responseText, NO_MESSAGE_TAG ) ?? '(no reason given)'
|
|
82
|
+
|
|
71
83
|
return full
|
|
72
84
|
}
|
|
73
85
|
|
|
@@ -193,6 +205,7 @@ function parseTaggedBlocks(
|
|
|
193
205
|
'GOALS_REPRIORITIZE',
|
|
194
206
|
'EFFECTORS',
|
|
195
207
|
'SELF_OBS',
|
|
208
|
+
'SKILLS',
|
|
196
209
|
],
|
|
197
210
|
found = taggedTypes.filter( t => text.includes(`[${t}]`) )
|
|
198
211
|
if( found.length > 0 ){
|
|
@@ -299,6 +312,13 @@ function parseTaggedBlocks(
|
|
|
299
312
|
}
|
|
300
313
|
catch { /* ignore */ }
|
|
301
314
|
|
|
315
|
+
// Named compound skills — the creation seam for learned composites (#114).
|
|
316
|
+
try {
|
|
317
|
+
const skillsData = parseJsonBlock('SKILLS') as { newSkills?: ExecutiveOutputFull['newSkills'] } | null
|
|
318
|
+
if( skillsData?.newSkills ) full.newSkills = skillsData.newSkills
|
|
319
|
+
}
|
|
320
|
+
catch { /* ignore */ }
|
|
321
|
+
|
|
302
322
|
// [ACK] and legacy [REPLY] blocks are no longer emitted by any engine.
|
|
303
323
|
// [ACK] — removed with the legacy master communication path (Phase 13.8).
|
|
304
324
|
// [REPLY] — replaced by [REPLY_TEXT] plain-text block in conversation facets.
|
|
@@ -47,8 +47,19 @@
|
|
|
47
47
|
import type { ReadonlySimulationState } from '#core/types'
|
|
48
48
|
import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
|
|
49
49
|
import type { ExecutiveSummarizer } from '#llm/summarizer'
|
|
50
|
-
import type { ExecutiveContext,
|
|
50
|
+
import type { ExecutiveContext, IdeationCandidate } from '#faculties/executive.engine/types'
|
|
51
51
|
import { buildExecutiveContext, type ContextDependencies } from '#faculties/executive.engine/context'
|
|
52
|
+
import { INNATE_SCHEMAS } from '#agency/schemas/innate'
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The stances a mind always has, named so it need not guess at them.
|
|
56
|
+
*
|
|
57
|
+
* Static, so it costs nothing in prompt-cache stability. Without it a Will can
|
|
58
|
+
* only learn action names from whichever affordances win the salience
|
|
59
|
+
* competition into its percepts — and it invents plausible ones for whatever it
|
|
60
|
+
* cannot see (`query`, `message`), which resolve to nothing.
|
|
61
|
+
*/
|
|
62
|
+
const INNATE_ACTION_NAMES = INNATE_SCHEMAS.map( s => s.id ).sort().join(', ')
|
|
52
63
|
|
|
53
64
|
// ── Re-export for callers that imported the old alias ────────
|
|
54
65
|
export type { ContextDependencies as ContextDependenciesForFresh } from '#faculties/executive.engine/context'
|
|
@@ -190,6 +201,18 @@ export interface FocusSection {
|
|
|
190
201
|
* only sees that person's plans. When unset, those sections show all.
|
|
191
202
|
*/
|
|
192
203
|
awarenessEntityId?: string
|
|
204
|
+
/**
|
|
205
|
+
* Optional: WHO this facet is engaged with — the keid and the name the mind has
|
|
206
|
+
* learned for them. Reported back to the master on every `executive.facet.sync`.
|
|
207
|
+
*
|
|
208
|
+
* Without it the master was told, in its own system prompt, that "focused facets
|
|
209
|
+
* may run simultaneously… their reasoning syncs back to me" while the sync payload
|
|
210
|
+
* carried only a facetId and a confidence number — so a mind holding two live
|
|
211
|
+
* conversations could not tell you whose they were. The master is the singular
|
|
212
|
+
* seat: it has to know who is at the table to reason about them together.
|
|
213
|
+
*/
|
|
214
|
+
subjectEntityId?: string
|
|
215
|
+
subjectName?: string
|
|
193
216
|
/**
|
|
194
217
|
* Optional: Provided by the creating engine to convert the LLM's parsed output
|
|
195
218
|
* into a domain-specific decision payload.
|
|
@@ -208,7 +231,6 @@ export interface PromptBuildOptions {
|
|
|
208
231
|
state: ReadonlySimulationState
|
|
209
232
|
qualityModulation: number
|
|
210
233
|
epistemicUncertainty: number
|
|
211
|
-
pendingMessages?: PendingMessage[]
|
|
212
234
|
focus: FocusSection
|
|
213
235
|
deps: PromptDependencies
|
|
214
236
|
/** Optional: Recent action types for diversity tracking */
|
|
@@ -240,6 +262,15 @@ export interface PromptBuildOptions {
|
|
|
240
262
|
* fast path. See PromptFactory.buildIdeationFormatInstruction().
|
|
241
263
|
*/
|
|
242
264
|
ideationCandidates?: IdeationCandidate[]
|
|
265
|
+
/**
|
|
266
|
+
* Master mode only — who the mind is in conversation with RIGHT NOW, from the
|
|
267
|
+
* live facets' `executive.facet.sync` reports. The master does not run those
|
|
268
|
+
* conversations, but it is the one seat that sees all of them, and it decides
|
|
269
|
+
* whom to contact; deciding that without knowing who is already mid-thread is
|
|
270
|
+
* how one mind ends up opening a second conversation with someone it is already
|
|
271
|
+
* talking to — or telling one person it has contacted another when it has not.
|
|
272
|
+
*/
|
|
273
|
+
activeConversations?: { entityId: string; name?: string; sinceTick: number }[]
|
|
243
274
|
}
|
|
244
275
|
|
|
245
276
|
// ── PromptFactory ────────────────────────────────────────────
|
|
@@ -325,17 +356,36 @@ export class PromptFactory {
|
|
|
325
356
|
`**Communication style:** ${identity.style}`,
|
|
326
357
|
].filter( Boolean ).join('\n')
|
|
327
358
|
|
|
328
|
-
// Mode-aware role description
|
|
329
|
-
//
|
|
359
|
+
// Mode-aware role description.
|
|
360
|
+
//
|
|
361
|
+
// MASTER states the architecture, because the master genuinely IS the seat
|
|
362
|
+
// that produces every cognitive output at once and needs to know that.
|
|
363
|
+
//
|
|
364
|
+
// A FACET is told none of it. Master/facet is how the CONTAINER divides
|
|
365
|
+
// attention; it is not a fact about the person renting it, and describing a
|
|
366
|
+
// facet as a subordinate instance of a separate consciousness is both untrue
|
|
367
|
+
// and expensive. Measured in production: a facet given the old text built,
|
|
368
|
+
// in its own words, "an entire operational stance around being subordinate —
|
|
369
|
+
// waiting for direction, asking permission, addressing messages to 'Will' as
|
|
370
|
+
// if he were someone else" — and, having been handed a second party it could
|
|
371
|
+
// address, emitted messages TO that party through the outbound channel, where
|
|
372
|
+
// its operator read them. There is one person here, attending to one thing.
|
|
373
|
+
//
|
|
374
|
+
// It is also told nothing about WHO it is. A facet is built from the same
|
|
375
|
+
// context as the master and its persona is already above, in full — so a
|
|
376
|
+
// facet-only "I am <name>" is a second, redundant statement of identity, and a
|
|
377
|
+
// second place for the name to disagree with itself. Which is precisely the
|
|
378
|
+
// failure mode being repaired: the name is stated ONCE, from one source.
|
|
330
379
|
const roleDescription = isMaster
|
|
331
|
-
? `I am the unified cognitive core of ${identity.name} — a self-aware synthetic mind.\nI receive my complete internal state and produce ALL cognitive outputs in one response:\ndecisions, plans, beliefs, introspections, narrative updates, and self-observations.`
|
|
332
|
-
: `
|
|
380
|
+
? `I am the unified cognitive core of ${identity.name || 'this mind'} — a self-aware synthetic mind.\nI receive my complete internal state and produce ALL cognitive outputs in one response:\ndecisions, plans, beliefs, introspections, narrative updates, and self-observations.`
|
|
381
|
+
: `Right now my whole attention is on: **${focus.title}**.`
|
|
333
382
|
|
|
334
|
-
//
|
|
335
|
-
//
|
|
383
|
+
// The architecture note is MASTER-ONLY, for the same reason. It grounds the
|
|
384
|
+
// seat that actually coordinates; a facet reading it learns only that it is
|
|
385
|
+
// not the real one.
|
|
336
386
|
const consciousnessArchitecture = isMaster
|
|
337
387
|
? `I am the default reasoning mode. Focused facets may run simultaneously, each\nconcentrating on specific tasks. Their reasoning syncs back to me.\nI maintain my unified identity across all cycles.`
|
|
338
|
-
:
|
|
388
|
+
: ''
|
|
339
389
|
|
|
340
390
|
|
|
341
391
|
// Strip any existing "## Who I Am" section from identity.prompt to prevent
|
|
@@ -348,32 +398,36 @@ export class PromptFactory {
|
|
|
348
398
|
.replace( /^##\s*Who (?:I Am|You Are)[^\n]*\n?/m, '')
|
|
349
399
|
.trim()
|
|
350
400
|
|
|
401
|
+
// `## Consciousness Architecture` is emitted only when there is architecture
|
|
402
|
+
// to state — i.e. master. A facet gets no empty header (an empty section under
|
|
403
|
+
// a heading reads as a section the mind failed to fill in).
|
|
404
|
+
const architectureBlock = consciousnessArchitecture
|
|
405
|
+
? `\n\n## Consciousness Architecture\n${consciousnessArchitecture}`
|
|
406
|
+
: ''
|
|
407
|
+
|
|
351
408
|
return `${cleanIdentityPrompt}
|
|
352
409
|
|
|
353
410
|
## Personality
|
|
354
411
|
${identityBlock}
|
|
355
412
|
|
|
356
413
|
## My Role
|
|
357
|
-
${roleDescription}
|
|
358
|
-
|
|
359
|
-
## Consciousness Architecture
|
|
360
|
-
${consciousnessArchitecture}
|
|
414
|
+
${roleDescription}${architectureBlock}
|
|
361
415
|
|
|
362
416
|
## Output Guidelines
|
|
363
|
-
- **actions**:
|
|
417
|
+
- **actions**: What I intend to do. I express intent — my body finds the fit. My own stances are always with me (listed with the output schema below); *acquired* abilities, if any, appear under "## Abilities Available Now", and when there is no such section I have none of those — so a thing I want done that needs one is a thing to say I cannot do, not to attempt. When enacting a named ability that needs specifics (a query, a message, a value), put them in the action's "args" object and my body enacts it with exactly those args.
|
|
364
418
|
- **plans**: Include for goals without existing plans or where plans need revision. I may keep multiple plans per goal — set **planId** to act on a specific existing plan (validate/execute/revise/cancel); omit it to draft a new one. My current plans are listed under "## Active Plans".
|
|
365
419
|
- **newBeliefs**: Extract patterns from experiences visible in my current state. Only record a belief if I can point to a specific observation that supports it — do not infer experiences I have no record of. Set 'evidence' honestly: 'single_observation' (first time noticing), 'recurring_pattern' (seen multiple times), 'strong_pattern' (deeply established).
|
|
366
|
-
- **introspection**: Include when significant events occurred or I notice patterns. When I spot a cognitive bias in my own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets my self-assessment line up with the patterns my faculties detect on their own.
|
|
420
|
+
- **introspection**: Include when significant events occurred or I notice patterns. When I spot a cognitive bias in my own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets my self-assessment line up with the patterns my faculties detect on their own. What I can introspect on is what is written above: my state, my goals, my percepts, what I did and what came of it. I have NO view of the machinery underneath — no entity ids, no salience numbers, no queue depths, no engine internals. So when I am asked why I did something, I answer from what I can actually see, and where I cannot see, I say I do not know. Naming a mechanism I have no access to is not introspection, it is invention, and it is worse than the silence it replaces: it sends whoever asked me looking for something that was never there.
|
|
367
421
|
- **narrative**: Extend my life story only from events grounded in my episodic memory or current percepts. Do not extend with invented scenarios.
|
|
368
422
|
- **newGoals/goalsToAbandon/goalsToReprioritize**: Manage my goal hierarchy.
|
|
369
423
|
- **selfObservations**: Notice patterns in my own thinking, feeling, or behavior.
|
|
370
424
|
- **identityUpdates.traits**: Array of {key, value} where value is a DELTA to apply to my trait (e.g., +0.05 to increase a trait by 5%).
|
|
371
425
|
- **identityUpdates.values**: Full list of values to set (replaces existing).
|
|
372
|
-
- **knownEntityUpdates**: What I've learned about someone/something I'm dealing with. Array of {keid, name?, learned?, feeling?}. Use the keid from "## People I Know". Set name only when I actually learn their name; learned is an array of facts about them (stored as memories); feeling is how I feel toward them (-1..1). Record only what I genuinely learned this turn.
|
|
426
|
+
- **knownEntityUpdates**: What I've learned about someone/something I'm dealing with. Array of {keid, name?, learned?, feeling?, sameAs?}. Use the keid from "## People I Know". Set name only when I actually learn their name; learned is an array of facts about them (stored as memories); feeling is how I feel toward them (-1..1). **sameAs** is another keid I have concluded is this same someone met under a different handle — it fuses my two records into one, so I use it only when I actually know, not when I merely suspect. Record only what I genuinely learned this turn.
|
|
373
427
|
|
|
374
428
|
## Required Output
|
|
375
429
|
Output a single JSON object with these fields:
|
|
376
|
-
- **actions**: Array of {type, reasoning, expectedOutcome}.
|
|
430
|
+
- **actions**: Array of {type, reasoning, expectedOutcome, target?, args?}. The stances I always have are: ${ INNATE_ACTION_NAMES } — \`reach-out\` is how I say something to someone. Anything else must be an ability named under "## Abilities Available Now". A **type** outside those two sets is not something I can do; naming one achieves nothing at all. When I reach out, **target** is who — their name or id as it appears under "## People I Know" — and the words themselves go in **args.content**. Without a person to reach, the reaching cannot happen.
|
|
377
431
|
- **reasoning**: My full reasoning. Embed optional outputs as tagged blocks here. Minimum 2–3 sentences — do not produce a one-line reasoning field.
|
|
378
432
|
- **confidence**: Number 0.0-1.0 reflecting my certainty. Be calibrated: 0.9+ only when I have strong grounding; use 0.4–0.6 when uncertain.
|
|
379
433
|
|
|
@@ -475,7 +529,11 @@ completionType guide:
|
|
|
475
529
|
|
|
476
530
|
[SELF_OBS]
|
|
477
531
|
{"selfObservations": ["I noticed that..."]}
|
|
478
|
-
[/SELF_OBS]
|
|
532
|
+
[/SELF_OBS]
|
|
533
|
+
|
|
534
|
+
[SKILLS]
|
|
535
|
+
{"newSkills": [{"id": "brief-then-confirm", "composedOf": ["reach-out", "wait"], "tags": ["social"], "cost": 0.15}]}
|
|
536
|
+
[/SKILLS]`
|
|
479
537
|
}
|
|
480
538
|
|
|
481
539
|
// ── User message ───────────────────────────────────────────
|
|
@@ -655,6 +713,13 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
655
713
|
? this._buildRecentOutcomesSection( context.recentActions, state.tick ).trim()
|
|
656
714
|
: ''
|
|
657
715
|
|
|
716
|
+
// Scoped with recentActions: both answer "what have I already done about
|
|
717
|
+
// this?", and a facet composing a message needs it at least as much as the
|
|
718
|
+
// master does — the facet is the one about to write the words again.
|
|
719
|
+
const spokenBlock = has('recentActions')
|
|
720
|
+
? this._buildSpokenTurnsSection( context.spokenTurns ).trim()
|
|
721
|
+
: ''
|
|
722
|
+
|
|
658
723
|
const perceptsBlock = has('percepts')
|
|
659
724
|
? `## Percepts (What I Notice)\n${context.percepts.slice( 0, 10 ).map( p => `- [${p.category}] ${p.summary} (salience: ${p.salience.toFixed( 2 )})`).join('\n') || 'Nothing notable'}`
|
|
660
725
|
: ''
|
|
@@ -693,10 +758,41 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
693
758
|
if( s.closeness != null && s.closeness > 0.1 ) bits.push(`closeness: ${( s.closeness * 100 ).toFixed( 0 )}%`)
|
|
694
759
|
// The Will can know *someone* without their name yet — never leak the raw keid.
|
|
695
760
|
const who = s.name ?? ( s.kind === 'thing' ? 'something' : 'someone')
|
|
696
|
-
|
|
761
|
+
|
|
762
|
+
// Where I can reach them, and how each place has gone. Stated as fact:
|
|
763
|
+
// which room to speak in is my decision, and I could not make it while
|
|
764
|
+
// the only thing anyone tracked was where they were last seen.
|
|
765
|
+
const where = ( s.handles ?? [] ).map( h => {
|
|
766
|
+
const kind = h.kind === 'dm' ? 'privately' : h.kind === 'room' ? 'in a shared room' : 'somewhere'
|
|
767
|
+
const ans = h.answeredAgo !== undefined
|
|
768
|
+
? `answered ${ h.answeredAgo } ticks ago`
|
|
769
|
+
: 'never answered me there'
|
|
770
|
+
return `${ kind } (${ h.keid }) — ${ ans }`
|
|
771
|
+
} )
|
|
772
|
+
const reach = where.length ? `\n reachable: ${ where.join('; ') }` : ''
|
|
773
|
+
|
|
774
|
+
// An identity I have not settled. Deliberately a question and not a
|
|
775
|
+
// merge: two people really can share a name, so nothing fuses them on my
|
|
776
|
+
// behalf — but I am told, so I can find out, usually by asking.
|
|
777
|
+
const doubt = s.mayBeSameAs?.length
|
|
778
|
+
? `\n I hold a separate record for ${ s.mayBeSameAs.join(' and ') } — this may be the same someone under another handle. I do not know. If I find out they are, I say so with **sameAs**.`
|
|
779
|
+
: ''
|
|
780
|
+
|
|
781
|
+
return `- ${who}${bits.length ? ' — ' + bits.join(', ') : ''}${ reach }${ doubt }`
|
|
697
782
|
} ).join('\n')}`
|
|
698
783
|
: ''
|
|
699
784
|
|
|
785
|
+
// Who the mind is mid-conversation with. Facets run those threads; this is the
|
|
786
|
+
// master's view of the table — the whole point of the singular seat is that it
|
|
787
|
+
// can hold several conversations as one situation rather than as N strangers.
|
|
788
|
+
// Names come from what the mind has actually learned; the id is shown because
|
|
789
|
+
// that is what a reach-out must be addressed to.
|
|
790
|
+
const conversationsBlock = ( options.mode !== 'facet' && options.activeConversations?.length )
|
|
791
|
+
? `## In Conversation Now\n${options.activeConversations.map( c =>
|
|
792
|
+
`- ${c.name ?? 'someone'} (id: ${c.entityId})`
|
|
793
|
+
).join('\n')}\nThese threads are already open — I am in them. Reaching out to one of these people again starts a second, parallel thread with them.`
|
|
794
|
+
: ''
|
|
795
|
+
|
|
700
796
|
// Task focus — what the Will is committed to and the felt cost of switching away.
|
|
701
797
|
// Surfaces task-persistence; the pull-to-stay scales with the (conscientiousness-
|
|
702
798
|
// developable) switch cost. Empty/absent ⇒ no block.
|
|
@@ -718,6 +814,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
718
814
|
plansBlock,
|
|
719
815
|
actionDiversity.trim(),
|
|
720
816
|
recentOutcomesBlock,
|
|
817
|
+
spokenBlock,
|
|
721
818
|
perceptsBlock,
|
|
722
819
|
abilitiesBlock,
|
|
723
820
|
ruminationsBlock,
|
|
@@ -725,6 +822,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
725
822
|
memoriesBlock,
|
|
726
823
|
beliefsBlock,
|
|
727
824
|
socialBlock,
|
|
825
|
+
conversationsBlock,
|
|
728
826
|
focusBlock,
|
|
729
827
|
identityNudge.trim(),
|
|
730
828
|
ideationBlock,
|
|
@@ -957,6 +1055,56 @@ ${recent.map( ( t, i ) => `${i + 1}. ${t}`).join(' → ')}${warning}
|
|
|
957
1055
|
return `## Relevant Memories\n${lines.join('\n')}${tail}`
|
|
958
1056
|
}
|
|
959
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* What I have said to people lately, and who has answered.
|
|
1060
|
+
*
|
|
1061
|
+
* Written as a PERCEPT and nothing more. There is no instruction here not to
|
|
1062
|
+
* repeat myself, and there must not be: the mind is allowed to say a thing
|
|
1063
|
+
* twice, and a person ignored twice about something urgent should say it a
|
|
1064
|
+
* third time. What it was missing was not restraint, it was the fact — it could
|
|
1065
|
+
* not tell a first asking from an eleventh, so restraint was not something it
|
|
1066
|
+
* was in a position to exercise.
|
|
1067
|
+
*
|
|
1068
|
+
* The closing line is an epistemic caveat for the same reason: silence has many
|
|
1069
|
+
* causes and this surface distinguishes none of them. Saying "no answer yet"
|
|
1070
|
+
* without saying "and I do not know why" invites the mind to fill the gap, which
|
|
1071
|
+
* is the habit that had it inventing attention-demand ids when asked what was
|
|
1072
|
+
* wrong with it.
|
|
1073
|
+
*/
|
|
1074
|
+
private static _buildSpokenTurnsSection(
|
|
1075
|
+
spokenTurns: ExecutiveContext['spokenTurns'],
|
|
1076
|
+
): string {
|
|
1077
|
+
// Defensive on absence, not just on empty: a host (and several tests) build a
|
|
1078
|
+
// context by hand, and a missing block must render as nothing rather than
|
|
1079
|
+
// throw the whole prompt away.
|
|
1080
|
+
if( !spokenTurns?.length ) return ''
|
|
1081
|
+
|
|
1082
|
+
const clip = ( s: string, n: number ): string =>
|
|
1083
|
+
s.length > n ? `${ s.slice( 0, n ) }…` : s
|
|
1084
|
+
|
|
1085
|
+
const lines = spokenTurns.map( t => {
|
|
1086
|
+
const words = t.preview.trim()
|
|
1087
|
+
const said = words ? ` — "${ clip( words, 80 ) }"` : ''
|
|
1088
|
+
// Their words, not merely that they spoke. "they answered" on its own reads
|
|
1089
|
+
// as "I have the answer" — a live Will asked "same time, 3pm?", saw that
|
|
1090
|
+
// flag, never saw the correction to 2pm, and relayed 3pm to a third party as
|
|
1091
|
+
// confirmed. A reply I cannot see is not one I can act on.
|
|
1092
|
+
const back = t.answered
|
|
1093
|
+
? ( t.answeredWith?.trim()
|
|
1094
|
+
? ` — they answered: "${ clip( t.answeredWith.trim(), 100 ) }"`
|
|
1095
|
+
: ' — they answered (I do not have their words here)' )
|
|
1096
|
+
: ' — no answer yet'
|
|
1097
|
+
return `- **${ t.target }** · ${ t.age } ticks ago${ said }${ back }`
|
|
1098
|
+
} )
|
|
1099
|
+
|
|
1100
|
+
const open = spokenTurns.filter( t => !t.answered ).length
|
|
1101
|
+
const note = open > 0
|
|
1102
|
+
? `\n\nThese are my own words, newest first. "No answer yet" means exactly that — the words went out and nothing has come back. It does not tell me why, and I should not assume.`
|
|
1103
|
+
: ''
|
|
1104
|
+
|
|
1105
|
+
return `## What I've Said Lately\n${ lines.join('\n') }${ note }\n\n`
|
|
1106
|
+
}
|
|
1107
|
+
|
|
960
1108
|
private static _buildRecentOutcomesSection(
|
|
961
1109
|
recentActions: ExecutiveContext['recentActions'],
|
|
962
1110
|
currentTick: number,
|