@mindot/will 0.1.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/LICENSE +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- package/src/types.ts +116 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/ack.reconciler.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// AckReconciler — idempotent dedup for the dual ack paths (Section 3).
|
|
6
|
+
//
|
|
7
|
+
// An outbound envelope can be acked twice: once via the socket.io emit callback
|
|
8
|
+
// (fast, best-effort) and once via a discrete inbound event (durable across
|
|
9
|
+
// reconnects — see SocketIoTransport). Both carry the same correlationId. This
|
|
10
|
+
// keyed deduper guarantees the ack is applied exactly once: the FIRST ack for a
|
|
11
|
+
// correlationId wins, every later one is a no-op.
|
|
12
|
+
//
|
|
13
|
+
// It is a pure deduper — classification (delivery vs result) and application
|
|
14
|
+
// (confirmDelivery / confirmExecution) stay in TransportController, which holds
|
|
15
|
+
// the collaborators. Bounded by a FIFO eviction window so it never grows without
|
|
16
|
+
// limit on a long-lived Will.
|
|
17
|
+
// ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const DEFAULT_MAX_TRACKED = 10_000
|
|
20
|
+
|
|
21
|
+
export class AckReconciler {
|
|
22
|
+
private readonly _seen = new Set<string>()
|
|
23
|
+
private readonly _order: string[] = []
|
|
24
|
+
private readonly _max: number
|
|
25
|
+
|
|
26
|
+
constructor( maxTracked: number = DEFAULT_MAX_TRACKED ){
|
|
27
|
+
this._max = maxTracked
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Record an ack for `correlationId`. Returns true the FIRST time (caller should
|
|
32
|
+
* apply the ack), false on every subsequent call (caller drops it).
|
|
33
|
+
*/
|
|
34
|
+
shouldApply( correlationId: string ): boolean {
|
|
35
|
+
if( this._seen.has( correlationId ) ) return false
|
|
36
|
+
|
|
37
|
+
this._seen.add( correlationId )
|
|
38
|
+
this._order.push( correlationId )
|
|
39
|
+
if( this._order.length > this._max ){
|
|
40
|
+
const evicted = this._order.shift()
|
|
41
|
+
if( evicted !== undefined ) this._seen.delete( evicted )
|
|
42
|
+
}
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Whether a correlationId has already been applied (without recording it). */
|
|
47
|
+
has( correlationId: string ): boolean { return this._seen.has( correlationId ) }
|
|
48
|
+
|
|
49
|
+
get size(): number { return this._seen.size }
|
|
50
|
+
|
|
51
|
+
clear(): void {
|
|
52
|
+
this._seen.clear()
|
|
53
|
+
this._order.length = 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/biography.writer.ts — per-Will session-biography writers
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// BiographyWriter owns the session-biography disk writers extracted from
|
|
6
|
+
// WillStem (R5-f): the behavioral session summary and the emotional
|
|
7
|
+
// biography (real-time spike/sustained-arousal events + end-of-session
|
|
8
|
+
// summary). All output is appended as JSONL under
|
|
9
|
+
// `data/wills/{willId}/profiles/`.
|
|
10
|
+
//
|
|
11
|
+
// WillStem calls these from its lifecycle (pause/archive) and tick loop
|
|
12
|
+
// (emotion-spike detection). The writers read (and, for the emotional
|
|
13
|
+
// summary, flush) the instance's `_sessionBehavior` aggregate; they touch
|
|
14
|
+
// only WillInstance fields, so they take the resolved instance directly
|
|
15
|
+
// and derive the Will id from `instance.config.id`.
|
|
16
|
+
//
|
|
17
|
+
// Behaviour is preserved verbatim from the original WillStem methods;
|
|
18
|
+
// this is a pure extract-collaborator refactor.
|
|
19
|
+
// ─────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
import { logger } from '#core/logger'
|
|
22
|
+
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
23
|
+
import { join } from 'node:path'
|
|
24
|
+
import { fileLoggingEnabled } from '#stem/tracts/transport/stream.transport'
|
|
25
|
+
import type { WillInstance } from '#stem/index'
|
|
26
|
+
|
|
27
|
+
export class BiographyWriter {
|
|
28
|
+
/** Profile dirs already ensured this process — avoids an mkdir syscall per write. */
|
|
29
|
+
private readonly _ensuredDirs = new Set<string>()
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Append one JSONL record to a Will's profiles/ dir. Dev-only (gated by
|
|
33
|
+
* `fileLoggingEnabled()`, matching SessionLogger) — in production this concern
|
|
34
|
+
* rides the `session_log`/transport path, not ephemeral local disk. The dir is
|
|
35
|
+
* ensured once per process, not per write.
|
|
36
|
+
*/
|
|
37
|
+
private _append( willId: string, file: string, record: unknown ): void {
|
|
38
|
+
if( !fileLoggingEnabled() ) return
|
|
39
|
+
|
|
40
|
+
const dataDir = process.env[ 'WILL_DATA_DIR' ] ?? './data'
|
|
41
|
+
const dir = join( dataDir, 'wills', willId, 'profiles' )
|
|
42
|
+
try {
|
|
43
|
+
if( !this._ensuredDirs.has( dir ) ){
|
|
44
|
+
mkdirSync( dir, { recursive: true } )
|
|
45
|
+
this._ensuredDirs.add( dir )
|
|
46
|
+
}
|
|
47
|
+
appendFileSync( join( dir, file ), JSON.stringify( record ) + '\n', 'utf8' )
|
|
48
|
+
} catch( err ){
|
|
49
|
+
logger.error( `[biography] ${file} write failed:`, err )
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compute and append a behavioral session summary to `behavioral.jsonl`.
|
|
54
|
+
* Captures action distribution, top/novel/impulsive action counts, avg
|
|
55
|
+
* confidence, valence/arousal ranges, and goal completion for the session.
|
|
56
|
+
* Topics discussed (TODO 7.2) intentionally omitted — requires NLP analysis
|
|
57
|
+
* on conversation content not available in state metrics.
|
|
58
|
+
*/
|
|
59
|
+
writeSessionSummary( instance: WillInstance ): void {
|
|
60
|
+
const sb = instance._sessionBehavior
|
|
61
|
+
if( !sb ) return
|
|
62
|
+
|
|
63
|
+
const willId = instance.config.id
|
|
64
|
+
|
|
65
|
+
// Action type distribution from the agency repertoire's enactment counts.
|
|
66
|
+
const actionDist: Record<string, number> = {}
|
|
67
|
+
for( const skill of instance.cognition.schemaRepertoire.skills().values() )
|
|
68
|
+
if( skill.enactments > 0 ) actionDist[ skill.schema ] = skill.enactments
|
|
69
|
+
|
|
70
|
+
const topAction = Object.entries( actionDist )
|
|
71
|
+
.sort( ( a, b ) => b[1] - a[1] )[ 0 ]?.[ 0 ] ?? 'none'
|
|
72
|
+
|
|
73
|
+
// Action types tried exactly once signal exploratory/novel behavior
|
|
74
|
+
const novelActionCount = Object.values( actionDist ).filter( c => c === 1 ).length
|
|
75
|
+
|
|
76
|
+
const avgConfidence = sb.confidenceCount > 0
|
|
77
|
+
? sb.confidenceSum / sb.confidenceCount
|
|
78
|
+
: null
|
|
79
|
+
|
|
80
|
+
const completionRate = sb.goalsTotal > 0
|
|
81
|
+
? sb.goalsCompleted / sb.goalsTotal
|
|
82
|
+
: null
|
|
83
|
+
|
|
84
|
+
const summary = {
|
|
85
|
+
type: 'session_summary',
|
|
86
|
+
wallTime: Date.now(),
|
|
87
|
+
sessionId: instance.sessionLogger?.sessionId ?? 'unknown',
|
|
88
|
+
willId,
|
|
89
|
+
startTick: sb.startTick,
|
|
90
|
+
endTick: instance.tickCount,
|
|
91
|
+
tickCount: instance.tickCount - sb.startTick,
|
|
92
|
+
actionDist,
|
|
93
|
+
topAction,
|
|
94
|
+
avgConfidence,
|
|
95
|
+
novelActionCount,
|
|
96
|
+
impulsiveActionCount: sb.impulsiveActionCount,
|
|
97
|
+
valenceRange: { min: sb.valenceMin, max: sb.valenceMax, spread: sb.valenceMax - sb.valenceMin },
|
|
98
|
+
arousalRange: { min: sb.arousalMin, max: sb.arousalMax, spread: sb.arousalMax - sb.arousalMin },
|
|
99
|
+
goalsTotal: sb.goalsTotal,
|
|
100
|
+
goalsCompleted: sb.goalsCompleted,
|
|
101
|
+
completionRate,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this._append( willId, 'behavioral.jsonl', summary )
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Append one emotional event entry to `emotional_biography.jsonl`.
|
|
109
|
+
* Called real-time on spike detection and sustained-arousal episode end.
|
|
110
|
+
*
|
|
111
|
+
* @param evtType 'spike' | 'sustained_high_arousal'
|
|
112
|
+
* @param fields Event-specific fields (tick, dimension, from/to/delta, etc.)
|
|
113
|
+
*/
|
|
114
|
+
writeEmotionalEvent(
|
|
115
|
+
instance: WillInstance,
|
|
116
|
+
evtType: 'spike' | 'sustained_high_arousal',
|
|
117
|
+
fields: Record<string, unknown>
|
|
118
|
+
): void {
|
|
119
|
+
const willId = instance.config.id
|
|
120
|
+
|
|
121
|
+
const entry = {
|
|
122
|
+
type: evtType,
|
|
123
|
+
wallTime: Date.now(),
|
|
124
|
+
sessionId: instance.sessionLogger?.sessionId ?? 'unknown',
|
|
125
|
+
willId,
|
|
126
|
+
...fields,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._append( willId, 'emotional_biography.jsonl', entry )
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compute and append a session emotional biography summary to
|
|
134
|
+
* `emotional_biography.jsonl`. Called at session end (pause or archive).
|
|
135
|
+
* Flushes any open sustained-arousal streak, then writes a `session_summary`
|
|
136
|
+
* entry covering dominant mood, valence/arousal arcs, spike count, and the
|
|
137
|
+
* number of completed sustained-high-arousal episodes.
|
|
138
|
+
*
|
|
139
|
+
* PMA tooling can read across all `session_summary` entries to build a cross-
|
|
140
|
+
* session emotional biography.
|
|
141
|
+
*/
|
|
142
|
+
writeEmotionalBiographySummary( instance: WillInstance ): void {
|
|
143
|
+
const sb = instance._sessionBehavior
|
|
144
|
+
if( !sb || sb.avgValenceCount === 0 ) return
|
|
145
|
+
|
|
146
|
+
const willId = instance.config.id
|
|
147
|
+
|
|
148
|
+
// Flush any open sustained-arousal streak before writing the summary.
|
|
149
|
+
if( sb.highArousalStreak >= 10 ){
|
|
150
|
+
sb.sustainedEpisodes++
|
|
151
|
+
this.writeEmotionalEvent( instance, 'sustained_high_arousal', {
|
|
152
|
+
startTick: instance.tickCount - sb.highArousalStreak,
|
|
153
|
+
durationTicks: sb.highArousalStreak,
|
|
154
|
+
tick: instance.tickCount,
|
|
155
|
+
})
|
|
156
|
+
sb.highArousalStreak = 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const avgValence = sb.avgValenceSum / sb.avgValenceCount
|
|
160
|
+
const dominantMood = avgValence > 0.15 ? 'positive' : avgValence < -0.10 ? 'negative' : 'neutral'
|
|
161
|
+
|
|
162
|
+
const summary = {
|
|
163
|
+
type: 'session_summary',
|
|
164
|
+
wallTime: Date.now(),
|
|
165
|
+
sessionId: instance.sessionLogger?.sessionId ?? 'unknown',
|
|
166
|
+
willId,
|
|
167
|
+
startTick: sb.startTick,
|
|
168
|
+
endTick: instance.tickCount,
|
|
169
|
+
tickCount: instance.tickCount - sb.startTick,
|
|
170
|
+
dominantMood,
|
|
171
|
+
avgValence: Math.round( avgValence * 1000 ) / 1000,
|
|
172
|
+
valenceArc: {
|
|
173
|
+
start: Math.round( sb.valenceStart * 1000 ) / 1000,
|
|
174
|
+
end: Math.round( sb.valenceEnd * 1000 ) / 1000,
|
|
175
|
+
min: Math.round( sb.valenceMin * 1000 ) / 1000,
|
|
176
|
+
max: Math.round( sb.valenceMax * 1000 ) / 1000,
|
|
177
|
+
},
|
|
178
|
+
arousalArc: {
|
|
179
|
+
start: Math.round( sb.arousalStart * 1000 ) / 1000,
|
|
180
|
+
end: Math.round( sb.arousalEnd * 1000 ) / 1000,
|
|
181
|
+
min: Math.round( sb.arousalMin * 1000 ) / 1000,
|
|
182
|
+
max: Math.round( sb.arousalMax * 1000 ) / 1000,
|
|
183
|
+
},
|
|
184
|
+
spikeCount: sb.spikeCount,
|
|
185
|
+
sustainedHighArousalEpisodes: sb.sustainedEpisodes,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._append( willId, 'emotional_biography.jsonl', summary )
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/effector.controller.ts — per-Will external-effector subsystem
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// effectorController owns the external-effector machinery extracted from
|
|
6
|
+
// WillStem (R5-d): the runtime allow-list (setAllowed), the host
|
|
7
|
+
// execution-feedback / reafference loop (confirmExecution), buffering of
|
|
8
|
+
// externally-dispatched invocations off the `agency.invocation` bus event
|
|
9
|
+
// (bufferInvocation), and draining that buffer for the delivery layer (drain).
|
|
10
|
+
//
|
|
11
|
+
// Agency-native (no legacy effector/decision.record stack): the MotorSchemaExecutor
|
|
12
|
+
// holds a host-owned action as an 'awaiting' `agency.intent` and publishes
|
|
13
|
+
// `agency.invocation` (carrying the intentId as the correlation handle). The host
|
|
14
|
+
// executes it and acks; `confirmExecution` turns that ack into an `agency.outcome`
|
|
15
|
+
// via `reconcileInvocation`, which the ReafferenceEngine consumes — learning the
|
|
16
|
+
// real result, freeing the awaiting intent, and (when the intent was a plan's
|
|
17
|
+
// frontier step) emitting the `action.outcome` the PlanningEngine advances on.
|
|
18
|
+
//
|
|
19
|
+
// The ops touch only WillInstance fields (no Will id needed), so these
|
|
20
|
+
// methods take the resolved instance directly. WillStem still validates
|
|
21
|
+
// existence via _get(id) before delegating.
|
|
22
|
+
// ─────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
import { logger } from '#core/logger'
|
|
25
|
+
import { reconcileInvocation } from '#agency/reconcile.learning'
|
|
26
|
+
import type { effectorInvocation } from '#types'
|
|
27
|
+
import type { WillInstance } from '#stem/index'
|
|
28
|
+
|
|
29
|
+
export class effectorController {
|
|
30
|
+
/**
|
|
31
|
+
* Update the set of allowed communication effectors at runtime via AccessGrants
|
|
32
|
+
* (the permission / sense gate the senses + reply path read).
|
|
33
|
+
*/
|
|
34
|
+
setAllowed( instance: WillInstance, effectors: string[] | null ): void {
|
|
35
|
+
instance.cognition.accessGrants.setAllowed( effectors )
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Buffer a host-owned effector invocation for delivery. Called from the WillStem
|
|
40
|
+
* `agency.invocation` bus subscription with the event payload
|
|
41
|
+
* (`{ schema, intentId, targetEntityId, parameters, tick }`). The awaiting
|
|
42
|
+
* `agency.intent` id is the **correlation handle** (`decisionRecordId`): the host
|
|
43
|
+
* echoes it on its result-ack, and `confirmExecution` uses it to find the intent.
|
|
44
|
+
*/
|
|
45
|
+
bufferInvocation( instance: WillInstance, payload: Record<string, unknown> ): void {
|
|
46
|
+
const intentId = ( payload.intentId as string ) ?? ''
|
|
47
|
+
instance.pendingEffectorInvocations.push({
|
|
48
|
+
id: intentId,
|
|
49
|
+
decisionRecordId: intentId, // correlation handle — the awaiting agency.intent id
|
|
50
|
+
effectorName: ( payload.schema as string ) ?? '',
|
|
51
|
+
parameters: ( payload.parameters as Record<string, unknown> ) ?? {},
|
|
52
|
+
targetEntityId: payload.targetEntityId as string | undefined,
|
|
53
|
+
reasoning: ( payload.reasoning as string ) ?? '',
|
|
54
|
+
tick: ( payload.tick as number ) ?? 0,
|
|
55
|
+
timestamp: Date.now()
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Drain all pending external effector invocations.
|
|
61
|
+
* Called by the SSE delivery layer each tick after drainOutbox.
|
|
62
|
+
*/
|
|
63
|
+
drain( instance: WillInstance ): effectorInvocation[] {
|
|
64
|
+
return instance.pendingEffectorInvocations.splice( 0 )
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── External Effector Feedback (agency-native reafference) ─────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Called by the host/WorldInterface after executing a host-owned effector.
|
|
70
|
+
* `invocationId` is the correlation handle the host echoed — the awaiting
|
|
71
|
+
* `agency.intent`'s id (= the `decisionRecordId` field of the effectorInvocation).
|
|
72
|
+
*
|
|
73
|
+
* It reconciles the ack into an `agency.outcome` (via `reconcileInvocation`),
|
|
74
|
+
* carrying the intent's efference copy (predicted reward/valence) so surprise is
|
|
75
|
+
* honest, and its plan provenance (planId/stepId) when it was a plan's frontier
|
|
76
|
+
* step. The ReafferenceEngine consumes that next tick: it learns the real result,
|
|
77
|
+
* frees the awaiting intent, and — for a plan-tagged outcome — emits the
|
|
78
|
+
* `action.outcome` the PlanningEngine advances on. No decision.record, no legacy
|
|
79
|
+
* `effector.confirmed`: the agency pipeline owns the loop end to end.
|
|
80
|
+
*/
|
|
81
|
+
confirmExecution(
|
|
82
|
+
instance: WillInstance,
|
|
83
|
+
invocationId: string,
|
|
84
|
+
result: {
|
|
85
|
+
success: boolean
|
|
86
|
+
description: string
|
|
87
|
+
metrics?: Record<string, number>
|
|
88
|
+
},
|
|
89
|
+
): void {
|
|
90
|
+
const tick = instance.tickCount
|
|
91
|
+
|
|
92
|
+
const intent = instance.simulation.stateManager.snapshot().entities.get( invocationId )
|
|
93
|
+
if( !intent || intent.type !== 'agency.intent' ){
|
|
94
|
+
// No awaiting intent for this id. Expected when the executor's 15-tick await
|
|
95
|
+
// timeout already abandoned it (writing a failed outcome + advancing any plan)
|
|
96
|
+
// before the host's late ack arrived, or the id is unknown. Drop the straggler.
|
|
97
|
+
logger.warn( `[effector] confirmExecution: no awaiting agency.intent "${invocationId}" (timed out / already reconciled?) — ignored` )
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const m = ( intent.metadata ?? {} ) as Record<string, unknown>
|
|
102
|
+
const schema = ( m['schema'] as string ) ?? 'unknown'
|
|
103
|
+
const predicted = {
|
|
104
|
+
reward: num( m['predictedReward'], num( m['expectedReward'], 0.5 ) ),
|
|
105
|
+
valence: num( m['predictedValence'], num( m['expectedValence'], 0 ) ),
|
|
106
|
+
}
|
|
107
|
+
const provenance = { planId: m['planId'] as string | undefined, stepId: m['stepId'] as string | undefined }
|
|
108
|
+
|
|
109
|
+
// Reconcile → agency.outcome. ReafferenceEngine consumes it next tick (learn +
|
|
110
|
+
// free the intent + emit the plan's action.outcome when plan-tagged).
|
|
111
|
+
instance.simulation.stateManager.setEntity(
|
|
112
|
+
reconcileInvocation( invocationId, schema, result, tick, predicted, provenance )
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// Optional host-supplied metric deltas (e.g. the world moved a body metric).
|
|
116
|
+
// Validate each value is a finite number before it touches simulation state —
|
|
117
|
+
// a NaN/Infinity/garbage value from a buggy host would persist and corrupt
|
|
118
|
+
// cognition (it isn't recomputed away like blended affect is).
|
|
119
|
+
if( result.metrics )
|
|
120
|
+
for( const [ k, v ] of Object.entries( result.metrics ) ){
|
|
121
|
+
if( typeof v === 'number' && Number.isFinite( v ) )
|
|
122
|
+
instance.simulation.stateManager.setMetric( k, v )
|
|
123
|
+
else
|
|
124
|
+
logger.warn( `[effector] confirmExecution: dropped non-finite metric "${k}"=${String( v )} from host ack (${schema})` )
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
instance.sessionLogger?.write({
|
|
128
|
+
type: 'action.outcome',
|
|
129
|
+
tick,
|
|
130
|
+
actionType: schema,
|
|
131
|
+
success: result.success,
|
|
132
|
+
outcome: result.description.slice( 0, 300 ),
|
|
133
|
+
outcomeQuality: result.success ? 0.8 : 0.2,
|
|
134
|
+
confirmedExternally: true,
|
|
135
|
+
} as never)
|
|
136
|
+
|
|
137
|
+
logger.info( `[effector] ✓ reconciled ${result.success ? 'success' : 'failure'}: intent "${invocationId}" (${schema})` )
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function num( v: unknown, fallback: number ): number {
|
|
142
|
+
return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
|
|
143
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/health.reporter.ts — per-Will cognitive-health reporter
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// HealthReporter owns the composite cognitive-health view extracted from
|
|
6
|
+
// WillStem (R5-f2): a pure, read-only computation over a running Will's
|
|
7
|
+
// belief quality, affect state, and goal state, collapsed into a single
|
|
8
|
+
// `overallScore` and a 'healthy' | 'drifting' | 'degraded' status band.
|
|
9
|
+
// Intended for developer dashboards and platform monitoring.
|
|
10
|
+
//
|
|
11
|
+
// WillStem.getCognitiveHealth delegates here. The op touches only
|
|
12
|
+
// WillInstance fields (no Will id needed), so this method takes the
|
|
13
|
+
// resolved instance directly; WillStem still validates existence via
|
|
14
|
+
// _get(id) before delegating.
|
|
15
|
+
//
|
|
16
|
+
// Behaviour is preserved verbatim from the original WillStem method;
|
|
17
|
+
// this is a pure extract-collaborator refactor.
|
|
18
|
+
// ─────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
import type { CognitiveHealth, WillInstance } from '#stem/index'
|
|
21
|
+
|
|
22
|
+
/** Round to 3 decimals — keeps health payloads readable. */
|
|
23
|
+
const r3 = ( n: number ): number => Math.round( n * 1000 ) / 1000
|
|
24
|
+
|
|
25
|
+
export class HealthReporter {
|
|
26
|
+
/**
|
|
27
|
+
* Returns a composite health summary for a running Will.
|
|
28
|
+
* Intended for developer dashboards and platform monitoring.
|
|
29
|
+
*
|
|
30
|
+
* Status bands:
|
|
31
|
+
* healthy — normal operating range
|
|
32
|
+
* drifting — one or more indicators approaching problematic thresholds
|
|
33
|
+
* degraded — one or more indicators clearly outside healthy range
|
|
34
|
+
*/
|
|
35
|
+
report( instance: WillInstance ): CognitiveHealth {
|
|
36
|
+
const
|
|
37
|
+
snap = instance.simulation.stateManager.snapshot(),
|
|
38
|
+
beliefs = instance.cognition.semanticIntegrator.getBeliefs(),
|
|
39
|
+
tick = instance.tickCount
|
|
40
|
+
|
|
41
|
+
// ── Belief quality ──────────────────────────────────────────
|
|
42
|
+
const totalBeliefs = beliefs.length
|
|
43
|
+
let
|
|
44
|
+
confidenceSum = 0,
|
|
45
|
+
highRiskCount = 0
|
|
46
|
+
|
|
47
|
+
for( const b of beliefs ){
|
|
48
|
+
confidenceSum += b.confidence
|
|
49
|
+
if( b.confidence > 0.85 && b.supportingEpisodes < 3 ) highRiskCount++
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const
|
|
53
|
+
avgConfidence = totalBeliefs > 0 ? confidenceSum / totalBeliefs : 0,
|
|
54
|
+
// Confidence health peaks at ~0.62 and falls off linearly toward either
|
|
55
|
+
// extreme (over-confidence and chronic doubt are both unhealthy).
|
|
56
|
+
beliefScore = totalBeliefs === 0
|
|
57
|
+
? 0.5 // no beliefs yet — neutral
|
|
58
|
+
: Math.max( 0,
|
|
59
|
+
1.0
|
|
60
|
+
- Math.abs( avgConfidence - 0.62 ) * 1.5 // deviation from ideal
|
|
61
|
+
- ( highRiskCount / Math.max( totalBeliefs, 1 ) ) * 0.8
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// ── Affect state ────────────────────────────────────────────
|
|
65
|
+
// Negative emotions AND sustained low valence both signal distress — the
|
|
66
|
+
// latter is exactly what the executive early-fires on, so health tracks it too.
|
|
67
|
+
const
|
|
68
|
+
valence = snap.metrics.get('affect.valence') ?? 0,
|
|
69
|
+
frustration = snap.metrics.get('emotion.frustration') ?? 0,
|
|
70
|
+
irritability = snap.metrics.get('emotion.irritability') ?? 0,
|
|
71
|
+
stress = snap.metrics.get('stress.load') ?? 0,
|
|
72
|
+
isElevated = irritability > 0.55 || frustration > 0.65 || stress > 0.7,
|
|
73
|
+
affectScore = Math.max( 0,
|
|
74
|
+
1.0
|
|
75
|
+
- Math.max( 0, frustration - 0.3 ) * 0.8
|
|
76
|
+
- Math.max( 0, irritability - 0.3 ) * 0.8
|
|
77
|
+
- Math.max( 0, stress - 0.4 ) * 0.6
|
|
78
|
+
- Math.max( 0, -valence - 0.15 ) * 0.7 // sustained low valence drags health down
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// ── Goal state ──────────────────────────────────────────────
|
|
82
|
+
// Use the O(1) type index rather than scanning every entity — health is polled.
|
|
83
|
+
let
|
|
84
|
+
activeGoals = 0,
|
|
85
|
+
totalGoals = 0
|
|
86
|
+
|
|
87
|
+
for( const entity of instance.simulation.stateManager.getEntitiesByType('goal') ){
|
|
88
|
+
totalGoals++
|
|
89
|
+
const s = entity.metadata?.status
|
|
90
|
+
if( s === 'active' || s === 'in_progress' ) activeGoals++
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const goalScore = totalGoals === 0
|
|
94
|
+
? 0.7 // no goals yet — mildly healthy
|
|
95
|
+
: Math.min( 1, 0.4 + ( activeGoals / totalGoals ) * 0.6 )
|
|
96
|
+
|
|
97
|
+
// ── Composite ───────────────────────────────────────────────
|
|
98
|
+
const
|
|
99
|
+
overallScore = ( beliefScore * 0.4 ) + ( affectScore * 0.4 ) + ( goalScore * 0.2 ),
|
|
100
|
+
status: CognitiveHealth['status'] =
|
|
101
|
+
overallScore >= 0.65 ? 'healthy' :
|
|
102
|
+
overallScore >= 0.40 ? 'drifting' : 'degraded'
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
tick,
|
|
106
|
+
status,
|
|
107
|
+
overallScore: r3( overallScore ),
|
|
108
|
+
beliefs: { total: totalBeliefs, avgConfidence: r3( avgConfidence ), highRisk: highRiskCount },
|
|
109
|
+
affect: { valence: r3( valence ), frustration: r3( frustration ), irritability: r3( irritability ), stress: r3( stress ), isElevated },
|
|
110
|
+
goals: { total: totalGoals, active: activeGoals }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/inbound.queue.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// InboundQueue — the determinism linchpin for external I/O.
|
|
6
|
+
//
|
|
7
|
+
// Every InboundEnvelope from the ExternalTransport (messages, percepts, acks)
|
|
8
|
+
// is enqueued here on arrival and applied LATER, at a fixed point in the tick
|
|
9
|
+
// loop, with a logicalTime (tick) stamp. This is what keeps a live, async,
|
|
10
|
+
// non-deterministic socket compatible with the replay-deterministic core:
|
|
11
|
+
// - arrival is decoupled from application
|
|
12
|
+
// - application happens on-tick, in FIFO order, stamped to that tick
|
|
13
|
+
// - the replay recorder logs each tick's drained batch; replay re-injects it
|
|
14
|
+
//
|
|
15
|
+
// The queue holds NO sockets and applies NOTHING itself — it is a buffer.
|
|
16
|
+
// ─────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
import type { InboundEnvelope } from './transport/types'
|
|
19
|
+
|
|
20
|
+
/** An inbound envelope paired with the tick it was applied on (set at drain). */
|
|
21
|
+
export interface StampedInbound {
|
|
22
|
+
envelope: InboundEnvelope
|
|
23
|
+
/** Tick at which this was drained + applied. Assigned by drain(tick). */
|
|
24
|
+
appliedTick: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class InboundQueue {
|
|
28
|
+
private _pending: InboundEnvelope[] = []
|
|
29
|
+
|
|
30
|
+
/** Buffer an inbound envelope. Called from the transport handler — never applies. */
|
|
31
|
+
enqueue( env: InboundEnvelope ): void {
|
|
32
|
+
this._pending.push( env )
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Number of envelopes waiting to be applied. */
|
|
36
|
+
get size(): number { return this._pending.length }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Remove and return all pending envelopes, stamped with the given tick.
|
|
40
|
+
* Called once per tick by the stem before simulation.step(). FIFO order is
|
|
41
|
+
* preserved so application is deterministic given the recorded batch.
|
|
42
|
+
*/
|
|
43
|
+
drain( tick: number ): StampedInbound[] {
|
|
44
|
+
if( this._pending.length === 0 ) return []
|
|
45
|
+
const batch = this._pending
|
|
46
|
+
this._pending = []
|
|
47
|
+
return batch.map( envelope => ({ envelope, appliedTick: tick }) )
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Discard everything without applying (teardown). */
|
|
51
|
+
clear(): void {
|
|
52
|
+
this._pending = []
|
|
53
|
+
}
|
|
54
|
+
}
|