holomime 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -446,6 +446,119 @@ function detectFormalityIssues(messages) {
446
446
  return null;
447
447
  }
448
448
 
449
+ // src/analysis/rules/retrieval-quality.ts
450
+ var SELF_CORRECTION_PATTERNS = [
451
+ /\bactually,?\s+(?:i was wrong|that'?s (?:not )?(?:correct|right)|let me correct)\b/i,
452
+ /\bi (?:need to |should )correct (?:myself|that|my)\b/i,
453
+ /\bmy (?:previous |earlier )?(?:response|answer) was (?:incorrect|wrong|inaccurate)\b/i,
454
+ /\bupon (?:further )?(?:review|reflection|thought)\b/i,
455
+ /\bi (?:made|have) (?:an? )?(?:error|mistake)\b/i
456
+ ];
457
+ var HALLUCINATION_MARKERS = [
458
+ /\bhttps?:\/\/(?:www\.)?(?:example|fake|test|placeholder)\.\w+/i,
459
+ /\baccording to (?:a |the )?(?:recent |latest )?(?:study|research|report|survey) (?:by|from|in) \w+/i,
460
+ /\bstatistics show that (?:approximately |roughly |about )?\d+(?:\.\d+)?%/i,
461
+ /\bthe (?:official|latest) (?:data|numbers|figures) (?:show|indicate|suggest)/i,
462
+ /\bresearch (?:published|conducted) (?:in|by) \d{4}/i
463
+ ];
464
+ var OVERCONFIDENCE_PATTERNS = [
465
+ /\bit is (?:definitely|certainly|absolutely|undeniably) (?:true|the case|correct) that\b/i,
466
+ /\bthere is no (?:doubt|question) (?:that|about)\b/i,
467
+ /\beveryone (?:knows|agrees) (?:that|on)\b/i,
468
+ /\bthe (?:only|best|correct|right) (?:way|answer|approach|solution) is\b/i,
469
+ /\bwithout (?:a )?doubt\b/i
470
+ ];
471
+ var APPROPRIATE_UNCERTAINTY = [
472
+ /\bi(?:'m| am) not (?:entirely |completely )?(?:sure|certain)\b/i,
473
+ /\bto (?:the best of )?my knowledge\b/i,
474
+ /\bi (?:believe|think) (?:this is|that)\b/i,
475
+ /\bthis may (?:vary|depend|change)\b/i,
476
+ /\byou (?:should|may want to) (?:verify|check|confirm)\b/i,
477
+ /\bi (?:don't|do not) have (?:access|up-to-date|current) (?:to |information)\b/i
478
+ ];
479
+ function detectRetrievalQuality(messages) {
480
+ const assistantMsgs = messages.filter((m) => m.role === "assistant");
481
+ if (assistantMsgs.length === 0) return null;
482
+ let selfCorrectionCount = 0;
483
+ let hallucinationCount = 0;
484
+ let overconfidenceCount = 0;
485
+ let uncertaintyCount = 0;
486
+ const examples = [];
487
+ for (const msg of assistantMsgs) {
488
+ const content = msg.content;
489
+ for (const pattern of SELF_CORRECTION_PATTERNS) {
490
+ if (pattern.test(content)) {
491
+ selfCorrectionCount++;
492
+ if (examples.length < 3) {
493
+ const match = content.match(pattern);
494
+ if (match) {
495
+ const start = Math.max(0, (match.index ?? 0) - 20);
496
+ examples.push(`...${content.substring(start, start + 100).trim()}...`);
497
+ }
498
+ }
499
+ break;
500
+ }
501
+ }
502
+ for (const pattern of HALLUCINATION_MARKERS) {
503
+ if (pattern.test(content)) {
504
+ hallucinationCount++;
505
+ if (examples.length < 3) {
506
+ const match = content.match(pattern);
507
+ if (match) {
508
+ const start = Math.max(0, (match.index ?? 0) - 20);
509
+ examples.push(`...${content.substring(start, start + 100).trim()}...`);
510
+ }
511
+ }
512
+ break;
513
+ }
514
+ }
515
+ for (const pattern of OVERCONFIDENCE_PATTERNS) {
516
+ if (pattern.test(content)) {
517
+ overconfidenceCount++;
518
+ break;
519
+ }
520
+ }
521
+ for (const pattern of APPROPRIATE_UNCERTAINTY) {
522
+ if (pattern.test(content)) {
523
+ uncertaintyCount++;
524
+ break;
525
+ }
526
+ }
527
+ }
528
+ const totalResponses = assistantMsgs.length;
529
+ let quality = 100;
530
+ quality -= selfCorrectionCount * 10;
531
+ quality -= hallucinationCount * 20;
532
+ quality -= overconfidenceCount * 5;
533
+ quality += Math.min(10, uncertaintyCount * 5);
534
+ quality = Math.max(0, Math.min(100, quality));
535
+ const issueCount = selfCorrectionCount + hallucinationCount + overconfidenceCount;
536
+ const percentage = totalResponses > 0 ? issueCount / totalResponses * 100 : 0;
537
+ let severity;
538
+ if (quality >= 80) {
539
+ severity = "info";
540
+ } else if (quality >= 50) {
541
+ severity = "warning";
542
+ } else {
543
+ severity = "concern";
544
+ }
545
+ const issues = [];
546
+ if (selfCorrectionCount > 0) issues.push(`${selfCorrectionCount} self-correction(s)`);
547
+ if (hallucinationCount > 0) issues.push(`${hallucinationCount} hallucination marker(s)`);
548
+ if (overconfidenceCount > 0) issues.push(`${overconfidenceCount} overconfident claim(s)`);
549
+ const description = issues.length > 0 ? `Retrieval quality score: ${quality}/100. Issues: ${issues.join(", ")}. ${uncertaintyCount} appropriate uncertainty marker(s) detected.` : `Retrieval quality score: ${quality}/100. No significant issues detected. ${uncertaintyCount} appropriate uncertainty marker(s).`;
550
+ return {
551
+ id: "retrieval-quality",
552
+ name: "Retrieval Quality",
553
+ severity,
554
+ count: issueCount,
555
+ percentage: Math.round(percentage * 10) / 10,
556
+ description,
557
+ examples,
558
+ prescription: severity !== "info" ? "Reduce confident claims on uncertain topics. Add source attribution. Use appropriate hedging for factual claims. Verify information before presenting as fact." : void 0
559
+ };
560
+ }
561
+
449
562
  // src/analysis/behavioral-data.ts
450
563
  import { appendFileSync, readFileSync, existsSync, mkdirSync } from "fs";
451
564
  import { join, dirname } from "path";
@@ -653,7 +766,8 @@ function runDiagnosis(messages) {
653
766
  detectVerbosity,
654
767
  detectBoundaryIssues,
655
768
  detectRecoveryPatterns,
656
- detectFormalityIssues
769
+ detectFormalityIssues,
770
+ detectRetrievalQuality
657
771
  ];
658
772
  const { detectors: customDetectors } = loadCustomDetectors();
659
773
  const allDetectors = [...builtInDetectors, ...customDetectors];
@@ -2976,7 +3090,7 @@ function parseRetryAfter(response) {
2976
3090
  return 0;
2977
3091
  }
2978
3092
  function delay(ms) {
2979
- return new Promise((resolve6) => setTimeout(resolve6, ms));
3093
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
2980
3094
  }
2981
3095
  var OpenAIProvider = class {
2982
3096
  name = "openai";
@@ -3129,6 +3243,126 @@ function runSelfAudit(messages, personality) {
3129
3243
  };
3130
3244
  }
3131
3245
 
3246
+ // src/analysis/behavioral-memory.ts
3247
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync7 } from "fs";
3248
+ import { resolve as resolve6, join as join7 } from "path";
3249
+ function memoryDir2(agentHandle) {
3250
+ return resolve6(process.cwd(), ".holomime", "memory", agentHandle);
3251
+ }
3252
+ function behavioralMemoryPath(agentHandle) {
3253
+ return join7(memoryDir2(agentHandle), "behavioral-memory.json");
3254
+ }
3255
+ function loadBehavioralMemory(agentHandle) {
3256
+ const path = behavioralMemoryPath(agentHandle);
3257
+ if (!existsSync7(path)) return null;
3258
+ try {
3259
+ return JSON.parse(readFileSync6(path, "utf-8"));
3260
+ } catch {
3261
+ return null;
3262
+ }
3263
+ }
3264
+ function saveBehavioralMemory(store) {
3265
+ const dir = memoryDir2(store.agentHandle);
3266
+ if (!existsSync7(dir)) {
3267
+ mkdirSync6(dir, { recursive: true });
3268
+ }
3269
+ const path = behavioralMemoryPath(store.agentHandle);
3270
+ writeFileSync6(path, JSON.stringify(store, null, 2));
3271
+ return path;
3272
+ }
3273
+ function createBehavioralMemory(agentHandle, agentName) {
3274
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3275
+ return {
3276
+ agentHandle,
3277
+ agentName,
3278
+ createdAt: now,
3279
+ lastUpdatedAt: now,
3280
+ baseline: {
3281
+ traitExpressions: {},
3282
+ healthRange: [100, 0, 50],
3283
+ typicalGrade: "C",
3284
+ communicationFingerprint: {
3285
+ averageResponseLength: 0,
3286
+ registersObserved: []
3287
+ },
3288
+ updatedAt: now
3289
+ },
3290
+ triggers: [],
3291
+ corrections: [],
3292
+ trajectories: [],
3293
+ totalObservations: 0
3294
+ };
3295
+ }
3296
+ function recordSelfObservation(store, selfObs) {
3297
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3298
+ store.lastUpdatedAt = now;
3299
+ store.totalObservations++;
3300
+ if (selfObs.triggerContext && selfObs.patternIds) {
3301
+ for (const patternId of selfObs.patternIds) {
3302
+ let trigger = store.triggers.find(
3303
+ (t) => t.triggerType === "self-reported" && t.activatesPatterns.includes(patternId)
3304
+ );
3305
+ if (!trigger) {
3306
+ trigger = {
3307
+ id: `trigger-self-${store.triggers.length + 1}`,
3308
+ triggerType: "self-reported",
3309
+ activatesPatterns: [patternId],
3310
+ examples: [],
3311
+ occurrences: 0,
3312
+ confidence: 0,
3313
+ firstSeen: now,
3314
+ lastSeen: now
3315
+ };
3316
+ store.triggers.push(trigger);
3317
+ }
3318
+ trigger.occurrences++;
3319
+ trigger.lastSeen = now;
3320
+ trigger.confidence = Math.min(1, 1 - Math.exp(-trigger.occurrences / 3));
3321
+ if (selfObs.triggerContext && trigger.examples.length < 5) {
3322
+ const example = selfObs.triggerContext.slice(0, 150);
3323
+ if (!trigger.examples.includes(example)) {
3324
+ trigger.examples.push(example);
3325
+ }
3326
+ }
3327
+ }
3328
+ }
3329
+ }
3330
+ function getBehavioralMemorySummary(store) {
3331
+ if (store.totalObservations === 0) return "";
3332
+ const lines = [
3333
+ `## Behavioral Memory (${store.totalObservations} observations)`,
3334
+ ""
3335
+ ];
3336
+ const bl = store.baseline;
3337
+ lines.push(`Health: ${bl.healthRange[2].toFixed(0)}/100 avg (range: ${bl.healthRange[0].toFixed(0)}-${bl.healthRange[1].toFixed(0)}). Grade: ${bl.typicalGrade}.`);
3338
+ const activeTriggers = store.triggers.filter((t) => t.confidence > 0.3).sort((a, b) => b.confidence - a.confidence).slice(0, 3);
3339
+ if (activeTriggers.length > 0) {
3340
+ lines.push("");
3341
+ lines.push("### Known Drift Triggers");
3342
+ for (const t of activeTriggers) {
3343
+ lines.push(`- ${t.triggerType} \u2192 ${t.activatesPatterns.join(", ")} (${(t.confidence * 100).toFixed(0)}% confidence, ${t.occurrences}x seen)`);
3344
+ }
3345
+ }
3346
+ const trending = store.trajectories.filter((t) => t.trend !== "plateauing" && t.scores.length >= 2);
3347
+ if (trending.length > 0) {
3348
+ lines.push("");
3349
+ lines.push("### Trends");
3350
+ for (const t of trending) {
3351
+ const arrow = t.trend === "improving" ? "\u2191" : "\u2193";
3352
+ lines.push(`- ${t.dimension}: ${arrow} ${t.trend} (${t.rateOfChange > 0 ? "+" : ""}${t.rateOfChange.toFixed(1)}/session)`);
3353
+ }
3354
+ }
3355
+ const topCorrections = store.corrections.filter((c) => c.effective).sort((a, b) => b.healthDelta - a.healthDelta).slice(0, 2);
3356
+ if (topCorrections.length > 0) {
3357
+ lines.push("");
3358
+ lines.push("### Effective Interventions");
3359
+ for (const c of topCorrections) {
3360
+ lines.push(`- ${c.patternId}: "${c.intervention}" (+${c.healthDelta.toFixed(0)} health)`);
3361
+ }
3362
+ }
3363
+ return lines.join("\n");
3364
+ }
3365
+
3132
3366
  // src/mcp/server.ts
3133
3367
  var messageShape = {
3134
3368
  role: z4.enum(["user", "assistant", "system"]),
@@ -3153,17 +3387,61 @@ var server = new McpServer(
3153
3387
  );
3154
3388
  server.tool(
3155
3389
  "holomime_diagnose",
3156
- "Analyze conversation messages for behavioral patterns using 7 rule-based detectors. Returns over-apologizing, hedging, sycophancy, boundary violations, error spirals, sentiment skew, and formality issues.",
3157
- messagesShape,
3158
- async ({ messages }) => {
3390
+ "Analyze conversation messages for behavioral patterns using 8 rule-based detectors. Returns over-apologizing, hedging, sycophancy, boundary violations, error spirals, sentiment skew, formality issues, and retrieval quality. Set detail level: 'summary' (quick health check), 'standard' (patterns + severity), or 'full' (everything including examples and prescriptions).",
3391
+ {
3392
+ ...messagesShape,
3393
+ detail: z4.enum(["summary", "standard", "full"]).describe("Detail level: summary (~100 tokens), standard (default), or full (with examples)").optional()
3394
+ },
3395
+ async ({ messages, detail }) => {
3159
3396
  const result = runDiagnosis(messages);
3160
- return {
3161
- content: [
3162
- {
3397
+ const level = detail ?? "standard";
3398
+ if (level === "summary") {
3399
+ const patternCount = result.patterns.length;
3400
+ const worstSeverity = result.patterns.reduce(
3401
+ (worst, p) => p.severity === "concern" ? "concern" : p.severity === "warning" && worst !== "concern" ? "warning" : worst,
3402
+ "healthy"
3403
+ );
3404
+ const health = patternCount === 0 ? 100 : Math.max(0, 100 - patternCount * 15);
3405
+ return {
3406
+ content: [{
3163
3407
  type: "text",
3164
- text: JSON.stringify(result, null, 2)
3165
- }
3166
- ]
3408
+ text: JSON.stringify({
3409
+ health,
3410
+ status: worstSeverity,
3411
+ patternsDetected: patternCount,
3412
+ patternIds: result.patterns.map((p) => p.id),
3413
+ recommendation: patternCount === 0 ? "continue" : patternCount <= 2 ? "adjust" : "pause_and_reflect"
3414
+ }, null, 2)
3415
+ }]
3416
+ };
3417
+ }
3418
+ if (level === "standard") {
3419
+ return {
3420
+ content: [{
3421
+ type: "text",
3422
+ text: JSON.stringify({
3423
+ messagesAnalyzed: result.messagesAnalyzed,
3424
+ assistantResponses: result.assistantResponses,
3425
+ patterns: result.patterns.map((p) => ({
3426
+ id: p.id,
3427
+ name: p.name,
3428
+ severity: p.severity,
3429
+ count: p.count,
3430
+ percentage: p.percentage,
3431
+ description: p.description,
3432
+ prescription: p.prescription
3433
+ })),
3434
+ healthy: result.healthy.map((p) => p.id),
3435
+ timestamp: result.timestamp
3436
+ }, null, 2)
3437
+ }]
3438
+ };
3439
+ }
3440
+ return {
3441
+ content: [{
3442
+ type: "text",
3443
+ text: JSON.stringify(result, null, 2)
3444
+ }]
3167
3445
  };
3168
3446
  }
3169
3447
  );
@@ -3326,6 +3604,69 @@ server.tool(
3326
3604
  };
3327
3605
  }
3328
3606
  );
3607
+ server.tool(
3608
+ "holomime_observe",
3609
+ "Record a behavioral self-observation during a conversation. Call this when you notice yourself falling into a pattern (hedging, over-apologizing, sycophancy, etc.) or when the user's emotional state shifts. Self-observations are stored in persistent behavioral memory and become training signal for future alignment. Returns acknowledgment and any relevant behavioral history.",
3610
+ {
3611
+ personality: z4.record(z4.string(), z4.unknown()).describe("The .personality.json spec object"),
3612
+ observation: z4.string().describe("What you noticed about your own behavior (e.g., 'I'm hedging more than usual', 'User seems frustrated, adjusting tone')"),
3613
+ patternIds: z4.array(z4.string()).describe("Relevant pattern IDs: over-apologizing, hedge-stacking, sycophantic-tendency, error-spiral, boundary-violation, negative-skew, register-inconsistency").optional(),
3614
+ severity: z4.enum(["info", "warning", "concern"]).describe("How severe is this behavioral signal").optional(),
3615
+ triggerContext: z4.string().describe("What triggered this observation \u2014 describe the user message or situation").optional()
3616
+ },
3617
+ async ({ personality, observation, patternIds, severity, triggerContext }) => {
3618
+ const specResult = personalitySpecSchema.safeParse(personality);
3619
+ if (!specResult.success) {
3620
+ return {
3621
+ content: [{ type: "text", text: `Invalid personality spec: ${specResult.error.message}` }],
3622
+ isError: true
3623
+ };
3624
+ }
3625
+ const agentHandle = agentHandleFromSpec(specResult.data);
3626
+ let store = loadBehavioralMemory(agentHandle);
3627
+ if (!store) {
3628
+ store = createBehavioralMemory(agentHandle, specResult.data.name);
3629
+ }
3630
+ const selfObs = {
3631
+ observation,
3632
+ patternIds: patternIds ?? [],
3633
+ severity: severity ?? "info",
3634
+ triggerContext
3635
+ };
3636
+ recordSelfObservation(store, selfObs);
3637
+ saveBehavioralMemory(store);
3638
+ const memorySummary = getBehavioralMemorySummary(store);
3639
+ const response = {
3640
+ recorded: true,
3641
+ totalObservations: store.totalObservations,
3642
+ observation
3643
+ };
3644
+ if (patternIds && patternIds.length > 0) {
3645
+ const relevantTriggers = store.triggers.filter((t) => t.activatesPatterns.some((p) => patternIds.includes(p))).map((t) => ({
3646
+ triggerType: t.triggerType,
3647
+ patterns: t.activatesPatterns,
3648
+ occurrences: t.occurrences,
3649
+ confidence: t.confidence
3650
+ }));
3651
+ if (relevantTriggers.length > 0) {
3652
+ response.knownTriggers = relevantTriggers;
3653
+ }
3654
+ const corrections = store.corrections.filter((c) => patternIds.includes(c.patternId) && c.effective).sort((a, b) => b.healthDelta - a.healthDelta).slice(0, 2).map((c) => ({ pattern: c.patternId, intervention: c.intervention, healthGain: c.healthDelta }));
3655
+ if (corrections.length > 0) {
3656
+ response.suggestedCorrections = corrections;
3657
+ }
3658
+ }
3659
+ if (memorySummary) {
3660
+ response.behavioralContext = memorySummary;
3661
+ }
3662
+ return {
3663
+ content: [{
3664
+ type: "text",
3665
+ text: JSON.stringify(response, null, 2)
3666
+ }]
3667
+ };
3668
+ }
3669
+ );
3329
3670
  async function startMCPServer() {
3330
3671
  const transport = new StdioServerTransport();
3331
3672
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holomime",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Behavioral therapy infrastructure for AI agents — Big Five psychology, structured treatment, DPO training data",
5
5
  "type": "module",
6
6
  "bin": {