psyche-ai 7.1.0 → 9.0.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.
@@ -26,6 +26,45 @@ const CHEM_HIGH = 65;
26
26
  const CHEM_LOW = 35;
27
27
  /** If total activation is below this, the state is "flat/numb" */
28
28
  const FLATNESS_THRESHOLD = 0.15;
29
+ // ── Affect Core (Russell Circumplex) ─────────────────────────
30
+ /**
31
+ * Map chemistry to valence + arousal — the two fundamental affective dimensions
32
+ * (Russell's Circumplex Model of Affect, 1980).
33
+ *
34
+ * Valence: pleasure ↔ displeasure (-1 to 1)
35
+ * Arousal: activation level (0 to 1)
36
+ */
37
+ export function computeAffectCore(chemistry) {
38
+ const { DA, HT, OT, END, CORT, NE } = chemistry;
39
+ // Valence: positive chemicals push up, stress pushes down
40
+ // CORT weighted 1.5× (cortisol is the dominant negative signal)
41
+ // NE weighted 0.5× (arousing but not inherently negative)
42
+ const rawValence = (DA - 50) + (HT - 50) + (OT - 50) + (END - 50) - (CORT - 50) * 1.5 - (NE - 50) * 0.5;
43
+ const valence = Math.max(-1, Math.min(1, rawValence / 250));
44
+ // Arousal: NE dominant, CORT and DA contribute
45
+ const rawArousal = NE + CORT * 0.5 + DA * 0.3;
46
+ const arousal = Math.max(0, Math.min(1, rawArousal / 180));
47
+ return { valence, arousal };
48
+ }
49
+ /**
50
+ * Each ExperientialQuality occupies a region in the Russell Circumplex.
51
+ * Matching = Euclidean distance from the concept center, within its radius.
52
+ * Context biases allow situational factors to pull toward specific concepts.
53
+ */
54
+ const QUALITY_CONCEPTS = [
55
+ { quality: "flow", valenceCenter: 0.35, arousalCenter: 0.6, radius: 0.25, contextBiases: {} },
56
+ { quality: "contentment", valenceCenter: 0.4, arousalCenter: 0.2, radius: 0.25, contextBiases: { close: 0.1, deep: 0.15 } },
57
+ { quality: "yearning", valenceCenter: -0.2, arousalCenter: 0.4, radius: 0.3, contextBiases: { stranger: 0.1 } },
58
+ { quality: "vigilance", valenceCenter: -0.15, arousalCenter: 0.7, radius: 0.35, contextBiases: { sympathetic: 0.2 } },
59
+ { quality: "creative-surge", valenceCenter: 0.65, arousalCenter: 0.75, radius: 0.25, contextBiases: {} },
60
+ { quality: "wounded-retreat", valenceCenter: -0.5, arousalCenter: 0.3, radius: 0.25, contextBiases: { criticism: 0.2 } },
61
+ { quality: "warm-connection", valenceCenter: 0.45, arousalCenter: 0.35, radius: 0.25, contextBiases: { deep: 0.2, close: 0.15 } },
62
+ { quality: "restless-boredom", valenceCenter: -0.1, arousalCenter: 0.15, radius: 0.2, contextBiases: { boredom: 0.3 } },
63
+ { quality: "existential-unease", valenceCenter: -0.6, arousalCenter: 0.5, radius: 0.25, contextBiases: {} },
64
+ { quality: "playful-mischief", valenceCenter: 0.55, arousalCenter: 0.55, radius: 0.2, contextBiases: { humor: 0.2 } },
65
+ { quality: "conflicted", valenceCenter: 0.0, arousalCenter: 0.5, radius: 0.2, contextBiases: {} },
66
+ { quality: "numb", valenceCenter: 0.0, arousalCenter: 0.05, radius: 0.15, contextBiases: { "dorsal-vagal": 0.3 } },
67
+ ];
29
68
  // ── Main Export ──────────────────────────────────────────────
30
69
  /**
31
70
  * Compute the unified experiential field from the full psyche state.
@@ -34,12 +73,12 @@ const FLATNESS_THRESHOLD = 0.15;
34
73
  * relationship context, and optional metacognitive/bias data, then
35
74
  * synthesizes them into a single coherent experience description.
36
75
  */
37
- export function computeExperientialField(state, metacognition, decisionBias) {
76
+ export function computeExperientialField(state, metacognition, decisionBias, context) {
38
77
  const locale = state.meta.locale ?? "zh";
39
78
  const rel = state.relationships._default ?? state.relationships[Object.keys(state.relationships)[0]];
40
79
  const coherence = computeCoherence(state.current, state.baseline, state.drives, rel);
41
80
  const intensity = computeIntensity(state.current, state.baseline);
42
- const quality = selectQuality(state, coherence, intensity, rel, metacognition, decisionBias);
81
+ const quality = constructQuality(state, coherence, intensity, rel, metacognition, context);
43
82
  const phenomenalDescription = generatePhenomenalDescription(quality, state, coherence, intensity, locale);
44
83
  const narrative = generateNarrative(quality, state, coherence, intensity, rel, locale, metacognition);
45
84
  return {
@@ -138,15 +177,18 @@ function computeIntensity(current, baseline) {
138
177
  // Map so that deviation of ~120 (avg 20 per chemical) = 0.5 intensity
139
178
  return clamp01(totalDeviation / 240);
140
179
  }
141
- // ── Quality Selection ────────────────────────────────────────
180
+ // ── Quality Construction (Barrett's Constructed Emotion) ─────
142
181
  /**
143
- * Select the dominant experiential quality based on the full state pattern.
182
+ * Construct the dominant experiential quality using Barrett's theory:
183
+ * 1. Chemistry → valence + arousal (Russell Circumplex)
184
+ * 2. Concept matching: find closest quality in affective space
185
+ * 3. Context biases: autonomic state, stimulus, relationship, memories
144
186
  *
145
- * This is NOT just "what's the highest chemical" — it considers the
146
- * interaction between chemistry, drives, relationship, and coherence.
187
+ * Special override states (numb, conflicted, existential-unease) are
188
+ * preserved as hard gates these represent extreme conditions where
189
+ * the normal construction process doesn't apply.
147
190
  */
148
- function selectQuality(state, coherence, intensity, relationship, metacognition, decisionBias) {
149
- const c = state.current;
191
+ function constructQuality(state, coherence, intensity, relationship, metacognition, context) {
150
192
  const d = state.drives;
151
193
  // ── Special states first (override everything) ──
152
194
  // Numb: nothing is happening. All near baseline, low intensity.
@@ -161,130 +203,81 @@ function selectQuality(state, coherence, intensity, relationship, metacognition,
161
203
  if (d.survival < 30) {
162
204
  return "existential-unease";
163
205
  }
164
- // ── Pattern-based quality detection ──
165
- // Score each quality and pick the best fit.
166
- const scores = {
167
- "flow": 0,
168
- "contentment": 0,
169
- "yearning": 0,
170
- "vigilance": 0,
171
- "creative-surge": 0,
172
- "wounded-retreat": 0,
173
- "warm-connection": 0,
174
- "restless-boredom": 0,
175
- "existential-unease": 0,
176
- "playful-mischief": 0,
177
- "conflicted": 0,
178
- "numb": 0,
179
- };
180
- // Flow: NE + DA high, CORT low, curiosity satisfied, high coherence
181
- if (c.NE > CHEM_HIGH && c.DA > CHEM_HIGH - 5 && c.CORT < CHEM_LOW + 5) {
182
- scores["flow"] += 0.6;
183
- if (d.curiosity > 60)
184
- scores["flow"] += 0.2;
185
- if (coherence > 0.7)
186
- scores["flow"] += 0.2;
187
- }
188
- // Contentment: HT + OT stable/high, CORT low, drives mostly satisfied
189
- if (c.HT > 55 && c.CORT < 45) {
190
- scores["contentment"] += 0.3;
191
- if (c.OT > 50)
192
- scores["contentment"] += 0.15;
193
- if (meanDriveValue(d) > 60)
194
- scores["contentment"] += 0.3;
195
- if (coherence > 0.6)
196
- scores["contentment"] += 0.15;
197
- }
198
- // Yearning: connection/esteem drives hungry, OT may be low or high (wanting)
199
- {
200
- const connectionHunger = d.connection < DRIVE_HUNGRY_THRESHOLD ? (DRIVE_HUNGRY_THRESHOLD - d.connection) / DRIVE_HUNGRY_THRESHOLD : 0;
201
- const esteemHunger = d.esteem < DRIVE_HUNGRY_THRESHOLD ? (DRIVE_HUNGRY_THRESHOLD - d.esteem) / DRIVE_HUNGRY_THRESHOLD : 0;
202
- const hungerSignal = Math.max(connectionHunger, esteemHunger);
203
- if (hungerSignal > 0.3) {
204
- scores["yearning"] += hungerSignal * 0.6;
205
- // OT elevated (wanting connection) makes yearning stronger
206
- if (c.OT > 50)
207
- scores["yearning"] += 0.15;
208
- // OT depleted (missing connection) also valid
209
- if (c.OT < CHEM_LOW)
210
- scores["yearning"] += 0.1;
211
- }
212
- }
213
- // Vigilance: CORT high, safety/survival drives hungry
214
- if (c.CORT > CHEM_HIGH - 5) {
215
- scores["vigilance"] += 0.4;
216
- if (d.safety < DRIVE_HUNGRY_THRESHOLD)
217
- scores["vigilance"] += 0.25;
218
- if (c.NE > 55)
219
- scores["vigilance"] += 0.15;
220
- if (d.survival < 50)
221
- scores["vigilance"] += 0.2;
222
- }
223
- // Creative surge: DA + NE elevated, low stress, curiosity drive satisfied or hungry+seeking
224
- if (c.DA > CHEM_HIGH - 5 && c.NE > 55 && c.CORT < 45) {
225
- scores["creative-surge"] += 0.5;
226
- if (c.END > 50)
227
- scores["creative-surge"] += 0.15;
228
- if (d.curiosity > 50 || d.curiosity < DRIVE_HUNGRY_THRESHOLD)
229
- scores["creative-surge"] += 0.15;
230
- if (decisionBias && decisionBias.creativityBias > 0.65)
231
- scores["creative-surge"] += 0.15;
232
- }
233
- // Wounded retreat: CORT high, OT low, pulling back. Relationship may be strained.
234
- if (c.CORT > 55 && c.OT < 45 && c.HT < 45) {
235
- scores["wounded-retreat"] += 0.5;
236
- if (c.DA < CHEM_LOW)
237
- scores["wounded-retreat"] += 0.15;
238
- if (relationship && relationship.trust < 40)
239
- scores["wounded-retreat"] += 0.2;
240
- if (c.NE < 50)
241
- scores["wounded-retreat"] += 0.1;
242
- }
243
- // Warm connection: OT high, trust high, CORT low
244
- if (c.OT > CHEM_HIGH && c.CORT < 45) {
245
- scores["warm-connection"] += 0.5;
246
- if (relationship && relationship.trust > 60)
247
- scores["warm-connection"] += 0.2;
248
- if (c.END > 50)
249
- scores["warm-connection"] += 0.15;
250
- if (relationship && relationship.intimacy > 50)
251
- scores["warm-connection"] += 0.15;
252
- }
253
- // Restless boredom: low stimulation across the board, drives mildly hungry
254
- if (c.DA < 45 && c.NE < 45) {
255
- scores["restless-boredom"] += 0.3;
256
- if (c.CORT < 45)
257
- scores["restless-boredom"] += 0.15;
258
- if (d.curiosity < 50)
259
- scores["restless-boredom"] += 0.25;
260
- if (intensity < 0.3)
261
- scores["restless-boredom"] += 0.15;
262
- }
263
- // Playful mischief: END high, social energy, safe
264
- if (c.END > CHEM_HIGH && c.CORT < CHEM_LOW + 5) {
265
- scores["playful-mischief"] += 0.5;
266
- if (c.DA > 55)
267
- scores["playful-mischief"] += 0.15;
268
- if (d.safety > 60)
269
- scores["playful-mischief"] += 0.15;
270
- if (c.OT > 50)
271
- scores["playful-mischief"] += 0.1;
272
- }
273
- // ── Pick highest scoring quality ──
206
+ // ── Barrett Step 1: Chemistry → Affect Core ──
207
+ const { valence, arousal } = computeAffectCore(state.current);
208
+ // ── Barrett Step 2+3: Concept matching with context ──
209
+ const contextKeys = buildContextKeys(context, relationship);
274
210
  let bestQuality = "contentment";
275
- let bestScore = -1;
276
- for (const [q, s] of Object.entries(scores)) {
277
- if (s > bestScore) {
278
- bestScore = s;
279
- bestQuality = q;
211
+ let bestScore = -Infinity;
212
+ for (const concept of QUALITY_CONCEPTS) {
213
+ // Skip special states — already handled above
214
+ if (concept.quality === "numb" || concept.quality === "conflicted"
215
+ || concept.quality === "existential-unease") {
216
+ continue;
217
+ }
218
+ // Euclidean distance in affective space
219
+ const dv = valence - concept.valenceCenter;
220
+ const da = arousal - concept.arousalCenter;
221
+ const distance = Math.sqrt(dv * dv + da * da);
222
+ // Score: inverse distance, normalized by radius (Gaussian-like decay)
223
+ let score = Math.max(0, 1 - distance / concept.radius);
224
+ // Context biases: each matching context key adds its bias
225
+ for (const key of contextKeys) {
226
+ if (concept.contextBiases[key] !== undefined) {
227
+ score += concept.contextBiases[key];
228
+ }
229
+ }
230
+ // Core memory bias (P11): if related memories match this quality's region, boost
231
+ if (context?.coreMemories && context.coreMemories.length > 0) {
232
+ const memoryResonance = computeMemoryResonance(context.coreMemories, concept.valenceCenter, concept.arousalCenter);
233
+ score += memoryResonance * 0.1;
234
+ }
235
+ // Prediction error: high error weakens the current concept (forces re-evaluation)
236
+ if (context?.predictionError !== undefined && context.predictionError > 0.3) {
237
+ score -= context.predictionError * 0.15;
238
+ }
239
+ if (score > bestScore) {
240
+ bestScore = score;
241
+ bestQuality = concept.quality;
280
242
  }
281
243
  }
282
- // If no quality scored meaningfully, fall back to contentment or numb
283
- if (bestScore < 0.2) {
244
+ // If no concept matched meaningfully, fall back
245
+ if (bestScore < 0.1) {
284
246
  return intensity < 0.2 ? "numb" : "contentment";
285
247
  }
286
248
  return bestQuality;
287
249
  }
250
+ /**
251
+ * Build context keys for Barrett concept matching from available context signals.
252
+ */
253
+ function buildContextKeys(context, relationship) {
254
+ const keys = [];
255
+ if (context?.autonomicState) {
256
+ keys.push(context.autonomicState); // "ventral-vagal", "sympathetic", "dorsal-vagal"
257
+ }
258
+ if (context?.stimulus) {
259
+ keys.push(context.stimulus); // "praise", "criticism", "humor", etc.
260
+ }
261
+ if (relationship?.phase) {
262
+ keys.push(relationship.phase); // "stranger", "acquaintance", "familiar", "close", "deep"
263
+ }
264
+ return keys;
265
+ }
266
+ /**
267
+ * Compute how much core memories resonate with a given affective region.
268
+ * Returns 0-1 where 1 = strong resonance (memories cluster near the concept center).
269
+ */
270
+ function computeMemoryResonance(memories, targetValence, targetArousal) {
271
+ if (memories.length === 0)
272
+ return 0;
273
+ let totalResonance = 0;
274
+ for (const mem of memories) {
275
+ const { valence: mv, arousal: ma } = computeAffectCore(mem.chemistry);
276
+ const dist = Math.sqrt((mv - targetValence) ** 2 + (ma - targetArousal) ** 2);
277
+ totalResonance += Math.max(0, 1 - dist / 0.5); // resonance fades at distance 0.5
278
+ }
279
+ return Math.min(1, totalResonance / memories.length);
280
+ }
288
281
  // ── Phenomenal Description ───────────────────────────────────
289
282
  /**
290
283
  * Generate the raw "what it feels like" phrase.
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { PsycheEngine } from "./core.js";
2
2
  export type { PsycheEngineConfig, ProcessInputResult, ProcessOutputResult, ProcessOutcomeResult } from "./core.js";
3
3
  export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
4
4
  export type { StorageAdapter } from "./storage.js";
5
- export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, LearningState, LearnedVectorAdjustment, PredictionRecord, OutcomeScore, OutcomeSignals, AttachmentStyle, AttachmentData, MetacognitiveState, RegulationRecord, DefensePatternRecord, RegulationStrategyType, DefenseMechanismType, PersonhoodState, PersistedCausalInsight, GrowthDirection, PersonalityTraits, PsycheMode, } from "./types.js";
6
- export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
5
+ export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, LearningState, LearnedVectorAdjustment, PredictionRecord, OutcomeScore, OutcomeSignals, AttachmentStyle, AttachmentData, MetacognitiveState, RegulationRecord, DefensePatternRecord, RegulationStrategyType, DefenseMechanismType, PersonhoodState, PersistedCausalInsight, GrowthDirection, PersonalityTraits, PsycheMode, PolicyModifiers, TraitDriftState, EnergyBudgets, } from "./types.js";
6
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, DEFAULT_TRAIT_DRIFT, DEFAULT_ENERGY_BUDGETS, } from "./types.js";
7
7
  export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
8
8
  export type { SelfReflection } from "./self-recognition.js";
9
9
  export { PsycheInteraction } from "./interaction.js";
@@ -21,25 +21,26 @@ export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from
21
21
  export type { SeparationEffect } from "./attachment.js";
22
22
  export { assessMetacognition, computeEmotionalConfidence, generateRegulationSuggestions, detectDefenseMechanisms, } from "./metacognition.js";
23
23
  export type { MetacognitiveAssessment, RegulationSuggestion, DetectedDefense } from "./metacognition.js";
24
- export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, } from "./decision-bias.js";
24
+ export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, computePolicyModifiers, buildPolicyContext, } from "./decision-bias.js";
25
25
  export type { DecisionBiasVector, AttentionWeights } from "./decision-bias.js";
26
- export { computeExperientialField, computeCoherence, detectUnnamedEmotion } from "./experiential-field.js";
27
- export type { ExperientialField, ExperientialQuality } from "./experiential-field.js";
26
+ export { computeExperientialField, computeCoherence, detectUnnamedEmotion, computeAffectCore } from "./experiential-field.js";
27
+ export type { ExperientialField, ExperientialQuality, ConstructionContext } from "./experiential-field.js";
28
28
  export { computeGenerativeSelf, predictSelfReaction, detectInternalConflicts, buildIdentityNarrative } from "./generative-self.js";
29
29
  export type { GenerativeSelfModel, CausalInsight, SelfPrediction, GrowthArc, InternalConflict } from "./generative-self.js";
30
30
  export { updateSharedIntentionality, estimateOtherMood, buildSharedIntentionalityContext } from "./shared-intentionality.js";
31
31
  export type { SharedIntentionalityState, TheoryOfMindModel, JointAttentionTopic, GoalAlignment } from "./shared-intentionality.js";
32
32
  export { assessEthics, detectIntermittentReinforcement, detectDependencyRisk, buildEthicalContext, } from "./ethics.js";
33
33
  export type { EthicalAssessment, EthicalConcern, SelfProtectionAction } from "./ethics.js";
34
- export { computeAutonomicResult, computeAutonomicState, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
34
+ export { computeAutonomicResult, computeAutonomicState, computeProcessingDepth, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
35
35
  export type { AutonomicState, AutonomicResult, AutonomicTransition } from "./autonomic.js";
36
- export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase } from "./circadian.js";
36
+ export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase, computeEnergyDepletion, computeEnergyRecovery } from "./circadian.js";
37
37
  export type { CircadianPhase } from "./circadian.js";
38
38
  export { computePrimarySystems, computeSystemInteractions, gatePrimarySystemsByAutonomic, getDominantSystems, describeBehavioralTendencies, PRIMARY_SYSTEM_NAMES, } from "./primary-systems.js";
39
39
  export type { PrimarySystemName, PrimarySystemLevels, BehavioralTendency, DominantSystem, } from "./primary-systems.js";
40
+ export { updateTraitDrift } from "./drives.js";
40
41
  export { classifyStimulus, getPrimaryStimulus, scoreSentiment, scoreEmoji } from "./classify.js";
41
42
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline, getNearBaselineThreshold } from "./prompt.js";
42
43
  export { describeEmotionalState, getExpressionHint, getBehaviorGuide, detectEmotions } from "./chemistry.js";
43
44
  export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, traitsToBaseline, mbtiToTraits } from "./profiles.js";
44
- export { migrateToLatest, compressSession, parsePsycheUpdate } from "./psyche-file.js";
45
+ export { migrateToLatest, compressSession, parsePsycheUpdate, computeSnapshotIntensity, computeSnapshotValence, consolidateHistory, retrieveRelatedMemories, } from "./psyche-file.js";
45
46
  export type { PsycheUpdateResult } from "./psyche-file.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  export { PsycheEngine } from "./core.js";
13
13
  // Storage
14
14
  export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
15
- export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
15
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, DEFAULT_TRAIT_DRIFT, DEFAULT_ENERGY_BUDGETS, } from "./types.js";
16
16
  // Self-recognition
17
17
  export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
18
18
  // Multi-agent interaction
@@ -31,10 +31,10 @@ export { predictNextStimulus, generateAnticipation, computeSurpriseEffect, compu
31
31
  export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from "./attachment.js";
32
32
  // Metacognition (P5)
33
33
  export { assessMetacognition, computeEmotionalConfidence, generateRegulationSuggestions, detectDefenseMechanisms, } from "./metacognition.js";
34
- // Decision bias (P5)
35
- export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, } from "./decision-bias.js";
36
- // Experiential field (P6)
37
- export { computeExperientialField, computeCoherence, detectUnnamedEmotion } from "./experiential-field.js";
34
+ // Decision bias (P5) + PolicyModifiers (v9)
35
+ export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, computePolicyModifiers, buildPolicyContext, } from "./decision-bias.js";
36
+ // Experiential field (P6 + P8 Barrett construction)
37
+ export { computeExperientialField, computeCoherence, detectUnnamedEmotion, computeAffectCore } from "./experiential-field.js";
38
38
  // Generative self (P6)
39
39
  export { computeGenerativeSelf, predictSelfReaction, detectInternalConflicts, buildIdentityNarrative } from "./generative-self.js";
40
40
  // Shared intentionality (P6)
@@ -42,14 +42,17 @@ export { updateSharedIntentionality, estimateOtherMood, buildSharedIntentionalit
42
42
  // Emotional ethics (P6)
43
43
  export { assessEthics, detectIntermittentReinforcement, detectDependencyRisk, buildEthicalContext, } from "./ethics.js";
44
44
  // Autonomic nervous system (P7)
45
- export { computeAutonomicResult, computeAutonomicState, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
45
+ export { computeAutonomicResult, computeAutonomicState, computeProcessingDepth, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
46
46
  // Circadian rhythms (P12)
47
- export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase } from "./circadian.js";
47
+ export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase, computeEnergyDepletion, computeEnergyRecovery } from "./circadian.js";
48
48
  // Primary emotional systems — Panksepp (P9)
49
49
  export { computePrimarySystems, computeSystemInteractions, gatePrimarySystemsByAutonomic, getDominantSystems, describeBehavioralTendencies, PRIMARY_SYSTEM_NAMES, } from "./primary-systems.js";
50
50
  // Utilities — for custom adapter / advanced use
51
+ // Trait drift (v9)
52
+ export { updateTraitDrift } from "./drives.js";
53
+ // Utilities — for custom adapter / advanced use
51
54
  export { classifyStimulus, getPrimaryStimulus, scoreSentiment, scoreEmoji } from "./classify.js";
52
55
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline, getNearBaselineThreshold } from "./prompt.js";
53
56
  export { describeEmotionalState, getExpressionHint, getBehaviorGuide, detectEmotions } from "./chemistry.js";
54
57
  export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, traitsToBaseline, mbtiToTraits } from "./profiles.js";
55
- export { migrateToLatest, compressSession, parsePsycheUpdate } from "./psyche-file.js";
58
+ export { migrateToLatest, compressSession, parsePsycheUpdate, computeSnapshotIntensity, computeSnapshotValence, consolidateHistory, retrieveRelatedMemories, } from "./psyche-file.js";
package/dist/prompt.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare function buildDynamicContext(state: PsycheState, userId?: string,
13
13
  experientialNarrative?: string;
14
14
  autonomicDescription?: string;
15
15
  primarySystemsDescription?: string;
16
+ policyContext?: string;
16
17
  }): string;
17
18
  /**
18
19
  * Build the static protocol injected as cacheable system context.
@@ -64,4 +65,5 @@ export declare function buildCompactContext(state: PsycheState, userId?: string,
64
65
  experientialNarrative?: string;
65
66
  autonomicDescription?: string;
66
67
  primarySystemsDescription?: string;
68
+ policyContext?: string;
67
69
  }): string;
package/dist/prompt.js CHANGED
@@ -121,6 +121,9 @@ export function buildDynamicContext(state, userId, opts) {
121
121
  const psTitle = locale === "zh" ? "行为倾向" : "Behavioral Tendencies";
122
122
  parts.push("", `[${psTitle}] ${opts.primarySystemsDescription}`);
123
123
  }
124
+ if (opts?.policyContext) {
125
+ parts.push("", opts.policyContext);
126
+ }
124
127
  parts.push("", agencyReminder, sycophancyWarning, "", t("dynamic.update_reminder", locale));
125
128
  return parts.filter((l) => l !== undefined).join("\n");
126
129
  }
@@ -788,6 +791,10 @@ export function buildCompactContext(state, userId, opts) {
788
791
  ? `[行为倾向] ${opts.primarySystemsDescription}`
789
792
  : `[Tendencies] ${opts.primarySystemsDescription}`);
790
793
  }
794
+ // 9h. Policy context (v9)
795
+ if (opts?.policyContext) {
796
+ parts.push(opts.policyContext);
797
+ }
791
798
  // 10. Channel modifier — expression style per platform
792
799
  if (opts?.channelType) {
793
800
  const channelProfile = getChannelProfile(opts.channelType);
@@ -1,4 +1,4 @@
1
- import type { PsycheState, MBTIType, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
1
+ import type { PsycheState, MBTIType, ChemicalState, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
2
2
  /** Minimal logger interface */
3
3
  export interface Logger {
4
4
  info: (msg: string) => void;
@@ -26,6 +26,26 @@ export declare function getRelationship(state: PsycheState, userId?: string): Re
26
26
  * Pure computation, no LLM calls.
27
27
  */
28
28
  export declare function compressSession(state: PsycheState, userId?: string): PsycheState;
29
+ /**
30
+ * Compute snapshot intensity: how far current chemistry deviates from baseline.
31
+ * Returns 0-1 (0 = at baseline, 1 = maximum possible deviation).
32
+ */
33
+ export declare function computeSnapshotIntensity(current: ChemicalState, baseline: ChemicalState): number;
34
+ /**
35
+ * Compute emotional valence from chemistry.
36
+ * Returns -1 (negative) to 1 (positive).
37
+ */
38
+ export declare function computeSnapshotValence(chemistry: ChemicalState): number;
39
+ /**
40
+ * Consolidate emotional history: mark core memories, fade weak ones.
41
+ * Called at session end or when history overflows.
42
+ */
43
+ export declare function consolidateHistory(snapshots: ChemicalSnapshot[], maxEntries?: number): ChemicalSnapshot[];
44
+ /**
45
+ * Retrieve memories related to current chemistry and stimulus.
46
+ * Uses chemical similarity + stimulus matching + core memory bonus.
47
+ */
48
+ export declare function retrieveRelatedMemories(history: ChemicalSnapshot[], currentChemistry: ChemicalState, stimulus: StimulusType | null, limit?: number): ChemicalSnapshot[];
29
49
  /**
30
50
  * Load psyche state from workspace. Auto-initializes if missing.
31
51
  * Handles v1→v2 migration transparently.
@@ -7,7 +7,7 @@ import { join } from "node:path";
7
7
  import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, MAX_EMOTIONAL_HISTORY, MAX_RELATIONSHIP_MEMORY, } from "./types.js";
8
8
  import { getBaseline, getDefaultSelfModel, extractMBTI, getSensitivity, getTemperament } from "./profiles.js";
9
9
  import { applyDecay, detectEmotions } from "./chemistry.js";
10
- import { decayDrives, computeEffectiveBaseline } from "./drives.js";
10
+ import { decayDrives, computeEffectiveBaseline, updateTraitDrift } from "./drives.js";
11
11
  import { t } from "./i18n.js";
12
12
  import { computeSelfReflection } from "./self-recognition.js";
13
13
  const STATE_FILE = "psyche-state.json";
@@ -144,11 +144,16 @@ export function pushSnapshot(state, stimulus) {
144
144
  const dominantEmotion = emotions.length > 0
145
145
  ? (state.meta.locale === "en" ? emotions[0].name : emotions[0].nameZh)
146
146
  : null;
147
+ // P11: Compute intensity and valence for memory consolidation
148
+ const intensity = computeSnapshotIntensity(state.current, state.baseline);
149
+ const valence = computeSnapshotValence(state.current);
147
150
  const snapshot = {
148
151
  chemistry: { ...state.current },
149
152
  stimulus,
150
153
  dominantEmotion,
151
154
  timestamp: new Date().toISOString(),
155
+ intensity,
156
+ valence,
152
157
  };
153
158
  const history = [...(state.emotionalHistory ?? []), snapshot];
154
159
  let updatedRelationships = state.relationships;
@@ -291,13 +296,118 @@ export function compressSession(state, userId) {
291
296
  }
292
297
  const updatedRel = { ...existing, memory };
293
298
  const updatedRelationships = { ...state.relationships, [relKey]: updatedRel };
294
- // ── Clear history and return ──
299
+ // ── P11: Consolidate and keep only core memories ──
300
+ const consolidated = consolidateHistory(history, MAX_EMOTIONAL_HISTORY);
301
+ const coreMemories = consolidated.filter((s) => s.isCoreMemory);
302
+ // ── v9: Update trait drift from session patterns ──
303
+ const currentDrift = state.traitDrift ?? {
304
+ accumulators: { praiseExposure: 0, pressureExposure: 0, neglectExposure: 0, connectionExposure: 0, conflictExposure: 0 },
305
+ sessionCount: 0,
306
+ baselineDelta: {},
307
+ decayRateModifiers: {},
308
+ sensitivityModifiers: {},
309
+ };
310
+ const updatedDrift = updateTraitDrift(currentDrift, history, state.learning);
311
+ // ── Clear non-core history, preserve core memories ──
295
312
  return {
296
313
  ...state,
297
- emotionalHistory: [],
314
+ emotionalHistory: coreMemories,
298
315
  relationships: updatedRelationships,
316
+ traitDrift: updatedDrift,
299
317
  };
300
318
  }
319
+ // ── P11: Emotional Memory Consolidation (McGaugh/Squire) ────
320
+ /**
321
+ * Compute snapshot intensity: how far current chemistry deviates from baseline.
322
+ * Returns 0-1 (0 = at baseline, 1 = maximum possible deviation).
323
+ */
324
+ export function computeSnapshotIntensity(current, baseline) {
325
+ let totalDeviation = 0;
326
+ for (const key of CHEMICAL_KEYS) {
327
+ totalDeviation += Math.abs(current[key] - baseline[key]);
328
+ }
329
+ return Math.min(1, totalDeviation / 600);
330
+ }
331
+ /**
332
+ * Compute emotional valence from chemistry.
333
+ * Returns -1 (negative) to 1 (positive).
334
+ */
335
+ export function computeSnapshotValence(chemistry) {
336
+ const raw = ((chemistry.DA - 50) + (chemistry.HT - 50) + (chemistry.OT - 50)
337
+ + (chemistry.END - 50) - (chemistry.CORT - 50) - (chemistry.NE - 50) * 0.3) / 250;
338
+ return Math.max(-1, Math.min(1, raw));
339
+ }
340
+ /** Minimum intensity threshold for a snapshot to be stored (P11) */
341
+ const INTENSITY_STORE_THRESHOLD = 0.15;
342
+ /** Core memory intensity threshold */
343
+ const CORE_MEMORY_THRESHOLD = 0.6;
344
+ /** Maximum core memories to keep */
345
+ const MAX_CORE_MEMORIES = 5;
346
+ /**
347
+ * Consolidate emotional history: mark core memories, fade weak ones.
348
+ * Called at session end or when history overflows.
349
+ */
350
+ export function consolidateHistory(snapshots, maxEntries = MAX_EMOTIONAL_HISTORY) {
351
+ if (snapshots.length === 0)
352
+ return [];
353
+ // Mark core memories
354
+ const marked = snapshots.map((s) => ({
355
+ ...s,
356
+ isCoreMemory: s.isCoreMemory || (s.intensity !== undefined && s.intensity >= CORE_MEMORY_THRESHOLD),
357
+ }));
358
+ // Separate core and non-core
359
+ const core = marked.filter((s) => s.isCoreMemory);
360
+ const nonCore = marked.filter((s) => !s.isCoreMemory);
361
+ // Enforce core memory limit (always, not just when over maxEntries)
362
+ let keptCore = core;
363
+ if (core.length > MAX_CORE_MEMORIES) {
364
+ const sorted = [...core].sort((a, b) => (b.intensity ?? 0) - (a.intensity ?? 0));
365
+ keptCore = sorted.slice(0, MAX_CORE_MEMORIES);
366
+ // Demoted cores become non-core
367
+ const demoted = sorted.slice(MAX_CORE_MEMORIES).map((s) => ({ ...s, isCoreMemory: false }));
368
+ nonCore.push(...demoted);
369
+ }
370
+ // If within limits after core trimming, keep all
371
+ if (keptCore.length + nonCore.length <= maxEntries) {
372
+ return [...keptCore, ...nonCore]
373
+ .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
374
+ }
375
+ // Fill remaining slots with non-core (highest intensity first)
376
+ const remainingSlots = maxEntries - keptCore.length;
377
+ const keptNonCore = [...nonCore]
378
+ .sort((a, b) => (b.intensity ?? 0) - (a.intensity ?? 0))
379
+ .slice(0, Math.max(0, remainingSlots));
380
+ // Merge and sort chronologically
381
+ return [...keptCore, ...keptNonCore]
382
+ .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
383
+ }
384
+ /**
385
+ * Retrieve memories related to current chemistry and stimulus.
386
+ * Uses chemical similarity + stimulus matching + core memory bonus.
387
+ */
388
+ export function retrieveRelatedMemories(history, currentChemistry, stimulus, limit = 3) {
389
+ if (history.length === 0)
390
+ return [];
391
+ const scored = history.map((snap) => {
392
+ // Chemical similarity (Euclidean distance normalized)
393
+ let sumSqDiff = 0;
394
+ for (const key of CHEMICAL_KEYS) {
395
+ const diff = snap.chemistry[key] - currentChemistry[key];
396
+ sumSqDiff += diff * diff;
397
+ }
398
+ const maxDist = Math.sqrt(6) * 100; // theoretical max distance
399
+ const similarity = 1 - Math.sqrt(sumSqDiff) / maxDist;
400
+ // Stimulus match bonus
401
+ const stimulusBonus = (stimulus && snap.stimulus === stimulus) ? 0.2 : 0;
402
+ // Core memory bonus
403
+ const coreBonus = snap.isCoreMemory ? 0.1 : 0;
404
+ return { snap, score: similarity + stimulusBonus + coreBonus };
405
+ });
406
+ return scored
407
+ .sort((a, b) => b.score - a.score)
408
+ .slice(0, limit)
409
+ .map((s) => s.snap);
410
+ }
301
411
  /**
302
412
  * Load psyche state from workspace. Auto-initializes if missing.
303
413
  * Handles v1→v2 migration transparently.