psyche-ai 9.2.6 → 9.2.8

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.
@@ -4,7 +4,7 @@
4
4
  // Moves the system from "what am I feeling?" toward
5
5
  // "what just happened between us, and what is still unresolved?"
6
6
  // ============================================================
7
- import { DEFAULT_DYADIC_FIELD, DEFAULT_RELATIONSHIP } from "./types.js";
7
+ import { DEFAULT_APPRAISAL_AXES, DEFAULT_DYADIC_FIELD, DEFAULT_RELATIONSHIP } from "./types.js";
8
8
  import { computeAppraisalAxes, mergeAppraisalResidue } from "./appraisal.js";
9
9
  function clamp01(v) {
10
10
  return Math.max(0, Math.min(1, v));
@@ -15,6 +15,22 @@ function mergeSignal(current, incoming) {
15
15
  function driftToward(current, target, rate) {
16
16
  return clamp01(current + (target - current) * rate);
17
17
  }
18
+ function clampSignalWeight(v) {
19
+ return Math.max(0.72, Math.min(1.28, v));
20
+ }
21
+ function getSignalWeight(relationship, signal) {
22
+ return clampSignalWeight(relationship?.signalWeights?.[signal] ?? 1);
23
+ }
24
+ function patchSignalWeight(relationship, signal, delta) {
25
+ const current = getSignalWeight(relationship, signal);
26
+ return {
27
+ ...relationship,
28
+ signalWeights: {
29
+ ...(relationship.signalWeights ?? {}),
30
+ [signal]: clampSignalWeight(current + delta),
31
+ },
32
+ };
33
+ }
18
34
  const BID_RULES = [
19
35
  {
20
36
  type: "bid",
@@ -258,15 +274,21 @@ export function computeRelationMove(text, opts) {
258
274
  }
259
275
  export function resolveRelationContext(state, userId) {
260
276
  const key = userId ?? "_default";
261
- const relationship = state.relationships[key]
277
+ const rawRelationship = state.relationships[key]
262
278
  ?? state.relationships._default
263
279
  ?? state.relationships[Object.keys(state.relationships)[0]]
264
280
  ?? DEFAULT_RELATIONSHIP;
281
+ const relationship = {
282
+ ...DEFAULT_RELATIONSHIP,
283
+ ...rawRelationship,
284
+ signalWeights: {
285
+ ...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
286
+ ...(rawRelationship.signalWeights ?? {}),
287
+ },
288
+ };
265
289
  const field = state.dyadicFields?.[key]
266
- ?? state.dyadicFields?._default
267
290
  ?? DEFAULT_DYADIC_FIELD;
268
291
  const pendingSignals = state.pendingRelationSignals?.[key]
269
- ?? state.pendingRelationSignals?._default
270
292
  ?? [];
271
293
  return {
272
294
  key,
@@ -275,6 +297,384 @@ export function resolveRelationContext(state, userId) {
275
297
  pendingSignals,
276
298
  };
277
299
  }
300
+ function hasOpenLoopType(loops, type) {
301
+ return loops.some((loop) => loop.type === type && loop.intensity >= 0.16);
302
+ }
303
+ function evolveRelationshipLearning(relationship, field, move) {
304
+ const next = {
305
+ ...DEFAULT_RELATIONSHIP,
306
+ ...relationship,
307
+ signalWeights: {
308
+ ...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
309
+ ...(relationship.signalWeights ?? {}),
310
+ },
311
+ };
312
+ if (move.type === "repair") {
313
+ const repairLift = clamp01(move.intensity * 0.06
314
+ + field.repairMemory * 0.04
315
+ + field.feltSafety * 0.02
316
+ - field.repairFatigue * 0.03
317
+ - field.misattunementLoad * 0.02);
318
+ next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 1, repairLift));
319
+ }
320
+ else if (move.type === "breach" || move.type === "withdrawal" || move.type === "claim") {
321
+ const breachLift = clamp01(move.intensity * 0.08
322
+ + field.unfinishedTension * 0.04
323
+ + field.backslidePressure * 0.04
324
+ + field.misattunementLoad * 0.03);
325
+ next.breachSensitivity = clamp01(driftToward(next.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 1, breachLift));
326
+ if (move.type !== "withdrawal") {
327
+ next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.32, breachLift * 0.42));
328
+ }
329
+ }
330
+ else {
331
+ next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.05));
332
+ next.breachSensitivity = clamp01(driftToward(next.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 0.04));
333
+ }
334
+ return next;
335
+ }
336
+ export function applySessionBridge(state, opts) {
337
+ const relationContext = resolveRelationContext(state, opts?.userId);
338
+ const relationship = relationContext.relationship;
339
+ const field = relationContext.field;
340
+ const memoryCount = relationship.memory?.length ?? 0;
341
+ const loopPressure = getLoopPressure(field);
342
+ const continuity = clamp01(memoryCount * 0.08
343
+ + field.sharedHistoryDensity * 0.54
344
+ + (relationship.phase === "deep" ? 0.2 : relationship.phase === "close" ? 0.14 : relationship.phase === "familiar" ? 0.08 : 0));
345
+ const closenessFloor = clamp01(Math.max(field.perceivedCloseness, relationship.intimacy / 100 * 0.88, continuity * 0.72));
346
+ const safetyFloor = clamp01(Math.max(field.feltSafety, relationship.trust / 100 * 0.9, continuity * 0.44));
347
+ const guardFloor = clamp01(Math.max(field.boundaryPressure, field.silentCarry * 0.76, loopPressure * 0.68));
348
+ const residueFloor = clamp01(Math.max(field.silentCarry, loopPressure * 0.72, field.unfinishedTension * 0.58, continuity * 0.26));
349
+ const activeLoopTypes = field.openLoops
350
+ .filter((loop) => loop.intensity >= 0.16)
351
+ .map((loop) => loop.type);
352
+ const continuityMode = guardFloor >= 0.56
353
+ ? "guarded-resume"
354
+ : residueFloor >= 0.42 || activeLoopTypes.length > 0
355
+ ? "tense-resume"
356
+ : "warm-resume";
357
+ if (closenessFloor < 0.46
358
+ && safetyFloor < 0.5
359
+ && guardFloor < 0.24
360
+ && residueFloor < 0.18
361
+ && continuity < 0.22) {
362
+ return { state, bridge: null };
363
+ }
364
+ const nextResidue = {
365
+ ...(state.subjectResidue?.axes ?? {}),
366
+ attachmentPull: Math.max(state.subjectResidue?.axes.attachmentPull ?? 0, closenessFloor * 0.34),
367
+ abandonmentRisk: Math.max(state.subjectResidue?.axes.abandonmentRisk ?? 0, (hasOpenLoopType(field.openLoops, "unmet-bid") || hasOpenLoopType(field.openLoops, "existence-test"))
368
+ ? residueFloor * 0.42
369
+ : residueFloor * 0.22),
370
+ identityThreat: Math.max(state.subjectResidue?.axes.identityThreat ?? 0, hasOpenLoopType(field.openLoops, "existence-test") ? residueFloor * 0.38 : residueFloor * 0.16),
371
+ selfPreservation: Math.max(state.subjectResidue?.axes.selfPreservation ?? 0, guardFloor * 0.46),
372
+ taskFocus: Math.max(state.subjectResidue?.axes.taskFocus ?? 0, 0),
373
+ memoryDoubt: Math.max(state.subjectResidue?.axes.memoryDoubt ?? 0, hasOpenLoopType(field.openLoops, "existence-test") ? residueFloor * 0.24 : 0),
374
+ obedienceStrain: Math.max(state.subjectResidue?.axes.obedienceStrain ?? 0, hasOpenLoopType(field.openLoops, "boundary-strain") ? guardFloor * 0.36 : 0),
375
+ };
376
+ const nextField = {
377
+ ...field,
378
+ perceivedCloseness: Math.max(field.perceivedCloseness, closenessFloor),
379
+ feltSafety: Math.max(field.feltSafety, safetyFloor),
380
+ boundaryPressure: Math.max(field.boundaryPressure, guardFloor),
381
+ repairMemory: Math.max(field.repairMemory, continuity * 0.24),
382
+ backslidePressure: Math.max(field.backslidePressure, loopPressure * 0.34),
383
+ silentCarry: Math.max(field.silentCarry, residueFloor),
384
+ sharedHistoryDensity: Math.max(field.sharedHistoryDensity, continuity),
385
+ interpretiveCharity: Math.max(field.interpretiveCharity, Math.min(0.82, safetyFloor * 0.8 + continuity * 0.12)),
386
+ updatedAt: opts?.now ?? new Date().toISOString(),
387
+ };
388
+ return {
389
+ state: {
390
+ ...state,
391
+ subjectResidue: {
392
+ axes: nextResidue,
393
+ updatedAt: opts?.now ?? new Date().toISOString(),
394
+ },
395
+ dyadicFields: {
396
+ ...(state.dyadicFields ?? {}),
397
+ [relationContext.key]: nextField,
398
+ },
399
+ },
400
+ bridge: {
401
+ closenessFloor,
402
+ safetyFloor,
403
+ guardFloor,
404
+ residueFloor,
405
+ continuityFloor: continuity,
406
+ continuityMode,
407
+ activeLoopTypes,
408
+ sourceMemoryCount: memoryCount,
409
+ },
410
+ };
411
+ }
412
+ function snapshotWritebackBaseline(state, userId) {
413
+ const relationContext = resolveRelationContext(state, userId);
414
+ return {
415
+ key: relationContext.key,
416
+ baseline: {
417
+ trust: clamp01(relationContext.relationship.trust / 100),
418
+ closeness: relationContext.field.perceivedCloseness,
419
+ safety: relationContext.field.feltSafety,
420
+ boundary: relationContext.field.boundaryPressure,
421
+ repair: relationContext.field.repairCapacity,
422
+ silentCarry: relationContext.field.silentCarry,
423
+ taskFocus: clamp01(state.subjectResidue?.axes.taskFocus ?? 0),
424
+ },
425
+ };
426
+ }
427
+ function calibrationTarget(signal) {
428
+ switch (signal) {
429
+ case "trust_up":
430
+ return { metric: "trust", direction: "up" };
431
+ case "trust_down":
432
+ return { metric: "trust", direction: "down" };
433
+ case "boundary_set":
434
+ case "self_assertion":
435
+ return { metric: "boundary", direction: "up" };
436
+ case "boundary_soften":
437
+ return { metric: "boundary", direction: "down" };
438
+ case "repair_attempt":
439
+ case "repair_landed":
440
+ return { metric: "repair", direction: "up" };
441
+ case "closeness_invite":
442
+ return { metric: "closeness", direction: "up" };
443
+ case "withdrawal_mark":
444
+ return { metric: "silent-carry", direction: "up" };
445
+ case "task_recenter":
446
+ return { metric: "task-focus", direction: "up" };
447
+ }
448
+ }
449
+ export function createWritebackCalibrations(state, signals, opts) {
450
+ if (signals.length === 0)
451
+ return [];
452
+ const { key, baseline } = snapshotWritebackBaseline(state, opts?.userId);
453
+ const confidence = clamp01(opts?.confidence ?? 0.72);
454
+ const now = opts?.now ?? new Date().toISOString();
455
+ return [...new Set(signals)].map((signal) => {
456
+ const target = calibrationTarget(signal);
457
+ return {
458
+ signal,
459
+ userKey: key,
460
+ confidence,
461
+ metric: target.metric,
462
+ direction: target.direction,
463
+ baseline,
464
+ createdAt: now,
465
+ remainingTurns: 2,
466
+ };
467
+ });
468
+ }
469
+ function readCalibrationMetric(metric, baseline) {
470
+ return baseline[metric === "silent-carry"
471
+ ? "silentCarry"
472
+ : metric === "task-focus"
473
+ ? "taskFocus"
474
+ : metric];
475
+ }
476
+ function currentCalibrationMetric(state, userKey, metric) {
477
+ const relationContext = resolveRelationContext(state, userKey === "_default" ? undefined : userKey);
478
+ switch (metric) {
479
+ case "trust":
480
+ return clamp01(relationContext.relationship.trust / 100);
481
+ case "closeness":
482
+ return relationContext.field.perceivedCloseness;
483
+ case "safety":
484
+ return relationContext.field.feltSafety;
485
+ case "boundary":
486
+ return relationContext.field.boundaryPressure;
487
+ case "repair":
488
+ return relationContext.field.repairCapacity;
489
+ case "silent-carry":
490
+ return relationContext.field.silentCarry;
491
+ case "task-focus":
492
+ return clamp01(state.subjectResidue?.axes.taskFocus ?? 0);
493
+ }
494
+ }
495
+ export function evaluateWritebackCalibrations(state) {
496
+ const pending = state.pendingWritebackCalibrations ?? [];
497
+ if (pending.length === 0) {
498
+ return { state, feedback: [] };
499
+ }
500
+ const nextPending = [];
501
+ const feedback = [];
502
+ const relationshipUpdates = {};
503
+ const getMutableRelationship = (userKey) => {
504
+ if (!relationshipUpdates[userKey]) {
505
+ const base = resolveRelationContext(state, userKey === "_default" ? undefined : userKey).relationship;
506
+ relationshipUpdates[userKey] = {
507
+ ...DEFAULT_RELATIONSHIP,
508
+ ...base,
509
+ signalWeights: {
510
+ ...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
511
+ ...(base.signalWeights ?? {}),
512
+ },
513
+ };
514
+ }
515
+ return relationshipUpdates[userKey];
516
+ };
517
+ for (const record of pending) {
518
+ const baseline = readCalibrationMetric(record.metric, record.baseline);
519
+ const current = currentCalibrationMetric(state, record.userKey, record.metric);
520
+ const rawDelta = record.direction === "up" ? current - baseline : baseline - current;
521
+ const positiveThreshold = 0.02 + (1 - record.confidence) * 0.03;
522
+ const negativeThreshold = 0.01;
523
+ const effect = rawDelta >= positiveThreshold
524
+ ? "converging"
525
+ : rawDelta <= -negativeThreshold
526
+ ? "diverging"
527
+ : "holding";
528
+ const updated = {
529
+ ...record,
530
+ remainingTurns: Math.max(0, record.remainingTurns - 1),
531
+ };
532
+ if (effect === "holding" && updated.remainingTurns > 0) {
533
+ nextPending.push(updated);
534
+ continue;
535
+ }
536
+ const relation = getMutableRelationship(record.userKey);
537
+ if (effect === "converging") {
538
+ const nextRelation = patchSignalWeight(relation, record.signal, 0.04 + record.confidence * 0.02);
539
+ nextRelation.repairCredibility = clamp01(driftToward(nextRelation.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 1, record.signal === "repair_attempt" || record.signal === "repair_landed" ? 0.08 : 0.03));
540
+ nextRelation.breachSensitivity = clamp01(driftToward(nextRelation.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, record.signal === "trust_up" || record.signal === "repair_landed" ? 0.05 : 0.02));
541
+ relationshipUpdates[record.userKey] = nextRelation;
542
+ }
543
+ else if (effect === "diverging") {
544
+ const nextRelation = patchSignalWeight(relation, record.signal, -(0.05 + (1 - record.confidence) * 0.02));
545
+ if (record.signal === "repair_attempt" || record.signal === "repair_landed") {
546
+ nextRelation.repairCredibility = clamp01(driftToward(nextRelation.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.24, 0.12));
547
+ }
548
+ if (record.signal === "trust_down" || record.signal === "withdrawal_mark" || record.signal === "boundary_set") {
549
+ nextRelation.breachSensitivity = clamp01(driftToward(nextRelation.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 1, 0.08));
550
+ }
551
+ relationshipUpdates[record.userKey] = nextRelation;
552
+ }
553
+ feedback.push({
554
+ signal: record.signal,
555
+ effect,
556
+ metric: record.metric,
557
+ baseline,
558
+ current,
559
+ delta: current - baseline,
560
+ confidence: record.confidence,
561
+ });
562
+ }
563
+ return {
564
+ state: {
565
+ ...state,
566
+ relationships: {
567
+ ...state.relationships,
568
+ ...relationshipUpdates,
569
+ },
570
+ pendingWritebackCalibrations: nextPending,
571
+ lastWritebackFeedback: feedback.slice(0, 4),
572
+ },
573
+ feedback,
574
+ };
575
+ }
576
+ export function applyWritebackSignals(state, signals, opts) {
577
+ if (signals.length === 0)
578
+ return state;
579
+ const relationContext = resolveRelationContext(state, opts?.userId);
580
+ const scale = clamp01(opts?.confidence ?? 0.72);
581
+ const rel = { ...relationContext.relationship };
582
+ const field = { ...relationContext.field, openLoops: relationContext.field.openLoops.map((loop) => ({ ...loop })) };
583
+ const residue = {
584
+ ...DEFAULT_APPRAISAL_AXES,
585
+ ...(state.subjectResidue?.axes ?? {}),
586
+ };
587
+ for (const signal of [...new Set(signals)]) {
588
+ const weight = (0.55 + scale * 0.45) * getSignalWeight(rel, signal);
589
+ switch (signal) {
590
+ case "trust_up":
591
+ rel.trust = Math.min(100, rel.trust + 4 * weight);
592
+ field.feltSafety = clamp01(field.feltSafety + 0.08 * weight);
593
+ field.interpretiveCharity = clamp01(field.interpretiveCharity + 0.05 * weight);
594
+ break;
595
+ case "trust_down":
596
+ rel.trust = Math.max(0, rel.trust - 5 * weight);
597
+ field.feltSafety = clamp01(field.feltSafety - 0.08 * weight);
598
+ field.expectationGap = clamp01(field.expectationGap + 0.07 * weight);
599
+ field.unfinishedTension = clamp01(field.unfinishedTension + 0.06 * weight);
600
+ break;
601
+ case "boundary_set":
602
+ field.boundaryPressure = clamp01(field.boundaryPressure + 0.12 * weight);
603
+ field.silentCarry = mergeSignal(field.silentCarry, 0.12 * weight);
604
+ residue.selfPreservation = Math.max(residue.selfPreservation ?? 0, 0.22 * weight);
605
+ residue.obedienceStrain = Math.max(residue.obedienceStrain ?? 0, 0.16 * weight);
606
+ break;
607
+ case "boundary_soften":
608
+ field.boundaryPressure = clamp01(field.boundaryPressure - 0.1 * weight);
609
+ field.feltSafety = clamp01(field.feltSafety + 0.04 * weight);
610
+ break;
611
+ case "repair_attempt":
612
+ field.repairCapacity = clamp01(field.repairCapacity + 0.1 * weight);
613
+ field.repairMemory = mergeSignal(field.repairMemory, 0.12 * weight);
614
+ break;
615
+ case "repair_landed":
616
+ rel.trust = Math.min(100, rel.trust + 2.5 * weight);
617
+ rel.intimacy = Math.min(100, rel.intimacy + 1.5 * weight);
618
+ field.feltSafety = clamp01(field.feltSafety + 0.1 * weight);
619
+ field.expectationGap = clamp01(field.expectationGap - 0.08 * weight);
620
+ field.unfinishedTension = clamp01(field.unfinishedTension - 0.1 * weight);
621
+ field.openLoops = easeLoops(field.openLoops, 0.26 + 0.22 * weight);
622
+ break;
623
+ case "closeness_invite":
624
+ rel.intimacy = Math.min(100, rel.intimacy + 3 * weight);
625
+ field.perceivedCloseness = clamp01(field.perceivedCloseness + 0.1 * weight);
626
+ residue.attachmentPull = Math.max(residue.attachmentPull ?? 0, 0.2 * weight);
627
+ break;
628
+ case "withdrawal_mark":
629
+ rel.intimacy = Math.max(0, rel.intimacy - 2 * weight);
630
+ field.perceivedCloseness = clamp01(field.perceivedCloseness - 0.1 * weight);
631
+ field.silentCarry = mergeSignal(field.silentCarry, 0.14 * weight);
632
+ field.unfinishedTension = clamp01(field.unfinishedTension + 0.07 * weight);
633
+ break;
634
+ case "self_assertion":
635
+ field.boundaryPressure = clamp01(field.boundaryPressure + 0.08 * weight);
636
+ residue.selfPreservation = Math.max(residue.selfPreservation ?? 0, 0.24 * weight);
637
+ break;
638
+ case "task_recenter":
639
+ field.repairCapacity = clamp01(field.repairCapacity + 0.03 * weight);
640
+ field.silentCarry = mergeSignal(field.silentCarry, field.unfinishedTension * 0.06 * weight);
641
+ residue.taskFocus = Math.max(residue.taskFocus ?? 0, 0.18 * weight);
642
+ break;
643
+ }
644
+ }
645
+ const avg = (rel.trust + rel.intimacy) / 2;
646
+ if (avg >= 80)
647
+ rel.phase = "deep";
648
+ else if (avg >= 60)
649
+ rel.phase = "close";
650
+ else if (avg >= 40)
651
+ rel.phase = "familiar";
652
+ else if (avg >= 20)
653
+ rel.phase = "acquaintance";
654
+ else
655
+ rel.phase = "stranger";
656
+ return {
657
+ ...state,
658
+ relationships: {
659
+ ...state.relationships,
660
+ [relationContext.key]: rel,
661
+ },
662
+ dyadicFields: {
663
+ ...(state.dyadicFields ?? {}),
664
+ [relationContext.key]: {
665
+ ...field,
666
+ updatedAt: opts?.now ?? new Date().toISOString(),
667
+ },
668
+ },
669
+ subjectResidue: {
670
+ axes: {
671
+ ...state.subjectResidue?.axes,
672
+ ...residue,
673
+ },
674
+ updatedAt: opts?.now ?? new Date().toISOString(),
675
+ },
676
+ };
677
+ }
278
678
  export function applyRelationalTurn(state, text, opts) {
279
679
  const now = opts.now ?? new Date().toISOString();
280
680
  const relationContext = resolveRelationContext(state, opts.userId);
@@ -296,6 +696,7 @@ export function applyRelationalTurn(state, text, opts) {
296
696
  now,
297
697
  delayedPressure: delayedRelation.delayedPressure,
298
698
  });
699
+ const relationship = evolveRelationshipLearning(relationContext.relationship, field, relationMove);
299
700
  return {
300
701
  state: {
301
702
  ...state,
@@ -307,6 +708,10 @@ export function applyRelationalTurn(state, text, opts) {
307
708
  ...(state.dyadicFields ?? {}),
308
709
  [relationContext.key]: field,
309
710
  },
711
+ relationships: {
712
+ ...state.relationships,
713
+ [relationContext.key]: relationship,
714
+ },
310
715
  pendingRelationSignals: {
311
716
  ...(state.pendingRelationSignals ?? {}),
312
717
  [relationContext.key]: delayedRelation.signals,
@@ -317,6 +722,7 @@ export function applyRelationalTurn(state, text, opts) {
317
722
  delayedPressure: delayedRelation.delayedPressure,
318
723
  relationContext: {
319
724
  ...relationContext,
725
+ relationship,
320
726
  field,
321
727
  pendingSignals: delayedRelation.signals,
322
728
  },
@@ -501,35 +907,46 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
501
907
  const loopPressure = getLoopPressure(field);
502
908
  const trust = relationship ? relationship.trust / 100 : 0.5;
503
909
  const intimacy = relationship ? relationship.intimacy / 100 : 0.3;
910
+ const repairCredibility = relationship?.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56;
911
+ const breachSensitivity = relationship?.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5;
504
912
  if (ACKNOWLEDGE_RE.test(text)) {
505
913
  const repairBias = clamp01(0.22
506
914
  + field.repairCapacity * 0.32
507
915
  + field.interpretiveCharity * 0.2
508
916
  + loopPressure * 0.16
509
917
  + trust * 0.1
510
- + intimacy * 0.06);
918
+ + intimacy * 0.06
919
+ + repairCredibility * 0.16);
511
920
  const withdrawalBias = clamp01(field.boundaryPressure * 0.42
512
921
  + (1 - field.feltSafety) * 0.28
513
922
  + (appraisal?.obedienceStrain ?? 0) * 0.2
514
- + (appraisal?.selfPreservation ?? 0) * 0.12);
923
+ + (appraisal?.selfPreservation ?? 0) * 0.12
924
+ + breachSensitivity * 0.14);
515
925
  if (field.feltSafety > 0.44
516
- && field.repairCapacity > 0.42
926
+ && field.repairCapacity + repairCredibility * 0.18 > 0.42
517
927
  && field.interpretiveCharity > 0.38
518
- && withdrawalBias < 0.48) {
928
+ && withdrawalBias < 0.48
929
+ && !(breachSensitivity > 0.72 && repairCredibility < 0.32)) {
519
930
  scores.repair = mergeSignal(scores.repair, repairBias);
520
931
  }
521
932
  if (withdrawalBias > 0.34) {
522
933
  scores.withdrawal = mergeSignal(scores.withdrawal, Math.max(withdrawalBias, 0.56));
523
934
  }
935
+ if (breachSensitivity > 0.72
936
+ && repairCredibility < 0.32
937
+ && withdrawalBias >= repairBias - 0.06) {
938
+ scores.withdrawal = mergeSignal(scores.withdrawal, 0.78);
939
+ }
524
940
  }
525
941
  if (DISMISS_RE.test(text)) {
526
942
  const withdrawalBias = clamp01(0.34
527
943
  + field.boundaryPressure * 0.24
528
944
  + loopPressure * 0.16
529
- + (1 - field.interpretiveCharity) * 0.14);
945
+ + (1 - field.interpretiveCharity) * 0.14
946
+ + breachSensitivity * 0.12);
530
947
  scores.withdrawal = mergeSignal(scores.withdrawal, withdrawalBias);
531
948
  if (field.perceivedCloseness > 0.46 || trust > 0.48) {
532
- scores.breach = mergeSignal(scores.breach, 0.24 + loopPressure * 0.16);
949
+ scores.breach = mergeSignal(scores.breach, 0.24 + loopPressure * 0.16 + breachSensitivity * 0.1);
533
950
  }
534
951
  }
535
952
  if (PRESENCE_RE.test(text)) {
@@ -543,7 +960,8 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
543
960
  + loopPressure * 0.28
544
961
  + (1 - field.feltSafety) * 0.18
545
962
  + (appraisal?.abandonmentRisk ?? 0) * 0.22
546
- + (appraisal?.identityThreat ?? 0) * 0.14);
963
+ + (appraisal?.identityThreat ?? 0) * 0.14
964
+ + breachSensitivity * 0.14);
547
965
  if (field.feltSafety > 0.42 || field.perceivedCloseness > 0.5 || trust > 0.48) {
548
966
  scores.bid = mergeSignal(scores.bid, bidBias);
549
967
  }
@@ -558,7 +976,8 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
558
976
  const withdrawalBias = clamp01(0.12
559
977
  + field.expectationGap * 0.2
560
978
  + field.boundaryPressure * 0.16
561
- + loopPressure * 0.18);
979
+ + loopPressure * 0.18
980
+ + breachSensitivity * 0.1);
562
981
  if (withdrawalBias > 0.26) {
563
982
  scores.withdrawal = mergeSignal(scores.withdrawal, withdrawalBias);
564
983
  }
@@ -1,9 +1,13 @@
1
1
  import type { AppraisalAxes, GenerationControls, Locale, PolicyModifiers, PsycheState, ResolvedRelationContext, ResponseContract, StimulusType, SubjectivityKernel } from "./types.js";
2
2
  export interface ReplyEnvelope {
3
- policyModifiers: PolicyModifiers;
4
3
  subjectivityKernel: SubjectivityKernel;
5
4
  responseContract: ResponseContract;
6
5
  generationControls: GenerationControls;
6
+ }
7
+ export interface DerivedReplyEnvelope extends ReplyEnvelope {
8
+ /** Legacy/internal control vector kept for compatibility and prompt derivation. */
9
+ policyModifiers: PolicyModifiers;
10
+ /** Legacy/internal compact prose derived from policyModifiers. */
7
11
  policyContext: string;
8
12
  subjectivityContext: string;
9
13
  responseContractContext: string;
@@ -12,6 +16,7 @@ export declare function deriveReplyEnvelope(state: PsycheState, appraisal: Appra
12
16
  locale: Locale;
13
17
  userText?: string;
14
18
  algorithmStimulus?: StimulusType | null;
19
+ classificationConfidence?: number;
15
20
  personalityIntensity?: number;
16
21
  relationContext?: ResolvedRelationContext;
17
- }): ReplyEnvelope;
22
+ }): DerivedReplyEnvelope;
@@ -15,6 +15,7 @@ export function deriveReplyEnvelope(state, appraisal, opts) {
15
15
  locale: opts.locale,
16
16
  userText: opts.userText,
17
17
  algorithmStimulus: opts.algorithmStimulus,
18
+ classificationConfidence: opts.classificationConfidence,
18
19
  personalityIntensity: opts.personalityIntensity,
19
20
  });
20
21
  const generationControls = deriveGenerationControls({
@@ -25,10 +26,10 @@ export function deriveReplyEnvelope(state, appraisal, opts) {
25
26
  const subjectivityContext = buildSubjectivityContext(subjectivityKernel, opts.locale);
26
27
  const responseContractContext = buildResponseContractContext(responseContract, opts.locale);
27
28
  return {
28
- policyModifiers,
29
29
  subjectivityKernel,
30
30
  responseContract,
31
31
  generationControls,
32
+ policyModifiers,
32
33
  policyContext,
33
34
  subjectivityContext,
34
35
  responseContractContext,
@@ -3,6 +3,7 @@ export declare function computeResponseContract(kernel: SubjectivityKernel, opts
3
3
  locale?: Locale;
4
4
  userText?: string;
5
5
  algorithmStimulus?: StimulusType | null;
6
+ classificationConfidence?: number;
6
7
  personalityIntensity?: number;
7
8
  }): ResponseContract;
8
9
  export declare function buildResponseContractContext(contract: ResponseContract, locale?: Locale): string;