@psiclawops/hypermem 0.9.3 → 0.9.4

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/INSTALL.md +71 -68
  3. package/README.md +11 -29
  4. package/assets/default-config.json +47 -0
  5. package/bin/hypermem-doctor.mjs +76 -2
  6. package/bin/hypermem-status.mjs +255 -7
  7. package/dist/adaptive-lifecycle.d.ts +39 -0
  8. package/dist/adaptive-lifecycle.d.ts.map +1 -1
  9. package/dist/adaptive-lifecycle.js +87 -9
  10. package/dist/background-indexer.d.ts.map +1 -1
  11. package/dist/background-indexer.js +7 -5
  12. package/dist/compositor.d.ts.map +1 -1
  13. package/dist/compositor.js +239 -20
  14. package/dist/hybrid-retrieval.d.ts +8 -0
  15. package/dist/hybrid-retrieval.d.ts.map +1 -1
  16. package/dist/hybrid-retrieval.js +112 -10
  17. package/dist/index.d.ts +15 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +17 -0
  20. package/dist/message-store.d.ts +62 -1
  21. package/dist/message-store.d.ts.map +1 -1
  22. package/dist/message-store.js +355 -2
  23. package/dist/open-domain.d.ts.map +1 -1
  24. package/dist/open-domain.js +3 -2
  25. package/dist/proactive-pass.d.ts +42 -2
  26. package/dist/proactive-pass.d.ts.map +1 -1
  27. package/dist/proactive-pass.js +294 -39
  28. package/dist/topic-synthesizer.d.ts.map +1 -1
  29. package/dist/topic-synthesizer.js +9 -3
  30. package/dist/types.d.ts +99 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/vector-store.d.ts +10 -1
  33. package/dist/vector-store.d.ts.map +1 -1
  34. package/dist/vector-store.js +45 -9
  35. package/docs/DIAGNOSTICS.md +87 -0
  36. package/docs/INTEGRATION_VALIDATION.md +40 -1
  37. package/docs/ROADMAP.md +25 -12
  38. package/docs/TUNING.md +45 -4
  39. package/install.sh +5 -60
  40. package/memory-plugin/dist/index.js +192 -0
  41. package/memory-plugin/openclaw.plugin.json +199 -2
  42. package/memory-plugin/package.json +2 -2
  43. package/package.json +21 -8
  44. package/plugin/dist/index.d.ts +2 -0
  45. package/plugin/dist/index.d.ts.map +1 -1
  46. package/plugin/dist/index.js +175 -8
  47. package/plugin/dist/index.js.map +1 -1
  48. package/plugin/openclaw.plugin.json +199 -2
  49. package/plugin/package.json +2 -2
  50. package/scripts/install-packed-runtime.mjs +99 -0
  51. package/scripts/install-runtime.mjs +164 -4
@@ -39,9 +39,28 @@ const flags = {
39
39
  health: args.includes('--health'),
40
40
  master: args.includes('--master') || args.includes('--planks'),
41
41
  help: args.includes('--help') || args.includes('-h'),
42
+ repairReferencedNoise: args.includes('--repair-referenced-noise'),
43
+ fleetRepair: args.includes('--fleet-repair'),
42
44
  agent: null,
43
45
  };
44
46
 
47
+ function numericArg(name, fallback) {
48
+ const idx = args.indexOf(name);
49
+ if (idx === -1 || !args[idx + 1]) return fallback;
50
+ const value = Number(args[idx + 1]);
51
+ return Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
52
+ }
53
+
54
+ const MAX_REPAIR_LIMIT = 500;
55
+ const repairLimit = Math.min(numericArg('--repair-limit', 100), MAX_REPAIR_LIMIT);
56
+ const DEFAULT_FLEET_AGENT_LIMIT = 20;
57
+ const DEFAULT_FLEET_CANDIDATES_PER_CONVERSATION = 500;
58
+ const DEFAULT_TOP_AGENTS = 10;
59
+ const fleetAgentLimit = numericArg('--fleet-agent-limit', DEFAULT_FLEET_AGENT_LIMIT);
60
+ const maxAgents = numericArg('--max-agents', fleetAgentLimit);
61
+ const maxCandidatesPerConversation = numericArg('--max-candidates-per-conversation', DEFAULT_FLEET_CANDIDATES_PER_CONVERSATION);
62
+ const topAgentsLimit = Math.max(1, numericArg('--top-agents', DEFAULT_TOP_AGENTS));
63
+
45
64
  const agentIdx = args.indexOf('--agent');
46
65
  if (agentIdx !== -1 && args[agentIdx + 1]) {
47
66
  flags.agent = args[agentIdx + 1];
@@ -59,11 +78,25 @@ Options:
59
78
  --agent <id> Scope metrics to a specific agent
60
79
  --json Output raw JSON instead of formatted summary
61
80
  --health Health checks only (exits 1 if any check fails)
81
+ --repair-referenced-noise Run conservative tree-safe referenced-noise compaction
82
+ --repair-limit <n> Max referenced-noise mutations per run (default: 100, capped at 500)
83
+ --fleet-repair Allow referenced-noise repair without --agent (off by default)
84
+ --fleet-agent-limit <n> Max agent DBs scanned in fleet maintenance summary (default: ${DEFAULT_FLEET_AGENT_LIMIT})
85
+ --max-agents <n> Alias for --fleet-agent-limit
86
+ --top-agents <n> Max agents shown in maintenance top list (default: ${DEFAULT_TOP_AGENTS})
87
+ --max-candidates-per-conversation <n>
88
+ Max noise candidates inspected per conversation in fleet mode (default: ${DEFAULT_FLEET_CANDIDATES_PER_CONVERSATION})
62
89
  -h, --help Show this help
63
90
  `);
64
91
  process.exit(0);
65
92
  }
66
93
 
94
+
95
+ if (flags.repairReferencedNoise && !flags.agent && !flags.fleetRepair) {
96
+ console.error('Error: --repair-referenced-noise requires --agent <id>. Use --fleet-repair only for explicit fleet-wide repair.');
97
+ process.exit(2);
98
+ }
99
+
67
100
  // ── Resolve config and data directory ────────────────────────────
68
101
  function readJsonIfExists(filePath) {
69
102
  if (!existsSync(filePath)) return null;
@@ -141,6 +174,38 @@ function resolveRuntimeConfig() {
141
174
  const runtime = resolveRuntimeConfig();
142
175
  const dataDir = runtime.dataDir;
143
176
 
177
+ function nestedValue(obj, dotted) {
178
+ return dotted.split('.').reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj);
179
+ }
180
+
181
+ function collectRecallSurfaceConfig(config) {
182
+ const checks = [
183
+ ['turnBudget.budgetFraction', 0.6],
184
+ ['turnBudget.minContextFraction', 0.18],
185
+ ['warming.protectedFloorEnabled', true],
186
+ ['warming.shapedWarmupDecay', true],
187
+ ['adjacency.enabled', true],
188
+ ['adjacency.boostMultiplier', 1.3],
189
+ ['adjacency.maxLookback', 5],
190
+ ['adjacency.maxClockDeltaMin', 10],
191
+ ['adjacency.evictionGuardMessages', 3],
192
+ ['adjacency.evictionGuardTokenCap', 4000],
193
+ ];
194
+ const compositor = config.compositor ?? {};
195
+ const items = checks.map(([pathKey, expected]) => {
196
+ const actual = nestedValue(compositor, pathKey);
197
+ return { path: `compositor.${pathKey}`, expected, actual: actual ?? null, ok: actual === expected };
198
+ });
199
+ const missing = items.filter(item => !item.ok);
200
+ return {
201
+ status: missing.length === 0 ? 'ok' : 'attention',
202
+ ok: items.length - missing.length,
203
+ total: items.length,
204
+ missing,
205
+ items,
206
+ };
207
+ }
208
+
144
209
  if (!existsSync(dataDir)) {
145
210
  console.error(`Error: data directory not found: ${dataDir}`);
146
211
  console.error('Is HyperMem installed? Set HYPERMEM_DATA_DIR if using a custom path.');
@@ -216,12 +281,121 @@ function findMessageDb(agentId) {
216
281
  return null;
217
282
  }
218
283
 
219
- function collectMessageStats(agentId) {
284
+
285
+ function listMessageDbs(agentId, limit = Infinity) {
220
286
  const agentsDir = join(dataDir, 'agents');
221
- if (!existsSync(agentsDir)) return { agentsWithMessages: 0, totalMessages: 0, newestMessageAt: null };
287
+ if (!existsSync(agentsDir)) return { items: [], totalAvailable: 0, skipped: 0, truncated: false };
222
288
  const agents = agentId ? [agentId] : readdirSync(agentsDir, { withFileTypes: true })
289
+ .filter(d => d.isDirectory())
290
+ .map(d => d.name)
291
+ .sort();
292
+ const existing = agents
293
+ .map(agent => ({ agent, path: join(agentsDir, agent, 'messages.db') }))
294
+ .filter(item => existsSync(item.path));
295
+ const boundedLimit = Number.isFinite(limit) ? Math.max(0, limit) : Infinity;
296
+ const items = existing.slice(0, boundedLimit);
297
+ return {
298
+ items,
299
+ totalAvailable: existing.length,
300
+ skipped: Math.max(0, existing.length - items.length),
301
+ truncated: existing.length > items.length,
302
+ };
303
+ }
304
+
305
+ function emptyReferencedNoiseHealth() {
306
+ return {
307
+ status: 'ok',
308
+ agentsScanned: 0,
309
+ conversationsScanned: 0,
310
+ noiseCandidates: 0,
311
+ referencedNoise: 0,
312
+ parentReferencedNoise: 0,
313
+ contextReferencedNoise: 0,
314
+ snapshotReferencedNoise: 0,
315
+ otherReferencedNoise: 0,
316
+ topAgents: [],
317
+ sampleRefs: [],
318
+ repair: null,
319
+ truncated: false,
320
+ agentsAvailable: 0,
321
+ agentsSkipped: 0,
322
+ caps: null,
323
+ };
324
+ }
325
+
326
+ async function collectReferencedNoiseHealth(agentId, repair = false) {
327
+ const health = emptyReferencedNoiseHealth();
328
+ const fleetMode = !agentId;
329
+ const agentLimit = fleetMode ? maxAgents : Infinity;
330
+ const candidateLimit = fleetMode ? maxCandidatesPerConversation : Infinity;
331
+ health.caps = fleetMode
332
+ ? { agents: agentLimit, candidatesPerConversation: candidateLimit, topAgents: topAgentsLimit }
333
+ : null;
334
+ const distPath = join(root, 'dist', 'proactive-pass.js');
335
+ if (!existsSync(distPath)) {
336
+ health.status = 'unknown';
337
+ health.error = 'dist/proactive-pass.js missing; run npm run build';
338
+ return health;
339
+ }
340
+ const { collectReferencedNoiseDebt, runTreeSafeNoiseCompaction } = await import(distPath);
341
+ const perAgent = [];
342
+ const repairs = [];
343
+
344
+ const messageDbs = listMessageDbs(agentId, agentLimit);
345
+ health.agentsAvailable = messageDbs.totalAvailable;
346
+ health.agentsSkipped = messageDbs.skipped;
347
+ health.truncated = messageDbs.truncated;
348
+
349
+ for (const item of messageDbs.items) {
350
+ const db = openDb(item.path, `${item.agent} messages.db`, false);
351
+ if (!db) continue;
352
+ try {
353
+ if (repair) {
354
+ const repaired = runTreeSafeNoiseCompaction(db, undefined, 20, repairLimit);
355
+ repairs.push({ agent: item.agent, ...repaired });
356
+ }
357
+ const debt = collectReferencedNoiseDebt(db, undefined, 20, candidateLimit);
358
+ health.agentsScanned += 1;
359
+ health.conversationsScanned += debt.conversationsScanned;
360
+ health.noiseCandidates += debt.noiseCandidates;
361
+ health.referencedNoise += debt.referencedNoise;
362
+ health.parentReferencedNoise += debt.parentReferencedNoise;
363
+ health.contextReferencedNoise += debt.contextReferencedNoise;
364
+ health.snapshotReferencedNoise += debt.snapshotReferencedNoise;
365
+ health.otherReferencedNoise += debt.otherReferencedNoise;
366
+ for (const ref of debt.sampleRefs) if (health.sampleRefs.length < 12) health.sampleRefs.push(`${item.agent}:${ref}`);
367
+ if (debt.referencedNoise > 0) perAgent.push({ agent: item.agent, referencedNoise: debt.referencedNoise, parentReferencedNoise: debt.parentReferencedNoise });
368
+ } finally {
369
+ try { db.close(); } catch {}
370
+ }
371
+ }
372
+
373
+ health.topAgents = perAgent
374
+ .sort((a, b) => b.referencedNoise - a.referencedNoise)
375
+ .slice(0, topAgentsLimit);
376
+ health.status = health.referencedNoise > 0 ? 'attention' : 'ok';
377
+ if (repair) {
378
+ health.repair = {
379
+ limit: repairLimit,
380
+ agentsAttempted: repairs.length,
381
+ deleted: repairs.reduce((sum, item) => sum + (item.deleted || 0), 0),
382
+ reparented: repairs.reduce((sum, item) => sum + (item.reparented || 0), 0),
383
+ skippedBlocked: repairs.reduce((sum, item) => sum + (item.skippedBlocked || 0), 0),
384
+ skippedRoot: repairs.reduce((sum, item) => sum + (item.skippedRoot || 0), 0),
385
+ failures: repairs.filter(item => item.fkCheck && item.fkCheck !== 'none'),
386
+ };
387
+ }
388
+ return health;
389
+ }
390
+
391
+ function collectMessageStats(agentId) {
392
+ const agentsDir = join(dataDir, 'agents');
393
+ if (!existsSync(agentsDir)) return { agentsWithMessages: 0, totalMessages: 0, newestMessageAt: null, agentsAvailable: 0, agentsSkipped: 0, truncated: false };
394
+ const allAgents = agentId ? [agentId] : readdirSync(agentsDir, { withFileTypes: true })
223
395
  .filter(d => d.isDirectory())
224
396
  .map(d => d.name);
397
+ const limit = agentId ? Infinity : maxAgents;
398
+ const agents = allAgents.slice(0, Number.isFinite(limit) ? Math.max(0, limit) : undefined);
225
399
  let agentsWithMessages = 0;
226
400
  let totalMessages = 0;
227
401
  let newestMessageAt = null;
@@ -237,7 +411,14 @@ function collectMessageStats(agentId) {
237
411
  if (newest && (!newestMessageAt || newest > newestMessageAt)) newestMessageAt = newest;
238
412
  try { db.close(); } catch {}
239
413
  }
240
- return { agentsWithMessages, totalMessages, newestMessageAt };
414
+ return {
415
+ agentsWithMessages,
416
+ totalMessages,
417
+ newestMessageAt,
418
+ agentsAvailable: allAgents.length,
419
+ agentsSkipped: Math.max(0, allAgents.length - agents.length),
420
+ truncated: allAgents.length > agents.length,
421
+ };
241
422
  }
242
423
 
243
424
  function getVectorDimensions(vectorDb, tableName) {
@@ -252,7 +433,35 @@ function getQuickCheck(db) {
252
433
  return row ? Object.values(row)[0] : 'unknown';
253
434
  }
254
435
 
255
- function collectMasterHealth(libraryDb, vectorDb, mainDb) {
436
+ function fileContains(filePath, text) {
437
+ try {
438
+ return existsSync(filePath) && readFileSync(filePath, 'utf8').includes(text);
439
+ } catch {
440
+ return false;
441
+ }
442
+ }
443
+
444
+ function collectHistoryQueryHealth(mainDb) {
445
+ const coreDts = join(root, 'dist', 'index.d.ts');
446
+ const memoryPluginJs = join(root, 'memory-plugin', 'dist', 'index.js');
447
+ const coreApi = fileContains(coreDts, 'queryHistory(query: HistoryQuery): HistoryQueryResult');
448
+ const pluginTool = fileContains(memoryPluginJs, 'history_query') && fileContains(memoryPluginJs, 'history.query');
449
+ const telemetry = fileContains(memoryPluginJs, 'history-query') && fileContains(memoryPluginJs, 'HYPERMEM_TELEMETRY_PATH');
450
+ const messagesTable = safeGet(mainDb, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'messages'`)?.sql ?? '';
451
+ const fencesTable = safeGet(mainDb, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'compaction_fences'`)?.sql ?? '';
452
+ const schemaReady = ['tool_calls', 'tool_results', 'context_id', 'topic_id'].every(col => messagesTable.includes(col))
453
+ && fencesTable.includes('fence_message_id');
454
+
455
+ return {
456
+ status: coreApi && pluginTool && telemetry && schemaReady ? 'ok' : 'fail',
457
+ coreApi,
458
+ pluginTool,
459
+ telemetry,
460
+ schemaReady,
461
+ };
462
+ }
463
+
464
+ async function collectMasterHealth(libraryDb, vectorDb, mainDb) {
256
465
  const agentClause = flags.agent ? 'AND agent_id = ?' : '';
257
466
  const params = flags.agent ? [flags.agent] : [];
258
467
 
@@ -374,11 +583,15 @@ function collectMasterHealth(libraryDb, vectorDb, mainDb) {
374
583
  const dbStatus = libraryQuickCheck === 'ok' && (vectorQuickCheck === 'ok' || vectorQuickCheck === 'missing') && (mainQuickCheck === 'ok' || mainQuickCheck === 'missing') ? 'ok' : 'fail';
375
584
 
376
585
  const messageStats = collectMessageStats(flags.agent);
586
+ const historyQueryHealth = collectHistoryQueryHealth(mainDb);
587
+ const recallSurfaceConfig = collectRecallSurfaceConfig(runtime.config);
377
588
  const totalTurns = safeGet(libraryDb, `SELECT COUNT(*) AS count FROM output_metrics WHERE 1=1 ${agentClause}`, params)?.count ?? 0;
378
589
  const avgLatency = safeGet(libraryDb, `SELECT AVG(latency_ms) AS value FROM output_metrics WHERE latency_ms IS NOT NULL ${agentClause}`, params)?.value ?? null;
379
590
  const avgInput = safeGet(libraryDb, `SELECT AVG(input_tokens) AS value FROM output_metrics WHERE input_tokens IS NOT NULL ${agentClause}`, params)?.value ?? null;
380
591
  const avgOutput = safeGet(libraryDb, `SELECT AVG(output_tokens) AS value FROM output_metrics WHERE output_tokens IS NOT NULL ${agentClause}`, params)?.value ?? null;
381
592
 
593
+ const referencedNoiseHealth = await collectReferencedNoiseHealth(flags.agent, flags.repairReferencedNoise);
594
+
382
595
  const issues = [];
383
596
  if (dbStatus === 'fail') issues.push('database quick_check failed');
384
597
  if (dimensionMatches === 'fail') issues.push('vector table dimensions do not match configured embedding dimensions');
@@ -386,8 +599,12 @@ function collectMasterHealth(libraryDb, vectorDb, mainDb) {
386
599
  if (episodeStatus === 'warn') issues.push(`episode vector coverage low (${episodeCoverage}%)`);
387
600
  if (knowledgeStatus === 'warn') issues.push(`knowledge vector coverage low (${knowledgeCoverage}%)`);
388
601
  if (!vectorDb) issues.push('shared vectors.db missing');
602
+ if (historyQueryHealth.status !== 'ok') issues.push('history.query surface incomplete');
603
+ if (recallSurfaceConfig.status !== 'ok') issues.push(`0.9.4 recall-surface config incomplete (${recallSurfaceConfig.ok}/${recallSurfaceConfig.total} recommended knobs)`);
604
+ if (referencedNoiseHealth.status === 'attention') issues.push(`referenced-noise compaction debt (${referencedNoiseHealth.referencedNoise} messages${referencedNoiseHealth.truncated ? ', bounded fleet sample' : ''})`);
605
+ if (referencedNoiseHealth.status === 'unknown') issues.push(`referenced-noise health unknown: ${referencedNoiseHealth.error}`);
389
606
 
390
- const overall = issues.length === 0 ? 'healthy' : (dbStatus === 'fail' || dimensionMatches === 'fail' ? 'degraded' : 'attention');
607
+ const overall = issues.length === 0 ? 'healthy' : (dbStatus === 'fail' || dimensionMatches === 'fail' || historyQueryHealth.status === 'fail' ? 'degraded' : 'attention');
391
608
 
392
609
  return {
393
610
  snapshotAt: new Date().toISOString(),
@@ -405,6 +622,7 @@ function collectMasterHealth(libraryDb, vectorDb, mainDb) {
405
622
  baseUrl: runtime.embedding.openaiBaseUrl ?? runtime.embedding.ollamaUrl ?? runtime.embedding.geminiBaseUrl ?? null,
406
623
  },
407
624
  reranker: runtime.config.reranker ? stripSecretFields(runtime.config.reranker) : null,
625
+ recallSurface: recallSurfaceConfig,
408
626
  },
409
627
  databases: {
410
628
  library: { path: libraryDbPath, ...fileInfo(libraryDbPath), quickCheck: libraryQuickCheck },
@@ -437,6 +655,12 @@ function collectMasterHealth(libraryDb, vectorDb, mainDb) {
437
655
  avgOutputTokens: avgOutput == null ? null : Math.round(avgOutput),
438
656
  semanticDiagnosticsPersisted: false,
439
657
  },
658
+ maintenance: {
659
+ referencedNoise: referencedNoiseHealth,
660
+ },
661
+ querySurfaces: {
662
+ historyQuery: historyQueryHealth,
663
+ },
440
664
  };
441
665
  }
442
666
 
@@ -461,6 +685,11 @@ function formatMasterHealth(h) {
461
685
  lines.push(` embed: ${h.config.embedding.provider ?? 'unknown'} / ${h.config.embedding.model ?? 'unknown'}${h.config.embedding.dimensions ? ` (${h.config.embedding.dimensions}d)` : ''}${h.config.embedding.batchSize ? ` batch=${h.config.embedding.batchSize}` : ''}`);
462
686
  if (h.config.embedding.baseUrl) lines.push(` base: ${h.config.embedding.baseUrl}`);
463
687
  lines.push(` rerank: ${h.config.reranker?.provider ?? 'none'}`);
688
+ const rs = h.config.recallSurface;
689
+ lines.push(` recall: ${icon(rs.status === 'ok' ? 'ok' : 'warn')} 0.9.4 surface ${rs.ok}/${rs.total} recommended knobs`);
690
+ for (const item of rs.missing.slice(0, 5)) {
691
+ lines.push(` missing ${item.path}=${JSON.stringify(item.expected)}`);
692
+ }
464
693
 
465
694
  lines.push('');
466
695
  lines.push('## Databases');
@@ -475,7 +704,7 @@ function formatMasterHealth(h) {
475
704
  lines.push(` episodes: ${h.memory.episodes.eligible.toLocaleString()} significant / ${h.memory.episodes.total.toLocaleString()} total`);
476
705
  lines.push(` knowledge:${String(h.memory.knowledge.active.toLocaleString()).padStart(7)} active`);
477
706
  lines.push(` chunks: ${h.memory.docChunks.total.toLocaleString()}`);
478
- lines.push(` messages: ${h.memory.messages.totalMessages.toLocaleString()} across ${h.memory.messages.agentsWithMessages} agent DBs${h.memory.messages.newestMessageAt ? `, newest ${h.memory.messages.newestMessageAt}` : ''}`);
707
+ lines.push(` messages: ${h.memory.messages.totalMessages.toLocaleString()} across ${h.memory.messages.agentsWithMessages} agent DBs${h.memory.messages.truncated ? `/${h.memory.messages.agentsAvailable} bounded` : ''}${h.memory.messages.newestMessageAt ? `, newest ${h.memory.messages.newestMessageAt}` : ''}`);
479
708
 
480
709
  lines.push('');
481
710
  lines.push('## Vector coverage');
@@ -503,6 +732,25 @@ function formatMasterHealth(h) {
503
732
  }
504
733
  lines.push(' semantic recall counts: runtime log only, not persisted yet');
505
734
 
735
+ lines.push('');
736
+ lines.push('## Maintenance');
737
+ const rn = h.maintenance.referencedNoise;
738
+ lines.push(` ${icon(rn.status === 'ok' ? 'ok' : rn.status === 'attention' ? 'warn' : 'fail')} referenced-noise debt=${rn.referencedNoise.toLocaleString()} parent=${rn.parentReferencedNoise.toLocaleString()} context=${rn.contextReferencedNoise.toLocaleString()} snapshot=${rn.snapshotReferencedNoise.toLocaleString()} agents=${rn.agentsScanned}${rn.truncated ? `/${rn.agentsAvailable} bounded` : ''}`);
739
+ if (rn.truncated) {
740
+ lines.push(` fleet scan bounded: skipped ${rn.agentsSkipped} agent DBs; rerun with --fleet-agent-limit ${rn.agentsAvailable} for a full scan`);
741
+ }
742
+ if (rn.topAgents.length > 0) {
743
+ lines.push(` top agents: ${rn.topAgents.map(item => `${item.agent}=${Number(item.referencedNoise).toLocaleString()}`).join(' ')}`);
744
+ }
745
+ if (rn.repair) {
746
+ lines.push(` repair: limit=${rn.repair.limit} deleted=${rn.repair.deleted} reparented=${rn.repair.reparented} skippedBlocked=${rn.repair.skippedBlocked} skippedRoot=${rn.repair.skippedRoot}`);
747
+ }
748
+
749
+ lines.push('');
750
+ lines.push('## Query surfaces');
751
+ const hq = h.querySurfaces.historyQuery;
752
+ lines.push(` ${icon(hq.status)} history.query coreApi=${hq.coreApi ? 'yes' : 'no'} pluginTool=${hq.pluginTool ? 'yes' : 'no'} telemetry=${hq.telemetry ? 'yes' : 'no'} schema=${hq.schemaReady ? 'yes' : 'no'}`);
753
+
506
754
  return lines.join('\n');
507
755
  }
508
756
 
@@ -538,7 +786,7 @@ if (vectorDb) {
538
786
 
539
787
  try {
540
788
  if (flags.master) {
541
- const master = collectMasterHealth(libraryDb, vectorDb, mainDb);
789
+ const master = await collectMasterHealth(libraryDb, vectorDb, mainDb);
542
790
  if (flags.json) console.log(JSON.stringify(master, null, 2));
543
791
  else console.log(formatMasterHealth(master));
544
792
  process.exit(master.overall === 'degraded' ? 1 : 0);
@@ -16,6 +16,8 @@ export interface AdaptiveLifecycleInput {
16
16
  pressureFraction?: number;
17
17
  /** Number of user turns observed in the session. */
18
18
  userTurnCount?: number;
19
+ /** Number of topic-bearing (substantive) user turns observed in the session. */
20
+ topicBearingTurnCount?: number;
19
21
  /** True when the incoming user turn explicitly starts with /new. */
20
22
  explicitNewSession?: boolean;
21
23
  /** Topic-shift confidence from the detector, 0..1. */
@@ -27,6 +29,33 @@ export interface AdaptiveLifecycleInput {
27
29
  /** Parent-session user turns observed when the fork was prepared. */
28
30
  forkedParentUserTurnCount?: number;
29
31
  }
32
+ /**
33
+ * Minimal message shape for the lightweight topic-bearing classifier.
34
+ * Used by compose paths to count substantive user turns from existing
35
+ * message data without content telemetry or schema migration.
36
+ */
37
+ export interface TopicBearingMessageLike {
38
+ role: string;
39
+ textContent: string | null;
40
+ isHeartbeat?: boolean;
41
+ }
42
+ /**
43
+ * Determine whether a user turn is "topic-bearing" (substantive).
44
+ *
45
+ * Heartbeat, empty, and small-talk turns are NOT topic-bearing and do not
46
+ * extend the warmup window. This is intentionally a lightweight heuristic:
47
+ * no topic-detector architecture change, no model calls.
48
+ *
49
+ * Mirrors the plugin afterTurn gradient semantics so compose-path band
50
+ * decisions stay consistent with afterTurn-path band decisions.
51
+ */
52
+ export declare function isTopicBearingTurn(msg: TopicBearingMessageLike): boolean;
53
+ /**
54
+ * Count topic-bearing turns in a message array.
55
+ *
56
+ * Pure helper: no side effects, no store access. Returns 0 for empty arrays.
57
+ */
58
+ export declare function countTopicBearingTurns(messages: TopicBearingMessageLike[]): number;
30
59
  /**
31
60
  * Eviction-pipeline step labels. The order in `AdaptiveEvictionPlan.steps`
32
61
  * is the order the compose-window cluster-drop path should attempt them.
@@ -47,6 +76,14 @@ export interface AdaptiveEvictionPlan {
47
76
  /** Ballast-reduction steps run before any cluster drop. Always true today. */
48
77
  preferBallastFirst: boolean;
49
78
  }
79
+ interface ProtectedWarmingMetadata {
80
+ /** Whether warming is currently protected by a hard floor. True for high/critical. */
81
+ isProtected: boolean;
82
+ /** The floor value warming cannot drop below. 0 when not protected. */
83
+ floor: number;
84
+ /** Human-readable explanation. */
85
+ reason: string;
86
+ }
50
87
  export interface AdaptiveLifecyclePolicy {
51
88
  band: AdaptiveLifecycleBand;
52
89
  pressureFraction: number;
@@ -59,6 +96,7 @@ export interface AdaptiveLifecyclePolicy {
59
96
  enableTopicCentroidEviction: boolean;
60
97
  triggerProactiveCompaction: boolean;
61
98
  evictionPlan: AdaptiveEvictionPlan;
99
+ protectedWarmingMetadata: ProtectedWarmingMetadata;
62
100
  reasons: string[];
63
101
  }
64
102
  /**
@@ -78,4 +116,5 @@ export declare function resolveAdaptiveEvictionPlan(band: AdaptiveLifecycleBand)
78
116
  * Resolve the adaptive lifecycle posture for one compose/afterTurn cycle.
79
117
  */
80
118
  export declare function resolveAdaptiveLifecyclePolicy(input?: AdaptiveLifecycleInput): AdaptiveLifecyclePolicy;
119
+ export {};
81
120
  //# sourceMappingURL=adaptive-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adaptive-lifecycle.d.ts","sourceRoot":"","sources":["../src/adaptive-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,UAAU,CAAC;AAEf,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sDAAsD;IACtD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wEAAwE;IACxE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,qEAAqE;IACrE,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf,qBAAqB,GACrB,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,wEAAwE;IACxE,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8EAA8E;IAC9E,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB,EAAE,MAAM,CAAC;IAClC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2BAA2B,EAAE,OAAO,CAAC;IACrC,0BAA0B,EAAE,OAAO,CAAC;IACpC,YAAY,EAAE,oBAAoB,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAsB7F;AAyGD;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,GAAE,sBAA2B,GACjC,uBAAuB,CAoBzB"}
1
+ {"version":3,"file":"adaptive-lifecycle.d.ts","sourceRoot":"","sources":["../src/adaptive-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,UAAU,CAAC;AAEf,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sDAAsD;IACtD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wEAAwE;IACxE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,qEAAqE;IACrE,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAmBxE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,uBAAuB,EAAE,GAAG,MAAM,CAElF;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf,qBAAqB,GACrB,kBAAkB,GAClB,0BAA0B,GAC1B,qBAAqB,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,wEAAwE;IACxE,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8EAA8E;IAC9E,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,sFAAsF;IACtF,WAAW,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB,EAAE,MAAM,CAAC;IAClC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2BAA2B,EAAE,OAAO,CAAC;IACrC,0BAA0B,EAAE,OAAO,CAAC;IACpC,YAAY,EAAE,oBAAoB,CAAC;IACnC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAsB7F;AA4ID;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,GAAE,sBAA2B,GACjC,uBAAuB,CAqBzB"}
@@ -6,6 +6,45 @@
6
6
  * recall, trim, compaction, and eviction posture from the same pressure band
7
7
  * so 0.9.0 does not grow another independent trim brain.
8
8
  */
9
+ import { stripMessageMetadata } from './topic-detector.js';
10
+ /**
11
+ * Determine whether a user turn is "topic-bearing" (substantive).
12
+ *
13
+ * Heartbeat, empty, and small-talk turns are NOT topic-bearing and do not
14
+ * extend the warmup window. This is intentionally a lightweight heuristic:
15
+ * no topic-detector architecture change, no model calls.
16
+ *
17
+ * Mirrors the plugin afterTurn gradient semantics so compose-path band
18
+ * decisions stay consistent with afterTurn-path band decisions.
19
+ */
20
+ export function isTopicBearingTurn(msg) {
21
+ if (msg.isHeartbeat)
22
+ return false;
23
+ if (msg.role !== 'user')
24
+ return false;
25
+ const text = stripMessageMetadata(msg.textContent ?? '').trim();
26
+ // Empty turn
27
+ if (text.length === 0)
28
+ return false;
29
+ // Small-talk: very short generic acknowledgments
30
+ if (text.length < 15) {
31
+ if (/^(ok|okay|yes|no|thanks|thank\s+you|got\s+it|sure|yep|nah|nope|alright|cool|nice|great|good|k|kk|heartbeat[_\s-]*ok|👍|👎|hi|hello|hey)[.!]*$/iu.test(text)) {
32
+ return false;
33
+ }
34
+ }
35
+ // Pure punctuation or emoji
36
+ if (/^[\s\p{P}\p{Emoji}]+$/u.test(text))
37
+ return false;
38
+ return true;
39
+ }
40
+ /**
41
+ * Count topic-bearing turns in a message array.
42
+ *
43
+ * Pure helper: no side effects, no store access. Returns 0 for empty arrays.
44
+ */
45
+ export function countTopicBearingTurns(messages) {
46
+ return messages.filter(isTopicBearingTurn).length;
47
+ }
9
48
  const BASELINE_EVICTION_STEPS = Object.freeze([
10
49
  'tool-gradient',
11
50
  'oversized-artifacts',
@@ -60,9 +99,9 @@ const PRESSURE_BANDS = Object.freeze({
60
99
  highMax: 0.85,
61
100
  });
62
101
  const WARMING_BY_BAND = Object.freeze({
63
- bootstrap: 0.55,
64
- warmup: 0.48,
65
- steady: 0.40,
102
+ bootstrap: 0.62,
103
+ warmup: 0.55,
104
+ steady: 0.45,
66
105
  elevated: 0.34,
67
106
  high: 0.28,
68
107
  critical: 0.20,
@@ -103,6 +142,9 @@ function isTopicShift(input) {
103
142
  }
104
143
  function classifyBand(input, pressure) {
105
144
  const userTurns = Math.max(0, Math.floor(input.userTurnCount ?? 0));
145
+ const topicBearingTurns = input.topicBearingTurnCount != null
146
+ ? Math.max(0, Math.floor(input.topicBearingTurnCount))
147
+ : null;
106
148
  if (input.explicitNewSession)
107
149
  return 'bootstrap';
108
150
  if (input.forkedContext) {
@@ -119,8 +161,19 @@ function classifyBand(input, pressure) {
119
161
  }
120
162
  if (userTurns === 0)
121
163
  return 'bootstrap';
122
- if (userTurns <= 4 && pressure < PRESSURE_BANDS.elevatedMax)
123
- return 'warmup';
164
+ // Packet 2: topic-bearing warmup decay. When topicBearingTurnCount is
165
+ // provided, warmup extends to 8 substantive turns so heartbeat/empty/
166
+ // small-talk turns do not prematurely graduate the session. Falls back
167
+ // to the historical 4 raw-turn window when topic-bearing count is
168
+ // unavailable (backward compatibility).
169
+ if (topicBearingTurns != null) {
170
+ if (topicBearingTurns <= 8 && pressure < PRESSURE_BANDS.elevatedMax)
171
+ return 'warmup';
172
+ }
173
+ else {
174
+ if (userTurns <= 4 && pressure < PRESSURE_BANDS.elevatedMax)
175
+ return 'warmup';
176
+ }
124
177
  if (pressure < PRESSURE_BANDS.steadyMax)
125
178
  return 'steady';
126
179
  if (pressure < PRESSURE_BANDS.elevatedMax)
@@ -131,18 +184,36 @@ function classifyBand(input, pressure) {
131
184
  }
132
185
  function smartRecallMultiplier(input, band) {
133
186
  if (input.explicitNewSession || isTopicShift(input))
134
- return 1.5;
187
+ return 1.75;
135
188
  if (band === 'bootstrap' || band === 'warmup')
136
- return 1.25;
189
+ return 1.4;
137
190
  if (band === 'high')
138
191
  return 0.85;
139
192
  if (band === 'critical')
140
193
  return 0.65;
141
194
  return 1.0;
142
195
  }
196
+ function resolveProtectedWarmingMetadata(band) {
197
+ if (band === 'bootstrap') {
198
+ return Object.freeze({ isProtected: true, floor: 0.372, reason: 'bootstrap protected warming floor' });
199
+ }
200
+ if (band === 'warmup') {
201
+ return Object.freeze({ isProtected: true, floor: 0.34, reason: 'warmup protected warming floor' });
202
+ }
203
+ if (band === 'high') {
204
+ return Object.freeze({ isProtected: true, floor: 0.28, reason: 'high-pressure warming floor' });
205
+ }
206
+ if (band === 'critical') {
207
+ return Object.freeze({ isProtected: true, floor: 0.20, reason: 'critical-pressure warming floor' });
208
+ }
209
+ return Object.freeze({ isProtected: false, floor: 0, reason: 'normal warming range' });
210
+ }
143
211
  function reasonsFor(input, band, pressure) {
144
212
  const reasons = [`band:${band}`, `pressure:${Math.round(pressure * 100)}%`];
145
213
  const turns = Math.max(0, Math.floor(input.userTurnCount ?? 0));
214
+ const topicBearingTurns = input.topicBearingTurnCount != null
215
+ ? Math.max(0, Math.floor(input.topicBearingTurnCount))
216
+ : null;
146
217
  if (input.explicitNewSession)
147
218
  reasons.push('explicit-new-session');
148
219
  if (input.forkedContext) {
@@ -156,8 +227,14 @@ function reasonsFor(input, band, pressure) {
156
227
  }
157
228
  if (turns === 0)
158
229
  reasons.push('cold-start');
159
- if (turns > 0 && turns <= 4)
160
- reasons.push(`early-session:${turns}`);
230
+ if (topicBearingTurns != null) {
231
+ if (topicBearingTurns > 0 && topicBearingTurns <= 8)
232
+ reasons.push(`topic-bearing:${topicBearingTurns}`);
233
+ }
234
+ else {
235
+ if (turns > 0 && turns <= 4)
236
+ reasons.push(`early-session:${turns}`);
237
+ }
161
238
  if (isTopicShift(input))
162
239
  reasons.push(`topic-shift:${(input.topicShiftConfidence ?? 0).toFixed(2)}`);
163
240
  if (band === 'high' || band === 'critical')
@@ -184,6 +261,7 @@ export function resolveAdaptiveLifecyclePolicy(input = {}) {
184
261
  enableTopicCentroidEviction: band === 'elevated' || triggerProactiveCompaction,
185
262
  triggerProactiveCompaction,
186
263
  evictionPlan: resolveAdaptiveEvictionPlan(band),
264
+ protectedWarmingMetadata: resolveProtectedWarmingMetadata(band),
187
265
  reasons: reasonsFor(input, band, pressureFraction),
188
266
  });
189
267
  }
@@ -1 +1 @@
1
- {"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"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"}
@@ -726,7 +726,8 @@ export class BackgroundIndexer {
726
726
  }
727
727
  for (const conv of convRows) {
728
728
  maintConsidered++;
729
- const lastProcessed = this._conversationLastProcessed.get(conv.id) ?? 0;
729
+ const conversationKey = `${agentId}:${conv.id}`;
730
+ const lastProcessed = this._conversationLastProcessed.get(conversationKey) ?? 0;
730
731
  if (now - lastProcessed < cooldownMs) {
731
732
  maintSkipped++;
732
733
  continue;
@@ -736,15 +737,16 @@ export class BackgroundIndexer {
736
737
  // clear any stale 'no-conversations' marker from an earlier agent.
737
738
  if (maintExitReason === 'no-conversations')
738
739
  maintExitReason = 'complete';
739
- const noiseSweepResult = runNoiseSweep(messageDb, conv.id, 20, maxCandidates);
740
- const toolDecayResult = runToolDecay(messageDb, conv.id, 40, maxCandidates);
740
+ const proactiveContext = { agentId };
741
+ const noiseSweepResult = runNoiseSweep(messageDb, conv.id, 20, maxCandidates, proactiveContext);
742
+ const toolDecayResult = runToolDecay(messageDb, conv.id, 40, maxCandidates, proactiveContext);
741
743
  const changed = noiseSweepResult.messagesDeleted + toolDecayResult.messagesUpdated;
742
744
  if (changed > 0) {
743
745
  maintMutated += changed;
744
- console.log(`[indexer] Proactive pass (conv ${conv.id}): swept ${noiseSweepResult.messagesDeleted} noise msgs, ` +
746
+ console.log(`[indexer] Proactive pass (agent ${agentId} conv ${conv.id}): swept ${noiseSweepResult.messagesDeleted} noise msgs, ` +
745
747
  `decayed ${toolDecayResult.messagesUpdated} tool results (${toolDecayResult.bytesFreed} bytes freed)`);
746
748
  }
747
- this._conversationLastProcessed.set(conv.id, now);
749
+ this._conversationLastProcessed.set(conversationKey, now);
748
750
  if (maintMutated >= maxCandidates) {
749
751
  maintExitReason = 'cap-reached';
750
752
  break;