@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,605 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/spaced.repetition.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SpacedRepetition — active memory maintenance via scheduled review.
|
|
7
|
+
*
|
|
8
|
+
* Implements the SM-2 algorithm (SuperMemo 2) for optimal review intervals:
|
|
9
|
+
* - Higher confidence → longer intervals between reviews
|
|
10
|
+
* - Successful review increases confidence and interval
|
|
11
|
+
* - Failed review decreases confidence and resets interval
|
|
12
|
+
*
|
|
13
|
+
* Runs independently of the forgetting curve (active vs passive processes ):
|
|
14
|
+
* - ForgettingCurve = passive decay (always happening)
|
|
15
|
+
* - SpacedRepetition = active reinforcement (periodic review)
|
|
16
|
+
*
|
|
17
|
+
* Part of Shard 2 (Memory Layer) — runs every tick, but only triggers
|
|
18
|
+
* review cycles on schedule.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { logger } from '#core/logger'
|
|
22
|
+
import type {
|
|
23
|
+
Duration,
|
|
24
|
+
Tick,
|
|
25
|
+
SimulationContext,
|
|
26
|
+
ReadonlySimulationState,
|
|
27
|
+
StateCommands,
|
|
28
|
+
} from '#core/types'
|
|
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 type { SemanticIntegrator, Belief } from '#faculties/semantic.engine'
|
|
33
|
+
import type { EpisodicConsolidator } from '#faculties/episodic.consolidator'
|
|
34
|
+
import type { ExecutiveEngine } from '#faculties/executive.engine'
|
|
35
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
36
|
+
import { SessionLogger } from '#stem/tracts/session.logger'
|
|
37
|
+
|
|
38
|
+
export interface SpacedRepetitionConfig {
|
|
39
|
+
/** Minimum ticks between review cycles */
|
|
40
|
+
reviewIntervalTicks?: number
|
|
41
|
+
/** Maximum beliefs to review per cycle */
|
|
42
|
+
maxReviewsPerCycle?: number
|
|
43
|
+
/** Minimum confidence before a belief qualifies for review */
|
|
44
|
+
minConfidenceForReview?: number
|
|
45
|
+
/** How much successful review increases confidence (0-1) */
|
|
46
|
+
successBoost?: number
|
|
47
|
+
/** How much failed review decreases confidence (0-1) */
|
|
48
|
+
failurePenalty?: number
|
|
49
|
+
/** Base interval for new beliefs (ticks) */
|
|
50
|
+
baseIntervalTicks?: number
|
|
51
|
+
/** Maximum interval cap (ticks) */
|
|
52
|
+
maxIntervalTicks?: number
|
|
53
|
+
/** Whether to surface beliefs to executive for re-evaluation */
|
|
54
|
+
executiveReviewEnabled?: boolean
|
|
55
|
+
/** Whether to actively rehearse salient episodic memories each review cycle
|
|
56
|
+
* (waking episodic spaced repetition). Default true. */
|
|
57
|
+
episodicRehearsalEnabled?: boolean
|
|
58
|
+
bus?: CognitiveBus
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ReviewRecord {
|
|
62
|
+
beliefId: string
|
|
63
|
+
/** Current interval length in ticks */
|
|
64
|
+
interval: number
|
|
65
|
+
/** Tick of the last review */
|
|
66
|
+
lastReviewedAt: Tick
|
|
67
|
+
/** Number of successful reviews in a row */
|
|
68
|
+
consecutiveSuccesses: number
|
|
69
|
+
/** Easiness factor (SM-2: 1.3 to 2.5, default 2.5) */
|
|
70
|
+
easinessFactor: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class SpacedRepetition implements SimulationEngine, CognitiveEngine {
|
|
74
|
+
readonly name = 'spaced-repetition'
|
|
75
|
+
|
|
76
|
+
private _reviewIntervalTicks: number
|
|
77
|
+
private _maxReviewsPerCycle: number
|
|
78
|
+
private _minConfidenceForReview: number
|
|
79
|
+
private _successBoost: number
|
|
80
|
+
private _failurePenalty: number
|
|
81
|
+
private _baseIntervalTicks: number
|
|
82
|
+
private _maxIntervalTicks: number
|
|
83
|
+
private _executiveReviewEnabled: boolean
|
|
84
|
+
private _episodicRehearsalEnabled: boolean
|
|
85
|
+
|
|
86
|
+
private _semanticIntegrator: SemanticIntegrator | null = null
|
|
87
|
+
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
88
|
+
private _executiveEngine: ExecutiveEngine | null = null
|
|
89
|
+
private _sessionLogger: SessionLogger | null = null
|
|
90
|
+
|
|
91
|
+
/** Review records keyed by beliefId */
|
|
92
|
+
private _reviewRecords = new Map<string, ReviewRecord>()
|
|
93
|
+
|
|
94
|
+
/** Last tick when a review cycle ran */
|
|
95
|
+
private _lastReviewCycleTick: Tick = 0
|
|
96
|
+
|
|
97
|
+
private _bus: CognitiveBus | null = null
|
|
98
|
+
private _restored = false
|
|
99
|
+
|
|
100
|
+
private readonly _model = new GenerativeModel()
|
|
101
|
+
|
|
102
|
+
constructor( config: SpacedRepetitionConfig = {} ){
|
|
103
|
+
this._bus = config.bus ?? null
|
|
104
|
+
this._reviewIntervalTicks = config.reviewIntervalTicks ?? 50
|
|
105
|
+
this._maxReviewsPerCycle = config.maxReviewsPerCycle ?? 5
|
|
106
|
+
this._minConfidenceForReview = config.minConfidenceForReview ?? 0.15
|
|
107
|
+
this._successBoost = config.successBoost ?? 0.05
|
|
108
|
+
this._failurePenalty = config.failurePenalty ?? 0.08
|
|
109
|
+
this._baseIntervalTicks = config.baseIntervalTicks ?? 10
|
|
110
|
+
this._maxIntervalTicks = config.maxIntervalTicks ?? 500
|
|
111
|
+
this._executiveReviewEnabled = config.executiveReviewEnabled ?? false
|
|
112
|
+
this._episodicRehearsalEnabled = config.episodicRehearsalEnabled ?? true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
116
|
+
attachSemanticIntegrator( integrator: SemanticIntegrator ): void { this._semanticIntegrator = integrator }
|
|
117
|
+
attachEpisodicConsolidator( consolidator: EpisodicConsolidator ): void { this._episodicConsolidator = consolidator }
|
|
118
|
+
attachExecutiveEngine( executive: ExecutiveEngine ): void { this._executiveEngine = executive }
|
|
119
|
+
attachSessionLogger( logger: SessionLogger ): void { this._sessionLogger = logger }
|
|
120
|
+
|
|
121
|
+
// ── Engine interface ─────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
124
|
+
const cfg = state.entities.get('engine-config-spaced-repetition')
|
|
125
|
+
if( !cfg ) return
|
|
126
|
+
|
|
127
|
+
const p = cfg.metadata?.params as Record<string, number | boolean> | undefined
|
|
128
|
+
if( !p ) return
|
|
129
|
+
|
|
130
|
+
if( p.reviewIntervalTicks != null ) this._reviewIntervalTicks = p.reviewIntervalTicks as number
|
|
131
|
+
if( p.maxReviewsPerCycle != null ) this._maxReviewsPerCycle = p.maxReviewsPerCycle as number
|
|
132
|
+
if( p.minConfidenceForReview != null ) this._minConfidenceForReview = p.minConfidenceForReview as number
|
|
133
|
+
if( p.successBoost != null ) this._successBoost = p.successBoost as number
|
|
134
|
+
if( p.failurePenalty != null ) this._failurePenalty = p.failurePenalty as number
|
|
135
|
+
if( p.baseIntervalTicks != null ) this._baseIntervalTicks = p.baseIntervalTicks as number
|
|
136
|
+
if( p.maxIntervalTicks != null ) this._maxIntervalTicks = p.maxIntervalTicks as number
|
|
137
|
+
if( p.executiveReviewEnabled != null ) this._executiveReviewEnabled = p.executiveReviewEnabled as boolean
|
|
138
|
+
if( p.episodicRehearsalEnabled != null ) this._episodicRehearsalEnabled = p.episodicRehearsalEnabled as boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
subscribes(): string[] {
|
|
142
|
+
return [
|
|
143
|
+
'belief.updated',
|
|
144
|
+
'executive.prediction.formed',
|
|
145
|
+
'spaced_repetition.review.completed'
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
publishes(): CognitiveEventSchema[] {
|
|
150
|
+
return [
|
|
151
|
+
{ type: 'spaced_repetition.review.due', version: 1, validate: () => null },
|
|
152
|
+
{ type: 'spaced_repetition.review.completed', version: 1, validate: () => null },
|
|
153
|
+
{ type: 'spaced_repetition.episodes.rehearsed', version: 1, validate: () => null }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
158
|
+
this._model.observe( e.type, e.salience )
|
|
159
|
+
|
|
160
|
+
switch( e.type ){
|
|
161
|
+
case 'executive.prediction.formed': {
|
|
162
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
163
|
+
if( p.predictedDomains.includes('memory') )
|
|
164
|
+
// Boost the stream this engine actually publishes salience on (the
|
|
165
|
+
// 'spaced_repetition.state.changed' event observes 'records'), so the
|
|
166
|
+
// executive's memory-precision reaches the workspace. (Was
|
|
167
|
+
// 'spaced_repetition.review_rate' — a stream never observed, so the
|
|
168
|
+
// knob was dead. See METACOGNITION_CYCLE_TODO.md 1d.)
|
|
169
|
+
this._model.setPrecision('spaced_repetition.records', 1.0 + p.confidence * 0.5 )
|
|
170
|
+
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'belief.updated': {
|
|
175
|
+
// When a new belief is formed, initialize its review record
|
|
176
|
+
const payload = e.payload as { total?: number; beliefId?: string }
|
|
177
|
+
if( payload.beliefId && !this._reviewRecords.has( payload.beliefId ) )
|
|
178
|
+
this._initializeRecord( payload.beliefId )
|
|
179
|
+
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case 'spaced_repetition.review.completed': {
|
|
184
|
+
// External review completion (e.g., from executive decision)
|
|
185
|
+
const payload = e.payload as { beliefId: string; success: boolean; tick: number }
|
|
186
|
+
if( payload.beliefId )
|
|
187
|
+
this._processReviewOutcome( payload.beliefId, payload.success, payload.tick )
|
|
188
|
+
|
|
189
|
+
break
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
snapshot(): Record<string, unknown> {
|
|
195
|
+
// FN9: capture salience and model state for deterministic replay
|
|
196
|
+
return {
|
|
197
|
+
model: this._model.snapshot(),
|
|
198
|
+
reviewRecords: Array.from( this._reviewRecords.entries() ),
|
|
199
|
+
lastReviewCycleTick: this._lastReviewCycleTick,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
restore( snap: Record<string, unknown> ): void {
|
|
204
|
+
if( !snap ) return
|
|
205
|
+
if( snap.model ) this._model.restore(snap.model as import('#cognition/generative.model').GenerativeModelSnapshot)
|
|
206
|
+
if( snap.reviewRecords ){
|
|
207
|
+
this._reviewRecords.clear()
|
|
208
|
+
for( const [ id, record ] of snap.reviewRecords as [string, ReviewRecord][])
|
|
209
|
+
this._reviewRecords.set( id, record )
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if( snap.lastReviewCycleTick != null ) this._lastReviewCycleTick = snap.lastReviewCycleTick as number
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Rehydrate review records from persisted 'spaced_repetition_record' entities.
|
|
217
|
+
* Called once on the first tick after snapshot restore.
|
|
218
|
+
*/
|
|
219
|
+
private _restoreFromState( state: ReadonlySimulationState ): void {
|
|
220
|
+
for( const entity of state.entities.values() ){
|
|
221
|
+
if( entity.type !== 'spaced_repetition_record') continue
|
|
222
|
+
if( this._reviewRecords.has( entity.id ) ) continue
|
|
223
|
+
|
|
224
|
+
const m = entity.metadata ?? {}
|
|
225
|
+
this._reviewRecords.set( entity.id, {
|
|
226
|
+
beliefId: entity.id,
|
|
227
|
+
interval: (m['interval'] as number) ?? this._baseIntervalTicks,
|
|
228
|
+
lastReviewedAt: (m['lastReviewedAt'] as number) ?? 0,
|
|
229
|
+
consecutiveSuccesses: (m['consecutiveSuccesses'] as number) ?? 0,
|
|
230
|
+
easinessFactor: (m['easinessFactor'] as number) ?? 2.5,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
logger.info(`[spaced-repetition] restored ${this._reviewRecords.size} review records from snapshot`)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async react(
|
|
238
|
+
delta: Duration,
|
|
239
|
+
tick: Tick,
|
|
240
|
+
state: ReadonlySimulationState,
|
|
241
|
+
context: SimulationContext
|
|
242
|
+
): Promise<EngineResult> {
|
|
243
|
+
this._readConfigFromState( state )
|
|
244
|
+
|
|
245
|
+
// On first tick after snapshot restore, rehydrate review records
|
|
246
|
+
if( !this._restored ){
|
|
247
|
+
this._restoreFromState( state )
|
|
248
|
+
this._restored = true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const commands: StateCommands = { set: [], metrics: [] }
|
|
252
|
+
|
|
253
|
+
// Check if it's time for a review cycle
|
|
254
|
+
const ticksSinceLastCycle = tick - this._lastReviewCycleTick
|
|
255
|
+
if( ticksSinceLastCycle >= this._reviewIntervalTicks ){
|
|
256
|
+
await this._runReviewCycle( tick, commands )
|
|
257
|
+
// Episodic rehearsal runs on the same cadence, but only while awake — the
|
|
258
|
+
// DreamSimulator owns sleep-time reactivation, so gating here avoids
|
|
259
|
+
// double-boosting the same memories.
|
|
260
|
+
const sleeping = ( state.metrics.get('state.sleeping') ?? 0 ) > 0
|
|
261
|
+
if( this._episodicRehearsalEnabled && !sleeping )
|
|
262
|
+
this._rehearseEpisodes( tick, commands )
|
|
263
|
+
this._lastReviewCycleTick = tick
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Persist all review records
|
|
267
|
+
for( const [ beliefId, record ] of this._reviewRecords.entries() )
|
|
268
|
+
commands.set!.push({
|
|
269
|
+
id: beliefId,
|
|
270
|
+
type: 'spaced_repetition_record',
|
|
271
|
+
updatedAt: tick,
|
|
272
|
+
metadata: {
|
|
273
|
+
interval: record.interval,
|
|
274
|
+
lastReviewedAt: record.lastReviewedAt,
|
|
275
|
+
consecutiveSuccesses: record.consecutiveSuccesses,
|
|
276
|
+
easinessFactor: record.easinessFactor
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
if( !commands.metrics ) commands.metrics = []
|
|
281
|
+
commands.metrics.push(
|
|
282
|
+
[ 'memory.spaced_repetition_records', this._reviewRecords.size ],
|
|
283
|
+
[ 'memory.spaced_repetition_last_cycle', this._lastReviewCycleTick ]
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
287
|
+
const _bus = this._bus
|
|
288
|
+
if( _bus && this._reviewRecords.size > 0 ){
|
|
289
|
+
const predErr = this._model.observe('spaced_repetition.records', this._reviewRecords.size )
|
|
290
|
+
if( !predErr.gated )
|
|
291
|
+
_bus.publish({
|
|
292
|
+
type: 'spaced_repetition.state.changed',
|
|
293
|
+
version: 1,
|
|
294
|
+
sourceEngine: this.name,
|
|
295
|
+
salience: Math.max(0.2, predErr.salience),
|
|
296
|
+
payload: { totalRecords: this._reviewRecords.size }
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { commands }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── Review cycle ─────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
private async _runReviewCycle( tick: Tick, commands: StateCommands ): Promise<void> {
|
|
306
|
+
if( !this._semanticIntegrator ) return
|
|
307
|
+
|
|
308
|
+
const allBeliefs = this._semanticIntegrator.getBeliefs()
|
|
309
|
+
const dueForReview: Belief[] = []
|
|
310
|
+
|
|
311
|
+
for( const belief of allBeliefs ){
|
|
312
|
+
if( belief.confidence < this._minConfidenceForReview ) continue
|
|
313
|
+
|
|
314
|
+
const record = this._reviewRecords.get( belief.id )
|
|
315
|
+
if( !record ){
|
|
316
|
+
this._initializeRecord( belief.id, tick )
|
|
317
|
+
// New records are due immediately for first review
|
|
318
|
+
dueForReview.push( belief )
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const timeSinceLastReview = tick - record.lastReviewedAt
|
|
323
|
+
if( timeSinceLastReview >= record.interval )
|
|
324
|
+
dueForReview.push( belief )
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Sort by priority: lowest confidence first (most at risk)
|
|
328
|
+
dueForReview.sort( ( a, b ) => a.confidence - b.confidence )
|
|
329
|
+
|
|
330
|
+
const toReview = dueForReview.slice( 0, this._maxReviewsPerCycle )
|
|
331
|
+
if( toReview.length === 0 ) return
|
|
332
|
+
|
|
333
|
+
logger.info(`[spaced-repetition] reviewing ${toReview.length} beliefs (${dueForReview.length} due total)`)
|
|
334
|
+
|
|
335
|
+
// Publish due events for external handling
|
|
336
|
+
const _bus = this._bus
|
|
337
|
+
if( _bus )
|
|
338
|
+
for( const belief of toReview )
|
|
339
|
+
_bus.publish({
|
|
340
|
+
type: 'spaced_repetition.review.due',
|
|
341
|
+
version: 1,
|
|
342
|
+
sourceEngine: this.name,
|
|
343
|
+
salience: Math.max(0.3, this._model.observe('spaced_repetition.review_due', 1).salience),
|
|
344
|
+
payload: {
|
|
345
|
+
beliefId: belief.id,
|
|
346
|
+
statement: belief.statement,
|
|
347
|
+
currentConfidence: belief.confidence,
|
|
348
|
+
interval: this._reviewRecords.get(belief.id)?.interval ?? this._baseIntervalTicks,
|
|
349
|
+
tick,
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Surface to executive for re-evaluation via introspection
|
|
354
|
+
// This would require extending ExecutiveEngine to accept review tasks
|
|
355
|
+
if( this._executiveReviewEnabled && this._executiveEngine )
|
|
356
|
+
await this._surfaceToExecutive( toReview, tick )
|
|
357
|
+
|
|
358
|
+
// Automatic reinforcement: assume successful recall
|
|
359
|
+
else
|
|
360
|
+
for( const belief of toReview )
|
|
361
|
+
this._processReviewOutcome(belief.id, true, tick, commands)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── Episodic rehearsal (waking episodic spaced repetition) ───
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Active waking rehearsal of salient episodic memories.
|
|
368
|
+
*
|
|
369
|
+
* Beliefs get scheduled SM-2 review above; episodes previously had only
|
|
370
|
+
* passive decay (ForgettingCurve) plus opportunistic sleep reactivation
|
|
371
|
+
* (DreamSimulator), so an emotionally significant memory the Will happened not
|
|
372
|
+
* to recall would simply fade. This selects the most worth-keeping episodes
|
|
373
|
+
* that are *due* — salient, not retrieved within a review interval, still
|
|
374
|
+
* above the pruning floor — and marks them retrieved. markRetrieved both nudges
|
|
375
|
+
* activation and (via retrievalCount) unlocks the ForgettingCurve's
|
|
376
|
+
* retrievalBoost, so rehearsed memories decay slower and persist.
|
|
377
|
+
*/
|
|
378
|
+
private _rehearseEpisodes( tick: Tick, commands: StateCommands ): void {
|
|
379
|
+
const consolidator = this._episodicConsolidator
|
|
380
|
+
if( !consolidator ) return
|
|
381
|
+
|
|
382
|
+
const episodes = consolidator.getAllEpisodes()
|
|
383
|
+
if( episodes.length === 0 ) return
|
|
384
|
+
|
|
385
|
+
const due: Array<{ id: string; score: number }> = []
|
|
386
|
+
for( const ep of episodes ){
|
|
387
|
+
// Leave the nearly-forgotten alone — let the ForgettingCurve retire them.
|
|
388
|
+
if( ep.activationStrength < 0.05 ) continue
|
|
389
|
+
// Due = never retrieved, or not retrieved within a review interval.
|
|
390
|
+
const since = ep.lastRetrievedAt === null ? Infinity : tick - ep.lastRetrievedAt
|
|
391
|
+
if( since < this._reviewIntervalTicks ) continue
|
|
392
|
+
|
|
393
|
+
// Prioritise emotionally significant memories that are starting to fade.
|
|
394
|
+
const emotionalIntensity = this._episodeEmotionalIntensity( ep.emotionalTags )
|
|
395
|
+
due.push( { id: ep.id, score: emotionalIntensity * 0.6 + ( 1 - ep.activationStrength ) * 0.4 } )
|
|
396
|
+
}
|
|
397
|
+
if( due.length === 0 ) return
|
|
398
|
+
|
|
399
|
+
// Highest score first; tie-break by id so rehearsal order is replay-stable.
|
|
400
|
+
due.sort( ( a, b ) => ( b.score - a.score ) || ( a.id < b.id ? -1 : 1 ) )
|
|
401
|
+
const rehearsed = due.slice( 0, this._maxReviewsPerCycle )
|
|
402
|
+
|
|
403
|
+
for( const { id } of rehearsed )
|
|
404
|
+
consolidator.markRetrieved( id, tick )
|
|
405
|
+
|
|
406
|
+
commands.metrics ??= []
|
|
407
|
+
commands.metrics.push( [ 'memory.episodic_rehearsed', rehearsed.length ] )
|
|
408
|
+
|
|
409
|
+
this._bus?.publish({
|
|
410
|
+
type: 'spaced_repetition.episodes.rehearsed',
|
|
411
|
+
version: 1,
|
|
412
|
+
sourceEngine: this.name,
|
|
413
|
+
salience: Math.max( 0.3, this._model.observe('spaced_repetition.episodic_rehearsal', rehearsed.length ).salience ),
|
|
414
|
+
payload: { rehearsed: rehearsed.length }
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private _episodeEmotionalIntensity( emotionalTags: Record<string, number> ): number {
|
|
419
|
+
const v = Object.values( emotionalTags )
|
|
420
|
+
if( v.length === 0 ) return 0
|
|
421
|
+
return v.reduce( ( s, x ) => s + Math.abs( x ), 0 ) / v.length
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private _initializeRecord( beliefId: string, tick: Tick = 0 ): void {
|
|
425
|
+
this._reviewRecords.set( beliefId, {
|
|
426
|
+
beliefId,
|
|
427
|
+
interval: this._baseIntervalTicks,
|
|
428
|
+
lastReviewedAt: tick,
|
|
429
|
+
consecutiveSuccesses: 0,
|
|
430
|
+
easinessFactor: 2.5
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private _processReviewOutcome(
|
|
435
|
+
beliefId: string,
|
|
436
|
+
success: boolean,
|
|
437
|
+
tick: Tick,
|
|
438
|
+
commands?: StateCommands
|
|
439
|
+
): void {
|
|
440
|
+
const record = this._reviewRecords.get( beliefId )
|
|
441
|
+
if( !record ){
|
|
442
|
+
this._initializeRecord( beliefId, tick )
|
|
443
|
+
// First review always considered success
|
|
444
|
+
this._processReviewOutcome( beliefId, true, tick, commands )
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const belief = this._semanticIntegrator?.getBeliefs().find( b => b.id === beliefId )
|
|
449
|
+
if( !belief ) return
|
|
450
|
+
|
|
451
|
+
const prevConfidence = belief.confidence
|
|
452
|
+
|
|
453
|
+
if( success ){
|
|
454
|
+
// SM-2 algorithm for interval calculation
|
|
455
|
+
let newInterval: number
|
|
456
|
+
if( record.consecutiveSuccesses === 0 ) newInterval = this._baseIntervalTicks
|
|
457
|
+
else if( record.consecutiveSuccesses === 1 ) newInterval = this._baseIntervalTicks * 2
|
|
458
|
+
else newInterval = Math.round( record.interval * record.easinessFactor )
|
|
459
|
+
|
|
460
|
+
record.interval = Math.min( newInterval, this._maxIntervalTicks )
|
|
461
|
+
record.consecutiveSuccesses++
|
|
462
|
+
|
|
463
|
+
// Easiness factor adjustment (SM-2: EF' = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02)))
|
|
464
|
+
// Quality 5 (perfect recall) increases EF, lower qualities decrease
|
|
465
|
+
const quality = 5 // Assume perfect recall for automatic reinforcement
|
|
466
|
+
record.easinessFactor = Math.max( 1.3, record.easinessFactor + ( 0.1 - ( 5 - quality) * 0.08 ) )
|
|
467
|
+
|
|
468
|
+
// Increase confidence
|
|
469
|
+
const newConfidence = Math.min( 1, belief.confidence + this._successBoost )
|
|
470
|
+
belief.confidence = newConfidence
|
|
471
|
+
belief.lastUpdatedAt = tick
|
|
472
|
+
|
|
473
|
+
// Record history if SemanticIntegrator exposes it
|
|
474
|
+
this._recordBeliefHistory( belief, tick, prevConfidence, 'spaced_repetition_reinforced' )
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// Failed review — reset interval and decrease confidence
|
|
478
|
+
record.interval = this._baseIntervalTicks
|
|
479
|
+
record.consecutiveSuccesses = 0
|
|
480
|
+
record.easinessFactor = Math.max( 1.3, record.easinessFactor - 0.2 )
|
|
481
|
+
|
|
482
|
+
const newConfidence = Math.max( 0.1, belief.confidence - this._failurePenalty )
|
|
483
|
+
belief.confidence = newConfidence
|
|
484
|
+
belief.lastUpdatedAt = tick
|
|
485
|
+
|
|
486
|
+
this._recordBeliefHistory( belief, tick, prevConfidence, 'spaced_repetition_failed' )
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
record.lastReviewedAt = tick
|
|
490
|
+
|
|
491
|
+
this._bus?.publish({
|
|
492
|
+
type: 'spaced_repetition.review.completed',
|
|
493
|
+
version: 1,
|
|
494
|
+
sourceEngine: this.name,
|
|
495
|
+
salience: Math.max( 0.4, Math.abs( belief.confidence - prevConfidence ) ),
|
|
496
|
+
payload: {
|
|
497
|
+
beliefId,
|
|
498
|
+
success,
|
|
499
|
+
oldConfidence: prevConfidence,
|
|
500
|
+
newConfidence: belief.confidence,
|
|
501
|
+
newInterval: record.interval,
|
|
502
|
+
tick
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Record belief history entry (mirrors SemanticIntegrator._recordHistory).
|
|
509
|
+
* This requires SemanticIntegrator to expose a public method for external updates.
|
|
510
|
+
*/
|
|
511
|
+
private _recordBeliefHistory(
|
|
512
|
+
belief: Belief,
|
|
513
|
+
tick: Tick,
|
|
514
|
+
prevConfidence: number,
|
|
515
|
+
cause: string
|
|
516
|
+
): void {
|
|
517
|
+
// This would call into SemanticIntegrator if it exposes a method
|
|
518
|
+
// For now, we rely on the integrator's own decay/update mechanisms
|
|
519
|
+
this._sessionLogger?.write({
|
|
520
|
+
type: 'belief.spaced_repetition',
|
|
521
|
+
tick,
|
|
522
|
+
beliefId: belief.id,
|
|
523
|
+
statement: belief.statement.slice( 0, 100 ),
|
|
524
|
+
oldConfidence: prevConfidence,
|
|
525
|
+
newConfidence: belief.confidence,
|
|
526
|
+
cause
|
|
527
|
+
} as any)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private async _surfaceToExecutive( beliefs: Belief[], tick: Tick ): Promise<void> {
|
|
531
|
+
if( !this._executiveEngine ) return
|
|
532
|
+
|
|
533
|
+
// Group beliefs by category for efficient processing
|
|
534
|
+
const byCategory = new Map<string, Belief[]>()
|
|
535
|
+
for( const belief of beliefs ){
|
|
536
|
+
const cat = byCategory.get( belief.category ) ?? []
|
|
537
|
+
|
|
538
|
+
cat.push( belief )
|
|
539
|
+
byCategory.set( belief.category, cat )
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// For each category, create a review prompt for the executive
|
|
543
|
+
// This would require extending ExecutiveEngine to accept external review requests
|
|
544
|
+
// via a new cognitive event type
|
|
545
|
+
|
|
546
|
+
this._bus?.publish({
|
|
547
|
+
type: 'executive.review.requested',
|
|
548
|
+
version: 1,
|
|
549
|
+
sourceEngine: this.name,
|
|
550
|
+
salience: 0.7,
|
|
551
|
+
payload: {
|
|
552
|
+
beliefsToReview: beliefs.map( b => ({
|
|
553
|
+
id: b.id,
|
|
554
|
+
statement: b.statement,
|
|
555
|
+
category: b.category,
|
|
556
|
+
currentConfidence: b.confidence,
|
|
557
|
+
})),
|
|
558
|
+
tick
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ── Public API ───────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Manually trigger a review for a specific belief.
|
|
567
|
+
* Used when the executive decides to re-evaluate a belief.
|
|
568
|
+
*/
|
|
569
|
+
requestReview( beliefId: string, tick: Tick ): void {
|
|
570
|
+
const record = this._reviewRecords.get( beliefId )
|
|
571
|
+
if( record ){
|
|
572
|
+
// Reset interval to force immediate review
|
|
573
|
+
record.interval = 0
|
|
574
|
+
record.lastReviewedAt = tick - this._reviewIntervalTicks
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Get the current review status for a belief.
|
|
580
|
+
*/
|
|
581
|
+
getReviewStatus( beliefId: string ): ReviewRecord | undefined {
|
|
582
|
+
return this._reviewRecords.get( beliefId )
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get all beliefs currently due for review (without triggering cycle).
|
|
587
|
+
*/
|
|
588
|
+
getDueForReview(tick: Tick ): Array<{ belief: Belief; record: ReviewRecord }> {
|
|
589
|
+
if( !this._semanticIntegrator ) return []
|
|
590
|
+
|
|
591
|
+
const result: Array<{ belief: Belief; record: ReviewRecord }> = []
|
|
592
|
+
const allBeliefs = this._semanticIntegrator.getBeliefs()
|
|
593
|
+
|
|
594
|
+
for( const belief of allBeliefs ){
|
|
595
|
+
const record = this._reviewRecords.get( belief.id )
|
|
596
|
+
if( !record ) continue
|
|
597
|
+
|
|
598
|
+
const timeSinceLastReview = tick - record.lastReviewedAt
|
|
599
|
+
if( timeSinceLastReview >= record.interval )
|
|
600
|
+
result.push({ belief, record })
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return result.sort( ( a, b ) => a.belief.confidence - b.belief.confidence )
|
|
604
|
+
}
|
|
605
|
+
}
|