@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.
- package/CHANGELOG.md +16 -0
- package/INSTALL.md +71 -68
- package/README.md +11 -29
- package/assets/default-config.json +47 -0
- package/bin/hypermem-doctor.mjs +76 -2
- package/bin/hypermem-status.mjs +255 -7
- package/dist/adaptive-lifecycle.d.ts +39 -0
- package/dist/adaptive-lifecycle.d.ts.map +1 -1
- package/dist/adaptive-lifecycle.js +87 -9
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +7 -5
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +239 -20
- package/dist/hybrid-retrieval.d.ts +8 -0
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +112 -10
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/message-store.d.ts +62 -1
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +355 -2
- package/dist/open-domain.d.ts.map +1 -1
- package/dist/open-domain.js +3 -2
- package/dist/proactive-pass.d.ts +42 -2
- package/dist/proactive-pass.d.ts.map +1 -1
- package/dist/proactive-pass.js +294 -39
- package/dist/topic-synthesizer.d.ts.map +1 -1
- package/dist/topic-synthesizer.js +9 -3
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +10 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +45 -9
- package/docs/DIAGNOSTICS.md +87 -0
- package/docs/INTEGRATION_VALIDATION.md +40 -1
- package/docs/ROADMAP.md +25 -12
- package/docs/TUNING.md +45 -4
- package/install.sh +5 -60
- package/memory-plugin/dist/index.js +192 -0
- package/memory-plugin/openclaw.plugin.json +199 -2
- package/memory-plugin/package.json +2 -2
- package/package.json +21 -8
- package/plugin/dist/index.d.ts +2 -0
- package/plugin/dist/index.d.ts.map +1 -1
- package/plugin/dist/index.js +175 -8
- package/plugin/dist/index.js.map +1 -1
- package/plugin/openclaw.plugin.json +199 -2
- package/plugin/package.json +2 -2
- package/scripts/install-packed-runtime.mjs +99 -0
- package/scripts/install-runtime.mjs +164 -4
package/bin/hypermem-status.mjs
CHANGED
|
@@ -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
|
-
|
|
284
|
+
|
|
285
|
+
function listMessageDbs(agentId, limit = Infinity) {
|
|
220
286
|
const agentsDir = join(dataDir, 'agents');
|
|
221
|
-
if (!existsSync(agentsDir)) return {
|
|
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 {
|
|
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
|
|
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;
|
|
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.
|
|
64
|
-
warmup: 0.
|
|
65
|
-
steady: 0.
|
|
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
|
-
|
|
123
|
-
|
|
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.
|
|
187
|
+
return 1.75;
|
|
135
188
|
if (band === 'bootstrap' || band === 'warmup')
|
|
136
|
-
return 1.
|
|
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 (
|
|
160
|
-
|
|
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
|
|
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
|
|
740
|
-
const
|
|
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(
|
|
749
|
+
this._conversationLastProcessed.set(conversationKey, now);
|
|
748
750
|
if (maintMutated >= maxCandidates) {
|
|
749
751
|
maintExitReason = 'cap-reached';
|
|
750
752
|
break;
|