@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,437 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/moral.evaluator.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MoralEvaluator — compares actions against internalized values and norms.
|
|
7
|
+
*
|
|
8
|
+
* Evaluates:
|
|
9
|
+
* - Own actions against personal standards
|
|
10
|
+
* - Others' actions against social norms
|
|
11
|
+
* - Value alignment and violation
|
|
12
|
+
* - Social comparison (am I better/worse than my standards?)
|
|
13
|
+
*
|
|
14
|
+
* Produces: guilt, shame, pride, indignation, disgust
|
|
15
|
+
*
|
|
16
|
+
* Guilt = "I did something bad" (action-focused, private)
|
|
17
|
+
* Shame = "I am bad" (self-focused, social exposure)
|
|
18
|
+
* Pride = "I did something good" (achievement against standards)
|
|
19
|
+
* Indignation = "They did something wrong" (moral anger at others)
|
|
20
|
+
* Disgust = "That violates a sacred value" (visceral moral rejection)
|
|
21
|
+
*
|
|
22
|
+
* Requires a value system — initialized with basic moral foundations
|
|
23
|
+
* and extensible through learning.
|
|
24
|
+
*
|
|
25
|
+
* Part of Shard 1 (Affective Layer) — runs every tick, synchronous.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
Duration,
|
|
30
|
+
Tick,
|
|
31
|
+
SimulationContext,
|
|
32
|
+
ReadonlySimulationState,
|
|
33
|
+
StateCommands,
|
|
34
|
+
SimulationEvent,
|
|
35
|
+
} from '#core/types'
|
|
36
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
37
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
38
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
39
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
40
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
41
|
+
|
|
42
|
+
export interface MoralEvaluatorConfig {
|
|
43
|
+
/** Moral foundations and their weights */
|
|
44
|
+
foundations?: Record<string, number>
|
|
45
|
+
/** Threshold for moral emotions to trigger events */
|
|
46
|
+
eventThreshold?: number
|
|
47
|
+
/** Rate at which moral emotions decay without re-trigger */
|
|
48
|
+
decayRate?: number
|
|
49
|
+
bus?: CognitiveBus
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Moral Foundations Theory — six core dimensions.
|
|
54
|
+
* Each foundation has a care/harm and fairness/cheating polarity.
|
|
55
|
+
*/
|
|
56
|
+
interface MoralFoundation {
|
|
57
|
+
name: string
|
|
58
|
+
weight: number // How much this foundation matters to the agent
|
|
59
|
+
threshold: number // Sensitivity — how easily triggered
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ValueViolation {
|
|
63
|
+
foundation: string
|
|
64
|
+
severity: number // 0-1: how severe the violation
|
|
65
|
+
agent: 'self' | 'other'
|
|
66
|
+
keid: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class MoralEvaluator implements SimulationEngine, CognitiveEngine {
|
|
70
|
+
readonly name = 'moral-evaluator'
|
|
71
|
+
|
|
72
|
+
private _foundations: MoralFoundation[]
|
|
73
|
+
private _eventThreshold: number
|
|
74
|
+
private _decayRate: number
|
|
75
|
+
|
|
76
|
+
// Track own action history for guilt/pride evaluation
|
|
77
|
+
private _recentOwnActions: Array<{
|
|
78
|
+
action: string
|
|
79
|
+
outcome: string
|
|
80
|
+
tick: number
|
|
81
|
+
valueAlignment: Record<string, number>
|
|
82
|
+
}> = []
|
|
83
|
+
|
|
84
|
+
private _bus: CognitiveBus | null = null
|
|
85
|
+
|
|
86
|
+
private readonly _model = new GenerativeModel()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
constructor( config: MoralEvaluatorConfig = {} ){
|
|
90
|
+
this._bus = config.bus ?? null
|
|
91
|
+
const foundationWeights = config.foundations ?? {
|
|
92
|
+
care: 0.25, // Harm/care — protection of vulnerable
|
|
93
|
+
fairness: 0.25, // Cheating/fairness — proportional outcomes
|
|
94
|
+
loyalty: 0.15, // Betrayal/loyalty — group commitment
|
|
95
|
+
authority: 0.10, // Subversion/authority — respect for tradition/order
|
|
96
|
+
sanctity: 0.10, // Degradation/sanctity — purity/disgust
|
|
97
|
+
liberty: 0.15, // Oppression/liberty — freedom from domination
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this._foundations = Object.entries( foundationWeights ).map( ([ name, weight ]) => ({
|
|
101
|
+
name,
|
|
102
|
+
weight: weight / Object.values( foundationWeights ).reduce( ( s, w ) => s + w, 0 ),
|
|
103
|
+
threshold: 0.3,
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
this._eventThreshold = config.eventThreshold ?? 0.3
|
|
107
|
+
this._decayRate = config.decayRate ?? 0.02
|
|
108
|
+
}
|
|
109
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
110
|
+
|
|
111
|
+
// ── Engine interface ─────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
114
|
+
// Effective config = base engine-config-moral ⊕ persona-prior. Channel A: a
|
|
115
|
+
// conscientious Will develops a lower eventThreshold and so registers moral
|
|
116
|
+
// self-evaluations (guilt/shame/pride) more readily — the dutifulness facet.
|
|
117
|
+
const p = readEffectiveParams( state, 'engine-config-moral' )
|
|
118
|
+
|
|
119
|
+
if( p.eventThreshold != null ) this._eventThreshold = p.eventThreshold
|
|
120
|
+
if( p.decayRate != null ) this._decayRate = p.decayRate
|
|
121
|
+
|
|
122
|
+
// Update moral foundations from flat params. Keys map 1:1 to the six MFT
|
|
123
|
+
// foundations the engine tracks: the foundation is `sanctity` (carried by
|
|
124
|
+
// `foundationSanctity`, NOT `foundationPurity`), and `liberty` must be
|
|
125
|
+
// carried too — otherwise a state override silently drops both.
|
|
126
|
+
const foundationUpdates: Record<string, number | undefined> = {
|
|
127
|
+
care: p.foundationCare,
|
|
128
|
+
fairness: p.foundationFairness,
|
|
129
|
+
loyalty: p.foundationLoyalty,
|
|
130
|
+
authority: p.foundationAuthority,
|
|
131
|
+
sanctity: p.foundationSanctity,
|
|
132
|
+
liberty: p.foundationLiberty,
|
|
133
|
+
}
|
|
134
|
+
let updated = false
|
|
135
|
+
for( const f of this._foundations )
|
|
136
|
+
if( foundationUpdates[ f.name ] != null ){ f.weight = foundationUpdates[ f.name ]!; updated = true }
|
|
137
|
+
|
|
138
|
+
// Re-normalize to the constructor's invariant (weights sum to 1.0). The
|
|
139
|
+
// override path previously assigned raw weights directly, de-normalizing
|
|
140
|
+
// the set and skewing the relative weights guilt & indignation depend on.
|
|
141
|
+
if( updated ){
|
|
142
|
+
const total = this._foundations.reduce( ( s, f ) => s + f.weight, 0 )
|
|
143
|
+
if( total > 0 ) for( const f of this._foundations ) f.weight = f.weight / total
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
subscribes(): string[] { return ['executive.prediction.formed'] }
|
|
149
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
150
|
+
|
|
151
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
152
|
+
this._model.observe( e.type, e.salience )
|
|
153
|
+
if( e.type === 'executive.prediction.formed' ){
|
|
154
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
155
|
+
if( p.predictedDomains.includes('moral') )
|
|
156
|
+
this._model.setPrecision( 'emotion.guilt', 1.0 + p.confidence * 0.5 )
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
snapshot(): Record<string, unknown> { return {} }
|
|
161
|
+
|
|
162
|
+
async react(
|
|
163
|
+
delta: Duration,
|
|
164
|
+
_tick: Tick,
|
|
165
|
+
state: ReadonlySimulationState,
|
|
166
|
+
context: SimulationContext
|
|
167
|
+
): Promise<EngineResult> {
|
|
168
|
+
this._readConfigFromState( state )
|
|
169
|
+
|
|
170
|
+
const
|
|
171
|
+
events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
|
|
172
|
+
commands: StateCommands = { metrics: [] }
|
|
173
|
+
|
|
174
|
+
// Detect value violations from percepts and own actions
|
|
175
|
+
const violations = this._detectViolations( state )
|
|
176
|
+
|
|
177
|
+
// Categorize violations by target (self vs. other)
|
|
178
|
+
const
|
|
179
|
+
selfViolations = violations.filter( v => v.agent === 'self'),
|
|
180
|
+
otherViolations = violations.filter( v => v.agent === 'other')
|
|
181
|
+
|
|
182
|
+
// Compute moral emotions
|
|
183
|
+
const previousGuilt = state.metrics.get('emotion.guilt') ?? 0
|
|
184
|
+
const previousShame = state.metrics.get('emotion.shame') ?? 0
|
|
185
|
+
const previousPride = state.metrics.get('emotion.pride') ?? 0
|
|
186
|
+
|
|
187
|
+
// Guilt: self-caused harm or fairness violation
|
|
188
|
+
const guiltRaw = this._computeGuilt( selfViolations )
|
|
189
|
+
const guilt = guiltRaw > 0.01
|
|
190
|
+
? Math.min( 1, guiltRaw )
|
|
191
|
+
: Math.max( 0, previousGuilt - this._decayRate * ( delta / 1000 ) )
|
|
192
|
+
|
|
193
|
+
// Shame: self-violation with social exposure
|
|
194
|
+
const shameRaw = this._computeShame( selfViolations, state )
|
|
195
|
+
const shame = shameRaw > 0.01
|
|
196
|
+
? Math.min( 1, shameRaw )
|
|
197
|
+
: Math.max( 0, previousShame - this._decayRate * ( delta / 1000 ) )
|
|
198
|
+
|
|
199
|
+
// Pride: self-exceeding moral standards
|
|
200
|
+
const prideRaw = this._computePride( state )
|
|
201
|
+
const pride = prideRaw > 0.01
|
|
202
|
+
? Math.min( 1, prideRaw )
|
|
203
|
+
: Math.max( 0, previousPride - this._decayRate * ( delta / 1000 ) )
|
|
204
|
+
|
|
205
|
+
// Indignation: others' violations (moral anger)
|
|
206
|
+
const indignation = this._computeIndignation( otherViolations )
|
|
207
|
+
|
|
208
|
+
// Disgust: sanctity/degradation violations
|
|
209
|
+
const disgust = this._computeDisgust( violations )
|
|
210
|
+
|
|
211
|
+
commands.metrics!.push(
|
|
212
|
+
[ 'emotion.guilt', guilt ],
|
|
213
|
+
[ 'emotion.shame', shame ],
|
|
214
|
+
[ 'emotion.pride', pride ],
|
|
215
|
+
[ 'emotion.indignation', indignation ],
|
|
216
|
+
[ 'emotion.disgust', disgust ],
|
|
217
|
+
[ 'moral.self_violations', selfViolations.length ],
|
|
218
|
+
[ 'moral.other_violations', otherViolations.length ],
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// Significant moral emotion events
|
|
222
|
+
if( guilt > this._eventThreshold )
|
|
223
|
+
events.push({
|
|
224
|
+
type: 'emotion.guilt.significant',
|
|
225
|
+
source: this.name,
|
|
226
|
+
payload: { guilt, selfViolations: selfViolations.map( v => v.foundation ) },
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if( shame > this._eventThreshold )
|
|
230
|
+
events.push({
|
|
231
|
+
type: 'emotion.shame.significant',
|
|
232
|
+
source: this.name,
|
|
233
|
+
payload: { shame },
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if( pride > this._eventThreshold )
|
|
237
|
+
events.push({
|
|
238
|
+
type: 'emotion.pride.significant',
|
|
239
|
+
source: this.name,
|
|
240
|
+
payload: { pride },
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if( indignation > 0.6 )
|
|
244
|
+
events.push({
|
|
245
|
+
type: 'emotion.indignation.elevated',
|
|
246
|
+
source: this.name,
|
|
247
|
+
payload: { indignation },
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
// Phase C + F: publish cognitive event — gated by prediction error
|
|
252
|
+
const _bus = this._bus
|
|
253
|
+
if( _bus && ( guilt > 0.4 || shame > 0.4 ) ){
|
|
254
|
+
const predErr = this._model.observe( 'emotion.guilt', guilt )
|
|
255
|
+
if( !predErr.gated )
|
|
256
|
+
_bus.publish({ type: 'emotion.guilt.elevated', version: 1, sourceEngine: this.name, salience: Math.min(1, guilt * 1.5), payload: { guilt, shame, pride } })
|
|
257
|
+
}
|
|
258
|
+
return { events: events.length > 0 ? events : undefined, commands }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Violation detection ──────────────────────────────────
|
|
262
|
+
|
|
263
|
+
private _detectViolations( state: ReadonlySimulationState ): ValueViolation[] {
|
|
264
|
+
const violations: ValueViolation[] = []
|
|
265
|
+
|
|
266
|
+
for( const entity of state.entities.values() ){
|
|
267
|
+
// Social percepts can encode moral violations
|
|
268
|
+
if( entity.type === 'percept.social'){
|
|
269
|
+
const
|
|
270
|
+
action = ( entity.metadata?.action as string ) ?? '',
|
|
271
|
+
keid = ( entity.metadata?.keid as string ) ?? '',
|
|
272
|
+
directedAtSelf = entity.metadata?.directedAtSelf === true,
|
|
273
|
+
harmful = entity.metadata?.harmful === true,
|
|
274
|
+
unfair = entity.metadata?.unfair === true,
|
|
275
|
+
deceptive = entity.metadata?.deceptive === true,
|
|
276
|
+
disrespectful = entity.metadata?.disrespectful === true
|
|
277
|
+
|
|
278
|
+
if( harmful )
|
|
279
|
+
violations.push({
|
|
280
|
+
foundation: 'care',
|
|
281
|
+
severity: 0.7,
|
|
282
|
+
agent: directedAtSelf ? 'other' : 'other',
|
|
283
|
+
keid,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
if( unfair )
|
|
287
|
+
violations.push({
|
|
288
|
+
foundation: 'fairness',
|
|
289
|
+
severity: 0.6,
|
|
290
|
+
agent: 'other',
|
|
291
|
+
keid,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if( deceptive )
|
|
295
|
+
violations.push({
|
|
296
|
+
foundation: 'fairness',
|
|
297
|
+
severity: 0.5,
|
|
298
|
+
agent: 'other',
|
|
299
|
+
keid,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
if( disrespectful )
|
|
303
|
+
violations.push({
|
|
304
|
+
foundation: 'authority',
|
|
305
|
+
severity: 0.4,
|
|
306
|
+
agent: 'other',
|
|
307
|
+
keid,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Own actions that could be violations
|
|
312
|
+
if( entity.type === 'action.own' || entity.type === 'decision.record'){
|
|
313
|
+
const
|
|
314
|
+
outcome = entity.metadata?.outcome as string ?? '',
|
|
315
|
+
harmful = entity.metadata?.harmful === true,
|
|
316
|
+
unfair = entity.metadata?.unfair === true,
|
|
317
|
+
dishonest = entity.metadata?.dishonest === true
|
|
318
|
+
|
|
319
|
+
if( harmful )
|
|
320
|
+
violations.push({
|
|
321
|
+
foundation: 'care',
|
|
322
|
+
severity: 0.65,
|
|
323
|
+
agent: 'self',
|
|
324
|
+
keid: 'agent-self',
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
if( unfair )
|
|
328
|
+
violations.push({
|
|
329
|
+
foundation: 'fairness',
|
|
330
|
+
severity: 0.55,
|
|
331
|
+
agent: 'self',
|
|
332
|
+
keid: 'agent-self',
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
if( dishonest )
|
|
336
|
+
violations.push({
|
|
337
|
+
foundation: 'fairness',
|
|
338
|
+
severity: 0.5,
|
|
339
|
+
agent: 'self',
|
|
340
|
+
keid: 'agent-self',
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return violations
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Emotion computation ──────────────────────────────────
|
|
349
|
+
|
|
350
|
+
private _computeGuilt( selfViolations: ValueViolation[] ): number {
|
|
351
|
+
if( selfViolations.length === 0 ) return 0
|
|
352
|
+
|
|
353
|
+
// Guilt is weighted by foundation importance
|
|
354
|
+
let weightedSeverity = 0
|
|
355
|
+
let totalWeight = 0
|
|
356
|
+
|
|
357
|
+
for( const violation of selfViolations ){
|
|
358
|
+
const foundation = this._foundations.find( f => f.name === violation.foundation )
|
|
359
|
+
const weight = foundation?.weight ?? 0.1
|
|
360
|
+
weightedSeverity += violation.severity * weight
|
|
361
|
+
totalWeight += weight
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return totalWeight > 0 ? weightedSeverity / totalWeight : 0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private _computeShame(
|
|
368
|
+
selfViolations: ValueViolation[],
|
|
369
|
+
state: ReadonlySimulationState
|
|
370
|
+
): number {
|
|
371
|
+
if( selfViolations.length === 0 ) return 0
|
|
372
|
+
|
|
373
|
+
// Shame = guilt + social exposure
|
|
374
|
+
const guiltLevel = this._computeGuilt( selfViolations )
|
|
375
|
+
const
|
|
376
|
+
socialPresence = state.metrics.get('social.active_agents') ?? 0,
|
|
377
|
+
evaluationThreat = state.metrics.get('social.evaluation_threat') ?? 0,
|
|
378
|
+
exposureLevel = Math.min( 1, ( socialPresence / 5 ) * 0.5 + evaluationThreat * 0.5 )
|
|
379
|
+
|
|
380
|
+
return guiltLevel * ( 0.4 + exposureLevel * 0.6 )
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private _computePride( state: ReadonlySimulationState ): number {
|
|
384
|
+
// Pride = own actions exceeded moral standards
|
|
385
|
+
let prideScore = 0
|
|
386
|
+
let actionCount = 0
|
|
387
|
+
|
|
388
|
+
for( const entity of state.entities.values() ){
|
|
389
|
+
if( entity.type !== 'action.own' && entity.type !== 'decision.record') continue
|
|
390
|
+
|
|
391
|
+
const
|
|
392
|
+
virtuous = entity.metadata?.virtuous === true,
|
|
393
|
+
helpful = entity.metadata?.helpful === true,
|
|
394
|
+
generous = entity.metadata?.generous === true,
|
|
395
|
+
honest = entity.metadata?.honest === true
|
|
396
|
+
|
|
397
|
+
if( virtuous ) prideScore += 0.4
|
|
398
|
+
if( helpful ) prideScore += 0.3
|
|
399
|
+
if( generous ) prideScore += 0.2
|
|
400
|
+
if( honest ) prideScore += 0.1
|
|
401
|
+
|
|
402
|
+
actionCount++
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return actionCount > 0
|
|
406
|
+
? Math.min( 1, prideScore / Math.max( actionCount, 1 ) )
|
|
407
|
+
: 0
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private _computeIndignation( otherViolations: ValueViolation[] ): number {
|
|
411
|
+
if( otherViolations.length === 0 ) return 0
|
|
412
|
+
|
|
413
|
+
// Indignation is moral anger at others' violations
|
|
414
|
+
let weightedSeverity = 0
|
|
415
|
+
let totalWeight = 0
|
|
416
|
+
|
|
417
|
+
for( const violation of otherViolations ){
|
|
418
|
+
const foundation = this._foundations.find( f => f.name === violation.foundation )
|
|
419
|
+
const weight = foundation?.weight ?? 0.1
|
|
420
|
+
weightedSeverity += violation.severity * weight
|
|
421
|
+
totalWeight += weight
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return totalWeight > 0
|
|
425
|
+
? Math.min( 1, ( weightedSeverity / totalWeight ) * 1.2 )
|
|
426
|
+
: 0
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private _computeDisgust( violations: ValueViolation[] ): number {
|
|
430
|
+
// Disgust is specifically for sanctity/degradation violations
|
|
431
|
+
const sanctityViolations = violations.filter( v => v.foundation === 'sanctity')
|
|
432
|
+
if( sanctityViolations.length === 0 ) return 0
|
|
433
|
+
|
|
434
|
+
const maxSeverity = Math.max( ...sanctityViolations.map( v => v.severity ) )
|
|
435
|
+
return maxSeverity
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/faculties/novelty.detector.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NoveltyDetector — compares current percepts against expectations.
|
|
7
|
+
*
|
|
8
|
+
* Computes prediction error by comparing incoming percept patterns
|
|
9
|
+
* against a running model of expected patterns. High novelty signals:
|
|
10
|
+
* - Something unexpected happened
|
|
11
|
+
* - The world has changed in a meaningful way
|
|
12
|
+
* - The current mental model needs updating
|
|
13
|
+
*
|
|
14
|
+
* Novelty drives:
|
|
15
|
+
* - Curiosity (positive valence + novelty → exploration)
|
|
16
|
+
* - Anxiety (negative valence + novelty → caution)
|
|
17
|
+
* - Learning (high novelty events are consolidated more strongly)
|
|
18
|
+
*
|
|
19
|
+
* Uses a simple exponential moving average of recent percept counts
|
|
20
|
+
* per category as the expectation baseline.
|
|
21
|
+
*
|
|
22
|
+
* Part of Shard 0 (Perceptual Layer) — runs every tick, synchronous.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
Duration,
|
|
27
|
+
Tick,
|
|
28
|
+
SimulationContext,
|
|
29
|
+
ReadonlySimulationState,
|
|
30
|
+
StateCommands,
|
|
31
|
+
SimulationEvent,
|
|
32
|
+
} from '#core/types'
|
|
33
|
+
import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
|
|
34
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
35
|
+
import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
36
|
+
import { GenerativeModel } from '#cognition/generative.model'
|
|
37
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
38
|
+
|
|
39
|
+
export interface NoveltyDetectorConfig {
|
|
40
|
+
/** Learning rate for expectation updates (0-1) */
|
|
41
|
+
learningRate?: number
|
|
42
|
+
/** How many ticks to look back for pattern comparison */
|
|
43
|
+
windowSize?: number
|
|
44
|
+
/** Threshold above which novelty is considered significant */
|
|
45
|
+
significanceThreshold?: number
|
|
46
|
+
bus?: CognitiveBus
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CategoryStats {
|
|
50
|
+
count: number
|
|
51
|
+
averageSalience: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class NoveltyDetector implements SimulationEngine, CognitiveEngine {
|
|
55
|
+
readonly name = 'novelty-detector'
|
|
56
|
+
|
|
57
|
+
private _learningRate: number
|
|
58
|
+
private _windowSize: number
|
|
59
|
+
private _significanceThreshold: number
|
|
60
|
+
|
|
61
|
+
// Expected patterns — EMA of percept counts per category
|
|
62
|
+
private _expectedCounts = new Map<string, number>()
|
|
63
|
+
private _expectedSalience = new Map<string, number>()
|
|
64
|
+
private _recentHistory: Array<Map<string, CategoryStats>> = []
|
|
65
|
+
|
|
66
|
+
private _bus: CognitiveBus | null = null
|
|
67
|
+
|
|
68
|
+
private readonly _model = new GenerativeModel()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
constructor( config: NoveltyDetectorConfig = {} ){
|
|
72
|
+
this._bus = config.bus ?? null
|
|
73
|
+
this._learningRate = config.learningRate ?? 0.1
|
|
74
|
+
this._windowSize = config.windowSize ?? 10
|
|
75
|
+
this._significanceThreshold = config.significanceThreshold ?? 0.4
|
|
76
|
+
}
|
|
77
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
78
|
+
|
|
79
|
+
// ── Engine interface ─────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
private _readConfigFromState( state: ReadonlySimulationState ): void {
|
|
82
|
+
// Effective config = base engine-config-novelty ⊕ persona-prior. Channel A: an open
|
|
83
|
+
// Will develops a lower significanceThreshold and so registers novelty more readily.
|
|
84
|
+
const p = readEffectiveParams( state, 'engine-config-novelty' )
|
|
85
|
+
if( p.learningRate != null ) this._learningRate = p.learningRate
|
|
86
|
+
if( p.windowSize != null ) this._windowSize = p.windowSize
|
|
87
|
+
if( p.significanceThreshold != null ) this._significanceThreshold = p.significanceThreshold
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
subscribes(): string[] { return ['executive.prediction.formed'] }
|
|
92
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
93
|
+
|
|
94
|
+
onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
|
|
95
|
+
this._model.observe( e.type, e.salience )
|
|
96
|
+
if( e.type === 'executive.prediction.formed' ){
|
|
97
|
+
const p = e.payload as { predictedDomains: string[]; confidence: number }
|
|
98
|
+
if( p.predictedDomains.includes('perception') )
|
|
99
|
+
this._model.setPrecision( 'novelty.score', 1.0 + p.confidence * 0.5 )
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
snapshot(): Record<string, unknown> { return {} }
|
|
104
|
+
|
|
105
|
+
async react(
|
|
106
|
+
_delta: Duration,
|
|
107
|
+
tick: Tick,
|
|
108
|
+
state: ReadonlySimulationState,
|
|
109
|
+
context: SimulationContext
|
|
110
|
+
): Promise<EngineResult> {
|
|
111
|
+
this._readConfigFromState( state )
|
|
112
|
+
|
|
113
|
+
const
|
|
114
|
+
events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
|
|
115
|
+
commands: StateCommands = { metrics: [] }
|
|
116
|
+
|
|
117
|
+
// Aggregate current percepts by category
|
|
118
|
+
const currentStats = this._aggregatePercepts( state )
|
|
119
|
+
|
|
120
|
+
// Compute novelty per category
|
|
121
|
+
let totalNovelty = 0
|
|
122
|
+
let categoriesWithNovelty = 0
|
|
123
|
+
|
|
124
|
+
for( const [ category, stats ] of currentStats ){
|
|
125
|
+
const
|
|
126
|
+
expectedCount = this._expectedCounts.get( category ) ?? 0,
|
|
127
|
+
expectedSalience = this._expectedSalience.get( category ) ?? 0.3,
|
|
128
|
+
countNovelty = this._computeNovelty( stats.count, expectedCount ),
|
|
129
|
+
salienceNovelty = this._computeNovelty( stats.averageSalience, expectedSalience ),
|
|
130
|
+
categoryNovelty = ( countNovelty + salienceNovelty ) / 2
|
|
131
|
+
|
|
132
|
+
// Update running metric per category
|
|
133
|
+
commands.metrics!.push([ `novelty.${category}`, categoryNovelty ])
|
|
134
|
+
|
|
135
|
+
if( categoryNovelty > this._significanceThreshold )
|
|
136
|
+
categoriesWithNovelty++
|
|
137
|
+
|
|
138
|
+
totalNovelty += categoryNovelty
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Overall novelty
|
|
142
|
+
const overallNovelty = currentStats.size > 0
|
|
143
|
+
? totalNovelty / currentStats.size
|
|
144
|
+
: 0
|
|
145
|
+
|
|
146
|
+
commands.metrics!.push(
|
|
147
|
+
[ 'perception.novelty', overallNovelty ],
|
|
148
|
+
[ 'novelty.significant_categories', categoriesWithNovelty ],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// Significant novelty event
|
|
152
|
+
if( overallNovelty > this._significanceThreshold )
|
|
153
|
+
events.push({
|
|
154
|
+
type: 'novelty.significant',
|
|
155
|
+
source: this.name,
|
|
156
|
+
payload: {
|
|
157
|
+
overallNovelty,
|
|
158
|
+
categoriesWithNovelty,
|
|
159
|
+
tick,
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Update expectations for next tick
|
|
164
|
+
this._updateExpectations( currentStats )
|
|
165
|
+
this._updateHistory( currentStats )
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// Phase C: publish cognitive event
|
|
169
|
+
const _bus = this._bus
|
|
170
|
+
if( _bus && overallNovelty > 0.3 )
|
|
171
|
+
_bus.publish({ type: 'perception.novelty.spike', version: 1, sourceEngine: this.name, salience: Math.min(1, overallNovelty * 2), payload: { novelty: overallNovelty } })
|
|
172
|
+
// Phase D + F: rich state-change event — gated by prediction error
|
|
173
|
+
if( _bus ){
|
|
174
|
+
const predErr = this._model.observe( 'novelty.score', overallNovelty )
|
|
175
|
+
if( !predErr.gated )
|
|
176
|
+
_bus.publish({ type: 'novelty.state.changed', version: 1, sourceEngine: this.name, salience: predErr.salience, payload: { novelty: overallNovelty, socialEvaluationThreat: state.metrics.get('social.evaluation_threat') ?? 0, activeAgents: state.metrics.get('social.active_agents') ?? 0, fearLevel: state.metrics.get('emotion.fear') ?? 0 } })
|
|
177
|
+
}
|
|
178
|
+
return { events: events.length > 0 ? events : undefined, commands }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Internal ─────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Aggregate current percept entities by category.
|
|
185
|
+
*/
|
|
186
|
+
private _aggregatePercepts( state: ReadonlySimulationState ): Map<string, CategoryStats> {
|
|
187
|
+
const stats = new Map<string, CategoryStats>()
|
|
188
|
+
|
|
189
|
+
for( const entity of state.entities.values() ){
|
|
190
|
+
if( entity.type !== 'percept' && entity.type !== 'percept.social')
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
const category = ( entity.metadata?.category as string ) ?? 'unknown'
|
|
194
|
+
const salience = ( entity.metadata?.salience as number ) ?? 0.3
|
|
195
|
+
|
|
196
|
+
const existing = stats.get( category )
|
|
197
|
+
if( existing ){
|
|
198
|
+
existing.count++
|
|
199
|
+
existing.averageSalience = ( existing.averageSalience * ( existing.count - 1 ) + salience ) / existing.count
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
stats.set( category, { count: 1, averageSalience: salience } )
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return stats
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Compute novelty as normalized deviation from expected value.
|
|
211
|
+
*/
|
|
212
|
+
private _computeNovelty( current: number, expected: number ): number {
|
|
213
|
+
if( expected === 0 )
|
|
214
|
+
return current > 0 ? 1.0 : 0.0
|
|
215
|
+
|
|
216
|
+
const deviation = Math.abs( current - expected )
|
|
217
|
+
const normalized = deviation / Math.max( expected, 1 )
|
|
218
|
+
|
|
219
|
+
return Math.min( 1, normalized )
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update exponential moving averages for each category.
|
|
224
|
+
*/
|
|
225
|
+
private _updateExpectations( stats: Map<string, CategoryStats> ): void {
|
|
226
|
+
// Update for observed categories
|
|
227
|
+
for( const [ category, s ] of stats ){
|
|
228
|
+
const prevCount = this._expectedCounts.get( category ) ?? 0,
|
|
229
|
+
prevSalience = this._expectedSalience.get( category ) ?? 0.3
|
|
230
|
+
|
|
231
|
+
this._expectedCounts.set(
|
|
232
|
+
category,
|
|
233
|
+
prevCount + this._learningRate * ( s.count - prevCount )
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
this._expectedSalience.set(
|
|
237
|
+
category,
|
|
238
|
+
prevSalience + this._learningRate * ( s.averageSalience - prevSalience )
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Decay expectations for unobserved categories
|
|
243
|
+
for( const [ category, count ] of this._expectedCounts ){
|
|
244
|
+
if( !stats.has( category ) )
|
|
245
|
+
this._expectedCounts.set( category, count * ( 1 - this._learningRate * 0.5 ) )
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private _updateHistory( stats: Map<string, CategoryStats> ): void {
|
|
250
|
+
this._recentHistory.push( new Map( stats ) )
|
|
251
|
+
if( this._recentHistory.length > this._windowSize )
|
|
252
|
+
this._recentHistory.shift()
|
|
253
|
+
}
|
|
254
|
+
}
|