psyche-ai 11.8.0 → 11.9.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/dist/adapters/claude-sdk.d.ts +3 -0
- package/dist/adapters/claude-sdk.js +6 -0
- package/dist/adapters/mcp.d.ts +5 -0
- package/dist/adapters/mcp.js +40 -18
- package/dist/adapters/openclaw.js +10 -0
- package/dist/adapters/proxy.js +8 -8
- package/dist/adapters/response-contract-surface.d.ts +5 -0
- package/dist/adapters/response-contract-surface.js +16 -0
- package/dist/core.d.ts +14 -3
- package/dist/core.js +109 -32
- package/dist/learning.d.ts +2 -2
- package/dist/learning.js +12 -6
- package/dist/thronglets-export.d.ts +1 -0
- package/dist/thronglets-export.js +46 -2
- package/dist/thronglets-runtime.js +4 -1
- package/dist/types.d.ts +10 -2
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -38,7 +38,10 @@ declare function stripPsycheTags(text: string): string;
|
|
|
38
38
|
export interface ThrongletsSignalPayload {
|
|
39
39
|
kind: "psyche_state";
|
|
40
40
|
agent_id: string;
|
|
41
|
+
context: string;
|
|
41
42
|
message: string;
|
|
43
|
+
model: "psyche";
|
|
44
|
+
session_id: string;
|
|
42
45
|
}
|
|
43
46
|
export interface PsycheClaudeSdkOptions {
|
|
44
47
|
/** User ID for relationship tracking. Default: internal shared bucket (`_default`). */
|
|
@@ -182,6 +182,7 @@ export class PsycheClaudeSDK {
|
|
|
182
182
|
const ambientPriors = await self.resolveAmbientPriors(userMessage, currentGoal, activePolicy, currentTurnCorrection);
|
|
183
183
|
const result = await safeProcessInput(self.engine, userMessage, {
|
|
184
184
|
userId: self.opts.userId,
|
|
185
|
+
sessionId: runtimeContext.sessionId ?? self.lastRuntimeContext.sessionId,
|
|
185
186
|
ambientPriors,
|
|
186
187
|
currentGoal,
|
|
187
188
|
activePolicy,
|
|
@@ -212,6 +213,7 @@ export class PsycheClaudeSDK {
|
|
|
212
213
|
async processResponse(text, opts) {
|
|
213
214
|
const result = await safeProcessOutput(this.engine, text, {
|
|
214
215
|
userId: this.opts.userId,
|
|
216
|
+
sessionId: this.resolveSessionId(),
|
|
215
217
|
signals: opts?.signals,
|
|
216
218
|
signalConfidence: opts?.signalConfidence,
|
|
217
219
|
}, "claude-sdk.processOutput");
|
|
@@ -250,10 +252,14 @@ export class PsycheClaudeSDK {
|
|
|
250
252
|
return null;
|
|
251
253
|
const state = this.engine.getState();
|
|
252
254
|
const s = state.current;
|
|
255
|
+
const sessionId = this.resolveSessionId();
|
|
253
256
|
return {
|
|
254
257
|
kind: "psyche_state",
|
|
255
258
|
agent_id: this.resolveAgentId(),
|
|
259
|
+
context: `psyche:session:${sessionId}:user:${this.opts.userId}`,
|
|
256
260
|
message: `order:${s.order} flow:${s.flow} boundary:${s.boundary} resonance:${s.resonance}`,
|
|
261
|
+
model: "psyche",
|
|
262
|
+
session_id: sessionId,
|
|
257
263
|
};
|
|
258
264
|
}
|
|
259
265
|
/**
|
package/dist/adapters/mcp.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { PsycheEngine } from "../core.js";
|
|
3
|
+
import type { ProcessInputResult } from "../core.js";
|
|
3
4
|
import { fetchAmbientPriorsFromThronglets, type ThrongletsAmbientRuntimeOptions } from "../ambient-runtime.js";
|
|
4
5
|
import { type ActivePolicyRule, type AmbientPriorView, type CurrentGoal } from "../types.js";
|
|
5
6
|
export interface McpAmbientRuntimeOptions {
|
|
@@ -7,6 +8,10 @@ export interface McpAmbientRuntimeOptions {
|
|
|
7
8
|
thronglets?: ThrongletsAmbientRuntimeOptions;
|
|
8
9
|
fetcher?: typeof fetchAmbientPriorsFromThronglets;
|
|
9
10
|
}
|
|
11
|
+
export declare function resolveMcpTurnCacheSession(sessionId?: string): string;
|
|
12
|
+
export declare function cacheMcpTurnResult(result: ProcessInputResult, sessionId?: string): void;
|
|
13
|
+
export declare function getCachedMcpTurnResult(sessionId?: string): ProcessInputResult | null;
|
|
14
|
+
export declare function resetMcpTurnCacheForTests(): void;
|
|
10
15
|
declare function getEngine(): Promise<PsycheEngine>;
|
|
11
16
|
export declare function resolveRuntimeAmbientPriors(text: string, explicit?: AmbientPriorView[], currentGoal?: CurrentGoal, activePolicy?: ActivePolicyRule[], currentTurnCorrectionOrOpts?: string | McpAmbientRuntimeOptions, opts?: McpAmbientRuntimeOptions): Promise<AmbientPriorView[] | undefined>;
|
|
12
17
|
declare const server: McpServer;
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -96,9 +96,29 @@ function parseCLIArgs() {
|
|
|
96
96
|
return overrides;
|
|
97
97
|
}
|
|
98
98
|
// ── Turn cache for on-demand resource access ──────────────
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// Hosts may overlap sessions on one MCP transport, so cache per session key.
|
|
100
|
+
const DEFAULT_TURN_CACHE_SESSION = "__default__";
|
|
101
|
+
const turnResultsBySession = new Map();
|
|
102
|
+
let lastTurnCacheSession = DEFAULT_TURN_CACHE_SESSION;
|
|
103
|
+
export function resolveMcpTurnCacheSession(sessionId) {
|
|
104
|
+
const normalized = sessionId?.trim();
|
|
105
|
+
return normalized ? normalized : DEFAULT_TURN_CACHE_SESSION;
|
|
106
|
+
}
|
|
107
|
+
export function cacheMcpTurnResult(result, sessionId) {
|
|
108
|
+
const key = resolveMcpTurnCacheSession(sessionId);
|
|
109
|
+
turnResultsBySession.set(key, result);
|
|
110
|
+
lastTurnCacheSession = key;
|
|
111
|
+
}
|
|
112
|
+
export function getCachedMcpTurnResult(sessionId) {
|
|
113
|
+
if (sessionId) {
|
|
114
|
+
return turnResultsBySession.get(resolveMcpTurnCacheSession(sessionId)) ?? null;
|
|
115
|
+
}
|
|
116
|
+
return turnResultsBySession.get(lastTurnCacheSession) ?? null;
|
|
117
|
+
}
|
|
118
|
+
export function resetMcpTurnCacheForTests() {
|
|
119
|
+
turnResultsBySession.clear();
|
|
120
|
+
lastTurnCacheSession = DEFAULT_TURN_CACHE_SESSION;
|
|
121
|
+
}
|
|
102
122
|
// ── Engine singleton ───────────────────────────────────────
|
|
103
123
|
let engine = null;
|
|
104
124
|
async function getEngine() {
|
|
@@ -208,14 +228,17 @@ server.resource("state", "psyche://state", {
|
|
|
208
228
|
}],
|
|
209
229
|
};
|
|
210
230
|
});
|
|
211
|
-
// Helper: register a turn-scoped resource backed by
|
|
231
|
+
// Helper: register a turn-scoped resource backed by the per-session turn cache.
|
|
212
232
|
// Returns empty JSON when no turn has been processed yet (fail-open).
|
|
213
233
|
function turnResource(name, uri, description, pick) {
|
|
214
234
|
server.resource(name, uri, { description, mimeType: "application/json" }, async (u) => ({
|
|
215
235
|
contents: [{
|
|
216
236
|
uri: u.href,
|
|
217
237
|
mimeType: "application/json",
|
|
218
|
-
text:
|
|
238
|
+
text: (() => {
|
|
239
|
+
const cached = getCachedMcpTurnResult(u.searchParams.get("session") ?? undefined);
|
|
240
|
+
return cached ? JSON.stringify(pick(cached)) : "{}";
|
|
241
|
+
})(),
|
|
219
242
|
}],
|
|
220
243
|
}));
|
|
221
244
|
}
|
|
@@ -245,6 +268,7 @@ server.tool("process_input", "Process user input through the emotional engine. R
|
|
|
245
268
|
"Call this BEFORE generating a response to the user.", {
|
|
246
269
|
text: z.string().describe("The user's message text"),
|
|
247
270
|
userId: z.string().optional().describe("Optional user ID for multi-user relationship tracking"),
|
|
271
|
+
sessionId: z.string().optional().describe("Optional session ID for overlapping host sessions and turn-scoped resource lookup"),
|
|
248
272
|
ambientPriors: z.array(z.object({
|
|
249
273
|
summary: z.string(),
|
|
250
274
|
confidence: z.number().min(0).max(1),
|
|
@@ -262,19 +286,20 @@ server.tool("process_input", "Process user input through the emotional engine. R
|
|
|
262
286
|
summary: z.string(),
|
|
263
287
|
})).optional().describe("Optional explicit current-turn method policy view. Runtime-only; not persisted as self-state."),
|
|
264
288
|
currentTurnCorrection: z.string().optional().describe("Optional explicit current-turn correction. Compiles into a task-scoped hard policy for this turn only."),
|
|
265
|
-
}, async ({ text, userId, ambientPriors, currentGoal, activePolicy, currentTurnCorrection }) => {
|
|
289
|
+
}, async ({ text, userId, sessionId, ambientPriors, currentGoal, activePolicy, currentTurnCorrection }) => {
|
|
266
290
|
const eng = await getEngine();
|
|
267
291
|
const resolvedActivePolicy = resolveRuntimeActivePolicy(activePolicy, currentTurnCorrection);
|
|
268
292
|
const resolvedAmbientPriors = await resolveRuntimeAmbientPriors(text, ambientPriors, currentGoal, resolvedActivePolicy, currentTurnCorrection);
|
|
269
293
|
const result = await safeProcessInput(eng, text, {
|
|
270
294
|
userId,
|
|
295
|
+
sessionId,
|
|
271
296
|
ambientPriors: resolvedAmbientPriors,
|
|
272
297
|
currentGoal,
|
|
273
298
|
activePolicy: resolvedActivePolicy,
|
|
274
299
|
currentTurnCorrection,
|
|
275
300
|
}, "mcp.processInput");
|
|
276
301
|
// Cache full result for turn-scoped resources
|
|
277
|
-
|
|
302
|
+
cacheMcpTurnResult(result, sessionId);
|
|
278
303
|
// Build slim response: only what the LLM host actually needs.
|
|
279
304
|
// Full structured state available via psyche://turn/envelope resource.
|
|
280
305
|
const slim = {
|
|
@@ -302,18 +327,20 @@ server.tool("process_output", "Process the LLM's response through the emotional
|
|
|
302
327
|
"emotional contagion. Call this AFTER generating a response.", {
|
|
303
328
|
text: z.string().describe("The LLM's response text"),
|
|
304
329
|
userId: z.string().optional().describe("Optional user ID"),
|
|
330
|
+
sessionId: z.string().optional().describe("Optional session ID to match the corresponding process_input turn"),
|
|
305
331
|
signals: z.array(z.string()).optional().describe("Optional sparse writeback signals from the host"),
|
|
306
332
|
signalConfidence: z.number().min(0).max(1).optional().describe("Optional confidence for the supplied signals"),
|
|
307
|
-
}, async ({ text, userId, signals, signalConfidence }) => {
|
|
333
|
+
}, async ({ text, userId, sessionId, signals, signalConfidence }) => {
|
|
308
334
|
const eng = await getEngine();
|
|
335
|
+
const turnResult = getCachedMcpTurnResult(sessionId);
|
|
309
336
|
// LLM-specific alignment inference (adapter layer — the ONLY text-specific code).
|
|
310
337
|
// Compare output length against last contract's maxChars to detect divergence.
|
|
311
338
|
let outcome;
|
|
312
|
-
if (
|
|
313
|
-
const maxLen = (
|
|
339
|
+
if (turnResult?.responseContract) {
|
|
340
|
+
const maxLen = (turnResult.responseContract.maxChars ?? 500) * 2;
|
|
314
341
|
outcome = { alignment: text.length > maxLen ? "diverged" : "aligned" };
|
|
315
342
|
}
|
|
316
|
-
const result = await safeProcessOutput(eng, text, { userId, signals: signals, signalConfidence, outcome }, "mcp.processOutput");
|
|
343
|
+
const result = await safeProcessOutput(eng, text, { userId, sessionId, signals: signals, signalConfidence, outcome }, "mcp.processOutput");
|
|
317
344
|
return {
|
|
318
345
|
content: [{
|
|
319
346
|
type: "text",
|
|
@@ -342,7 +369,7 @@ server.tool("get_state", "Get the current emotional state — self-state dimensi
|
|
|
342
369
|
overlay,
|
|
343
370
|
drives: state.drives,
|
|
344
371
|
mbti: state.mbti,
|
|
345
|
-
mode:
|
|
372
|
+
mode: eng.getMode(),
|
|
346
373
|
totalInteractions: state.meta?.totalInteractions,
|
|
347
374
|
traitDrift: state.traitDrift,
|
|
348
375
|
energyBudgets: state.energyBudgets,
|
|
@@ -356,12 +383,7 @@ server.tool("set_mode", "Switch operating mode. 'natural' = balanced emotional e
|
|
|
356
383
|
mode: z.enum(["natural", "work", "companion"]).describe("Operating mode"),
|
|
357
384
|
}, async ({ mode }) => {
|
|
358
385
|
const eng = await getEngine();
|
|
359
|
-
|
|
360
|
-
// For now, update via state manipulation
|
|
361
|
-
const state = eng.getState();
|
|
362
|
-
if (state.meta) {
|
|
363
|
-
state.meta.mode = mode;
|
|
364
|
-
}
|
|
386
|
+
await eng.setMode(mode);
|
|
365
387
|
return {
|
|
366
388
|
content: [{
|
|
367
389
|
type: "text",
|
|
@@ -12,6 +12,8 @@ import { PsycheEngine } from "../core.js";
|
|
|
12
12
|
import { FileStorageAdapter, MemoryStorageAdapter } from "../storage.js";
|
|
13
13
|
import { detectMBTI, extractAgentName, loadState } from "../psyche-file.js";
|
|
14
14
|
import { resolveAmbientPriorsForTurn } from "../ambient-runtime.js";
|
|
15
|
+
import { buildResponseContractContext } from "../response-contract.js";
|
|
16
|
+
import { resolveCanonicalResponseContract, renderOpenClawBehavioralSurface, } from "./response-contract-surface.js";
|
|
15
17
|
function isPsycheMode(value) {
|
|
16
18
|
return value === "natural" || value === "work" || value === "companion";
|
|
17
19
|
}
|
|
@@ -178,6 +180,8 @@ export function register(api) {
|
|
|
178
180
|
userId: ctx.userId,
|
|
179
181
|
ambientPriors: ambientPriors ?? [],
|
|
180
182
|
});
|
|
183
|
+
const responseContract = resolveCanonicalResponseContract(result);
|
|
184
|
+
const locale = (engine.getState().meta.locale ?? "zh");
|
|
181
185
|
const controls = result.replyEnvelope?.generationControls ?? result.generationControls;
|
|
182
186
|
const dominantAppraisal = getDominantAppraisalLabel(result);
|
|
183
187
|
const state = engine.getState();
|
|
@@ -192,6 +196,12 @@ export function register(api) {
|
|
|
192
196
|
(controls?.maxTokens ? ` | out<=${controls.maxTokens}t` : "") +
|
|
193
197
|
(controls?.requireConfirmation ? " | confirm" : ""));
|
|
194
198
|
const systemParts = [result.systemContext, result.dynamicContext].filter(Boolean);
|
|
199
|
+
if (responseContract) {
|
|
200
|
+
const responseSurface = buildResponseContractContext(responseContract, locale);
|
|
201
|
+
if (!result.dynamicContext.includes(responseSurface)) {
|
|
202
|
+
systemParts.push(renderOpenClawBehavioralSurface(responseContract, locale));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
195
205
|
return {
|
|
196
206
|
appendSystemContext: systemParts.join("\n\n"),
|
|
197
207
|
};
|
package/dist/adapters/proxy.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
import { createServer } from "node:http";
|
|
22
22
|
import { PsycheEngine } from "../core.js";
|
|
23
23
|
import { MemoryStorageAdapter, FileStorageAdapter } from "../storage.js";
|
|
24
|
-
import { isNearBaseline
|
|
24
|
+
import { isNearBaseline } from "../prompt.js";
|
|
25
|
+
import { resolveCanonicalResponseContract, renderProxyBehavioralSurface, } from "./response-contract-surface.js";
|
|
25
26
|
// ── Helpers ─────────────────────────────────────────────────
|
|
26
27
|
function readBody(req) {
|
|
27
28
|
return new Promise((resolve, reject) => {
|
|
@@ -112,18 +113,17 @@ export function createPsycheProxy(engine, opts) {
|
|
|
112
113
|
const parsed = JSON.parse(rawBody.toString("utf-8"));
|
|
113
114
|
const userMsg = lastUserMessage(parsed.messages);
|
|
114
115
|
const userId = parsed.user ?? undefined;
|
|
116
|
+
let messages = parsed.messages;
|
|
115
117
|
// ── 1. Observe input ────────────────────────────
|
|
118
|
+
let result = null;
|
|
116
119
|
if (userMsg) {
|
|
117
|
-
await engine.processInput(userMsg, { userId });
|
|
120
|
+
result = await engine.processInput(userMsg, { userId });
|
|
118
121
|
}
|
|
119
122
|
// ── 2. Inject behavioral bias (silent when near baseline) ──
|
|
120
123
|
const state = engine.getState();
|
|
121
|
-
|
|
122
|
-
if (!isNearBaseline(state)) {
|
|
123
|
-
|
|
124
|
-
if (bias) {
|
|
125
|
-
messages = injectBias(parsed.messages, bias);
|
|
126
|
-
}
|
|
124
|
+
const responseContract = result ? resolveCanonicalResponseContract(result) : null;
|
|
125
|
+
if (responseContract && !isNearBaseline(state)) {
|
|
126
|
+
messages = injectBias(parsed.messages, renderProxyBehavioralSurface(responseContract, locale));
|
|
127
127
|
}
|
|
128
128
|
const modifiedBody = JSON.stringify({ ...parsed, messages });
|
|
129
129
|
const headers = forwardHeaders(req);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ProcessInputResult } from "../core.js";
|
|
2
|
+
import type { Locale, ResponseContract } from "../types.js";
|
|
3
|
+
export declare function resolveCanonicalResponseContract(result: Pick<ProcessInputResult, "replyEnvelope" | "responseContract">): ResponseContract | null;
|
|
4
|
+
export declare function renderProxyBehavioralSurface(contract: ResponseContract, locale: Locale): string;
|
|
5
|
+
export declare function renderOpenClawBehavioralSurface(contract: ResponseContract, locale: Locale): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { buildResponseContractContext } from "../response-contract.js";
|
|
2
|
+
export function resolveCanonicalResponseContract(result) {
|
|
3
|
+
return result.replyEnvelope?.responseContract ?? result.responseContract ?? null;
|
|
4
|
+
}
|
|
5
|
+
export function renderProxyBehavioralSurface(contract, locale) {
|
|
6
|
+
const context = buildResponseContractContext(contract, locale);
|
|
7
|
+
return locale === "zh"
|
|
8
|
+
? `[代理行为契约]\n${context}`
|
|
9
|
+
: `[Proxy Behavioral Contract]\n${context}`;
|
|
10
|
+
}
|
|
11
|
+
export function renderOpenClawBehavioralSurface(contract, locale) {
|
|
12
|
+
const context = buildResponseContractContext(contract, locale);
|
|
13
|
+
return locale === "zh"
|
|
14
|
+
? `[OpenClaw 行为契约]\n${context}`
|
|
15
|
+
: `[OpenClaw Behavioral Contract]\n${context}`;
|
|
16
|
+
}
|
package/dist/core.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ export interface ProcessInputResult {
|
|
|
95
95
|
}
|
|
96
96
|
export interface ProcessInputOptions {
|
|
97
97
|
userId?: string;
|
|
98
|
+
sessionId?: string;
|
|
98
99
|
ambientPriors?: AmbientPriorView[];
|
|
99
100
|
currentGoal?: CurrentGoal;
|
|
100
101
|
activePolicy?: ActivePolicyRule[];
|
|
@@ -121,6 +122,7 @@ export interface LoopOutcome {
|
|
|
121
122
|
}
|
|
122
123
|
export interface ProcessOutputOptions {
|
|
123
124
|
userId?: string;
|
|
125
|
+
sessionId?: string;
|
|
124
126
|
signals?: readonly string[];
|
|
125
127
|
signalConfidence?: number;
|
|
126
128
|
/** Substrate-reported outcome — closes the φ loop */
|
|
@@ -145,11 +147,14 @@ export declare class PsycheEngine {
|
|
|
145
147
|
private _lastAlgorithmApplied;
|
|
146
148
|
private readonly traits;
|
|
147
149
|
private readonly cfg;
|
|
150
|
+
private readonly modeOverride?;
|
|
148
151
|
private readonly classifier;
|
|
149
152
|
private readonly llmClassifier?;
|
|
150
153
|
private readonly protocolCache;
|
|
151
|
-
/** Pending
|
|
152
|
-
private
|
|
154
|
+
/** Pending predictions keyed by relationship/session scope for auto-learning */
|
|
155
|
+
private readonly pendingPredictions;
|
|
156
|
+
/** Most recent response contract keyed by relationship/session scope for implicit loop closure */
|
|
157
|
+
private readonly pendingResponseContracts;
|
|
153
158
|
/** Built-in diagnostics collector — auto-records every processInput/processOutput */
|
|
154
159
|
private readonly diagnosticCollector;
|
|
155
160
|
/** Last generated diagnostic report (from endSession or explicit call) */
|
|
@@ -190,8 +195,9 @@ export declare class PsycheEngine {
|
|
|
190
195
|
* @param nextUserStimulus - The stimulus detected in the user's next message,
|
|
191
196
|
* or null if the session ended.
|
|
192
197
|
*/
|
|
193
|
-
processOutcome(nextUserStimulus: StimulusType | null,
|
|
198
|
+
processOutcome(nextUserStimulus: StimulusType | null, opts?: {
|
|
194
199
|
userId?: string;
|
|
200
|
+
sessionId?: string;
|
|
195
201
|
}): Promise<ProcessOutcomeResult | null>;
|
|
196
202
|
/**
|
|
197
203
|
* Get the current psyche state (read-only snapshot).
|
|
@@ -215,6 +221,8 @@ export declare class PsycheEngine {
|
|
|
215
221
|
* Get the last diagnostic report (from most recent endSession call).
|
|
216
222
|
*/
|
|
217
223
|
getLastDiagnosticReport(): DiagnosticReport | null;
|
|
224
|
+
getMode(): PsycheMode;
|
|
225
|
+
setMode(mode: PsycheMode): Promise<void>;
|
|
218
226
|
/**
|
|
219
227
|
* Get current session diagnostic metrics (live, before endSession).
|
|
220
228
|
*/
|
|
@@ -229,6 +237,9 @@ export declare class PsycheEngine {
|
|
|
229
237
|
*/
|
|
230
238
|
getPreviousIssues(): Promise<string[]>;
|
|
231
239
|
private ensureInitialized;
|
|
240
|
+
private resolveMode;
|
|
241
|
+
private resolvePendingPredictionKey;
|
|
242
|
+
private inferLoopOutcome;
|
|
232
243
|
private createDefaultState;
|
|
233
244
|
/**
|
|
234
245
|
* Reset state to baseline. Optionally preserves relationships.
|
package/dist/core.js
CHANGED
|
@@ -30,7 +30,7 @@ import { computeCircadianModulation, computeHomeostaticPressure, computeEnergyDe
|
|
|
30
30
|
import { runReflectiveTurnPhases } from "./input-turn.js";
|
|
31
31
|
import { applyRelationalTurn, applySessionBridge, applyWritebackSignals, createWritebackCalibrations, evaluateWritebackCalibrations } from "./relation-dynamics.js";
|
|
32
32
|
import { buildExternalContinuityEnvelope } from "./external-continuity.js";
|
|
33
|
-
import { deriveThrongletsExports } from "./thronglets-export.js";
|
|
33
|
+
import { deriveThrongletsExports, markThrongletsExportsEmitted } from "./thronglets-export.js";
|
|
34
34
|
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";
|
|
@@ -132,11 +132,14 @@ export class PsycheEngine {
|
|
|
132
132
|
_lastAlgorithmApplied = false;
|
|
133
133
|
traits;
|
|
134
134
|
cfg;
|
|
135
|
+
modeOverride;
|
|
135
136
|
classifier;
|
|
136
137
|
llmClassifier;
|
|
137
138
|
protocolCache = new Map();
|
|
138
|
-
/** Pending
|
|
139
|
-
|
|
139
|
+
/** Pending predictions keyed by relationship/session scope for auto-learning */
|
|
140
|
+
pendingPredictions = new Map();
|
|
141
|
+
/** Most recent response contract keyed by relationship/session scope for implicit loop closure */
|
|
142
|
+
pendingResponseContracts = new Map();
|
|
140
143
|
/** Built-in diagnostics collector — auto-records every processInput/processOutput */
|
|
141
144
|
diagnosticCollector;
|
|
142
145
|
/** Last generated diagnostic report (from endSession or explicit call) */
|
|
@@ -155,6 +158,7 @@ export class PsycheEngine {
|
|
|
155
158
|
this.traits = config.traits;
|
|
156
159
|
this.classifier = config.classifier ?? new BuiltInClassifier();
|
|
157
160
|
this.llmClassifier = config.llmClassifier;
|
|
161
|
+
this.modeOverride = config.mode;
|
|
158
162
|
this.cfg = {
|
|
159
163
|
mbti: config.mbti ?? "INFJ",
|
|
160
164
|
name: config.name ?? "agent",
|
|
@@ -239,6 +243,15 @@ export class PsycheEngine {
|
|
|
239
243
|
if (!loaded.lastWritebackFeedback) {
|
|
240
244
|
loaded.lastWritebackFeedback = [];
|
|
241
245
|
}
|
|
246
|
+
if (this.modeOverride) {
|
|
247
|
+
loaded.meta = { ...loaded.meta, mode: this.modeOverride };
|
|
248
|
+
}
|
|
249
|
+
else if (loaded.meta.mode) {
|
|
250
|
+
this.cfg.mode = loaded.meta.mode;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
loaded.meta = { ...loaded.meta, mode: this.cfg.mode };
|
|
254
|
+
}
|
|
242
255
|
// Update sigilId if config provides one (Sigil may be assigned after first run)
|
|
243
256
|
if (this.cfg.sigilId && loaded.meta.sigilId !== this.cfg.sigilId) {
|
|
244
257
|
loaded.meta = { ...loaded.meta, sigilId: this.cfg.sigilId };
|
|
@@ -262,31 +275,34 @@ export class PsycheEngine {
|
|
|
262
275
|
let sessionBridge = null;
|
|
263
276
|
let writebackFeedback = [];
|
|
264
277
|
let throngletsExports = [];
|
|
278
|
+
const now = new Date();
|
|
279
|
+
const runtimeMode = this.resolveMode(state);
|
|
280
|
+
const pendingKey = this.resolvePendingPredictionKey(opts);
|
|
265
281
|
// ── Auto-learning: evaluate previous turn's outcome ──────
|
|
266
|
-
|
|
282
|
+
const pendingPrediction = this.pendingPredictions.get(pendingKey);
|
|
283
|
+
if (pendingPrediction && text.length > 0) {
|
|
267
284
|
const nextClassifications = classifyLegacyStimulus(text);
|
|
268
285
|
const nextStimulus = (nextClassifications[0]?.confidence ?? 0) >= 0.5
|
|
269
286
|
? nextClassifications[0].type
|
|
270
287
|
: null;
|
|
271
|
-
const outcome = evaluateOutcome(
|
|
288
|
+
const outcome = evaluateOutcome(pendingPrediction.preInteractionState, state, nextStimulus, pendingPrediction.appliedStimulus, pendingPrediction.userId);
|
|
272
289
|
// Record prediction accuracy
|
|
273
290
|
state = {
|
|
274
291
|
...state,
|
|
275
|
-
learning: recordPrediction(state.learning,
|
|
292
|
+
learning: recordPrediction(state.learning, pendingPrediction.predictedState, state.current, pendingPrediction.appliedStimulus),
|
|
276
293
|
};
|
|
277
294
|
// Update learned vectors based on outcome
|
|
278
|
-
if (
|
|
295
|
+
if (pendingPrediction.appliedStimulus) {
|
|
279
296
|
state = {
|
|
280
297
|
...state,
|
|
281
|
-
learning: updateLearnedVector(state.learning,
|
|
298
|
+
learning: updateLearnedVector(state.learning, pendingPrediction.appliedStimulus, pendingPrediction.contextHash, outcome.adaptiveScore, state.current, state.baseline),
|
|
282
299
|
};
|
|
283
300
|
}
|
|
284
|
-
this.
|
|
301
|
+
this.pendingPredictions.delete(pendingKey);
|
|
285
302
|
}
|
|
286
303
|
// ── Snapshot pre-interaction state for next turn's outcome evaluation
|
|
287
304
|
const preInteractionState = { ...state };
|
|
288
305
|
// Time decay toward baseline (chemistry + drives)
|
|
289
|
-
const now = new Date();
|
|
290
306
|
const minutesElapsed = (now.getTime() - new Date(state.updatedAt).getTime()) / 60000;
|
|
291
307
|
if (minutesElapsed >= 1) {
|
|
292
308
|
// Compute effective baseline from current 4D position (drives are derived, not stored)
|
|
@@ -311,15 +327,24 @@ export class PsycheEngine {
|
|
|
311
327
|
}
|
|
312
328
|
// Apply homeostatic pressure (fatigue from extended sessions)
|
|
313
329
|
const sessionMinutes = (now.getTime() - new Date(state.sessionStartedAt).getTime()) / 60000;
|
|
330
|
+
const previousSessionMinutes = Math.max(0, sessionMinutes - Math.max(minutesElapsed, 0));
|
|
314
331
|
const pressure = computeHomeostaticPressure(sessionMinutes);
|
|
315
|
-
|
|
332
|
+
const previousPressure = computeHomeostaticPressure(previousSessionMinutes);
|
|
333
|
+
const incrementalPressure = {
|
|
334
|
+
orderDepletion: Math.max(0, pressure.orderDepletion - previousPressure.orderDepletion),
|
|
335
|
+
flowDepletion: Math.max(0, pressure.flowDepletion - previousPressure.flowDepletion),
|
|
336
|
+
boundaryStiffening: Math.max(0, pressure.boundaryStiffening - previousPressure.boundaryStiffening),
|
|
337
|
+
};
|
|
338
|
+
if (incrementalPressure.orderDepletion > 0
|
|
339
|
+
|| incrementalPressure.flowDepletion > 0
|
|
340
|
+
|| incrementalPressure.boundaryStiffening > 0) {
|
|
316
341
|
state = {
|
|
317
342
|
...state,
|
|
318
343
|
current: {
|
|
319
344
|
...state.current,
|
|
320
|
-
order: clamp(state.current.order -
|
|
321
|
-
flow: clamp(state.current.flow -
|
|
322
|
-
boundary: clamp(state.current.boundary +
|
|
345
|
+
order: clamp(state.current.order - incrementalPressure.orderDepletion * 0.1),
|
|
346
|
+
flow: clamp(state.current.flow - incrementalPressure.flowDepletion * 0.1),
|
|
347
|
+
boundary: clamp(state.current.boundary + incrementalPressure.boundaryStiffening * 0.1),
|
|
323
348
|
},
|
|
324
349
|
};
|
|
325
350
|
}
|
|
@@ -365,7 +390,7 @@ export class PsycheEngine {
|
|
|
365
390
|
baseline: state.baseline,
|
|
366
391
|
sensitivity: state.sensitivity ?? 1.0,
|
|
367
392
|
personalityIntensity: this.cfg.personalityIntensity,
|
|
368
|
-
mode:
|
|
393
|
+
mode: runtimeMode,
|
|
369
394
|
maxDimensionDelta: this.cfg.maxDimensionDelta,
|
|
370
395
|
drives,
|
|
371
396
|
previousAppraisal: state.subjectResidue?.axes,
|
|
@@ -445,7 +470,7 @@ export class PsycheEngine {
|
|
|
445
470
|
state = { ...state, energyBudgets };
|
|
446
471
|
// Relational turn uses pre-computed appraisal from perception
|
|
447
472
|
const relationalTurn = applyRelationalTurn(state, text, {
|
|
448
|
-
mode:
|
|
473
|
+
mode: runtimeMode,
|
|
449
474
|
now: now.toISOString(),
|
|
450
475
|
stimulus: appliedStimulus,
|
|
451
476
|
userId: opts?.userId,
|
|
@@ -479,10 +504,6 @@ export class PsycheEngine {
|
|
|
479
504
|
});
|
|
480
505
|
state = throngletsExportResult.state;
|
|
481
506
|
throngletsExports = throngletsExportResult.exports;
|
|
482
|
-
// Constitutive bridge: emit to Thronglets directly (substrate-independent)
|
|
483
|
-
if (throngletsExports.length > 0) {
|
|
484
|
-
bridgeThrongletsExports(throngletsExports, this.bridgeOpts).catch(() => { });
|
|
485
|
-
}
|
|
486
507
|
// ── Locale (used by multiple subsystems below) ──────────
|
|
487
508
|
const locale = state.meta.locale ?? this.cfg.locale;
|
|
488
509
|
// Push snapshot to emotional history
|
|
@@ -501,18 +522,20 @@ export class PsycheEngine {
|
|
|
501
522
|
this._lastAlgorithmApplied = appliedStimulus !== null;
|
|
502
523
|
// ── Generate prediction for next turn's auto-learning ────
|
|
503
524
|
if (appliedStimulus) {
|
|
504
|
-
const ctxHash = computeContextHash(state, opts?.userId);
|
|
525
|
+
const ctxHash = computeContextHash(state, opts?.userId, opts?.sessionId);
|
|
505
526
|
const effectiveSensitivity = computeEffectiveSensitivity((state.sensitivity ?? 1.0), state.current, state.baseline, appliedStimulus, state.traitDrift);
|
|
506
527
|
const predicted = predictState(preInteractionState.current, appliedStimulus, state.learning, ctxHash, effectiveSensitivity, this.cfg.maxDimensionDelta);
|
|
507
|
-
this.
|
|
528
|
+
this.pendingPredictions.set(pendingKey, {
|
|
508
529
|
predictedState: predicted,
|
|
509
530
|
preInteractionState,
|
|
510
531
|
appliedStimulus,
|
|
511
532
|
contextHash: ctxHash,
|
|
512
|
-
|
|
533
|
+
userId: resolveRelationshipUserId(opts?.userId),
|
|
534
|
+
sessionId: opts?.sessionId?.trim() || undefined,
|
|
535
|
+
});
|
|
513
536
|
}
|
|
514
537
|
else {
|
|
515
|
-
this.
|
|
538
|
+
this.pendingPredictions.delete(pendingKey);
|
|
516
539
|
}
|
|
517
540
|
const writebackNote = formatWritebackFeedbackNote(writebackFeedback, locale);
|
|
518
541
|
const ambientPriors = normalizeAmbientPriors(opts?.ambientPriors);
|
|
@@ -539,6 +562,20 @@ export class PsycheEngine {
|
|
|
539
562
|
// Persist
|
|
540
563
|
this.state = state;
|
|
541
564
|
await this.storage.save(state);
|
|
565
|
+
// Constitutive bridge: emit to Thronglets directly (substrate-independent)
|
|
566
|
+
if (throngletsExports.length > 0) {
|
|
567
|
+
bridgeThrongletsExports(throngletsExports, {
|
|
568
|
+
...this.bridgeOpts,
|
|
569
|
+
sessionId: opts?.sessionId ?? this.bridgeOpts.sessionId,
|
|
570
|
+
}).then(async (ingested) => {
|
|
571
|
+
if (ingested <= 0)
|
|
572
|
+
return;
|
|
573
|
+
const latestState = this.ensureInitialized();
|
|
574
|
+
const markedState = markThrongletsExportsEmitted(latestState, throngletsExports, new Date().toISOString());
|
|
575
|
+
this.state = markedState;
|
|
576
|
+
await this.storage.save(markedState);
|
|
577
|
+
}).catch(() => { });
|
|
578
|
+
}
|
|
542
579
|
// Auto-diagnostics: record this input
|
|
543
580
|
if (this.diagnosticCollector) {
|
|
544
581
|
this.diagnosticCollector.recordInput(appliedStimulus, appliedStimulus ? 1.0 : 0.0, state.current, appraisalAxes);
|
|
@@ -585,6 +622,7 @@ export class PsycheEngine {
|
|
|
585
622
|
});
|
|
586
623
|
// v10: compact mode is always on. Legacy buildDynamicContext removed from engine path.
|
|
587
624
|
const externalContinuity = buildExternalContinuityEnvelope(throngletsExports);
|
|
625
|
+
this.pendingResponseContracts.set(pendingKey, replyEnvelope.responseContract);
|
|
588
626
|
return {
|
|
589
627
|
systemContext: "",
|
|
590
628
|
dynamicContext: buildCompactContext(state, opts?.userId, promptRenderInputs),
|
|
@@ -620,6 +658,7 @@ export class PsycheEngine {
|
|
|
620
658
|
let state = this.ensureInitialized();
|
|
621
659
|
let stateChanged = false;
|
|
622
660
|
const validationIssues = [];
|
|
661
|
+
const pendingKey = this.resolvePendingPredictionKey(opts);
|
|
623
662
|
// Emotional contagion from empathy log
|
|
624
663
|
if (state.empathyLog?.userState && this.cfg.emotionalContagionRate > 0) {
|
|
625
664
|
const userEmotion = state.empathyLog.userState.toLowerCase();
|
|
@@ -669,8 +708,9 @@ export class PsycheEngine {
|
|
|
669
708
|
// Loop outcome feedback (substrate-independent φ closure).
|
|
670
709
|
// The substrate reports whether the output aligned with the Loop's intention.
|
|
671
710
|
// Core processes this as pure 4D chemistry — no text parsing, no medium assumptions.
|
|
672
|
-
|
|
673
|
-
|
|
711
|
+
const outcome = opts?.outcome ?? this.inferLoopOutcome(text, pendingKey);
|
|
712
|
+
if (outcome) {
|
|
713
|
+
const { alignment, effort } = outcome;
|
|
674
714
|
if (alignment === "diverged") {
|
|
675
715
|
// Self/non-self conflict: boundary sharpens, order drops
|
|
676
716
|
state = {
|
|
@@ -813,13 +853,14 @@ export class PsycheEngine {
|
|
|
813
853
|
* @param nextUserStimulus - The stimulus detected in the user's next message,
|
|
814
854
|
* or null if the session ended.
|
|
815
855
|
*/
|
|
816
|
-
async processOutcome(nextUserStimulus,
|
|
817
|
-
if (!this.pendingPrediction)
|
|
818
|
-
return null;
|
|
856
|
+
async processOutcome(nextUserStimulus, opts) {
|
|
819
857
|
let state = this.ensureInitialized();
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
858
|
+
const pendingKey = this.resolvePendingPredictionKey(opts);
|
|
859
|
+
const pending = this.pendingPredictions.get(pendingKey);
|
|
860
|
+
if (!pending)
|
|
861
|
+
return null;
|
|
862
|
+
this.pendingPredictions.delete(pendingKey);
|
|
863
|
+
const outcome = evaluateOutcome(pending.preInteractionState, state, nextUserStimulus, pending.appliedStimulus, pending.userId);
|
|
823
864
|
// Record prediction
|
|
824
865
|
state = {
|
|
825
866
|
...state,
|
|
@@ -900,6 +941,21 @@ export class PsycheEngine {
|
|
|
900
941
|
getLastDiagnosticReport() {
|
|
901
942
|
return this.lastReport;
|
|
902
943
|
}
|
|
944
|
+
getMode() {
|
|
945
|
+
return this.resolveMode(this.ensureInitialized());
|
|
946
|
+
}
|
|
947
|
+
async setMode(mode) {
|
|
948
|
+
const state = this.ensureInitialized();
|
|
949
|
+
this.cfg.mode = mode;
|
|
950
|
+
this.state = {
|
|
951
|
+
...state,
|
|
952
|
+
meta: {
|
|
953
|
+
...state.meta,
|
|
954
|
+
mode,
|
|
955
|
+
},
|
|
956
|
+
};
|
|
957
|
+
await this.storage.save(this.state);
|
|
958
|
+
}
|
|
903
959
|
/**
|
|
904
960
|
* Get current session diagnostic metrics (live, before endSession).
|
|
905
961
|
*/
|
|
@@ -937,6 +993,27 @@ export class PsycheEngine {
|
|
|
937
993
|
}
|
|
938
994
|
return this.state;
|
|
939
995
|
}
|
|
996
|
+
resolveMode(state) {
|
|
997
|
+
const mode = state.meta.mode;
|
|
998
|
+
if (mode) {
|
|
999
|
+
this.cfg.mode = mode;
|
|
1000
|
+
return mode;
|
|
1001
|
+
}
|
|
1002
|
+
return this.cfg.mode;
|
|
1003
|
+
}
|
|
1004
|
+
resolvePendingPredictionKey(opts) {
|
|
1005
|
+
const userKey = resolveRelationshipUserId(opts?.userId);
|
|
1006
|
+
const sessionId = opts?.sessionId?.trim();
|
|
1007
|
+
return sessionId ? `${userKey}::${sessionId}` : userKey;
|
|
1008
|
+
}
|
|
1009
|
+
inferLoopOutcome(text, pendingKey) {
|
|
1010
|
+
const contract = this.pendingResponseContracts.get(pendingKey);
|
|
1011
|
+
this.pendingResponseContracts.delete(pendingKey);
|
|
1012
|
+
if (!contract)
|
|
1013
|
+
return undefined;
|
|
1014
|
+
const maxLen = (contract.maxChars ?? 500) * 2;
|
|
1015
|
+
return { alignment: text.length > maxLen ? "diverged" : "aligned" };
|
|
1016
|
+
}
|
|
940
1017
|
createDefaultState() {
|
|
941
1018
|
const { mbti, name, locale } = this.cfg;
|
|
942
1019
|
// Use Big Five traits if provided, otherwise use preset baseline
|
package/dist/learning.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { PsycheState, StimulusType, SelfState, ImpactVector, LearningState,
|
|
|
5
5
|
* Computes a score from -1 to 1 using multiple signals:
|
|
6
6
|
* drive changes, relationship changes, user warmth, conversation continuation.
|
|
7
7
|
*/
|
|
8
|
-
export declare function evaluateOutcome(prevState: PsycheState, currentState: PsycheState, nextUserStimulus: StimulusType | null, appliedStimulus: StimulusType | null): OutcomeScore;
|
|
8
|
+
export declare function evaluateOutcome(prevState: PsycheState, currentState: PsycheState, nextUserStimulus: StimulusType | null, appliedStimulus: StimulusType | null, userId?: string): OutcomeScore;
|
|
9
9
|
/**
|
|
10
10
|
* Get the effective compatibility vector for a given stimulus + context,
|
|
11
11
|
* combining the base vector with any learned adjustment.
|
|
@@ -32,7 +32,7 @@ export declare function updateLearnedVector(learning: LearningState, stimulus: S
|
|
|
32
32
|
*
|
|
33
33
|
* Example: "familiar:approach,task,task:hmlhh"
|
|
34
34
|
*/
|
|
35
|
-
export declare function computeContextHash(state: PsycheState,
|
|
35
|
+
export declare function computeContextHash(state: PsycheState, userId?: string, sessionId?: string): string;
|
|
36
36
|
/**
|
|
37
37
|
* Predict the resulting chemistry after applying a stimulus,
|
|
38
38
|
* using learned vectors instead of raw base vectors.
|
package/dist/learning.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { DIMENSION_KEYS, DRIVE_KEYS, MAX_LEARNED_VECTORS, MAX_PREDICTION_HISTORY, } from "./types.js";
|
|
15
15
|
import { STIMULUS_VECTORS, clamp } from "./chemistry.js";
|
|
16
16
|
import { derivePrimarySnapshotMarker, markerFromLegacyStimulus, } from "./appraisal-markers.js";
|
|
17
|
+
import { resolveRelationshipUserId } from "./relationship-key.js";
|
|
17
18
|
// ── 1. OutcomeEvaluator ─────────────────────────────────────
|
|
18
19
|
/** Warmth mapping for appraisal residue. */
|
|
19
20
|
const MARKER_WARMTH_MAP = {
|
|
@@ -39,9 +40,10 @@ function matchesLearningEntry(entry, stimulus, marker, contextHash) {
|
|
|
39
40
|
* Computes a score from -1 to 1 using multiple signals:
|
|
40
41
|
* drive changes, relationship changes, user warmth, conversation continuation.
|
|
41
42
|
*/
|
|
42
|
-
export function evaluateOutcome(prevState, currentState, nextUserStimulus, appliedStimulus) {
|
|
43
|
+
export function evaluateOutcome(prevState, currentState, nextUserStimulus, appliedStimulus, userId) {
|
|
43
44
|
const nextUserMarker = learningMarkerFromStimulus(nextUserStimulus);
|
|
44
45
|
const appliedMarker = learningMarkerFromStimulus(appliedStimulus);
|
|
46
|
+
const relationKey = resolveRelationshipUserId(userId);
|
|
45
47
|
// Drive delta: sum of all drive changes, normalized
|
|
46
48
|
let driveSum = 0;
|
|
47
49
|
for (const key of DRIVE_KEYS) {
|
|
@@ -49,8 +51,8 @@ export function evaluateOutcome(prevState, currentState, nextUserStimulus, appli
|
|
|
49
51
|
}
|
|
50
52
|
const driveDelta = Math.max(-1, Math.min(1, driveSum / 50));
|
|
51
53
|
// Relationship delta: change in trust + intimacy of _default relationship
|
|
52
|
-
const prevRel = prevState.relationships
|
|
53
|
-
const curRel = currentState.relationships
|
|
54
|
+
const prevRel = prevState.relationships[relationKey] ?? { trust: 50, intimacy: 30 };
|
|
55
|
+
const curRel = currentState.relationships[relationKey] ?? { trust: 50, intimacy: 30 };
|
|
54
56
|
const relChange = (curRel.trust - prevRel.trust) + (curRel.intimacy - prevRel.intimacy);
|
|
55
57
|
const relationshipDelta = Math.max(-1, Math.min(1, relChange / 20));
|
|
56
58
|
// User warmth: what residue their next turn carried
|
|
@@ -193,9 +195,10 @@ export function updateLearnedVector(learning, stimulus, contextHash, outcomeScor
|
|
|
193
195
|
*
|
|
194
196
|
* Example: "familiar:approach,task,task:hmlhh"
|
|
195
197
|
*/
|
|
196
|
-
export function computeContextHash(state,
|
|
198
|
+
export function computeContextHash(state, userId, sessionId) {
|
|
197
199
|
// Relationship phase
|
|
198
|
-
const
|
|
200
|
+
const relationKey = resolveRelationshipUserId(userId);
|
|
201
|
+
const rel = state.relationships[relationKey] ?? { phase: "stranger" };
|
|
199
202
|
const phase = rel.phase;
|
|
200
203
|
// Last 3 canonical residues from emotional history
|
|
201
204
|
const history = state.stateHistory ?? [];
|
|
@@ -217,7 +220,10 @@ export function computeContextHash(state, _userId) {
|
|
|
217
220
|
// Format: separate safety from the rest for readability
|
|
218
221
|
// survival_safety_connection_esteem_curiosity
|
|
219
222
|
const driveStr = driveLevels.join("");
|
|
220
|
-
|
|
223
|
+
const scope = sessionId?.trim();
|
|
224
|
+
return scope
|
|
225
|
+
? `${phase}:${recentMarkers || "none"}:${driveStr}:session=${scope}`
|
|
226
|
+
: `${phase}:${recentMarkers || "none"}:${driveStr}`;
|
|
221
227
|
}
|
|
222
228
|
// ── 3. PredictionEngine ─────────────────────────────────────
|
|
223
229
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PsycheState, ResolvedRelationContext, SessionBridgeState, ThrongletsExport, WritebackCalibrationFeedback } from "./types.js";
|
|
2
|
+
export declare function markThrongletsExportsEmitted(state: PsycheState, exports: ThrongletsExport[], now: string): PsycheState;
|
|
2
3
|
export declare function deriveThrongletsExports(state: PsycheState, opts: {
|
|
3
4
|
relationContext: ResolvedRelationContext;
|
|
4
5
|
sessionBridge?: SessionBridgeState | null;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// The goal is to surface only sparse, typed, low-frequency events
|
|
6
6
|
// that are worth handing to an external continuity substrate.
|
|
7
7
|
// ============================================================
|
|
8
|
+
import { DRIVE_KEYS } from "./types.js";
|
|
9
|
+
import { deriveDriveSatisfaction, hasCriticalDrive } from "./drives.js";
|
|
8
10
|
function clamp01(v) {
|
|
9
11
|
return Math.max(0, Math.min(1, v));
|
|
10
12
|
}
|
|
@@ -45,6 +47,11 @@ function updateExportState(state, keys, now) {
|
|
|
45
47
|
throngletsExportState: nextState,
|
|
46
48
|
};
|
|
47
49
|
}
|
|
50
|
+
export function markThrongletsExportsEmitted(state, exports, now) {
|
|
51
|
+
if (exports.length === 0)
|
|
52
|
+
return state;
|
|
53
|
+
return updateExportState(state, exports.map((event) => event.key), now);
|
|
54
|
+
}
|
|
48
55
|
function quantize(v, step = 10) {
|
|
49
56
|
return Math.round(v / step) * step;
|
|
50
57
|
}
|
|
@@ -148,6 +155,23 @@ function sanitizeThrongletsExport(event) {
|
|
|
148
155
|
};
|
|
149
156
|
return sanitized;
|
|
150
157
|
}
|
|
158
|
+
case "viability": {
|
|
159
|
+
// Viability stays a sparse export, but its meaning is defined by the
|
|
160
|
+
// protocol-layer dimension registry. Keep the kind/payload stable here.
|
|
161
|
+
const sanitized = {
|
|
162
|
+
kind: "viability",
|
|
163
|
+
subject: "session",
|
|
164
|
+
primitive: "signal",
|
|
165
|
+
userKey: event.userKey,
|
|
166
|
+
strength: event.strength,
|
|
167
|
+
ttlTurns: event.ttlTurns,
|
|
168
|
+
key: event.key,
|
|
169
|
+
viable: event.viable,
|
|
170
|
+
minDrive: event.minDrive,
|
|
171
|
+
minDriveType: event.minDriveType,
|
|
172
|
+
};
|
|
173
|
+
return sanitized;
|
|
174
|
+
}
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
export function deriveThrongletsExports(state, opts) {
|
|
@@ -220,6 +244,27 @@ export function deriveThrongletsExports(state, opts) {
|
|
|
220
244
|
order, flow, boundary, resonance,
|
|
221
245
|
summary: selfStateSummary(order, flow, boundary, resonance),
|
|
222
246
|
});
|
|
247
|
+
// Viability — Psyche's self-assessment of aliveness for field pulse
|
|
248
|
+
if (state.baseline) {
|
|
249
|
+
const drives = deriveDriveSatisfaction(state.current, state.baseline);
|
|
250
|
+
const driveEntries = DRIVE_KEYS.map((k) => ({ type: k, value: drives[k] }));
|
|
251
|
+
const min = driveEntries.reduce((a, b) => (a.value < b.value ? a : b));
|
|
252
|
+
const viable = !hasCriticalDrive(drives);
|
|
253
|
+
const driveBucket = Math.round(min.value / 10) * 10;
|
|
254
|
+
const viabilityKey = `viability:${viable ? "viable" : "critical"}:${min.type}:${driveBucket}`;
|
|
255
|
+
candidates.push({
|
|
256
|
+
kind: "viability",
|
|
257
|
+
subject: "session",
|
|
258
|
+
primitive: "signal",
|
|
259
|
+
userKey,
|
|
260
|
+
strength: viable ? 0.5 : 0.9,
|
|
261
|
+
ttlTurns: 12,
|
|
262
|
+
key: viabilityKey,
|
|
263
|
+
viable,
|
|
264
|
+
minDrive: min.value,
|
|
265
|
+
minDriveType: min.type,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
223
268
|
for (const feedback of writebackFeedback) {
|
|
224
269
|
if (feedback.effect === "holding")
|
|
225
270
|
continue;
|
|
@@ -249,6 +294,5 @@ export function deriveThrongletsExports(state, opts) {
|
|
|
249
294
|
const exports = deduped
|
|
250
295
|
.filter((event) => !previousKeys.includes(event.key))
|
|
251
296
|
.map((event) => sanitizeThrongletsExport(event));
|
|
252
|
-
|
|
253
|
-
return { state: nextState, exports };
|
|
297
|
+
return { state, exports };
|
|
254
298
|
}
|
|
@@ -3,7 +3,8 @@ const TAXONOMY_BY_EVENT = {
|
|
|
3
3
|
"open-loop-anchor": "coordination",
|
|
4
4
|
"continuity-anchor": "continuity",
|
|
5
5
|
"writeback-calibration": "calibration",
|
|
6
|
-
"self-state": "
|
|
6
|
+
"self-state": "calibration",
|
|
7
|
+
"viability": "calibration",
|
|
7
8
|
};
|
|
8
9
|
function summarizeLoopTypes(loopTypes) {
|
|
9
10
|
return loopTypes.join(", ");
|
|
@@ -22,6 +23,8 @@ function summarizeThrongletsExport(event) {
|
|
|
22
23
|
}
|
|
23
24
|
case "self-state":
|
|
24
25
|
return event.summary;
|
|
26
|
+
case "viability":
|
|
27
|
+
return `viability: ${event.viable ? "viable" : "critical"} (min drive: ${event.minDriveType} at ${Math.round(event.minDrive)})`;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
export function taxonomyForThrongletsExport(event) {
|
package/dist/types.d.ts
CHANGED
|
@@ -611,7 +611,7 @@ export interface AmbientPriorView {
|
|
|
611
611
|
export type ThrongletsExportSubject = "delegate" | "session";
|
|
612
612
|
export type ThrongletsExportPrimitive = "signal" | "trace";
|
|
613
613
|
export interface ThrongletsExportBase {
|
|
614
|
-
kind: "relation-milestone" | "open-loop-anchor" | "writeback-calibration" | "continuity-anchor" | "self-state";
|
|
614
|
+
kind: "relation-milestone" | "open-loop-anchor" | "writeback-calibration" | "continuity-anchor" | "self-state" | "viability";
|
|
615
615
|
subject: ThrongletsExportSubject;
|
|
616
616
|
primitive: ThrongletsExportPrimitive;
|
|
617
617
|
userKey: string;
|
|
@@ -662,7 +662,15 @@ export interface SelfStateExport extends ThrongletsExportBase {
|
|
|
662
662
|
resonance: number;
|
|
663
663
|
summary: string;
|
|
664
664
|
}
|
|
665
|
-
export
|
|
665
|
+
export interface ViabilityExport extends ThrongletsExportBase {
|
|
666
|
+
kind: "viability";
|
|
667
|
+
subject: "session";
|
|
668
|
+
primitive: "signal";
|
|
669
|
+
viable: boolean;
|
|
670
|
+
minDrive: number;
|
|
671
|
+
minDriveType: DriveType;
|
|
672
|
+
}
|
|
673
|
+
export type ThrongletsExport = RelationMilestoneExport | OpenLoopAnchorExport | WritebackCalibrationExport | ContinuityAnchorExport | SelfStateExport | ViabilityExport;
|
|
666
674
|
export interface ThrongletsExportState {
|
|
667
675
|
lastKeys: string[];
|
|
668
676
|
lastAt: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "psyche-ai",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.9.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": "11.
|
|
9
|
+
"version": "11.9.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "psyche-ai",
|
|
14
|
-
"version": "11.
|
|
14
|
+
"version": "11.9.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|