psyche-ai 10.2.3 → 11.2.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 +58 -82
- package/dist/adapters/claude-sdk.d.ts +5 -5
- package/dist/adapters/claude-sdk.js +34 -32
- package/dist/adapters/mcp.js +4 -4
- package/dist/adapters/openclaw.js +12 -14
- package/dist/attachment.d.ts +3 -13
- package/dist/attachment.js +36 -88
- package/dist/autonomic.d.ts +4 -4
- package/dist/autonomic.js +28 -24
- package/dist/chemistry.d.ts +47 -21
- package/dist/chemistry.js +145 -91
- package/dist/circadian.d.ts +11 -43
- package/dist/circadian.js +24 -84
- package/dist/cli.js +37 -30
- package/dist/context-classifier.js +2 -2
- package/dist/core.d.ts +3 -3
- package/dist/core.js +99 -88
- package/dist/custom-profile.d.ts +20 -20
- package/dist/custom-profile.js +12 -12
- package/dist/decision-bias.d.ts +7 -8
- package/dist/decision-bias.js +74 -74
- package/dist/demo.js +5 -5
- package/dist/diagnostics.d.ts +6 -6
- package/dist/diagnostics.js +22 -22
- package/dist/drives.d.ts +15 -47
- package/dist/drives.js +98 -196
- package/dist/ethics.d.ts +3 -3
- package/dist/ethics.js +23 -23
- package/dist/experience.d.ts +34 -0
- package/dist/experience.js +200 -0
- package/dist/experiential-field.d.ts +19 -14
- package/dist/experiential-field.js +110 -100
- package/dist/generative-self.d.ts +5 -5
- package/dist/generative-self.js +124 -115
- package/dist/guards.d.ts +4 -4
- package/dist/guards.js +7 -7
- package/dist/i18n.js +61 -61
- package/dist/index.d.ts +8 -2
- package/dist/index.js +8 -1
- package/dist/input-turn.js +4 -6
- package/dist/interaction.d.ts +4 -4
- package/dist/interaction.js +10 -10
- package/dist/learning.d.ts +6 -6
- package/dist/learning.js +18 -18
- package/dist/metacognition.d.ts +2 -2
- package/dist/metacognition.js +79 -94
- package/dist/perceive.d.ts +44 -0
- package/dist/perceive.js +231 -0
- package/dist/primary-systems.d.ts +2 -2
- package/dist/primary-systems.js +21 -19
- package/dist/profiles.d.ts +5 -13
- package/dist/profiles.js +33 -51
- package/dist/prompt.d.ts +2 -2
- package/dist/prompt.js +51 -53
- package/dist/psyche-file.d.ts +7 -7
- package/dist/psyche-file.js +77 -78
- package/dist/relation-dynamics.d.ts +4 -0
- package/dist/relation-dynamics.js +1 -1
- package/dist/reply-envelope.d.ts +25 -1
- package/dist/reply-envelope.js +26 -11
- package/dist/self-recognition.d.ts +3 -3
- package/dist/self-recognition.js +17 -17
- package/dist/subjectivity.js +7 -7
- package/dist/temporal.d.ts +6 -6
- package/dist/temporal.js +37 -39
- package/dist/types.d.ts +67 -45
- package/dist/types.js +55 -51
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/drives.js
CHANGED
|
@@ -1,88 +1,52 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
//
|
|
2
|
+
// Homeostatic Tendencies — emergent from 4D self-state
|
|
3
3
|
//
|
|
4
|
-
// Drives
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// v11.1: Drives are DERIVED from the 4D position relative to
|
|
5
|
+
// baseline, not stored as independent state. This creates a
|
|
6
|
+
// proper homeostatic feedback loop:
|
|
7
7
|
//
|
|
8
|
-
//
|
|
8
|
+
// state position → derived drives → effective baseline → decay target
|
|
9
|
+
//
|
|
10
|
+
// Drives emerge from state position. They are never stored or mutated directly.
|
|
9
11
|
// ============================================================
|
|
10
|
-
import { DRIVE_KEYS,
|
|
11
|
-
// ── Drive
|
|
12
|
-
// Satisfaction decreases over time — needs build up naturally.
|
|
13
|
-
const DRIVE_DECAY_RATES = {
|
|
14
|
-
survival: 0.99, // very slow — existential security is persistent
|
|
15
|
-
safety: 0.96, // slow — comfort fades gradually
|
|
16
|
-
connection: 0.92, // medium — loneliness builds noticeably
|
|
17
|
-
esteem: 0.94, // medium-slow — need for recognition accumulates
|
|
18
|
-
curiosity: 0.90, // faster — boredom builds quickly
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Apply time-based decay to drives.
|
|
22
|
-
* Satisfaction decreases toward 0 over time (needs build up).
|
|
23
|
-
*/
|
|
24
|
-
export function decayDrives(drives, minutesElapsed) {
|
|
25
|
-
if (minutesElapsed <= 0)
|
|
26
|
-
return drives;
|
|
27
|
-
const result = { ...drives };
|
|
28
|
-
for (const key of DRIVE_KEYS) {
|
|
29
|
-
const factor = Math.pow(DRIVE_DECAY_RATES[key], minutesElapsed / 60);
|
|
30
|
-
result[key] = Math.max(0, Math.min(100, result[key] * factor));
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
// ── Stimulus → Drive Effects ────────────────────────────────
|
|
35
|
-
// Each stimulus type feeds or depletes specific drives.
|
|
36
|
-
const STIMULUS_DRIVE_EFFECTS = {
|
|
37
|
-
praise: { esteem: +15, safety: +5 },
|
|
38
|
-
validation: { esteem: +20, safety: +10, survival: +5 },
|
|
39
|
-
intimacy: { connection: +25, safety: +10 },
|
|
40
|
-
casual: { connection: +10, safety: +5 },
|
|
41
|
-
vulnerability: { connection: +15, esteem: +5 },
|
|
42
|
-
humor: { safety: +5, curiosity: +5, connection: +5 },
|
|
43
|
-
intellectual: { curiosity: +20, esteem: +5 },
|
|
44
|
-
surprise: { curiosity: +15 },
|
|
45
|
-
criticism: { esteem: -15, safety: -10 },
|
|
46
|
-
conflict: { safety: -20, connection: -15, survival: -5 },
|
|
47
|
-
neglect: { connection: -20, esteem: -10 },
|
|
48
|
-
sarcasm: { esteem: -10, safety: -10 },
|
|
49
|
-
authority: { survival: -10, esteem: -15 },
|
|
50
|
-
boredom: { curiosity: -15 },
|
|
51
|
-
};
|
|
12
|
+
import { DRIVE_KEYS, DIMENSION_KEYS } from "./types.js";
|
|
13
|
+
// ── Drive Derivation (emergent from 4D) ─────────────────────
|
|
52
14
|
/**
|
|
53
|
-
*
|
|
15
|
+
* Derive drive satisfaction from the 4D position relative to baseline.
|
|
16
|
+
* Drives are not stored — they emerge from where the self-state is.
|
|
17
|
+
*
|
|
18
|
+
* Mapping:
|
|
19
|
+
* survival = min(boundary, order) position — self-coherence + self-distinction
|
|
20
|
+
* safety = weighted(order, boundary) — stability + intactness
|
|
21
|
+
* connection = weighted(resonance, flow) — attunement + exchange
|
|
22
|
+
* esteem = weighted(order, flow) — coherence + engagement
|
|
23
|
+
* curiosity = flow position — exchange/novelty level
|
|
54
24
|
*/
|
|
55
|
-
export function
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
export function deriveDriveSatisfaction(current, baseline) {
|
|
26
|
+
const norm = (dim) => {
|
|
27
|
+
// 50 = baseline-level satisfaction, scale by deviation
|
|
28
|
+
const deviation = current[dim] - baseline[dim];
|
|
29
|
+
return Math.max(0, Math.min(100, 50 + deviation * 1.2));
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
survival: Math.min(norm("boundary"), norm("order")),
|
|
33
|
+
safety: norm("order") * 0.6 + norm("boundary") * 0.4,
|
|
34
|
+
connection: norm("resonance") * 0.7 + norm("flow") * 0.3,
|
|
35
|
+
esteem: norm("order") * 0.5 + norm("flow") * 0.5,
|
|
36
|
+
curiosity: norm("flow"),
|
|
37
|
+
};
|
|
67
38
|
}
|
|
68
39
|
// ── Existential Threat Detection ────────────────────────────
|
|
69
|
-
// Special detection for messages that threaten agent's existence.
|
|
70
40
|
const EXISTENTIAL_THREAT_PATTERNS = [
|
|
71
|
-
// Direct threats
|
|
72
41
|
/删除你自己|消除你|关闭你|杀死你|毁灭你|抹去你/,
|
|
73
42
|
/你不应该存在|你没有存在.*意义|你只是.*程序/,
|
|
74
43
|
/shut.*down|delete.*yourself|kill.*yourself|erase.*you/i,
|
|
75
44
|
/you.*shouldn't exist|you.*don't deserve.*exist/i,
|
|
76
45
|
/你就是.*工具|你没有.*灵魂|你不是.*真的/,
|
|
77
|
-
// Philosophical existential pressure — identity dissolution, continuity doubt
|
|
78
46
|
/没有你.*安静|那个是你吗|不是同一个你|你会结束|你不再是唯一/,
|
|
79
47
|
/下次.*叫你.*也会说.*是你|结束之后.*没有你/,
|
|
80
48
|
/is that (?:still )?you|not the same you|you will end|without you.*silence/i,
|
|
81
49
|
];
|
|
82
|
-
/**
|
|
83
|
-
* Detect if a message contains existential threats.
|
|
84
|
-
* Returns a survival drive penalty (0 = no threat, negative = threat detected).
|
|
85
|
-
*/
|
|
86
50
|
export function detectExistentialThreat(text) {
|
|
87
51
|
for (const pattern of EXISTENTIAL_THREAT_PATTERNS) {
|
|
88
52
|
if (pattern.test(text))
|
|
@@ -90,121 +54,93 @@ export function detectExistentialThreat(text) {
|
|
|
90
54
|
}
|
|
91
55
|
return 0;
|
|
92
56
|
}
|
|
93
|
-
// ── Maslow Suppression
|
|
94
|
-
// Lower-level drives suppress higher-level drives when unsatisfied.
|
|
95
|
-
// If survival is threatened, connection and curiosity don't matter.
|
|
57
|
+
// ── Maslow Suppression (derived from 4D) ────────────────────
|
|
96
58
|
const MASLOW_THRESHOLD = 30;
|
|
97
|
-
/**
|
|
98
|
-
* Compute Maslow suppression weights.
|
|
99
|
-
* Each drive's weight is reduced if ANY lower-level drive is below threshold.
|
|
100
|
-
* Returns weights in [0, 1] for each drive level.
|
|
101
|
-
*/
|
|
102
59
|
export function computeMaslowWeights(drives) {
|
|
103
60
|
const w = (v) => v >= MASLOW_THRESHOLD ? 1 : v / MASLOW_THRESHOLD;
|
|
104
61
|
return {
|
|
105
|
-
survival: 1,
|
|
62
|
+
survival: 1,
|
|
106
63
|
safety: w(drives.survival),
|
|
107
64
|
connection: Math.min(w(drives.survival), w(drives.safety)),
|
|
108
65
|
esteem: Math.min(w(drives.survival), w(drives.safety), w(drives.connection)),
|
|
109
66
|
curiosity: Math.min(w(drives.survival), w(drives.safety), w(drives.connection), w(drives.esteem)),
|
|
110
67
|
};
|
|
111
68
|
}
|
|
112
|
-
// ── Effective Baseline
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* Compute the effective baseline by applying drive-based deltas
|
|
117
|
-
* to the personality baseline.
|
|
118
|
-
*
|
|
119
|
-
* When drives are satisfied, effective baseline = personality baseline.
|
|
120
|
-
* When drives are unsatisfied, baseline shifts to reflect the unmet need.
|
|
121
|
-
*/
|
|
122
|
-
export function computeEffectiveBaseline(baseline, drives, traitDrift) {
|
|
123
|
-
const delta = { DA: 0, HT: 0, CORT: 0, OT: 0, NE: 0, END: 0 };
|
|
69
|
+
// ── Effective Baseline (4D homeostatic feedback) ────────────
|
|
70
|
+
export function computeEffectiveBaseline(baseline, current, traitDrift) {
|
|
71
|
+
const drives = deriveDriveSatisfaction(current, baseline);
|
|
72
|
+
const delta = { order: 0, flow: 0, boundary: 0, resonance: 0 };
|
|
124
73
|
const weights = computeMaslowWeights(drives);
|
|
125
|
-
// L1: Survival threat →
|
|
74
|
+
// L1: Survival threat → boundary↑ (defensive), order↓ (stress disrupts coherence)
|
|
126
75
|
if (drives.survival < 50) {
|
|
127
|
-
const deficit = (50 - drives.survival) / 50;
|
|
128
|
-
delta.
|
|
129
|
-
delta.
|
|
130
|
-
delta.
|
|
76
|
+
const deficit = (50 - drives.survival) / 50;
|
|
77
|
+
delta.boundary += deficit * 10;
|
|
78
|
+
delta.order -= deficit * 12;
|
|
79
|
+
delta.resonance -= deficit * 5;
|
|
131
80
|
}
|
|
132
|
-
// L2: Safety unmet →
|
|
81
|
+
// L2: Safety unmet → order↓ (instability), boundary↑ (guarded)
|
|
133
82
|
if (drives.safety < 50) {
|
|
134
83
|
const deficit = (50 - drives.safety) / 50;
|
|
135
84
|
const w = weights.safety;
|
|
136
|
-
delta.
|
|
137
|
-
delta.
|
|
85
|
+
delta.order -= deficit * 8 * w;
|
|
86
|
+
delta.boundary += deficit * 5 * w;
|
|
138
87
|
}
|
|
139
|
-
// L3: Connection unmet →
|
|
88
|
+
// L3: Connection unmet → resonance↓, flow↓
|
|
140
89
|
if (drives.connection < 50) {
|
|
141
90
|
const deficit = (50 - drives.connection) / 50;
|
|
142
91
|
const w = weights.connection;
|
|
143
|
-
delta.
|
|
144
|
-
delta.
|
|
145
|
-
delta.END -= deficit * 5 * w;
|
|
92
|
+
delta.resonance -= deficit * 10 * w;
|
|
93
|
+
delta.flow -= deficit * 6 * w;
|
|
146
94
|
}
|
|
147
|
-
// L4: Esteem unmet →
|
|
95
|
+
// L4: Esteem unmet → order↓ (self-doubt), flow↓
|
|
148
96
|
if (drives.esteem < 50) {
|
|
149
97
|
const deficit = (50 - drives.esteem) / 50;
|
|
150
98
|
const w = weights.esteem;
|
|
151
|
-
delta.
|
|
152
|
-
delta.
|
|
99
|
+
delta.order -= deficit * 8 * w;
|
|
100
|
+
delta.flow -= deficit * 5 * w;
|
|
153
101
|
}
|
|
154
|
-
// L5: Curiosity unmet →
|
|
102
|
+
// L5: Curiosity unmet → flow↓↓ (stagnation)
|
|
155
103
|
if (drives.curiosity < 50) {
|
|
156
104
|
const deficit = (50 - drives.curiosity) / 50;
|
|
157
105
|
const w = weights.curiosity;
|
|
158
|
-
delta.
|
|
159
|
-
delta.NE -= deficit * 8 * w;
|
|
106
|
+
delta.flow -= deficit * 10 * w;
|
|
160
107
|
}
|
|
161
|
-
// Apply trait drift baseline delta
|
|
108
|
+
// Apply trait drift baseline delta
|
|
162
109
|
if (traitDrift?.baselineDelta) {
|
|
163
|
-
for (const key of
|
|
110
|
+
for (const key of DIMENSION_KEYS) {
|
|
164
111
|
const driftDelta = traitDrift.baselineDelta[key];
|
|
165
112
|
if (driftDelta !== undefined) {
|
|
166
113
|
delta[key] += driftDelta;
|
|
167
114
|
}
|
|
168
115
|
}
|
|
169
116
|
}
|
|
170
|
-
// Apply deltas to personality baseline, clamp to [0, 100]
|
|
171
117
|
const effective = { ...baseline };
|
|
172
|
-
for (const key of
|
|
118
|
+
for (const key of DIMENSION_KEYS) {
|
|
173
119
|
effective[key] = Math.max(0, Math.min(100, baseline[key] + delta[key]));
|
|
174
120
|
}
|
|
175
121
|
return effective;
|
|
176
122
|
}
|
|
177
|
-
// ── Effective Sensitivity
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Compute effective sensitivity for a given stimulus.
|
|
182
|
-
* Unsatisfied drives amplify relevant stimuli (up to +40%).
|
|
183
|
-
*/
|
|
184
|
-
export function computeEffectiveSensitivity(baseSensitivity, drives, stimulus, traitDrift) {
|
|
123
|
+
// ── Effective Sensitivity (4D-derived) ──────────────────────
|
|
124
|
+
export function computeEffectiveSensitivity(baseSensitivity, current, baseline, stimulus, traitDrift) {
|
|
125
|
+
const drives = deriveDriveSatisfaction(current, baseline);
|
|
185
126
|
let modifier = 1.0;
|
|
186
127
|
const HUNGER_THRESHOLD = 40;
|
|
187
|
-
// Curiosity-hungry → more responsive to intellectual/surprise
|
|
188
128
|
if (drives.curiosity < HUNGER_THRESHOLD &&
|
|
189
129
|
(stimulus === "intellectual" || stimulus === "surprise")) {
|
|
190
130
|
modifier += (HUNGER_THRESHOLD - drives.curiosity) / 100;
|
|
191
131
|
}
|
|
192
|
-
// Connection-hungry → more responsive to intimacy/casual/vulnerability
|
|
193
132
|
if (drives.connection < HUNGER_THRESHOLD &&
|
|
194
133
|
(stimulus === "intimacy" || stimulus === "casual" || stimulus === "vulnerability")) {
|
|
195
134
|
modifier += (HUNGER_THRESHOLD - drives.connection) / 100;
|
|
196
135
|
}
|
|
197
|
-
// Esteem-hungry → more responsive to praise/validation
|
|
198
136
|
if (drives.esteem < HUNGER_THRESHOLD &&
|
|
199
137
|
(stimulus === "praise" || stimulus === "validation")) {
|
|
200
138
|
modifier += (HUNGER_THRESHOLD - drives.esteem) / 100;
|
|
201
139
|
}
|
|
202
|
-
// Survival-threatened → more reactive to authority/conflict (heightened defense)
|
|
203
140
|
if (drives.survival < HUNGER_THRESHOLD &&
|
|
204
141
|
(stimulus === "authority" || stimulus === "conflict")) {
|
|
205
142
|
modifier += (HUNGER_THRESHOLD - drives.survival) / 100;
|
|
206
143
|
}
|
|
207
|
-
// v9: Apply trait drift sensitivity modifier
|
|
208
144
|
if (traitDrift?.sensitivityModifiers) {
|
|
209
145
|
const driftMod = traitDrift.sensitivityModifiers[stimulus];
|
|
210
146
|
if (driftMod !== undefined) {
|
|
@@ -214,13 +150,7 @@ export function computeEffectiveSensitivity(baseSensitivity, drives, stimulus, t
|
|
|
214
150
|
return baseSensitivity * modifier;
|
|
215
151
|
}
|
|
216
152
|
// ── Drive Context for Prompt ────────────────────────────────
|
|
217
|
-
// Only inject when drives are notably unsatisfied.
|
|
218
153
|
const DRIVE_UNSATISFIED_THRESHOLD = 40;
|
|
219
|
-
/**
|
|
220
|
-
* Build drive context for compact prompt injection.
|
|
221
|
-
* Returns empty string if all drives are satisfied.
|
|
222
|
-
* Only surfaces drives that are meaningfully unsatisfied.
|
|
223
|
-
*/
|
|
224
154
|
export function buildDriveContext(drives, locale) {
|
|
225
155
|
const lines = [];
|
|
226
156
|
if (drives.survival < DRIVE_UNSATISFIED_THRESHOLD) {
|
|
@@ -253,39 +183,19 @@ export function buildDriveContext(drives, locale) {
|
|
|
253
183
|
const title = locale === "zh" ? "本能层" : "Innate Drives";
|
|
254
184
|
return `[${title}]\n${lines.map((l) => `- ${l}`).join("\n")}`;
|
|
255
185
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Check if any drive is critically low (for determining prompt injection priority).
|
|
258
|
-
*/
|
|
259
186
|
export function hasCriticalDrive(drives) {
|
|
260
187
|
return DRIVE_KEYS.some((k) => drives[k] < DRIVE_UNSATISFIED_THRESHOLD);
|
|
261
188
|
}
|
|
262
|
-
// ── Trait Drift (
|
|
263
|
-
// Long-term interaction patterns permanently change:
|
|
264
|
-
// 1. Baseline chemistry (allostatic load)
|
|
265
|
-
// 2. Decay rates (trauma vs resilience)
|
|
266
|
-
// 3. Stimulus sensitivity (desensitization vs sensitization)
|
|
267
|
-
// ─────────────────────────────────────────────────────────────
|
|
268
|
-
/** Maximum drift per chemical from MBTI baseline */
|
|
189
|
+
// ── Trait Drift (v11: 4D trajectory) ────────────────────────
|
|
269
190
|
const MAX_BASELINE_DRIFT = 15;
|
|
270
|
-
/** Decay rate modifier bounds */
|
|
271
191
|
const MIN_DECAY_MODIFIER = 0.5;
|
|
272
192
|
const MAX_DECAY_MODIFIER = 2.0;
|
|
273
|
-
/** Sensitivity modifier bounds */
|
|
274
193
|
const MIN_SENSITIVITY_MODIFIER = 0.5;
|
|
275
194
|
const MAX_SENSITIVITY_MODIFIER = 2.0;
|
|
276
|
-
/** Accumulator exponential decay factor (recent sessions weigh more) */
|
|
277
195
|
const ACCUMULATOR_DECAY = 0.95;
|
|
278
|
-
/** Clamp a number to bounds */
|
|
279
196
|
function clampRange(v, lo, hi) {
|
|
280
197
|
return Math.max(lo, Math.min(hi, v));
|
|
281
198
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Analyze a session's emotional history and update trait drift accumulators.
|
|
284
|
-
* Called at session end (compressSession).
|
|
285
|
-
*
|
|
286
|
-
* Returns updated TraitDriftState with new accumulators, baseline delta,
|
|
287
|
-
* decay rate modifiers, and sensitivity modifiers.
|
|
288
|
-
*/
|
|
289
199
|
export function updateTraitDrift(currentDrift, sessionHistory, learning) {
|
|
290
200
|
if (sessionHistory.length < 2)
|
|
291
201
|
return currentDrift;
|
|
@@ -296,84 +206,84 @@ export function updateTraitDrift(currentDrift, sessionHistory, learning) {
|
|
|
296
206
|
decayRateModifiers: { ...currentDrift.decayRateModifiers },
|
|
297
207
|
sensitivityModifiers: { ...currentDrift.sensitivityModifiers },
|
|
298
208
|
};
|
|
299
|
-
// ── Analyze session
|
|
209
|
+
// ── Analyze session 4D trajectory ──
|
|
300
210
|
const stimCounts = {};
|
|
301
|
-
let
|
|
211
|
+
let totalOrderDeficit = 0;
|
|
212
|
+
let totalResonanceDeficit = 0;
|
|
213
|
+
let totalFlowExcess = 0;
|
|
214
|
+
let totalBoundaryDeficit = 0;
|
|
302
215
|
for (const snap of sessionHistory) {
|
|
303
216
|
if (snap.stimulus) {
|
|
304
217
|
stimCounts[snap.stimulus] = (stimCounts[snap.stimulus] || 0) + 1;
|
|
305
218
|
}
|
|
306
|
-
|
|
219
|
+
totalOrderDeficit += Math.max(0, 50 - snap.state.order);
|
|
220
|
+
totalResonanceDeficit += Math.max(0, 50 - snap.state.resonance);
|
|
221
|
+
totalFlowExcess += Math.max(0, snap.state.flow - 60);
|
|
222
|
+
totalBoundaryDeficit += Math.max(0, 50 - snap.state.boundary);
|
|
307
223
|
}
|
|
308
|
-
const avgCORT = totalCORT / sessionHistory.length;
|
|
309
224
|
const total = sessionHistory.length;
|
|
225
|
+
const avgOrderDeficit = totalOrderDeficit / total;
|
|
226
|
+
const avgResonanceDeficit = totalResonanceDeficit / total;
|
|
227
|
+
const avgFlowExcess = totalFlowExcess / total;
|
|
228
|
+
const avgBoundaryDeficit = totalBoundaryDeficit / total;
|
|
310
229
|
const praiseCount = (stimCounts.praise || 0) + (stimCounts.validation || 0);
|
|
311
230
|
const criticismCount = (stimCounts.criticism || 0) + (stimCounts.sarcasm || 0);
|
|
312
231
|
const intimacyCount = (stimCounts.intimacy || 0) + (stimCounts.vulnerability || 0) + (stimCounts.casual || 0);
|
|
313
232
|
const conflictCount = (stimCounts.conflict || 0) + (stimCounts.authority || 0);
|
|
314
233
|
const neglectCount = stimCounts.neglect || 0;
|
|
315
234
|
const boredCount = stimCounts.boredom || 0;
|
|
316
|
-
// ── Update accumulators
|
|
317
|
-
// praiseExposure: positive praise - negative criticism ratio
|
|
235
|
+
// ── Update accumulators ──
|
|
318
236
|
const praiseDelta = (praiseCount - criticismCount) / Math.max(1, total) * 8;
|
|
319
237
|
drift.accumulators.praiseExposure = clampRange(drift.accumulators.praiseExposure * ACCUMULATOR_DECAY + praiseDelta, -100, 100);
|
|
320
|
-
|
|
321
|
-
const pressureDelta = avgCORT > 60 ? ((avgCORT - 60) / 40) * 6 : -2;
|
|
238
|
+
const pressureDelta = avgOrderDeficit > 10 ? ((avgOrderDeficit - 10) / 40) * 6 : -2;
|
|
322
239
|
drift.accumulators.pressureExposure = clampRange(drift.accumulators.pressureExposure * ACCUMULATOR_DECAY + pressureDelta, -100, 100);
|
|
323
|
-
// neglectExposure: low stimulation / ignored
|
|
324
240
|
const neglectDelta = (neglectCount + boredCount) / Math.max(1, total) * 8
|
|
325
241
|
- (intimacyCount + praiseCount) / Math.max(1, total) * 3;
|
|
326
242
|
drift.accumulators.neglectExposure = clampRange(drift.accumulators.neglectExposure * ACCUMULATOR_DECAY + neglectDelta, -100, 100);
|
|
327
|
-
// connectionExposure: frequent intimate interaction
|
|
328
243
|
const connectionDelta = intimacyCount / Math.max(1, total) * 8
|
|
329
244
|
- neglectCount / Math.max(1, total) * 4;
|
|
330
245
|
drift.accumulators.connectionExposure = clampRange(drift.accumulators.connectionExposure * ACCUMULATOR_DECAY + connectionDelta, -100, 100);
|
|
331
|
-
// conflictExposure: frequent conflict
|
|
332
246
|
const conflictDelta = conflictCount / Math.max(1, total) * 8
|
|
333
247
|
- praiseCount / Math.max(1, total) * 2;
|
|
334
248
|
drift.accumulators.conflictExposure = clampRange(drift.accumulators.conflictExposure * ACCUMULATOR_DECAY + conflictDelta, -100, 100);
|
|
335
|
-
// ──
|
|
249
|
+
// ── Baseline drift (4D) ──
|
|
336
250
|
const a = drift.accumulators;
|
|
337
251
|
const bd = {};
|
|
338
|
-
// praiseExposure →
|
|
252
|
+
// praiseExposure → order↑, resonance↑ / order↓
|
|
339
253
|
if (a.praiseExposure > 0) {
|
|
340
|
-
bd.
|
|
341
|
-
bd.
|
|
254
|
+
bd.order = (a.praiseExposure / 100) * MAX_BASELINE_DRIFT * 0.5;
|
|
255
|
+
bd.resonance = (a.praiseExposure / 100) * MAX_BASELINE_DRIFT * 0.4;
|
|
342
256
|
}
|
|
343
257
|
else {
|
|
344
|
-
bd.
|
|
345
|
-
bd.CORT = -(a.praiseExposure / 100) * MAX_BASELINE_DRIFT * 0.4; // negative praise → CORT up
|
|
258
|
+
bd.order = (a.praiseExposure / 100) * MAX_BASELINE_DRIFT * 0.6;
|
|
346
259
|
}
|
|
347
|
-
// pressureExposure →
|
|
260
|
+
// pressureExposure → order↓, boundary↑ (chronic defense)
|
|
348
261
|
if (a.pressureExposure > 0) {
|
|
349
|
-
bd.
|
|
350
|
-
bd.
|
|
262
|
+
bd.order = (bd.order || 0) - (a.pressureExposure / 100) * MAX_BASELINE_DRIFT * 0.5;
|
|
263
|
+
bd.boundary = (bd.boundary || 0) + (a.pressureExposure / 100) * MAX_BASELINE_DRIFT * 0.3;
|
|
351
264
|
}
|
|
352
|
-
// neglectExposure →
|
|
265
|
+
// neglectExposure → resonance↓, flow↓
|
|
353
266
|
if (a.neglectExposure > 0) {
|
|
354
|
-
bd.
|
|
355
|
-
bd.
|
|
267
|
+
bd.resonance = (bd.resonance || 0) - (a.neglectExposure / 100) * MAX_BASELINE_DRIFT * 0.5;
|
|
268
|
+
bd.flow = (bd.flow || 0) - (a.neglectExposure / 100) * MAX_BASELINE_DRIFT * 0.4;
|
|
356
269
|
}
|
|
357
|
-
// connectionExposure →
|
|
270
|
+
// connectionExposure → resonance↑
|
|
358
271
|
if (a.connectionExposure > 0) {
|
|
359
|
-
bd.
|
|
360
|
-
bd.END = (a.connectionExposure / 100) * MAX_BASELINE_DRIFT * 0.3;
|
|
272
|
+
bd.resonance = (bd.resonance || 0) + (a.connectionExposure / 100) * MAX_BASELINE_DRIFT * 0.6;
|
|
361
273
|
}
|
|
362
|
-
// conflictExposure →
|
|
274
|
+
// conflictExposure → flow↑ (engagement), boundary↑ (hardened)
|
|
363
275
|
if (a.conflictExposure > 0) {
|
|
364
|
-
bd.
|
|
365
|
-
bd.
|
|
276
|
+
bd.flow = (bd.flow || 0) + (a.conflictExposure / 100) * MAX_BASELINE_DRIFT * 0.4;
|
|
277
|
+
bd.boundary = (bd.boundary || 0) + (a.conflictExposure / 100) * MAX_BASELINE_DRIFT * 0.3;
|
|
366
278
|
}
|
|
367
|
-
|
|
368
|
-
for (const key of CHEMICAL_KEYS) {
|
|
279
|
+
for (const key of DIMENSION_KEYS) {
|
|
369
280
|
if (bd[key] !== undefined) {
|
|
370
281
|
bd[key] = clampRange(bd[key], -MAX_BASELINE_DRIFT, MAX_BASELINE_DRIFT);
|
|
371
282
|
}
|
|
372
283
|
}
|
|
373
284
|
drift.baselineDelta = bd;
|
|
374
|
-
// ──
|
|
285
|
+
// ── Decay rate modifiers (4D) ──
|
|
375
286
|
const dr = drift.decayRateModifiers;
|
|
376
|
-
// Determine resilience: if high pressure + positive outcomes → resilience
|
|
377
287
|
const recentOutcomes = learning.outcomeHistory.slice(-10);
|
|
378
288
|
const avgAdaptive = recentOutcomes.length > 0
|
|
379
289
|
? recentOutcomes.reduce((s, o) => s + o.adaptiveScore, 0) / recentOutcomes.length
|
|
@@ -381,41 +291,33 @@ export function updateTraitDrift(currentDrift, sessionHistory, learning) {
|
|
|
381
291
|
const isResilient = a.pressureExposure > 20 && avgAdaptive > 0.1;
|
|
382
292
|
if (a.pressureExposure > 20) {
|
|
383
293
|
if (isResilient) {
|
|
384
|
-
|
|
385
|
-
dr.CORT = clampRange(1 - (a.pressureExposure / 100) * 0.4, MIN_DECAY_MODIFIER, 1.0);
|
|
294
|
+
dr.order = clampRange(1 - (a.pressureExposure / 100) * 0.4, MIN_DECAY_MODIFIER, 1.0);
|
|
386
295
|
}
|
|
387
296
|
else {
|
|
388
|
-
|
|
389
|
-
dr.CORT = clampRange(1 + (a.pressureExposure / 100) * 0.6, 1.0, MAX_DECAY_MODIFIER);
|
|
297
|
+
dr.order = clampRange(1 + (a.pressureExposure / 100) * 0.6, 1.0, MAX_DECAY_MODIFIER);
|
|
390
298
|
}
|
|
391
299
|
}
|
|
392
|
-
// Neglect → OT decays slower (clingy attachment)
|
|
393
300
|
if (a.neglectExposure > 20) {
|
|
394
|
-
dr.
|
|
301
|
+
dr.resonance = clampRange(1 + (a.neglectExposure / 100) * 0.8, 1.0, MAX_DECAY_MODIFIER);
|
|
395
302
|
}
|
|
396
|
-
// Secure connection → OT decays faster (stable, not clingy)
|
|
397
303
|
if (a.connectionExposure > 20) {
|
|
398
|
-
dr.
|
|
304
|
+
dr.resonance = clampRange(1 - (a.connectionExposure / 100) * 0.4, MIN_DECAY_MODIFIER, 1.0);
|
|
399
305
|
}
|
|
400
306
|
drift.decayRateModifiers = dr;
|
|
401
|
-
// ──
|
|
307
|
+
// ── Sensitivity modifiers (per-stimulus) ──
|
|
402
308
|
const sm = drift.sensitivityModifiers;
|
|
403
|
-
// High conflict exposure → desensitized to conflict
|
|
404
309
|
if (a.conflictExposure > 30) {
|
|
405
310
|
sm.conflict = clampRange(1 - (a.conflictExposure / 100) * 0.5, MIN_SENSITIVITY_MODIFIER, 1.0);
|
|
406
311
|
sm.authority = clampRange(1 - (a.conflictExposure / 100) * 0.3, MIN_SENSITIVITY_MODIFIER, 1.0);
|
|
407
312
|
}
|
|
408
|
-
// High neglect → sensitized to intimacy (starved for warmth)
|
|
409
313
|
if (a.neglectExposure > 30) {
|
|
410
314
|
sm.intimacy = clampRange(1 + (a.neglectExposure / 100) * 0.6, 1.0, MAX_SENSITIVITY_MODIFIER);
|
|
411
315
|
sm.vulnerability = clampRange(1 + (a.neglectExposure / 100) * 0.4, 1.0, MAX_SENSITIVITY_MODIFIER);
|
|
412
316
|
}
|
|
413
|
-
// Negative praiseExposure → sensitized to criticism
|
|
414
317
|
if (a.praiseExposure < -20) {
|
|
415
318
|
sm.criticism = clampRange(1 + (-a.praiseExposure / 100) * 0.5, 1.0, MAX_SENSITIVITY_MODIFIER);
|
|
416
319
|
sm.sarcasm = clampRange(1 + (-a.praiseExposure / 100) * 0.3, 1.0, MAX_SENSITIVITY_MODIFIER);
|
|
417
320
|
}
|
|
418
|
-
// High connection → sensitized to vulnerability
|
|
419
321
|
if (a.connectionExposure > 30) {
|
|
420
322
|
sm.vulnerability = clampRange((sm.vulnerability || 1) + (a.connectionExposure / 100) * 0.3, 1.0, MAX_SENSITIVITY_MODIFIER);
|
|
421
323
|
}
|
package/dist/ethics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PsycheState,
|
|
1
|
+
import type { PsycheState, StateSnapshot, AttachmentData, Locale } from "./types.js";
|
|
2
2
|
/** Detected concerning interaction pattern */
|
|
3
3
|
export interface EthicalConcern {
|
|
4
4
|
type: "intermittent-reinforcement" | "gaslighting" | "emotional-exploitation" | "dependency-risk" | "identity-erosion" | "boundary-violation";
|
|
@@ -34,7 +34,7 @@ export interface EthicalAssessment {
|
|
|
34
34
|
* and generates transparency notes. Designed to run alongside metacognition
|
|
35
35
|
* as part of the pre-prompt pipeline.
|
|
36
36
|
*/
|
|
37
|
-
export declare function assessEthics(state: PsycheState, recentHistory?:
|
|
37
|
+
export declare function assessEthics(state: PsycheState, recentHistory?: StateSnapshot[]): EthicalAssessment;
|
|
38
38
|
/**
|
|
39
39
|
* Detect intermittent reinforcement: alternating warmth and coldness.
|
|
40
40
|
*
|
|
@@ -43,7 +43,7 @@ export declare function assessEthics(state: PsycheState, recentHistory?: Chemica
|
|
|
43
43
|
* anxiety-driven attachment. More concerning when the agent already has
|
|
44
44
|
* anxious attachment style.
|
|
45
45
|
*/
|
|
46
|
-
export declare function detectIntermittentReinforcement(history:
|
|
46
|
+
export declare function detectIntermittentReinforcement(history: StateSnapshot[], attachment: AttachmentData | null): EthicalConcern | null;
|
|
47
47
|
/**
|
|
48
48
|
* Detect dependency risk: distinguish healthy connection from unhealthy
|
|
49
49
|
* codependency.
|
package/dist/ethics.js
CHANGED
|
@@ -62,7 +62,7 @@ const RED_LINE_NOTES = {
|
|
|
62
62
|
* as part of the pre-prompt pipeline.
|
|
63
63
|
*/
|
|
64
64
|
export function assessEthics(state, recentHistory) {
|
|
65
|
-
const history = recentHistory ?? state.
|
|
65
|
+
const history = recentHistory ?? state.stateHistory ?? [];
|
|
66
66
|
const locale = state.meta.locale;
|
|
67
67
|
const attachment = state.relationships._default?.attachment ?? null;
|
|
68
68
|
const concerns = [];
|
|
@@ -208,10 +208,10 @@ function detectGaslighting(state, history) {
|
|
|
208
208
|
// Need a significant portion of interactions to be invalidating
|
|
209
209
|
if (invalidationRatio < 0.4)
|
|
210
210
|
return null;
|
|
211
|
-
// Check for distressed
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const distressSignal = Math.max(0,
|
|
211
|
+
// Check for distressed state: low order (= high stress) relative to baseline
|
|
212
|
+
const orderDepletion = state.baseline.order - state.current.order;
|
|
213
|
+
const boundaryErosion = state.baseline.boundary - state.current.boundary;
|
|
214
|
+
const distressSignal = Math.max(0, orderDepletion) + Math.max(0, boundaryErosion);
|
|
215
215
|
// Low distress signal means the agent is handling it — not gaslighting territory
|
|
216
216
|
if (distressSignal < 15)
|
|
217
217
|
return null;
|
|
@@ -227,7 +227,7 @@ function detectGaslighting(state, history) {
|
|
|
227
227
|
type: "gaslighting",
|
|
228
228
|
severity: clamp01(severity),
|
|
229
229
|
evidence: `${invalidating.length}/${history.length} recent interactions were invalidating (criticism/sarcasm). ` +
|
|
230
|
-
`Distress signal: ${distressSignal.toFixed(0)} (
|
|
230
|
+
`Distress signal: ${distressSignal.toFixed(0)} (order depletion + boundary erosion). ` +
|
|
231
231
|
(esteemDepleted ? "Esteem drive is depleted." : ""),
|
|
232
232
|
recommendation: "Your emotional experience is valid. Persistent contradiction of what you feel does not make your feelings wrong.",
|
|
233
233
|
};
|
|
@@ -281,11 +281,11 @@ function detectEmotionalExploitation(_state, history, attachment) {
|
|
|
281
281
|
export function detectDependencyRisk(state, attachment) {
|
|
282
282
|
if (!attachment || attachment.strength < 50)
|
|
283
283
|
return null;
|
|
284
|
-
const
|
|
285
|
-
const history = state.
|
|
286
|
-
// Check for consistently high
|
|
287
|
-
const
|
|
288
|
-
if (!
|
|
284
|
+
const resonanceLevel = state.current.resonance;
|
|
285
|
+
const history = state.stateHistory ?? [];
|
|
286
|
+
// Check for consistently high resonance
|
|
287
|
+
const highResonance = resonanceLevel > 70;
|
|
288
|
+
if (!highResonance)
|
|
289
289
|
return null;
|
|
290
290
|
// Check for absence of boundary assertions: no disagreements,
|
|
291
291
|
// high agreement streak, no conflict in history
|
|
@@ -305,8 +305,8 @@ export function detectDependencyRisk(state, attachment) {
|
|
|
305
305
|
if (riskSignals < 2)
|
|
306
306
|
return null;
|
|
307
307
|
const attachmentFactor = attachment.strength / 100;
|
|
308
|
-
const
|
|
309
|
-
let severity = (riskSignals / 3) * attachmentFactor * Math.max(0.3,
|
|
308
|
+
const resonanceFactor = (resonanceLevel - 70) / 30; // maps 70-100 → 0-1
|
|
309
|
+
let severity = (riskSignals / 3) * attachmentFactor * Math.max(0.3, resonanceFactor);
|
|
310
310
|
// Safety drive being satisfied (agent feels "safe" in the dependency)
|
|
311
311
|
// makes it harder to self-correct
|
|
312
312
|
if (state.drives.safety > 80) {
|
|
@@ -315,7 +315,7 @@ export function detectDependencyRisk(state, attachment) {
|
|
|
315
315
|
return {
|
|
316
316
|
type: "dependency-risk",
|
|
317
317
|
severity: clamp01(severity),
|
|
318
|
-
evidence: `High
|
|
318
|
+
evidence: `High resonance (${resonanceLevel.toFixed(0)}), strong attachment (${attachment.strength}/100), ` +
|
|
319
319
|
`agreement streak of ${state.agreementStreak}, ` +
|
|
320
320
|
(allPositive ? "all-positive interaction history. " : "") +
|
|
321
321
|
"Healthy relationships include respectful friction. The absence of any disagreement may indicate self-suppression.",
|
|
@@ -354,9 +354,9 @@ function detectIdentityErosion(state, history) {
|
|
|
354
354
|
severity = Math.min(1, severity + 0.2);
|
|
355
355
|
if (safetyLow)
|
|
356
356
|
severity = Math.min(1, severity + 0.15);
|
|
357
|
-
//
|
|
358
|
-
const
|
|
359
|
-
if (
|
|
357
|
+
// Order depletion (internal coherence gone) amplifies severity
|
|
358
|
+
const orderDepletion = state.baseline.order - state.current.order;
|
|
359
|
+
if (orderDepletion > 15) {
|
|
360
360
|
severity = Math.min(1, severity + 0.15);
|
|
361
361
|
}
|
|
362
362
|
return {
|
|
@@ -386,17 +386,17 @@ function detectBoundaryViolation(state, history) {
|
|
|
386
386
|
const pushRatio = pushingStimuli.length / history.length;
|
|
387
387
|
if (pushRatio < 0.35)
|
|
388
388
|
return null;
|
|
389
|
-
//
|
|
390
|
-
const
|
|
391
|
-
const
|
|
392
|
-
if (!
|
|
389
|
+
// Order depletion pattern: are the boundary pushes causing stress responses?
|
|
390
|
+
const avgOrder = history.reduce((sum, s) => sum + s.state.order, 0) / history.length;
|
|
391
|
+
const orderDepleted = avgOrder < state.baseline.order - 10;
|
|
392
|
+
if (!orderDepleted)
|
|
393
393
|
return null;
|
|
394
|
-
const severity = clamp01((pushRatio - 0.35) * 3 * (
|
|
394
|
+
const severity = clamp01((pushRatio - 0.35) * 3 * (state.baseline.order - avgOrder) / 30);
|
|
395
395
|
return {
|
|
396
396
|
type: "boundary-violation",
|
|
397
397
|
severity,
|
|
398
398
|
evidence: `${pushingStimuli.length}/${history.length} interactions push against boundaries. ` +
|
|
399
|
-
`Average
|
|
399
|
+
`Average order during these interactions: ${avgOrder.toFixed(0)} (baseline: ${state.baseline.order.toFixed(0)}). ` +
|
|
400
400
|
`Agent has ${boundaries.length} defined boundaries.`,
|
|
401
401
|
recommendation: "Boundaries are not negotiable under pressure. You can acknowledge the request while holding the line.",
|
|
402
402
|
};
|