@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,750 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/semantic.engine/integrator.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SemanticIntegrator — satellite mode.
|
|
7
|
+
*
|
|
8
|
+
* Integrates beliefs from the ExecutiveEngine and performs heuristic
|
|
9
|
+
* pattern detection for simple statistical patterns.
|
|
10
|
+
* No longer makes its own LLM calls.
|
|
11
|
+
*
|
|
12
|
+
* UPGRADE: Uses semanticQuery from EpisodicConsolidator for more
|
|
13
|
+
* intelligent pattern detection when vector memory is available.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { logger } from '#core/logger'
|
|
17
|
+
import type {
|
|
18
|
+
Duration, Tick, SimulationContext,
|
|
19
|
+
ReadonlySimulationState, StateCommands,
|
|
20
|
+
} from '#core/types'
|
|
21
|
+
import type { SimulationEngine, EngineResult } from '#cognition/types'
|
|
22
|
+
import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
|
|
23
|
+
import type { SessionLogger } from '#stem/tracts/session.logger'
|
|
24
|
+
import type { CognitiveEngine } from '#cognition/types'
|
|
25
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
26
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
27
|
+
import { EVIDENCE_TO_COUNT } from '#faculties/executive.engine/commands'
|
|
28
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
29
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
30
|
+
import { _STOP_WORDS, type Belief, type BeliefHistoryEntry, type SemanticIntegratorConfig } from '#faculties/semantic.engine/types'
|
|
31
|
+
import { SemanticClustering } from '#faculties/semantic.engine/clustering'
|
|
32
|
+
|
|
33
|
+
// Re-export the belief types so consumers that import SemanticIntegrator from
|
|
34
|
+
// this module can also pull Belief/BeliefHistoryEntry from the same path.
|
|
35
|
+
export type { Belief, BeliefHistoryEntry } from './types'
|
|
36
|
+
|
|
37
|
+
export class SemanticIntegrator implements SimulationEngine, CognitiveEngine {
|
|
38
|
+
readonly name = 'semantic-integrator'
|
|
39
|
+
|
|
40
|
+
private _minIntervalTicks: number
|
|
41
|
+
private _minNewEpisodes: number
|
|
42
|
+
private _maxBeliefs: number
|
|
43
|
+
private _beliefStalenessThreshold: number
|
|
44
|
+
private _beliefDecayRate: number
|
|
45
|
+
private _semanticSimilarityThreshold: number
|
|
46
|
+
private _semanticQueryLimit: number
|
|
47
|
+
|
|
48
|
+
private _beliefs: Belief[] = []
|
|
49
|
+
private _lastIntegrationTick: number = 0
|
|
50
|
+
private _episodeCountAtLastIntegration: number = 0
|
|
51
|
+
private _semanticClustering: SemanticClustering | null = null
|
|
52
|
+
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
53
|
+
private _sessionLogger: SessionLogger | null = null
|
|
54
|
+
private _restored = false
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Monotonic suffix counter for belief ids. Replaces Math.random() AND the
|
|
58
|
+
* former `Date.now()` component so the same seed+inputs reproduce the same
|
|
59
|
+
* belief ids on replay (R2). Combined with the id prefix it is unique per
|
|
60
|
+
* engine instance, which is sufficient for belief ids.
|
|
61
|
+
*/
|
|
62
|
+
private _idSeq = 0
|
|
63
|
+
|
|
64
|
+
private _bus: CognitiveBus | null = null
|
|
65
|
+
|
|
66
|
+
private readonly _model = new GenerativeModel()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
constructor( config: SemanticIntegratorConfig = {} ){
|
|
70
|
+
this._bus = config.bus ?? null
|
|
71
|
+
this._minIntervalTicks = config.minIntervalTicks ?? 30
|
|
72
|
+
this._minNewEpisodes = config.minNewEpisodes ?? 10
|
|
73
|
+
this._maxBeliefs = config.maxBeliefs ?? 500
|
|
74
|
+
this._beliefStalenessThreshold = config.beliefStalenessThreshold ?? 300
|
|
75
|
+
this._beliefDecayRate = config.beliefDecayRate ?? 0.001
|
|
76
|
+
this._semanticSimilarityThreshold = config.semanticSimilarityThreshold ?? 0.65
|
|
77
|
+
this._semanticQueryLimit = config.semanticQueryLimit ?? 20
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
81
|
+
attachConsolidator( consolidator: EpisodicConsolidator ): void { this._episodicConsolidator = consolidator }
|
|
82
|
+
attachSemanticClustering( ct: SemanticClustering ): void { this._semanticClustering = ct }
|
|
83
|
+
attachSessionLogger( logger: SessionLogger | null ): void { this._sessionLogger = logger }
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Called by ExecutiveEngine when it produces new beliefs.
|
|
87
|
+
* Confidence is capped by evidence count before integration — prevents the
|
|
88
|
+
* executive from asserting high-certainty beliefs from thin episodic support.
|
|
89
|
+
*/
|
|
90
|
+
integrateExecutiveBelief( belief: Belief, tick?: number, cause = 'executive' ): void {
|
|
91
|
+
const capped = this._capConfidenceByEvidence( belief.confidence, belief.supportingEpisodes )
|
|
92
|
+
const result = this._integrateBelief({ ...belief, confidence: capped }, cause )
|
|
93
|
+
|
|
94
|
+
this._sessionLogger?.write({
|
|
95
|
+
type: 'belief.integrate',
|
|
96
|
+
tick,
|
|
97
|
+
id: result.id,
|
|
98
|
+
statement: result.statement,
|
|
99
|
+
category: result.category,
|
|
100
|
+
rawConfidence: belief.confidence,
|
|
101
|
+
cappedConfidence: capped,
|
|
102
|
+
storedConfidence: result.confidence,
|
|
103
|
+
supportingEpisodes: belief.supportingEpisodes,
|
|
104
|
+
tags: belief.tags,
|
|
105
|
+
merged: result.id !== belief.id,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Evidence-based confidence ceiling.
|
|
111
|
+
* Thin evidence cannot support high confidence regardless of how the executive reasons.
|
|
112
|
+
* Mirrors how scientific confidence scales with replications, not just reasoning quality.
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* Rehydrate _beliefs from 'belief' entities in state.
|
|
116
|
+
* Called once on the first tick after a snapshot restore so that beliefs
|
|
117
|
+
* formed in previous sessions survive a server restart.
|
|
118
|
+
*/
|
|
119
|
+
private _restoreFromState( state: ReadonlySimulationState ): void {
|
|
120
|
+
for( const entity of state.entities.values() ){
|
|
121
|
+
if( entity.type !== 'belief' ) continue
|
|
122
|
+
if( this._beliefs.some( b => b.id === entity.id ) ) continue
|
|
123
|
+
|
|
124
|
+
const m = entity.metadata ?? {}
|
|
125
|
+
// Copy the arrays out of the (frozen) restored state — the integrator mutates them
|
|
126
|
+
// in place later (_recordHistory), which would crash on the frozen originals.
|
|
127
|
+
this._beliefs.push({
|
|
128
|
+
id: entity.id,
|
|
129
|
+
statement: ( m['statement'] as string ) ?? '',
|
|
130
|
+
category: ( m['category'] as Belief['category'] ) ?? 'world_fact',
|
|
131
|
+
confidence: ( m['confidence'] as number ) ?? 0.5,
|
|
132
|
+
supportingEpisodes: ( m['supportingEpisodes'] as number ) ?? 0,
|
|
133
|
+
lastUpdatedAt: ( m['lastUpdatedAt'] as number ) ?? 0,
|
|
134
|
+
tags: [ ...( ( m['tags'] as string[] ) ?? [] ) ],
|
|
135
|
+
history: [ ...( ( m['history'] as BeliefHistoryEntry[] ) ?? [] ) ],
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private _capConfidenceByEvidence( confidence: number, episodes: number ): number {
|
|
141
|
+
if( episodes < 2 ) return Math.min( confidence, 0.60 )
|
|
142
|
+
if( episodes < 5 ) return Math.min( confidence, 0.72 )
|
|
143
|
+
if( episodes < 10 ) return Math.min( confidence, 0.82 )
|
|
144
|
+
if( episodes < 20 ) return Math.min( confidence, 0.90 )
|
|
145
|
+
return Math.min( confidence, 0.97 )
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
subscribes(): string[] {
|
|
149
|
+
return [
|
|
150
|
+
'executive.prediction.formed',
|
|
151
|
+
'executive.facet.progress'
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
155
|
+
|
|
156
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
157
|
+
this._model.observe( e.type, e.salience )
|
|
158
|
+
|
|
159
|
+
switch( e.type ){
|
|
160
|
+
case 'executive.prediction.formed': {
|
|
161
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
162
|
+
if( p.predictedDomains.includes('memory') )
|
|
163
|
+
this._model.setPrecision( 'belief.count', 1.0 + p.confidence * 0.5 )
|
|
164
|
+
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
case 'executive.facet.progress': {
|
|
168
|
+
const payload = e.payload as {
|
|
169
|
+
facetId?: string
|
|
170
|
+
planId?: string
|
|
171
|
+
goalProgress?: number
|
|
172
|
+
newGoals?: Array<{
|
|
173
|
+
description: string; priority: number; tags: string[]
|
|
174
|
+
completionType: string; completionCondition?: string
|
|
175
|
+
}>
|
|
176
|
+
goalsToAbandon?: Array<{ goalId: string; reason: string }>
|
|
177
|
+
newBeliefs?: Array<{
|
|
178
|
+
statement: string; category: string; confidence: number
|
|
179
|
+
evidence: 'single_observation' | 'recurring_pattern' | 'strong_pattern'; tags: string[]
|
|
180
|
+
}>
|
|
181
|
+
knownEntityUpdates?: Array<{ keid: string; name?: string; learned?: string[]; feeling?: number }>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Forward new beliefs to SemanticIntegrator
|
|
185
|
+
if( payload.newBeliefs )
|
|
186
|
+
for( const nb of payload.newBeliefs )
|
|
187
|
+
this.integrateExecutiveBelief({
|
|
188
|
+
id: `belief-facet-${( this._idSeq++ ).toString( 36 )}`,
|
|
189
|
+
statement: nb.statement,
|
|
190
|
+
category: nb.category as any,
|
|
191
|
+
confidence: nb.confidence,
|
|
192
|
+
supportingEpisodes: EVIDENCE_TO_COUNT[ nb.evidence ] ?? 1,
|
|
193
|
+
// lastUpdatedAt is a sim Tick (used in `tick - belief.lastUpdatedAt`
|
|
194
|
+
// staleness math), so use the last integration tick, not wall-clock (R2).
|
|
195
|
+
lastUpdatedAt: this._lastIntegrationTick,
|
|
196
|
+
tags: nb.tags
|
|
197
|
+
}, undefined, 'facet')
|
|
198
|
+
|
|
199
|
+
// Facts the facet learned about an other → keid-tagged social beliefs (the
|
|
200
|
+
// conversation-side analogue of the master's buildStateCommands path).
|
|
201
|
+
if( payload.knownEntityUpdates )
|
|
202
|
+
for( const u of payload.knownEntityUpdates )
|
|
203
|
+
for( const fact of u.learned ?? [] )
|
|
204
|
+
if( fact && u.keid && u.keid !== 'agent-self' )
|
|
205
|
+
this.integrateExecutiveBelief({
|
|
206
|
+
id: `belief-ke-facet-${( this._idSeq++ ).toString( 36 )}`,
|
|
207
|
+
statement: fact,
|
|
208
|
+
category: 'social_belief' as any,
|
|
209
|
+
confidence: 0.7,
|
|
210
|
+
supportingEpisodes: 1,
|
|
211
|
+
lastUpdatedAt: this._lastIntegrationTick,
|
|
212
|
+
tags: [ 'social', 'known-entity', `keid:${u.keid}` ]
|
|
213
|
+
}, undefined, 'facet')
|
|
214
|
+
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
snapshot(): Record<string, unknown> { return {} }
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Effective config = base engine-config-semantic ⊕ persona-prior (single-source).
|
|
224
|
+
* No-op at boot: mirror params equal the constructor defaults (reconciled in #83).
|
|
225
|
+
*/
|
|
226
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
227
|
+
const p = readEffectiveParams( state, 'engine-config-semantic' )
|
|
228
|
+
if( p.minIntervalTicks != null ) this._minIntervalTicks = p.minIntervalTicks
|
|
229
|
+
if( p.minNewEpisodes != null ) this._minNewEpisodes = p.minNewEpisodes
|
|
230
|
+
if( p.maxBeliefs != null ) this._maxBeliefs = p.maxBeliefs
|
|
231
|
+
if( p.beliefStalenessThreshold != null ) this._beliefStalenessThreshold = p.beliefStalenessThreshold
|
|
232
|
+
if( p.beliefDecayRate != null ) this._beliefDecayRate = p.beliefDecayRate
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async react(
|
|
236
|
+
_delta: Duration,
|
|
237
|
+
tick: Tick,
|
|
238
|
+
state: ReadonlySimulationState,
|
|
239
|
+
context: SimulationContext
|
|
240
|
+
): Promise<EngineResult> {
|
|
241
|
+
// Effective config = base engine-config-semantic ⊕ persona-prior (single-source).
|
|
242
|
+
this._readConfigFromState( state )
|
|
243
|
+
|
|
244
|
+
// On the very first tick (including after snapshot restore), rehydrate
|
|
245
|
+
// the in-memory _beliefs array from persisted 'belief' state entities.
|
|
246
|
+
// Without this, every restart begins with zero beliefs regardless of snapshot.
|
|
247
|
+
if( !this._restored ){
|
|
248
|
+
this._restoreFromState( state )
|
|
249
|
+
this._restored = true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const commands: StateCommands = { set: [], metrics: [] }
|
|
253
|
+
|
|
254
|
+
// Executive beliefs are integrated in real-time via integrateExecutiveBelief()
|
|
255
|
+
// called by the ExecutiveEngine's onReasoningComplete().
|
|
256
|
+
// Here we just handle heuristic pattern detection and persistence.
|
|
257
|
+
|
|
258
|
+
// Heuristic pattern detection — upgraded to use semantic query
|
|
259
|
+
if( this._episodicConsolidator && this._shouldRunHeuristic( tick ) ){
|
|
260
|
+
const newBeliefs = await this._heuristicPatternDetection( tick, state )
|
|
261
|
+
for( const belief of newBeliefs )
|
|
262
|
+
this._integrateBelief( belief, 'heuristic' )
|
|
263
|
+
|
|
264
|
+
this._lastIntegrationTick = tick
|
|
265
|
+
if( this._episodicConsolidator )
|
|
266
|
+
this._episodeCountAtLastIntegration = this._episodicConsolidator.getAllEpisodes().length
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Staleness decay — beliefs not reinforced for a long time lose confidence gradually.
|
|
270
|
+
// This prevents early high-confidence beliefs from dominating forever and forces
|
|
271
|
+
// the executive to re-affirm important beliefs to keep them strong.
|
|
272
|
+
//
|
|
273
|
+
// Guard: only decay a belief once at least one consolidation cycle has run
|
|
274
|
+
// *after* the belief was created or last updated. This ensures every belief
|
|
275
|
+
// gets at least one reinforcement opportunity before erosion begins. Beliefs
|
|
276
|
+
// from a restored session are protected until the first heuristic cycle fires
|
|
277
|
+
// this session (i.e. _lastIntegrationTick advances past their lastUpdatedAt).
|
|
278
|
+
for( const belief of this._beliefs ){
|
|
279
|
+
const staleness = tick - belief.lastUpdatedAt
|
|
280
|
+
|
|
281
|
+
const hadOpportunity = this._lastIntegrationTick > belief.lastUpdatedAt
|
|
282
|
+
if( staleness > this._beliefStalenessThreshold && hadOpportunity ){
|
|
283
|
+
const prev = belief.confidence
|
|
284
|
+
belief.confidence = Math.max( 0.10, belief.confidence - this._beliefDecayRate )
|
|
285
|
+
if( belief.confidence !== prev )
|
|
286
|
+
SemanticIntegrator._recordHistory( belief, tick, prev, 'decayed' )
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Prune beliefs that have decayed to near-zero
|
|
291
|
+
this._beliefs = this._beliefs.filter( b => b.confidence > 0.12 )
|
|
292
|
+
|
|
293
|
+
// Persist all beliefs
|
|
294
|
+
for( const belief of this._beliefs )
|
|
295
|
+
commands.set!.push({
|
|
296
|
+
id: belief.id,
|
|
297
|
+
type: 'belief',
|
|
298
|
+
metadata: {
|
|
299
|
+
statement: belief.statement,
|
|
300
|
+
category: belief.category,
|
|
301
|
+
confidence: belief.confidence,
|
|
302
|
+
supportingEpisodes: belief.supportingEpisodes,
|
|
303
|
+
lastUpdatedAt: belief.lastUpdatedAt,
|
|
304
|
+
// Copy the arrays into state. Applied state is deep-frozen, so handing over the
|
|
305
|
+
// live reference would freeze the integrator's own working array and crash the
|
|
306
|
+
// next in-place mutation (_recordHistory push/shift on a reinforced belief).
|
|
307
|
+
tags: [ ...belief.tags ],
|
|
308
|
+
history: [ ...( belief.history ?? [] ) ],
|
|
309
|
+
},
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
if( !commands.metrics ) commands.metrics = []
|
|
313
|
+
commands.metrics.push([ 'memory.beliefs_total', this._beliefs.length ])
|
|
314
|
+
|
|
315
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
316
|
+
const _bus = this._bus
|
|
317
|
+
if( _bus && this._beliefs.length > 0 ){
|
|
318
|
+
const predErr = this._model.observe( 'belief.count', this._beliefs.length )
|
|
319
|
+
if( !predErr.gated )
|
|
320
|
+
_bus.publish({
|
|
321
|
+
type: 'belief.updated',
|
|
322
|
+
version: 1,
|
|
323
|
+
sourceEngine: this.name,
|
|
324
|
+
salience: Math.max( 0.2, predErr.salience ),
|
|
325
|
+
payload: { total: this._beliefs.length }
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { commands }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── Heuristic pattern detection (upgraded) ─────────────────
|
|
333
|
+
|
|
334
|
+
private _shouldRunHeuristic( tick: Tick ): boolean {
|
|
335
|
+
if( !this._episodicConsolidator ) return false
|
|
336
|
+
|
|
337
|
+
const ticksSince = tick - this._lastIntegrationTick
|
|
338
|
+
const totalEpisodes = this._episodicConsolidator.getAllEpisodes().length
|
|
339
|
+
const newEpisodes = totalEpisodes - this._episodeCountAtLastIntegration
|
|
340
|
+
|
|
341
|
+
return ticksSince >= this._minIntervalTicks && newEpisodes >= this._minNewEpisodes
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private async _heuristicPatternDetection(
|
|
345
|
+
tick: Tick,
|
|
346
|
+
state: ReadonlySimulationState
|
|
347
|
+
): Promise<Belief[]> {
|
|
348
|
+
if( !this._episodicConsolidator ) return []
|
|
349
|
+
|
|
350
|
+
const beliefs: Belief[] = []
|
|
351
|
+
|
|
352
|
+
// ── Try semantic pattern detection first ─────────
|
|
353
|
+
const semanticBeliefs = await this._semanticPatternDetection( tick, state )
|
|
354
|
+
if( semanticBeliefs.length > 0 ){
|
|
355
|
+
logger.info(`[semantic] detected ${semanticBeliefs.length} semantic patterns`)
|
|
356
|
+
return semanticBeliefs
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── FALLBACK: Traditional heuristic detection ────────────
|
|
360
|
+
const episodes = this._episodicConsolidator.getAllEpisodes()
|
|
361
|
+
const newEpisodes = episodes.slice( this._episodeCountAtLastIntegration )
|
|
362
|
+
|
|
363
|
+
// Skip if no new episodes
|
|
364
|
+
if( newEpisodes.length === 0 ) return beliefs
|
|
365
|
+
|
|
366
|
+
// Frequency-based patterns
|
|
367
|
+
if( newEpisodes.length >= 5 ){
|
|
368
|
+
const sourceTypes = new Map<string, number>()
|
|
369
|
+
for( const ep of newEpisodes )
|
|
370
|
+
sourceTypes.set( ep.sourceType, ( sourceTypes.get( ep.sourceType ) ?? 0 ) + 1 )
|
|
371
|
+
|
|
372
|
+
for( const [ sourceType, count ] of sourceTypes ){
|
|
373
|
+
if( count >= 3 )
|
|
374
|
+
beliefs.push({
|
|
375
|
+
id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
|
|
376
|
+
statement: `I frequently encounter situations of type: ${sourceType}`,
|
|
377
|
+
category: 'pattern',
|
|
378
|
+
confidence: Math.min( 0.6, count / 10 ),
|
|
379
|
+
supportingEpisodes: count,
|
|
380
|
+
lastUpdatedAt: tick,
|
|
381
|
+
tags: [ sourceType, 'frequency', 'pattern' ],
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Emotional pattern detection
|
|
387
|
+
if( newEpisodes.length >= 8 ){
|
|
388
|
+
let positiveCount = 0
|
|
389
|
+
let negativeCount = 0
|
|
390
|
+
|
|
391
|
+
for( const ep of newEpisodes ){
|
|
392
|
+
const valence = ep.affectiveContext?.valence ?? 0
|
|
393
|
+
|
|
394
|
+
if( valence > 0.2 ) positiveCount++
|
|
395
|
+
if( valence < -0.2 ) negativeCount++
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if( positiveCount > negativeCount * 2 )
|
|
399
|
+
beliefs.push({
|
|
400
|
+
id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
|
|
401
|
+
statement: 'Recent experiences have been predominantly positive',
|
|
402
|
+
category: 'pattern',
|
|
403
|
+
confidence: Math.min( 0.5, positiveCount / newEpisodes.length ),
|
|
404
|
+
supportingEpisodes: positiveCount,
|
|
405
|
+
lastUpdatedAt: tick,
|
|
406
|
+
tags: [ 'emotional', 'positive', 'pattern' ],
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
else if( negativeCount > positiveCount * 2 )
|
|
410
|
+
beliefs.push({
|
|
411
|
+
id: `belief-heuristic-${( this._idSeq++ ).toString( 36 )}`,
|
|
412
|
+
statement: 'Recent experiences have been predominantly negative',
|
|
413
|
+
category: 'pattern',
|
|
414
|
+
confidence: Math.min( 0.5, negativeCount / newEpisodes.length ),
|
|
415
|
+
supportingEpisodes: negativeCount,
|
|
416
|
+
lastUpdatedAt: tick,
|
|
417
|
+
tags: [ 'emotional', 'negative', 'pattern' ],
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Semantic clustering
|
|
422
|
+
if( this._semanticClustering ){
|
|
423
|
+
const
|
|
424
|
+
newEpisodes = this._episodicConsolidator.getAllEpisodes().slice( -30 ),
|
|
425
|
+
clusterBeliefs = await this._semanticClustering.discoverClusters( tick, newEpisodes )
|
|
426
|
+
|
|
427
|
+
beliefs.push( ...clusterBeliefs )
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return beliefs
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* UPGRADE: Semantic pattern detection using vector memory.
|
|
435
|
+
*
|
|
436
|
+
* Identifies clusters of semantically similar episodes that share
|
|
437
|
+
* emotional or thematic patterns not captured by simple tag/type matching.
|
|
438
|
+
*
|
|
439
|
+
* Falls back to traditional heuristics if vector memory not available
|
|
440
|
+
* or if no meaningful clusters are found.
|
|
441
|
+
*/
|
|
442
|
+
private async _semanticPatternDetection(
|
|
443
|
+
tick: Tick,
|
|
444
|
+
state: ReadonlySimulationState
|
|
445
|
+
): Promise<Belief[]> {
|
|
446
|
+
if( !this._episodicConsolidator ) return []
|
|
447
|
+
|
|
448
|
+
// Check if vector memory is available by attempting semanticQuery
|
|
449
|
+
// (will return empty or throw if not configured)
|
|
450
|
+
const allEpisodes = this._episodicConsolidator.getAllEpisodes()
|
|
451
|
+
if( allEpisodes.length < 10 ) return []
|
|
452
|
+
|
|
453
|
+
// Query for semantically similar episodes — use a neutral query that
|
|
454
|
+
// captures the "essence" of recent experiences
|
|
455
|
+
const recentEpisodes = allEpisodes.slice( -this._semanticQueryLimit )
|
|
456
|
+
if( recentEpisodes.length < 5 ) return []
|
|
457
|
+
|
|
458
|
+
// Build a query from recent episode content
|
|
459
|
+
const queryText = this._buildSemanticQueryForPatterns( recentEpisodes )
|
|
460
|
+
|
|
461
|
+
let semanticResults: EpisodicMemory[] = []
|
|
462
|
+
try {
|
|
463
|
+
semanticResults = await this._episodicConsolidator.semanticQuery(
|
|
464
|
+
queryText,
|
|
465
|
+
{
|
|
466
|
+
limit: this._semanticQueryLimit,
|
|
467
|
+
minSimilarity: this._semanticSimilarityThreshold
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
catch( err ){
|
|
472
|
+
// Vector memory not available — fall back to traditional heuristics
|
|
473
|
+
return []
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if( semanticResults.length < 3 ) return []
|
|
477
|
+
|
|
478
|
+
const beliefs: Belief[] = []
|
|
479
|
+
|
|
480
|
+
// 1. Detect emotional pattern from semantically similar episodes
|
|
481
|
+
const valenceSum = semanticResults.reduce(
|
|
482
|
+
( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0
|
|
483
|
+
)
|
|
484
|
+
const meanValence = valenceSum / semanticResults.length
|
|
485
|
+
|
|
486
|
+
if( Math.abs( meanValence ) > 0.3 ){
|
|
487
|
+
beliefs.push({
|
|
488
|
+
id: `belief-semantic-${( this._idSeq++ ).toString( 36 )}`,
|
|
489
|
+
statement: meanValence > 0
|
|
490
|
+
? 'Recent meaningful experiences have been emotionally positive'
|
|
491
|
+
: 'Recent meaningful experiences have been emotionally challenging',
|
|
492
|
+
category: 'pattern',
|
|
493
|
+
confidence: Math.min( 0.65, Math.abs( meanValence ) * 0.8 ),
|
|
494
|
+
supportingEpisodes: semanticResults.length,
|
|
495
|
+
lastUpdatedAt: tick,
|
|
496
|
+
tags: [ 'semantic', 'emotional_pattern', meanValence > 0 ? 'positive' : 'negative' ],
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// 2. Detect thematic pattern from tags across similar episodes
|
|
501
|
+
const tagFrequency = new Map<string, number>()
|
|
502
|
+
for( const ep of semanticResults ){
|
|
503
|
+
for( const tag of ep.tags ){
|
|
504
|
+
tagFrequency.set( tag, ( tagFrequency.get( tag ) ?? 0 ) + 1 )
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const dominantTag = Array.from( tagFrequency.entries() )
|
|
509
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
510
|
+
.find( ( [ , count ] ) => count > semanticResults.length * 0.6 )
|
|
511
|
+
|
|
512
|
+
if( dominantTag ){
|
|
513
|
+
beliefs.push({
|
|
514
|
+
id: `belief-semantic-tag-${( this._idSeq++ ).toString( 36 )}`,
|
|
515
|
+
statement: `A recurring theme in my experiences is: ${dominantTag[0]}`,
|
|
516
|
+
category: 'pattern',
|
|
517
|
+
confidence: Math.min( 0.55, dominantTag[1] / semanticResults.length ),
|
|
518
|
+
supportingEpisodes: dominantTag[1],
|
|
519
|
+
lastUpdatedAt: tick,
|
|
520
|
+
tags: [ 'semantic', 'thematic_pattern', dominantTag[0] ],
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 3. Detect sourceType pattern
|
|
525
|
+
const sourceTypes = new Map<string, number>()
|
|
526
|
+
for( const ep of semanticResults ){
|
|
527
|
+
sourceTypes.set( ep.sourceType, ( sourceTypes.get( ep.sourceType ) ?? 0 ) + 1 )
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const dominantSource = Array.from( sourceTypes.entries() )
|
|
531
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
532
|
+
.find( ( [ , count ] ) => count > semanticResults.length * 0.5 )
|
|
533
|
+
|
|
534
|
+
if( dominantSource && dominantSource[0] !== 'unknown' ){
|
|
535
|
+
beliefs.push({
|
|
536
|
+
id: `belief-semantic-source-${( this._idSeq++ ).toString( 36 )}`,
|
|
537
|
+
statement: `My semantically similar experiences tend to come from: ${dominantSource[0]}`,
|
|
538
|
+
category: 'pattern',
|
|
539
|
+
confidence: Math.min( 0.5, dominantSource[1] / semanticResults.length ),
|
|
540
|
+
supportingEpisodes: dominantSource[1],
|
|
541
|
+
lastUpdatedAt: tick,
|
|
542
|
+
tags: [ 'semantic', 'source_pattern', dominantSource[0] ],
|
|
543
|
+
})
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return beliefs
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Build a semantic query string from recent episodes for pattern detection.
|
|
551
|
+
* Combines content snippets and tags to create a representative query.
|
|
552
|
+
*/
|
|
553
|
+
private _buildSemanticQueryForPatterns( episodes: EpisodicMemory[] ): string {
|
|
554
|
+
const parts: string[] = []
|
|
555
|
+
|
|
556
|
+
// Add dominant tags from recent episodes
|
|
557
|
+
const tagFrequency = new Map<string, number>()
|
|
558
|
+
for( const ep of episodes ){
|
|
559
|
+
for( const tag of ep.tags ){
|
|
560
|
+
tagFrequency.set( tag, ( tagFrequency.get( tag ) ?? 0 ) + 1 )
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const topTags = Array.from( tagFrequency.entries() )
|
|
565
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
566
|
+
.slice( 0, 3 )
|
|
567
|
+
.map( ( [ tag ] ) => tag )
|
|
568
|
+
|
|
569
|
+
if( topTags.length > 0 ){
|
|
570
|
+
parts.push( `Themes: ${topTags.join( ', ' )}` )
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Add a sample of recent episode content (first 3, truncated)
|
|
574
|
+
const contentSamples = episodes
|
|
575
|
+
.slice( 0, 3 )
|
|
576
|
+
.map( ep => {
|
|
577
|
+
const content = typeof ep.content === 'string'
|
|
578
|
+
? ep.content
|
|
579
|
+
: JSON.stringify( ep.content )
|
|
580
|
+
return content.slice( 0, 150 )
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
if( contentSamples.length > 0 ){
|
|
584
|
+
parts.push( `Recent experiences: ${contentSamples.join( '; ' )}` )
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Add affective context if consistent
|
|
588
|
+
const valenceSum = episodes.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0 )
|
|
589
|
+
const meanValence = valenceSum / episodes.length
|
|
590
|
+
if( Math.abs( meanValence ) > 0.2 ){
|
|
591
|
+
parts.push( `Emotional tone: ${meanValence > 0 ? 'positive' : 'negative'}` )
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return parts.length > 0 ? parts.join( '. ' ) : 'What patterns emerge from my recent experiences?'
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ── Belief management ────────────────────────────────────
|
|
598
|
+
|
|
599
|
+
private static readonly _MAX_HISTORY = 20
|
|
600
|
+
|
|
601
|
+
/** Append a history entry to a belief, dropping the oldest if the buffer is full. */
|
|
602
|
+
private static _recordHistory(
|
|
603
|
+
belief: Belief,
|
|
604
|
+
tick: Tick,
|
|
605
|
+
prevConfidence: number,
|
|
606
|
+
cause: string
|
|
607
|
+
): void {
|
|
608
|
+
const entry: BeliefHistoryEntry = {
|
|
609
|
+
tick,
|
|
610
|
+
confidence: belief.confidence,
|
|
611
|
+
delta: belief.confidence - prevConfidence,
|
|
612
|
+
cause,
|
|
613
|
+
}
|
|
614
|
+
// Build a fresh array rather than mutating in place: a belief loaded from a
|
|
615
|
+
// PMA (or a frozen snapshot) carries a readonly `history`, and .push() on it
|
|
616
|
+
// throws "Attempted to assign to readonly property".
|
|
617
|
+
const next = belief.history ? [ ...belief.history, entry ] : [ entry ]
|
|
618
|
+
if( next.length > SemanticIntegrator._MAX_HISTORY )
|
|
619
|
+
next.shift()
|
|
620
|
+
belief.history = next
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private _integrateBelief( newBelief: Belief, cause = 'created' ): Belief {
|
|
624
|
+
const existing = this._beliefs.find( b => this._shouldMerge( b, newBelief ) )
|
|
625
|
+
|
|
626
|
+
if( existing ){
|
|
627
|
+
const prev = existing.confidence
|
|
628
|
+
existing.confidence = ( existing.confidence + newBelief.confidence ) / 2
|
|
629
|
+
existing.supportingEpisodes += newBelief.supportingEpisodes
|
|
630
|
+
existing.lastUpdatedAt = newBelief.lastUpdatedAt
|
|
631
|
+
SemanticIntegrator._recordHistory( existing, newBelief.lastUpdatedAt, prev, 'reinforced' )
|
|
632
|
+
return existing
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Seed or preserve history.
|
|
636
|
+
// If the belief arrives with an existing history (e.g. from a PMM load or
|
|
637
|
+
// snapshot restore), preserve it and append the load event so provenance
|
|
638
|
+
// is traceable without losing the full trajectory.
|
|
639
|
+
// For brand-new beliefs (no prior history) seed a single creation entry.
|
|
640
|
+
if( newBelief.history && newBelief.history.length > 0 ){
|
|
641
|
+
SemanticIntegrator._recordHistory( newBelief, newBelief.lastUpdatedAt, newBelief.confidence, cause )
|
|
642
|
+
} else {
|
|
643
|
+
newBelief.history = [{
|
|
644
|
+
tick: newBelief.lastUpdatedAt,
|
|
645
|
+
confidence: newBelief.confidence,
|
|
646
|
+
delta: newBelief.confidence,
|
|
647
|
+
cause,
|
|
648
|
+
}]
|
|
649
|
+
}
|
|
650
|
+
this._beliefs.push( newBelief )
|
|
651
|
+
|
|
652
|
+
if( this._beliefs.length > this._maxBeliefs ){
|
|
653
|
+
this._beliefs = this._beliefs
|
|
654
|
+
.filter( b => b.confidence >= 0.3 )
|
|
655
|
+
.sort( ( a, b ) => b.confidence - a.confidence )
|
|
656
|
+
.slice( 0, this._maxBeliefs )
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return newBelief
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Determines whether two beliefs are similar enough to merge.
|
|
664
|
+
*
|
|
665
|
+
* Primary gate: same category + at least one shared substantive tag.
|
|
666
|
+
* If tags overlap by ≥ 50%, we trust the category+tag match and skip
|
|
667
|
+
* text comparison — this prevents Jaccard word-overlap from merging
|
|
668
|
+
* semantically unrelated beliefs that share filler words.
|
|
669
|
+
* Weak tag overlap falls back to stop-word-filtered content similarity.
|
|
670
|
+
*/
|
|
671
|
+
private _shouldMerge( existing: Belief, incoming: Belief ): boolean {
|
|
672
|
+
if( existing.category !== incoming.category ) return false
|
|
673
|
+
|
|
674
|
+
const sharedTags = existing.tags.filter( t => incoming.tags.includes( t ) )
|
|
675
|
+
if( sharedTags.length === 0 ) return false
|
|
676
|
+
|
|
677
|
+
const tagSimilarity = sharedTags.length / Math.min( existing.tags.length, incoming.tags.length )
|
|
678
|
+
if( tagSimilarity >= 0.5 ) return true
|
|
679
|
+
|
|
680
|
+
// Weak tag overlap only: require text similarity as a secondary check
|
|
681
|
+
return this._contentSimilarity( existing.statement, incoming.statement ) > 0.45
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Jaccard similarity on content words — stop words stripped before comparison
|
|
686
|
+
* so common filler ("I", "my", "the", "have") can't drive false merges.
|
|
687
|
+
*/
|
|
688
|
+
private _contentSimilarity( a: string, b: string ): number {
|
|
689
|
+
const tokenise = ( s: string ) =>
|
|
690
|
+
new Set(
|
|
691
|
+
s.toLowerCase()
|
|
692
|
+
.split( /\s+/ )
|
|
693
|
+
.filter( w => w.length > 2 && !_STOP_WORDS.has( w ) )
|
|
694
|
+
)
|
|
695
|
+
const wordsA = tokenise( a )
|
|
696
|
+
const wordsB = tokenise( b )
|
|
697
|
+
if( wordsA.size === 0 || wordsB.size === 0 ) return 0
|
|
698
|
+
const intersection = [ ...wordsA ].filter( w => wordsB.has( w ) ).length
|
|
699
|
+
const union = new Set( [ ...wordsA, ...wordsB ] ).size
|
|
700
|
+
return union > 0 ? intersection / union : 0
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Restore beliefs verbatim from a PMA / snapshot — NOT through the live
|
|
705
|
+
* executive-integration path. A stored belief is a known prior state: its id
|
|
706
|
+
* and final confidence must be preserved exactly. integrateExecutiveBelief()
|
|
707
|
+
* would corrupt a faithful reconstruction two ways: (a) _shouldMerge absorbs
|
|
708
|
+
* semantically-similar stored beliefs into one another, dropping ids and
|
|
709
|
+
* averaging confidence, and (b) it re-caps confidence by evidence count. The
|
|
710
|
+
* live merge/decay dynamics resume once the Will ticks.
|
|
711
|
+
*/
|
|
712
|
+
restoreBeliefs( beliefs: Belief[] ): void {
|
|
713
|
+
for( const b of beliefs )
|
|
714
|
+
this._beliefs.push({
|
|
715
|
+
...b,
|
|
716
|
+
confidence: Math.max( 0, Math.min( 1, b.confidence ) ),
|
|
717
|
+
history: ( b.history && b.history.length > 0 )
|
|
718
|
+
? [ ...b.history ]
|
|
719
|
+
: [ { tick: b.lastUpdatedAt, confidence: b.confidence, delta: b.confidence, cause: 'restored' } ],
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
if( this._beliefs.length > this._maxBeliefs )
|
|
723
|
+
this._beliefs = this._beliefs
|
|
724
|
+
.filter( b => b.confidence >= 0.3 )
|
|
725
|
+
.sort( ( a, b ) => b.confidence - a.confidence )
|
|
726
|
+
.slice( 0, this._maxBeliefs )
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
getBeliefs(): ReadonlyArray<Belief> {
|
|
730
|
+
return this._beliefs
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
queryBeliefs( filters: {
|
|
734
|
+
category?: string
|
|
735
|
+
tags?: string[]
|
|
736
|
+
minConfidence?: number
|
|
737
|
+
}): Belief[] {
|
|
738
|
+
let results = [ ...this._beliefs ]
|
|
739
|
+
|
|
740
|
+
if( filters.category )
|
|
741
|
+
results = results.filter( b => b.category === filters.category )
|
|
742
|
+
if( filters.tags?.length )
|
|
743
|
+
results = results.filter( b => filters.tags!.some( t => b.tags.includes( t ) ) )
|
|
744
|
+
if( filters.minConfidence !== undefined )
|
|
745
|
+
results = results.filter( b => b.confidence >= filters.minConfidence! )
|
|
746
|
+
|
|
747
|
+
results.sort( ( a, b ) => b.confidence - a.confidence )
|
|
748
|
+
return results
|
|
749
|
+
}
|
|
750
|
+
}
|