@mindot/will 0.9.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 +16660 -15206
- package/dist/index.js.map +1 -1
- package/dist/{channels → surface/channels}/discord.d.ts +50 -3
- package/dist/{channels → surface/channels}/discord.js +102 -7
- 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 +7 -4
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +4406 -2904
- 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-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +237 -3
- package/src/cognition/agency/conversation.aim.ts +32 -0
- package/src/cognition/agency/engines/action.selector.ts +59 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
- package/src/cognition/agency/engines/reafference.engine.ts +43 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- 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 +16 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +38 -1
- package/src/cognition/config.mirror.entities.ts +0 -1
- package/src/cognition/event.schemas.ts +0 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/context.ts +41 -35
- package/src/cognition/faculties/executive.engine/engine.ts +270 -113
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
- package/src/cognition/faculties/executive.engine/parser.ts +100 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
- package/src/cognition/faculties/executive.engine/types.ts +18 -15
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +50 -2
- package/src/cognition/faculties/known.entity.tracker.ts +22 -3
- 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/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/working.memory.ts +6 -1
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.embedder.ts +1 -3
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +97 -16
- package/src/cognition/senses/base.sense.engine.ts +104 -6
- package/src/cognition/senses/index.ts +71 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +23 -0
- package/src/cognition/utilities/token.tracker.ts +36 -5
- package/src/core/orchestrator.ts +13 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +102 -25
- package/src/llm/wire.contracts.ts +38 -0
- package/src/pma/index.ts +1 -1
- package/src/stem/index.ts +92 -21
- package/src/stem/mind.ts +26 -2
- 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/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/{channels → surface/channels}/discord.ts +224 -7
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/{channels → surface/channels}/types.ts +1 -1
- package/src/{channels → surface/channels}/whatsapp.ts +9 -6
- package/src/{cli.ts → surface/cli.ts} +6 -6
- 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 +156 -28
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- 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/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
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { logger } from '#core/logger'
|
|
21
|
+
import { readAliases, canonicalOf } from '#cognition/social.identity'
|
|
21
22
|
import type { SessionLogger } from '#stem/tracts/session.logger'
|
|
22
23
|
import type {
|
|
23
24
|
Tick,
|
|
@@ -28,6 +29,9 @@ import type {
|
|
|
28
29
|
EntityInput
|
|
29
30
|
} from '#core/types'
|
|
30
31
|
import { AsyncEngine } from '#core/async.engine'
|
|
32
|
+
import {
|
|
33
|
+
actionRecordEntity, staleActionRecordIds, type ActionStatus,
|
|
34
|
+
} from '#faculties/executive.engine/action.record'
|
|
31
35
|
import type { IntermediateStream } from '#core/async.engine'
|
|
32
36
|
import type { WorkingMemory } from '#faculties/working.memory'
|
|
33
37
|
import type { GoalManager, GoalState } from '#faculties/goal.manager'
|
|
@@ -84,8 +88,6 @@ import type { EngineResult } from '#core/orchestrator'
|
|
|
84
88
|
import type { Duration } from '#core/types'
|
|
85
89
|
import { wallClock } from '#core/wall.clock'
|
|
86
90
|
|
|
87
|
-
// Re-export for compatibility
|
|
88
|
-
export { ExecutiveFacet, type ExecutiveFacetHandle }
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
93
|
* How hard a reasoning facet pulls on attention, as an `attention.demand` urgency.
|
|
@@ -199,13 +201,39 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
199
201
|
private readonly _facetSupervisor = new FacetSupervisor()
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
|
-
* Who each live facet is engaged with, learned from
|
|
203
|
-
* Keyed by facetId; the last sync wins. Rendered into
|
|
204
|
-
* the singular seat can reason across its
|
|
205
|
-
* at the same table" — which it cannot
|
|
206
|
-
* Stale entries age out on read (see
|
|
204
|
+
* Who each live facet is engaged with AND what it concluded there, learned from
|
|
205
|
+
* `executive.facet.sync`. Keyed by facetId; the last sync wins. Rendered into
|
|
206
|
+
* the master's own prompt so the singular seat can reason across its
|
|
207
|
+
* conversations "as if they were sitting at the same table" — which it cannot
|
|
208
|
+
* do while it only knows facet numbers. Stale entries age out on read (see
|
|
209
|
+
* _activeConversations).
|
|
210
|
+
*
|
|
211
|
+
* `concluded` IS THE RETURN LEG, and it did not exist. `executive.facet.sync`
|
|
212
|
+
* has always carried the facet's full `reasoning`; this handler received it and
|
|
213
|
+
* dropped it on the floor. So the sync was one-way: the master's thinking flows
|
|
214
|
+
* DOWN to facets as "What I've Been Turning Over", while what a facet worked out
|
|
215
|
+
* came back as identity and a salience spike — that attention had been engaged,
|
|
216
|
+
* and with whom, never what it concluded.
|
|
217
|
+
*
|
|
218
|
+
* A facet is the same mind with a focus, not a subordinate reporting in. A mind
|
|
219
|
+
* that cannot read back its own thinking from where its attention has been is
|
|
220
|
+
* split, and the prompt comment two files over already named the consequence:
|
|
221
|
+
* "telling one person it has contacted another when it has not".
|
|
222
|
+
*
|
|
223
|
+
* It is kept HERE, on the engine, rather than as a percept, because a percept is
|
|
224
|
+
* swept after 2 ticks and a working-memory item decays below the retrieval
|
|
225
|
+
* threshold in about 9 — while the master's own interval is 15. Anything routed
|
|
226
|
+
* that way is usually gone before the master next reads. This survives to the
|
|
227
|
+
* next cycle by construction.
|
|
207
228
|
*/
|
|
208
|
-
private _facetSubjects = new Map<string, {
|
|
229
|
+
private _facetSubjects = new Map<string, {
|
|
230
|
+
entityId: string
|
|
231
|
+
name?: string
|
|
232
|
+
tick: number
|
|
233
|
+
concluded?: string
|
|
234
|
+
/** Commitments the facet declared toward a THIRD party while attending here. */
|
|
235
|
+
promised?: Array<{ what: string; target?: string; gist?: string; tick: number }>
|
|
236
|
+
}>()
|
|
209
237
|
|
|
210
238
|
// ── Cognitive models ───────────────────────────────────────
|
|
211
239
|
private readonly _model = new GenerativeModel()
|
|
@@ -247,6 +275,11 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
247
275
|
// See EscalationBuffer for the full rationale (R5-g-2).
|
|
248
276
|
private readonly _escalations = new EscalationBuffer()
|
|
249
277
|
|
|
278
|
+
/** First tick of an unbroken `hasPendingWork` suppression; null when the seat is free. */
|
|
279
|
+
private _mutedSince: Tick | null = null
|
|
280
|
+
/** Whether this suppression episode has already been reported. */
|
|
281
|
+
private _mutedWarned = false
|
|
282
|
+
|
|
250
283
|
/** Set/clear the chunk broadcaster (called by WillManager when SSE clients connect). */
|
|
251
284
|
setChunkBroadcaster( fn: (( chunk: string ) => void) | null ): void {
|
|
252
285
|
this._chunkBroadcaster = fn
|
|
@@ -446,7 +479,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
446
479
|
// attachments. The supervisor owns the registry + attention budget and
|
|
447
480
|
// performs the throw-checks on bus / director / state ref.
|
|
448
481
|
return this._facetSupervisor.spawn( {
|
|
449
|
-
...( key ? { key } : {} ),
|
|
482
|
+
...( key ? { key: this._canonicalKey( key ) } : {} ),
|
|
450
483
|
bus: this._bus,
|
|
451
484
|
llmDirector: this._llmDirector,
|
|
452
485
|
stateRef: this._lastStateRef,
|
|
@@ -469,7 +502,45 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
469
502
|
* See FacetSupervisor.handleFor.
|
|
470
503
|
*/
|
|
471
504
|
facetFor( key: string ): ExecutiveFacetHandle | undefined {
|
|
472
|
-
|
|
505
|
+
// Tolerant of BOTH id spaces, canonical first.
|
|
506
|
+
//
|
|
507
|
+
// A facet registered before its anchor was visible is still that person's
|
|
508
|
+
// open thread, and answering "nobody home" would open a second one beside it
|
|
509
|
+
// — the exact failure this key exists to prevent. Two ways that happens: a
|
|
510
|
+
// first message racing the tracker's mint, and a later name-merge that
|
|
511
|
+
// re-points one anchor onto another (KNOWN_ENTITY Phase 5, which is
|
|
512
|
+
// deliberately revisable and never destructively re-keys).
|
|
513
|
+
return this._facetSupervisor.handleFor( this._canonicalKey( key ) )
|
|
514
|
+
?? this._facetSupervisor.handleFor( key )
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* A facet key in the PERSON's id space, not the address's.
|
|
519
|
+
*
|
|
520
|
+
* `FacetSpawnDeps.key` is `<role>:<entityId>`, and the two sides of that
|
|
521
|
+
* contract disagreed about which space `entityId` lives in. Audition spawns a
|
|
522
|
+
* conversation facet keyed by `percept.speakerEntityId` — the transport address
|
|
523
|
+
* the message arrived on (`discord:1019…`) — while `authorOutreach` asks for
|
|
524
|
+
* `conversation:<anchor>` (`ke:1sqlkux`), because the executive resolves a
|
|
525
|
+
* person to their anchor before willing anything at them.
|
|
526
|
+
*
|
|
527
|
+
* So the dedup this key exists for never fired for a master-willed outreach.
|
|
528
|
+
* Every one spawned a transient rival facet on someone the mind was already
|
|
529
|
+
* talking to — which is what the key was added to prevent, and what its own
|
|
530
|
+
* comment claims it does. Observed live: a reply and an unprompted second
|
|
531
|
+
* answer to the same question 27 seconds apart, the second composed by a facet
|
|
532
|
+
* that could not see the first.
|
|
533
|
+
*
|
|
534
|
+
* Safe to resolve here because KnownEntityTracker mints the anchor on FIRST
|
|
535
|
+
* sight of an address, deterministically from it (R2), so a conversation is
|
|
536
|
+
* keyed the same way from its first message onward.
|
|
537
|
+
*/
|
|
538
|
+
private _canonicalKey( key: string ): string {
|
|
539
|
+
const cut = key.indexOf(':')
|
|
540
|
+
if( cut < 0 || !this._lastStateRef ) return key
|
|
541
|
+
const id = key.slice( cut + 1 )
|
|
542
|
+
const canon = canonicalOf( readAliases( this._lastStateRef.entities ), id )
|
|
543
|
+
return canon === id ? key : `${ key.slice( 0, cut ) }:${ canon }`
|
|
473
544
|
}
|
|
474
545
|
|
|
475
546
|
// ── CognitiveEngine interface ──────────────────────────────
|
|
@@ -477,6 +548,49 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
477
548
|
subscribes(): string[] { return [ '*' ] }
|
|
478
549
|
publishes(): CognitiveEventSchema[] { return [] }
|
|
479
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Fold one resolved act into the mind's record of its own doing.
|
|
553
|
+
*
|
|
554
|
+
* `withheld` is kept distinct from `failed` deliberately: the mind formed the
|
|
555
|
+
* act and chose not to complete it, which is a judgement, not an inability.
|
|
556
|
+
* Collapsing them is the #123 mistake — a COO learning it was bad at speaking
|
|
557
|
+
* from the times it decided not to speak.
|
|
558
|
+
*/
|
|
559
|
+
private _recordAction( event: CognitiveEvent ): StateCommands | void {
|
|
560
|
+
const p = event.payload as {
|
|
561
|
+
actionType?: unknown; success?: unknown; targetEntityId?: unknown
|
|
562
|
+
description?: unknown; planId?: unknown; tick?: unknown
|
|
563
|
+
} | undefined
|
|
564
|
+
const type = typeof p?.actionType === 'string' ? p.actionType : undefined
|
|
565
|
+
if( !type ) return
|
|
566
|
+
|
|
567
|
+
const status: ActionStatus = event.type === 'action.withheld' ? 'withheld'
|
|
568
|
+
: p?.success === false ? 'failed'
|
|
569
|
+
: 'completed'
|
|
570
|
+
|
|
571
|
+
const record = actionRecordEntity({
|
|
572
|
+
type, status,
|
|
573
|
+
tick: typeof p?.tick === 'number' ? p.tick : 0,
|
|
574
|
+
...( typeof p?.targetEntityId === 'string' ? { targetEntityId: p.targetEntityId } : {} ),
|
|
575
|
+
// WHOLE. 120 here, 300 at the session log and 700 at the MCP boundary were
|
|
576
|
+
// three unexamined numbers cutting the same string, and the cut was the
|
|
577
|
+
// engine deciding how much of what a host said the mind was allowed to
|
|
578
|
+
// keep. What a host sends is consumed in its entirety; what the engine
|
|
579
|
+
// itself composes it may bound (see `PERCEPT_SUMMARY_CAP`), because there
|
|
580
|
+
// it is not destroying anyone's only copy.
|
|
581
|
+
//
|
|
582
|
+
// Safe to leave unbounded only because P2 split fate from facts: this
|
|
583
|
+
// carries a FATE, which is short by nature. The answer to a lookup leaves
|
|
584
|
+
// through `observation` and reaches the mind as a percept.
|
|
585
|
+
outcome: typeof p?.description === 'string' ? p.description : '',
|
|
586
|
+
...( typeof p?.planId === 'string' ? { planId: p.planId } : {} ),
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
// Pruning happens in `react`, which has frozen state to prune against; an
|
|
590
|
+
// event handler sees only the event.
|
|
591
|
+
return { set: [ record ] }
|
|
592
|
+
}
|
|
593
|
+
|
|
480
594
|
snapshot(): Record<string, unknown> {
|
|
481
595
|
return {
|
|
482
596
|
bufferSize: this._gatingState.salienceBuffer.length,
|
|
@@ -488,6 +602,24 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
488
602
|
onCognitiveEvent( event: CognitiveEvent ): StateCommands | void {
|
|
489
603
|
if( event.sourceEngine === this.name ) return
|
|
490
604
|
|
|
605
|
+
// What became of what I did.
|
|
606
|
+
//
|
|
607
|
+
// the outcomes section has existed — section, builder and type —
|
|
608
|
+
// since it was written, fed by `context.ts` scanning `decision.record` for
|
|
609
|
+
// an `actionStatus` that nothing in the engine has ever set. It rendered 0
|
|
610
|
+
// times across every prompt a live COO received. So the mind could see what
|
|
611
|
+
// it had SAID and never what it had DONE, and its only other record of
|
|
612
|
+
// acting is `## Recent Actions`, which is built from the executive's own
|
|
613
|
+
// DECISIONS. A record of acting that contains only intentions cannot
|
|
614
|
+
// distinguish an intention from an act — and one did not, reporting a spec
|
|
615
|
+
// as drafted and sent when it had no effectors and had sent nothing.
|
|
616
|
+
//
|
|
617
|
+
// Written here because these events already arrive here (`subscribes()` is
|
|
618
|
+
// `['*']`) and the returned commands are drained onto state by the
|
|
619
|
+
// orchestrator. See `action.record.ts`.
|
|
620
|
+
if( event.type === 'action.outcome' || event.type === 'action.withheld')
|
|
621
|
+
return this._recordAction( event )
|
|
622
|
+
|
|
491
623
|
// Spare attention scales the facet allowance within the persona's ceiling.
|
|
492
624
|
if( event.type === 'attention.state.changed'){
|
|
493
625
|
const p = event.payload as { freeFraction: number }
|
|
@@ -553,6 +685,14 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
553
685
|
result.commands.delete = [ ...( result.commands.delete ?? [] ), ...focus.delete ]
|
|
554
686
|
}
|
|
555
687
|
|
|
688
|
+
// Hold as many acts as the prompt shows and no more. Done here rather than
|
|
689
|
+
// in the event handler because pruning needs frozen state to prune against.
|
|
690
|
+
const stale = staleActionRecordIds( state.entities as never )
|
|
691
|
+
if( stale.length > 0 ){
|
|
692
|
+
result.commands ??= {}
|
|
693
|
+
result.commands.delete = [ ...( result.commands.delete ?? [] ), ...stale ]
|
|
694
|
+
}
|
|
695
|
+
|
|
556
696
|
return result
|
|
557
697
|
}
|
|
558
698
|
|
|
@@ -677,6 +817,26 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
677
817
|
// Always update counters, even when not activating
|
|
678
818
|
updateGatingState( this._gatingState, state, tick, result.shouldActivate, result.cleanedBuffer )
|
|
679
819
|
|
|
820
|
+
// A reasoning pass that never settles holds this gate shut until the
|
|
821
|
+
// AsyncEngine's stale prune drops it 600 ticks later, and every one of those
|
|
822
|
+
// ticks was silent — the mind simply stopped deliberating and nothing said
|
|
823
|
+
// why. Warn ONCE, at the point a pass has outlived any healthy one; warning
|
|
824
|
+
// every tick would bury the line it exists to surface.
|
|
825
|
+
if( result.reason === 'hasPendingWork'){
|
|
826
|
+
this._mutedSince ??= tick
|
|
827
|
+
// Once per episode, not once at an exact tick: react() is not guaranteed to
|
|
828
|
+
// be called on every tick, and an `=== threshold` test that the clock steps
|
|
829
|
+
// over never fires at all — a silent warning about a silence.
|
|
830
|
+
if( !this._mutedWarned && tick - this._mutedSince >= this._executiveInterval * 3 ){
|
|
831
|
+
this._mutedWarned = true
|
|
832
|
+
logger.warn(
|
|
833
|
+
`[executive] no cycle for ${ tick - this._mutedSince } ticks — a reasoning pass ` +
|
|
834
|
+
`has not settled and is holding the seat (tick=${tick})`
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
else { this._mutedSince = null; this._mutedWarned = false }
|
|
839
|
+
|
|
680
840
|
if( result.shouldActivate )
|
|
681
841
|
logger.info(`[executive] activating — reason: ${result.reason} (tick=${tick})`)
|
|
682
842
|
|
|
@@ -991,11 +1151,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
991
1151
|
plansCount: executiveOutput.plans?.length ?? 0,
|
|
992
1152
|
goalsNew: executiveOutput.newGoals ?? [],
|
|
993
1153
|
goalsAbandon: executiveOutput.goalsToAbandon ?? [],
|
|
994
|
-
replies: ( executiveOutput.conversationReplies ?? [] ).map( r => ( {
|
|
995
|
-
targetEntityId: r.targetEntityId,
|
|
996
|
-
targetEntityName: r.targetEntityName,
|
|
997
|
-
messages: r.messages,
|
|
998
|
-
} ) ),
|
|
999
1154
|
hasIntrospection: !!executiveOutput.introspection,
|
|
1000
1155
|
hasNarrative: !!executiveOutput.narrative
|
|
1001
1156
|
} )
|
|
@@ -1191,16 +1346,17 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1191
1346
|
|
|
1192
1347
|
// Clear processed messages
|
|
1193
1348
|
|
|
1194
|
-
// Merge escalation percepts into final commands
|
|
1195
|
-
//
|
|
1196
|
-
|
|
1197
|
-
|
|
1349
|
+
// Merge escalation percepts into the final commands.
|
|
1350
|
+
//
|
|
1351
|
+
// `_reconcileUndertakings` used to sit here, discharging promises the engine
|
|
1352
|
+
// judged kept and dropping restatements. Both are gone with the undertaking
|
|
1353
|
+
// harness: a promise is no longer a percept, so there is nothing to
|
|
1354
|
+
// reconcile. What the facet declared rides the sync tract to the master's own
|
|
1355
|
+
// prompt, and what to do about it — including whether it is still worth
|
|
1356
|
+
// doing — is the mind's, through the goal machinery it already has.
|
|
1357
|
+
if( escalationPercepts.length ){
|
|
1198
1358
|
commands.set ??= []
|
|
1199
|
-
commands.set.push( ...
|
|
1200
|
-
}
|
|
1201
|
-
if( discharge.length ){
|
|
1202
|
-
commands.delete ??= []
|
|
1203
|
-
commands.delete.push( ...discharge )
|
|
1359
|
+
commands.set.push( ...escalationPercepts )
|
|
1204
1360
|
}
|
|
1205
1361
|
|
|
1206
1362
|
// ── Persist deliberation cache (mirrors the rolling summary) ──
|
|
@@ -1271,7 +1427,13 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1271
1427
|
|
|
1272
1428
|
return [ ...this._facetSubjects.values() ]
|
|
1273
1429
|
.sort( ( a, b ) => b.tick - a.tick )
|
|
1274
|
-
.map( s => ({
|
|
1430
|
+
.map( s => ({
|
|
1431
|
+
entityId: s.entityId,
|
|
1432
|
+
...( s.name ? { name: s.name } : {} ),
|
|
1433
|
+
sinceTick: s.tick,
|
|
1434
|
+
...( s.concluded ? { concluded: s.concluded } : {} ),
|
|
1435
|
+
...( s.promised?.length ? { promised: s.promised } : {} ),
|
|
1436
|
+
}) )
|
|
1275
1437
|
}
|
|
1276
1438
|
|
|
1277
1439
|
/**
|
|
@@ -1311,76 +1473,6 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1311
1473
|
* claim that the words are unsent; whether to say more to that person is then an
|
|
1312
1474
|
* ordinary competition like any other.
|
|
1313
1475
|
*/
|
|
1314
|
-
private _reconcileUndertakings(
|
|
1315
|
-
incoming: EntityInput[],
|
|
1316
|
-
state: ReadonlySimulationState,
|
|
1317
|
-
): { keep: EntityInput[]; discharge: string[] } {
|
|
1318
|
-
/** Latest tick at which anything was sent to each target. */
|
|
1319
|
-
const contacted = new Map<string, number>()
|
|
1320
|
-
for( const [ , e ] of state.entities ){
|
|
1321
|
-
if( e.type !== 'conversation.sent') continue
|
|
1322
|
-
const m = e.metadata as Record<string, unknown> | undefined
|
|
1323
|
-
const target = typeof m?.['targetEntityId'] === 'string' ? m['targetEntityId'] as string : undefined
|
|
1324
|
-
if( !target ) continue
|
|
1325
|
-
// metadata.tick where set, else the entity's own sim-clock tick. Reading
|
|
1326
|
-
// metadata alone defaulted every record to 0, so `contacted >= madeAt` was
|
|
1327
|
-
// false for any promise made after tick 0 — this discharge never once fired.
|
|
1328
|
-
const at = typeof m?.['tick'] === 'number' ? m['tick'] as number
|
|
1329
|
-
: typeof ( e as { tick?: number } ).tick === 'number' ? ( e as { tick?: number } ).tick as number
|
|
1330
|
-
: 0
|
|
1331
|
-
contacted.set( target, Math.max( contacted.get( target ) ?? 0, at ) )
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
/** An undertaking is spent once we have reached its target since making it. */
|
|
1335
|
-
const honoured = ( target: string | undefined, madeAt: number ): boolean =>
|
|
1336
|
-
!!target && ( contacted.get( target ) ?? -1 ) >= madeAt
|
|
1337
|
-
|
|
1338
|
-
const undertakingOf = ( m: Record<string, unknown> | undefined ) =>
|
|
1339
|
-
m?.['category'] === 'undertaking'
|
|
1340
|
-
? {
|
|
1341
|
-
target: typeof m['undertakingTarget'] === 'string' ? m['undertakingTarget'] as string : undefined,
|
|
1342
|
-
madeAt: typeof m['tick'] === 'number' ? m['tick'] as number : 0,
|
|
1343
|
-
}
|
|
1344
|
-
: undefined
|
|
1345
|
-
|
|
1346
|
-
const discharge: string[] = []
|
|
1347
|
-
/** Targets we are already carrying a live, unhonoured undertaking toward. */
|
|
1348
|
-
const carrying = new Set<string>()
|
|
1349
|
-
|
|
1350
|
-
for( const [ id, e ] of state.entities ){
|
|
1351
|
-
if( e.type !== 'percept') continue
|
|
1352
|
-
const u = undertakingOf( e.metadata as Record<string, unknown> | undefined )
|
|
1353
|
-
if( !u ) continue
|
|
1354
|
-
|
|
1355
|
-
// No target ⇒ it predates the field this reconciliation keys on, so it can
|
|
1356
|
-
// never be matched against a contact and would assert "nothing has gone to
|
|
1357
|
-
// them yet" for the rest of the mind's life. Seven of these were found in a
|
|
1358
|
-
// live snapshot, each one re-read every cycle. Undischargeable is strictly
|
|
1359
|
-
// worse than gone.
|
|
1360
|
-
if( !u.target ){ discharge.push( id ); continue }
|
|
1361
|
-
|
|
1362
|
-
if( honoured( u.target, u.madeAt ) ) discharge.push( id )
|
|
1363
|
-
else carrying.add( u.target )
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// Drop an incoming restatement of something already honoured or already held.
|
|
1367
|
-
// Duplicates were how seven of these piled up: each master cycle drained a
|
|
1368
|
-
// fresh copy of a promise that could never be marked kept.
|
|
1369
|
-
const keep = incoming.filter( p => {
|
|
1370
|
-
const u = undertakingOf( p.metadata as Record<string, unknown> | undefined )
|
|
1371
|
-
if( !u?.target ) return true
|
|
1372
|
-
if( honoured( u.target, u.madeAt ) ) return false
|
|
1373
|
-
if( carrying.has( u.target ) ) return false
|
|
1374
|
-
carrying.add( u.target )
|
|
1375
|
-
return true
|
|
1376
|
-
} )
|
|
1377
|
-
|
|
1378
|
-
if( discharge.length )
|
|
1379
|
-
logger.info(`[executive] ${ discharge.length } undertaking(s) discharged — the contact was made`)
|
|
1380
|
-
|
|
1381
|
-
return { keep, discharge }
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1476
|
private _onFacetSync( event: CognitiveEvent ): void {
|
|
1385
1477
|
const payload = event.payload as {
|
|
1386
1478
|
facetId?: string
|
|
@@ -1391,13 +1483,28 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1391
1483
|
subjectName?: string
|
|
1392
1484
|
}
|
|
1393
1485
|
|
|
1394
|
-
// Remember WHO this facet is with,
|
|
1395
|
-
|
|
1486
|
+
// Remember WHO this facet is with, and WHAT it worked out there. The second
|
|
1487
|
+
// half is the return leg: `reasoning` has always been on this payload and was
|
|
1488
|
+
// read into a local and then dropped, so the master learned that its attention
|
|
1489
|
+
// had been engaged and never what it concluded.
|
|
1490
|
+
//
|
|
1491
|
+
// Carried whole, not clipped. For the master this is the only copy — the
|
|
1492
|
+
// facet's own history is in the facet, and once that conversation ends the
|
|
1493
|
+
// thinking is gone. Live facets are few (one per open conversation), so this
|
|
1494
|
+
// is bounded by how many people the mind is talking to, not by traffic.
|
|
1495
|
+
if( payload.facetId && payload.subjectEntityId ){
|
|
1496
|
+
const prior = this._facetSubjects.get( payload.facetId )
|
|
1396
1497
|
this._facetSubjects.set( payload.facetId, {
|
|
1397
1498
|
entityId: payload.subjectEntityId,
|
|
1398
1499
|
...( payload.subjectName ? { name: payload.subjectName } : {} ),
|
|
1399
1500
|
tick: payload.tick ?? this._lastExecutiveTick ?? 0,
|
|
1501
|
+
...( payload.reasoning ? { concluded: payload.reasoning } : {} ),
|
|
1502
|
+
// Promises ride along rather than being re-derived: a commitment the facet
|
|
1503
|
+
// DECLARED is a fact, while the same thing read back out of prose is a
|
|
1504
|
+
// guess. Preserved across syncs until the master has read them.
|
|
1505
|
+
...( prior?.promised?.length ? { promised: prior.promised } : {} ),
|
|
1400
1506
|
})
|
|
1507
|
+
}
|
|
1401
1508
|
|
|
1402
1509
|
// Unconditional (not gated on WORKSPACE_THRESHOLD like ordinary traffic):
|
|
1403
1510
|
// a facet reporting back is the master's own attention returning, and the
|
|
@@ -1461,31 +1568,81 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
|
|
|
1461
1568
|
// unfalsifiable: it could be marked kept by something that happened first.
|
|
1462
1569
|
const tick = payload.tick ?? this._currentTick
|
|
1463
1570
|
|
|
1571
|
+
// AN UNDERTAKING RIDES THE TRACT, NOT A HARNESS OF ITS OWN.
|
|
1572
|
+
//
|
|
1573
|
+
// It used to become a percept plus a standing ideomotor intent plus a bespoke
|
|
1574
|
+
// discharge rule — the engine deciding how hard a promise pulls and when it
|
|
1575
|
+
// counts as kept. That is judgement, and judgement is the mind's. What is not
|
|
1576
|
+
// the mind's to supply is the FACT crossing from where its attention was to
|
|
1577
|
+
// where its singular seat is; that crossing is anatomy.
|
|
1578
|
+
//
|
|
1579
|
+
// So it is filed with what the facet concluded and rendered to the master at
|
|
1580
|
+
// its next cycle. Reading that it promised someone something and has not done
|
|
1581
|
+
// it, the master may form a goal — which it can already do, and which the goal
|
|
1582
|
+
// machinery then lifts, plans and completes. Or it may not, and that is a
|
|
1583
|
+
// decision rather than a mechanism failing.
|
|
1584
|
+
//
|
|
1585
|
+
// The old route could not have worked anyway: a percept is swept at +2 ticks
|
|
1586
|
+
// and decays out of working memory at about +9, while the master's interval
|
|
1587
|
+
// is 15. The notice was usually gone before the seat it was addressed to ran.
|
|
1588
|
+
// Spike the salience buffer FIRST, for both kinds. The master must NOT be
|
|
1589
|
+
// handed the inbound as a message to answer — that would make it produce a
|
|
1590
|
+
// [REPLY], duplicating the facet's and breaking the facet/master boundary.
|
|
1591
|
+
// (The queue that once carried inbound this way was removed in #114: nothing
|
|
1592
|
+
// ever filled it.) Done before the undertaking branch returns, so a promise
|
|
1593
|
+
// still pulls the master's next cycle forward rather than waiting out the
|
|
1594
|
+
// interval — which matters more now that the promise is what it will read.
|
|
1595
|
+
this._gatingState.salienceBuffer.push({ event, tick })
|
|
1596
|
+
|
|
1597
|
+
const from = payload.subjectName ?? payload.subjectEntityId ?? payload.facetId ?? 'a focus'
|
|
1598
|
+
|
|
1599
|
+
if( payload.body.kind === 'undertaking'){
|
|
1600
|
+
const body = payload.body
|
|
1601
|
+
// Keyed by facet because that is what the master's view of its open threads
|
|
1602
|
+
// is keyed by. A handoff with no facet id has nowhere to file — say so
|
|
1603
|
+
// rather than dropping it quietly, which is how a promise disappears with
|
|
1604
|
+
// nobody able to tell whether it was ever made.
|
|
1605
|
+
if( !payload.facetId ){
|
|
1606
|
+
logger.warn(`[executive] commitment "${ body.what }" arrived with no facetId — nowhere to file it`)
|
|
1607
|
+
return
|
|
1608
|
+
}
|
|
1609
|
+
// Created if absent: a facet can hand something up before its first sync,
|
|
1610
|
+
// and requiring the entry to exist first made that promise vanish.
|
|
1611
|
+
const at = this._facetSubjects.get( payload.facetId ) ?? {
|
|
1612
|
+
entityId: payload.subjectEntityId ?? payload.facetId,
|
|
1613
|
+
...( payload.subjectName ? { name: payload.subjectName } : {} ),
|
|
1614
|
+
tick,
|
|
1615
|
+
}
|
|
1616
|
+
at.promised = [
|
|
1617
|
+
// Deduped on WHAT was promised, falling back to the target when two
|
|
1618
|
+
// wordings mean the same contact: restating a promise is the same
|
|
1619
|
+
// promise, and the master does not need telling twice.
|
|
1620
|
+
...( at.promised ?? [] ).filter( p =>
|
|
1621
|
+
p.what !== body.what && !( body.target !== undefined && p.target === body.target ) ),
|
|
1622
|
+
{ what: body.what,
|
|
1623
|
+
...( body.target ? { target: body.target } : {} ),
|
|
1624
|
+
...( body.gist ? { gist: body.gist } : {} ), tick },
|
|
1625
|
+
]
|
|
1626
|
+
this._facetSubjects.set( payload.facetId, at )
|
|
1627
|
+
logger.info(
|
|
1628
|
+
`[executive] filed commitment from ${from} → "${ body.what }" ` +
|
|
1629
|
+
`(it reads this at its next cycle; what to do about it is its own call)`
|
|
1630
|
+
)
|
|
1631
|
+
return
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1464
1634
|
this._escalations.push({
|
|
1465
1635
|
tick,
|
|
1466
1636
|
...( payload.facetId ? { facetId: payload.facetId } : {} ),
|
|
1467
1637
|
...( payload.subjectEntityId ? { subjectEntityId: payload.subjectEntityId } : {} ),
|
|
1468
1638
|
...( payload.subjectName ? { subjectName: payload.subjectName } : {} ),
|
|
1469
1639
|
...( payload.threadId ? { threadId: payload.threadId } : {} ),
|
|
1470
|
-
body: payload.body.
|
|
1471
|
-
? { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) }
|
|
1472
|
-
: { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) },
|
|
1640
|
+
body: { ...payload.body, reasoning: ( payload.body.reasoning ?? '').slice( 0, 400 ) },
|
|
1473
1641
|
})
|
|
1474
1642
|
|
|
1475
|
-
// Spike the salience buffer so the master fires soon rather than waiting for
|
|
1476
|
-
// the next scheduled interval. The master must NOT be handed the inbound as a
|
|
1477
|
-
// message to answer — that would make it produce a [REPLY], duplicating the
|
|
1478
|
-
// facet's and breaking the facet/master boundary. (The queue that once carried
|
|
1479
|
-
// inbound this way was removed in #114: nothing ever filled it.)
|
|
1480
|
-
this._gatingState.salienceBuffer.push({ event, tick })
|
|
1481
|
-
|
|
1482
|
-
const from = payload.subjectName ?? payload.subjectEntityId ?? payload.facetId ?? 'a focus'
|
|
1483
1643
|
logger.info(
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
`→ reach ${payload.body.target} (confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1487
|
-
: `[executive] master queued escalation percept from ${from} ` +
|
|
1488
|
-
`(confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1644
|
+
`[executive] master queued escalation percept from ${from} ` +
|
|
1645
|
+
`(confidence=${payload.confidence?.toFixed( 2 )})`
|
|
1489
1646
|
)
|
|
1490
1647
|
}
|
|
1491
1648
|
|