@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,673 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/semantic.engine/clustering.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SemanticClustering — advanced pattern detection using vector similarity.
|
|
7
|
+
*
|
|
8
|
+
* Extends the SemanticIntegrator's pattern detection with:
|
|
9
|
+
* - Hierarchical clustering of semantically similar episodes
|
|
10
|
+
* - Prototype extraction (centroid of cluster)
|
|
11
|
+
* - Temporal trend detection (beliefs that strengthen/weaken over time)
|
|
12
|
+
* - Anomaly detection (episodes that contradict existing beliefs)
|
|
13
|
+
*
|
|
14
|
+
* Runs as a satellite to SemanticIntegrator, triggered during
|
|
15
|
+
* heuristic pattern detection cycles.
|
|
16
|
+
*
|
|
17
|
+
* Part of Shard 2 (Memory Layer)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { logger } from '#core/logger'
|
|
21
|
+
import type { Tick } from '#core/types'
|
|
22
|
+
import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
|
|
23
|
+
import type { CognitiveBus } from '#cognition/bus'
|
|
24
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
25
|
+
import { _STOP_WORDS, Belief } from '#faculties/semantic.engine/types'
|
|
26
|
+
|
|
27
|
+
export interface Cluster {
|
|
28
|
+
id: string
|
|
29
|
+
episodes: EpisodicMemory[]
|
|
30
|
+
centroid: number[] // Vector centroid of the cluster (if vector available)
|
|
31
|
+
prototypeStatement: string // Natural language description of the cluster
|
|
32
|
+
valenceMean: number
|
|
33
|
+
valenceStd: number
|
|
34
|
+
dominantEmotions: Array<[string, number]> // Emotion → average intensity
|
|
35
|
+
dominantTags: Array<[string, number]> // Tag → frequency
|
|
36
|
+
sourceTypes: Array<[string, number]> // SourceType → frequency
|
|
37
|
+
firstSeen: Tick
|
|
38
|
+
lastSeen: Tick
|
|
39
|
+
episodeCount: number
|
|
40
|
+
stabilityScore: number // How consistent the cluster is (0-1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TemporalTrend {
|
|
44
|
+
beliefStatement: string
|
|
45
|
+
direction: 'increasing' | 'decreasing' | 'stable'
|
|
46
|
+
slope: number
|
|
47
|
+
confidence: number
|
|
48
|
+
observations: Array<{ tick: Tick; valence: number; count: number }>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SemanticClusteringConfig {
|
|
52
|
+
/** Minimum episodes to form a cluster */
|
|
53
|
+
minClusterSize?: number
|
|
54
|
+
/** Minimum similarity threshold for cluster membership (0-1) */
|
|
55
|
+
clusterSimilarityThreshold?: number
|
|
56
|
+
/** Maximum distance from centroid to be considered in cluster (0-1) */
|
|
57
|
+
maxCentroidDistance?: number
|
|
58
|
+
/** Minimum number of temporal windows for trend detection */
|
|
59
|
+
minTrendWindows?: number
|
|
60
|
+
/** Ticks per temporal window for trend analysis */
|
|
61
|
+
trendWindowTicks?: number
|
|
62
|
+
bus?: CognitiveBus
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class SemanticClustering {
|
|
66
|
+
readonly name = 'semantic-clustering'
|
|
67
|
+
|
|
68
|
+
private _minClusterSize: number
|
|
69
|
+
private _clusterSimilarityThreshold: number
|
|
70
|
+
private _maxCentroidDistance: number
|
|
71
|
+
private _minTrendWindows: number
|
|
72
|
+
private _trendWindowTicks: number
|
|
73
|
+
|
|
74
|
+
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
75
|
+
private _bus: CognitiveBus | null = null
|
|
76
|
+
private _clusters: Cluster[] = []
|
|
77
|
+
|
|
78
|
+
private _idSeq = 0
|
|
79
|
+
private readonly _model = new GenerativeModel()
|
|
80
|
+
|
|
81
|
+
constructor( config: SemanticClusteringConfig = {} ){
|
|
82
|
+
this._bus = config.bus ?? null
|
|
83
|
+
this._minClusterSize = config.minClusterSize ?? 5
|
|
84
|
+
this._clusterSimilarityThreshold = config.clusterSimilarityThreshold ?? 0.65
|
|
85
|
+
this._maxCentroidDistance = config.maxCentroidDistance ?? 0.4
|
|
86
|
+
this._minTrendWindows = config.minTrendWindows ?? 3
|
|
87
|
+
this._trendWindowTicks = config.trendWindowTicks ?? 200
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
91
|
+
attachConsolidator( consolidator: EpisodicConsolidator ): void { this._episodicConsolidator = consolidator }
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Main entry point: discover clusters from recent episodes.
|
|
95
|
+
* Called by SemanticIntegrator during heuristic pattern detection.
|
|
96
|
+
*/
|
|
97
|
+
async discoverClusters( tick: Tick, recentEpisodes: EpisodicMemory[] ): Promise<Belief[]> {
|
|
98
|
+
if( !this._episodicConsolidator || recentEpisodes.length < this._minClusterSize )
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
const newBeliefs: Belief[] = []
|
|
102
|
+
|
|
103
|
+
// 1. Find clusters using vector similarity
|
|
104
|
+
const clusters = await this._findClusters( recentEpisodes, tick )
|
|
105
|
+
|
|
106
|
+
// 2. For each cluster, generate belief statements
|
|
107
|
+
for( const cluster of clusters ){
|
|
108
|
+
const belief = this._clusterToBelief( cluster, tick )
|
|
109
|
+
if( belief ){
|
|
110
|
+
newBeliefs.push(belief)
|
|
111
|
+
this._clusters.push(cluster)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 3. Detect temporal trends (beliefs that are becoming stronger/weaker)
|
|
116
|
+
const trends = await this._detectTemporalTrends( tick )
|
|
117
|
+
for( const trend of trends )
|
|
118
|
+
if( trend.confidence > 0.7 ){
|
|
119
|
+
const belief = this._trendToBelief( trend, tick )
|
|
120
|
+
belief && newBeliefs.push( belief )
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 4. Detect anomalies (episodes that contradict existing beliefs)
|
|
124
|
+
const anomalies = await this._detectAnomalies( recentEpisodes, tick )
|
|
125
|
+
for( const anomaly of anomalies )
|
|
126
|
+
if( anomaly.confidence > 0.6 )
|
|
127
|
+
newBeliefs.push( this._anomalyToBelief( anomaly, tick ) )
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if( newBeliefs.length > 0 )
|
|
131
|
+
logger.info(`[semantic-clustering] discovered ${newBeliefs.length} beliefs from ` +
|
|
132
|
+
`${recentEpisodes.length} episodes (${clusters.length} clusters, ` +
|
|
133
|
+
`${trends.length} trends, ${anomalies.length} anomalies)`)
|
|
134
|
+
|
|
135
|
+
return newBeliefs
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Private clustering methods ───────────────────────────
|
|
139
|
+
|
|
140
|
+
private async _findClusters( episodes: EpisodicMemory[], tick: Tick ): Promise<Cluster[]> {
|
|
141
|
+
if( !this._episodicConsolidator ) return []
|
|
142
|
+
|
|
143
|
+
const
|
|
144
|
+
clusters: Cluster[] = [],
|
|
145
|
+
assigned = new Set<string>()
|
|
146
|
+
|
|
147
|
+
// Try vector-based clustering first
|
|
148
|
+
let useVectorClustering = true
|
|
149
|
+
|
|
150
|
+
// Test if vector memory is available by attempting a query
|
|
151
|
+
try { await this._episodicConsolidator.semanticQuery('test', { limit: 1 }) }
|
|
152
|
+
catch { useVectorClustering = false }
|
|
153
|
+
|
|
154
|
+
if( useVectorClustering ){
|
|
155
|
+
// Use vector similarity for clustering
|
|
156
|
+
for( let i = 0; i < episodes.length && clusters.length < 10; i++ ){
|
|
157
|
+
const seed = episodes[i]
|
|
158
|
+
if( !seed || assigned.has( seed.id ) ) continue
|
|
159
|
+
|
|
160
|
+
// Find similar episodes using vector query
|
|
161
|
+
const similar = await this._episodicConsolidator.semanticQuery(
|
|
162
|
+
this._episodeToQuery( seed ),
|
|
163
|
+
{
|
|
164
|
+
limit: this._minClusterSize * 2,
|
|
165
|
+
minSimilarity: this._clusterSimilarityThreshold
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// Filter to only recent episodes and not already assigned
|
|
170
|
+
const clusterEpisodes = similar.filter( ep => episodes.includes( ep ) && !assigned.has( ep.id ) )
|
|
171
|
+
if( clusterEpisodes.length >= this._minClusterSize ){
|
|
172
|
+
const cluster = await this._buildCluster( clusterEpisodes, tick )
|
|
173
|
+
clusters.push( cluster )
|
|
174
|
+
|
|
175
|
+
for( const ep of clusterEpisodes )
|
|
176
|
+
assigned.add( ep.id )
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fallback: content-similarity clustering for episodes that don't have vectors
|
|
182
|
+
const unassigned = episodes.filter( ep => !assigned.has( ep.id ) )
|
|
183
|
+
if( unassigned.length >= this._minClusterSize ){
|
|
184
|
+
const fallbackClusters = this._fallbackClustering( unassigned, tick )
|
|
185
|
+
clusters.push( ...fallbackClusters )
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return clusters
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private _fallbackClustering( episodes: EpisodicMemory[], tick: Tick ): Cluster[] {
|
|
192
|
+
const
|
|
193
|
+
clusters: Cluster[] = [],
|
|
194
|
+
assigned = new Set<string>()
|
|
195
|
+
|
|
196
|
+
for( let i = 0; i < episodes.length; i++ ){
|
|
197
|
+
const seed = episodes[i]
|
|
198
|
+
if( !seed || assigned.has( seed.id ) ) continue
|
|
199
|
+
|
|
200
|
+
const clusterEpisodes: EpisodicMemory[] = [ seed ]
|
|
201
|
+
|
|
202
|
+
for( let j = i + 1; j < episodes.length; j++ ){
|
|
203
|
+
const other = episodes[j]
|
|
204
|
+
if( !other || assigned.has( other.id ) ) continue
|
|
205
|
+
|
|
206
|
+
const similarity = this._contentSimilarity(
|
|
207
|
+
this._episodeToText( seed ),
|
|
208
|
+
this._episodeToText( other )
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if( similarity > this._clusterSimilarityThreshold )
|
|
212
|
+
clusterEpisodes.push( other )
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if( clusterEpisodes.length >= this._minClusterSize ){
|
|
216
|
+
const cluster = this._buildClusterFromContent( clusterEpisodes, tick )
|
|
217
|
+
clusters.push( cluster )
|
|
218
|
+
|
|
219
|
+
for( const ep of clusterEpisodes )
|
|
220
|
+
assigned.add( ep.id )
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return clusters
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async _buildCluster( episodes: EpisodicMemory[], tick: Tick ): Promise<Cluster> {
|
|
228
|
+
const valenceSum = episodes.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0 )
|
|
229
|
+
const valenceMean = valenceSum / episodes.length
|
|
230
|
+
const valenceStd = Math.sqrt( episodes.reduce( ( s, ep ) => {
|
|
231
|
+
const diff = ( ep.affectiveContext?.valence ?? 0 ) - valenceMean
|
|
232
|
+
return s + diff * diff
|
|
233
|
+
}, 0 ) / episodes.length
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// Aggregate emotions
|
|
237
|
+
const emotionMap = new Map<string, number>()
|
|
238
|
+
for( const ep of episodes )
|
|
239
|
+
for( const [ emotion, intensity ] of Object.entries( ep.emotionalTags ?? {} ) )
|
|
240
|
+
emotionMap.set( emotion, ( emotionMap.get( emotion ) ?? 0 ) + intensity )
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
const dominantEmotions = Array.from( emotionMap.entries() )
|
|
244
|
+
.map( ([ e, sum ]) => [ e, sum / episodes.length ] as [string, number])
|
|
245
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
246
|
+
.slice( 0, 3 )
|
|
247
|
+
|
|
248
|
+
// Aggregate tags
|
|
249
|
+
const tagMap = new Map<string, number>()
|
|
250
|
+
for( const ep of episodes )
|
|
251
|
+
for( const tag of ep.tags )
|
|
252
|
+
tagMap.set( tag, ( tagMap.get( tag ) ?? 0) + 1 )
|
|
253
|
+
|
|
254
|
+
const dominantTags = Array.from( tagMap.entries() )
|
|
255
|
+
.map( ([ t, count ]) => [ t, count / episodes.length ] as [string, number])
|
|
256
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
257
|
+
.slice( 0, 5 )
|
|
258
|
+
|
|
259
|
+
// Aggregate source types
|
|
260
|
+
const sourceMap = new Map<string, number>()
|
|
261
|
+
for( const ep of episodes )
|
|
262
|
+
sourceMap.set( ep.sourceType, ( sourceMap.get( ep.sourceType ) ?? 0 ) + 1 )
|
|
263
|
+
|
|
264
|
+
const sourceTypes = Array.from( sourceMap.entries() )
|
|
265
|
+
.map( ([ s, count ]) => [ s, count / episodes.length ] as [string, number])
|
|
266
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
267
|
+
|
|
268
|
+
// Find first and last seen timestamps
|
|
269
|
+
const timestamps = episodes.map( ep => ep.timestamp )
|
|
270
|
+
const firstSeen = Math.min( ...timestamps )
|
|
271
|
+
const lastSeen = Math.max( ...timestamps )
|
|
272
|
+
|
|
273
|
+
// Calculate stability (inverse of valence variance)
|
|
274
|
+
const stabilityScore = Math.max( 0, 1 - Math.min( 1, valenceStd / 0.5 ) )
|
|
275
|
+
|
|
276
|
+
// Generate prototype statement
|
|
277
|
+
const prototypeStatement = this._generatePrototypeStatement(
|
|
278
|
+
episodes,
|
|
279
|
+
dominantEmotions,
|
|
280
|
+
dominantTags,
|
|
281
|
+
valenceMean
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
this._idSeq++
|
|
285
|
+
return {
|
|
286
|
+
id: `cluster-${tick}-${this._idSeq}`,
|
|
287
|
+
episodes,
|
|
288
|
+
centroid: [], // Would be populated from vector if available
|
|
289
|
+
prototypeStatement,
|
|
290
|
+
valenceMean,
|
|
291
|
+
valenceStd,
|
|
292
|
+
dominantEmotions,
|
|
293
|
+
dominantTags,
|
|
294
|
+
sourceTypes,
|
|
295
|
+
firstSeen,
|
|
296
|
+
lastSeen,
|
|
297
|
+
episodeCount: episodes.length,
|
|
298
|
+
stabilityScore
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private _buildClusterFromContent( episodes: EpisodicMemory[], tick: Tick ): Cluster {
|
|
303
|
+
// Same as _buildCluster but without vector centroid
|
|
304
|
+
const valenceSum = episodes.reduce( ( s, ep ) => s + (ep.affectiveContext?.valence ?? 0), 0 )
|
|
305
|
+
const valenceMean = valenceSum / episodes.length
|
|
306
|
+
const valenceStd = Math.sqrt( episodes.reduce( ( s, ep ) => {
|
|
307
|
+
const diff = (ep.affectiveContext?.valence ?? 0) - valenceMean
|
|
308
|
+
return s + diff * diff
|
|
309
|
+
}, 0 ) / episodes.length
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
const emotionMap = new Map<string, number>()
|
|
313
|
+
for( const ep of episodes )
|
|
314
|
+
for( const [ emotion, intensity ] of Object.entries( ep.emotionalTags ?? {}) )
|
|
315
|
+
emotionMap.set( emotion, ( emotionMap.get( emotion ) ?? 0 ) + intensity )
|
|
316
|
+
|
|
317
|
+
const dominantEmotions = Array.from( emotionMap.entries() )
|
|
318
|
+
.map( ([ e, sum] ) => [ e, sum / episodes.length ] as [string, number])
|
|
319
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
320
|
+
.slice( 0, 3 )
|
|
321
|
+
|
|
322
|
+
const tagMap = new Map<string, number>()
|
|
323
|
+
for( const ep of episodes )
|
|
324
|
+
for( const tag of ep.tags )
|
|
325
|
+
tagMap.set( tag, (tagMap.get( tag ) ?? 0) + 1 )
|
|
326
|
+
|
|
327
|
+
const dominantTags = Array.from( tagMap.entries() )
|
|
328
|
+
.map( ([ t, count ]) => [ t, count / episodes.length ] as [string, number])
|
|
329
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
330
|
+
.slice( 0, 5 )
|
|
331
|
+
|
|
332
|
+
const sourceMap = new Map<string, number>()
|
|
333
|
+
for( const ep of episodes )
|
|
334
|
+
sourceMap.set( ep.sourceType, (sourceMap.get( ep.sourceType ) ?? 0) + 1 )
|
|
335
|
+
|
|
336
|
+
const sourceTypes = Array.from( sourceMap.entries() )
|
|
337
|
+
.map( ([ s, count ]) => [ s, count / episodes.length ] as [string, number])
|
|
338
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
339
|
+
|
|
340
|
+
const timestamps = episodes.map( ep => ep.timestamp )
|
|
341
|
+
const firstSeen = Math.min( ...timestamps )
|
|
342
|
+
const lastSeen = Math.max( ...timestamps )
|
|
343
|
+
const stabilityScore = Math.max( 0, 1 - Math.min( 1, valenceStd / 0.5 ) )
|
|
344
|
+
|
|
345
|
+
const prototypeStatement = this._generatePrototypeStatement(
|
|
346
|
+
episodes,
|
|
347
|
+
dominantEmotions,
|
|
348
|
+
dominantTags,
|
|
349
|
+
valenceMean
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
this._idSeq++
|
|
353
|
+
return {
|
|
354
|
+
id: `cluster-fallback-${tick}-${this._idSeq}`,
|
|
355
|
+
episodes,
|
|
356
|
+
centroid: [],
|
|
357
|
+
prototypeStatement,
|
|
358
|
+
valenceMean,
|
|
359
|
+
valenceStd,
|
|
360
|
+
dominantEmotions,
|
|
361
|
+
dominantTags,
|
|
362
|
+
sourceTypes,
|
|
363
|
+
firstSeen,
|
|
364
|
+
lastSeen,
|
|
365
|
+
episodeCount: episodes.length,
|
|
366
|
+
stabilityScore
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ── Belief generation ────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
private _clusterToBelief( cluster: Cluster, tick: Tick ): Belief | null {
|
|
373
|
+
// Only form beliefs from stable clusters with meaningful patterns
|
|
374
|
+
if( cluster.stabilityScore < 0.4 ) return null
|
|
375
|
+
if( cluster.episodeCount < this._minClusterSize ) return null
|
|
376
|
+
|
|
377
|
+
let statement = cluster.prototypeStatement
|
|
378
|
+
let confidence = Math.min( 0.7, 0.3 + cluster.stabilityScore * 0.4 )
|
|
379
|
+
let category: Belief['category'] = 'pattern'
|
|
380
|
+
let tags = [ ...cluster.dominantTags.map( ([ t ]) => t ), 'semantic_cluster' ]
|
|
381
|
+
|
|
382
|
+
// Add emotional qualifier if valence is extreme
|
|
383
|
+
if( cluster.valenceMean > 0.4 ){
|
|
384
|
+
statement = `Positive experiences often center around: ${statement}`
|
|
385
|
+
tags.push('positive_pattern')
|
|
386
|
+
|
|
387
|
+
confidence += 0.05
|
|
388
|
+
}
|
|
389
|
+
else if( cluster.valenceMean < -0.3 ){
|
|
390
|
+
statement = `Challenging experiences often involve: ${statement}`
|
|
391
|
+
tags.push('negative_pattern')
|
|
392
|
+
|
|
393
|
+
confidence += 0.05
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Categorize based on source
|
|
397
|
+
if( cluster.sourceTypes.some( ([ s ]) => s === 'social' || s === 'conversation') ){
|
|
398
|
+
category = 'social_belief'
|
|
399
|
+
tags.push('social')
|
|
400
|
+
}
|
|
401
|
+
else if( cluster.sourceTypes.some( ([ s ]) => s === 'interoception') ){
|
|
402
|
+
category = 'self_belief'
|
|
403
|
+
tags.push('self')
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
confidence = Math.min( 0.75, confidence )
|
|
407
|
+
|
|
408
|
+
this._idSeq++
|
|
409
|
+
return {
|
|
410
|
+
id: `belief-cluster-${tick}-${this._idSeq}`,
|
|
411
|
+
statement,
|
|
412
|
+
category,
|
|
413
|
+
confidence,
|
|
414
|
+
supportingEpisodes: cluster.episodeCount,
|
|
415
|
+
lastUpdatedAt: tick,
|
|
416
|
+
tags: tags.slice( 0, 8 )
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private _generatePrototypeStatement(
|
|
421
|
+
episodes: EpisodicMemory[],
|
|
422
|
+
dominantEmotions: Array<[string, number]>,
|
|
423
|
+
dominantTags: Array<[string, number]>,
|
|
424
|
+
valenceMean: number
|
|
425
|
+
): string {
|
|
426
|
+
// Extract common themes from episode content
|
|
427
|
+
const contentWords = new Map<string, number>()
|
|
428
|
+
|
|
429
|
+
for( const ep of episodes.slice( 0, 20 ) ){
|
|
430
|
+
const text = this._episodeToText( ep )
|
|
431
|
+
const words = text.toLowerCase()
|
|
432
|
+
.split(/\s+/)
|
|
433
|
+
.filter( w => w.length > 3 && !_STOP_WORDS.has( w ) )
|
|
434
|
+
|
|
435
|
+
for( const word of words.slice( 0, 10 ) )
|
|
436
|
+
contentWords.set( word, ( contentWords.get( word ) ?? 0 ) + 1 )
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const topWords = Array.from( contentWords.entries() )
|
|
440
|
+
.sort( ( a, b ) => b[1] - a[1] )
|
|
441
|
+
.slice( 0, 4 )
|
|
442
|
+
.map( ([ w ]) => w)
|
|
443
|
+
|
|
444
|
+
if( dominantTags.length > 0 && dominantTags[0] && dominantTags[0][1] > 0.5 )
|
|
445
|
+
return `Themes related to "${dominantTags[0][0]}" appear repeatedly`
|
|
446
|
+
|
|
447
|
+
if( topWords.length >= 2 )
|
|
448
|
+
return `Patterns involving ${topWords.slice(0, 3).join(', ')} emerge in my experiences`
|
|
449
|
+
|
|
450
|
+
if( dominantEmotions.length > 0 && dominantEmotions[0] && dominantEmotions[0][1] > 0.4 ){
|
|
451
|
+
const emotion = dominantEmotions[0][0]
|
|
452
|
+
return `I frequently experience ${emotion} in similar situations`
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return `A recurring pattern has emerged in my recent experiences`
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Temporal trend detection ─────────────────────────────
|
|
459
|
+
|
|
460
|
+
private async _detectTemporalTrends( tick: Tick ): Promise<TemporalTrend[]> {
|
|
461
|
+
if( !this._episodicConsolidator ) return []
|
|
462
|
+
|
|
463
|
+
const trends: TemporalTrend[] = []
|
|
464
|
+
const windowSize = this._trendWindowTicks
|
|
465
|
+
const startTick = tick - windowSize * this._minTrendWindows
|
|
466
|
+
|
|
467
|
+
// Group episodes by time window
|
|
468
|
+
const windows: Map<number, EpisodicMemory[]> = new Map()
|
|
469
|
+
const allEpisodes = this._episodicConsolidator.getAllEpisodes()
|
|
470
|
+
|
|
471
|
+
for( const ep of allEpisodes ){
|
|
472
|
+
if( ep.timestamp < startTick ) continue
|
|
473
|
+
const windowIndex = Math.floor( ep.timestamp / windowSize )
|
|
474
|
+
const window = windows.get( windowIndex ) ?? []
|
|
475
|
+
|
|
476
|
+
window.push( ep )
|
|
477
|
+
windows.set( windowIndex, window )
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const sortedWindows = Array.from( windows.keys() ).sort( ( a, b ) => a - b )
|
|
481
|
+
if( sortedWindows.length < this._minTrendWindows ) return []
|
|
482
|
+
|
|
483
|
+
// For each source type and emotion, compute trend
|
|
484
|
+
const sourceTypes = [ 'percept', 'social', 'goal', 'thought', 'interoception' ]
|
|
485
|
+
for( const sourceType of sourceTypes ){
|
|
486
|
+
const observations: Array<{ tick: number; valence: number; count: number }> = []
|
|
487
|
+
|
|
488
|
+
for( const winIdx of sortedWindows ){
|
|
489
|
+
const episodes = windows.get( winIdx ) ?? []
|
|
490
|
+
|
|
491
|
+
const filtered = episodes.filter( ep => ep.sourceType === sourceType )
|
|
492
|
+
if( filtered.length === 0 ) continue
|
|
493
|
+
|
|
494
|
+
const valenceSum = filtered.reduce( ( s, ep ) => s + ( ep.affectiveContext?.valence ?? 0 ), 0)
|
|
495
|
+
observations.push({
|
|
496
|
+
tick: winIdx * windowSize,
|
|
497
|
+
valence: valenceSum / filtered.length,
|
|
498
|
+
count: filtered.length,
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if( observations.length >= this._minTrendWindows ){
|
|
503
|
+
const trend = this._computeLinearTrend( observations )
|
|
504
|
+
if( Math.abs( trend.slope ) > 0.02 && trend.confidence > 0.6 )
|
|
505
|
+
trends.push({
|
|
506
|
+
beliefStatement: `My experiences with ${sourceType} are becoming ${trend.direction === 'increasing' ? 'more positive' : 'more negative'}`,
|
|
507
|
+
direction: trend.direction,
|
|
508
|
+
slope: trend.slope,
|
|
509
|
+
confidence: trend.confidence,
|
|
510
|
+
observations
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return trends
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private _computeLinearTrend( observations: Array<{ tick: number; valence: number; count: number }> ): {
|
|
519
|
+
slope: number
|
|
520
|
+
direction: 'increasing' | 'decreasing' | 'stable'
|
|
521
|
+
confidence: number
|
|
522
|
+
}{
|
|
523
|
+
const n = observations.length
|
|
524
|
+
if( n < 2 )
|
|
525
|
+
return { slope: 0, direction: 'stable', confidence: 0 }
|
|
526
|
+
|
|
527
|
+
// Simple linear regression
|
|
528
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0
|
|
529
|
+
for( let i = 0; i < n; i++ ){
|
|
530
|
+
const x = i // Use index as x for normalized time
|
|
531
|
+
const y = observations[i]!.valence
|
|
532
|
+
|
|
533
|
+
sumX += x
|
|
534
|
+
sumY += y
|
|
535
|
+
sumXY += x * y
|
|
536
|
+
sumX2 += x * x
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const denominator = n * sumX2 - sumX * sumX
|
|
540
|
+
if( denominator === 0 )
|
|
541
|
+
return { slope: 0, direction: 'stable', confidence: 0 }
|
|
542
|
+
|
|
543
|
+
const slope = ( n * sumXY - sumX * sumY ) / denominator
|
|
544
|
+
|
|
545
|
+
// Calculate R-squared for confidence
|
|
546
|
+
const yMean = sumY / n
|
|
547
|
+
let ssTot = 0, ssRes = 0
|
|
548
|
+
for( let i = 0; i < n; i++ ){
|
|
549
|
+
const x = i
|
|
550
|
+
const y = observations[i]!.valence
|
|
551
|
+
const yPred = yMean + slope * ( x - ( sumX / n ) )
|
|
552
|
+
|
|
553
|
+
ssTot += (y - yMean) ** 2
|
|
554
|
+
ssRes += (y - yPred) ** 2
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0
|
|
558
|
+
const direction: 'increasing' | 'decreasing' | 'stable' = slope > 0.02 ? 'increasing' : slope < -0.02 ? 'decreasing' : 'stable'
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
slope,
|
|
562
|
+
direction,
|
|
563
|
+
confidence: Math.min( 0.8, 0.3 + rSquared * 0.5 ),
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private _trendToBelief( trend: TemporalTrend, tick: Tick ): Belief | null {
|
|
568
|
+
if( trend.confidence < 0.6) return null
|
|
569
|
+
|
|
570
|
+
this._idSeq++
|
|
571
|
+
return {
|
|
572
|
+
id: `belief-trend-${tick}-${this._idSeq}`,
|
|
573
|
+
statement: trend.beliefStatement,
|
|
574
|
+
category: 'pattern',
|
|
575
|
+
confidence: trend.confidence,
|
|
576
|
+
supportingEpisodes: trend.observations.reduce( ( s, o ) => s + o.count, 0 ),
|
|
577
|
+
lastUpdatedAt: tick,
|
|
578
|
+
tags: [ 'temporal_trend', trend.direction, 'semantic_cluster' ],
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ── Anomaly detection ────────────────────────────────────
|
|
583
|
+
|
|
584
|
+
private async _detectAnomalies(
|
|
585
|
+
episodes: EpisodicMemory[],
|
|
586
|
+
tick: Tick
|
|
587
|
+
): Promise<Array<{ statement: string; confidence: number; episode: EpisodicMemory }>> {
|
|
588
|
+
const anomalies: Array<{ statement: string; confidence: number; episode: EpisodicMemory }> = []
|
|
589
|
+
|
|
590
|
+
// For each existing cluster, check if new episodes contradict it
|
|
591
|
+
for( const cluster of this._clusters ){
|
|
592
|
+
for( const episode of episodes ){
|
|
593
|
+
const similarity = this._contentSimilarity( cluster.prototypeStatement, this._episodeToText( episode ) )
|
|
594
|
+
|
|
595
|
+
// If episode is semantically similar but emotionally opposite, it's an anomaly
|
|
596
|
+
if( similarity > 0.4 ){
|
|
597
|
+
const valenceDiff = Math.abs( episode.affectiveContext?.valence ?? 0 - cluster.valenceMean )
|
|
598
|
+
if( valenceDiff > 0.6 )
|
|
599
|
+
anomalies.push({
|
|
600
|
+
statement: `Not all experiences about ${cluster.prototypeStatement.toLowerCase()} follow the usual pattern`,
|
|
601
|
+
confidence: Math.min( 0.65, 0.4 + valenceDiff * 0.3 ),
|
|
602
|
+
episode,
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return anomalies.slice( 0, 3 )
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private _anomalyToBelief(
|
|
612
|
+
anomaly: { statement: string; confidence: number; episode: EpisodicMemory },
|
|
613
|
+
tick: Tick
|
|
614
|
+
): Belief {
|
|
615
|
+
this._idSeq++
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
id: `belief-anomaly-${tick}-${this._idSeq}`,
|
|
619
|
+
statement: anomaly.statement,
|
|
620
|
+
category: 'pattern',
|
|
621
|
+
confidence: anomaly.confidence,
|
|
622
|
+
supportingEpisodes: 1,
|
|
623
|
+
lastUpdatedAt: tick,
|
|
624
|
+
tags: ['anomaly', 'exception', 'semantic_cluster']
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ── Helpers ──────────────────────────────────────────────
|
|
629
|
+
|
|
630
|
+
private _episodeToText( episode: EpisodicMemory ): string {
|
|
631
|
+
if( typeof episode.content === 'string')
|
|
632
|
+
return episode.content
|
|
633
|
+
|
|
634
|
+
if( typeof episode.content === 'object' && episode.content !== null ){
|
|
635
|
+
const content = episode.content as Record<string, unknown>
|
|
636
|
+
if( typeof content['summary'] === 'string') return content['summary']
|
|
637
|
+
if( typeof content['description'] === 'string') return content['description']
|
|
638
|
+
if( typeof content['userMessage'] === 'string') return content['userMessage']
|
|
639
|
+
|
|
640
|
+
return JSON.stringify( episode.content )
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return String( episode.content )
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private _episodeToQuery( episode: EpisodicMemory ): string {
|
|
647
|
+
// Build a rich query string that captures emotional and thematic content
|
|
648
|
+
const text = this._episodeToText( episode )
|
|
649
|
+
const valence = episode.affectiveContext?.valence ?? 0
|
|
650
|
+
const emotionHint = valence > 0.3 ? 'positive' : valence < -0.3 ? 'negative' : 'neutral'
|
|
651
|
+
|
|
652
|
+
return `${text} ${emotionHint} ${episode.tags.slice( 0, 3 ).join(' ')}`
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private _contentSimilarity( a: string, b: string ): number {
|
|
656
|
+
const tokenize = ( s: string ) => new Set( s.toLowerCase().split(/\s+/).filter( w => w.length > 2 && !_STOP_WORDS.has( w ) ) )
|
|
657
|
+
|
|
658
|
+
const wordsA = tokenize(a)
|
|
659
|
+
const wordsB = tokenize(b)
|
|
660
|
+
|
|
661
|
+
if( wordsA.size === 0 || wordsB.size === 0 ) return 0
|
|
662
|
+
|
|
663
|
+
const intersection = [ ...wordsA].filter( w => wordsB.has( w ) ).length
|
|
664
|
+
const union = new Set([ ...wordsA, ...wordsB ]).size
|
|
665
|
+
|
|
666
|
+
return union > 0 ? intersection / union : 0
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ── Public API ───────────────────────────────────────────
|
|
670
|
+
|
|
671
|
+
getClusters(): ReadonlyArray<Cluster> { return this._clusters }
|
|
672
|
+
clearClusters(): void { this._clusters = [] }
|
|
673
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/semantic.engine/index.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export { SemanticIntegrator } from '#faculties/semantic.engine/integrator'
|
|
6
|
+
export type { SemanticIntegratorConfig, BeliefHistoryEntry, Belief } from '#faculties/semantic.engine/types'
|