@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
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ExecutiveFacet — one facet per entityId, kept alive for the session duration.
|
|
12
12
|
*
|
|
13
13
|
* Architecture:
|
|
14
|
-
* - External call: WillManager.
|
|
14
|
+
* - External call: WillManager.senseText() → audition.sense(TextMessage)
|
|
15
15
|
* - Percept published on bus: senses.audition.percept
|
|
16
16
|
* - Facet handoffs to the seat: executive.facet.handoff (escalation | undertaking)
|
|
17
17
|
* - GoalManager integration: automatic via executive.facet.progress (bus)
|
|
@@ -73,10 +73,13 @@ import type {
|
|
|
73
73
|
SensoryInput,
|
|
74
74
|
LanguagePercept,
|
|
75
75
|
TextMessage,
|
|
76
|
+
Transduced,
|
|
76
77
|
VoiceChunk
|
|
77
78
|
} from '#senses/index'
|
|
79
|
+
import { PERCEPT_SUMMARY_CAP } from '#cognition/percept.entity'
|
|
78
80
|
import { validateFacetHandoff, type HandoffBody } from '#faculties/executive.engine/escalation.buffer'
|
|
79
81
|
import { fnv1a } from '#agency/consequence'
|
|
82
|
+
import type { OutreachResult } from '#agency/engines/motor.schema.executor'
|
|
80
83
|
|
|
81
84
|
// ── Internal types ─────────────────────────────────────────────
|
|
82
85
|
|
|
@@ -85,6 +88,15 @@ interface ConversationDecision {
|
|
|
85
88
|
reply: string
|
|
86
89
|
/** Individual reply bubbles for display (separate SSE chunks). */
|
|
87
90
|
replyBubbles: string[]
|
|
91
|
+
/**
|
|
92
|
+
* The mind DECLARED silence — it considered speaking and chose not to.
|
|
93
|
+
*
|
|
94
|
+
* Distinct from empty bubbles, which also happens when authoring timed out, the
|
|
95
|
+
* facet budget was full, or a second pass deferred to one already in flight.
|
|
96
|
+
* Only this one is an answer; the others are the absence of one, and the
|
|
97
|
+
* executor must keep holding those.
|
|
98
|
+
*/
|
|
99
|
+
withheld?: boolean
|
|
88
100
|
targetEntityId: string
|
|
89
101
|
/**
|
|
90
102
|
* Actions this facet aimed at someone OTHER than the person it is talking to.
|
|
@@ -178,7 +190,7 @@ export function partitionOutwardIntents(
|
|
|
178
190
|
// [REPLY_TEXT] is plain prose — no JSON wrapper, no targetEntityId needed.
|
|
179
191
|
// The facet is entity-scoped so the recipient is always the speakerEntityId.
|
|
180
192
|
|
|
181
|
-
const CONVERSATION_OUTPUT_FORMAT = `\
|
|
193
|
+
export const CONVERSATION_OUTPUT_FORMAT = `\
|
|
182
194
|
## Response Format (REQUIRED)
|
|
183
195
|
|
|
184
196
|
Step 1 — JSON object (my private reasoning, optionally in a \`\`\`json code block):
|
|
@@ -217,6 +229,24 @@ That is recorded and NEVER sent. Anything between the [REPLY_TEXT] markers IS SE
|
|
|
217
229
|
like "[no message this cycle — waiting for their reply]" does not describe my silence to
|
|
218
230
|
myself, it delivers that sentence to them. If I write both blocks, the silence wins.
|
|
219
231
|
|
|
232
|
+
## Reaching someone who is not in this conversation
|
|
233
|
+
[REPLY_TEXT] is delivered TO THE PERSON I AM TALKING TO, and to nobody else. It has an
|
|
234
|
+
audience, not just a reader. So words meant for a third party do not become a message to
|
|
235
|
+
that third party by being about them — they are handed to the speaker, who reads something
|
|
236
|
+
addressed to someone else, while the person it was actually for never hears it.
|
|
237
|
+
|
|
238
|
+
To reach someone else I name them in an action:
|
|
239
|
+
|
|
240
|
+
{"type": "reach-out", "target": "<their name or id as it appears under '## People I Know'>",
|
|
241
|
+
"args": {"content": "what I want to say to them"}}
|
|
242
|
+
|
|
243
|
+
I am one conversation of a mind that is having several. Opening a channel is not mine to
|
|
244
|
+
do — that action hands the intention to the part of me that owns whom I contact. It is a
|
|
245
|
+
decision, not a delivery: at the moment I write it nothing has reached anyone, and it may
|
|
246
|
+
yet come to nothing. So I never tell the person in front of me that I have contacted
|
|
247
|
+
someone, or that a message is on its way. The most I can truthfully say is that I mean to.
|
|
248
|
+
I keep [REPLY_TEXT] for the person in front of me.
|
|
249
|
+
|
|
220
250
|
## When to use GOALS_NEW (almost always)
|
|
221
251
|
If the speaker requests, mentions, or implies something I should follow through on — embed [GOALS_NEW] in my reasoning.
|
|
222
252
|
This tracks intent across future cycles on its own.
|
|
@@ -347,6 +377,27 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
347
377
|
* off-tick engine has. Stamped from `FacetDecision.tick`, and used to key the
|
|
348
378
|
* conversation records it writes into state.
|
|
349
379
|
*/
|
|
380
|
+
/**
|
|
381
|
+
* Audition does NOT lay down a percept trace (SIGNAL_BOUNDARY P0, step 3).
|
|
382
|
+
*
|
|
383
|
+
* Every other sense does, and a host implementing a new one gets it by
|
|
384
|
+
* default. This is the grandfathered exception, and the reason is measurement
|
|
385
|
+
* rather than principle: audition is the only live sense, and switching it on
|
|
386
|
+
* routes every inbound message to five consumers it has never reached —
|
|
387
|
+
* `action.selector`'s rupture gate (where a fresh high-salience exafferent
|
|
388
|
+
* percept can preempt an awaiting intent), working memory, the executive
|
|
389
|
+
* prompt, novelty, and reafference credit. On a deployed Will that is not a
|
|
390
|
+
* tweak; it is a different mind, and it deserves a measured rollout rather
|
|
391
|
+
* than a line in a refactor.
|
|
392
|
+
*
|
|
393
|
+
* Audition is not trace-less meanwhile: it writes `conversation.received`
|
|
394
|
+
* through its own sink, which is what SocialPerception reads. That record is
|
|
395
|
+
* social — sourceKeid, directedAtSelf, action:'communication' — and is NOT
|
|
396
|
+
* the generic percept the other senses now write. Two different traces for
|
|
397
|
+
* two different readers; this flag turns off only the second.
|
|
398
|
+
*/
|
|
399
|
+
protected readonly tracesPercepts = false
|
|
400
|
+
|
|
350
401
|
private _lastDecisionTick = 0
|
|
351
402
|
/** Speaker attachment strength accessor (0–1) — weights salience by relationship. */
|
|
352
403
|
private _getAttachmentScore: (( entityId: string ) => number) | null = null
|
|
@@ -557,7 +608,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
557
608
|
const salience = this._model.observe(`audition.${entityId}`, langEnergy ).salience
|
|
558
609
|
|
|
559
610
|
// ── Percept ────────────────────────────────────────────────
|
|
560
|
-
const
|
|
611
|
+
const transduced: Transduced<LanguagePercept> = {
|
|
561
612
|
domain: 'audition',
|
|
562
613
|
channel: msg.kind,
|
|
563
614
|
content,
|
|
@@ -565,6 +616,10 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
565
616
|
speakerEntityId: entityId,
|
|
566
617
|
threadId,
|
|
567
618
|
digest: this._digests.getDigest( threadId ),
|
|
619
|
+
// What a heard turn amounts to, for readers that do not know this is
|
|
620
|
+
// audition. Bounded, because `summary` renders into the executive prompt
|
|
621
|
+
// and a pasted essay would take the whole percept budget.
|
|
622
|
+
summary: `${ speakerName } said: ${ content }`.slice( 0, PERCEPT_SUMMARY_CAP ),
|
|
568
623
|
salience,
|
|
569
624
|
// Arrival metadata for an EXTERNAL inbound message (network/RPC boundary):
|
|
570
625
|
// no sim clock in scope here and the value is not replayed — wallClock() is
|
|
@@ -575,7 +630,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
575
630
|
|
|
576
631
|
// Publish to CognitiveBus — AttentionAllocator et al. can react.
|
|
577
632
|
// publishPercept() (base) is the single emit chokepoint on senses.<domain>.percept.
|
|
578
|
-
this.publishPercept(
|
|
633
|
+
const percept = this.publishPercept<LanguagePercept>( transduced, msg )
|
|
579
634
|
|
|
580
635
|
// ── Update digest with inbound turn ───────────────────────
|
|
581
636
|
this._digests.append( threadId, 'user', content )
|
|
@@ -640,6 +695,16 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
640
695
|
this._turnDone.get( entityId )?.()
|
|
641
696
|
}
|
|
642
697
|
|
|
698
|
+
/**
|
|
699
|
+
* True while a turn with this person is still resolving — they spoke and the
|
|
700
|
+
* reply has not landed yet. The agency asks before delivering a self-initiated
|
|
701
|
+
* message (see OutreachAuthor.isSpeakingTo), so one mind does not reach one
|
|
702
|
+
* person down two paths in the same tick.
|
|
703
|
+
*/
|
|
704
|
+
isSpeakingTo( entityId: string ): boolean {
|
|
705
|
+
return this._turnDone.has( entityId )
|
|
706
|
+
}
|
|
707
|
+
|
|
643
708
|
// ── Facet lifecycle ─────────────────────────────────────────
|
|
644
709
|
|
|
645
710
|
private async _routeToFacet( percept: LanguagePercept, speakerName: string ): Promise<boolean> {
|
|
@@ -845,8 +910,8 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
845
910
|
* (MotorSchemaExecutor) to deliver through the proactive communicate path; empty
|
|
846
911
|
* when no executive is attached or the facet budget is full (caller then awaits).
|
|
847
912
|
*/
|
|
848
|
-
async authorOutreach( entityId: string, entityName: string, gist?: string ): Promise<
|
|
849
|
-
if( !this._executiveEngine ) return []
|
|
913
|
+
async authorOutreach( entityId: string, entityName: string, gist?: string ): Promise<OutreachResult> {
|
|
914
|
+
if( !this._executiveEngine ) return { bubbles: [] }
|
|
850
915
|
|
|
851
916
|
// One authoring pass per person at a time.
|
|
852
917
|
//
|
|
@@ -862,7 +927,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
862
927
|
// had a chance to read it, which is the outcome we want anyway.
|
|
863
928
|
if( this._outreachInFlight.has( entityId ) ){
|
|
864
929
|
logger.info(`[audition-engine] already composing an outreach to ${ entityId } — not opening a second`)
|
|
865
|
-
return []
|
|
930
|
+
return { bubbles: [] }
|
|
866
931
|
}
|
|
867
932
|
|
|
868
933
|
// Already talking to them? Then this is not a second thread — it is a thing to
|
|
@@ -889,7 +954,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
889
954
|
return spawned.handle
|
|
890
955
|
} )()
|
|
891
956
|
|
|
892
|
-
if( !handle ) return []
|
|
957
|
+
if( !handle ) return { bubbles: [] }
|
|
893
958
|
|
|
894
959
|
if( openThread )
|
|
895
960
|
logger.info(`[audition-engine] composing outreach to ${ entityId } inside the open conversation (${ openThread.facetId })`)
|
|
@@ -934,7 +999,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
934
999
|
// saying something — nobody is waiting on this.
|
|
935
1000
|
if( output.noMessage !== undefined ){
|
|
936
1001
|
logger.info(`[audition-engine] chose not to reach out to ${ entityName }: ${ output.noMessage.slice( 0, 120 ) }`)
|
|
937
|
-
return { reply: '', replyBubbles: [], targetEntityId: entityId, requiresMasterAttention: false }
|
|
1002
|
+
return { reply: '', replyBubbles: [], withheld: true, targetEntityId: entityId, requiresMasterAttention: false }
|
|
938
1003
|
}
|
|
939
1004
|
const rawReply = output.replyText?.trim() ?? ''
|
|
940
1005
|
const bubbles = rawReply.split( /\n{2,}/ ).map( b => b.trim() ).filter( Boolean )
|
|
@@ -947,13 +1012,13 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
947
1012
|
// a safety timeout, then tear the transient facet down.
|
|
948
1013
|
this._outreachInFlight.add( entityId )
|
|
949
1014
|
try {
|
|
950
|
-
const
|
|
1015
|
+
const authored = await new Promise<OutreachResult>( resolve => {
|
|
951
1016
|
let settled = false
|
|
952
1017
|
let unsub: () => void = () => {}
|
|
953
1018
|
let timer: ReturnType<typeof setTimeout>
|
|
954
|
-
const done = (
|
|
1019
|
+
const done = ( r: OutreachResult ): void => { if( settled ) return; settled = true; clearTimeout( timer ); unsub(); resolve( r ) }
|
|
955
1020
|
timer = setTimeout(
|
|
956
|
-
() => { logger.warn(`[audition-engine] outreach authoring timed out for ${ entityId }`); done( [] ) },
|
|
1021
|
+
() => { logger.warn(`[audition-engine] outreach authoring timed out for ${ entityId }`); done( { bubbles: [] } ) },
|
|
957
1022
|
60_000, // generous: the facet LLM authors in ~8–18s
|
|
958
1023
|
)
|
|
959
1024
|
// ONLY this report's decision. Sharing a live conversation facet means its
|
|
@@ -962,20 +1027,29 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
962
1027
|
// composed it unprompted — and deliver it twice.
|
|
963
1028
|
unsub = handle.subscribe( d => {
|
|
964
1029
|
if( d.respondingToType !== 'outreach') return
|
|
965
|
-
|
|
1030
|
+
const decision = d.decision as ConversationDecision
|
|
1031
|
+
// A facet reasoned and its decision arrived — `answered`, whatever it
|
|
1032
|
+
// holds. That is what separates an empty ANSWER from the empty every
|
|
1033
|
+
// other exit on this method returns, and it is the only place on the
|
|
1034
|
+
// method that can honestly say so.
|
|
1035
|
+
done( {
|
|
1036
|
+
bubbles: decision.replyBubbles ?? [],
|
|
1037
|
+
withheld: decision.withheld === true,
|
|
1038
|
+
answered: true,
|
|
1039
|
+
} )
|
|
966
1040
|
} )
|
|
967
1041
|
// The focus rides the REPORT, so a shared conversation facet keeps its own
|
|
968
1042
|
// standing focus and its next inbound turn resumes untouched.
|
|
969
1043
|
Promise.resolve( handle.report({ type: 'outreach', payload: { entityId, gist }, focus: outreachFocus }) ).catch( err => {
|
|
970
1044
|
logger.warn(`[audition-engine] outreach report failed for ${ entityId }: ${ ( err as Error ).message }`)
|
|
971
|
-
done( [] )
|
|
1045
|
+
done( { bubbles: [] } )
|
|
972
1046
|
} )
|
|
973
1047
|
} )
|
|
974
1048
|
|
|
975
1049
|
// Only tear down what we opened. Destroying a borrowed conversation facet
|
|
976
1050
|
// would end the conversation as a side effect of speaking in it.
|
|
977
1051
|
if( !openThread ) handle.destroy()
|
|
978
|
-
return
|
|
1052
|
+
return authored
|
|
979
1053
|
}
|
|
980
1054
|
catch( err ){
|
|
981
1055
|
// A facet that throws is a pass that produced no words — the same outcome as
|
|
@@ -983,7 +1057,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
983
1057
|
// author". Letting it escape would reject inside MotorSchemaExecutor's
|
|
984
1058
|
// fire-and-forget authoring chain instead.
|
|
985
1059
|
logger.warn(`[audition-engine] outreach authoring failed for ${ entityId }: ${ ( err as Error ).message }`)
|
|
986
|
-
return []
|
|
1060
|
+
return { bubbles: [] }
|
|
987
1061
|
}
|
|
988
1062
|
// finally{} on every path — the timeout resolves empty rather than throwing, but
|
|
989
1063
|
// a destroy() or report() that throws must not leave this person permanently
|
|
@@ -1256,6 +1330,13 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
1256
1330
|
)
|
|
1257
1331
|
handoff({
|
|
1258
1332
|
kind: 'undertaking',
|
|
1333
|
+
// The commitment in the mind's own words. A contact is one KIND of
|
|
1334
|
+
// promise, not the only kind the tract can carry — see
|
|
1335
|
+
// UndertakingHandoff. This producer only makes contact-shaped ones,
|
|
1336
|
+
// because a facet declares an outward intent by naming a
|
|
1337
|
+
// communicate action; anything else it means to follow through on
|
|
1338
|
+
// goes through [GOALS_NEW], which the format already asks for.
|
|
1339
|
+
what: `reach ${ intent.target }`,
|
|
1259
1340
|
target: intent.target,
|
|
1260
1341
|
reasoning: intent.reasoning ?? '',
|
|
1261
1342
|
...( intent.gist ? { gist: intent.gist } : {} ),
|
|
@@ -29,7 +29,10 @@ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
|
29
29
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
30
30
|
import type { StateCommands } from '#core/types'
|
|
31
31
|
import type { AccessGrants } from '#agency/access.grants'
|
|
32
|
-
import type {
|
|
32
|
+
import type { SensorySignal } from '#senses/provenance'
|
|
33
|
+
import { perceptEntity, type PerceptEntity } from '#cognition/percept.entity'
|
|
34
|
+
import { fnv1a } from '#agency/consequence'
|
|
35
|
+
import type { SenseEngine, SensoryInput, SenseDomain, Percept, Transduced } from '#senses/index'
|
|
33
36
|
|
|
34
37
|
export abstract class BaseSenseEngine implements SenseEngine {
|
|
35
38
|
abstract readonly name: string
|
|
@@ -38,7 +41,7 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
38
41
|
/**
|
|
39
42
|
* The `SensoryInput.kind` values this engine consumes. Inputs of any other
|
|
40
43
|
* kind are ignored silently by `ingest()` (no warning, no work) — this is how
|
|
41
|
-
* a single `
|
|
44
|
+
* a single `senseSignal(domain, input)` call can be routed leniently.
|
|
42
45
|
*/
|
|
43
46
|
protected abstract readonly acceptedKinds: ReadonlySet<SensoryInput['kind']>
|
|
44
47
|
|
|
@@ -51,12 +54,45 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
51
54
|
|
|
52
55
|
protected _bus: CognitiveBus | null = null
|
|
53
56
|
protected _grants: AccessGrants | null = null
|
|
57
|
+
protected _trace: (( e: PerceptEntity ) => void) | null = null
|
|
58
|
+
protected _now: (() => number) | null = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether this sense lays down a `percept` trace in state (SIGNAL_BOUNDARY P0).
|
|
62
|
+
*
|
|
63
|
+
* ON by default, because that is the contract a host is owed: implement a
|
|
64
|
+
* sense, and what it senses reaches the five things that read percepts — the
|
|
65
|
+
* rupture gate, reafference credit, working memory, the executive prompt, and
|
|
66
|
+
* novelty. Before this, `publishPercept()` emitted a bus event three
|
|
67
|
+
* subscribers glanced at for one tick and nothing else, so a robot host
|
|
68
|
+
* ingesting frames could never remember having SEEN anything.
|
|
69
|
+
*
|
|
70
|
+
* `AuditionEngine` overrides it to `false` — see the comment there. It is the
|
|
71
|
+
* documented exception, not the template.
|
|
72
|
+
*/
|
|
73
|
+
protected readonly tracesPercepts: boolean = true
|
|
54
74
|
|
|
55
75
|
// ── Wiring ───────────────────────────────────────────────
|
|
56
76
|
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
57
77
|
/** Inject the AccessGrants so `ingest()` honours `gateEffector` (the permission gate). */
|
|
58
78
|
attachGrants( g: AccessGrants ): void { this._grants = g }
|
|
59
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Wire the sense to state: where a percept goes, and what tick it is now.
|
|
82
|
+
*
|
|
83
|
+
* Both together, never one: a percept without a tick is uncollectable — the
|
|
84
|
+
* sweeper reads `metadata.tick` and nothing else — which is the leak P0 step 2
|
|
85
|
+
* closed in two other writers. And the tick has to be injected rather than
|
|
86
|
+
* remembered, because a sense is INGEST-DRIVEN and off-tick: it has no `react()`
|
|
87
|
+
* to be handed one in. Audition's own `_lastDecisionTick` is the cautionary
|
|
88
|
+
* case — it lags to whenever the executive last decided, so a message arriving
|
|
89
|
+
* forty ticks later would be stamped forty ticks stale and swept on arrival.
|
|
90
|
+
*/
|
|
91
|
+
attachPerceptTrace( write: ( e: PerceptEntity ) => void, currentTick: () => number ): void {
|
|
92
|
+
this._trace = write
|
|
93
|
+
this._now = currentTick
|
|
94
|
+
}
|
|
95
|
+
|
|
60
96
|
// ── CognitiveEngine defaults ─────────────────────────────
|
|
61
97
|
publishes(): CognitiveEventSchema[] {
|
|
62
98
|
return [ { type: `senses.${this.domain}.percept`, version: 1, validate: () => null } ]
|
|
@@ -71,7 +107,7 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
71
107
|
* the domain-specific work to `_perceive()`. Subclasses never re-handle gating
|
|
72
108
|
* or filtering — they only implement `_perceive()`.
|
|
73
109
|
*/
|
|
74
|
-
async
|
|
110
|
+
async sense( input: SensoryInput ): Promise<void> {
|
|
75
111
|
// Permission gate — AccessGrants decides whether this sense may ingest.
|
|
76
112
|
if( this.gateEffector && this._grants && !this._grants.isAllowed( this.gateEffector ) ) return
|
|
77
113
|
if( !this.acceptedKinds.has( input.kind ) ) return
|
|
@@ -86,15 +122,77 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
86
122
|
* Publish a percept on this engine's `senses.<domain>.percept` topic. The
|
|
87
123
|
* single emit chokepoint — the AttentionAllocator (and, in future, a
|
|
88
124
|
* cross-modal binder) observes percepts here.
|
|
125
|
+
*
|
|
126
|
+
* `from` is the signal this percept was transduced FROM, and it is required
|
|
127
|
+
* rather than optional on purpose: every percept has a cause, and the one
|
|
128
|
+
* fact transduction must not lose is whose doing that cause was. Taking it
|
|
129
|
+
* here — instead of stashing the in-flight input on a field — is what keeps
|
|
130
|
+
* the stamp correct while `_perceive()` is async and two ingests overlap.
|
|
131
|
+
*
|
|
132
|
+
* The percept arrives as `Transduced`, i.e. WITHOUT the two fields, so a
|
|
133
|
+
* sense engine cannot supply them at all. An earlier cut let one through: the
|
|
134
|
+
* stamp overwrote `provenance` unconditionally but `sourceIntentId` only when
|
|
135
|
+
* the host supplied one, so an engine could fabricate an intent id and it
|
|
136
|
+
* survived — provenance the mind would later trust, laundered by the very
|
|
137
|
+
* step that exists to establish it. The type now refuses it outright; the
|
|
138
|
+
* host's assertion is the only authority.
|
|
139
|
+
*
|
|
140
|
+
* Returns the stamped percept so a sense engine that needs it downstream (as
|
|
141
|
+
* audition does, to route the turn) uses the SAME object the bus saw, rather
|
|
142
|
+
* than a second one that could drift from it.
|
|
89
143
|
*/
|
|
90
|
-
protected publishPercept( percept:
|
|
144
|
+
protected publishPercept<P extends Percept>( percept: Transduced<P>, from: SensorySignal ): P {
|
|
145
|
+
// Stripped, then re-applied — belt AND braces. `Transduced` makes forging a
|
|
146
|
+
// compile error, but a type is only as strong as the compiler that saw it:
|
|
147
|
+
// a JS host, or a consumer built against an older .d.ts, hands over whatever
|
|
148
|
+
// it likes. The strip is what holds at runtime.
|
|
149
|
+
const { provenance: _stale, sourceIntentId: _staleIntent, ...rest } =
|
|
150
|
+
percept as Transduced<P> & Partial<SensorySignal>
|
|
151
|
+
|
|
152
|
+
const stamped = {
|
|
153
|
+
...rest,
|
|
154
|
+
provenance: from.provenance,
|
|
155
|
+
...( from.sourceIntentId ? { sourceIntentId: from.sourceIntentId } : {} ),
|
|
156
|
+
} as P
|
|
157
|
+
|
|
91
158
|
this._bus?.publish({
|
|
92
159
|
type: `senses.${this.domain}.percept`,
|
|
93
160
|
version: 1,
|
|
94
161
|
sourceEngine: this.name,
|
|
95
|
-
salience:
|
|
96
|
-
payload:
|
|
162
|
+
salience: stamped.salience,
|
|
163
|
+
payload: stamped,
|
|
97
164
|
})
|
|
165
|
+
|
|
166
|
+
this._writeTrace( stamped )
|
|
167
|
+
return stamped
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Lay the percept down in state, so it reaches the faculties that read
|
|
172
|
+
* percepts rather than only the three that were listening on the bus this
|
|
173
|
+
* tick. Silent when the sense opts out or the host wired no sink.
|
|
174
|
+
*
|
|
175
|
+
* The id is content-derived and tick-stamped — never `wallClock()` — because
|
|
176
|
+
* this entity lives in state, and a wall-clock id makes a recorded and a
|
|
177
|
+
* replayed run diverge (R2). Two identical signals from one entity on one
|
|
178
|
+
* tick collapse to one percept, which is the same coalescing audition already
|
|
179
|
+
* applies to a burst of identical messages.
|
|
180
|
+
*/
|
|
181
|
+
private _writeTrace( p: Percept ): void {
|
|
182
|
+
if( !this.tracesPercepts || !this._trace || !this._now ) return
|
|
183
|
+
|
|
184
|
+
const tick = this._now()
|
|
185
|
+
this._trace( perceptEntity( {
|
|
186
|
+
id: `sense-${ this.domain }-${ tick }-${ fnv1a( `${ p.sourceEntityId }\u0000${ p.summary }` ) }`,
|
|
187
|
+
tick,
|
|
188
|
+
salience: p.salience,
|
|
189
|
+
category: this.domain,
|
|
190
|
+
summary: p.summary,
|
|
191
|
+
provenance: p.provenance,
|
|
192
|
+
entityId: p.sourceEntityId,
|
|
193
|
+
...( p.sourceIntentId !== undefined ? { sourceIntentId: p.sourceIntentId } : {} ),
|
|
194
|
+
...( p.data !== undefined ? { data: p.data } : {} ),
|
|
195
|
+
} ) )
|
|
98
196
|
}
|
|
99
197
|
}
|
|
100
198
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import type { CognitiveEngine } from '#cognition/types'
|
|
23
23
|
import type { CognitiveBus } from '#cognition/bus'
|
|
24
|
+
import type { SensorySignal } from './provenance'
|
|
24
25
|
|
|
25
26
|
// ── Domain ────────────────────────────────────────────────────
|
|
26
27
|
|
|
@@ -31,15 +32,57 @@ export type SenseDomain =
|
|
|
31
32
|
| 'olfaction' // ambient signals — OlfactionEngine (shell)
|
|
32
33
|
| 'gustation' // self-evaluation — GustationEngine (shell)
|
|
33
34
|
|
|
35
|
+
// ── Provenance (re-exported from ./provenance) ────────────────
|
|
36
|
+
|
|
37
|
+
export type { SignalProvenance, SensorySignal, Transduced } from './provenance'
|
|
38
|
+
export { asProvenance } from './provenance'
|
|
39
|
+
|
|
34
40
|
// ── Percept types ─────────────────────────────────────────────
|
|
35
41
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Base percept published on the CognitiveBus.
|
|
44
|
+
*
|
|
45
|
+
* It extends `SensorySignal` because transduction does not change whose doing a
|
|
46
|
+
* signal was: what the host stamped on the input is what the percept carries.
|
|
47
|
+
* `BaseSenseEngine.publishPercept()` applies both from the input, so a percept
|
|
48
|
+
* ALWAYS carries provenance — no consumer downstream ever writes a fallback.
|
|
49
|
+
*/
|
|
50
|
+
export interface Percept extends SensorySignal {
|
|
38
51
|
domain: SenseDomain
|
|
39
52
|
sourceEntityId: string
|
|
40
53
|
timestamp: number
|
|
41
54
|
salience: number // 0–1, computed by the sense engine
|
|
42
55
|
raw: unknown // original input object
|
|
56
|
+
/**
|
|
57
|
+
* A LABEL for what was sensed. Required, so a percept is never invisible —
|
|
58
|
+
* `extractPercepts` skips one without it.
|
|
59
|
+
*
|
|
60
|
+
* A label, not the payload, and not a meaning. The engine composes it from
|
|
61
|
+
* what it has (the signal's name, a compact rendering) — A HOST IS NEVER
|
|
62
|
+
* ASKED TO WRITE ONE. Demanding prose from an integration puts the mind's own
|
|
63
|
+
* work on the wrong side of the boundary: a robot's vision system reports
|
|
64
|
+
* `object_detected, confidence 0.9, bbox […]`, and making it also write
|
|
65
|
+
* "I see a red ball on the table" is asking the arm to do the thinking.
|
|
66
|
+
*
|
|
67
|
+
* Because the ENGINE composes this, `PERCEPT_SUMMARY_CAP` may bound it —
|
|
68
|
+
* bounding its own words destroys nobody's only copy. `data` beside it is the
|
|
69
|
+
* host's, and is never bounded.
|
|
70
|
+
*/
|
|
71
|
+
summary: string
|
|
72
|
+
/**
|
|
73
|
+
* What the host actually sent, in the shape it had it — whole, unreshaped,
|
|
74
|
+
* uncapped.
|
|
75
|
+
*
|
|
76
|
+
* The mind's job is to make meaning by connecting pieces of information, and
|
|
77
|
+
* it cannot do that from a sentence somebody else wrote about the pieces. A
|
|
78
|
+
* host reporting `memberCount: 47` under a summary saying "a few people"
|
|
79
|
+
* leaves a mind that can never recover 47.
|
|
80
|
+
*
|
|
81
|
+
* Distinct from `raw`, which is the whole `SensoryInput` envelope and is
|
|
82
|
+
* engine-internal — never persisted. This is, and it reaches state, working
|
|
83
|
+
* memory and the prompt.
|
|
84
|
+
*/
|
|
85
|
+
data?: unknown
|
|
43
86
|
}
|
|
44
87
|
|
|
45
88
|
/** Audition-specific percept — carries language content and thread context. */
|
|
@@ -56,7 +99,7 @@ export interface LanguagePercept extends Percept {
|
|
|
56
99
|
// ── Sensory input types (discriminated union) ─────────────────
|
|
57
100
|
|
|
58
101
|
// Audition
|
|
59
|
-
export interface TextMessage {
|
|
102
|
+
export interface TextMessage extends SensorySignal {
|
|
60
103
|
kind: 'text'
|
|
61
104
|
entityId: string
|
|
62
105
|
threadId: string
|
|
@@ -75,9 +118,24 @@ export interface TextMessage {
|
|
|
75
118
|
* false — an unknown room is not known to be public.
|
|
76
119
|
*/
|
|
77
120
|
direct?: boolean
|
|
121
|
+
/**
|
|
122
|
+
* What this room is CALLED, where the channel knows.
|
|
123
|
+
*
|
|
124
|
+
* A room has had a dossier of its own since 0.9.0 and no way to be named, so
|
|
125
|
+
* every place the mind knew was the id it was reached at —
|
|
126
|
+
* `discord:1531261362838441996`. It rendered to the mind as "something", which
|
|
127
|
+
* is what the prompt says for a `thing` with no name, and left a mind deciding
|
|
128
|
+
* *where* to say something choosing between two opaque numbers.
|
|
129
|
+
*
|
|
130
|
+
* A display label, not an address: `#general` is what a person calls the room,
|
|
131
|
+
* `discord:1531…` is how a message gets there, and 0.9.0 established that those
|
|
132
|
+
* are different facts. The bridge composes it, because what a room is called is
|
|
133
|
+
* a platform's business and the mind should not learn Discord's spelling.
|
|
134
|
+
*/
|
|
135
|
+
threadName?: string
|
|
78
136
|
}
|
|
79
137
|
|
|
80
|
-
export interface VoiceChunk {
|
|
138
|
+
export interface VoiceChunk extends SensorySignal {
|
|
81
139
|
kind: 'voice'
|
|
82
140
|
entityId: string
|
|
83
141
|
threadId: string
|
|
@@ -86,14 +144,14 @@ export interface VoiceChunk {
|
|
|
86
144
|
}
|
|
87
145
|
|
|
88
146
|
// Vision (stub)
|
|
89
|
-
export interface ImageFrame {
|
|
147
|
+
export interface ImageFrame extends SensorySignal {
|
|
90
148
|
kind: 'image'
|
|
91
149
|
entityId: string
|
|
92
150
|
data: Buffer
|
|
93
151
|
mimeType: string
|
|
94
152
|
}
|
|
95
153
|
|
|
96
|
-
export interface VideoSegment {
|
|
154
|
+
export interface VideoSegment extends SensorySignal {
|
|
97
155
|
kind: 'video'
|
|
98
156
|
entityId: string
|
|
99
157
|
frames: ImageFrame[]
|
|
@@ -101,41 +159,41 @@ export interface VideoSegment {
|
|
|
101
159
|
}
|
|
102
160
|
|
|
103
161
|
// Somatosensation (stub)
|
|
104
|
-
export interface WebhookEvent {
|
|
162
|
+
export interface WebhookEvent extends SensorySignal {
|
|
105
163
|
kind: 'webhook'
|
|
106
164
|
source: string
|
|
107
165
|
payload: unknown
|
|
108
166
|
headers: Record<string, string>
|
|
109
167
|
}
|
|
110
168
|
|
|
111
|
-
export interface SystemSignal {
|
|
169
|
+
export interface SystemSignal extends SensorySignal {
|
|
112
170
|
kind: 'system'
|
|
113
171
|
signal: string
|
|
114
172
|
data: unknown
|
|
115
173
|
}
|
|
116
174
|
|
|
117
175
|
// Olfaction (stub)
|
|
118
|
-
export interface AmbientMetric {
|
|
176
|
+
export interface AmbientMetric extends SensorySignal {
|
|
119
177
|
kind: 'ambient'
|
|
120
178
|
metricKey: string
|
|
121
179
|
value: number
|
|
122
180
|
trend: 'rising' | 'falling' | 'stable'
|
|
123
181
|
}
|
|
124
182
|
|
|
125
|
-
export interface BackgroundSignal {
|
|
183
|
+
export interface BackgroundSignal extends SensorySignal {
|
|
126
184
|
kind: 'background'
|
|
127
185
|
category: string
|
|
128
186
|
data: unknown
|
|
129
187
|
}
|
|
130
188
|
|
|
131
189
|
// Gustation (stub)
|
|
132
|
-
export interface InternalEvaluation {
|
|
190
|
+
export interface InternalEvaluation extends SensorySignal {
|
|
133
191
|
kind: 'self-eval'
|
|
134
192
|
context: string
|
|
135
193
|
trigger: string
|
|
136
194
|
}
|
|
137
195
|
|
|
138
|
-
export interface SelfAssessmentTrigger {
|
|
196
|
+
export interface SelfAssessmentTrigger extends SensorySignal {
|
|
139
197
|
kind: 'assessment'
|
|
140
198
|
goalId?: string
|
|
141
199
|
checkType: string
|
|
@@ -171,7 +229,7 @@ export type SensoryInput =
|
|
|
171
229
|
export interface SenseEngine extends CognitiveEngine {
|
|
172
230
|
readonly domain: SenseDomain
|
|
173
231
|
attachBus( bus: CognitiveBus ): void
|
|
174
|
-
|
|
232
|
+
sense( input: SensoryInput ): Promise<void>
|
|
175
233
|
}
|
|
176
234
|
|
|
177
235
|
// ── Re-exports ────────────────────────────────────────────────
|