@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,202 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/forgetting.curve.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ForgettingCurve — applies spaced repetition dynamics to episodic memories.
|
|
7
|
+
*
|
|
8
|
+
* Models the Ebbinghaus forgetting curve:
|
|
9
|
+
* - Memories decay exponentially without reactivation
|
|
10
|
+
* - Each retrieval resets and strengthens the memory
|
|
11
|
+
* - Emotionally intense memories decay slower
|
|
12
|
+
* - Sleep consolidates and protects memories
|
|
13
|
+
*
|
|
14
|
+
* Operates on the episodic memory store, decaying activation
|
|
15
|
+
* strength over time and removing memories that fall below
|
|
16
|
+
* the retrieval threshold.
|
|
17
|
+
*
|
|
18
|
+
* Part of Shard 2 (Memory Layer) — runs every tick, synchronous.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
Duration,
|
|
23
|
+
Tick,
|
|
24
|
+
SimulationContext,
|
|
25
|
+
ReadonlySimulationState,
|
|
26
|
+
StateCommands,
|
|
27
|
+
} from '#core/types'
|
|
28
|
+
import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
|
|
29
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
30
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
31
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
32
|
+
import { GenerativeModel, type GenerativeModelSnapshot } from '#cognition/generative.model'
|
|
33
|
+
|
|
34
|
+
export interface ForgettingCurveConfig {
|
|
35
|
+
/** Base forgetting rate (Ebbinghaus: ~0.3 per log-time unit) */
|
|
36
|
+
baseForgettingRate?: number
|
|
37
|
+
/** How much emotional intensity slows forgetting (0-1, 1 = no forgetting) */
|
|
38
|
+
emotionProtection?: number
|
|
39
|
+
/** Minimum activation before a memory is pruned */
|
|
40
|
+
pruningThreshold?: number
|
|
41
|
+
/** Maximum memories to prune per tick (avoids stalls) */
|
|
42
|
+
maxPrunePerTick?: number
|
|
43
|
+
bus?: CognitiveBus
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class ForgettingCurve implements SimulationEngine, CognitiveEngine {
|
|
47
|
+
readonly name = 'forgetting-curve'
|
|
48
|
+
|
|
49
|
+
private _baseForgettingRate: number
|
|
50
|
+
private _emotionProtection: number
|
|
51
|
+
private _pruningThreshold: number
|
|
52
|
+
private _maxPrunePerTick: number
|
|
53
|
+
|
|
54
|
+
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
55
|
+
|
|
56
|
+
private _bus: CognitiveBus | null = null
|
|
57
|
+
|
|
58
|
+
private readonly _model = new GenerativeModel()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
constructor( config: ForgettingCurveConfig = {} ){
|
|
62
|
+
this._bus = config.bus ?? null
|
|
63
|
+
this._baseForgettingRate = config.baseForgettingRate ?? 0.02
|
|
64
|
+
this._emotionProtection = config.emotionProtection ?? 0.7
|
|
65
|
+
this._pruningThreshold = config.pruningThreshold ?? 0.01
|
|
66
|
+
this._maxPrunePerTick = config.maxPrunePerTick ?? 10
|
|
67
|
+
}
|
|
68
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
69
|
+
|
|
70
|
+
// ── Reference to episodic consolidator ───────────────────
|
|
71
|
+
|
|
72
|
+
attachConsolidator( consolidator: EpisodicConsolidator ): void {
|
|
73
|
+
this._episodicConsolidator = consolidator
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Engine interface ─────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
79
|
+
const cfg = state.entities.get('engine-config-forgetting')
|
|
80
|
+
if( !cfg ) return
|
|
81
|
+
|
|
82
|
+
const p = cfg.metadata?.params as Record<string, number> | undefined
|
|
83
|
+
if( !p ) return
|
|
84
|
+
|
|
85
|
+
if( p.baseForgettingRate != null ) this._baseForgettingRate = p.baseForgettingRate
|
|
86
|
+
if( p.emotionProtection != null ) this._emotionProtection = p.emotionProtection
|
|
87
|
+
if( p.pruningThreshold != null ) this._pruningThreshold = p.pruningThreshold
|
|
88
|
+
if( p.maxPrunePerTick != null ) this._maxPrunePerTick = p.maxPrunePerTick
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
subscribes(): string[] { return ['executive.prediction.formed'] }
|
|
93
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
94
|
+
|
|
95
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
96
|
+
this._model.observe( e.type, e.salience )
|
|
97
|
+
if( e.type === 'executive.prediction.formed' ){
|
|
98
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
99
|
+
if( p.predictedDomains.includes('memory') )
|
|
100
|
+
this._model.setPrecision( 'memory.decay_rate', 1.0 + p.confidence * 0.5 )
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
snapshot(): Record<string, unknown> {
|
|
105
|
+
// FN9: previously returned {} — the salience/generative-model sub-states were
|
|
106
|
+
// accumulating mutable state invisible to the event-sourced store, so a
|
|
107
|
+
// restore/replay zeroed them out. Capture them so the curve resumes identically.
|
|
108
|
+
return {
|
|
109
|
+
model: this._model.snapshot(),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
restore( snap: Record<string, unknown> ): void {
|
|
114
|
+
if( !snap ) return
|
|
115
|
+
if( snap.model ) this._model.restore( snap.model as GenerativeModelSnapshot )
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async react(
|
|
119
|
+
delta: Duration,
|
|
120
|
+
tick: Tick,
|
|
121
|
+
state: ReadonlySimulationState,
|
|
122
|
+
context: SimulationContext
|
|
123
|
+
): Promise<EngineResult> {
|
|
124
|
+
this._readConfigFromState( state )
|
|
125
|
+
|
|
126
|
+
const commands: StateCommands = { metrics: [] }
|
|
127
|
+
if( !this._episodicConsolidator )
|
|
128
|
+
return { commands }
|
|
129
|
+
|
|
130
|
+
const
|
|
131
|
+
allEpisodes = this._episodicConsolidator.getAllEpisodes(),
|
|
132
|
+
deltaSeconds = delta / 1000,
|
|
133
|
+
isSleeping = state.metrics.get('state.sleeping') ?? 0
|
|
134
|
+
|
|
135
|
+
let decayedCount = 0
|
|
136
|
+
const toPrune: string[] = []
|
|
137
|
+
// Compute decayed activations without mutating the consolidator's live
|
|
138
|
+
// objects: those references may already be held by other engines this
|
|
139
|
+
// tick. We hand the results to the owning consolidator, which applies
|
|
140
|
+
// them as an immutable update (single writer, no read-phase reach-in).
|
|
141
|
+
const decay = new Map<string, number>()
|
|
142
|
+
|
|
143
|
+
for( let i = allEpisodes.length - 1; i >= 0; i-- ){
|
|
144
|
+
const episode = allEpisodes[i]!
|
|
145
|
+
if( !episode ) continue
|
|
146
|
+
|
|
147
|
+
// Compute effective forgetting rate
|
|
148
|
+
const
|
|
149
|
+
emotionalIntensity = this._computeEmotionalIntensity( episode ),
|
|
150
|
+
emotionProtection = emotionalIntensity * this._emotionProtection,
|
|
151
|
+
retrievalBoost = episode.retrievalCount > 0
|
|
152
|
+
? Math.min( 0.5, episode.retrievalCount * 0.1 )
|
|
153
|
+
: 0,
|
|
154
|
+
sleepProtection = isSleeping > 0 ? 0.3 : 0, // Sleep protects memories
|
|
155
|
+
effectiveRate = this._baseForgettingRate
|
|
156
|
+
* ( 1 - emotionProtection )
|
|
157
|
+
* ( 1 - retrievalBoost )
|
|
158
|
+
* ( 1 - sleepProtection )
|
|
159
|
+
|
|
160
|
+
// Compute decay (pure — no mutation of the shared episode object).
|
|
161
|
+
const nextStrength = Math.max( 0, episode.activationStrength - effectiveRate * deltaSeconds )
|
|
162
|
+
decay.set( episode.id, nextStrength )
|
|
163
|
+
decayedCount++
|
|
164
|
+
|
|
165
|
+
// Collect for pruning if below threshold — the consolidator owns the
|
|
166
|
+
// store, so it performs the actual removal after the scan.
|
|
167
|
+
if( nextStrength < this._pruningThreshold && toPrune.length < this._maxPrunePerTick )
|
|
168
|
+
toPrune.push( episode.id )
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Commit decay through the store owner (immutable replace), then evict
|
|
172
|
+
// decayed memories from the store, vector index, and sim state.
|
|
173
|
+
this._episodicConsolidator.applyDecay( decay )
|
|
174
|
+
|
|
175
|
+
let prunedCount = 0
|
|
176
|
+
if( toPrune.length > 0 ){
|
|
177
|
+
const removed = await this._episodicConsolidator.pruneEpisodes( toPrune )
|
|
178
|
+
prunedCount = removed.length
|
|
179
|
+
if( removed.length > 0 )
|
|
180
|
+
commands.delete = ( commands.delete ?? [] ).concat( removed )
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
commands.metrics!.push(
|
|
184
|
+
[ 'memory.decayed_this_tick', decayedCount ],
|
|
185
|
+
[ 'memory.pruned_this_tick', prunedCount ],
|
|
186
|
+
[ 'memory.total_episodes', allEpisodes.length - prunedCount ],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
// Phase C: publish cognitive event
|
|
190
|
+
const _bus = this._bus
|
|
191
|
+
if( _bus && decayedCount > 0 )
|
|
192
|
+
_bus.publish({ type: 'memory.decay.applied', version: 1, sourceEngine: this.name, salience: this._model.observe( 'memory.decay_rate', decayedCount ).salience, payload: { decayed: decayedCount } })
|
|
193
|
+
|
|
194
|
+
return { commands }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private _computeEmotionalIntensity( episode: EpisodicMemory ): number {
|
|
198
|
+
const values = Object.values( episode.emotionalTags )
|
|
199
|
+
if( values.length === 0 ) return 0
|
|
200
|
+
return values.reduce( ( s, v ) => s + Math.abs( v ), 0 ) / values.length
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/frustration.evaluator.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FrustrationEvaluator — detects blocked goals and repeated failures.
|
|
7
|
+
*
|
|
8
|
+
* Evaluates:
|
|
9
|
+
* - Goals stuck without progress (via goal.blocked events)
|
|
10
|
+
* - Repeated blocked events on same goal
|
|
11
|
+
* - Violated expectations (cached confidence × goal-loss signals)
|
|
12
|
+
* - Perceived unfairness (via interaction.occurred events)
|
|
13
|
+
*
|
|
14
|
+
* Produces: frustration, anger, irritability
|
|
15
|
+
*
|
|
16
|
+
* Frustration = goal blockage without clear target
|
|
17
|
+
* Anger = goal blockage with identifiable external cause
|
|
18
|
+
* Irritability = accumulated low-grade frustration lowering threshold
|
|
19
|
+
*
|
|
20
|
+
* Part of Shard 1 (Affective Layer) — hybrid: event-driven inputs, temporal react.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
Duration,
|
|
25
|
+
Tick,
|
|
26
|
+
SimulationContext,
|
|
27
|
+
ReadonlySimulationState,
|
|
28
|
+
StateCommands,
|
|
29
|
+
SimulationEvent,
|
|
30
|
+
} from '#core/types'
|
|
31
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
32
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
33
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
34
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
35
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
36
|
+
|
|
37
|
+
export interface FrustrationEvaluatorConfig {
|
|
38
|
+
/** Ticks without progress before frustration begins */
|
|
39
|
+
stuckThreshold?: number
|
|
40
|
+
/** How quickly irritability accumulates */
|
|
41
|
+
irritabilityRate?: number
|
|
42
|
+
/** Decay rate when frustration resolves */
|
|
43
|
+
decayRate?: number
|
|
44
|
+
/**
|
|
45
|
+
* Habituation rate — proportional to current irritability level.
|
|
46
|
+
* Prevents the one-way ratchet to 1.0 under chronic goal blockage.
|
|
47
|
+
* Natural ceiling: irritabilityRate / habituationRate (e.g. 0.02/0.03 ≈ 0.67).
|
|
48
|
+
*/
|
|
49
|
+
habituationRate?: number
|
|
50
|
+
bus?: CognitiveBus
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class FrustrationEvaluator implements SimulationEngine, CognitiveEngine {
|
|
54
|
+
readonly name = 'frustration-evaluator'
|
|
55
|
+
|
|
56
|
+
private _stuckThreshold: number
|
|
57
|
+
private _irritabilityRate: number
|
|
58
|
+
private _decayRate: number
|
|
59
|
+
private _habituationRate: number
|
|
60
|
+
|
|
61
|
+
// Own output — cached so react() doesn't read state.metrics
|
|
62
|
+
private _cachedFrustration: number = 0
|
|
63
|
+
private _cachedAnger: number = 0
|
|
64
|
+
private _cachedIrritability: number = 0
|
|
65
|
+
|
|
66
|
+
// Inputs from cognitive events
|
|
67
|
+
private _blockedGoals = new Map<string, { ticksStuck: number; priority: number; refreshedAt: number }>()
|
|
68
|
+
private _goalBlockedCounts = new Map<string, number>() // repeated-failure proxy
|
|
69
|
+
private _reactCount = 0 // incremented each react() for expiry
|
|
70
|
+
private _unfairnessSignal: number = 0 // from interaction.occurred
|
|
71
|
+
private _cachedConfidence: number = 0.5 // from executive.prediction.formed
|
|
72
|
+
private _cachedGoalLoss: number = 0 // future: loss.goal event
|
|
73
|
+
private _cachedDisappointment: number = 0 // future: emotion.disappointment event
|
|
74
|
+
|
|
75
|
+
private _bus: CognitiveBus | null = null
|
|
76
|
+
|
|
77
|
+
private readonly _model = new GenerativeModel()
|
|
78
|
+
|
|
79
|
+
constructor( config: FrustrationEvaluatorConfig = {} ){
|
|
80
|
+
this._bus = config.bus ?? null
|
|
81
|
+
this._stuckThreshold = config.stuckThreshold ?? 5
|
|
82
|
+
this._irritabilityRate = config.irritabilityRate ?? 0.02
|
|
83
|
+
this._decayRate = config.decayRate ?? 0.08
|
|
84
|
+
this._habituationRate = config.habituationRate ?? 0.03
|
|
85
|
+
}
|
|
86
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
87
|
+
|
|
88
|
+
// ── Engine interface ─────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
subscribes(): string[] {
|
|
91
|
+
return [
|
|
92
|
+
'executive.prediction.formed',
|
|
93
|
+
'goal.blocked',
|
|
94
|
+
'interaction.occurred',
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
99
|
+
|
|
100
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
101
|
+
this._model.observe( e.type, e.salience )
|
|
102
|
+
|
|
103
|
+
if( e.type === 'executive.prediction.formed' ){
|
|
104
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
105
|
+
if( p.predictedDomains.includes('affect') )
|
|
106
|
+
this._model.setPrecision( 'emotion.frustration', 1.0 + p.confidence * 0.5 )
|
|
107
|
+
this._cachedConfidence = p.confidence
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if( e.type === 'goal.blocked' ){
|
|
111
|
+
const p = e.payload as { goalId: string; ticksStuck: number; priority: number }
|
|
112
|
+
this._blockedGoals.set( p.goalId, { ticksStuck: p.ticksStuck, priority: p.priority, refreshedAt: this._reactCount } )
|
|
113
|
+
this._goalBlockedCounts.set( p.goalId, ( this._goalBlockedCounts.get( p.goalId ) ?? 0 ) + 1 )
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if( e.type === 'interaction.occurred' ){
|
|
117
|
+
const p = e.payload as { keid: string; valence: number; intensity: number; directedAtSelf: boolean }
|
|
118
|
+
if( p.directedAtSelf && p.valence < -0.5 )
|
|
119
|
+
this._unfairnessSignal = Math.min( 1, this._unfairnessSignal + 0.3 )
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
snapshot(): Record<string, unknown> { return {} }
|
|
124
|
+
|
|
125
|
+
async react(
|
|
126
|
+
delta: Duration,
|
|
127
|
+
_tick: Tick,
|
|
128
|
+
state: ReadonlySimulationState,
|
|
129
|
+
_context: SimulationContext
|
|
130
|
+
): Promise<EngineResult> {
|
|
131
|
+
const
|
|
132
|
+
events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
|
|
133
|
+
commands: StateCommands = { metrics: [] }
|
|
134
|
+
|
|
135
|
+
// Channel A (emotional-stability): how fast low-grade frustration snowballs into
|
|
136
|
+
// chronic irritability is read live as base ⊕ persona-prior. A Will that has shown
|
|
137
|
+
// steady affect develops a lower build-rate here, so it stays even-keeled longer —
|
|
138
|
+
// distinct from resilience's frustrationTolerance (how much is *tolerated*). Pure +
|
|
139
|
+
// deterministic; falls back to the constructor default when no seed/prior is present.
|
|
140
|
+
const frustrationCfg = readEffectiveParams( state, 'engine-config-frustration' )
|
|
141
|
+
const irritabilityRate = frustrationCfg.irritabilityRate ?? this._irritabilityRate
|
|
142
|
+
|
|
143
|
+
// Channel A (agreeableness — "yielding in conflict"): how strongly provocation
|
|
144
|
+
// (unfairness + blocked progress) turns into anger. An agreeable Will develops this
|
|
145
|
+
// DOWN — wronged, it accommodates rather than retaliates. A distinct facet from #6's
|
|
146
|
+
// warmth (reward.socialWeight): warmth = approach/connection, yielding = low antagonism.
|
|
147
|
+
const angerReactivity = frustrationCfg.angerReactivity ?? 0.7
|
|
148
|
+
|
|
149
|
+
// Channel A (resilience — recovery): how fast frustration/anger/irritability fade is
|
|
150
|
+
// refreshed each tick as base ⊕ persona-prior. A resilient Will develops a higher decay
|
|
151
|
+
// rate and so recovers from a bad patch sooner — distinct from resilience's
|
|
152
|
+
// frustrationTolerance (how much is endured, goal-manager #10) and from emotional-
|
|
153
|
+
// stability's irritabilityRate (how fast it builds, #5).
|
|
154
|
+
this._decayRate = frustrationCfg.decayRate ?? this._decayRate
|
|
155
|
+
|
|
156
|
+
// Expire blocked-goal entries that have not been refreshed for > 1.5×STUCK_THRESHOLD
|
|
157
|
+
// react() calls. goal.manager re-fires goal.blocked every STUCK_THRESHOLD ticks, so
|
|
158
|
+
// a goal that resolves (stops emitting) will expire here after ~25–30 ticks.
|
|
159
|
+
const expiryAge = Math.round( 20 * 1.5 ) // matches goal.manager._STUCK_THRESHOLD
|
|
160
|
+
for( const [ id, g ] of this._blockedGoals ){
|
|
161
|
+
if( this._reactCount - g.refreshedAt > expiryAge ){
|
|
162
|
+
this._blockedGoals.delete( id )
|
|
163
|
+
this._goalBlockedCounts.delete( id )
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this._reactCount++
|
|
168
|
+
|
|
169
|
+
const previousFrustration = this._cachedFrustration
|
|
170
|
+
const previousAnger = this._cachedAnger
|
|
171
|
+
const previousIrritability = this._cachedIrritability
|
|
172
|
+
|
|
173
|
+
const
|
|
174
|
+
goalBlockage = this._assessGoalBlockage(),
|
|
175
|
+
repeatedFailure = this._assessRepeatedFailures(),
|
|
176
|
+
violatedExpect = this._assessViolatedExpectations(),
|
|
177
|
+
unfairness = this._unfairnessSignal
|
|
178
|
+
|
|
179
|
+
// Decay unfairness signal between events
|
|
180
|
+
this._unfairnessSignal = Math.max( 0, this._unfairnessSignal - this._decayRate * 0.5 * ( delta / 1000 ) )
|
|
181
|
+
|
|
182
|
+
const frustrationSources = goalBlockage + repeatedFailure + violatedExpect + unfairness
|
|
183
|
+
|
|
184
|
+
const
|
|
185
|
+
// Frustration: blocked progress without clear external cause
|
|
186
|
+
frustration = frustrationSources > 0.01
|
|
187
|
+
? Math.min( 1, frustrationSources )
|
|
188
|
+
: Math.max( 0, previousFrustration - this._decayRate * ( delta / 1000 ) ),
|
|
189
|
+
|
|
190
|
+
// Anger: identifiable external cause for blockage
|
|
191
|
+
anger = ( goalBlockage > 0.3 && unfairness > 0.2 )
|
|
192
|
+
? Math.min( 1, ( goalBlockage + unfairness ) * angerReactivity )
|
|
193
|
+
: Math.max( 0, previousAnger - this._decayRate * 0.5 * ( delta / 1000 ) ),
|
|
194
|
+
|
|
195
|
+
// Irritability: accumulated low-grade frustration with proportional habituation.
|
|
196
|
+
// Habituation (habituationRate * currentLevel) prevents the one-way ratchet to 1.0:
|
|
197
|
+
// under sustained frustration, irritability converges to irritabilityRate/habituationRate
|
|
198
|
+
// (default ≈ 0.67) rather than locking at ceiling indefinitely.
|
|
199
|
+
irritability = frustration > 0.1
|
|
200
|
+
? Math.max( 0, Math.min( 1,
|
|
201
|
+
previousIrritability
|
|
202
|
+
+ irritabilityRate * ( delta / 1000 )
|
|
203
|
+
- this._habituationRate * previousIrritability * ( delta / 1000 )
|
|
204
|
+
))
|
|
205
|
+
: Math.max( 0, previousIrritability - this._decayRate * 2 * ( delta / 1000 ) )
|
|
206
|
+
|
|
207
|
+
// Cache for next tick
|
|
208
|
+
this._cachedFrustration = frustration
|
|
209
|
+
this._cachedAnger = anger
|
|
210
|
+
this._cachedIrritability = irritability
|
|
211
|
+
|
|
212
|
+
commands.metrics!.push(
|
|
213
|
+
[ 'emotion.frustration', frustration ],
|
|
214
|
+
[ 'emotion.anger', anger ],
|
|
215
|
+
[ 'emotion.irritability', irritability ],
|
|
216
|
+
[ 'frustration.goal_blockage', goalBlockage ],
|
|
217
|
+
[ 'frustration.repeated_failure', repeatedFailure ],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
// Anger event
|
|
221
|
+
if( anger > 0.5 )
|
|
222
|
+
events.push({
|
|
223
|
+
type: 'emotion.anger.significant',
|
|
224
|
+
source: this.name,
|
|
225
|
+
payload: { anger, frustration, unfairness },
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// Chronic frustration event
|
|
229
|
+
if( frustration > 0.6 && previousFrustration > 0.5 )
|
|
230
|
+
events.push({
|
|
231
|
+
type: 'emotion.frustration.chronic',
|
|
232
|
+
source: this.name,
|
|
233
|
+
payload: { frustration, irritability },
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
237
|
+
const _bus = this._bus
|
|
238
|
+
if( _bus && frustration > 0.5 ){
|
|
239
|
+
const predErr = this._model.observe( 'emotion.frustration', frustration )
|
|
240
|
+
if( !predErr.gated )
|
|
241
|
+
_bus.publish({ type: 'emotion.frustration.elevated', version: 1, sourceEngine: this.name, salience: Math.min(1, frustration), payload: { frustration } })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { events: events.length > 0 ? events : undefined, commands }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Internal ─────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
private _assessGoalBlockage(): number {
|
|
250
|
+
if( this._blockedGoals.size === 0 ) return 0
|
|
251
|
+
|
|
252
|
+
return Math.min( 1,
|
|
253
|
+
Array.from( this._blockedGoals.values() ).reduce( ( sum, g ) =>
|
|
254
|
+
sum + Math.min( 1, g.ticksStuck / ( this._stuckThreshold * 4 ) ) * 0.3,
|
|
255
|
+
0
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private _assessRepeatedFailures(): number {
|
|
261
|
+
const repeatedGoals = Array.from( this._goalBlockedCounts.values() )
|
|
262
|
+
.filter( count => count >= 3 )
|
|
263
|
+
|
|
264
|
+
if( repeatedGoals.length === 0 ) return 0
|
|
265
|
+
|
|
266
|
+
return Math.min( 1,
|
|
267
|
+
repeatedGoals.reduce( ( sum, count ) =>
|
|
268
|
+
sum + Math.min( 1, count / 10 ) * 0.25,
|
|
269
|
+
0
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private _assessViolatedExpectations(): number {
|
|
275
|
+
// High confidence + negative outcome = violated expectation
|
|
276
|
+
return this._cachedConfidence > 0.6
|
|
277
|
+
? this._cachedGoalLoss * this._cachedConfidence * 0.5
|
|
278
|
+
: this._cachedDisappointment * 0.3
|
|
279
|
+
}
|
|
280
|
+
}
|