@psiclawops/hypermem 0.1.0 → 0.5.1
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/ARCHITECTURE.md +4 -3
- package/README.md +457 -174
- package/dist/background-indexer.d.ts +19 -4
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +329 -17
- package/dist/cache.d.ts +110 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +495 -0
- package/dist/compaction-fence.d.ts +1 -1
- package/dist/compaction-fence.js +1 -1
- package/dist/compositor.d.ts +114 -27
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +1678 -229
- package/dist/content-type-classifier.d.ts +41 -0
- package/dist/content-type-classifier.d.ts.map +1 -0
- package/dist/content-type-classifier.js +181 -0
- package/dist/cross-agent.d.ts +5 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +5 -0
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +6 -2
- package/dist/desired-state-store.d.ts +1 -1
- package/dist/desired-state-store.d.ts.map +1 -1
- package/dist/desired-state-store.js +15 -5
- package/dist/doc-chunk-store.d.ts +26 -1
- package/dist/doc-chunk-store.d.ts.map +1 -1
- package/dist/doc-chunk-store.js +114 -1
- package/dist/doc-chunker.d.ts +1 -1
- package/dist/doc-chunker.js +1 -1
- package/dist/dreaming-promoter.d.ts +86 -0
- package/dist/dreaming-promoter.d.ts.map +1 -0
- package/dist/dreaming-promoter.js +381 -0
- package/dist/episode-store.d.ts +2 -1
- package/dist/episode-store.d.ts.map +1 -1
- package/dist/episode-store.js +4 -4
- package/dist/fact-store.d.ts +19 -1
- package/dist/fact-store.d.ts.map +1 -1
- package/dist/fact-store.js +64 -3
- package/dist/fleet-store.d.ts +1 -1
- package/dist/fleet-store.js +1 -1
- package/dist/fos-mod.d.ts +178 -0
- package/dist/fos-mod.d.ts.map +1 -0
- package/dist/fos-mod.js +416 -0
- package/dist/hybrid-retrieval.d.ts +5 -1
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +7 -3
- package/dist/image-eviction.d.ts +49 -0
- package/dist/image-eviction.d.ts.map +1 -0
- package/dist/image-eviction.js +251 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -43
- package/dist/keystone-scorer.d.ts +51 -0
- package/dist/keystone-scorer.d.ts.map +1 -0
- package/dist/keystone-scorer.js +52 -0
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-lint.d.ts +29 -0
- package/dist/knowledge-lint.d.ts.map +1 -0
- package/dist/knowledge-lint.js +116 -0
- package/dist/knowledge-store.d.ts +1 -1
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +8 -2
- package/dist/library-schema.d.ts +3 -3
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +324 -3
- package/dist/message-store.d.ts +15 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +51 -1
- package/dist/metrics-dashboard.d.ts +114 -0
- package/dist/metrics-dashboard.d.ts.map +1 -0
- package/dist/metrics-dashboard.js +260 -0
- package/dist/obsidian-exporter.d.ts +57 -0
- package/dist/obsidian-exporter.d.ts.map +1 -0
- package/dist/obsidian-exporter.js +274 -0
- package/dist/obsidian-watcher.d.ts +147 -0
- package/dist/obsidian-watcher.d.ts.map +1 -0
- package/dist/obsidian-watcher.js +403 -0
- package/dist/open-domain.d.ts +46 -0
- package/dist/open-domain.d.ts.map +1 -0
- package/dist/open-domain.js +125 -0
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/preservation-gate.d.ts +1 -1
- package/dist/preservation-gate.js +1 -1
- package/dist/proactive-pass.d.ts +63 -0
- package/dist/proactive-pass.d.ts.map +1 -0
- package/dist/proactive-pass.js +239 -0
- package/dist/profiles.d.ts +44 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +227 -0
- package/dist/provider-translator.d.ts +13 -3
- package/dist/provider-translator.d.ts.map +1 -1
- package/dist/provider-translator.js +63 -9
- package/dist/rate-limiter.d.ts +1 -1
- package/dist/rate-limiter.js +1 -1
- package/dist/repair-tool-pairs.d.ts +38 -0
- package/dist/repair-tool-pairs.d.ts.map +1 -0
- package/dist/repair-tool-pairs.js +138 -0
- package/dist/retrieval-policy.d.ts +51 -0
- package/dist/retrieval-policy.d.ts.map +1 -0
- package/dist/retrieval-policy.js +77 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -2
- package/dist/secret-scanner.d.ts +1 -1
- package/dist/secret-scanner.js +1 -1
- package/dist/seed.d.ts +2 -2
- package/dist/seed.js +2 -2
- package/dist/session-flusher.d.ts +53 -0
- package/dist/session-flusher.d.ts.map +1 -0
- package/dist/session-flusher.js +69 -0
- package/dist/session-topic-map.d.ts +41 -0
- package/dist/session-topic-map.d.ts.map +1 -0
- package/dist/session-topic-map.js +77 -0
- package/dist/spawn-context.d.ts +54 -0
- package/dist/spawn-context.d.ts.map +1 -0
- package/dist/spawn-context.js +159 -0
- package/dist/system-store.d.ts +1 -1
- package/dist/system-store.js +1 -1
- package/dist/temporal-store.d.ts +80 -0
- package/dist/temporal-store.d.ts.map +1 -0
- package/dist/temporal-store.js +149 -0
- package/dist/topic-detector.d.ts +35 -0
- package/dist/topic-detector.d.ts.map +1 -0
- package/dist/topic-detector.js +249 -0
- package/dist/topic-store.d.ts +1 -1
- package/dist/topic-store.js +1 -1
- package/dist/topic-synthesizer.d.ts +51 -0
- package/dist/topic-synthesizer.d.ts.map +1 -0
- package/dist/topic-synthesizer.js +315 -0
- package/dist/trigger-registry.d.ts +63 -0
- package/dist/trigger-registry.d.ts.map +1 -0
- package/dist/trigger-registry.js +163 -0
- package/dist/types.d.ts +214 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/vector-store.d.ts +43 -5
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +189 -10
- package/dist/version.d.ts +34 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +34 -0
- package/dist/wiki-page-emitter.d.ts +65 -0
- package/dist/wiki-page-emitter.d.ts.map +1 -0
- package/dist/wiki-page-emitter.js +258 -0
- package/dist/work-store.d.ts +1 -1
- package/dist/work-store.js +1 -1
- package/package.json +15 -5
- package/dist/redis.d.ts +0 -188
- package/dist/redis.d.ts.map +0 -1
- package/dist/redis.js +0 -534
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Background Indexer
|
|
3
3
|
*
|
|
4
4
|
* Processes message history to extract structured knowledge:
|
|
5
5
|
* - Facts: atomic pieces of learned information
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { DatabaseSync } from 'node:sqlite';
|
|
20
20
|
import type { IndexerConfig, SessionCursor } from './types.js';
|
|
21
|
+
import { type DreamerConfig } from './dreaming-promoter.js';
|
|
21
22
|
import type { VectorStore } from './vector-store.js';
|
|
22
23
|
export interface IndexerStats {
|
|
23
24
|
agentId: string;
|
|
@@ -26,6 +27,8 @@ export interface IndexerStats {
|
|
|
26
27
|
episodesRecorded: number;
|
|
27
28
|
topicsUpdated: number;
|
|
28
29
|
knowledgeUpserted: number;
|
|
30
|
+
/** Number of superseded fact vectors tombstoned from the vector index this tick. */
|
|
31
|
+
tombstoned: number;
|
|
29
32
|
elapsedMs: number;
|
|
30
33
|
/** Number of messages that were post-cursor (unseen by model, high-signal priority). */
|
|
31
34
|
postCursorMessages: number;
|
|
@@ -47,10 +50,13 @@ export declare class BackgroundIndexer {
|
|
|
47
50
|
private listAgents?;
|
|
48
51
|
private getCursor?;
|
|
49
52
|
private readonly config;
|
|
53
|
+
private readonly dreamerConfig;
|
|
50
54
|
private intervalHandle;
|
|
51
55
|
private running;
|
|
52
56
|
private vectorStore;
|
|
53
|
-
|
|
57
|
+
private synthesizer;
|
|
58
|
+
private tickCount;
|
|
59
|
+
constructor(config?: Partial<IndexerConfig>, getMessageDb?: ((agentId: string) => DatabaseSync) | undefined, getLibraryDb?: (() => DatabaseSync) | undefined, listAgents?: (() => string[]) | undefined, getCursor?: CursorFetcher | undefined, dreamerConfig?: Partial<DreamerConfig>);
|
|
54
60
|
/**
|
|
55
61
|
* Set the vector store for embedding new facts/episodes at index time.
|
|
56
62
|
* Optional — if not set, indexer runs without embedding (FTS5-only mode).
|
|
@@ -104,14 +110,23 @@ export declare class BackgroundIndexer {
|
|
|
104
110
|
* Parse a duration string like "24h", "7d" into seconds.
|
|
105
111
|
*/
|
|
106
112
|
private parseDuration;
|
|
113
|
+
/**
|
|
114
|
+
* One-time backfill: embed episodes with sig>=0.5 that were missed by the
|
|
115
|
+
* old >=0.7 vectorization threshold.
|
|
116
|
+
*
|
|
117
|
+
* Gated by a system_state flag 'indexer:episode_backfill_v1' so it runs
|
|
118
|
+
* exactly once even across gateway restarts. Safe to re-run manually
|
|
119
|
+
* (delete the flag row first) if re-backfill is ever needed.
|
|
120
|
+
*/
|
|
121
|
+
backfillEpisodeVectors(): Promise<void>;
|
|
107
122
|
/**
|
|
108
123
|
* Get current watermarks for all agents.
|
|
109
124
|
*/
|
|
110
125
|
getWatermarks(libraryDb: DatabaseSync): WatermarkState[];
|
|
111
126
|
}
|
|
112
127
|
/**
|
|
113
|
-
* Create and start a background indexer connected to
|
|
128
|
+
* Create and start a background indexer connected to hypermem databases.
|
|
114
129
|
* Used by the hook or a standalone daemon.
|
|
115
130
|
*/
|
|
116
|
-
export declare function createIndexer(getMessageDb: (agentId: string) => DatabaseSync, getLibraryDb: () => DatabaseSync, listAgents: () => string[], config?: Partial<IndexerConfig>, getCursor?: CursorFetcher, vectorStore?: VectorStore): BackgroundIndexer;
|
|
131
|
+
export declare function createIndexer(getMessageDb: (agentId: string) => DatabaseSync, getLibraryDb: () => DatabaseSync, listAgents: () => string[], config?: Partial<IndexerConfig>, getCursor?: CursorFetcher, vectorStore?: VectorStore, dreamerConfig?: Partial<DreamerConfig>): BackgroundIndexer;
|
|
117
132
|
//# sourceMappingURL=background-indexer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAiB,aAAa,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Background Indexer
|
|
3
3
|
*
|
|
4
4
|
* Processes message history to extract structured knowledge:
|
|
5
5
|
* - Facts: atomic pieces of learned information
|
|
@@ -16,12 +16,49 @@
|
|
|
16
16
|
* - Bounded: processes N messages per tick to avoid blocking
|
|
17
17
|
* - Observable: logs extraction stats for monitoring
|
|
18
18
|
*/
|
|
19
|
+
import { lintKnowledge } from './knowledge-lint.js';
|
|
19
20
|
import { MessageStore } from './message-store.js';
|
|
21
|
+
import { runNoiseSweep, runToolDecay } from './proactive-pass.js';
|
|
22
|
+
import { TopicSynthesizer } from './topic-synthesizer.js';
|
|
23
|
+
import { runDreamingPassForFleet } from './dreaming-promoter.js';
|
|
20
24
|
import { FactStore } from './fact-store.js';
|
|
21
25
|
import { EpisodeStore } from './episode-store.js';
|
|
22
26
|
import { TopicStore } from './topic-store.js';
|
|
23
27
|
import { KnowledgeStore } from './knowledge-store.js';
|
|
28
|
+
import { TemporalStore } from './temporal-store.js';
|
|
24
29
|
import { isSafeForSharedVisibility } from './secret-scanner.js';
|
|
30
|
+
// ─── Agent-to-Domain Map ────────────────────────────────────────
|
|
31
|
+
// Maps well-known agent IDs to their primary domain.
|
|
32
|
+
// Used to populate the `domain` column on extracted facts so that
|
|
33
|
+
// domain-scoped retrieval (e.g. getActiveFacts({ domain: 'infrastructure' }))
|
|
34
|
+
// returns results. New agents default to 'general'.
|
|
35
|
+
const AGENT_DOMAIN_MAP = {
|
|
36
|
+
forge: 'infrastructure',
|
|
37
|
+
vigil: 'infrastructure',
|
|
38
|
+
pylon: 'infrastructure',
|
|
39
|
+
plane: 'infrastructure',
|
|
40
|
+
compass: 'product',
|
|
41
|
+
helm: 'product',
|
|
42
|
+
chisel: 'product',
|
|
43
|
+
facet: 'product',
|
|
44
|
+
sentinel: 'security',
|
|
45
|
+
bastion: 'security',
|
|
46
|
+
gauge: 'security',
|
|
47
|
+
clarity: 'ux',
|
|
48
|
+
anvil: 'governance',
|
|
49
|
+
vanguard: 'strategy',
|
|
50
|
+
crucible: 'development',
|
|
51
|
+
relay: 'communications',
|
|
52
|
+
main: 'general',
|
|
53
|
+
'channel-mini': 'general',
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Derive a domain label for a fact based on agent ID.
|
|
57
|
+
* Falls back to 'general' for unknown agents.
|
|
58
|
+
*/
|
|
59
|
+
function domainForAgent(agentId) {
|
|
60
|
+
return AGENT_DOMAIN_MAP[agentId] ?? 'general';
|
|
61
|
+
}
|
|
25
62
|
function extractFactCandidates(content) {
|
|
26
63
|
const facts = [];
|
|
27
64
|
if (!content || content.length < 20)
|
|
@@ -82,6 +119,29 @@ function extractFactCandidates(content) {
|
|
|
82
119
|
* Rejects pattern matches that are code, table fragments, questions,
|
|
83
120
|
* or too short to be meaningful facts.
|
|
84
121
|
*/
|
|
122
|
+
/**
|
|
123
|
+
* Operational boilerplate phrases that appear frequently across sessions
|
|
124
|
+
* but carry zero signal value. High knn similarity makes them *worse*
|
|
125
|
+
* retrieval candidates — they match everything and contaminate episodes.
|
|
126
|
+
*/
|
|
127
|
+
const OPERATIONAL_BOILERPLATE = [
|
|
128
|
+
/timed?\s*out\s*waiting/i,
|
|
129
|
+
/message\s*was\s*delivered/i,
|
|
130
|
+
/no\s*reply\s*(back\s*)?yet/i,
|
|
131
|
+
/picked?\s*it\s*up\s*on\s*(next\s*)?heartbeat/i,
|
|
132
|
+
/session\s*not\s*found/i,
|
|
133
|
+
/\bretrying\b/i,
|
|
134
|
+
/tool\s*call\s*failed/i,
|
|
135
|
+
/exec\s*completed/i,
|
|
136
|
+
/no\s*reply\s*needed/i,
|
|
137
|
+
/still\s*waiting/i,
|
|
138
|
+
/will\s*pick\s*(it\s*)?up\s*(on\s*(next|the))?/i,
|
|
139
|
+
/message\s*is\s*in\s*(his|her|their|the)\s*queue/i,
|
|
140
|
+
/sent\s+to\s+(anvil|compass|clarity|sentinel|vanguard|forge)/i,
|
|
141
|
+
/dispatched\s+(it\s+)?to/i,
|
|
142
|
+
/timed\s*out\s*after/i,
|
|
143
|
+
/\bNO_REPLY\b/,
|
|
144
|
+
];
|
|
85
145
|
function isQualityFact(content) {
|
|
86
146
|
// Too short — sentence fragments
|
|
87
147
|
if (content.length < 40)
|
|
@@ -131,6 +191,30 @@ function isQualityFact(content) {
|
|
|
131
191
|
const alphaChars = (content.match(/[a-zA-Z]/g) || []).length;
|
|
132
192
|
if (alphaChars / content.length < 0.5)
|
|
133
193
|
return false;
|
|
194
|
+
// TUNE-013: External/untrusted content markers — web search excerpts,
|
|
195
|
+
// external doc pulls, and injected context blocks should never become facts.
|
|
196
|
+
if (/<<<\s*(END_EXTERNAL|BEGIN_EXTERNAL|EXTERNAL_UNTRUSTED|UNTRUSTED_CONTENT)/i.test(content))
|
|
197
|
+
return false;
|
|
198
|
+
if (/EXTERNAL_UNTRUSTED_CONTENT\s+id=/.test(content))
|
|
199
|
+
return false;
|
|
200
|
+
// TUNE-013: Multi-paragraph content — real extracted facts are single sentences.
|
|
201
|
+
// More than 2 newlines means we captured a paragraph or structured block, not a fact.
|
|
202
|
+
const newlineCount = (content.match(/\n/g) || []).length;
|
|
203
|
+
if (newlineCount > 2)
|
|
204
|
+
return false;
|
|
205
|
+
// TUNE-013: URL-heavy content — external source snippets, not actionable facts
|
|
206
|
+
const urlMatches = content.match(/https?:\/\/\S+/g) || [];
|
|
207
|
+
if (urlMatches.length >= 2)
|
|
208
|
+
return false; // one URL in a fact is ok; multiple = source snippet
|
|
209
|
+
// TUNE-013: Content starting with a markdown heading is section text, not a fact
|
|
210
|
+
if (/^#{1,4}\s/.test(content.trim()))
|
|
211
|
+
return false;
|
|
212
|
+
// TUNE-014: Operational boilerplate — phrases common across sessions that produce
|
|
213
|
+
// high knn similarity scores but carry zero signal. They cross-contaminate episodes.
|
|
214
|
+
for (const pattern of OPERATIONAL_BOILERPLATE) {
|
|
215
|
+
if (pattern.test(content))
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
134
218
|
return true;
|
|
135
219
|
}
|
|
136
220
|
/**
|
|
@@ -318,14 +402,33 @@ export class BackgroundIndexer {
|
|
|
318
402
|
listAgents;
|
|
319
403
|
getCursor;
|
|
320
404
|
config;
|
|
405
|
+
dreamerConfig;
|
|
321
406
|
intervalHandle = null;
|
|
322
407
|
running = false;
|
|
323
408
|
vectorStore = null;
|
|
324
|
-
|
|
409
|
+
synthesizer = null;
|
|
410
|
+
tickCount = 0;
|
|
411
|
+
constructor(config, getMessageDb, getLibraryDb, listAgents, getCursor, dreamerConfig) {
|
|
325
412
|
this.getMessageDb = getMessageDb;
|
|
326
413
|
this.getLibraryDb = getLibraryDb;
|
|
327
414
|
this.listAgents = listAgents;
|
|
328
415
|
this.getCursor = getCursor;
|
|
416
|
+
// Initialize synthesizer if libraryDb accessor is available
|
|
417
|
+
if (getLibraryDb) {
|
|
418
|
+
const libDb = getLibraryDb();
|
|
419
|
+
if (libDb) {
|
|
420
|
+
this.synthesizer = new TopicSynthesizer(libDb, (agentId) => {
|
|
421
|
+
if (!getMessageDb)
|
|
422
|
+
return null;
|
|
423
|
+
try {
|
|
424
|
+
return getMessageDb(agentId);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
329
432
|
this.config = {
|
|
330
433
|
enabled: config?.enabled ?? true,
|
|
331
434
|
factExtractionMode: config?.factExtractionMode ?? 'tiered',
|
|
@@ -333,8 +436,11 @@ export class BackgroundIndexer {
|
|
|
333
436
|
topicClosedAfter: config?.topicClosedAfter ?? '7d',
|
|
334
437
|
factDecayRate: config?.factDecayRate ?? 0.01,
|
|
335
438
|
episodeSignificanceThreshold: config?.episodeSignificanceThreshold ?? 0.5,
|
|
336
|
-
periodicInterval: config?.periodicInterval ??
|
|
439
|
+
periodicInterval: config?.periodicInterval ?? 60000, // 1 minute
|
|
440
|
+
batchSize: config?.batchSize ?? 128,
|
|
441
|
+
maxMessagesPerTick: config?.maxMessagesPerTick ?? 500,
|
|
337
442
|
};
|
|
443
|
+
this.dreamerConfig = dreamerConfig ?? {};
|
|
338
444
|
}
|
|
339
445
|
/**
|
|
340
446
|
* Set the vector store for embedding new facts/episodes at index time.
|
|
@@ -355,13 +461,19 @@ export class BackgroundIndexer {
|
|
|
355
461
|
this.tick().catch(err => {
|
|
356
462
|
console.error('[indexer] Initial tick failed:', err);
|
|
357
463
|
});
|
|
464
|
+
// Run episode vector backfill once at startup (no-op if already done)
|
|
465
|
+
if (this.vectorStore && this.getLibraryDb) {
|
|
466
|
+
this.backfillEpisodeVectors().catch(err => {
|
|
467
|
+
console.error('[indexer] Episode backfill failed:', err);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
358
470
|
// Then periodically
|
|
359
471
|
this.intervalHandle = setInterval(() => {
|
|
360
472
|
this.tick().catch(err => {
|
|
361
473
|
console.error('[indexer] Periodic tick failed:', err);
|
|
362
474
|
});
|
|
363
475
|
}, this.config.periodicInterval);
|
|
364
|
-
console.log(`[indexer] Started with interval ${this.config.periodicInterval}ms`);
|
|
476
|
+
console.log(`[indexer] Started with interval ${this.config.periodicInterval}ms, batchSize ${this.config.batchSize}, maxPerTick ${this.config.maxMessagesPerTick}`);
|
|
365
477
|
}
|
|
366
478
|
/**
|
|
367
479
|
* Stop periodic indexing.
|
|
@@ -389,10 +501,16 @@ export class BackgroundIndexer {
|
|
|
389
501
|
}
|
|
390
502
|
const agents = this.listAgents();
|
|
391
503
|
const libraryDb = this.getLibraryDb();
|
|
504
|
+
let tickTotal = 0;
|
|
392
505
|
for (const agentId of agents) {
|
|
506
|
+
if (tickTotal >= this.config.maxMessagesPerTick) {
|
|
507
|
+
console.log(`[indexer] maxMessagesPerTick (${this.config.maxMessagesPerTick}) reached — deferring remaining agents`);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
393
510
|
try {
|
|
394
511
|
const stats = await this.processAgent(agentId, libraryDb);
|
|
395
|
-
|
|
512
|
+
tickTotal += stats.messagesProcessed;
|
|
513
|
+
if (stats.messagesProcessed > 0 || stats.tombstoned > 0) {
|
|
396
514
|
results.push(stats);
|
|
397
515
|
}
|
|
398
516
|
}
|
|
@@ -405,10 +523,84 @@ export class BackgroundIndexer {
|
|
|
405
523
|
const totalMessages = results.reduce((s, r) => s + r.messagesProcessed, 0);
|
|
406
524
|
const totalFacts = results.reduce((s, r) => s + r.factsExtracted, 0);
|
|
407
525
|
const totalEpisodes = results.reduce((s, r) => s + r.episodesRecorded, 0);
|
|
408
|
-
|
|
526
|
+
const totalTombstoned = results.reduce((s, r) => s + r.tombstoned, 0);
|
|
527
|
+
const tombstonedPart = totalTombstoned > 0 ? `, ${totalTombstoned} tombstoned` : '';
|
|
528
|
+
console.log(`[indexer] Tick complete: ${totalMessages} messages → ${totalFacts} facts, ${totalEpisodes} episodes${tombstonedPart}`);
|
|
409
529
|
}
|
|
410
530
|
// Run decay on every tick
|
|
411
531
|
this.applyDecay(libraryDb);
|
|
532
|
+
// Topic synthesis — run for each agent after main indexer tick
|
|
533
|
+
if (this.synthesizer) {
|
|
534
|
+
for (const agentId of agents) {
|
|
535
|
+
try {
|
|
536
|
+
const synthResult = this.synthesizer.tick(agentId);
|
|
537
|
+
if (synthResult.topicsSynthesized > 0) {
|
|
538
|
+
console.log(`[indexer] Synthesized ${synthResult.topicsSynthesized} topics for ${agentId}, ${synthResult.knowledgeEntriesWritten} knowledge entries`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
// Non-fatal
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Knowledge lint — every LINT_FREQUENCY ticks
|
|
547
|
+
this.tickCount++;
|
|
548
|
+
if (this.tickCount % 10 === 0 && this.getLibraryDb) {
|
|
549
|
+
try {
|
|
550
|
+
const libDb = this.getLibraryDb();
|
|
551
|
+
if (libDb) {
|
|
552
|
+
const lint = lintKnowledge(libDb);
|
|
553
|
+
if (lint.staleDecayed > 0 || lint.coverageGaps.length > 0) {
|
|
554
|
+
console.log(`[indexer] Lint: ${lint.staleDecayed} stale decayed, ${lint.orphansFound} orphans, ${lint.coverageGaps.length} coverage gaps`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
// Non-fatal
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Dreaming promotion pass — every tickInterval ticks (default 12 = ~1hr)
|
|
563
|
+
const dreamerEnabled = this.dreamerConfig.enabled ?? false;
|
|
564
|
+
const dreamerTickInterval = this.dreamerConfig.tickInterval ?? 12;
|
|
565
|
+
if (dreamerEnabled && this.tickCount % dreamerTickInterval === 0 && this.getLibraryDb) {
|
|
566
|
+
try {
|
|
567
|
+
const libDb = this.getLibraryDb();
|
|
568
|
+
if (libDb) {
|
|
569
|
+
const dreamResults = await runDreamingPassForFleet(agents, libDb, this.dreamerConfig);
|
|
570
|
+
const totalPromoted = dreamResults.reduce((s, r) => s + r.promoted, 0);
|
|
571
|
+
if (totalPromoted > 0) {
|
|
572
|
+
console.log(`[indexer] Dreaming: promoted ${totalPromoted} facts across ${dreamResults.length} agents`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
// Non-fatal — dreaming failures never block indexing
|
|
578
|
+
console.warn('[indexer] Dreaming pass failed (non-fatal):', err.message);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Run proactive passes on each agent's message DB
|
|
582
|
+
for (const agentId of agents) {
|
|
583
|
+
const messageDb = this.getMessageDb(agentId);
|
|
584
|
+
if (!messageDb)
|
|
585
|
+
continue;
|
|
586
|
+
// Get active conversations for this agent
|
|
587
|
+
let convRows;
|
|
588
|
+
try {
|
|
589
|
+
convRows = messageDb.prepare(`SELECT id FROM conversations WHERE agent_id = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 10`).all(agentId);
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
for (const conv of convRows) {
|
|
595
|
+
const noiseSweepResult = runNoiseSweep(messageDb, conv.id);
|
|
596
|
+
const toolDecayResult = runToolDecay(messageDb, conv.id);
|
|
597
|
+
// Log only if something changed
|
|
598
|
+
if (noiseSweepResult.messagesDeleted > 0 || toolDecayResult.messagesUpdated > 0) {
|
|
599
|
+
console.log(`[indexer] Proactive pass (conv ${conv.id}): swept ${noiseSweepResult.messagesDeleted} noise msgs, ` +
|
|
600
|
+
`decayed ${toolDecayResult.messagesUpdated} tool results (${toolDecayResult.bytesFreed} bytes freed)`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
412
604
|
}
|
|
413
605
|
finally {
|
|
414
606
|
this.running = false;
|
|
@@ -433,12 +625,19 @@ export class BackgroundIndexer {
|
|
|
433
625
|
const episodeStore = new EpisodeStore(libraryDb);
|
|
434
626
|
const topicStore = new TopicStore(libraryDb);
|
|
435
627
|
const knowledgeStore = new KnowledgeStore(libraryDb);
|
|
628
|
+
const temporalStore = new TemporalStore(libraryDb);
|
|
436
629
|
// Get watermark — last processed message ID for this agent
|
|
437
630
|
const watermark = this.getWatermark(libraryDb, agentId);
|
|
438
631
|
const lastProcessedId = watermark?.lastMessageId ?? 0;
|
|
439
|
-
// Fetch unindexed messages (batch size
|
|
440
|
-
const messages = this.getUnindexedMessages(messageDb, agentId, lastProcessedId,
|
|
632
|
+
// Fetch unindexed messages (batch size from config)
|
|
633
|
+
const messages = this.getUnindexedMessages(messageDb, agentId, lastProcessedId, this.config.batchSize);
|
|
441
634
|
if (messages.length === 0) {
|
|
635
|
+
// Even with no new messages, run tombstone cleanup in case supersedes
|
|
636
|
+
// were written externally (e.g. via FactStore.markSuperseded()).
|
|
637
|
+
let tombstoned = 0;
|
|
638
|
+
if (this.vectorStore) {
|
|
639
|
+
tombstoned = this.vectorStore.tombstoneSuperseded();
|
|
640
|
+
}
|
|
442
641
|
return {
|
|
443
642
|
agentId,
|
|
444
643
|
messagesProcessed: 0,
|
|
@@ -446,6 +645,7 @@ export class BackgroundIndexer {
|
|
|
446
645
|
episodesRecorded: 0,
|
|
447
646
|
topicsUpdated: 0,
|
|
448
647
|
knowledgeUpserted: 0,
|
|
648
|
+
tombstoned,
|
|
449
649
|
postCursorMessages: 0,
|
|
450
650
|
elapsedMs: Date.now() - start,
|
|
451
651
|
};
|
|
@@ -479,6 +679,7 @@ export class BackgroundIndexer {
|
|
|
479
679
|
let episodesRecorded = 0;
|
|
480
680
|
let topicsUpdated = 0;
|
|
481
681
|
let knowledgeUpserted = 0;
|
|
682
|
+
let supersededFacts = 0;
|
|
482
683
|
let maxMessageId = lastProcessedId;
|
|
483
684
|
for (const msg of ordered) {
|
|
484
685
|
const content = msg.textContent || '';
|
|
@@ -493,12 +694,32 @@ export class BackgroundIndexer {
|
|
|
493
694
|
try {
|
|
494
695
|
const fact = factStore.addFact(agentId, factContent, {
|
|
495
696
|
scope: 'agent',
|
|
697
|
+
domain: domainForAgent(agentId),
|
|
496
698
|
confidence: factConfidence,
|
|
497
699
|
sourceType: 'indexer',
|
|
498
700
|
sourceSessionKey: this.getSessionKeyForMessage(messageDb, msg.conversationId),
|
|
499
701
|
sourceRef: `msg:${msg.id}`,
|
|
500
702
|
});
|
|
501
703
|
factsExtracted++;
|
|
704
|
+
// ── Supersedes detection ─────────────────────────────────
|
|
705
|
+
// Check if the newly extracted fact supersedes an existing one.
|
|
706
|
+
// A supersede is detected when an existing active fact shares the
|
|
707
|
+
// same 60-char prefix (same topic, different phrasing/update).
|
|
708
|
+
if (fact.id) {
|
|
709
|
+
// Index into temporal store (ingest_at as proxy, confidence=0.5)
|
|
710
|
+
temporalStore.indexFact(fact.id, agentId, fact.createdAt);
|
|
711
|
+
const oldFactId = factStore.findSupersedableByContent(agentId, factContent);
|
|
712
|
+
if (oldFactId !== null && oldFactId !== fact.id) {
|
|
713
|
+
const didSupersede = factStore.markSuperseded(oldFactId, fact.id);
|
|
714
|
+
if (didSupersede) {
|
|
715
|
+
supersededFacts++;
|
|
716
|
+
// Immediately remove the stale vector so it can't surface in KNN recall
|
|
717
|
+
if (this.vectorStore) {
|
|
718
|
+
this.vectorStore.removeItem('facts', oldFactId);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
502
723
|
// Embed new fact for semantic recall (best-effort, non-blocking)
|
|
503
724
|
if (this.vectorStore && fact.id) {
|
|
504
725
|
this.vectorStore.indexItem('facts', fact.id, factContent, fact.domain || undefined)
|
|
@@ -520,10 +741,12 @@ export class BackgroundIndexer {
|
|
|
520
741
|
significance: episode.significance,
|
|
521
742
|
visibility: episodeVisibility,
|
|
522
743
|
sessionKey: this.getSessionKeyForMessage(messageDb, msg.conversationId),
|
|
744
|
+
sourceMessageId: msg.id,
|
|
523
745
|
});
|
|
524
746
|
episodesRecorded++;
|
|
525
|
-
// Embed
|
|
526
|
-
|
|
747
|
+
// Embed episodes at sig>=0.5 (lowered from 0.7 — discovery/config_change events
|
|
748
|
+
// at sig=0.5 are real operational events, not noise).
|
|
749
|
+
if (this.vectorStore && recorded?.id && episode.significance >= 0.5) {
|
|
527
750
|
this.vectorStore.indexItem('episodes', recorded.id, episode.summary, episode.type)
|
|
528
751
|
.catch(() => { });
|
|
529
752
|
}
|
|
@@ -564,6 +787,12 @@ export class BackgroundIndexer {
|
|
|
564
787
|
}
|
|
565
788
|
// Update watermark
|
|
566
789
|
this.setWatermark(libraryDb, agentId, maxMessageId);
|
|
790
|
+
// Run tombstone pass: remove vector entries for any facts marked superseded
|
|
791
|
+
// (covers both the supersedes detected above and external markSuperseded calls).
|
|
792
|
+
let tombstoned = 0;
|
|
793
|
+
if (this.vectorStore) {
|
|
794
|
+
tombstoned = this.vectorStore.tombstoneSuperseded();
|
|
795
|
+
}
|
|
567
796
|
return {
|
|
568
797
|
agentId,
|
|
569
798
|
messagesProcessed: messages.length,
|
|
@@ -571,6 +800,7 @@ export class BackgroundIndexer {
|
|
|
571
800
|
episodesRecorded,
|
|
572
801
|
topicsUpdated,
|
|
573
802
|
knowledgeUpserted,
|
|
803
|
+
tombstoned,
|
|
574
804
|
postCursorMessages: postCursor.length,
|
|
575
805
|
elapsedMs: Date.now() - start,
|
|
576
806
|
};
|
|
@@ -667,22 +897,25 @@ export class BackgroundIndexer {
|
|
|
667
897
|
// Mark dormant topics
|
|
668
898
|
const dormantThreshold = this.parseDuration(this.config.topicDormantAfter);
|
|
669
899
|
if (dormantThreshold > 0) {
|
|
900
|
+
// Compute threshold timestamp in JS and pass as parameter — avoids SQL template interpolation.
|
|
901
|
+
const dormantBefore = new Date(Date.now() - dormantThreshold * 1000).toISOString();
|
|
670
902
|
libraryDb.prepare(`
|
|
671
903
|
UPDATE topics
|
|
672
904
|
SET status = 'dormant'
|
|
673
905
|
WHERE status = 'active'
|
|
674
|
-
AND updated_at <
|
|
675
|
-
`).run();
|
|
906
|
+
AND updated_at < ?
|
|
907
|
+
`).run(dormantBefore);
|
|
676
908
|
}
|
|
677
909
|
// Close old dormant topics
|
|
678
910
|
const closedThreshold = this.parseDuration(this.config.topicClosedAfter);
|
|
679
911
|
if (closedThreshold > 0) {
|
|
912
|
+
const closedBefore = new Date(Date.now() - closedThreshold * 1000).toISOString();
|
|
680
913
|
libraryDb.prepare(`
|
|
681
914
|
UPDATE topics
|
|
682
915
|
SET status = 'closed'
|
|
683
916
|
WHERE status = 'dormant'
|
|
684
|
-
AND updated_at <
|
|
685
|
-
`).run();
|
|
917
|
+
AND updated_at < ?
|
|
918
|
+
`).run(closedBefore);
|
|
686
919
|
}
|
|
687
920
|
}
|
|
688
921
|
/**
|
|
@@ -701,6 +934,85 @@ export class BackgroundIndexer {
|
|
|
701
934
|
default: return 0;
|
|
702
935
|
}
|
|
703
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* One-time backfill: embed episodes with sig>=0.5 that were missed by the
|
|
939
|
+
* old >=0.7 vectorization threshold.
|
|
940
|
+
*
|
|
941
|
+
* Gated by a system_state flag 'indexer:episode_backfill_v1' so it runs
|
|
942
|
+
* exactly once even across gateway restarts. Safe to re-run manually
|
|
943
|
+
* (delete the flag row first) if re-backfill is ever needed.
|
|
944
|
+
*/
|
|
945
|
+
async backfillEpisodeVectors() {
|
|
946
|
+
if (!this.vectorStore || !this.getLibraryDb)
|
|
947
|
+
return;
|
|
948
|
+
const libraryDb = this.getLibraryDb();
|
|
949
|
+
const BACKFILL_FLAG = 'episode_backfill_v1';
|
|
950
|
+
// Ensure system_state table exists (schema may not have been applied yet)
|
|
951
|
+
try {
|
|
952
|
+
libraryDb.prepare(`
|
|
953
|
+
CREATE TABLE IF NOT EXISTS system_state (
|
|
954
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
955
|
+
category TEXT NOT NULL,
|
|
956
|
+
key TEXT NOT NULL,
|
|
957
|
+
value TEXT,
|
|
958
|
+
updated_at TEXT NOT NULL,
|
|
959
|
+
updated_by TEXT,
|
|
960
|
+
ttl TEXT,
|
|
961
|
+
UNIQUE(category, key)
|
|
962
|
+
)
|
|
963
|
+
`).run();
|
|
964
|
+
}
|
|
965
|
+
catch {
|
|
966
|
+
// Table already exists — safe to ignore
|
|
967
|
+
}
|
|
968
|
+
// Check if backfill already completed
|
|
969
|
+
const existing = libraryDb.prepare("SELECT value FROM system_state WHERE category = 'indexer' AND key = ?").get(BACKFILL_FLAG);
|
|
970
|
+
if (existing) {
|
|
971
|
+
// Already done
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
console.log('[indexer] Starting episode vector backfill (sig>=0.5, not yet vectorized)...');
|
|
975
|
+
// Find episodes with sig>=0.5 that have no vec_index_map entry.
|
|
976
|
+
// We join against vec_index_map using a fallback: if the table is in a
|
|
977
|
+
// separate DB (vectors.db), we query it directly via the VectorStore.
|
|
978
|
+
let episodes;
|
|
979
|
+
try {
|
|
980
|
+
episodes = libraryDb.prepare(`
|
|
981
|
+
SELECT id, summary, event_type
|
|
982
|
+
FROM episodes
|
|
983
|
+
WHERE significance >= 0.5
|
|
984
|
+
ORDER BY created_at DESC
|
|
985
|
+
`).all();
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
console.warn('[indexer] Backfill: could not query episodes table');
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
let queued = 0;
|
|
992
|
+
let skipped = 0;
|
|
993
|
+
for (const ep of episodes) {
|
|
994
|
+
// Check if already vectorized
|
|
995
|
+
if (this.vectorStore.hasItem('episodes', ep.id)) {
|
|
996
|
+
skipped++;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
await this.vectorStore.indexItem('episodes', ep.id, ep.summary, ep.event_type);
|
|
1001
|
+
queued++;
|
|
1002
|
+
}
|
|
1003
|
+
catch {
|
|
1004
|
+
// Non-fatal — keep going
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
// Mark backfill complete
|
|
1008
|
+
const now = new Date().toISOString();
|
|
1009
|
+
libraryDb.prepare(`
|
|
1010
|
+
INSERT INTO system_state (category, key, value, updated_at, updated_by)
|
|
1011
|
+
VALUES ('indexer', ?, ?, ?, 'indexer')
|
|
1012
|
+
ON CONFLICT(category, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
1013
|
+
`).run(BACKFILL_FLAG, JSON.stringify({ completedAt: now, queued, skipped }), now);
|
|
1014
|
+
console.log(`[indexer] Episode backfill complete: ${queued} queued, ${skipped} already vectorized`);
|
|
1015
|
+
}
|
|
704
1016
|
/**
|
|
705
1017
|
* Get current watermarks for all agents.
|
|
706
1018
|
*/
|
|
@@ -720,11 +1032,11 @@ export class BackgroundIndexer {
|
|
|
720
1032
|
}
|
|
721
1033
|
// ─── Standalone runner ──────────────────────────────────────────
|
|
722
1034
|
/**
|
|
723
|
-
* Create and start a background indexer connected to
|
|
1035
|
+
* Create and start a background indexer connected to hypermem databases.
|
|
724
1036
|
* Used by the hook or a standalone daemon.
|
|
725
1037
|
*/
|
|
726
|
-
export function createIndexer(getMessageDb, getLibraryDb, listAgents, config, getCursor, vectorStore) {
|
|
727
|
-
const indexer = new BackgroundIndexer(config, getMessageDb, getLibraryDb, listAgents, getCursor);
|
|
1038
|
+
export function createIndexer(getMessageDb, getLibraryDb, listAgents, config, getCursor, vectorStore, dreamerConfig) {
|
|
1039
|
+
const indexer = new BackgroundIndexer(config, getMessageDb, getLibraryDb, listAgents, getCursor, dreamerConfig);
|
|
728
1040
|
if (vectorStore)
|
|
729
1041
|
indexer.setVectorStore(vectorStore);
|
|
730
1042
|
return indexer;
|