@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
package/src/llm/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
import { type ModelRouter, isNullRouter } from '#llm/routing'
|
|
14
14
|
import { getCompletionRecorder, getCompletionSource } from '#core/completion.recorder'
|
|
15
15
|
import type { LLMCompletionRecord } from '#core/completion.recorder'
|
|
16
|
-
import { withGate } from '#llm/gate'
|
|
16
|
+
import { withGate, gateFor } from '#llm/gate'
|
|
17
17
|
import { matchConversationFocus, wrapReplyText } from '#llm/wire.contracts'
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -264,6 +264,48 @@ export interface LLMCallResult {
|
|
|
264
264
|
cacheWriteTok?: number
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The usage block on an Anthropic-wire SSE event. Every field is optional
|
|
269
|
+
* because which of them a host populates — and on which event — varies: see
|
|
270
|
+
* `takeUsage` in `_callAnthropicStream`.
|
|
271
|
+
*/
|
|
272
|
+
export interface StreamUsage {
|
|
273
|
+
input_tokens?: number
|
|
274
|
+
output_tokens?: number
|
|
275
|
+
cache_read_input_tokens?: number
|
|
276
|
+
cache_creation_input_tokens?: number
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Running token totals accumulated across a stream's usage events. */
|
|
280
|
+
export interface StreamTokens {
|
|
281
|
+
inputTok: number
|
|
282
|
+
outputTok: number
|
|
283
|
+
cacheReadTok: number
|
|
284
|
+
cacheWriteTok: number
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Fold one SSE usage block into the running totals.
|
|
289
|
+
*
|
|
290
|
+
* Which event carries the real numbers is host-specific. Real Anthropic puts
|
|
291
|
+
* the input side on `message_start` and only `output_tokens` on `message_delta`.
|
|
292
|
+
* Z.ai (glm) sends `message_start` with every field zeroed and reports input,
|
|
293
|
+
* output and cache together on the final `message_delta`. Reading input from
|
|
294
|
+
* `message_start` alone recorded 0 input and 0 cache for every GLM call ever
|
|
295
|
+
* made — 441,896 output tokens against 0 input across one 7-hour COO run.
|
|
296
|
+
*
|
|
297
|
+
* So: fold from wherever it arrives, and let a later non-zero reading win. A
|
|
298
|
+
* zero never overwrites a figure already in hand, and a real figure always
|
|
299
|
+
* replaces a placeholder — which makes the fold correct under either ordering.
|
|
300
|
+
*/
|
|
301
|
+
export function foldStreamUsage( acc: StreamTokens, u: StreamUsage ): StreamTokens {
|
|
302
|
+
if( u.input_tokens ) acc.inputTok = u.input_tokens
|
|
303
|
+
if( u.output_tokens ) acc.outputTok = u.output_tokens
|
|
304
|
+
if( u.cache_read_input_tokens ) acc.cacheReadTok = u.cache_read_input_tokens
|
|
305
|
+
if( u.cache_creation_input_tokens ) acc.cacheWriteTok = u.cache_creation_input_tokens
|
|
306
|
+
return acc
|
|
307
|
+
}
|
|
308
|
+
|
|
267
309
|
/**
|
|
268
310
|
* Cost-attribution metadata for a single LLM call. The same director instance is
|
|
269
311
|
* shared by the master executive, every facet (conversation/planning/outreach),
|
|
@@ -703,11 +745,24 @@ export class LLMDirector {
|
|
|
703
745
|
onChunk: ( chunk: string ) => void,
|
|
704
746
|
temperature?: number,
|
|
705
747
|
): Promise<LLMCallResult> {
|
|
706
|
-
//
|
|
707
|
-
// truncate a healthy
|
|
708
|
-
//
|
|
748
|
+
// IDLE deadline, not a whole-response one. A hard cap on the total stream
|
|
749
|
+
// would truncate a healthy long generation; no cap at all lets a stalled
|
|
750
|
+
// socket hang forever. A live generation keeps emitting and a dead one goes
|
|
751
|
+
// quiet, so the clock is restarted by every chunk that arrives and fires
|
|
752
|
+
// only after `_timeoutMs` of silence — the first byte included.
|
|
753
|
+
//
|
|
754
|
+
// It used to be cleared the moment the response headers arrived, which left
|
|
755
|
+
// the read loop with no deadline at all. A mid-stream stall then never
|
|
756
|
+
// settled, and the reasoning promise it belonged to stayed in the executive's
|
|
757
|
+
// `_pending` map forever: `hasPendingWork` gated every cycle and the master
|
|
758
|
+
// seat went silent for the full 600-tick prune horizon. Observed live as ten
|
|
759
|
+
// minutes of a mind not deliberating, announced by nothing.
|
|
709
760
|
const controller = new AbortController()
|
|
710
|
-
|
|
761
|
+
let timer = setTimeout( () => controller.abort(), this._timeoutMs )
|
|
762
|
+
const restartDeadline = (): void => {
|
|
763
|
+
clearTimeout( timer )
|
|
764
|
+
timer = setTimeout( () => controller.abort(), this._timeoutMs )
|
|
765
|
+
}
|
|
711
766
|
|
|
712
767
|
let res: Response
|
|
713
768
|
try {
|
|
@@ -732,24 +787,23 @@ export class LLMDirector {
|
|
|
732
787
|
throw err
|
|
733
788
|
}
|
|
734
789
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if( !res.ok )
|
|
790
|
+
if( !res.ok ){
|
|
791
|
+
clearTimeout( timer )
|
|
738
792
|
throw new Error(`Anthropic stream ${res.status}: ${( await res.text() ).slice(0, 300)}`)
|
|
793
|
+
}
|
|
739
794
|
|
|
740
795
|
const reader = res.body!.getReader()
|
|
741
796
|
const decoder = new TextDecoder()
|
|
742
797
|
let buffer = ''
|
|
743
|
-
let fullText
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
let cacheReadTok = 0
|
|
747
|
-
let cacheWriteTok = 0
|
|
798
|
+
let fullText = ''
|
|
799
|
+
const tokens: StreamTokens = {
|
|
800
|
+
inputTok: 0, outputTok: 0, cacheReadTok: 0, cacheWriteTok: 0 }
|
|
748
801
|
|
|
749
802
|
try {
|
|
750
803
|
while( true ){
|
|
751
804
|
const { done, value } = await reader.read()
|
|
752
805
|
if( done ) break
|
|
806
|
+
restartDeadline()
|
|
753
807
|
buffer += decoder.decode( value, { stream: true })
|
|
754
808
|
|
|
755
809
|
const lines = buffer.split('\n')
|
|
@@ -764,32 +818,52 @@ export class LLMDirector {
|
|
|
764
818
|
const ev = JSON.parse( raw ) as {
|
|
765
819
|
type: string
|
|
766
820
|
delta?: { type: string; text?: string; stop_reason?: string }
|
|
767
|
-
message?: { usage?:
|
|
768
|
-
usage?:
|
|
821
|
+
message?: { usage?: StreamUsage }
|
|
822
|
+
usage?: StreamUsage
|
|
769
823
|
}
|
|
770
824
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
825
|
+
// Usage arrives in different events depending on the host. Real
|
|
826
|
+
// Anthropic reports the input side up front on `message_start` and
|
|
827
|
+
// only `output_tokens` on `message_delta`. Anthropic-WIRE hosts do
|
|
828
|
+
// not all follow that: Z.ai (glm) sends `message_start` with zeroed
|
|
829
|
+
// placeholders and puts the true figures — input, output AND cache —
|
|
830
|
+
// on the final `message_delta`. Reading the input side from
|
|
831
|
+
// `message_start` alone therefore recorded 0 input and 0 cache for
|
|
832
|
+
// every GLM call ever made, while output logged correctly: 441,896
|
|
833
|
+
// output tokens against 0 input across one 7-hour COO run, which is
|
|
834
|
+
// not a possible shape for a conversation.
|
|
835
|
+
//
|
|
836
|
+
// So take usage wherever it appears and let a later non-zero reading
|
|
837
|
+
// win — a zero never overwrites a figure already in hand, and a real
|
|
838
|
+
// figure always replaces a placeholder.
|
|
839
|
+
if( ev.type === 'message_start' && ev.message?.usage )
|
|
840
|
+
foldStreamUsage( tokens, ev.message.usage )
|
|
776
841
|
else if( ev.type === 'content_block_delta' && ev.delta?.text ){
|
|
777
842
|
fullText += ev.delta.text
|
|
778
843
|
onChunk( ev.delta.text )
|
|
779
844
|
}
|
|
780
|
-
else if( ev.type === 'message_delta' && ev.usage
|
|
781
|
-
|
|
782
|
-
}
|
|
845
|
+
else if( ev.type === 'message_delta' && ev.usage )
|
|
846
|
+
foldStreamUsage( tokens, ev.usage )
|
|
783
847
|
}
|
|
784
848
|
catch { /* ignore malformed events */ }
|
|
785
849
|
}
|
|
786
850
|
}
|
|
787
851
|
}
|
|
852
|
+
catch( err ){
|
|
853
|
+
// An abort here is the idle deadline, not a caller cancelling: nothing
|
|
854
|
+
// arrived for `_timeoutMs` while the stream was still open. Naming it is
|
|
855
|
+
// the whole point — a stall that throws is a pass that failed, and a pass
|
|
856
|
+
// that failed settles its promise and frees the seat.
|
|
857
|
+
if( controller.signal.aborted )
|
|
858
|
+
throw new Error(`LLM stream to ${ep.provider} stalled — no data for ${this._timeoutMs}ms`)
|
|
859
|
+
throw err
|
|
860
|
+
}
|
|
788
861
|
finally {
|
|
862
|
+
clearTimeout( timer )
|
|
789
863
|
reader.releaseLock()
|
|
790
864
|
}
|
|
791
865
|
|
|
792
|
-
return { text: fullText,
|
|
866
|
+
return { text: fullText, ...tokens }
|
|
793
867
|
}
|
|
794
868
|
|
|
795
869
|
/**
|
|
@@ -831,7 +905,10 @@ export class LLMDirector {
|
|
|
831
905
|
() => ep.wire === 'anthropic'
|
|
832
906
|
? this._callAnthropicStream( ep, systemPrompt, userMessage, () => {}, temperature )
|
|
833
907
|
: this._callProvider( ep, systemPrompt, userMessage, temperature ),
|
|
834
|
-
|
|
908
|
+
`executive/direct:${ meta.function }`,
|
|
909
|
+
// A reply to a waiting person takes the reserved lane; everything the
|
|
910
|
+
// mind is doing for itself shares the general one. See `gateFor`.
|
|
911
|
+
gateFor( meta.function ),
|
|
835
912
|
)
|
|
836
913
|
|
|
837
914
|
// Record token usage + cost into this Will's injected tracker (R4), tagged
|
|
@@ -58,6 +58,44 @@ export function wrapReplyText( body: string ): string {
|
|
|
58
58
|
return [ REPLY_TEXT_OPEN, body, REPLY_TEXT_CLOSE ].join('\n')
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Every marker that is PROTOCOL rather than content.
|
|
63
|
+
*
|
|
64
|
+
* Named explicitly rather than matched as `[ANYTHING]`, because bracketed text is
|
|
65
|
+
* ordinary in real messages — "[1]", "[see §4.4]", "[REDACTED]" are things a mind
|
|
66
|
+
* may legitimately say, and a greedy strip would eat them.
|
|
67
|
+
*/
|
|
68
|
+
export const PROTOCOL_TAGS: readonly string[] = [
|
|
69
|
+
REPLY_TEXT_TAG, NO_MESSAGE_TAG, 'INTROSPECTION', 'NARRATIVE', 'SELF_OBS',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remove any protocol marker that survived inside extracted content.
|
|
74
|
+
*
|
|
75
|
+
* `extractTextBlock` slices from the first `[TAG]` to the first `[/TAG]` after
|
|
76
|
+
* it, so a STRAY second opener inside the body is carried out as content — and
|
|
77
|
+
* the bubble splitter, seeing a line of its own, delivers it as a message.
|
|
78
|
+
*
|
|
79
|
+
* Live: a COO's reply to a technical document went out as four substantive
|
|
80
|
+
* bubbles followed by a fifth reading exactly `[REPLY_TEXT]`. The person got a
|
|
81
|
+
* message whose entire content was the name of the slot it should have filled.
|
|
82
|
+
*
|
|
83
|
+
* These tokens can never be legitimate content — they are the wire, not the
|
|
84
|
+
* words — so stripping them is not censorship of anything the mind meant. A line
|
|
85
|
+
* left empty by the removal is dropped so it cannot become an empty bubble.
|
|
86
|
+
*/
|
|
87
|
+
export function stripProtocolMarkers( text: string ): string {
|
|
88
|
+
let out = text
|
|
89
|
+
for( const tag of PROTOCOL_TAGS )
|
|
90
|
+
out = out.split(`[${ tag }]`).join('').split(`[/${ tag }]`).join('')
|
|
91
|
+
|
|
92
|
+
return out
|
|
93
|
+
.split('\n')
|
|
94
|
+
.filter( ( line, i, all ) => line.trim() !== '' || ( i > 0 && i < all.length - 1 && all[ i - 1 ]?.trim() !== '') )
|
|
95
|
+
.join('\n')
|
|
96
|
+
.trim()
|
|
97
|
+
}
|
|
98
|
+
|
|
61
99
|
// ── Conversation-facet focus — render ↔ match pair ────────────
|
|
62
100
|
// The AuditionEngine RENDERS these lines into the facet focus; the mock LLM
|
|
63
101
|
// MATCHES them to detect "this call is a conversation turn" and synthesize a
|
package/src/pma/index.ts
CHANGED
|
@@ -282,7 +282,7 @@ export class PMADistiller {
|
|
|
282
282
|
* @param willId The Will's ID
|
|
283
283
|
* @param willName The Will's display name
|
|
284
284
|
* @param state Current simulation state (from stateManager.snapshot())
|
|
285
|
-
* @param sessionId Active session ID — recorded as
|
|
285
|
+
* @param sessionId Active session ID — recorded on the snapshot as its origin
|
|
286
286
|
* @param dataDir Root data dir (defaults to WILL_DATA_DIR env or './data')
|
|
287
287
|
* @param repertoire The agency competence layer (in-memory manager). When
|
|
288
288
|
* provided, the Will's learned skills + composite schemas are
|
package/src/stem/index.ts
CHANGED
|
@@ -38,6 +38,8 @@ import { TransportController } from '#stem/tracts/transport.controller'
|
|
|
38
38
|
import { InboundQueue } from '#stem/tracts/inbound.queue'
|
|
39
39
|
import type { ExternalTransport } from '#stem/tracts/transport'
|
|
40
40
|
import { effectorController } from '#stem/tracts/effector.controller'
|
|
41
|
+
import type { EffectorAck } from '#stem/tracts/effector/types'
|
|
42
|
+
import type { PolicyArbiter } from '#stem/policy/arbiter'
|
|
41
43
|
import { externalSchemas } from '#agency/schemas/external'
|
|
42
44
|
import { inFlightOnRestore } from '#agency/restart'
|
|
43
45
|
import { buildEngineConfigEntities } from '#cognition/config.mirror.entities'
|
|
@@ -575,16 +577,30 @@ export class WillStem {
|
|
|
575
577
|
? `${offlineMins} minutes`
|
|
576
578
|
: `${Math.round( offlineMins / 60 )} hours`
|
|
577
579
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
580
|
+
// Through the SENSE DOOR, not a hand-written percept (SIGNAL_BOUNDARY P1).
|
|
581
|
+
//
|
|
582
|
+
// This used to build a `percept` entity here, by hand, and every bug it
|
|
583
|
+
// had came from that: no tick (so it never expired, and told the executive
|
|
584
|
+
// "I was offline for 3 hours" for the rest of the mind's life), and no
|
|
585
|
+
// provenance (so `action.selector`'s rupture gate excluded it exactly as
|
|
586
|
+
// it excludes the mind's own echo — a mind returning after hours away
|
|
587
|
+
// could not be ruptured by noticing).
|
|
588
|
+
//
|
|
589
|
+
// Both were fixed by hand in P0 step 2. This removes the hand: waking is
|
|
590
|
+
// the world touching the mind, so it arrives as a `SystemSignal` through
|
|
591
|
+
// somatosensation like any other touch, and the sense supplies the tick,
|
|
592
|
+
// the trace, and the stamp because that is what a sense does. The wake
|
|
593
|
+
// event stops being special.
|
|
594
|
+
//
|
|
595
|
+
// Fire-and-forget: `ingest` is async and the resume path is not. The
|
|
596
|
+
// signal reaches state on the sense's own timing, exactly as an inbound
|
|
597
|
+
// message does.
|
|
598
|
+
void instance.cognition.somatosensationEngine.sense({
|
|
599
|
+
kind: 'system',
|
|
600
|
+
signal: 'WAKE',
|
|
601
|
+
provenance: 'exafferent', // time passed; nothing I did caused it
|
|
602
|
+
data: {
|
|
603
|
+
summary: `I was offline for ${duration}. I am now online again.`,
|
|
588
604
|
offlineMs,
|
|
589
605
|
},
|
|
590
606
|
})
|
|
@@ -731,7 +747,35 @@ export class WillStem {
|
|
|
731
747
|
* Resume the Will after loading to start ticking with the seeded state.
|
|
732
748
|
*/
|
|
733
749
|
loadPMA( id: string, pma: PMASnapshot ): void {
|
|
734
|
-
|
|
750
|
+
const instance = this._get( id )
|
|
751
|
+
this._pma.load( id, instance, pma )
|
|
752
|
+
|
|
753
|
+
// A MIND THAT WAKES SHOULD KNOW IT WAS AWAY (SIGNAL_BOUNDARY P1, follow-up).
|
|
754
|
+
//
|
|
755
|
+
// `resumeWill` has always raised a wake event, gated on `instance.pausedAt`
|
|
756
|
+
// — and `pausedAt` is set ONLY by `pauseWill()`, an in-session pause. The
|
|
757
|
+
// hibernate→wake lifecycle never touches it: `Will.wake` calls
|
|
758
|
+
// `createWill( config, startPaused: true )`, which sets `status = 'paused'`
|
|
759
|
+
// but leaves `pausedAt` null, and `resumeWill` then skips the whole block.
|
|
760
|
+
//
|
|
761
|
+
// So the one lifecycle every deployed Will actually uses was the one that
|
|
762
|
+
// never fired. Found by running a live Will after P1 routed the wake through
|
|
763
|
+
// the sense door: the door was correct and nothing walked through it. The
|
|
764
|
+
// unit test passed because it set `pausedAt` by hand — which is precisely
|
|
765
|
+
// the kind of test that agrees with you instead of checking.
|
|
766
|
+
//
|
|
767
|
+
// The PMA has carried the answer all along: `distilledAt` is when this mind
|
|
768
|
+
// was distilled, i.e. when it stopped. Loading a PMA IS the statement "this
|
|
769
|
+
// mind existed before and has been away since then", so this is where the
|
|
770
|
+
// fact belongs. A Will born fresh never calls this and is never told it
|
|
771
|
+
// woke, which is right — it was not away, it did not exist.
|
|
772
|
+
//
|
|
773
|
+
// Guarded: a clock skew or a hand-edited artifact must not tell a mind it
|
|
774
|
+
// has been away for negative time, or since 1970.
|
|
775
|
+
const distilled = pma.distilledAt
|
|
776
|
+
if( typeof distilled === 'number' && Number.isFinite( distilled )
|
|
777
|
+
&& distilled > 0 && distilled <= Date.now() )
|
|
778
|
+
instance.pausedAt = new Date( distilled )
|
|
735
779
|
}
|
|
736
780
|
|
|
737
781
|
// ── Replay ─────────────────────────────────────────────────
|
|
@@ -871,15 +915,20 @@ export class WillStem {
|
|
|
871
915
|
* `agency.intent` id). Reconciles it into an `agency.outcome` the ReafferenceEngine
|
|
872
916
|
* consumes — learning the result, freeing the intent, and advancing the plan it
|
|
873
917
|
* served (if any). See effectorController.confirmExecution.
|
|
918
|
+
*
|
|
919
|
+
* Takes the full `EffectorAck`. It used to declare its own narrower shape —
|
|
920
|
+
* `{ success, description, metrics? }` — which P2 left behind: the controller
|
|
921
|
+
* has routed `observation` into a reafferent percept since #150, but a host
|
|
922
|
+
* driving the stem DIRECTLY could not typecheck one, so the facts half of the
|
|
923
|
+
* ack was reachable only through the SDK facade. A type that silently withholds
|
|
924
|
+
* half a boundary from the hosts most likely to need it — a robot control
|
|
925
|
+
* layer, a game loop, anything not using the facade — is the same defect class
|
|
926
|
+
* as a comment that outlives its claim, with a compiler enforcing it.
|
|
874
927
|
*/
|
|
875
928
|
confirmEffectorExecution(
|
|
876
929
|
id: string,
|
|
877
930
|
invocationId: string,
|
|
878
|
-
result:
|
|
879
|
-
success: boolean
|
|
880
|
-
description: string
|
|
881
|
-
metrics?: Record<string, number>
|
|
882
|
-
},
|
|
931
|
+
result: EffectorAck,
|
|
883
932
|
): void {
|
|
884
933
|
this._effector.confirmExecution( this._get( id ), invocationId, result )
|
|
885
934
|
}
|
|
@@ -894,6 +943,28 @@ export class WillStem {
|
|
|
894
943
|
this._effector.resolveEscalation( this._get( id ), invocationId, approved )
|
|
895
944
|
}
|
|
896
945
|
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Install the Policy Decision Point consulted before every host-owned
|
|
949
|
+
* effector invocation is handed to the world (POLICY_REAFFERENCE P0).
|
|
950
|
+
* Passing `null` restores the no-op default — a stem with no arbiter
|
|
951
|
+
* installed runs byte-identical to one built before the policy seam existed.
|
|
952
|
+
*
|
|
953
|
+
* SCOPE: one arbiter per `WillStem`, not per Will — `effectorController` is a
|
|
954
|
+
* single instance shared by every Will this stem hosts (`setAllowed`,
|
|
955
|
+
* `resolveEscalation` etc. take a resolved instance; this does not, because
|
|
956
|
+
* there is only one). `Will.create()` / `Will.wake()` each allocate a fresh
|
|
957
|
+
* `WillStem`, so on that (recommended) path this is per-Will in practice. A
|
|
958
|
+
* host running several Wills on ONE shared `WillStem` — e.g. a multi-tenant
|
|
959
|
+
* service holding many users' Wills in one process — installs ONE arbiter
|
|
960
|
+
* for all of them; branch on `invocation.willId` inside `evaluate()` if
|
|
961
|
+
* that's your host, the same way `PolicyVerdictRecord`/`PolicyVerdictSource`
|
|
962
|
+
* are already keyed per-Will for recording and replay.
|
|
963
|
+
*/
|
|
964
|
+
setArbiter( arbiter: PolicyArbiter | null ): void {
|
|
965
|
+
this._effector.setArbiter( arbiter )
|
|
966
|
+
}
|
|
967
|
+
|
|
897
968
|
// ── Messaging / outbox (11.1) ────────────────────────────────────────────
|
|
898
969
|
// Delegates to OutboxController (R5-c). `_get(id)` validates the Will exists
|
|
899
970
|
// and supplies the WillInstance; the outbox ops touch only instance fields.
|
|
@@ -952,8 +1023,8 @@ export class WillStem {
|
|
|
952
1023
|
* @param id Will ID
|
|
953
1024
|
* @param input TextMessage — `{ kind: 'text', entityId, threadId, content, speakerName? }`
|
|
954
1025
|
*/
|
|
955
|
-
async
|
|
956
|
-
await this._sensory.
|
|
1026
|
+
async senseText( id: string, input: TextMessage ): Promise<void> {
|
|
1027
|
+
await this._sensory.senseText( this._get( id ), input )
|
|
957
1028
|
}
|
|
958
1029
|
|
|
959
1030
|
/**
|
|
@@ -1005,10 +1076,10 @@ export class WillStem {
|
|
|
1005
1076
|
/**
|
|
1006
1077
|
* Route a raw SensoryInput to the appropriate sense engine by domain.
|
|
1007
1078
|
* Used by the debug `POST /senses/:domain/ingest` route.
|
|
1008
|
-
* Audition inputs are gated by the 'listen' effector like
|
|
1079
|
+
* Audition inputs are gated by the 'listen' effector like senseText().
|
|
1009
1080
|
*/
|
|
1010
|
-
async
|
|
1011
|
-
await this._sensory.
|
|
1081
|
+
async senseSignal( id: string, domain: string, input: SensoryInput ): Promise<void> {
|
|
1082
|
+
await this._sensory.senseSignal( this._get( id ), domain, input )
|
|
1012
1083
|
}
|
|
1013
1084
|
|
|
1014
1085
|
listWills(): WillSummary[] {
|
package/src/stem/mind.ts
CHANGED
|
@@ -26,14 +26,14 @@ import { OutboxWriter } from '#stem/tracts/outbox.writer'
|
|
|
26
26
|
import { ExecutiveSummarizer } from '#llm/summarizer'
|
|
27
27
|
import type { LLMProvider } from '#llm/index'
|
|
28
28
|
import { TableRouter, chainRouters, type ModelRouter, type RoutingRule } from '#llm/routing'
|
|
29
|
-
import { resolveProfile } from '#profiles/index'
|
|
29
|
+
import { resolveProfile } from '#stem/profiles/index'
|
|
30
30
|
import { DefaultVectorMemoryAdapter } from '#memory/vector.adapter'
|
|
31
31
|
import { OpenAICompatibleEmbedder, MockEmbedder } from '#memory/vector.embedder'
|
|
32
32
|
import type { VectorMemoryAdapter } from '#memory/vector.adapter'
|
|
33
33
|
import type { ExternalTransport } from '#stem/tracts/transport'
|
|
34
34
|
import type { Cognition, OutboxMessage } from '#types'
|
|
35
35
|
import type { StorageAdapter } from '#core/abstracts'
|
|
36
|
-
import '#profiles/built-in'
|
|
36
|
+
import '#stem/profiles/built-in'
|
|
37
37
|
|
|
38
38
|
// ── Agency pipeline (new action system) ──────────────────────
|
|
39
39
|
import { ProactiveCommunicator } from '#agency/proactive.communicator'
|
|
@@ -100,6 +100,7 @@ import {
|
|
|
100
100
|
GustationEngine
|
|
101
101
|
} from '#cognition/index'
|
|
102
102
|
import { buildEngineConfigEntities, mergeEngineConfig, EngineConfigEntity } from '#cognition/config.mirror.entities'
|
|
103
|
+
import { endogenousTypes } from '#cognition/sense.boundary'
|
|
103
104
|
import { mergeIdentity, composeIdentityPrompt, WILL_CORE_PREAMBLE } from '#cognition/identity.entity'
|
|
104
105
|
import {
|
|
105
106
|
isReferentId, readAliases, handlesOf, defaultHandle,
|
|
@@ -1064,6 +1065,23 @@ function _constructCognition(
|
|
|
1064
1065
|
// buildExecutiveContext (already vector-backed via the consolidator).
|
|
1065
1066
|
auditionEngine.attachMemorySink( entity => simulation.stateManager.setEntity( entity ) )
|
|
1066
1067
|
|
|
1068
|
+
// Every sense lays down a percept trace (SIGNAL_BOUNDARY P0). Wired for all
|
|
1069
|
+
// five, not just the implemented one, because that is the contract a host is
|
|
1070
|
+
// owed: implement a sense and what it senses reaches the faculties that read
|
|
1071
|
+
// percepts, rather than a bus event three subscribers glance at for one tick.
|
|
1072
|
+
//
|
|
1073
|
+
// The tick is a getter, not a value — a sense is ingest-driven and off-tick,
|
|
1074
|
+
// so it has no `react()` to be handed one in, and a captured number would
|
|
1075
|
+
// stamp every percept with whenever assembly ran.
|
|
1076
|
+
//
|
|
1077
|
+
// AuditionEngine opts out (`tracesPercepts = false`); it still gets the wiring
|
|
1078
|
+
// so turning it on is a one-word change when the measurement is done.
|
|
1079
|
+
for( const sense of [ auditionEngine, visionEngine, somatosensationEngine, olfactionEngine, gustationEngine ] )
|
|
1080
|
+
sense.attachPerceptTrace(
|
|
1081
|
+
entity => simulation.stateManager.setEntity( entity ),
|
|
1082
|
+
() => simulation.clock.currentTick,
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1067
1085
|
// Referent → address + room. Closes over the state manager because the writer
|
|
1068
1086
|
// is deliberately stateless; this is the one seam both send paths cross, so the
|
|
1069
1087
|
// translation happens once rather than in each of them.
|
|
@@ -1307,6 +1325,12 @@ function _registerEngines( simulation: DefaultSimulation, cognition: Cognition,
|
|
|
1307
1325
|
activeEngines
|
|
1308
1326
|
.sort( ( a: any, b: any ) => a.priority - b.priority )
|
|
1309
1327
|
.forEach( e => simulation.addEngine( e ) )
|
|
1328
|
+
|
|
1329
|
+
// Where this mind ends. Read from the LIVE engine list, not a copy, so an
|
|
1330
|
+
// engine a host registers after assembly is inside the boundary too — its
|
|
1331
|
+
// declared `writes` are machinery, and machinery is not perceived.
|
|
1332
|
+
cognition.exteroception.attachBoundary(
|
|
1333
|
+
() => endogenousTypes( simulation.orchestrator.engines ) )
|
|
1310
1334
|
}
|
|
1311
1335
|
|
|
1312
1336
|
/**
|
|
@@ -98,6 +98,16 @@ export interface PolicyInvocation {
|
|
|
98
98
|
schema: string
|
|
99
99
|
parameters: Record<string, unknown>
|
|
100
100
|
targetEntityId?: string
|
|
101
|
+
/**
|
|
102
|
+
* The addresses this host knows `targetEntityId` by (e.g. `discord:123…`) —
|
|
103
|
+
* the same enrichment a host effector handler already receives on `ctx`
|
|
104
|
+
* (`EffectorHandler`'s `ctx.targetAddresses`, `surface/sdk/will.ts`). The
|
|
105
|
+
* arbiter sits at the identical boundary, one step earlier in the same flow,
|
|
106
|
+
* so withholding it here bought no extra privacy — a handler downstream of an
|
|
107
|
+
* ALLOW already sees it. Absent when the invocation binds no target or the
|
|
108
|
+
* entity has no known address.
|
|
109
|
+
*/
|
|
110
|
+
targetAddresses?: readonly string[]
|
|
101
111
|
/** The ability's declared meaning, as given by the host at wiring time. */
|
|
102
112
|
description?: string
|
|
103
113
|
tick: number
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Side-effect import — registers all built-in world profiles.
|
|
2
|
+
// Import this once at application entry (mind.ts) before resolveProfile is called.
|
|
3
|
+
import '#stem/profiles/customer-service'
|
|
4
|
+
import '#stem/profiles/companion'
|
|
5
|
+
import '#stem/profiles/smart-home'
|
|
6
|
+
import '#stem/profiles/game-npc'
|
|
7
|
+
import '#stem/profiles/company-brain'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/profiles/index.ts — World profile registry
|
|
2
|
+
// src/stem/profiles/index.ts — World profile registry
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// A world profile is a named configuration preset that:
|