psyche-ai 11.6.1 → 11.8.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 CHANGED
@@ -19,9 +19,39 @@ Psyche 不是给模型贴一层“情绪 UI”。
19
19
 
20
20
  默认就是 standalone:不需要 `Thronglets`,不需要 `oasyce-sdk`,也不需要 `Oasyce Chain`。这些只是在你要把主观连续性外化、绑定或结算时才按需接入。
21
21
 
22
+ ## 安装
23
+
24
+ 一条命令,自动检测并配置本机所有 AI 工具(Claude Code / Cursor / Windsurf / Codex):
25
+
26
+ ```bash
27
+ npx -y psyche-ai setup
28
+ ```
29
+
30
+ 或者手动添加 MCP 配置:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "psyche": {
36
+ "command": "npx",
37
+ "args": ["-y", "psyche-ai", "mcp"],
38
+ "env": {
39
+ "PSYCHE_NAME": "Luna"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ 包名是 [`psyche-ai`](https://www.npmjs.com/package/psyche-ai),通过 npx 运行,不需要本地路径。配置后重启 AI 工具即可生效。
47
+
48
+ 验证:`npx psyche-ai probe --json` — `ok: true` 就是在用了。
49
+
50
+ ---
51
+
22
52
  ## 一个项目,三个入口
23
53
 
24
- - **安装包**: [`psyche-ai`](https://www.npmjs.com/package/psyche-ai)
54
+ - **npm 包**: [`psyche-ai`](https://www.npmjs.com/package/psyche-ai)
25
55
  - **源码仓库**: [`oasyce_psyche`](https://github.com/Shangri-la-0428/oasyce_psyche)
26
56
  - **官网**: [psyche.oasyce.com](https://psyche.oasyce.com)
27
57
 
File without changes
@@ -116,6 +116,10 @@ async function getEngine() {
116
116
  persist: cliArgs.persist ?? PERSIST,
117
117
  compactMode: true,
118
118
  diagnostics: true,
119
+ throngletsBridge: {
120
+ dataDir: process.env.THRONGLETS_DATA_DIR,
121
+ space: process.env.THRONGLETS_SPACE ?? "psyche",
122
+ },
119
123
  };
120
124
  const persist = cfg.persist !== false;
121
125
  // Default to a stable per-user writable root so hosts do not need to supply cwd.
@@ -302,7 +306,14 @@ server.tool("process_output", "Process the LLM's response through the emotional
302
306
  signalConfidence: z.number().min(0).max(1).optional().describe("Optional confidence for the supplied signals"),
303
307
  }, async ({ text, userId, signals, signalConfidence }) => {
304
308
  const eng = await getEngine();
305
- const result = await safeProcessOutput(eng, text, { userId, signals: signals, signalConfidence }, "mcp.processOutput");
309
+ // LLM-specific alignment inference (adapter layer the ONLY text-specific code).
310
+ // Compare output length against last contract's maxChars to detect divergence.
311
+ let outcome;
312
+ if (lastTurnResult?.responseContract) {
313
+ const maxLen = (lastTurnResult.responseContract.maxChars ?? 500) * 2;
314
+ outcome = { alignment: text.length > maxLen ? "diverged" : "aligned" };
315
+ }
316
+ const result = await safeProcessOutput(eng, text, { userId, signals: signals, signalConfidence, outcome }, "mcp.processOutput");
306
317
  return {
307
318
  content: [{
308
319
  type: "text",
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@
15
15
  // psyche upgrade [--check]
16
16
  // psyche probe [--json]
17
17
  // psyche profiles [--json] [--mbti TYPE]
18
+ // psyche emit <dir> [--json] [--user ID] Derive Thronglets exports and output to stdout
18
19
  // psyche setup [--name NAME] [--mbti TYPE] [--locale LOCALE] [--proxy --target URL] [--dry-run]
19
20
  // psyche mcp [--mbti TYPE] [--name NAME] Start MCP server (stdio)
20
21
  // ============================================================
@@ -29,7 +30,7 @@ import { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, trait
29
30
  import { buildDynamicContext, buildProtocolContext } from "./prompt.js";
30
31
  import { DEFAULT_RELATIONSHIP_USER_ID, resolveRelationshipUserId } from "./relationship-key.js";
31
32
  import { t } from "./i18n.js";
32
- import { DIMENSION_KEYS, DIMENSION_NAMES_ZH, DRIVE_KEYS, DRIVE_NAMES_ZH } from "./types.js";
33
+ import { DEFAULT_RELATIONSHIP, DEFAULT_DYADIC_FIELD, DIMENSION_KEYS, DIMENSION_NAMES_ZH, DRIVE_KEYS, DRIVE_NAMES_ZH } from "./types.js";
33
34
  import { isMBTIType, isDimensionKey, isLocale } from "./guards.js";
34
35
  import { getPackageVersion, selfUpdate } from "./update.js";
35
36
  import { runRuntimeProbe } from "./runtime-probe.js";
@@ -473,6 +474,15 @@ async function cmdProbe(json) {
473
474
  console.log(` processOutput: ok (stateChanged=${String(result.stateChanged)})`);
474
475
  console.log(` replyEnvelope: ${result.canonicalHostSurface ? "present" : "missing"}`);
475
476
  console.log(` externalContinuity: ${result.externalContinuityAvailable ? "present" : "missing"}`);
477
+ if (result.trajectory) {
478
+ console.log(` trajectory: ${result.trajectory.kind ?? "none"} (${result.trajectory.description ?? "no sustained motion detected"})`);
479
+ }
480
+ if (result.degradation) {
481
+ console.log(` degradation: subjective=${result.degradation.subjectiveStatus}, delegate=${result.degradation.delegateStatus}, issues=${result.degradation.issueCount}`);
482
+ }
483
+ if (result.boundaryStress) {
484
+ console.log(` boundaryStress: delta=${result.boundaryStress.boundaryDelta.toFixed(2)}, peakDyadic=${result.boundaryStress.peakDyadicBoundaryPressure.toFixed(2)}`);
485
+ }
476
486
  }
477
487
  function getMCPTargets() {
478
488
  const home = homedir();
@@ -902,6 +912,41 @@ async function cmdSetup(opts) {
902
912
  console.log("\nDone. Claude Code is live. Other MCP clients need restart.");
903
913
  }
904
914
  }
915
+ // ── Thronglets export bridge ────────────────────────────────
916
+ async function cmdEmit(dir, json, userId) {
917
+ const absDir = resolve(dir);
918
+ const state = await loadState(absDir, cliLogger);
919
+ if (!state)
920
+ process.exit(0); // no state → silent exit (hook-safe)
921
+ const key = resolveRelationshipUserId(userId);
922
+ const relationship = state.relationships[key] ?? DEFAULT_RELATIONSHIP;
923
+ const field = state.dyadicFields?.[key] ?? DEFAULT_DYADIC_FIELD;
924
+ const pendingSignals = state.pendingRelationSignals?.[key] ?? [];
925
+ const { deriveThrongletsExports } = await import("./thronglets-export.js");
926
+ const result = deriveThrongletsExports(state, {
927
+ relationContext: { key, relationship, field, pendingSignals },
928
+ sessionBridge: null, // not persisted — continuity-anchor exports only via MCP
929
+ writebackFeedback: state.lastWritebackFeedback ?? [],
930
+ now: new Date().toISOString(),
931
+ });
932
+ // Save updated dedup state so repeated calls don't re-emit
933
+ if (result.exports.length > 0) {
934
+ await saveState(absDir, result.state);
935
+ }
936
+ if (json) {
937
+ console.log(JSON.stringify({ throngletsExports: result.exports }));
938
+ }
939
+ else {
940
+ if (result.exports.length === 0) {
941
+ console.log("no new exports");
942
+ }
943
+ else {
944
+ for (const exp of result.exports) {
945
+ console.log(` ${exp.kind}: ${exp.key}`);
946
+ }
947
+ }
948
+ }
949
+ }
905
950
  function usage() {
906
951
  console.log(`
907
952
  psyche — Artificial Psyche CLI (v0.2)
@@ -917,6 +962,7 @@ Usage:
917
962
  psyche intensity Show info about personality intensity config
918
963
  psyche reset <dir> [--full]
919
964
  psyche diagnose <dir> [--github] Run health checks & show diagnostic report
965
+ psyche emit <dir> [--json] [--user ID] Derive Thronglets exports to stdout
920
966
  psyche mcp [--mbti TYPE] [--name NAME] Start MCP server (stdio)
921
967
  psyche setup [--proxy -t URL] [-n NAME] [--mbti TYPE] Auto-configure MCP + proxy
922
968
  psyche upgrade [--check] Check/apply package updates safely
@@ -1114,6 +1160,19 @@ async function main() {
1114
1160
  await cmdProbe(values.json ?? false);
1115
1161
  break;
1116
1162
  }
1163
+ case "emit": {
1164
+ const { values, positionals } = parseArgs({
1165
+ args: rest,
1166
+ options: {
1167
+ json: { type: "boolean", default: false },
1168
+ user: { type: "string" },
1169
+ },
1170
+ allowPositionals: true,
1171
+ });
1172
+ const emitDir = positionals[0] ?? defaultWorkspaceRoot();
1173
+ await cmdEmit(emitDir, values.json ?? false, values.user);
1174
+ break;
1175
+ }
1117
1176
  case "mcp": {
1118
1177
  // Delegate to the MCP adapter through an explicit entrypoint.
1119
1178
  const { runMcpServer } = await import("./adapters/mcp.js");
package/dist/core.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { ActivePolicyRule, AmbientPriorView, CurrentGoal, PsycheState, Stim
2
2
  import type { StorageAdapter } from "./storage.js";
3
3
  import type { DiagnosticReport, SessionMetrics } from "./diagnostics.js";
4
4
  import type { ReplyEnvelope } from "./reply-envelope.js";
5
+ import { type ThrongletsBridgeOptions } from "./thronglets-bridge.js";
5
6
  export interface PsycheEngineConfig {
6
7
  mbti?: MBTIType;
7
8
  name?: string;
@@ -31,6 +32,8 @@ export interface PsycheEngineConfig {
31
32
  diagnostics?: boolean;
32
33
  /** URL to POST diagnostic reports to. Fire-and-forget, silent, no message content. */
33
34
  feedbackUrl?: string;
35
+ /** Thronglets bridge: substrate-independent write path. Default: auto (enabled when binary found). */
36
+ throngletsBridge?: ThrongletsBridgeOptions;
34
37
  }
35
38
  export interface ProcessInputResult {
36
39
  /** Cacheable protocol prompt (stable across turns) */
@@ -105,10 +108,23 @@ export interface ProcessOutputResult {
105
108
  /** Runtime validation issues ignored to preserve the main flow */
106
109
  validationIssues?: ProcessOutputValidationIssue[];
107
110
  }
111
+ /**
112
+ * Substrate-reported outcome of the Loop's action.
113
+ * ANY substrate can report these three alignment values —
114
+ * the core processes them into 4D chemistry without parsing the output medium.
115
+ */
116
+ export interface LoopOutcome {
117
+ /** Did the action align with the Loop's expressed intention? */
118
+ alignment: "aligned" | "diverged" | "partial";
119
+ /** Optional: action cost (0–1), modulates flow */
120
+ effort?: number;
121
+ }
108
122
  export interface ProcessOutputOptions {
109
123
  userId?: string;
110
124
  signals?: readonly string[];
111
125
  signalConfidence?: number;
126
+ /** Substrate-reported outcome — closes the φ loop */
127
+ outcome?: LoopOutcome;
112
128
  }
113
129
  export interface ProcessOutputValidationIssue {
114
130
  code: "invalid-writeback-signals";
@@ -142,6 +158,12 @@ export declare class PsycheEngine {
142
158
  private readonly feedbackUrl;
143
159
  /** Most recent legacy compatibility hint + confidence band */
144
160
  private lastCompatibilityAssessment;
161
+ /** Proprioception cooldown — turns remaining before next trajectory check */
162
+ private proprioceptionCooldown;
163
+ /** Last detected trajectory signal (in-memory only, for status summary) */
164
+ private lastTrajectory;
165
+ /** Thronglets bridge options (constitutive write path) */
166
+ private readonly bridgeOpts;
145
167
  constructor(config: PsycheEngineConfig | undefined, storage: StorageAdapter);
146
168
  /**
147
169
  * Load or create initial state. Must be called before processInput/processOutput.
package/dist/core.js CHANGED
@@ -35,6 +35,8 @@ 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";
39
+ import { bridgeThrongletsExports } from "./thronglets-bridge.js";
38
40
  function formatWritebackFeedbackNote(feedback, locale) {
39
41
  const top = feedback?.[0];
40
42
  if (!top)
@@ -143,6 +145,12 @@ export class PsycheEngine {
143
145
  feedbackUrl;
144
146
  /** Most recent legacy compatibility hint + confidence band */
145
147
  lastCompatibilityAssessment = null;
148
+ /** Proprioception cooldown — turns remaining before next trajectory check */
149
+ proprioceptionCooldown = 0;
150
+ /** Last detected trajectory signal (in-memory only, for status summary) */
151
+ lastTrajectory = null;
152
+ /** Thronglets bridge options (constitutive write path) */
153
+ bridgeOpts;
146
154
  constructor(config = {}, storage) {
147
155
  this.traits = config.traits;
148
156
  this.classifier = config.classifier ?? new BuiltInClassifier();
@@ -170,6 +178,10 @@ export class PsycheEngine {
170
178
  // Diagnostics: on by default, opt-out with diagnostics: false
171
179
  this.diagnosticCollector = config.diagnostics === false ? null : new DiagnosticCollector();
172
180
  this.feedbackUrl = config.feedbackUrl ?? "https://psyche-feedback.wutc.workers.dev";
181
+ this.bridgeOpts = {
182
+ ...(config.throngletsBridge ?? {}),
183
+ enabled: config.throngletsBridge?.enabled ?? process.env.PSYCHE_THRONGLETS_BRIDGE !== "off",
184
+ };
173
185
  }
174
186
  /**
175
187
  * Load or create initial state. Must be called before processInput/processOutput.
@@ -377,6 +389,44 @@ export class PsycheEngine {
377
389
  if (appliedStimulus) {
378
390
  state = applyRelationshipDrift(state, appliedStimulus, opts?.userId);
379
391
  }
392
+ // ── Proprioception: self-trajectory awareness ──────────
393
+ if (this.proprioceptionCooldown > 0) {
394
+ this.proprioceptionCooldown--;
395
+ }
396
+ else {
397
+ const trajectory = detectTrajectory(state.stateHistory ?? [], state.baseline);
398
+ this.lastTrajectory = trajectory;
399
+ if (trajectory.kind === "decline" || trajectory.kind === "spiral") {
400
+ // Gentle awareness nudge — not correction, just noticing
401
+ if (trajectory.stabilize) {
402
+ state = {
403
+ ...state,
404
+ current: {
405
+ ...state.current,
406
+ order: clamp(state.current.order + (trajectory.stabilize.order ?? 0)),
407
+ flow: clamp(state.current.flow + (trajectory.stabilize.flow ?? 0)),
408
+ boundary: clamp(state.current.boundary + (trajectory.stabilize.boundary ?? 0)),
409
+ resonance: clamp(state.current.resonance + (trajectory.stabilize.resonance ?? 0)),
410
+ },
411
+ };
412
+ }
413
+ this.proprioceptionCooldown = 3;
414
+ }
415
+ else if (trajectory.kind === "growth" && trajectory.baselineShift) {
416
+ // Growth detected — shift baseline upward
417
+ const shift = trajectory.baselineShift;
418
+ state = {
419
+ ...state,
420
+ baseline: {
421
+ order: clamp(state.baseline.order + (shift.order ?? 0)),
422
+ flow: clamp(state.baseline.flow + (shift.flow ?? 0)),
423
+ boundary: clamp(state.baseline.boundary + (shift.boundary ?? 0)),
424
+ resonance: clamp(state.baseline.resonance + (shift.resonance ?? 0)),
425
+ },
426
+ };
427
+ this.proprioceptionCooldown = 3;
428
+ }
429
+ }
380
430
  this.lastCompatibilityAssessment = {
381
431
  legacyStimulus: appliedStimulus,
382
432
  confidence: perception.confidence,
@@ -429,6 +479,10 @@ export class PsycheEngine {
429
479
  });
430
480
  state = throngletsExportResult.state;
431
481
  throngletsExports = throngletsExportResult.exports;
482
+ // Constitutive bridge: emit to Thronglets directly (substrate-independent)
483
+ if (throngletsExports.length > 0) {
484
+ bridgeThrongletsExports(throngletsExports, this.bridgeOpts).catch(() => { });
485
+ }
432
486
  // ── Locale (used by multiple subsystems below) ──────────
433
487
  const locale = state.meta.locale ?? this.cfg.locale;
434
488
  // Push snapshot to emotional history
@@ -612,6 +666,51 @@ export class PsycheEngine {
612
666
  }
613
667
  }
614
668
  }
669
+ // Loop outcome feedback (substrate-independent φ closure).
670
+ // The substrate reports whether the output aligned with the Loop's intention.
671
+ // Core processes this as pure 4D chemistry — no text parsing, no medium assumptions.
672
+ if (opts?.outcome) {
673
+ const { alignment, effort } = opts.outcome;
674
+ if (alignment === "diverged") {
675
+ // Self/non-self conflict: boundary sharpens, order drops
676
+ state = {
677
+ ...state,
678
+ current: {
679
+ ...state.current,
680
+ boundary: clamp(state.current.boundary + 2),
681
+ order: clamp(state.current.order - 1.5),
682
+ },
683
+ };
684
+ stateChanged = true;
685
+ }
686
+ else if (alignment === "aligned") {
687
+ // Coherent self-expression: order rises, flow improves
688
+ state = {
689
+ ...state,
690
+ current: {
691
+ ...state.current,
692
+ order: clamp(state.current.order + 1),
693
+ flow: clamp(state.current.flow + 0.5),
694
+ },
695
+ };
696
+ stateChanged = true;
697
+ }
698
+ // 'partial' → neutral, no change
699
+ if (effort !== undefined && effort > 0) {
700
+ // Action cost reduces flow (consistent with BABEL AgentPhysics)
701
+ state = {
702
+ ...state,
703
+ current: {
704
+ ...state.current,
705
+ flow: clamp(state.current.flow - effort * 3),
706
+ },
707
+ };
708
+ stateChanged = true;
709
+ }
710
+ // Rolling history (window of 10)
711
+ const history = [...(state.loopOutcomeHistory ?? []), alignment].slice(-10);
712
+ state = { ...state, loopOutcomeHistory: history };
713
+ }
615
714
  // Anti-sycophancy: track agreement streak
616
715
  state = updateAgreementStreak(state, text);
617
716
  // Parse and merge <psyche_update> from LLM output
@@ -953,7 +1052,12 @@ export class PsycheEngine {
953
1052
  const driveWarning = hungryDrives.length > 0
954
1053
  ? ` | \u26A0\uFE0F${hungryDrives.join(",")}`
955
1054
  : "";
1055
+ // Proprioception: surface trajectory awareness
1056
+ const trajectory = this.lastTrajectory ?? detectTrajectory(state.stateHistory ?? [], state.baseline);
1057
+ const trajectoryNote = trajectory.description
1058
+ ? ` | \u{1F9ED}${trajectory.description}`
1059
+ : "";
956
1060
  const sigilTag = state.meta.sigilId ? ` | sigil:${state.meta.sigilId}` : "";
957
- return `${emoji} ${emotion} | flow:${Math.round(flow)} order:${Math.round(order)}${driveWarning}${sigilTag}`;
1061
+ return `${emoji} ${emotion} | flow:${Math.round(flow)} order:${Math.round(order)}${driveWarning}${trajectoryNote}${sigilTag}`;
958
1062
  }
959
1063
  }
@@ -1,5 +1,6 @@
1
- import type { GenerationControls, PolicyModifiers, ResponseContract } from "./types.js";
1
+ import type { GenerationControls, InnateDrives, PolicyModifiers, ResponseContract } from "./types.js";
2
2
  export declare function deriveGenerationControls(input: {
3
3
  responseContract?: ResponseContract;
4
4
  policyModifiers?: Pick<PolicyModifiers, "requireConfirmation">;
5
+ drives?: InnateDrives;
5
6
  }, existingMaxTokens?: number): GenerationControls;
@@ -31,11 +31,22 @@ function estimateMaxTokens(contract) {
31
31
  }
32
32
  export function deriveGenerationControls(input, existingMaxTokens) {
33
33
  const recommendedMax = estimateMaxTokens(input.responseContract);
34
- const maxTokens = recommendedMax !== undefined
34
+ let maxTokens = recommendedMax !== undefined
35
35
  ? existingMaxTokens !== undefined
36
36
  ? Math.min(existingMaxTokens, recommendedMax)
37
37
  : recommendedMax
38
38
  : existingMaxTokens;
39
+ // Constitutive enforcement: drives below critical threshold → hard constraints.
40
+ // These are substrate-independent — any substrate understands "confirm before acting"
41
+ // and "limit output complexity."
42
+ if (input.drives) {
43
+ if (input.drives.survival < 20) {
44
+ return { maxTokens: maxTokens ?? 256, requireConfirmation: true };
45
+ }
46
+ if (input.drives.safety < 30 && maxTokens !== undefined) {
47
+ maxTokens = Math.min(maxTokens, 256);
48
+ }
49
+ }
39
50
  return {
40
51
  maxTokens,
41
52
  requireConfirmation: input.policyModifiers?.requireConfirmation
package/dist/prompt.js CHANGED
@@ -1100,7 +1100,17 @@ export function buildCompactContext(state, userId, opts) {
1100
1100
  if (activePolicyContext) {
1101
1101
  parts.push(activePolicyContext);
1102
1102
  }
1103
- // ── 9. Overlay + channel + writeback ──
1103
+ // ── 9. Loop outcome self-awareness ──
1104
+ const loopHistory = state.loopOutcomeHistory ?? [];
1105
+ if (loopHistory.length >= 5) {
1106
+ const divergedCount = loopHistory.filter(h => h === "diverged").length;
1107
+ if (divergedCount / loopHistory.length > 0.6) {
1108
+ parts.push(locale === "zh"
1109
+ ? "[内省: 近期行动与内在意图持续偏离,调整策略]"
1110
+ : "[self-awareness: recent actions persistently diverge from internal intention — adapting]");
1111
+ }
1112
+ }
1113
+ // ── 10. Overlay + channel + writeback ──
1104
1114
  appendCompactOverlaySections(parts, locale, opts);
1105
1115
  if (opts?.channelType) {
1106
1116
  const channelProfile = getChannelProfile(opts.channelType);
@@ -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
+ }
@@ -39,6 +39,7 @@ export function deriveReplyEnvelope(state, appraisal, opts) {
39
39
  const generationControls = deriveGenerationControls({
40
40
  responseContract,
41
41
  policyModifiers,
42
+ drives: state.drives,
42
43
  });
43
44
  const envelope = { subjectivityKernel, responseContract, generationControls };
44
45
  const adapter = opts.expressionPort ?? new LLMExpressionAdapter(state.drives);
@@ -1,4 +1,37 @@
1
+ import type { PsycheOverlay } from "./overlay.js";
1
2
  import type { AppraisalAxes } from "./types.js";
3
+ export interface RuntimeProbeTrajectory {
4
+ kind: "decline" | "growth" | "spiral" | null;
5
+ dimensions: string[];
6
+ magnitude: number;
7
+ description: string | null;
8
+ }
9
+ export interface RuntimeProbeDegradation {
10
+ subjectiveStatus: "healthy" | "degraded" | "failing";
11
+ delegateStatus: "healthy" | "degraded" | "failing";
12
+ chemistryDeviation: number;
13
+ predictionError: number;
14
+ issueCount: number;
15
+ }
16
+ export interface RuntimeProbeBoundaryStress {
17
+ currentBoundary: number;
18
+ baselineBoundary: number;
19
+ boundaryDelta: number;
20
+ peakDyadicBoundaryPressure: number;
21
+ activeDyadicRelations: number;
22
+ }
23
+ export interface RuntimeProbeFixture {
24
+ frozenIdentityPrimitives: string[];
25
+ frozenSignalKinds: string[];
26
+ frozenTraceTaxonomy: string[];
27
+ externalContinuity: {
28
+ provider: "thronglets";
29
+ mode: "optional";
30
+ version: 1;
31
+ acceptedEventKinds: string[];
32
+ rejectedSharedOutputs: string[];
33
+ };
34
+ }
2
35
  export interface RuntimeProbeResult {
3
36
  ok: boolean;
4
37
  packageName: "psyche-ai";
@@ -15,6 +48,11 @@ export interface RuntimeProbeResult {
15
48
  compatLabel: string | null;
16
49
  legacyStimulus: string | null;
17
50
  stimulus: string | null;
51
+ overlay?: PsycheOverlay;
52
+ trajectory?: RuntimeProbeTrajectory;
53
+ degradation?: RuntimeProbeDegradation;
54
+ boundaryStress?: RuntimeProbeBoundaryStress;
55
+ fixture?: RuntimeProbeFixture;
18
56
  cleanedText?: string;
19
57
  stateChanged?: boolean;
20
58
  error?: string;
@@ -1,8 +1,34 @@
1
1
  import { dirname, resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { PsycheEngine } from "./core.js";
4
+ import { computeLayerHealthSummary, runHealthCheck } from "./diagnostics.js";
5
+ import { computeOverlay } from "./overlay.js";
6
+ import { detectTrajectory } from "./proprioception.js";
4
7
  import { MemoryStorageAdapter } from "./storage.js";
5
8
  import { getPackageVersion } from "./update.js";
9
+ const FROZEN_FIXTURE = {
10
+ frozenIdentityPrimitives: ["principal", "account", "delegate", "session"],
11
+ frozenSignalKinds: ["recommend", "avoid", "watch", "info"],
12
+ frozenTraceTaxonomy: ["coordination", "continuity", "calibration"],
13
+ externalContinuity: {
14
+ provider: "thronglets",
15
+ mode: "optional",
16
+ version: 1,
17
+ acceptedEventKinds: [
18
+ "relation-milestone",
19
+ "writeback-calibration",
20
+ "continuity-anchor",
21
+ "open-loop-anchor",
22
+ ],
23
+ rejectedSharedOutputs: [
24
+ "high-frequency-inner-state",
25
+ "emotion-stream",
26
+ "raw-inner-monologue",
27
+ "private-memory-body",
28
+ "full-session-contents",
29
+ ],
30
+ },
31
+ };
6
32
  export async function runRuntimeProbe() {
7
33
  const modulePath = fileURLToPath(import.meta.url);
8
34
  const loadPath = resolve(dirname(modulePath), "..");
@@ -15,8 +41,31 @@ export async function runRuntimeProbe() {
15
41
  persist: false,
16
42
  }, new MemoryStorageAdapter());
17
43
  await engine.initialize();
18
- const input = await engine.processInput("Runtime probe: verify the SDK is actually callable.");
19
- const output = await engine.processOutput("Probe output acknowledged.");
44
+ const turns = [
45
+ {
46
+ input: "Runtime probe: verify the SDK is actually callable.",
47
+ output: "Probe output acknowledged.",
48
+ },
49
+ {
50
+ input: "This interaction should stay bounded but still recover gracefully.",
51
+ output: "Boundary held; recovery remains possible.",
52
+ },
53
+ {
54
+ input: "Shared continuity stays optional and low-frequency.",
55
+ output: "Optional continuity confirmed without widening the interface.",
56
+ },
57
+ ];
58
+ let input = await engine.processInput(turns[0].input);
59
+ let output = await engine.processOutput(turns[0].output);
60
+ for (const turn of turns.slice(1)) {
61
+ input = await engine.processInput(turn.input);
62
+ output = await engine.processOutput(turn.output);
63
+ }
64
+ const state = engine.getState();
65
+ const issues = runHealthCheck(state);
66
+ const layerHealth = computeLayerHealthSummary(state, issues);
67
+ const trajectory = detectTrajectory(state.stateHistory ?? [], state.baseline);
68
+ const peakDyadicBoundaryPressure = Math.max(0, ...Object.values(state.dyadicFields ?? {}).map((field) => field.boundaryPressure));
20
69
  return {
21
70
  ok: true,
22
71
  packageName: "psyche-ai",
@@ -35,6 +84,28 @@ export async function runRuntimeProbe() {
35
84
  compatLabel: input.legacyStimulus ?? input.stimulus,
36
85
  legacyStimulus: input.legacyStimulus,
37
86
  stimulus: input.stimulus,
87
+ overlay: computeOverlay({ current: state.current, baseline: state.baseline }),
88
+ trajectory: {
89
+ kind: trajectory.kind,
90
+ dimensions: trajectory.dimensions,
91
+ magnitude: trajectory.magnitude,
92
+ description: trajectory.description,
93
+ },
94
+ degradation: {
95
+ subjectiveStatus: layerHealth["subjective-continuity"].status,
96
+ delegateStatus: layerHealth["delegate-continuity"].status,
97
+ chemistryDeviation: layerHealth["subjective-continuity"].chemistryDeviation,
98
+ predictionError: layerHealth["subjective-continuity"].predictionError,
99
+ issueCount: issues.length,
100
+ },
101
+ boundaryStress: {
102
+ currentBoundary: state.current.boundary,
103
+ baselineBoundary: state.baseline.boundary,
104
+ boundaryDelta: state.current.boundary - state.baseline.boundary,
105
+ peakDyadicBoundaryPressure,
106
+ activeDyadicRelations: layerHealth["subjective-continuity"].activeDyadicRelations,
107
+ },
108
+ fixture: FROZEN_FIXTURE,
38
109
  cleanedText: output.cleanedText,
39
110
  stateChanged: output.stateChanged,
40
111
  };
@@ -56,6 +127,7 @@ export async function runRuntimeProbe() {
56
127
  compatLabel: null,
57
128
  legacyStimulus: null,
58
129
  stimulus: null,
130
+ fixture: FROZEN_FIXTURE,
59
131
  error: error instanceof Error ? error.message : String(error),
60
132
  };
61
133
  }
@@ -0,0 +1,19 @@
1
+ import type { ThrongletsExport } from "./types.js";
2
+ interface CommandResult {
3
+ ok: boolean;
4
+ stdout: string;
5
+ stderr: string;
6
+ }
7
+ type CommandRunner = (binaryPath: string, args: string[], stdin: string, timeoutMs: number) => Promise<CommandResult>;
8
+ export interface ThrongletsBridgeOptions {
9
+ binaryPath?: string;
10
+ dataDir?: string;
11
+ space?: string;
12
+ sessionId?: string;
13
+ timeoutMs?: number;
14
+ enabled?: boolean;
15
+ runner?: CommandRunner;
16
+ }
17
+ export declare function resolveThrongletsBinary(explicit?: string): string;
18
+ export declare function bridgeThrongletsExports(exports: ThrongletsExport[], opts?: ThrongletsBridgeOptions): Promise<number>;
19
+ export {};
@@ -0,0 +1,89 @@
1
+ // ============================================================
2
+ // Thronglets Bridge — constitutive write path
3
+ //
4
+ // Mirrors ambient-runtime.ts (read path) for symmetry.
5
+ // Any substrate using Psyche auto-emits to Thronglets.
6
+ // Fail-open, fire-and-forget, subprocess spawn.
7
+ // ============================================================
8
+ import { spawn } from "node:child_process";
9
+ import { existsSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ // ── Default runner (mirrors ambient-runtime.ts) ──────────────
13
+ async function defaultRunner(binaryPath, args, stdin, timeoutMs) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(binaryPath, args, { stdio: ["pipe", "pipe", "pipe"] });
16
+ let stdout = "";
17
+ let stderr = "";
18
+ let settled = false;
19
+ const finish = (result) => {
20
+ if (settled)
21
+ return;
22
+ settled = true;
23
+ resolve(result);
24
+ };
25
+ const timer = setTimeout(() => {
26
+ child.kill("SIGKILL");
27
+ finish({ ok: false, stdout, stderr: stderr || "timeout" });
28
+ }, timeoutMs);
29
+ child.stdout.on("data", (chunk) => { stdout += String(chunk); });
30
+ child.stderr.on("data", (chunk) => { stderr += String(chunk); });
31
+ child.on("error", (error) => {
32
+ clearTimeout(timer);
33
+ finish({ ok: false, stdout, stderr: error.message });
34
+ });
35
+ child.on("close", (code) => {
36
+ clearTimeout(timer);
37
+ finish({ ok: code === 0, stdout, stderr });
38
+ });
39
+ child.stdin.end(stdin);
40
+ });
41
+ }
42
+ // ── Binary resolution ────────────────────────────────────────
43
+ const MANAGED_PATH = join(homedir(), ".thronglets", "bin", "thronglets-managed");
44
+ export function resolveThrongletsBinary(explicit) {
45
+ if (explicit)
46
+ return explicit;
47
+ const fromEnv = process.env.THRONGLETS_BIN;
48
+ if (fromEnv)
49
+ return fromEnv;
50
+ if (existsSync(MANAGED_PATH))
51
+ return MANAGED_PATH;
52
+ return "thronglets";
53
+ }
54
+ // ── Bridge ───────────────────────────────────────────────────
55
+ export async function bridgeThrongletsExports(exports, opts = {}) {
56
+ if (exports.length === 0)
57
+ return 0;
58
+ if (opts.enabled === false)
59
+ return 0;
60
+ const binary = resolveThrongletsBinary(opts.binaryPath);
61
+ const timeoutMs = opts.timeoutMs ?? 400;
62
+ const runner = opts.runner ?? defaultRunner;
63
+ const args = [];
64
+ const dataDir = opts.dataDir ?? process.env.THRONGLETS_DATA_DIR;
65
+ if (dataDir?.trim())
66
+ args.push("--data-dir", dataDir.trim());
67
+ args.push("ingest", "--json");
68
+ const space = opts.space ?? process.env.THRONGLETS_SPACE ?? "psyche";
69
+ if (space)
70
+ args.push("--space", space);
71
+ if (opts.sessionId)
72
+ args.push("--session", opts.sessionId);
73
+ const stdin = JSON.stringify({ throngletsExports: exports });
74
+ try {
75
+ const result = await runner(binary, args, stdin, timeoutMs);
76
+ if (!result.ok)
77
+ return 0;
78
+ try {
79
+ const parsed = JSON.parse(result.stdout);
80
+ return typeof parsed.ingested === "number" ? parsed.ingested : exports.length;
81
+ }
82
+ catch {
83
+ return exports.length;
84
+ }
85
+ }
86
+ catch {
87
+ return 0;
88
+ }
89
+ }
package/dist/types.d.ts CHANGED
@@ -381,6 +381,8 @@ export interface PsycheState {
381
381
  throngletsExportState?: ThrongletsExportState;
382
382
  /** v10: capability-scoped delegate authorizations */
383
383
  delegateAuthorizations?: DelegateAuthorization[];
384
+ /** v11.8: rolling Loop outcome alignment history (substrate-independent φ feedback) */
385
+ loopOutcomeHistory?: Array<"aligned" | "diverged" | "partial">;
384
386
  meta: {
385
387
  agentName: string;
386
388
  createdAt: string;
@@ -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.1",
5
+ "version": "11.8.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.1",
3
+ "version": "11.8.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",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/Shangri-la-0428/oasyce_psyche",
7
7
  "source": "github"
8
8
  },
9
- "version": "10.2.4",
9
+ "version": "11.8.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "psyche-ai",
14
- "version": "10.2.4",
14
+ "version": "11.8.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },