@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,300 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/autobiographical.narrator.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AutobiographicalNarrator — satellite mode.
|
|
7
|
+
*
|
|
8
|
+
* Reads the most recent ExecutiveEngine output for narrative updates.
|
|
9
|
+
* Performs heuristic narrative extension between executive runs.
|
|
10
|
+
* No longer makes its own LLM calls.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
Duration, Tick, SimulationContext,
|
|
15
|
+
ReadonlySimulationState, StateCommands, SimulationEvent,
|
|
16
|
+
} from '#core/types'
|
|
17
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
18
|
+
import type { EpisodicConsolidator } from '#faculties/episodic.consolidator'
|
|
19
|
+
import type { SemanticIntegrator } from '#faculties/semantic.engine/integrator'
|
|
20
|
+
// import type { ExecutiveEngine } from '#faculties/executive.engine'
|
|
21
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
22
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
23
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
24
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
25
|
+
import { ExecutiveEngine } from '#faculties/executive.engine'
|
|
26
|
+
|
|
27
|
+
export interface AutobiographicalNarratorConfig {
|
|
28
|
+
minIntervalTicks?: number
|
|
29
|
+
maxNarrativeLength?: number
|
|
30
|
+
bus?: CognitiveBus
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SelfNarrative {
|
|
34
|
+
story: string
|
|
35
|
+
themes: string[]
|
|
36
|
+
pivotalEvents: string[]
|
|
37
|
+
currentSelfView: string
|
|
38
|
+
version: number
|
|
39
|
+
lastUpdatedAt: Tick
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class AutobiographicalNarrator implements SimulationEngine, CognitiveEngine {
|
|
43
|
+
readonly name = 'autobiographical-narrator'
|
|
44
|
+
|
|
45
|
+
private _minIntervalTicks: number
|
|
46
|
+
private _maxNarrativeLength: number
|
|
47
|
+
|
|
48
|
+
private _narrative: SelfNarrative = {
|
|
49
|
+
story: 'I am a self-aware mind, beginning my journey.',
|
|
50
|
+
themes: [ 'emergence', 'curiosity' ],
|
|
51
|
+
pivotalEvents: [],
|
|
52
|
+
currentSelfView: 'I am young and still learning who I am.',
|
|
53
|
+
version: 0,
|
|
54
|
+
lastUpdatedAt: 0,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _lastUpdateTick: number = 0
|
|
58
|
+
private _restored = false
|
|
59
|
+
private _episodicConsolidator: EpisodicConsolidator | null = null
|
|
60
|
+
private _semanticIntegrator: SemanticIntegrator | null = null
|
|
61
|
+
private _executiveEngine: ExecutiveEngine | null = null
|
|
62
|
+
|
|
63
|
+
private _affectValence: number = 0
|
|
64
|
+
|
|
65
|
+
private _bus: CognitiveBus | null = null
|
|
66
|
+
|
|
67
|
+
private readonly _model = new GenerativeModel()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
constructor( config: AutobiographicalNarratorConfig = {} ){
|
|
71
|
+
this._bus = config.bus ?? null
|
|
72
|
+
// Single-source: the seeded engine-config-narrator entity is authoritative
|
|
73
|
+
// (minIntervalTicks 50); this default is only the fallback when the entity is
|
|
74
|
+
// absent, so it must match. (Was 300 — a stale default the live mirror of 50
|
|
75
|
+
// never reached because the narrator didn't read its config. See
|
|
76
|
+
// METACOGNITION_CYCLE_TODO.md: engine-config single-source.)
|
|
77
|
+
this._minIntervalTicks = config.minIntervalTicks ?? 50
|
|
78
|
+
this._maxNarrativeLength = config.maxNarrativeLength ?? 5000
|
|
79
|
+
}
|
|
80
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
81
|
+
|
|
82
|
+
attachEpisodicConsolidator( ec: EpisodicConsolidator ): void {
|
|
83
|
+
this._episodicConsolidator = ec
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
attachSemanticIntegrator( si: SemanticIntegrator ): void {
|
|
87
|
+
this._semanticIntegrator = si
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
attachExecutiveEngine( oe: ExecutiveEngine ): void {
|
|
91
|
+
this._executiveEngine = oe
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getNarrative(): SelfNarrative {
|
|
95
|
+
return { ...this._narrative }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
subscribes(): string[] { return ["affect.state.changed","executive.prediction.formed"] }
|
|
99
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
100
|
+
|
|
101
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
102
|
+
this._model.observe( e.type, e.salience )
|
|
103
|
+
switch( e.type ){
|
|
104
|
+
case 'affect.state.changed':
|
|
105
|
+
this._affectValence = (e.payload as Record<string,number>)['valence'] ?? this._affectValence
|
|
106
|
+
break
|
|
107
|
+
case 'executive.prediction.formed': {
|
|
108
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
109
|
+
if( p.predictedDomains.includes('narrative') )
|
|
110
|
+
this._model.setPrecision( 'narrative.change', 1.0 + p.confidence * 0.5 )
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
snapshot(): Record<string, unknown> {
|
|
117
|
+
return {
|
|
118
|
+
affectValence: this._affectValence,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async react(
|
|
123
|
+
_delta: Duration,
|
|
124
|
+
tick: Tick,
|
|
125
|
+
state: ReadonlySimulationState,
|
|
126
|
+
context: SimulationContext
|
|
127
|
+
): Promise<EngineResult> {
|
|
128
|
+
// On first tick after snapshot restore, rehydrate narrative from
|
|
129
|
+
// the 'self-narrative' entity so the Will picks up where it left off.
|
|
130
|
+
if( !this._restored ){
|
|
131
|
+
this._restoreFromState( state )
|
|
132
|
+
this._restored = true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = []
|
|
136
|
+
const commands: StateCommands = { set: [], metrics: [] }
|
|
137
|
+
|
|
138
|
+
// Effective cadence = base ⊕ persona-prior (self_model.updated → narrative edge).
|
|
139
|
+
this._readConfigFromState( state )
|
|
140
|
+
|
|
141
|
+
if( tick - this._lastUpdateTick < this._minIntervalTicks )
|
|
142
|
+
return { commands }
|
|
143
|
+
|
|
144
|
+
// Try executive output first
|
|
145
|
+
const executiveOutput = this._executiveEngine?.latestOutput
|
|
146
|
+
|
|
147
|
+
// How much the narrative actually moved this pass — the surprise signal the
|
|
148
|
+
// generative model gates on, replacing the monotonic version counter.
|
|
149
|
+
let narrativeSignificance = 0
|
|
150
|
+
|
|
151
|
+
if( executiveOutput?.narrative && this._executiveEngine?.isFresh( tick ) ){
|
|
152
|
+
// Executive narrative is fresh — use it
|
|
153
|
+
this._narrative.version++
|
|
154
|
+
this._narrative.lastUpdatedAt = tick
|
|
155
|
+
this._narrative.story = ( this._narrative.story + '\n\n' + executiveOutput.narrative )
|
|
156
|
+
.slice( -this._maxNarrativeLength )
|
|
157
|
+
this._narrative.currentSelfView = executiveOutput.currentSelfView ?? this._narrative.currentSelfView
|
|
158
|
+
|
|
159
|
+
if( executiveOutput.narrativeThemes ){
|
|
160
|
+
this._narrative.themes = [
|
|
161
|
+
...new Set( [ ...this._narrative.themes, ...executiveOutput.narrativeThemes ] ),
|
|
162
|
+
].slice( -10 )
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if( executiveOutput.identityUpdates?.traits ){
|
|
166
|
+
// Apply trait deltas
|
|
167
|
+
const existingIdentity = state.entities.get('identity-self')
|
|
168
|
+
if( existingIdentity ){
|
|
169
|
+
const currentTraits = ( existingIdentity.metadata?.traits as Record<string, number> ) ?? {}
|
|
170
|
+
const updatedTraits = { ...currentTraits }
|
|
171
|
+
for( const { key: trait, value: delta } of executiveOutput.identityUpdates.traits )
|
|
172
|
+
updatedTraits[ trait ] = Math.max( 0, Math.min( 1, ( updatedTraits[ trait ] ?? 0.5 ) + delta ) )
|
|
173
|
+
|
|
174
|
+
commands.set!.push({
|
|
175
|
+
id: 'identity-self',
|
|
176
|
+
type: 'will.identity',
|
|
177
|
+
createdAt: existingIdentity.createdAt,
|
|
178
|
+
metadata: {
|
|
179
|
+
...existingIdentity.metadata,
|
|
180
|
+
traits: updatedTraits,
|
|
181
|
+
version: ( ( existingIdentity.metadata?.version as number ) ?? 1 ) + 1,
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
narrativeSignificance = executiveOutput.narrativeThemes?.length ?? 1
|
|
188
|
+
commands.metrics!.push([ 'narrative.source', 1 ])
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Heuristic narrative extension
|
|
192
|
+
const episodes = this._episodicConsolidator?.query({ limit: 20 }) ?? []
|
|
193
|
+
const significant = episodes.filter(
|
|
194
|
+
e => e.activationStrength > 0.5 || Math.abs( e.affectiveContext?.valence ?? 0 ) > 0.4
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if( significant.length > 0 ){
|
|
198
|
+
const meanValence = significant.reduce(
|
|
199
|
+
( s, e ) => s + ( e.affectiveContext?.valence ?? 0 ), 0
|
|
200
|
+
) / significant.length
|
|
201
|
+
|
|
202
|
+
const tone = meanValence > 0.3 ? 'fulfilling' : meanValence < -0.3 ? 'challenging' : 'eventful'
|
|
203
|
+
const chapter = `[Chapter ${this._narrative.version + 1}] I experienced ${significant.length} significant events. It was a ${tone} period.`
|
|
204
|
+
|
|
205
|
+
this._narrative.version++
|
|
206
|
+
this._narrative.lastUpdatedAt = tick
|
|
207
|
+
this._narrative.story = ( this._narrative.story + '\n\n' + chapter )
|
|
208
|
+
.slice( -this._maxNarrativeLength )
|
|
209
|
+
this._narrative.currentSelfView = this._heuristicSelfView( state )
|
|
210
|
+
|
|
211
|
+
for( const ep of significant.slice( 0, 3 ) ){
|
|
212
|
+
const summary = typeof ep.content === 'string'
|
|
213
|
+
? ep.content.slice( 0, 150 )
|
|
214
|
+
: JSON.stringify( ep.content ).slice( 0, 150 )
|
|
215
|
+
this._narrative.pivotalEvents.push(`[tick ${ep.timestamp}] ${summary}`)
|
|
216
|
+
}
|
|
217
|
+
if( this._narrative.pivotalEvents.length > 20 )
|
|
218
|
+
this._narrative.pivotalEvents = this._narrative.pivotalEvents.slice( -20 )
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
narrativeSignificance = significant.length
|
|
222
|
+
commands.metrics!.push([ 'narrative.source', 0 ])
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this._lastUpdateTick = tick
|
|
226
|
+
|
|
227
|
+
commands.set!.push({
|
|
228
|
+
id: 'self-narrative',
|
|
229
|
+
type: 'self_narrative',
|
|
230
|
+
metadata: {
|
|
231
|
+
story: this._narrative.story.slice( 0, this._maxNarrativeLength ),
|
|
232
|
+
themes: this._narrative.themes,
|
|
233
|
+
pivotalEvents: this._narrative.pivotalEvents.slice( 0, 20 ),
|
|
234
|
+
currentSelfView: this._narrative.currentSelfView,
|
|
235
|
+
version: this._narrative.version,
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
commands.metrics!.push(
|
|
240
|
+
[ 'narrative.version', this._narrative.version ],
|
|
241
|
+
[ 'narrative.theme_count', this._narrative.themes.length ],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
246
|
+
const _bus = this._bus
|
|
247
|
+
if( _bus && this._narrative.version > 0 ){
|
|
248
|
+
const predErr = this._model.observe( 'narrative.change', narrativeSignificance )
|
|
249
|
+
if( !predErr.gated )
|
|
250
|
+
_bus.publish({ type: 'narrative.updated', version: 1, sourceEngine: this.name, salience: Math.max( 0.2, predErr.salience ), payload: { version: this._narrative.version, significance: narrativeSignificance } })
|
|
251
|
+
}
|
|
252
|
+
return { events: events.length > 0 ? events : undefined, commands }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private _heuristicSelfView( state: ReadonlySimulationState ): string {
|
|
256
|
+
const valence = this._affectValence
|
|
257
|
+
return valence > 0.3
|
|
258
|
+
? 'I feel positive about who I am becoming.'
|
|
259
|
+
: valence < -0.3
|
|
260
|
+
? 'I am struggling with my current state.'
|
|
261
|
+
: 'I am in a period of transition.'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Effective config = base engine-config-narrator ⊕ persona-prior. Single-sourced
|
|
266
|
+
* now that the seeded base (minIntervalTicks 50) and the constructor default
|
|
267
|
+
* agree, so the consolidator's self_model.updated → narrative edge applies
|
|
268
|
+
* cleanly: a significant identity change lowers this interval and the Will
|
|
269
|
+
* re-narrates its life story sooner.
|
|
270
|
+
*/
|
|
271
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
272
|
+
const p = readEffectiveParams( state, 'engine-config-narrator' )
|
|
273
|
+
if( p.minIntervalTicks != null ) this._minIntervalTicks = p.minIntervalTicks
|
|
274
|
+
if( p.maxNarrativeLength != null ) this._maxNarrativeLength = p.maxNarrativeLength
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Rehydrate _narrative from the persisted 'self-narrative' entity.
|
|
279
|
+
* Called once on the first tick after a snapshot restore so the
|
|
280
|
+
* narrator continues from the saved story rather than the fresh-boot default.
|
|
281
|
+
*/
|
|
282
|
+
private _restoreFromState( state: ReadonlySimulationState ): void {
|
|
283
|
+
const entity = state.entities.get('self-narrative')
|
|
284
|
+
if( !entity ) return
|
|
285
|
+
const m = entity.metadata ?? {}
|
|
286
|
+
const story = ( m['story'] as string | undefined ) ?? ''
|
|
287
|
+
if( !story ) return // entity exists but empty — keep the boot default
|
|
288
|
+
|
|
289
|
+
this._narrative = {
|
|
290
|
+
story,
|
|
291
|
+
themes: ( m['themes'] as string[] ) ?? this._narrative.themes,
|
|
292
|
+
pivotalEvents: ( m['pivotalEvents'] as string[] ) ?? [],
|
|
293
|
+
currentSelfView: ( m['currentSelfView'] as string ) ?? this._narrative.currentSelfView,
|
|
294
|
+
version: ( m['version'] as number ) ?? 0,
|
|
295
|
+
lastUpdatedAt: ( m['lastUpdatedAt'] as number ) ?? 0,
|
|
296
|
+
}
|
|
297
|
+
// Prevent the narrator from immediately rewriting a just-restored narrative
|
|
298
|
+
this._lastUpdateTick = this._narrative.lastUpdatedAt
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/bias.detector.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* BiasDetector — identifies recurring patterns of error in the agent's
|
|
7
|
+
* own reasoning.
|
|
8
|
+
*
|
|
9
|
+
* Scans decision history for:
|
|
10
|
+
* - Anchoring (over-reliance on first piece of information)
|
|
11
|
+
* - Confirmation bias (seeking evidence that confirms existing beliefs)
|
|
12
|
+
* - Recency bias (overweighting recent events)
|
|
13
|
+
* - Availability bias (overweighting vivid/salient memories)
|
|
14
|
+
* - Overgeneralization (forming broad beliefs from few samples)
|
|
15
|
+
*
|
|
16
|
+
* Part of Shard 4 (Meta-Cognitive Layer) — runs periodically, synchronous.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
Duration,
|
|
21
|
+
Tick,
|
|
22
|
+
SimulationContext,
|
|
23
|
+
ReadonlySimulationState,
|
|
24
|
+
StateCommands,
|
|
25
|
+
SimulationEvent,
|
|
26
|
+
} from '#core/types'
|
|
27
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
28
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
29
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
30
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
31
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
32
|
+
|
|
33
|
+
/** Confidence added to a detected bias the executive independently named. */
|
|
34
|
+
const EXECUTIVE_CORROBORATION_BOOST = 0.15
|
|
35
|
+
|
|
36
|
+
export interface BiasDetectorConfig {
|
|
37
|
+
/** Minimum decisions before bias detection activates */
|
|
38
|
+
minDecisions?: number
|
|
39
|
+
/** Ticks between bias scans */
|
|
40
|
+
scanIntervalTicks?: number
|
|
41
|
+
/** Whether to flag biases as events */
|
|
42
|
+
emitBiasEvents?: boolean
|
|
43
|
+
bus?: CognitiveBus
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface DetectedBias {
|
|
47
|
+
type: string
|
|
48
|
+
description: string
|
|
49
|
+
confidence: number
|
|
50
|
+
supportingEvidence: string[]
|
|
51
|
+
detectedAt: Tick
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class BiasDetector implements SimulationEngine, CognitiveEngine {
|
|
55
|
+
readonly name = 'bias-detector'
|
|
56
|
+
|
|
57
|
+
private _executiveNamedBiases: Set<string> = new Set()
|
|
58
|
+
private _minDecisions: number
|
|
59
|
+
private _scanIntervalTicks: number
|
|
60
|
+
private _emitBiasEvents: boolean
|
|
61
|
+
|
|
62
|
+
private _lastScanTick: number = 0
|
|
63
|
+
private _detectedBiases: DetectedBias[] = []
|
|
64
|
+
|
|
65
|
+
private _bus: CognitiveBus | null = null
|
|
66
|
+
|
|
67
|
+
private readonly _model = new GenerativeModel()
|
|
68
|
+
|
|
69
|
+
constructor( config: BiasDetectorConfig = {} ){
|
|
70
|
+
this._bus = config.bus ?? null
|
|
71
|
+
this._minDecisions = config.minDecisions ?? 10
|
|
72
|
+
this._scanIntervalTicks = config.scanIntervalTicks ?? 100
|
|
73
|
+
this._emitBiasEvents = config.emitBiasEvents ?? true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Engine interface ─────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
79
|
+
subscribes(): string[] {
|
|
80
|
+
return [
|
|
81
|
+
'executive.self.reflection',
|
|
82
|
+
'executive.prediction.formed'
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
86
|
+
|
|
87
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
88
|
+
this._model.observe( e.type, e.salience )
|
|
89
|
+
|
|
90
|
+
switch( e.type ){
|
|
91
|
+
case 'executive.self.reflection': {
|
|
92
|
+
// Executive named biases → seed the detector's known-bias list
|
|
93
|
+
const p = e.payload as Record<string, unknown>
|
|
94
|
+
const named = ( p['identifiedBiases'] as string[] ) ?? []
|
|
95
|
+
for( const n of named )
|
|
96
|
+
this._executiveNamedBiases.add( n )
|
|
97
|
+
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
case 'executive.prediction.formed': {
|
|
101
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
102
|
+
if( p.predictedDomains.includes('metacognition') )
|
|
103
|
+
this._model.setPrecision( 'bias.novelty', 1.0 + p.confidence * 0.5 )
|
|
104
|
+
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
snapshot(): Record<string, unknown> { return { executiveNamedBiases: this._executiveNamedBiases.size } }
|
|
111
|
+
|
|
112
|
+
async react(
|
|
113
|
+
_delta: Duration,
|
|
114
|
+
tick: Tick,
|
|
115
|
+
state: ReadonlySimulationState,
|
|
116
|
+
context: SimulationContext
|
|
117
|
+
): Promise<EngineResult> {
|
|
118
|
+
const
|
|
119
|
+
events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
|
|
120
|
+
commands: StateCommands = { metrics: [] }
|
|
121
|
+
|
|
122
|
+
// Effective config = base engine-config-bias-detector ⊕ persona-prior (single-source).
|
|
123
|
+
this._readConfigFromState( state )
|
|
124
|
+
|
|
125
|
+
if( tick - this._lastScanTick < this._scanIntervalTicks )
|
|
126
|
+
return { commands }
|
|
127
|
+
|
|
128
|
+
this._lastScanTick = tick
|
|
129
|
+
|
|
130
|
+
// Scan for biases
|
|
131
|
+
const newBiases = this._scanForBiases( state )
|
|
132
|
+
|
|
133
|
+
// Update detected biases
|
|
134
|
+
for( const bias of newBiases )
|
|
135
|
+
this._integrateBias( bias )
|
|
136
|
+
|
|
137
|
+
// Persist as entities
|
|
138
|
+
for( const bias of this._detectedBiases ){
|
|
139
|
+
commands.set ??= []
|
|
140
|
+
commands.set.push({
|
|
141
|
+
id: `bias-${bias.type}-${bias.detectedAt}`,
|
|
142
|
+
type: 'cognitive_bias',
|
|
143
|
+
createdAt: bias.detectedAt,
|
|
144
|
+
metadata: {
|
|
145
|
+
biasType: bias.type,
|
|
146
|
+
description: bias.description,
|
|
147
|
+
confidence: bias.confidence,
|
|
148
|
+
evidence: bias.supportingEvidence
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
commands.metrics!.push([
|
|
154
|
+
'metacognition.biases_detected', this._detectedBiases.length
|
|
155
|
+
])
|
|
156
|
+
|
|
157
|
+
if( newBiases.length > 0 && this._emitBiasEvents )
|
|
158
|
+
events.push({
|
|
159
|
+
type: 'bias.detected',
|
|
160
|
+
source: this.name,
|
|
161
|
+
payload: { newBiases: newBiases.map( b => b.type ) },
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
166
|
+
const _bus = this._bus
|
|
167
|
+
if( _bus && this._detectedBiases.length > 0 ){
|
|
168
|
+
// Surprise is driven by how many biases *this scan* surfaced, not the
|
|
169
|
+
// cumulative count (which only grows, then saturates against the prune
|
|
170
|
+
// cap — a near-constant the gate could never read as surprising).
|
|
171
|
+
const predErr = this._model.observe( 'bias.novelty', newBiases.length )
|
|
172
|
+
if( !predErr.gated )
|
|
173
|
+
_bus.publish({
|
|
174
|
+
type: 'bias.detected',
|
|
175
|
+
version: 1,
|
|
176
|
+
sourceEngine: this.name,
|
|
177
|
+
salience: Math.max( 0.4, predErr.salience ),
|
|
178
|
+
payload: {
|
|
179
|
+
count: this._detectedBiases.length,
|
|
180
|
+
newCount: newBiases.length,
|
|
181
|
+
types: newBiases.map( b => b.type )
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { events: events.length > 0 ? events : undefined, commands }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Effective config = base engine-config-bias-detector ⊕ persona-prior. Seeded
|
|
191
|
+
* base matches the constructor defaults (no drift). Only the numeric tunables
|
|
192
|
+
* are single-sourced here; `emitBiasEvents` is a boolean (stored 1/0 in the
|
|
193
|
+
* mirror) and stays constructor-only to avoid a lossy numeric coercion.
|
|
194
|
+
*/
|
|
195
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
196
|
+
const p = readEffectiveParams( state, 'engine-config-bias-detector' )
|
|
197
|
+
|
|
198
|
+
if( p.minDecisions != null ) this._minDecisions = p.minDecisions
|
|
199
|
+
if( p.scanIntervalTicks != null ) this._scanIntervalTicks = p.scanIntervalTicks
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Bias scanning ────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
// NOTE (R2): `detectedAt` is the sim tick (state.tick), so detection replays
|
|
205
|
+
// deterministically and the field is a true Tick — no ms→tick cast.
|
|
206
|
+
private _scanForBiases( state: ReadonlySimulationState ): DetectedBias[] {
|
|
207
|
+
const biases: DetectedBias[] = []
|
|
208
|
+
|
|
209
|
+
// 1. Overgeneralization: high-confidence beliefs from few episodes
|
|
210
|
+
{
|
|
211
|
+
const
|
|
212
|
+
beliefs = [ ...state.entities.values() ].filter( e => e.type === 'belief' )
|
|
213
|
+
.map( e => ({
|
|
214
|
+
confidence: ( e.metadata?.['confidence'] as number ) ?? 0,
|
|
215
|
+
supportingEpisodes: ( e.metadata?.['supportingEpisodes'] as number ) ?? 0,
|
|
216
|
+
statement: ( e.metadata?.['statement'] as string ) ?? '',
|
|
217
|
+
})),
|
|
218
|
+
overgeneralized = beliefs.filter( b => b.confidence > 0.7 && b.supportingEpisodes < 5 )
|
|
219
|
+
|
|
220
|
+
if( overgeneralized.length > 0 )
|
|
221
|
+
biases.push({
|
|
222
|
+
type: 'overgeneralization',
|
|
223
|
+
description: `Formed ${overgeneralized.length} high-confidence beliefs from limited experience`,
|
|
224
|
+
confidence: Math.min( 0.9, overgeneralized.length * 0.2 ),
|
|
225
|
+
supportingEvidence: overgeneralized.map( b => b.statement ),
|
|
226
|
+
detectedAt: state.tick,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 2. Recency bias: check if decisions heavily weight recent memories
|
|
231
|
+
{
|
|
232
|
+
const allEpisodes = [ ...state.entities.values() ]
|
|
233
|
+
.filter( e => e.type === 'episodic_memory' )
|
|
234
|
+
.map( e => ({ timestamp: ( e.metadata?.['tick'] as number ) ?? 0 }))
|
|
235
|
+
if( allEpisodes.length > 20 ){
|
|
236
|
+
// `e.timestamp` is an episode tick, so the recency window is 50 ticks.
|
|
237
|
+
const recentCount = allEpisodes.filter( e => e.timestamp > state.tick - 50 ).length
|
|
238
|
+
|
|
239
|
+
const recentRatio = recentCount / allEpisodes.length
|
|
240
|
+
if( recentRatio > 0.6 )
|
|
241
|
+
biases.push({
|
|
242
|
+
type: 'recency_bias',
|
|
243
|
+
description: `Heavy reliance on recent experiences (${( recentRatio * 100 ).toFixed( 0 )}% of weighted decisions)`,
|
|
244
|
+
confidence: Math.min( 0.8, recentRatio ),
|
|
245
|
+
supportingEvidence: [
|
|
246
|
+
`${recentCount} of ${allEpisodes.length} weighted episodes are recent`,
|
|
247
|
+
],
|
|
248
|
+
detectedAt: state.tick,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 3. Confirmation bias: check if beliefs are self-reinforcing
|
|
254
|
+
{
|
|
255
|
+
const selfBeliefs = [ ...state.entities.values() ]
|
|
256
|
+
.filter( e => e.type === 'belief'
|
|
257
|
+
&& (e.metadata?.['category'] as string | undefined) === 'self_belief'
|
|
258
|
+
&& ((e.metadata?.['confidence'] as number | undefined) ?? 0) >= 0.5
|
|
259
|
+
)
|
|
260
|
+
.map( e => ({
|
|
261
|
+
confidence: ( e.metadata?.['confidence'] as number ) ?? 0,
|
|
262
|
+
supportingEpisodes: ( e.metadata?.['supportingEpisodes'] as number ) ?? 0,
|
|
263
|
+
statement: ( e.metadata?.['statement'] as string ) ?? '',
|
|
264
|
+
}))
|
|
265
|
+
|
|
266
|
+
// Detection: if many self-beliefs cluster around the same theme
|
|
267
|
+
// and have high confidence despite limited evidence, possible confirmation bias
|
|
268
|
+
const highConfidenceLowEvidence = selfBeliefs.filter( b => b.confidence > 0.7 && b.supportingEpisodes < 8 )
|
|
269
|
+
|
|
270
|
+
if( highConfidenceLowEvidence.length >= 3 )
|
|
271
|
+
biases.push({
|
|
272
|
+
type: 'confirmation_bias',
|
|
273
|
+
description: 'Self-beliefs may be self-reinforcing without sufficient disconfirming evidence',
|
|
274
|
+
confidence: Math.min( 0.7, highConfidenceLowEvidence.length * 0.15 ),
|
|
275
|
+
supportingEvidence: highConfidenceLowEvidence.map( b => b.statement ),
|
|
276
|
+
detectedAt: state.tick,
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 4. Decision-pattern biases (overconfidence, repetition) — consolidated here so
|
|
281
|
+
// bias-detection has a single home (introspection used to detect these too).
|
|
282
|
+
{
|
|
283
|
+
const decisions = [ ...state.entities.values() ]
|
|
284
|
+
.filter( e => e.type === 'decision.record' )
|
|
285
|
+
.map( e => ({
|
|
286
|
+
actionType: ( e.metadata?.['actionType'] as string ) ?? 'unknown',
|
|
287
|
+
confidence: ( e.metadata?.['confidence'] as number ) ?? 0.5,
|
|
288
|
+
}))
|
|
289
|
+
|
|
290
|
+
if( decisions.length >= 5 ){
|
|
291
|
+
const avgConfidence = decisions.reduce( ( s, d ) => s + d.confidence, 0 ) / decisions.length
|
|
292
|
+
if( avgConfidence > 0.8 )
|
|
293
|
+
biases.push({
|
|
294
|
+
type: 'overconfidence',
|
|
295
|
+
description: `Recent decisions made at very high average confidence (${( avgConfidence * 100 ).toFixed( 0 )}%)`,
|
|
296
|
+
confidence: Math.min( 0.8, avgConfidence ),
|
|
297
|
+
supportingEvidence: [ `${decisions.length} decisions, average confidence ${avgConfidence.toFixed( 2 )}` ],
|
|
298
|
+
detectedAt: state.tick,
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const uniqueActions = new Set( decisions.map( d => d.actionType ) )
|
|
302
|
+
if( uniqueActions.size === 1 )
|
|
303
|
+
biases.push({
|
|
304
|
+
type: 'repetition',
|
|
305
|
+
description: `Repetitive behaviour — only taking "${[ ...uniqueActions ][ 0 ]}" actions`,
|
|
306
|
+
confidence: 0.6,
|
|
307
|
+
supportingEvidence: [ `${decisions.length} consecutive "${[ ...uniqueActions ][ 0 ]}" decisions` ],
|
|
308
|
+
detectedAt: state.tick,
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Executive-corroboration: when the executive independently named a bias the
|
|
314
|
+
// detector also found this scan, raise that bias's confidence — two sources
|
|
315
|
+
// agreeing is stronger evidence. (Wires the previously-recorded-but-unused
|
|
316
|
+
// _executiveNamedBiases; Phase 1a tie-off.)
|
|
317
|
+
for( const b of biases )
|
|
318
|
+
if( this._executiveCorroborates( b.type ) )
|
|
319
|
+
b.confidence = Math.min( 1, b.confidence + EXECUTIVE_CORROBORATION_BOOST )
|
|
320
|
+
|
|
321
|
+
return biases
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* True if the executive (via executive.self.reflection) independently named a
|
|
326
|
+
* bias matching `type`. Normalised, fuzzy match — the executive names biases in
|
|
327
|
+
* free text ("recency bias") while the detector uses codes ("recency_bias").
|
|
328
|
+
*/
|
|
329
|
+
private _executiveCorroborates( type: string ): boolean {
|
|
330
|
+
if( this._executiveNamedBiases.size === 0 ) return false
|
|
331
|
+
const norm = ( s: string ): string => s.toLowerCase().replace( /[^a-z]/g, '' )
|
|
332
|
+
const t = norm( type )
|
|
333
|
+
for( const named of this._executiveNamedBiases ){
|
|
334
|
+
const n = norm( named )
|
|
335
|
+
if( n.length > 0 && ( n.includes( t ) || t.includes( n ) ) ) return true
|
|
336
|
+
}
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private _integrateBias( newBias: DetectedBias ): void {
|
|
341
|
+
const existing = this._detectedBiases.find( b => b.type === newBias.type )
|
|
342
|
+
|
|
343
|
+
if( existing ){
|
|
344
|
+
existing.confidence = ( existing.confidence + newBias.confidence ) / 2
|
|
345
|
+
existing.supportingEvidence = [
|
|
346
|
+
...existing.supportingEvidence,
|
|
347
|
+
...newBias.supportingEvidence,
|
|
348
|
+
].slice( 0, 10 )
|
|
349
|
+
}
|
|
350
|
+
else this._detectedBiases.push( newBias )
|
|
351
|
+
|
|
352
|
+
// Prune old biases
|
|
353
|
+
if( this._detectedBiases.length > 20 )
|
|
354
|
+
this._detectedBiases = this._detectedBiases.slice( -15 )
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get currently detected biases.
|
|
359
|
+
*/
|
|
360
|
+
getDetectedBiases(): ReadonlyArray<DetectedBias> {
|
|
361
|
+
return this._detectedBiases
|
|
362
|
+
}
|
|
363
|
+
}
|