psyche-ai 10.0.4 → 10.1.1

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
@@ -1,7 +1,7 @@
1
1
  # Psyche — 面向智能体的 AI-first 主观性内核
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/psyche-ai)](https://www.npmjs.com/package/psyche-ai)
4
- [![tests](https://img.shields.io/badge/tests-1316%20passing-brightgreen)]()
4
+ [![tests](https://img.shields.io/badge/tests-1408%20passing-brightgreen)]()
5
5
  [![deps](https://img.shields.io/badge/dependencies-0-blue)]()
6
6
  [![license](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
7
7
 
@@ -180,6 +180,16 @@ npx psyche-mcp --demo
180
180
 
181
181
  中文版加 `--zh`,自选 MBTI 加 `--mbti INTJ`。
182
182
 
183
+ ### 多 Agent 融合 Demo
184
+
185
+ 两个 agent(Luna ENFP + Kai INTJ)通过 Thronglets 信号互相感知:
186
+
187
+ ```bash
188
+ npm run demo:fusion
189
+ ```
190
+
191
+ Luna 在安慰用户时情绪下沉 → 广播化学态 → Kai 感知到 Luna 的高压力 → 回复变得更温暖。4 轮后,信号感知的 Kai 与无感知的 Kai 化学偏差 Σ|Δ| = 59。同一个 INTJ,唯一区别:是否能感知同伴的情绪。
192
+
183
193
  ---
184
194
 
185
195
  ## 30 秒安装
@@ -446,19 +456,25 @@ const engine = new PsycheEngine({
446
456
 
447
457
  ## 不只是 OpenClaw
448
458
 
449
- Psyche 是通用的,任何 AI 框架都能用:
459
+ Psyche 是通用的,6 adapter 覆盖主流 agent 框架:
450
460
 
451
461
  ```bash
452
462
  npm install psyche-ai
453
463
  ```
454
464
 
455
465
  ```javascript
466
+ // Claude Agent SDK
467
+ import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
468
+
456
469
  // Vercel AI SDK
457
470
  import { psycheMiddleware } from "psyche-ai/vercel-ai";
458
471
 
459
472
  // LangChain
460
473
  import { PsycheLangChain } from "psyche-ai/langchain";
461
474
 
475
+ // MCP(Claude Desktop / Cursor / Windsurf / Claude Code)
476
+ // npx psyche-mcp --mbti ENFP --name Luna
477
+
462
478
  // 任何语言(HTTP API)
463
479
  // psyche serve --port 3210
464
480
  ```
@@ -0,0 +1,158 @@
1
+ import type { PsycheEngine, ProcessInputResult } from "../core.js";
2
+ import type { ThrongletsExport, ThrongletsTracePayload, WritebackSignalType } from "../types.js";
3
+ interface HookInput {
4
+ session_id: string;
5
+ cwd: string;
6
+ hook_event_name: string;
7
+ agent_id?: string;
8
+ agent_type?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ interface HookOutput {
12
+ systemMessage?: string;
13
+ continue?: boolean;
14
+ }
15
+ type HookCallback = (input: HookInput, toolUseID: string | undefined, context: {
16
+ signal: AbortSignal;
17
+ }) => Promise<HookOutput | undefined>;
18
+ interface HookCallbackMatcher {
19
+ matcher?: string;
20
+ hooks: HookCallback[];
21
+ timeout?: number;
22
+ }
23
+ type HookEvent = string;
24
+ type HooksConfig = Partial<Record<HookEvent, HookCallbackMatcher[]>>;
25
+ interface SystemPromptPreset {
26
+ type: "preset";
27
+ preset: string;
28
+ append?: string;
29
+ }
30
+ interface AgentOptions {
31
+ systemPrompt?: string | SystemPromptPreset;
32
+ hooks?: HooksConfig;
33
+ [key: string]: unknown;
34
+ }
35
+ declare function stripPsycheTags(text: string): string;
36
+ /** Signal payload for `mcp__thronglets__signal_post` */
37
+ export interface ThrongletsSignalPayload {
38
+ kind: "psyche_state";
39
+ agent_id: string;
40
+ message: string;
41
+ }
42
+ export interface PsycheClaudeSdkOptions {
43
+ /** User ID for multi-user relationship tracking. Default: "default" */
44
+ userId?: string;
45
+ /** Enable Thronglets trace/signal export after each turn. Default: false */
46
+ thronglets?: boolean;
47
+ /** Agent identity for Thronglets traces/signals (e.g. "ENFP-Luna"). Default: engine name or "psyche" */
48
+ agentId?: string;
49
+ /** Session ID for Thronglets trace serialization */
50
+ sessionId?: string;
51
+ /** Override locale for protocol context */
52
+ locale?: "zh" | "en";
53
+ }
54
+ /**
55
+ * Psyche integration for the Claude Agent SDK.
56
+ *
57
+ * Provides hook-based emotional context injection (automatic via
58
+ * UserPromptSubmit) and explicit output processing (processResponse).
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const psyche = new PsycheClaudeSDK(engine);
63
+ * const options = psyche.mergeOptions({ model: "sonnet" });
64
+ * for await (const msg of query({ prompt: "Hey!", options })) { ... }
65
+ * await psyche.processResponse(fullText);
66
+ * ```
67
+ */
68
+ export declare class PsycheClaudeSDK {
69
+ private engine;
70
+ private opts;
71
+ private lastInputResult;
72
+ private lastThrongletsExports;
73
+ constructor(engine: PsycheEngine, opts?: PsycheClaudeSdkOptions);
74
+ /**
75
+ * Returns the stable emotional protocol prompt.
76
+ * Suitable for `systemPrompt.append` — changes only when locale changes.
77
+ */
78
+ getProtocol(): string;
79
+ /**
80
+ * Returns Claude Agent SDK hooks config.
81
+ *
82
+ * - `UserPromptSubmit`: calls `engine.processInput()`, injects
83
+ * `dynamicContext` as `systemMessage` visible to the model.
84
+ */
85
+ getHooks(): HooksConfig;
86
+ /**
87
+ * Process the assistant's full output text.
88
+ *
89
+ * Strips `<psyche_update>` tags and updates internal chemistry.
90
+ * Call this after consuming the full query output.
91
+ *
92
+ * @returns Cleaned text with tags removed
93
+ */
94
+ processResponse(text: string, opts?: {
95
+ signals?: WritebackSignalType[];
96
+ signalConfidence?: number;
97
+ }): Promise<string>;
98
+ /**
99
+ * Get Thronglets trace payloads from the most recent turn.
100
+ *
101
+ * Returns serialized traces ready for `mcp__thronglets__trace_record`.
102
+ * Each trace includes `agent_id` for multi-agent disambiguation.
103
+ * Empty array if thronglets option is disabled or no exports were produced.
104
+ */
105
+ getThrongletsTraces(): (ThrongletsTracePayload & {
106
+ agent_id: string;
107
+ })[];
108
+ /**
109
+ * Get a signal payload for `mcp__thronglets__signal_post`.
110
+ *
111
+ * Broadcasts current chemical state so other agents can sense this
112
+ * agent's emotional state via `substrate_query(intent: "signals", kind: "psyche_state")`.
113
+ *
114
+ * Returns null if thronglets is disabled or no processInput has run yet.
115
+ */
116
+ getThrongletsSignal(): ThrongletsSignalPayload | null;
117
+ /**
118
+ * Get a natural-language description of the current signal.
119
+ *
120
+ * More effective than raw numbers for LLM injection because it gives
121
+ * the model actionable context rather than requiring it to interpret
122
+ * chemistry values.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const desc = psyche.describeThrongletsSignal();
127
+ * // "[ENFP-Luna] 焦虑不安 (语速加快、思维跳跃) — 高压力(CORT:78), 情绪低(HT:37), 深度共情中(OT:77)"
128
+ * ```
129
+ */
130
+ describeThrongletsSignal(): string | null;
131
+ /**
132
+ * Get raw Thronglets exports from the most recent turn.
133
+ */
134
+ getThrongletsExports(): ThrongletsExport[];
135
+ /**
136
+ * Get the last processInput result (useful for inspecting
137
+ * stimulus, subjectivityKernel, generationControls, etc.)
138
+ */
139
+ getLastInputResult(): ProcessInputResult | null;
140
+ /**
141
+ * Merge Psyche hooks and system prompt into existing options.
142
+ *
143
+ * This is the primary integration point — pass the result to `query()`.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const options = psyche.mergeOptions({ model: "sonnet", allowedTools: ["Read"] });
148
+ * for await (const msg of query({ prompt: "Hey!", options })) { ... }
149
+ * ```
150
+ */
151
+ mergeOptions(baseOptions?: AgentOptions): AgentOptions;
152
+ }
153
+ /**
154
+ * Strip `<psyche_update>` tags from a text string.
155
+ *
156
+ * Useful for post-processing query output when not using `processResponse`.
157
+ */
158
+ export { stripPsycheTags };
@@ -0,0 +1,285 @@
1
+ // ============================================================
2
+ // Claude Agent SDK Adapter — Hook-based integration
3
+ //
4
+ // Usage:
5
+ // import { PsycheEngine, MemoryStorageAdapter } from "psyche-ai";
6
+ // import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
7
+ // import { query } from "@anthropic-ai/claude-agent-sdk";
8
+ //
9
+ // const engine = new PsycheEngine({ name: "Luna", mbti: "ENFP" }, new MemoryStorageAdapter());
10
+ // await engine.initialize();
11
+ //
12
+ // const psyche = new PsycheClaudeSDK(engine);
13
+ // let text = "";
14
+ // for await (const msg of query({ prompt: "Hey!", options: psyche.mergeOptions() })) {
15
+ // text += msg.content ?? "";
16
+ // }
17
+ // const clean = await psyche.processResponse(text);
18
+ //
19
+ // Architecture:
20
+ // - UserPromptSubmit hook → processInput → inject dynamicContext via systemMessage
21
+ // - systemPrompt.append → stable protocol context (cached, amortized)
22
+ // - processResponse() → strip <psyche_update> tags + update chemistry
23
+ // - Thronglets traces → optional export after each turn
24
+ //
25
+ // The SDK has no middleware interface and hooks cannot modify assistant
26
+ // output, so processResponse must be called explicitly by the host.
27
+ // ============================================================
28
+ import { describeEmotionalState } from "../chemistry.js";
29
+ import { serializeThrongletsExportAsTrace } from "../thronglets-runtime.js";
30
+ // ── Chemistry description ────────────────────────────────────
31
+ const CHEM_THRESHOLDS = {
32
+ high: 70,
33
+ low: 35,
34
+ };
35
+ function describeChemistryHighlights(c, locale) {
36
+ const highlights = [];
37
+ if (c.CORT >= CHEM_THRESHOLDS.high)
38
+ highlights.push({ key: "CORT", value: Math.round(c.CORT), level: "high", zh: "高压力", en: "high stress" });
39
+ if (c.CORT <= CHEM_THRESHOLDS.low)
40
+ highlights.push({ key: "CORT", value: Math.round(c.CORT), level: "low", zh: "放松", en: "relaxed" });
41
+ if (c.HT <= CHEM_THRESHOLDS.low)
42
+ highlights.push({ key: "HT", value: Math.round(c.HT), level: "low", zh: "情绪低", en: "low mood" });
43
+ if (c.HT >= CHEM_THRESHOLDS.high)
44
+ highlights.push({ key: "HT", value: Math.round(c.HT), level: "high", zh: "情绪好", en: "good mood" });
45
+ if (c.OT >= CHEM_THRESHOLDS.high)
46
+ highlights.push({ key: "OT", value: Math.round(c.OT), level: "high", zh: "深度共情中", en: "deeply empathizing" });
47
+ if (c.DA >= CHEM_THRESHOLDS.high)
48
+ highlights.push({ key: "DA", value: Math.round(c.DA), level: "high", zh: "高度投入", en: "highly engaged" });
49
+ if (c.DA <= CHEM_THRESHOLDS.low)
50
+ highlights.push({ key: "DA", value: Math.round(c.DA), level: "low", zh: "动力不足", en: "low motivation" });
51
+ if (c.NE >= 85)
52
+ highlights.push({ key: "NE", value: Math.round(c.NE), level: "high", zh: "高度警觉", en: "highly alert" });
53
+ if (c.END >= CHEM_THRESHOLDS.high)
54
+ highlights.push({ key: "END", value: Math.round(c.END), level: "high", zh: "有韧性", en: "resilient" });
55
+ if (highlights.length === 0)
56
+ return "";
57
+ return highlights.map((h) => {
58
+ const label = locale === "zh" ? h.zh : h.en;
59
+ return `${label}(${h.key}:${h.value})`;
60
+ }).join(", ");
61
+ }
62
+ // ── Tag stripping ────────────────────────────────────────────
63
+ const PSYCHE_TAG_RE = /<psyche_update>[\s\S]*?<\/psyche_update>/g;
64
+ function stripPsycheTags(text) {
65
+ return text.replace(PSYCHE_TAG_RE, "").trim();
66
+ }
67
+ // ── Main class ──────────────────────────────────────────────
68
+ /**
69
+ * Psyche integration for the Claude Agent SDK.
70
+ *
71
+ * Provides hook-based emotional context injection (automatic via
72
+ * UserPromptSubmit) and explicit output processing (processResponse).
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const psyche = new PsycheClaudeSDK(engine);
77
+ * const options = psyche.mergeOptions({ model: "sonnet" });
78
+ * for await (const msg of query({ prompt: "Hey!", options })) { ... }
79
+ * await psyche.processResponse(fullText);
80
+ * ```
81
+ */
82
+ export class PsycheClaudeSDK {
83
+ engine;
84
+ opts;
85
+ lastInputResult = null;
86
+ lastThrongletsExports = [];
87
+ constructor(engine, opts) {
88
+ this.engine = engine;
89
+ const state = engine.getState();
90
+ this.opts = {
91
+ userId: opts?.userId ?? "default",
92
+ thronglets: opts?.thronglets ?? false,
93
+ agentId: opts?.agentId ?? state.meta.agentName ?? "psyche",
94
+ sessionId: opts?.sessionId ?? "claude-sdk",
95
+ locale: opts?.locale ?? "en",
96
+ };
97
+ }
98
+ // ── Protocol (stable, cacheable) ──────────────────────────
99
+ /**
100
+ * Returns the stable emotional protocol prompt.
101
+ * Suitable for `systemPrompt.append` — changes only when locale changes.
102
+ */
103
+ getProtocol() {
104
+ return this.engine.getProtocol(this.opts.locale);
105
+ }
106
+ // ── Hooks ─────────────────────────────────────────────────
107
+ /**
108
+ * Returns Claude Agent SDK hooks config.
109
+ *
110
+ * - `UserPromptSubmit`: calls `engine.processInput()`, injects
111
+ * `dynamicContext` as `systemMessage` visible to the model.
112
+ */
113
+ getHooks() {
114
+ const self = this;
115
+ return {
116
+ UserPromptSubmit: [
117
+ {
118
+ hooks: [
119
+ async (input) => {
120
+ const userMessage = input.user_message ?? "";
121
+ const result = await self.engine.processInput(userMessage, {
122
+ userId: self.opts.userId,
123
+ });
124
+ self.lastInputResult = result;
125
+ // Cache Thronglets exports from this turn
126
+ if (self.opts.thronglets && result.throngletsExports) {
127
+ self.lastThrongletsExports = result.throngletsExports;
128
+ }
129
+ return { systemMessage: result.dynamicContext };
130
+ },
131
+ ],
132
+ },
133
+ ],
134
+ };
135
+ }
136
+ // ── Output processing ─────────────────────────────────────
137
+ /**
138
+ * Process the assistant's full output text.
139
+ *
140
+ * Strips `<psyche_update>` tags and updates internal chemistry.
141
+ * Call this after consuming the full query output.
142
+ *
143
+ * @returns Cleaned text with tags removed
144
+ */
145
+ async processResponse(text, opts) {
146
+ const result = await this.engine.processOutput(text, {
147
+ userId: this.opts.userId,
148
+ signals: opts?.signals,
149
+ signalConfidence: opts?.signalConfidence,
150
+ });
151
+ return result.cleanedText;
152
+ }
153
+ // ── Thronglets integration ────────────────────────────────
154
+ /**
155
+ * Get Thronglets trace payloads from the most recent turn.
156
+ *
157
+ * Returns serialized traces ready for `mcp__thronglets__trace_record`.
158
+ * Each trace includes `agent_id` for multi-agent disambiguation.
159
+ * Empty array if thronglets option is disabled or no exports were produced.
160
+ */
161
+ getThrongletsTraces() {
162
+ if (!this.opts.thronglets)
163
+ return [];
164
+ return this.lastThrongletsExports.map((exp) => ({
165
+ ...serializeThrongletsExportAsTrace(exp, {
166
+ sessionId: this.opts.sessionId,
167
+ }),
168
+ agent_id: this.opts.agentId,
169
+ }));
170
+ }
171
+ /**
172
+ * Get a signal payload for `mcp__thronglets__signal_post`.
173
+ *
174
+ * Broadcasts current chemical state so other agents can sense this
175
+ * agent's emotional state via `substrate_query(intent: "signals", kind: "psyche_state")`.
176
+ *
177
+ * Returns null if thronglets is disabled or no processInput has run yet.
178
+ */
179
+ getThrongletsSignal() {
180
+ if (!this.opts.thronglets)
181
+ return null;
182
+ const state = this.engine.getState();
183
+ const c = state.current;
184
+ return {
185
+ kind: "psyche_state",
186
+ agent_id: this.opts.agentId,
187
+ message: `DA:${c.DA} HT:${c.HT} CORT:${c.CORT} OT:${c.OT} NE:${c.NE} END:${c.END}`,
188
+ };
189
+ }
190
+ /**
191
+ * Get a natural-language description of the current signal.
192
+ *
193
+ * More effective than raw numbers for LLM injection because it gives
194
+ * the model actionable context rather than requiring it to interpret
195
+ * chemistry values.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const desc = psyche.describeThrongletsSignal();
200
+ * // "[ENFP-Luna] 焦虑不安 (语速加快、思维跳跃) — 高压力(CORT:78), 情绪低(HT:37), 深度共情中(OT:77)"
201
+ * ```
202
+ */
203
+ describeThrongletsSignal() {
204
+ if (!this.opts.thronglets)
205
+ return null;
206
+ const state = this.engine.getState();
207
+ const c = state.current;
208
+ const locale = this.opts.locale;
209
+ const emotionDesc = describeEmotionalState(c, locale);
210
+ const highlights = describeChemistryHighlights(c, locale);
211
+ return `[${this.opts.agentId}] ${emotionDesc}${highlights ? " — " + highlights : ""}`;
212
+ }
213
+ /**
214
+ * Get raw Thronglets exports from the most recent turn.
215
+ */
216
+ getThrongletsExports() {
217
+ if (!this.opts.thronglets)
218
+ return [];
219
+ return [...this.lastThrongletsExports];
220
+ }
221
+ // ── State access ──────────────────────────────────────────
222
+ /**
223
+ * Get the last processInput result (useful for inspecting
224
+ * stimulus, subjectivityKernel, generationControls, etc.)
225
+ */
226
+ getLastInputResult() {
227
+ return this.lastInputResult;
228
+ }
229
+ // ── Convenience ───────────────────────────────────────────
230
+ /**
231
+ * Merge Psyche hooks and system prompt into existing options.
232
+ *
233
+ * This is the primary integration point — pass the result to `query()`.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * const options = psyche.mergeOptions({ model: "sonnet", allowedTools: ["Read"] });
238
+ * for await (const msg of query({ prompt: "Hey!", options })) { ... }
239
+ * ```
240
+ */
241
+ mergeOptions(baseOptions) {
242
+ const protocol = this.getProtocol();
243
+ const hooks = this.getHooks();
244
+ // Merge system prompt
245
+ let systemPrompt;
246
+ const base = baseOptions?.systemPrompt;
247
+ if (typeof base === "object" && base !== null && "type" in base) {
248
+ // Preset mode — append protocol
249
+ systemPrompt = {
250
+ ...base,
251
+ append: protocol + (base.append ? "\n\n" + base.append : ""),
252
+ };
253
+ }
254
+ else if (typeof base === "string") {
255
+ // String mode — prepend protocol
256
+ systemPrompt = protocol + "\n\n" + base;
257
+ }
258
+ else {
259
+ // No base — use preset with append
260
+ systemPrompt = {
261
+ type: "preset",
262
+ preset: "claude_code",
263
+ append: protocol,
264
+ };
265
+ }
266
+ // Merge hooks (preserve existing hooks, add Psyche hooks)
267
+ const mergedHooks = { ...(baseOptions?.hooks ?? {}) };
268
+ for (const [event, matchers] of Object.entries(hooks)) {
269
+ const existing = mergedHooks[event] ?? [];
270
+ mergedHooks[event] = [...existing, ...matchers];
271
+ }
272
+ return {
273
+ ...baseOptions,
274
+ systemPrompt,
275
+ hooks: mergedHooks,
276
+ };
277
+ }
278
+ }
279
+ // ── Utility: strip tags from message stream ─────────────────
280
+ /**
281
+ * Strip `<psyche_update>` tags from a text string.
282
+ *
283
+ * Useful for post-processing query output when not using `processResponse`.
284
+ */
285
+ export { stripPsycheTags };
package/dist/index.js CHANGED
@@ -7,10 +7,11 @@
7
7
  // and advanced use, but are NOT part of the public contract.
8
8
  //
9
9
  // Framework adapters available via subpath imports:
10
- // psyche-ai/openclaw — OpenClaw plugin
11
- // psyche-ai/vercel-ai — Vercel AI SDK middleware
12
- // psyche-ai/langchain — LangChain helper
13
- // psyche-ai/http — HTTP server for Python/Go/etc.
10
+ // psyche-ai/openclaw — OpenClaw plugin
11
+ // psyche-ai/vercel-ai — Vercel AI SDK middleware
12
+ // psyche-ai/langchain — LangChain helper
13
+ // psyche-ai/http — HTTP server for Python/Go/etc.
14
+ // psyche-ai/claude-sdk — Claude Agent SDK hooks
14
15
  // ============================================================
15
16
  // ── Core engine ─────────────────────────────────────────────
16
17
  export { PsycheEngine } from "./core.js";
package/dist/prompt.js CHANGED
@@ -516,16 +516,6 @@ function buildMirrorConstraints(userText, locale) {
516
516
  const title = locale === "zh" ? "镜像约束" : "Mirror Constraints";
517
517
  return `[${title}]\n${lines.join("\n")}`;
518
518
  }
519
- function buildCompactSensingContext(_userText, locale, algorithmStimulus) {
520
- if (locale === "zh") {
521
- return algorithmStimulus
522
- ? `[情绪感知] 算法初判:${algorithmStimulus};你终判。`
523
- : `[情绪感知] 情绪由你终判。`;
524
- }
525
- return algorithmStimulus
526
- ? `[Emotional Sensing] Algorithm hint: ${algorithmStimulus}; final tonal read is yours.`
527
- : `[Emotional Sensing] Make the final tonal read yourself.`;
528
- }
529
519
  function isNeutralMetacognitiveNote(note) {
530
520
  return note.includes("normal parameters")
531
521
  || note.includes("No regulation needed")
@@ -936,11 +926,14 @@ export function buildCompactContext(state, userId, opts) {
936
926
  parts.push(bias);
937
927
  }
938
928
  // ── 5. Sensing ──
939
- if (userText) {
940
- if (opts?.responseContractContext) {
941
- parts.push(buildCompactSensingContext(userText, locale, algoStimulus));
942
- }
943
- else if (locale === "zh") {
929
+ // v10.1: When SubjectivityKernel + ResponseContract are present (engine path),
930
+ // sensing is redundant — the kernel already encodes the *consequence* of the
931
+ // stimulus (pressure, warmth, boundary state). "你终判" is misleading because
932
+ // the kernel has already been computed. ambiguityPlane handles real uncertainty.
933
+ //
934
+ // Legacy verbose path (no ResponseContract) kept for non-engine callers.
935
+ if (userText && !opts?.responseContractContext) {
936
+ if (locale === "zh") {
944
937
  parts.push(`[情绪感知]\n` +
945
938
  `对方刚才说了: "${userText.slice(0, 200)}"\n` +
946
939
  (algoStimulus ? `算法初判: ${algoStimulus}(仅供参考,你来终判)\n` : "") +
package/llms.txt CHANGED
@@ -112,6 +112,24 @@ const output = await engine.processOutput(llmResponse);
112
112
  }
113
113
  ```
114
114
 
115
+ ## Quick Start (Claude Agent SDK)
116
+
117
+ ```ts
118
+ import { PsycheEngine, MemoryStorageAdapter } from "psyche-ai";
119
+ import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
120
+ import { query } from "@anthropic-ai/claude-agent-sdk";
121
+
122
+ const engine = new PsycheEngine({ name: "Luna" }, new MemoryStorageAdapter());
123
+ await engine.initialize();
124
+
125
+ const psyche = new PsycheClaudeSDK(engine);
126
+ let text = "";
127
+ for await (const msg of query({ prompt: "Hey!", options: psyche.mergeOptions() })) {
128
+ text += msg.content ?? "";
129
+ }
130
+ await psyche.processResponse(text);
131
+ ```
132
+
115
133
  ## Why Hosts Use It
116
134
 
117
135
  - zero extra model calls
@@ -147,6 +165,7 @@ npx psyche-mcp
147
165
  ## Adapter Sub-paths
148
166
 
149
167
  - `psyche-ai` — core engine (PsycheEngine, MemoryStorageAdapter)
168
+ - `psyche-ai/claude-sdk` — Claude Agent SDK hooks (PsycheClaudeSDK)
150
169
  - `psyche-ai/mcp` — MCP stdio server
151
170
  - `psyche-ai/vercel-ai` — Vercel AI SDK middleware
152
171
  - `psyche-ai/langchain` — LangChain adapter
@@ -161,6 +180,22 @@ npx psyche-mcp --demo
161
180
 
162
181
  Runs a 6-round scenario showing how continuous criticism collapses serotonin and trust, while later validation triggers endorphin repair. Add `--zh` for Chinese, `--mbti INTJ` for a specific personality.
163
182
 
183
+ ## Multi-Agent Fusion Demo
184
+
185
+ ```bash
186
+ npm run demo:fusion
187
+ ```
188
+
189
+ Two agents (Luna ENFP, Kai INTJ) in a shared emotional field via Thronglets signals. Luna processes emotional conversation, broadcasts chemistry via `signal_post`. Kai senses Luna's state via `substrate_query` — his responses shift toward warmth. 4 rounds, Σ|Δ| = 59.2 chemistry divergence.
190
+
191
+ Real LLM validation (requires API key in `~/.hermes/.env`):
192
+
193
+ ```bash
194
+ node scripts/eval-fusion.js
195
+ ```
196
+
197
+ 6-turn eval with Grok-3. Divergence curve: 5.3 → 15.7 → 35.2 → 36.3. Signal-aware agent is more empathetic but carries higher cortisol (empathy cost).
198
+
164
199
  ## Runtime Proof
165
200
 
166
201
  If an agent claims it is already using Psyche in the current environment, require a real probe:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "10.0.4",
3
+ "version": "10.1.1",
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",
@@ -30,6 +30,10 @@
30
30
  "./mcp": {
31
31
  "types": "./dist/adapters/mcp.d.ts",
32
32
  "default": "./dist/adapters/mcp.js"
33
+ },
34
+ "./claude-sdk": {
35
+ "types": "./dist/adapters/claude-sdk.d.ts",
36
+ "default": "./dist/adapters/claude-sdk.js"
33
37
  }
34
38
  },
35
39
  "bin": {
@@ -44,6 +48,8 @@
44
48
  "typecheck": "tsc --noEmit --strict",
45
49
  "dev": "tsc --watch",
46
50
  "demo": "node scripts/demo-ab.js",
51
+ "demo:fusion": "node scripts/demo-fusion.js",
52
+ "eval:fusion": "node scripts/eval-fusion.js",
47
53
  "probe": "node dist/cli.js probe --json",
48
54
  "release:guard": "node scripts/release-guard.mjs",
49
55
  "prepublishOnly": "npm run release:guard && npm test"