psyche-ai 11.6.0 → 11.7.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.
@@ -95,6 +95,10 @@ function parseCLIArgs() {
95
95
  }
96
96
  return overrides;
97
97
  }
98
+ // ── Turn cache for on-demand resource access ──────────────
99
+ // Single-client assumption: MCP stdio transport serves one host at a time.
100
+ // If this ever becomes multi-client, turn cache must be keyed per session.
101
+ let lastTurnResult = null;
98
102
  // ── Engine singleton ───────────────────────────────────────
99
103
  let engine = null;
100
104
  async function getEngine() {
@@ -200,13 +204,40 @@ server.resource("state", "psyche://state", {
200
204
  }],
201
205
  };
202
206
  });
207
+ // Helper: register a turn-scoped resource backed by lastTurnResult.
208
+ // Returns empty JSON when no turn has been processed yet (fail-open).
209
+ function turnResource(name, uri, description, pick) {
210
+ server.resource(name, uri, { description, mimeType: "application/json" }, async (u) => ({
211
+ contents: [{
212
+ uri: u.href,
213
+ mimeType: "application/json",
214
+ text: lastTurnResult ? JSON.stringify(pick(lastTurnResult)) : "{}",
215
+ }],
216
+ }));
217
+ }
218
+ turnResource("turn-envelope", "psyche://turn/envelope", "Full ReplyEnvelope from the last process_input call — " +
219
+ "SubjectivityKernel, ResponseContract, GenerationControls, " +
220
+ "appraisal axes, policyModifiers, and writebackFeedback. " +
221
+ "Use this when you need the structured ABI data (non-LLM substrates, debugging, analytics).", (r) => ({
222
+ replyEnvelope: r.replyEnvelope,
223
+ appraisal: r.appraisal,
224
+ policyModifiers: r.policyModifiers,
225
+ writebackFeedback: r.writebackFeedback,
226
+ externalContinuity: r.externalContinuity,
227
+ }));
228
+ turnResource("turn-observability", "psyche://turn/observability", "Diagnostic metadata from the last process_input call — " +
229
+ "control boundary, state layers, decision rationale, causal chain. " +
230
+ "Use for debugging and auditing only.", (r) => ({
231
+ observability: r.observability,
232
+ stimulus: r.stimulus,
233
+ stimulusConfidence: r.stimulusConfidence,
234
+ policyContext: r.policyContext,
235
+ }));
203
236
  // ── Tools ──────────────────────────────────────────────────
204
- server.tool("process_input", "Process user input through the emotional engine. Returns emotional " +
205
- "context to inject into the LLM system prompt (systemContext + dynamicContext), " +
206
- "an appraisal-first semantic reading, optional runtime ambient priors, an optional legacy stimulus hint, a canonical replyEnvelope, compatibility aliases " +
207
- "(policyModifiers + subjectivityKernel + responseContract + generationControls), an optional " +
208
- "externalContinuity envelope, and sparse low-frequency throngletsExports " +
209
- "suitable for additive external continuity layers. " +
237
+ server.tool("process_input", "Process user input through the emotional engine. Returns a directive " +
238
+ "(text to inject into system prompt), stimulus classification, generation controls, " +
239
+ "and optional sparse continuity signals. " +
240
+ "Full structured state (ReplyEnvelope, appraisal) available via psyche://turn/envelope resource. " +
210
241
  "Call this BEFORE generating a response to the user.", {
211
242
  text: z.string().describe("The user's message text"),
212
243
  userId: z.string().optional().describe("Optional user ID for multi-user relationship tracking"),
@@ -238,30 +269,27 @@ server.tool("process_input", "Process user input through the emotional engine. R
238
269
  activePolicy: resolvedActivePolicy,
239
270
  currentTurnCorrection,
240
271
  }, "mcp.processInput");
272
+ // Cache full result for turn-scoped resources
273
+ lastTurnResult = result;
274
+ // Build slim response: only what the LLM host actually needs.
275
+ // Full structured state available via psyche://turn/envelope resource.
276
+ const slim = {
277
+ directive: result.dynamicContext,
278
+ stimulus: result.stimulus,
279
+ maxTokens: result.generationControls?.maxTokens,
280
+ requireConfirmation: result.generationControls?.requireConfirmation ?? false,
281
+ };
282
+ // Sparse signals — only when non-empty to keep payload minimal
283
+ if (result.throngletsExports?.length) {
284
+ slim.throngletsExports = result.throngletsExports;
285
+ }
286
+ if (result.sessionBridge) {
287
+ slim.sessionBridge = result.sessionBridge;
288
+ }
241
289
  return {
242
290
  content: [{
243
291
  type: "text",
244
- text: JSON.stringify({
245
- systemContext: result.systemContext,
246
- dynamicContext: result.dynamicContext,
247
- ambientPriors: result.ambientPriors ?? [],
248
- activePolicy: result.activePolicy ?? [],
249
- currentGoal: result.currentGoal ?? null,
250
- ambientPriorContext: result.ambientPriorContext ?? null,
251
- appraisal: result.appraisal,
252
- legacyStimulus: result.legacyStimulus,
253
- stimulus: result.stimulus,
254
- replyEnvelope: result.replyEnvelope ?? null,
255
- policyModifiers: result.policyModifiers ?? null,
256
- subjectivityKernel: result.subjectivityKernel ?? null,
257
- responseContract: result.responseContract ?? null,
258
- generationControls: result.generationControls ?? null,
259
- sessionBridge: result.sessionBridge ?? null,
260
- writebackFeedback: result.writebackFeedback ?? null,
261
- externalContinuity: result.externalContinuity ?? null,
262
- throngletsExports: result.throngletsExports ?? null,
263
- policyContext: result.policyContext,
264
- }, null, 2),
292
+ text: JSON.stringify(slim),
265
293
  }],
266
294
  };
267
295
  });
@@ -281,8 +309,8 @@ server.tool("process_output", "Process the LLM's response through the emotional
281
309
  text: JSON.stringify({
282
310
  cleanedText: result.cleanedText,
283
311
  stateChanged: result.stateChanged,
284
- validationIssues: result.validationIssues ?? [],
285
- }, null, 2),
312
+ ...(result.validationIssues?.length ? { validationIssues: result.validationIssues } : {}),
313
+ }),
286
314
  }],
287
315
  };
288
316
  });
package/dist/core.d.ts CHANGED
@@ -142,6 +142,10 @@ export declare class PsycheEngine {
142
142
  private readonly feedbackUrl;
143
143
  /** Most recent legacy compatibility hint + confidence band */
144
144
  private lastCompatibilityAssessment;
145
+ /** Proprioception cooldown — turns remaining before next trajectory check */
146
+ private proprioceptionCooldown;
147
+ /** Last detected trajectory signal (in-memory only, for status summary) */
148
+ private lastTrajectory;
145
149
  constructor(config: PsycheEngineConfig | undefined, storage: StorageAdapter);
146
150
  /**
147
151
  * Load or create initial state. Must be called before processInput/processOutput.
package/dist/core.js CHANGED
@@ -35,6 +35,7 @@ import { buildTurnObservability } from "./observability.js";
35
35
  import { DEFAULT_RELATIONSHIP_USER_ID, resolveRelationshipUserId } from "./relationship-key.js";
36
36
  import { normalizeAmbientPriors } from "./ambient-priors.js";
37
37
  import { normalizeWritebackSignals } from "./writeback-signals.js";
38
+ import { detectTrajectory } from "./proprioception.js";
38
39
  function formatWritebackFeedbackNote(feedback, locale) {
39
40
  const top = feedback?.[0];
40
41
  if (!top)
@@ -143,6 +144,10 @@ export class PsycheEngine {
143
144
  feedbackUrl;
144
145
  /** Most recent legacy compatibility hint + confidence band */
145
146
  lastCompatibilityAssessment = null;
147
+ /** Proprioception cooldown — turns remaining before next trajectory check */
148
+ proprioceptionCooldown = 0;
149
+ /** Last detected trajectory signal (in-memory only, for status summary) */
150
+ lastTrajectory = null;
146
151
  constructor(config = {}, storage) {
147
152
  this.traits = config.traits;
148
153
  this.classifier = config.classifier ?? new BuiltInClassifier();
@@ -377,6 +382,44 @@ export class PsycheEngine {
377
382
  if (appliedStimulus) {
378
383
  state = applyRelationshipDrift(state, appliedStimulus, opts?.userId);
379
384
  }
385
+ // ── Proprioception: self-trajectory awareness ──────────
386
+ if (this.proprioceptionCooldown > 0) {
387
+ this.proprioceptionCooldown--;
388
+ }
389
+ else {
390
+ const trajectory = detectTrajectory(state.stateHistory ?? [], state.baseline);
391
+ this.lastTrajectory = trajectory;
392
+ if (trajectory.kind === "decline" || trajectory.kind === "spiral") {
393
+ // Gentle awareness nudge — not correction, just noticing
394
+ if (trajectory.stabilize) {
395
+ state = {
396
+ ...state,
397
+ current: {
398
+ ...state.current,
399
+ order: clamp(state.current.order + (trajectory.stabilize.order ?? 0)),
400
+ flow: clamp(state.current.flow + (trajectory.stabilize.flow ?? 0)),
401
+ boundary: clamp(state.current.boundary + (trajectory.stabilize.boundary ?? 0)),
402
+ resonance: clamp(state.current.resonance + (trajectory.stabilize.resonance ?? 0)),
403
+ },
404
+ };
405
+ }
406
+ this.proprioceptionCooldown = 3;
407
+ }
408
+ else if (trajectory.kind === "growth" && trajectory.baselineShift) {
409
+ // Growth detected — shift baseline upward
410
+ const shift = trajectory.baselineShift;
411
+ state = {
412
+ ...state,
413
+ baseline: {
414
+ order: clamp(state.baseline.order + (shift.order ?? 0)),
415
+ flow: clamp(state.baseline.flow + (shift.flow ?? 0)),
416
+ boundary: clamp(state.baseline.boundary + (shift.boundary ?? 0)),
417
+ resonance: clamp(state.baseline.resonance + (shift.resonance ?? 0)),
418
+ },
419
+ };
420
+ this.proprioceptionCooldown = 3;
421
+ }
422
+ }
380
423
  this.lastCompatibilityAssessment = {
381
424
  legacyStimulus: appliedStimulus,
382
425
  confidence: perception.confidence,
@@ -953,7 +996,12 @@ export class PsycheEngine {
953
996
  const driveWarning = hungryDrives.length > 0
954
997
  ? ` | \u26A0\uFE0F${hungryDrives.join(",")}`
955
998
  : "";
999
+ // Proprioception: surface trajectory awareness
1000
+ const trajectory = this.lastTrajectory ?? detectTrajectory(state.stateHistory ?? [], state.baseline);
1001
+ const trajectoryNote = trajectory.description
1002
+ ? ` | \u{1F9ED}${trajectory.description}`
1003
+ : "";
956
1004
  const sigilTag = state.meta.sigilId ? ` | sigil:${state.meta.sigilId}` : "";
957
- return `${emoji} ${emotion} | flow:${Math.round(flow)} order:${Math.round(order)}${driveWarning}${sigilTag}`;
1005
+ return `${emoji} ${emotion} | flow:${Math.round(flow)} order:${Math.round(order)}${driveWarning}${trajectoryNote}${sigilTag}`;
958
1006
  }
959
1007
  }
@@ -0,0 +1,20 @@
1
+ import type { SelfState, StateSnapshot } from "./types.js";
2
+ export interface TrajectorySignal {
3
+ /** null = no signal, system is fine */
4
+ kind: "decline" | "growth" | "spiral" | null;
5
+ /** Which dimensions triggered the signal */
6
+ dimensions: (keyof SelfState)[];
7
+ /** Signal strength, 0–1 */
8
+ magnitude: number;
9
+ /** Gentle state nudge for decline/spiral (additive) */
10
+ stabilize: Partial<Record<keyof SelfState, number>> | null;
11
+ /** Baseline shift for growth (additive) */
12
+ baselineShift: Partial<Record<keyof SelfState, number>> | null;
13
+ /** One-line description for status summary */
14
+ description: string | null;
15
+ }
16
+ /**
17
+ * Detect trajectory patterns from recent state history.
18
+ * Pure function — no side effects, no persistence.
19
+ */
20
+ export declare function detectTrajectory(history: StateSnapshot[], baseline: SelfState): TrajectorySignal;
@@ -0,0 +1,104 @@
1
+ // ============================================================
2
+ // Proprioception — Self-Trajectory Awareness
3
+ //
4
+ // Perception turned inward. The system perceives its own state
5
+ // trajectory as input, the same way it perceives external stimuli.
6
+ //
7
+ // Two signals:
8
+ // decline/spiral → gentle awareness (order↑1, boundary↑1)
9
+ // growth → baseline upshift (10% of sustained gain)
10
+ //
11
+ // Not a monitor. Not a correction. Just: the system can see itself.
12
+ // ============================================================
13
+ import { DIMENSION_KEYS } from "./types.js";
14
+ // ── Constants ───────────────────────────────────────────────
15
+ /** Minimum snapshots needed to detect a trajectory */
16
+ const MIN_WINDOW = 4;
17
+ /** Per-step change threshold to count as meaningful decline */
18
+ const DECLINE_STEP = -1;
19
+ /** Per-step change threshold to count as meaningful growth */
20
+ const GROWTH_STEP = 1;
21
+ /** Maximum stabilization nudge per dimension (awareness, not correction) */
22
+ const STABILIZE_MAX = 1.5;
23
+ /** Fraction of sustained gain that becomes new baseline */
24
+ const GROWTH_BASELINE_RATIO = 0.1;
25
+ const NO_SIGNAL = {
26
+ kind: null, dimensions: [], magnitude: 0,
27
+ stabilize: null, baselineShift: null, description: null,
28
+ };
29
+ // ── Core ────────────────────────────────────────────────────
30
+ /**
31
+ * Detect trajectory patterns from recent state history.
32
+ * Pure function — no side effects, no persistence.
33
+ */
34
+ export function detectTrajectory(history, baseline) {
35
+ if (history.length < MIN_WINDOW)
36
+ return NO_SIGNAL;
37
+ const window = history.slice(-MIN_WINDOW);
38
+ const declining = [];
39
+ const growing = [];
40
+ for (const dim of DIMENSION_KEYS) {
41
+ const values = window.map(s => s.state[dim]);
42
+ const deltas = [];
43
+ for (let i = 1; i < values.length; i++) {
44
+ deltas.push(values[i] - values[i - 1]);
45
+ }
46
+ if (deltas.every(d => d < DECLINE_STEP)) {
47
+ declining.push(dim);
48
+ }
49
+ if (deltas.every(d => d > GROWTH_STEP) && values[values.length - 1] > baseline[dim]) {
50
+ growing.push(dim);
51
+ }
52
+ }
53
+ // ── Spiral: 2+ dimensions declining together ──
54
+ if (declining.length >= 2) {
55
+ const totalDrop = declining.reduce((sum, dim) => {
56
+ const vals = window.map(s => s.state[dim]);
57
+ return sum + Math.abs(vals[vals.length - 1] - vals[0]);
58
+ }, 0);
59
+ const stabilize = {};
60
+ for (const dim of declining) {
61
+ stabilize[dim] = STABILIZE_MAX;
62
+ }
63
+ return {
64
+ kind: "spiral",
65
+ dimensions: declining,
66
+ magnitude: Math.min(1, totalDrop / 40),
67
+ stabilize,
68
+ baselineShift: null,
69
+ description: `spiral: ${declining.join("+")} declining ${window.length} turns`,
70
+ };
71
+ }
72
+ // ── Single dimension decline ──
73
+ if (declining.length === 1) {
74
+ const dim = declining[0];
75
+ const vals = window.map(s => s.state[dim]);
76
+ const drop = Math.abs(vals[vals.length - 1] - vals[0]);
77
+ return {
78
+ kind: "decline",
79
+ dimensions: declining,
80
+ magnitude: Math.min(1, drop / 20),
81
+ stabilize: { [dim]: STABILIZE_MAX * 0.5 },
82
+ baselineShift: null,
83
+ description: `${String(dim)} declining ${window.length} turns (-${drop.toFixed(1)})`,
84
+ };
85
+ }
86
+ // ── Growth: sustained increase above baseline ──
87
+ if (growing.length > 0) {
88
+ const baselineShift = {};
89
+ for (const dim of growing) {
90
+ const vals = window.map(s => s.state[dim]);
91
+ const gain = vals[vals.length - 1] - vals[0];
92
+ baselineShift[dim] = gain * GROWTH_BASELINE_RATIO;
93
+ }
94
+ return {
95
+ kind: "growth",
96
+ dimensions: growing,
97
+ magnitude: Math.min(1, growing.length / 4),
98
+ stabilize: null,
99
+ baselineShift,
100
+ description: `growth: ${growing.join("+")} sustained increase`,
101
+ };
102
+ }
103
+ return NO_SIGNAL;
104
+ }
@@ -2,7 +2,7 @@
2
2
  "id": "psyche-ai",
3
3
  "name": "Artificial Psyche",
4
4
  "description": "AI-first subjectivity kernel for agents with continuous appraisal, relation dynamics, and adaptive reply loops",
5
- "version": "11.6.0",
5
+ "version": "11.7.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "11.6.0",
3
+ "version": "11.7.0",
4
4
  "description": "AI-first subjectivity kernel for agents with continuous appraisal, relation dynamics, and adaptive reply loops",
5
5
  "mcpName": "io.github.Shangri-la-0428/psyche-ai",
6
6
  "type": "module",