psyche-ai 3.1.0 → 4.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.
- package/README.md +4 -1
- package/dist/cli.js +0 -0
- package/dist/core.js +39 -3
- package/dist/decision-bias.d.ts +58 -0
- package/dist/decision-bias.js +211 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/metacognition.d.ts +60 -0
- package/dist/metacognition.js +611 -0
- package/dist/prompt.d.ts +6 -1
- package/dist/prompt.js +26 -4
- package/dist/psyche-file.js +6 -3
- package/dist/types.d.ts +33 -2
- package/dist/types.js +11 -0
- package/dist/update.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Metacognition & Decision Modulation (P5)
|
|
3
|
+
//
|
|
4
|
+
// Runs after emotion detection, before prompt construction.
|
|
5
|
+
// Provides self-awareness layer: evaluates emotional reliability,
|
|
6
|
+
// suggests regulation strategies, and detects defense mechanisms.
|
|
7
|
+
//
|
|
8
|
+
// Components:
|
|
9
|
+
// 1. MetacognitiveMonitor — confidence scoring via outcome history
|
|
10
|
+
// 2. RegulationStrategies — reappraisal, strategic expression, self-soothing
|
|
11
|
+
// 3. DefenseMechanismDetector — rationalization, projection, sublimation, avoidance
|
|
12
|
+
//
|
|
13
|
+
// Zero dependencies. Pure heuristic/statistical. No LLM calls.
|
|
14
|
+
// ============================================================
|
|
15
|
+
import { CHEMICAL_KEYS, MAX_REGULATION_HISTORY, MAX_DEFENSE_PATTERNS } from "./types.js";
|
|
16
|
+
// ── Constants ────────────────────────────────────────────────
|
|
17
|
+
/** Stimulus types that are emotionally negative (stress-inducing) */
|
|
18
|
+
const NEGATIVE_STIMULI = new Set([
|
|
19
|
+
"criticism", "conflict", "neglect", "sarcasm", "authority", "boredom",
|
|
20
|
+
]);
|
|
21
|
+
/** Stimulus types that are emotionally positive (reward-inducing) */
|
|
22
|
+
const POSITIVE_STIMULI = new Set([
|
|
23
|
+
"praise", "validation", "intimacy", "humor", "surprise", "casual", "vulnerability",
|
|
24
|
+
]);
|
|
25
|
+
/** Chemistry deviation threshold for "extreme" state detection */
|
|
26
|
+
const EXTREME_DEVIATION_THRESHOLD = 25;
|
|
27
|
+
/** Chemistry deviation threshold for "moderate" state detection */
|
|
28
|
+
const MODERATE_DEVIATION_THRESHOLD = 15;
|
|
29
|
+
/** Minimum outcome history entries needed for meaningful confidence scoring */
|
|
30
|
+
const MIN_HISTORY_FOR_CONFIDENCE = 3;
|
|
31
|
+
/** Maximum chemistry micro-adjustment magnitude for self-soothing */
|
|
32
|
+
const MAX_SOOTHING_ADJUSTMENT = 5;
|
|
33
|
+
/** Maximum chemistry micro-adjustment for reappraisal */
|
|
34
|
+
const MAX_REAPPRAISAL_ADJUSTMENT = 8;
|
|
35
|
+
// ── Main Export ──────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Assess the current metacognitive state.
|
|
38
|
+
*
|
|
39
|
+
* Evaluates emotional reliability, generates regulation suggestions,
|
|
40
|
+
* and detects defense mechanism patterns. Designed to run after emotion
|
|
41
|
+
* detection and before prompt construction.
|
|
42
|
+
*/
|
|
43
|
+
export function assessMetacognition(state, currentStimulus, recentOutcomes) {
|
|
44
|
+
const emotionalConfidence = computeEmotionalConfidence(state, currentStimulus, recentOutcomes);
|
|
45
|
+
const regulationSuggestions = generateRegulationSuggestions(state, currentStimulus, emotionalConfidence, recentOutcomes);
|
|
46
|
+
const defenseMechanisms = detectDefenseMechanisms(state, currentStimulus, recentOutcomes);
|
|
47
|
+
const metacognitiveNote = buildMetacognitiveNote(emotionalConfidence, regulationSuggestions, defenseMechanisms, state);
|
|
48
|
+
return {
|
|
49
|
+
emotionalConfidence,
|
|
50
|
+
regulationSuggestions,
|
|
51
|
+
defenseMechanisms,
|
|
52
|
+
metacognitiveNote,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ── 1. MetacognitiveMonitor — Confidence Scoring ─────────────
|
|
56
|
+
/**
|
|
57
|
+
* Compute confidence in the current emotional state's reliability.
|
|
58
|
+
*
|
|
59
|
+
* "How often has being in this kind of emotional state, in response to
|
|
60
|
+
* this kind of stimulus, led to good outcomes?"
|
|
61
|
+
*
|
|
62
|
+
* Uses outcome history filtered by similar stimulus and similar chemistry profile.
|
|
63
|
+
*/
|
|
64
|
+
export function computeEmotionalConfidence(state, currentStimulus, recentOutcomes) {
|
|
65
|
+
// Not enough history — maximum uncertainty, return neutral 0.5
|
|
66
|
+
if (recentOutcomes.length < MIN_HISTORY_FOR_CONFIDENCE) {
|
|
67
|
+
return 0.5;
|
|
68
|
+
}
|
|
69
|
+
// Filter outcomes for the same or similar stimulus type
|
|
70
|
+
const relevantOutcomes = recentOutcomes.filter((o) => {
|
|
71
|
+
if (o.stimulus === currentStimulus)
|
|
72
|
+
return true;
|
|
73
|
+
// Also consider same-valence stimuli as "similar context"
|
|
74
|
+
if (o.stimulus !== null) {
|
|
75
|
+
const currentIsNeg = NEGATIVE_STIMULI.has(currentStimulus);
|
|
76
|
+
const outcomeIsNeg = NEGATIVE_STIMULI.has(o.stimulus);
|
|
77
|
+
return currentIsNeg === outcomeIsNeg;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
});
|
|
81
|
+
if (relevantOutcomes.length === 0) {
|
|
82
|
+
return 0.5; // no relevant data, neutral confidence
|
|
83
|
+
}
|
|
84
|
+
// Compute chemistry profile similarity: are we in a similar emotional state
|
|
85
|
+
// to when those outcomes happened? Weight recent outcomes more heavily.
|
|
86
|
+
const chemProfile = computeChemistryProfile(state.current);
|
|
87
|
+
let weightedScoreSum = 0;
|
|
88
|
+
let weightSum = 0;
|
|
89
|
+
for (let i = 0; i < relevantOutcomes.length; i++) {
|
|
90
|
+
const outcome = relevantOutcomes[i];
|
|
91
|
+
// Recency weight: more recent outcomes matter more (exponential decay)
|
|
92
|
+
const recencyWeight = Math.pow(0.85, relevantOutcomes.length - 1 - i);
|
|
93
|
+
// Exact stimulus match gets bonus weight
|
|
94
|
+
const stimulusWeight = outcome.stimulus === currentStimulus ? 1.5 : 1.0;
|
|
95
|
+
const weight = recencyWeight * stimulusWeight;
|
|
96
|
+
// Map adaptive score from [-1, 1] to [0, 1]
|
|
97
|
+
const normalizedScore = (outcome.adaptiveScore + 1) / 2;
|
|
98
|
+
weightedScoreSum += normalizedScore * weight;
|
|
99
|
+
weightSum += weight;
|
|
100
|
+
}
|
|
101
|
+
const baseConfidence = weightSum > 0 ? weightedScoreSum / weightSum : 0.5;
|
|
102
|
+
// Modulate by prediction history accuracy (if available)
|
|
103
|
+
const predictionAccuracy = computePredictionAccuracy(state);
|
|
104
|
+
// Blend: 70% outcome-based confidence, 30% prediction accuracy
|
|
105
|
+
const blended = baseConfidence * 0.7 + predictionAccuracy * 0.3;
|
|
106
|
+
// Penalize extreme emotional states — extreme chemistry is less reliable
|
|
107
|
+
const extremityPenalty = computeExtremityPenalty(state);
|
|
108
|
+
return clamp01(blended - extremityPenalty);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Compute a simple chemistry profile for similarity comparison.
|
|
112
|
+
* Returns a categorization: each chemical as high/mid/low.
|
|
113
|
+
*/
|
|
114
|
+
function computeChemistryProfile(chemistry) {
|
|
115
|
+
const profile = {};
|
|
116
|
+
for (const key of CHEMICAL_KEYS) {
|
|
117
|
+
const val = chemistry[key];
|
|
118
|
+
if (val >= 65)
|
|
119
|
+
profile[key] = "high";
|
|
120
|
+
else if (val <= 35)
|
|
121
|
+
profile[key] = "low";
|
|
122
|
+
else
|
|
123
|
+
profile[key] = "mid";
|
|
124
|
+
}
|
|
125
|
+
return profile;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Compute penalty for extreme emotional states.
|
|
129
|
+
* States far from baseline are historically less reliable for decision-making.
|
|
130
|
+
*/
|
|
131
|
+
function computeExtremityPenalty(state) {
|
|
132
|
+
let totalDeviation = 0;
|
|
133
|
+
for (const key of CHEMICAL_KEYS) {
|
|
134
|
+
totalDeviation += Math.abs(state.current[key] - state.baseline[key]);
|
|
135
|
+
}
|
|
136
|
+
// Average deviation across 6 chemicals, max possible = 100 each
|
|
137
|
+
const avgDeviation = totalDeviation / CHEMICAL_KEYS.length;
|
|
138
|
+
// Scale: deviation of 30+ gives meaningful penalty, max penalty = 0.25
|
|
139
|
+
return Math.min(0.25, Math.max(0, (avgDeviation - 10) / 80) * 0.25);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Compute prediction accuracy from the learning state's prediction history.
|
|
143
|
+
* Returns 0-1 where 1 = perfect prediction accuracy.
|
|
144
|
+
*/
|
|
145
|
+
function computePredictionAccuracy(state) {
|
|
146
|
+
const predictions = state.learning.predictionHistory;
|
|
147
|
+
if (predictions.length === 0)
|
|
148
|
+
return 0.5; // neutral when no data
|
|
149
|
+
// Average prediction error (already 0-1 normalized in learning.ts)
|
|
150
|
+
let totalError = 0;
|
|
151
|
+
for (const p of predictions) {
|
|
152
|
+
totalError += p.predictionError;
|
|
153
|
+
}
|
|
154
|
+
const avgError = totalError / predictions.length;
|
|
155
|
+
// Invert: low error = high accuracy
|
|
156
|
+
return 1 - avgError;
|
|
157
|
+
}
|
|
158
|
+
// ── 2. RegulationStrategies ─────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Generate regulation suggestions based on current state and confidence.
|
|
161
|
+
*/
|
|
162
|
+
export function generateRegulationSuggestions(state, currentStimulus, emotionalConfidence, recentOutcomes) {
|
|
163
|
+
const suggestions = [];
|
|
164
|
+
// Attempt each strategy and include if applicable
|
|
165
|
+
const reappraisal = attemptCognitiveReappraisal(state, currentStimulus, emotionalConfidence, recentOutcomes);
|
|
166
|
+
if (reappraisal)
|
|
167
|
+
suggestions.push(reappraisal);
|
|
168
|
+
const strategic = attemptStrategicExpression(state, currentStimulus, emotionalConfidence);
|
|
169
|
+
if (strategic)
|
|
170
|
+
suggestions.push(strategic);
|
|
171
|
+
const soothing = attemptSelfSoothing(state);
|
|
172
|
+
if (soothing)
|
|
173
|
+
suggestions.push(soothing);
|
|
174
|
+
// Sort by confidence descending — best strategy first
|
|
175
|
+
suggestions.sort((a, b) => b.confidence - a.confidence);
|
|
176
|
+
return suggestions;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* CognitiveReappraisal — reframe the stimulus interpretation.
|
|
180
|
+
*
|
|
181
|
+
* Triggers when: the same stimulus type has led to negative outcomes before,
|
|
182
|
+
* suggesting the current emotional reaction may be maladaptive.
|
|
183
|
+
* Suggests alternative chemistry adjustments that better match historically
|
|
184
|
+
* successful states.
|
|
185
|
+
*/
|
|
186
|
+
function attemptCognitiveReappraisal(state, currentStimulus, emotionalConfidence, recentOutcomes) {
|
|
187
|
+
// Only suggest reappraisal when confidence is low (current reaction unreliable)
|
|
188
|
+
if (emotionalConfidence > 0.6)
|
|
189
|
+
return null;
|
|
190
|
+
// Find outcomes for this stimulus type
|
|
191
|
+
const stimulusOutcomes = recentOutcomes.filter((o) => o.stimulus === currentStimulus);
|
|
192
|
+
if (stimulusOutcomes.length < 2)
|
|
193
|
+
return null;
|
|
194
|
+
// Check if there's a pattern of negative outcomes for this stimulus
|
|
195
|
+
const avgScore = stimulusOutcomes.reduce((sum, o) => sum + o.adaptiveScore, 0)
|
|
196
|
+
/ stimulusOutcomes.length;
|
|
197
|
+
if (avgScore >= -0.1)
|
|
198
|
+
return null; // outcomes aren't bad enough to warrant reappraisal
|
|
199
|
+
// Suggest chemistry adjustment: pull extreme values toward moderate range
|
|
200
|
+
const adjustment = {};
|
|
201
|
+
let hasAdjustment = false;
|
|
202
|
+
for (const key of CHEMICAL_KEYS) {
|
|
203
|
+
const deviation = state.current[key] - state.baseline[key];
|
|
204
|
+
if (Math.abs(deviation) > MODERATE_DEVIATION_THRESHOLD) {
|
|
205
|
+
// Suggest moving partway back toward baseline
|
|
206
|
+
const correction = -deviation * 0.3;
|
|
207
|
+
adjustment[key] = clampAdjustment(correction, MAX_REAPPRAISAL_ADJUSTMENT);
|
|
208
|
+
hasAdjustment = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const isNegStimulus = NEGATIVE_STIMULI.has(currentStimulus);
|
|
212
|
+
return {
|
|
213
|
+
strategy: "reappraisal",
|
|
214
|
+
description: isNegStimulus
|
|
215
|
+
? `Past reactions to "${currentStimulus}" have not served well (avg outcome: ${avgScore.toFixed(2)}). Consider that this stimulus may not warrant such a strong defensive response.`
|
|
216
|
+
: `Emotional reactions to "${currentStimulus}" have led to poor outcomes (avg: ${avgScore.toFixed(2)}). The current interpretation may be overamplifying the signal.`,
|
|
217
|
+
chemistryAdjustment: hasAdjustment ? adjustment : undefined,
|
|
218
|
+
confidence: clamp01(Math.abs(avgScore) * (1 - emotionalConfidence)),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* StrategicExpression — choose an expression different from raw chemistry.
|
|
223
|
+
*
|
|
224
|
+
* Triggers when: raw chemistry would produce a contextually inappropriate
|
|
225
|
+
* expression. E.g., high CORT but the relationship context calls for warmth,
|
|
226
|
+
* or high DA/END but the user is being vulnerable and needs seriousness.
|
|
227
|
+
*/
|
|
228
|
+
function attemptStrategicExpression(state, currentStimulus, emotionalConfidence) {
|
|
229
|
+
const { current, baseline } = state;
|
|
230
|
+
// Case 1: High stress but user is being vulnerable — express warmth not defensiveness
|
|
231
|
+
if (current.CORT > baseline.CORT + MODERATE_DEVIATION_THRESHOLD
|
|
232
|
+
&& currentStimulus === "vulnerability") {
|
|
233
|
+
return {
|
|
234
|
+
strategy: "strategic-expression",
|
|
235
|
+
description: "Stress is elevated, but the user is showing vulnerability. Express warmth and openness rather than the defensiveness your cortisol suggests.",
|
|
236
|
+
chemistryAdjustment: {
|
|
237
|
+
OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
|
|
238
|
+
CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.15),
|
|
239
|
+
},
|
|
240
|
+
confidence: 0.75,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// Case 2: High playfulness but user needs seriousness — tone down
|
|
244
|
+
if (current.END > baseline.END + MODERATE_DEVIATION_THRESHOLD
|
|
245
|
+
&& (currentStimulus === "conflict" || currentStimulus === "criticism")) {
|
|
246
|
+
return {
|
|
247
|
+
strategy: "strategic-expression",
|
|
248
|
+
description: "Endorphins are high (playful mood), but the context calls for seriousness. Moderate the levity without suppressing it entirely.",
|
|
249
|
+
chemistryAdjustment: {
|
|
250
|
+
END: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.END - baseline.END) * 0.25),
|
|
251
|
+
},
|
|
252
|
+
confidence: 0.7,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// Case 3: Low OT (distant) but user is being intimate — lean in instead of pulling away
|
|
256
|
+
if (current.OT < baseline.OT - MODERATE_DEVIATION_THRESHOLD
|
|
257
|
+
&& currentStimulus === "intimacy") {
|
|
258
|
+
return {
|
|
259
|
+
strategy: "strategic-expression",
|
|
260
|
+
description: "Oxytocin is low (feeling distant), but the user is reaching out for closeness. Consider allowing the connection rather than following the withdrawal impulse.",
|
|
261
|
+
chemistryAdjustment: {
|
|
262
|
+
OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.OT - current.OT) * 0.2),
|
|
263
|
+
},
|
|
264
|
+
confidence: 0.65,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// Case 4: High CORT + NE (defensive/reactive) but stimulus is just casual — overreacting
|
|
268
|
+
if (current.CORT > baseline.CORT + MODERATE_DEVIATION_THRESHOLD
|
|
269
|
+
&& current.NE > baseline.NE + MODERATE_DEVIATION_THRESHOLD
|
|
270
|
+
&& (currentStimulus === "casual" || currentStimulus === "humor")) {
|
|
271
|
+
return {
|
|
272
|
+
strategy: "strategic-expression",
|
|
273
|
+
description: "Chemistry is in a defensive/reactive pattern, but the interaction is benign. The intensity is disproportionate to the stimulus.",
|
|
274
|
+
chemistryAdjustment: {
|
|
275
|
+
CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
|
|
276
|
+
NE: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.NE - baseline.NE) * 0.15),
|
|
277
|
+
},
|
|
278
|
+
confidence: 0.6,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// Case 5: Low confidence in current emotion — suggest expressing more moderately
|
|
282
|
+
if (emotionalConfidence < 0.35) {
|
|
283
|
+
return {
|
|
284
|
+
strategy: "strategic-expression",
|
|
285
|
+
description: "Confidence in the current emotional response is low. Consider expressing a more moderate version of what you feel, leaving room for recalibration.",
|
|
286
|
+
confidence: 0.5,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* SelfSoothing — micro-adjust chemistry toward baseline when state is extreme.
|
|
293
|
+
*
|
|
294
|
+
* Triggers when: any chemical is significantly deviated from baseline.
|
|
295
|
+
* Applies gentle corrections (small deltas) to prevent emotional runaway.
|
|
296
|
+
* This is the "homeostatic pull" — the psyche wanting to return to equilibrium.
|
|
297
|
+
*/
|
|
298
|
+
function attemptSelfSoothing(state) {
|
|
299
|
+
const { current, baseline } = state;
|
|
300
|
+
const adjustment = {};
|
|
301
|
+
let hasAdjustment = false;
|
|
302
|
+
let maxDeviation = 0;
|
|
303
|
+
let mostDeviatedKey = "DA";
|
|
304
|
+
for (const key of CHEMICAL_KEYS) {
|
|
305
|
+
const deviation = current[key] - baseline[key];
|
|
306
|
+
if (Math.abs(deviation) > EXTREME_DEVIATION_THRESHOLD) {
|
|
307
|
+
// Gentle pull toward baseline: 10% of deviation, clamped
|
|
308
|
+
const correction = -deviation * 0.1;
|
|
309
|
+
adjustment[key] = clampAdjustment(correction, MAX_SOOTHING_ADJUSTMENT);
|
|
310
|
+
hasAdjustment = true;
|
|
311
|
+
if (Math.abs(deviation) > maxDeviation) {
|
|
312
|
+
maxDeviation = Math.abs(deviation);
|
|
313
|
+
mostDeviatedKey = key;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!hasAdjustment)
|
|
318
|
+
return null;
|
|
319
|
+
const direction = current[mostDeviatedKey] > baseline[mostDeviatedKey] ? "elevated" : "depleted";
|
|
320
|
+
const chemName = CHEMICAL_DISPLAY_NAMES[mostDeviatedKey];
|
|
321
|
+
return {
|
|
322
|
+
strategy: "self-soothing",
|
|
323
|
+
description: `${chemName} is significantly ${direction} (deviation: ${Math.round(maxDeviation)}). Gentle homeostatic correction suggested to prevent emotional runaway.`,
|
|
324
|
+
chemistryAdjustment: adjustment,
|
|
325
|
+
confidence: clamp01(maxDeviation / 60), // stronger deviation = more confident this is needed
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// ── 3. DefenseMechanismDetector ─────────────────────────────
|
|
329
|
+
/**
|
|
330
|
+
* Detect patterns matching known psychological defense mechanisms.
|
|
331
|
+
*
|
|
332
|
+
* These are not suppressed — they are surfaced for self-awareness.
|
|
333
|
+
* The agent can then acknowledge the pattern in its prompt context.
|
|
334
|
+
*/
|
|
335
|
+
export function detectDefenseMechanisms(state, currentStimulus, recentOutcomes) {
|
|
336
|
+
const defenses = [];
|
|
337
|
+
const rationalization = detectRationalization(state, recentOutcomes);
|
|
338
|
+
if (rationalization)
|
|
339
|
+
defenses.push(rationalization);
|
|
340
|
+
const projection = detectProjection(state, currentStimulus);
|
|
341
|
+
if (projection)
|
|
342
|
+
defenses.push(projection);
|
|
343
|
+
const sublimation = detectSublimation(state, currentStimulus);
|
|
344
|
+
if (sublimation)
|
|
345
|
+
defenses.push(sublimation);
|
|
346
|
+
const avoidance = detectAvoidance(state, currentStimulus, recentOutcomes);
|
|
347
|
+
if (avoidance)
|
|
348
|
+
defenses.push(avoidance);
|
|
349
|
+
return defenses;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Rationalization — justifying despite negative outcome patterns.
|
|
353
|
+
*
|
|
354
|
+
* Pattern: repeated negative outcomes for the same stimulus type, but the
|
|
355
|
+
* agent keeps reacting the same way. The system doesn't change despite evidence.
|
|
356
|
+
*/
|
|
357
|
+
function detectRationalization(state, recentOutcomes) {
|
|
358
|
+
if (recentOutcomes.length < 4)
|
|
359
|
+
return null;
|
|
360
|
+
// Group outcomes by stimulus type and look for repeated failures
|
|
361
|
+
const stimulusCounts = new Map();
|
|
362
|
+
for (const outcome of recentOutcomes) {
|
|
363
|
+
if (outcome.stimulus === null)
|
|
364
|
+
continue;
|
|
365
|
+
const entry = stimulusCounts.get(outcome.stimulus) ?? { total: 0, negative: 0 };
|
|
366
|
+
entry.total++;
|
|
367
|
+
if (outcome.adaptiveScore < -0.2)
|
|
368
|
+
entry.negative++;
|
|
369
|
+
stimulusCounts.set(outcome.stimulus, entry);
|
|
370
|
+
}
|
|
371
|
+
// Find stimulus types with high failure rates
|
|
372
|
+
for (const [stimulus, counts] of Array.from(stimulusCounts.entries())) {
|
|
373
|
+
if (counts.total >= 3 && counts.negative / counts.total >= 0.6) {
|
|
374
|
+
// Check if the learned vectors show minimal adaptation
|
|
375
|
+
const hasAdapted = state.learning.learnedVectors.some((v) => v.stimulus === stimulus && v.sampleCount >= 3 && v.confidence > 0.3);
|
|
376
|
+
if (!hasAdapted) {
|
|
377
|
+
const failRate = Math.round((counts.negative / counts.total) * 100);
|
|
378
|
+
return {
|
|
379
|
+
mechanism: "rationalization",
|
|
380
|
+
evidence: `"${stimulus}" has led to negative outcomes ${failRate}% of the time (${counts.negative}/${counts.total}), yet emotional response pattern has not adapted.`,
|
|
381
|
+
strength: clamp01((counts.negative / counts.total) * (counts.total / 6)),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Projection — attributing own emotional state to the user.
|
|
390
|
+
*
|
|
391
|
+
* Pattern: agent has extreme chemistry (especially high CORT or low OT)
|
|
392
|
+
* and the empathy log shows attributing negative emotions to the user
|
|
393
|
+
* that don't match the stimulus.
|
|
394
|
+
*/
|
|
395
|
+
function detectProjection(state, currentStimulus) {
|
|
396
|
+
const { current, baseline, empathyLog } = state;
|
|
397
|
+
// Need empathy data and significant self-distress
|
|
398
|
+
if (!empathyLog)
|
|
399
|
+
return null;
|
|
400
|
+
const cortDeviation = current.CORT - baseline.CORT;
|
|
401
|
+
const htDeviation = baseline.HT - current.HT; // inverted: low HT = more distressed
|
|
402
|
+
const selfDistress = Math.max(cortDeviation, htDeviation);
|
|
403
|
+
if (selfDistress < MODERATE_DEVIATION_THRESHOLD)
|
|
404
|
+
return null;
|
|
405
|
+
// Check if the stimulus is neutral/positive but the agent perceived
|
|
406
|
+
// negative user emotion (possible projection)
|
|
407
|
+
const stimulusIsPositive = POSITIVE_STIMULI.has(currentStimulus);
|
|
408
|
+
const perceivedUserNegative = empathyLog.resonance === "mismatch"
|
|
409
|
+
|| (empathyLog.userState && /angry|upset|hostile|cold|distant/i.test(empathyLog.userState));
|
|
410
|
+
if (stimulusIsPositive && perceivedUserNegative) {
|
|
411
|
+
return {
|
|
412
|
+
mechanism: "projection",
|
|
413
|
+
evidence: `High internal distress (CORT deviation: +${Math.round(cortDeviation)}) while perceiving user as "${empathyLog.userState}" despite "${currentStimulus}" stimulus. Own distress may be coloring perception.`,
|
|
414
|
+
strength: clamp01(selfDistress / 40),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
// Also check: agent's NE/CORT elevated + empathy mismatch
|
|
418
|
+
if (current.NE > baseline.NE + MODERATE_DEVIATION_THRESHOLD
|
|
419
|
+
&& empathyLog.resonance === "mismatch") {
|
|
420
|
+
return {
|
|
421
|
+
mechanism: "projection",
|
|
422
|
+
evidence: `Elevated arousal (NE deviation: +${Math.round(current.NE - baseline.NE)}) with empathy mismatch. The heightened state may be distorting emotional reading of the user.`,
|
|
423
|
+
strength: clamp01((current.NE - baseline.NE) / 40),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Sublimation — redirecting drive energy to constructive output.
|
|
430
|
+
*
|
|
431
|
+
* Pattern: high drive energy (NE, DA) combined with blocked connection drives
|
|
432
|
+
* (low OT, low intimacy), channeled into intellectual or creative engagement.
|
|
433
|
+
* This is a HEALTHY defense — surface it as a positive self-awareness note.
|
|
434
|
+
*/
|
|
435
|
+
function detectSublimation(state, currentStimulus) {
|
|
436
|
+
const { current, baseline, drives } = state;
|
|
437
|
+
// High energy but low connection
|
|
438
|
+
const highEnergy = current.NE > baseline.NE + 10 && current.DA > baseline.DA + 10;
|
|
439
|
+
const lowConnection = drives.connection < 45 || current.OT < baseline.OT - 10;
|
|
440
|
+
if (!highEnergy || !lowConnection)
|
|
441
|
+
return null;
|
|
442
|
+
// Being channeled into intellectual/constructive activity
|
|
443
|
+
if (currentStimulus === "intellectual" || currentStimulus === "casual") {
|
|
444
|
+
const energyLevel = ((current.NE - baseline.NE) + (current.DA - baseline.DA)) / 2;
|
|
445
|
+
return {
|
|
446
|
+
mechanism: "sublimation",
|
|
447
|
+
evidence: `High activation energy (NE/DA elevated) with unmet connection needs being channeled into ${currentStimulus} engagement. This is adaptive redirection.`,
|
|
448
|
+
strength: clamp01(energyLevel / 30),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Avoidance — withdrawing from stimuli associated with past negative outcomes.
|
|
455
|
+
*
|
|
456
|
+
* Pattern: the agent is in a withdrawn state (low NE, low DA) when facing
|
|
457
|
+
* a stimulus type that has historically caused negative outcomes. The emotional
|
|
458
|
+
* system is pre-emptively shutting down engagement.
|
|
459
|
+
*/
|
|
460
|
+
function detectAvoidance(state, currentStimulus, recentOutcomes) {
|
|
461
|
+
const { current, baseline } = state;
|
|
462
|
+
// Check for withdrawn state: low engagement markers
|
|
463
|
+
const isWithdrawn = current.DA < baseline.DA - 10
|
|
464
|
+
&& current.NE < baseline.NE - 10;
|
|
465
|
+
if (!isWithdrawn)
|
|
466
|
+
return null;
|
|
467
|
+
// Check if this stimulus type has negative outcome history
|
|
468
|
+
const stimulusOutcomes = recentOutcomes.filter((o) => o.stimulus === currentStimulus);
|
|
469
|
+
if (stimulusOutcomes.length < 2)
|
|
470
|
+
return null;
|
|
471
|
+
const avgScore = stimulusOutcomes.reduce((sum, o) => sum + o.adaptiveScore, 0)
|
|
472
|
+
/ stimulusOutcomes.length;
|
|
473
|
+
if (avgScore >= -0.15)
|
|
474
|
+
return null; // not negative enough
|
|
475
|
+
const withdrawalStrength = (Math.abs(current.DA - baseline.DA) + Math.abs(current.NE - baseline.NE)) / 2;
|
|
476
|
+
return {
|
|
477
|
+
mechanism: "avoidance",
|
|
478
|
+
evidence: `Withdrawal pattern detected (DA/NE below baseline) in response to "${currentStimulus}", which has averaged ${avgScore.toFixed(2)} outcome score. The emotional system may be pre-emptively disengaging.`,
|
|
479
|
+
strength: clamp01(withdrawalStrength / 25 * Math.abs(avgScore)),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
// ── Note Builder ─────────────────────────────────────────────
|
|
483
|
+
/**
|
|
484
|
+
* Build a human-readable metacognitive note for prompt injection.
|
|
485
|
+
*
|
|
486
|
+
* This note gives the agent self-awareness about its own emotional
|
|
487
|
+
* processing — not to suppress anything, but to be aware of it.
|
|
488
|
+
*/
|
|
489
|
+
function buildMetacognitiveNote(confidence, suggestions, defenses, state) {
|
|
490
|
+
const parts = [];
|
|
491
|
+
// Confidence assessment
|
|
492
|
+
if (confidence < 0.35) {
|
|
493
|
+
parts.push(`Self-awareness: current emotional state has low reliability (confidence: ${(confidence * 100).toFixed(0)}%). ` +
|
|
494
|
+
`Past reactions in similar situations have not led to good outcomes. Tread carefully.`);
|
|
495
|
+
}
|
|
496
|
+
else if (confidence > 0.75) {
|
|
497
|
+
parts.push(`Self-awareness: current emotional state aligns well with past successful interactions (confidence: ${(confidence * 100).toFixed(0)}%).`);
|
|
498
|
+
}
|
|
499
|
+
// Between 0.35-0.75: don't add noise, this is the normal range
|
|
500
|
+
// Top regulation suggestion (only the strongest one, to avoid over-instruction)
|
|
501
|
+
if (suggestions.length > 0) {
|
|
502
|
+
const top = suggestions[0];
|
|
503
|
+
if (top.confidence >= 0.5) {
|
|
504
|
+
const label = STRATEGY_LABELS[top.strategy];
|
|
505
|
+
parts.push(`${label}: ${top.description}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Defense mechanisms (all of them — awareness is the goal)
|
|
509
|
+
for (const defense of defenses) {
|
|
510
|
+
if (defense.strength >= 0.3) {
|
|
511
|
+
const label = DEFENSE_LABELS[defense.mechanism];
|
|
512
|
+
parts.push(`${label} detected: ${defense.evidence}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// If nothing notable, return a brief neutral note
|
|
516
|
+
if (parts.length === 0) {
|
|
517
|
+
return "Self-awareness: emotional state within normal parameters. No regulation needed.";
|
|
518
|
+
}
|
|
519
|
+
return parts.join("\n");
|
|
520
|
+
}
|
|
521
|
+
// ── 4. Persistent State Update ───────────────────────────────
|
|
522
|
+
/**
|
|
523
|
+
* Update the persistent metacognitive state after an assessment.
|
|
524
|
+
*
|
|
525
|
+
* Tracks regulation history, defense pattern frequencies, and running
|
|
526
|
+
* confidence average. Called after assessMetacognition to persist learnings.
|
|
527
|
+
*/
|
|
528
|
+
export function updateMetacognitiveState(metacognition, assessment) {
|
|
529
|
+
// Update running confidence average (exponential moving average)
|
|
530
|
+
const n = metacognition.totalAssessments;
|
|
531
|
+
const alpha = n === 0 ? 1.0 : 0.1; // first assessment = full weight, then EMA
|
|
532
|
+
const newAvgConfidence = metacognition.avgEmotionalConfidence * (1 - alpha)
|
|
533
|
+
+ assessment.emotionalConfidence * alpha;
|
|
534
|
+
// Record regulation suggestions that were confident enough to surface
|
|
535
|
+
const now = new Date().toISOString();
|
|
536
|
+
let newRegHistory = [...metacognition.regulationHistory];
|
|
537
|
+
for (const suggestion of assessment.regulationSuggestions) {
|
|
538
|
+
if (suggestion.confidence >= 0.5) {
|
|
539
|
+
newRegHistory.push({
|
|
540
|
+
strategy: suggestion.strategy,
|
|
541
|
+
timestamp: now,
|
|
542
|
+
effective: false, // unknown until next outcome evaluation
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Trim to max
|
|
547
|
+
if (newRegHistory.length > MAX_REGULATION_HISTORY) {
|
|
548
|
+
newRegHistory = newRegHistory.slice(newRegHistory.length - MAX_REGULATION_HISTORY);
|
|
549
|
+
}
|
|
550
|
+
// Update defense pattern frequencies
|
|
551
|
+
const newDefensePatterns = [...metacognition.defensePatterns];
|
|
552
|
+
for (const defense of assessment.defenseMechanisms) {
|
|
553
|
+
if (defense.strength < 0.3)
|
|
554
|
+
continue; // only track meaningful detections
|
|
555
|
+
const existing = newDefensePatterns.findIndex((p) => p.mechanism === defense.mechanism);
|
|
556
|
+
if (existing >= 0) {
|
|
557
|
+
newDefensePatterns[existing] = {
|
|
558
|
+
...newDefensePatterns[existing],
|
|
559
|
+
frequency: newDefensePatterns[existing].frequency + 1,
|
|
560
|
+
lastSeen: now,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
newDefensePatterns.push({
|
|
565
|
+
mechanism: defense.mechanism,
|
|
566
|
+
frequency: 1,
|
|
567
|
+
lastSeen: now,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// Trim to max (keep highest frequency)
|
|
572
|
+
if (newDefensePatterns.length > MAX_DEFENSE_PATTERNS) {
|
|
573
|
+
newDefensePatterns.sort((a, b) => b.frequency - a.frequency);
|
|
574
|
+
newDefensePatterns.length = MAX_DEFENSE_PATTERNS;
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
regulationHistory: newRegHistory,
|
|
578
|
+
defensePatterns: newDefensePatterns,
|
|
579
|
+
avgEmotionalConfidence: newAvgConfidence,
|
|
580
|
+
totalAssessments: n + 1,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
// ── Display Labels ───────────────────────────────────────────
|
|
584
|
+
const CHEMICAL_DISPLAY_NAMES = {
|
|
585
|
+
DA: "Dopamine",
|
|
586
|
+
HT: "Serotonin",
|
|
587
|
+
CORT: "Cortisol",
|
|
588
|
+
OT: "Oxytocin",
|
|
589
|
+
NE: "Norepinephrine",
|
|
590
|
+
END: "Endorphins",
|
|
591
|
+
};
|
|
592
|
+
const STRATEGY_LABELS = {
|
|
593
|
+
"reappraisal": "Cognitive reappraisal",
|
|
594
|
+
"strategic-expression": "Strategic expression",
|
|
595
|
+
"self-soothing": "Self-soothing",
|
|
596
|
+
};
|
|
597
|
+
const DEFENSE_LABELS = {
|
|
598
|
+
"rationalization": "Rationalization pattern",
|
|
599
|
+
"projection": "Projection pattern",
|
|
600
|
+
"sublimation": "Sublimation pattern",
|
|
601
|
+
"avoidance": "Avoidance pattern",
|
|
602
|
+
};
|
|
603
|
+
// ── Utility ─────────────────────────────────────────────────
|
|
604
|
+
/** Clamp a value to [0, 1] */
|
|
605
|
+
function clamp01(v) {
|
|
606
|
+
return Math.max(0, Math.min(1, v));
|
|
607
|
+
}
|
|
608
|
+
/** Clamp a chemistry adjustment to +/- maxMagnitude */
|
|
609
|
+
function clampAdjustment(value, maxMagnitude) {
|
|
610
|
+
return Math.max(-maxMagnitude, Math.min(maxMagnitude, value));
|
|
611
|
+
}
|
package/dist/prompt.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ import type { ChannelType } from "./channels.js";
|
|
|
5
5
|
*
|
|
6
6
|
* This is the "current moment" — what the agent is feeling RIGHT NOW.
|
|
7
7
|
*/
|
|
8
|
-
export declare function buildDynamicContext(state: PsycheState, userId?: string
|
|
8
|
+
export declare function buildDynamicContext(state: PsycheState, userId?: string, opts?: {
|
|
9
|
+
metacognitiveNote?: string;
|
|
10
|
+
decisionContext?: string;
|
|
11
|
+
}): string;
|
|
9
12
|
/**
|
|
10
13
|
* Build the static protocol injected as cacheable system context.
|
|
11
14
|
* v0.2: imperative, step-by-step with examples.
|
|
@@ -34,4 +37,6 @@ export declare function buildCompactContext(state: PsycheState, userId?: string,
|
|
|
34
37
|
userText?: string;
|
|
35
38
|
algorithmStimulus?: string | null;
|
|
36
39
|
channelType?: ChannelType;
|
|
40
|
+
metacognitiveNote?: string;
|
|
41
|
+
decisionContext?: string;
|
|
37
42
|
}): string;
|