@psiclawops/hypermem 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +31 -39
- package/README.md +20 -14
- package/bin/hypermem-status.mjs +1 -1
- package/dist/background-indexer.d.ts +14 -3
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +135 -27
- package/dist/budget-policy.d.ts +22 -0
- package/dist/budget-policy.d.ts.map +1 -0
- package/dist/budget-policy.js +27 -0
- package/dist/cache.d.ts +11 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/compositor-utils.d.ts +31 -0
- package/dist/compositor-utils.d.ts.map +1 -0
- package/dist/compositor-utils.js +47 -0
- package/dist/compositor.d.ts +163 -1
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +862 -130
- package/dist/content-hash.d.ts +43 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +75 -0
- package/dist/context-store.d.ts +54 -0
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +102 -0
- package/dist/contradiction-audit-store.d.ts +54 -0
- package/dist/contradiction-audit-store.d.ts.map +1 -0
- package/dist/contradiction-audit-store.js +88 -0
- package/dist/contradiction-resolution-policy.d.ts +21 -0
- package/dist/contradiction-resolution-policy.d.ts.map +1 -0
- package/dist/contradiction-resolution-policy.js +17 -0
- package/dist/cross-agent.d.ts +1 -1
- package/dist/cross-agent.js +17 -17
- package/dist/degradation.d.ts +102 -0
- package/dist/degradation.d.ts.map +1 -0
- package/dist/degradation.js +141 -0
- package/dist/dreaming-promoter.d.ts +39 -1
- package/dist/dreaming-promoter.d.ts.map +1 -1
- package/dist/dreaming-promoter.js +70 -4
- package/dist/index.d.ts +70 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -29
- package/dist/knowledge-lint.d.ts +2 -0
- package/dist/knowledge-lint.d.ts.map +1 -1
- package/dist/knowledge-lint.js +40 -1
- package/dist/library-schema.d.ts +7 -2
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +236 -1
- package/dist/message-store.d.ts +64 -1
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +137 -1
- package/dist/proactive-pass.d.ts +2 -2
- package/dist/proactive-pass.d.ts.map +1 -1
- package/dist/proactive-pass.js +66 -12
- package/dist/replay-recovery.d.ts +29 -0
- package/dist/replay-recovery.d.ts.map +1 -0
- package/dist/replay-recovery.js +82 -0
- package/dist/reranker.d.ts +95 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +308 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -1
- package/dist/seed.d.ts +1 -1
- package/dist/seed.js +1 -1
- package/dist/session-flusher.d.ts +4 -4
- package/dist/session-flusher.d.ts.map +1 -1
- package/dist/session-flusher.js +3 -3
- package/dist/spawn-context.d.ts +1 -1
- package/dist/spawn-context.js +1 -1
- package/dist/tool-artifact-store.d.ts +98 -0
- package/dist/tool-artifact-store.d.ts.map +1 -0
- package/dist/tool-artifact-store.js +244 -0
- package/dist/topic-detector.js +2 -2
- package/dist/topic-store.d.ts +6 -0
- package/dist/topic-store.d.ts.map +1 -1
- package/dist/topic-store.js +39 -0
- package/dist/topic-synthesizer.js +1 -1
- package/dist/trigger-registry.d.ts +1 -1
- package/dist/trigger-registry.js +4 -4
- package/dist/types.d.ts +235 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +2 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +3 -0
- package/dist/version.d.ts +10 -10
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -10
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module @psiclawops/hypermem
|
|
5
5
|
*
|
|
6
6
|
* Architecture:
|
|
7
|
-
* L1:
|
|
7
|
+
* L1: CacheLayer — SQLite `:memory:` hot session working memory
|
|
8
8
|
* L2: messages.db — per-agent conversation log (rotatable)
|
|
9
9
|
* L3: vectors.db — per-agent semantic search index (reconstructable)
|
|
10
10
|
* L4: library.db — fleet-wide structured knowledge (crown jewel)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
export { ENGINE_VERSION, MIN_NODE_VERSION, MIN_REDIS_VERSION, SQLITE_VEC_VERSION, MAIN_SCHEMA_VERSION, LIBRARY_SCHEMA_VERSION_EXPORT, HYPERMEM_COMPAT_VERSION, SCHEMA_COMPAT } from './version.js';
|
|
13
13
|
export { DatabaseManager } from './db.js';
|
|
14
14
|
export { MessageStore } from './message-store.js';
|
|
15
|
+
export { ToolArtifactStore } from './tool-artifact-store.js';
|
|
15
16
|
export { FactStore } from './fact-store.js';
|
|
16
17
|
export { KnowledgeStore } from './knowledge-store.js';
|
|
17
18
|
export { TopicStore } from './topic-store.js';
|
|
@@ -20,14 +21,28 @@ export { PreferenceStore } from './preference-store.js';
|
|
|
20
21
|
export { FleetStore } from './fleet-store.js';
|
|
21
22
|
export { SystemStore } from './system-store.js';
|
|
22
23
|
export { WorkStore } from './work-store.js';
|
|
23
|
-
export { ensureContextSchema, getActiveContext, getOrCreateActiveContext, updateContextHead, archiveContext, rotateSessionContext } from './context-store.js';
|
|
24
|
+
export { ensureContextSchema, getActiveContext, getOrCreateActiveContext, updateContextHead, archiveContext, rotateSessionContext, getContextById, getArchivedContexts, getArchivedContext, getContextLineage, getForkChildren } from './context-store.js';
|
|
24
25
|
export { DesiredStateStore } from './desired-state-store.js';
|
|
25
26
|
export { ExpertiseStore } from './expertise-store.js';
|
|
26
27
|
export { evictStaleContent, DEFAULT_EVICTION_CONFIG } from './image-eviction.js';
|
|
27
28
|
export { KnowledgeGraph } from './knowledge-graph.js';
|
|
28
29
|
export { RateLimiter, createRateLimitedEmbedder } from './rate-limiter.js';
|
|
29
30
|
export { CacheLayer } from './cache.js';
|
|
30
|
-
export {
|
|
31
|
+
export { TRIM_SOFT_TARGET, TRIM_GROWTH_THRESHOLD, TRIM_HEADROOM_FRACTION, TRIM_BUDGET_POLICY, resolveTrimBudgets, } from './budget-policy.js';
|
|
32
|
+
// ── Phase C0.2: Canonical degradation contracts ───────────────────────────────
|
|
33
|
+
export {
|
|
34
|
+
// Reason enum + all values
|
|
35
|
+
DEGRADATION_REASONS, DEGRADATION_LIMITS, isDegradationReason, isReplayState,
|
|
36
|
+
// Tool-chain stub
|
|
37
|
+
formatToolChainStub, parseToolChainStub, isToolChainStub,
|
|
38
|
+
// Artifact reference
|
|
39
|
+
formatArtifactRef, parseArtifactRef, isArtifactRef,
|
|
40
|
+
// Replay marker
|
|
41
|
+
formatReplayMarker, parseReplayMarker, isReplayMarker,
|
|
42
|
+
// Generic detector
|
|
43
|
+
isDegradedContent, } from './degradation.js';
|
|
44
|
+
export { REPLAY_RECOVERY_POLICY, decideReplayRecovery, isColdRedisReplay, isReplayRecovered, } from './replay-recovery.js';
|
|
45
|
+
export { Compositor, applyToolGradientToWindow, canPersistReshapedHistory, OPENCLAW_BOOTSTRAP_FILES, resolveToolChainEjections } from './compositor.js';
|
|
31
46
|
export { TRIGGER_REGISTRY, TRIGGER_REGISTRY_VERSION, TRIGGER_REGISTRY_HASH, DEFAULT_TRIGGERS, matchTriggers, } from './trigger-registry.js';
|
|
32
47
|
export { ensureCompactionFenceSchema, updateCompactionFence, getCompactionFence, getCompactionEligibility, getCompactableMessages, } from './compaction-fence.js';
|
|
33
48
|
export { verifyPreservation, verifyPreservationFromVectors, } from './preservation-gate.js';
|
|
@@ -66,12 +81,14 @@ import { KnowledgeGraph } from './knowledge-graph.js';
|
|
|
66
81
|
import { DesiredStateStore } from './desired-state-store.js';
|
|
67
82
|
import { CacheLayer } from './cache.js';
|
|
68
83
|
import { Compositor } from './compositor.js';
|
|
84
|
+
import { getArchivedContexts } from './context-store.js';
|
|
69
85
|
import { VectorStore } from './vector-store.js';
|
|
70
86
|
import { userMessageToNeutral, fromProviderFormat } from './provider-translator.js';
|
|
71
87
|
import { stripMessageMetadata } from './topic-detector.js';
|
|
72
88
|
import { DocChunkStore } from './doc-chunk-store.js';
|
|
73
89
|
import { WorkspaceSeeder } from './seed.js';
|
|
74
90
|
import { crossAgentQuery, buildOrgRegistryFromDb } from './cross-agent.js';
|
|
91
|
+
import fs from 'node:fs';
|
|
75
92
|
import path from 'node:path';
|
|
76
93
|
import os from 'node:os';
|
|
77
94
|
const DEFAULT_CONFIG = {
|
|
@@ -80,7 +97,7 @@ const DEFAULT_CONFIG = {
|
|
|
80
97
|
cache: {
|
|
81
98
|
keyPrefix: 'hm:',
|
|
82
99
|
sessionTTL: 14400, // 4 hours — system/identity/meta slots
|
|
83
|
-
historyTTL: 604800, // 7 days — extended for
|
|
100
|
+
historyTTL: 604800, // 7 days — extended for canvas display
|
|
84
101
|
},
|
|
85
102
|
compositor: {
|
|
86
103
|
// TUNE-010 (2026-04-02): Raised from 65000 → 90000.
|
|
@@ -108,22 +125,214 @@ const DEFAULT_CONFIG = {
|
|
|
108
125
|
maxMessagesPerTick: 500,
|
|
109
126
|
},
|
|
110
127
|
embedding: {
|
|
111
|
-
provider: '
|
|
128
|
+
provider: 'ollama',
|
|
112
129
|
ollamaUrl: 'http://localhost:11434',
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
batchSize: 100,
|
|
130
|
+
model: 'nomic-embed-text',
|
|
131
|
+
dimensions: 768,
|
|
132
|
+
timeout: 10000,
|
|
133
|
+
batchSize: 32,
|
|
118
134
|
},
|
|
119
135
|
};
|
|
136
|
+
function escapeRegExp(value) {
|
|
137
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
138
|
+
}
|
|
139
|
+
function normalizeAgentId(value) {
|
|
140
|
+
return value
|
|
141
|
+
.trim()
|
|
142
|
+
.toLowerCase()
|
|
143
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
144
|
+
.replace(/^-+|-+$/g, '');
|
|
145
|
+
}
|
|
146
|
+
function parseIdentityField(markdown, label) {
|
|
147
|
+
const pattern = new RegExp(`^\\s*-\\s+\\*\\*${escapeRegExp(label)}:\\*\\*\\s*(.+?)\\s*$`, 'mi');
|
|
148
|
+
const match = markdown.match(pattern);
|
|
149
|
+
return match?.[1]?.trim() || null;
|
|
150
|
+
}
|
|
151
|
+
function parseSoulName(markdown) {
|
|
152
|
+
const anchor = markdown.match(/You are \*\*([^*]+)\*\*/);
|
|
153
|
+
if (anchor?.[1])
|
|
154
|
+
return anchor[1].trim();
|
|
155
|
+
const heading = markdown.match(/^#\s+SOUL\.md\s+[—-]\s+([^,\n]+)/m);
|
|
156
|
+
return heading?.[1]?.trim() || null;
|
|
157
|
+
}
|
|
158
|
+
function parseSoulRole(markdown) {
|
|
159
|
+
const anchor = markdown.match(/You are \*\*[^*]+\*\*\s+[—-]\s+([^\.\n]+)/);
|
|
160
|
+
return anchor?.[1]?.trim() || null;
|
|
161
|
+
}
|
|
162
|
+
function inferTierFromRole(role) {
|
|
163
|
+
const lower = (role || '').toLowerCase();
|
|
164
|
+
if (!lower)
|
|
165
|
+
return 'unknown';
|
|
166
|
+
if (lower.includes('council') || lower.includes(' seat'))
|
|
167
|
+
return 'council';
|
|
168
|
+
if (lower.includes('director'))
|
|
169
|
+
return 'director';
|
|
170
|
+
if (lower.includes('specialist') || lower.includes('aide-de-camp'))
|
|
171
|
+
return 'specialist';
|
|
172
|
+
return 'unknown';
|
|
173
|
+
}
|
|
174
|
+
function parseReportTargets(raw) {
|
|
175
|
+
if (!raw)
|
|
176
|
+
return [];
|
|
177
|
+
const stripped = raw.replace(/\([^)]*\)/g, ' ');
|
|
178
|
+
const parts = stripped
|
|
179
|
+
.split(/\s*(?:\+|,|\/|&|\band\b)\s*/i)
|
|
180
|
+
.map(part => normalizeAgentId(part))
|
|
181
|
+
.filter(Boolean);
|
|
182
|
+
return [...new Set(parts)];
|
|
183
|
+
}
|
|
184
|
+
function mergeStartupCandidate(target, partial) {
|
|
185
|
+
const existing = target.get(partial.agentId);
|
|
186
|
+
if (!existing) {
|
|
187
|
+
target.set(partial.agentId, {
|
|
188
|
+
agentId: partial.agentId,
|
|
189
|
+
displayName: partial.displayName || partial.agentId,
|
|
190
|
+
tier: partial.tier || 'unknown',
|
|
191
|
+
orgId: partial.orgId ?? null,
|
|
192
|
+
reportsTo: partial.reportsTo ?? null,
|
|
193
|
+
reportTargets: partial.reportTargets ? [...partial.reportTargets] : [],
|
|
194
|
+
metadata: partial.metadata ?? null,
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (partial.displayName && existing.displayName === existing.agentId) {
|
|
199
|
+
existing.displayName = partial.displayName;
|
|
200
|
+
}
|
|
201
|
+
if (partial.tier && existing.tier === 'unknown') {
|
|
202
|
+
existing.tier = partial.tier;
|
|
203
|
+
}
|
|
204
|
+
if (partial.orgId && !existing.orgId) {
|
|
205
|
+
existing.orgId = partial.orgId;
|
|
206
|
+
}
|
|
207
|
+
if (partial.reportsTo && !existing.reportsTo) {
|
|
208
|
+
existing.reportsTo = partial.reportsTo;
|
|
209
|
+
}
|
|
210
|
+
if (partial.reportTargets?.length) {
|
|
211
|
+
existing.reportTargets = [...new Set([...existing.reportTargets, ...partial.reportTargets])];
|
|
212
|
+
}
|
|
213
|
+
if (partial.metadata) {
|
|
214
|
+
existing.metadata = { ...(existing.metadata ?? {}), ...partial.metadata };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function discoverStartupFleetCandidates(dbManager, opts = {}) {
|
|
218
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
219
|
+
const workspaceRoots = opts.workspaceRoots ?? [
|
|
220
|
+
path.join(homeDir, '.openclaw', 'workspace'),
|
|
221
|
+
path.join(homeDir, '.openclaw', 'workspace'),
|
|
222
|
+
];
|
|
223
|
+
const candidates = new Map();
|
|
224
|
+
for (const root of workspaceRoots) {
|
|
225
|
+
if (!fs.existsSync(root))
|
|
226
|
+
continue;
|
|
227
|
+
let entries = [];
|
|
228
|
+
try {
|
|
229
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
if (!entry.isDirectory())
|
|
236
|
+
continue;
|
|
237
|
+
const workspacePath = path.join(root, entry.name);
|
|
238
|
+
const identityPath = path.join(workspacePath, 'IDENTITY.md');
|
|
239
|
+
const soulPath = path.join(workspacePath, 'SOUL.md');
|
|
240
|
+
if (!fs.existsSync(identityPath) && !fs.existsSync(soulPath))
|
|
241
|
+
continue;
|
|
242
|
+
let identityText = '';
|
|
243
|
+
let soulText = '';
|
|
244
|
+
try {
|
|
245
|
+
if (fs.existsSync(identityPath))
|
|
246
|
+
identityText = fs.readFileSync(identityPath, 'utf8');
|
|
247
|
+
}
|
|
248
|
+
catch { }
|
|
249
|
+
try {
|
|
250
|
+
if (fs.existsSync(soulPath))
|
|
251
|
+
soulText = fs.readFileSync(soulPath, 'utf8');
|
|
252
|
+
}
|
|
253
|
+
catch { }
|
|
254
|
+
const displayName = parseIdentityField(identityText, 'Name') ||
|
|
255
|
+
parseSoulName(soulText) ||
|
|
256
|
+
entry.name;
|
|
257
|
+
const role = parseIdentityField(identityText, 'Role') ||
|
|
258
|
+
parseSoulRole(soulText) ||
|
|
259
|
+
null;
|
|
260
|
+
const reportTargets = parseReportTargets(parseIdentityField(identityText, 'Reports to'));
|
|
261
|
+
const agentId = normalizeAgentId(entry.name || displayName);
|
|
262
|
+
if (!agentId)
|
|
263
|
+
continue;
|
|
264
|
+
mergeStartupCandidate(candidates, {
|
|
265
|
+
agentId,
|
|
266
|
+
displayName,
|
|
267
|
+
tier: inferTierFromRole(role),
|
|
268
|
+
reportTargets,
|
|
269
|
+
metadata: {
|
|
270
|
+
startupSeed: {
|
|
271
|
+
source: 'workspace-identity',
|
|
272
|
+
workspacePath,
|
|
273
|
+
reportsToRaw: reportTargets,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (opts.includeMessageDbAgents !== false) {
|
|
280
|
+
for (const agentId of dbManager.listAgents()) {
|
|
281
|
+
mergeStartupCandidate(candidates, {
|
|
282
|
+
agentId,
|
|
283
|
+
displayName: agentId,
|
|
284
|
+
tier: 'unknown',
|
|
285
|
+
metadata: {
|
|
286
|
+
startupSeed: {
|
|
287
|
+
source: 'message-db',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const resolved = [...candidates.values()];
|
|
294
|
+
const knownIds = new Set(resolved.map(candidate => candidate.agentId));
|
|
295
|
+
const councilIds = new Set(resolved
|
|
296
|
+
.filter(candidate => candidate.tier === 'council')
|
|
297
|
+
.map(candidate => candidate.agentId));
|
|
298
|
+
for (const candidate of resolved) {
|
|
299
|
+
const preferredLead = candidate.reportTargets.find(target => councilIds.has(target)) ||
|
|
300
|
+
candidate.reportTargets.find(target => knownIds.has(target)) ||
|
|
301
|
+
null;
|
|
302
|
+
if (candidate.tier === 'council' && !candidate.orgId) {
|
|
303
|
+
candidate.orgId = `${candidate.agentId}-org`;
|
|
304
|
+
}
|
|
305
|
+
if (candidate.tier === 'director' && preferredLead) {
|
|
306
|
+
candidate.reportsTo = preferredLead;
|
|
307
|
+
if (!candidate.orgId) {
|
|
308
|
+
candidate.orgId = `${preferredLead}-org`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!candidate.reportsTo && preferredLead && candidate.tier !== 'director') {
|
|
312
|
+
candidate.reportsTo = preferredLead;
|
|
313
|
+
}
|
|
314
|
+
// Null out reportsTo if it points outside the known fleet (e.g. a human operator ID)
|
|
315
|
+
if (candidate.reportsTo && !knownIds.has(candidate.reportsTo)) {
|
|
316
|
+
candidate.reportsTo = null;
|
|
317
|
+
}
|
|
318
|
+
candidate.metadata = {
|
|
319
|
+
...(candidate.metadata ?? {}),
|
|
320
|
+
startupSeed: {
|
|
321
|
+
...(candidate.metadata ?? {}).startupSeed,
|
|
322
|
+
derivedOrgId: candidate.orgId,
|
|
323
|
+
derivedReportsTo: candidate.reportsTo,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return resolved.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
328
|
+
}
|
|
120
329
|
/**
|
|
121
330
|
* hypermem — the main API facade.
|
|
122
331
|
*
|
|
123
332
|
* Usage:
|
|
124
333
|
* const hm = await hypermem.create({ dataDir: '~/.openclaw/hypermem' });
|
|
125
|
-
* await hm.record('
|
|
126
|
-
* const result = await hm.compose({ agentId: '
|
|
334
|
+
* await hm.record('alice', 'agent:alice:webchat:main', userMsg);
|
|
335
|
+
* const result = await hm.compose({ agentId: 'alice', sessionKey: '...', ... });
|
|
127
336
|
*/
|
|
128
337
|
export class HyperMem {
|
|
129
338
|
dbManager;
|
|
@@ -176,23 +385,43 @@ export class HyperMem {
|
|
|
176
385
|
// hybridSearch() continues in FTS5-only mode.
|
|
177
386
|
// The vector store is shared (not per-agent) — facts/episodes from all agents
|
|
178
387
|
// are indexed together, keyed by (source_table, source_id).
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
hm.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
388
|
+
if (merged.embedding.provider === 'none') {
|
|
389
|
+
console.log('[hypermem] Embedding provider: none — semantic search disabled, using FTS5 fallback');
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
try {
|
|
393
|
+
const vectorDb = hm.dbManager.getSharedVectorDb();
|
|
394
|
+
if (vectorDb) {
|
|
395
|
+
const vs = new VectorStore(vectorDb, merged.embedding, hm.dbManager.getLibraryDb());
|
|
396
|
+
vs.ensureTables();
|
|
397
|
+
hm.compositor.setVectorStore(vs);
|
|
398
|
+
const embeddingDesc = merged.embedding.provider === 'openai'
|
|
399
|
+
? `${merged.embedding.openaiBaseUrl?.includes('openrouter') ? 'openrouter' : 'openai'}/${merged.embedding.model ?? 'text-embedding-3-small'}`
|
|
400
|
+
: `ollama/${merged.embedding.model ?? 'nomic-embed-text'}`;
|
|
401
|
+
console.log(`[hypermem] Vector store initialized (sqlite-vec + ${embeddingDesc})`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.warn('[hypermem] sqlite-vec unavailable — semantic recall in FTS5-only mode');
|
|
405
|
+
}
|
|
189
406
|
}
|
|
190
|
-
|
|
191
|
-
console.warn('[hypermem]
|
|
407
|
+
catch (err) {
|
|
408
|
+
console.warn('[hypermem] Vector store init failed (non-fatal):', err.message);
|
|
192
409
|
}
|
|
193
410
|
}
|
|
194
|
-
|
|
195
|
-
|
|
411
|
+
const autoStartupFleetSeeding = merged.startupFleetSeeding !== false &&
|
|
412
|
+
!path.resolve(merged.dataDir).startsWith(path.resolve(os.tmpdir()) + path.sep);
|
|
413
|
+
if (autoStartupFleetSeeding) {
|
|
414
|
+
try {
|
|
415
|
+
const startupSeed = await hm.seedFleetAgentsOnStartup();
|
|
416
|
+
if (startupSeed.discovered > 0) {
|
|
417
|
+
console.log(`[hypermem] Startup fleet seed: ${startupSeed.inserted} inserted, ` +
|
|
418
|
+
`${startupSeed.updated} updated, ${startupSeed.skipped} unchanged, ` +
|
|
419
|
+
`${startupSeed.orgsCreated} orgs, ${startupSeed.hydratedAgents} cached`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
console.warn('[hypermem] Startup fleet seed failed (non-fatal):', err.message);
|
|
424
|
+
}
|
|
196
425
|
}
|
|
197
426
|
return hm;
|
|
198
427
|
}
|
|
@@ -267,6 +496,67 @@ export class HyperMem {
|
|
|
267
496
|
const libraryDb = this.dbManager.getLibraryDb();
|
|
268
497
|
return this.compositor.compose(request, db, libraryDb);
|
|
269
498
|
}
|
|
499
|
+
// ─── Tool Artifacts (L2: per-agent, schema v9) ───────────────────
|
|
500
|
+
/**
|
|
501
|
+
* Persist a full tool result payload and return the durable record.
|
|
502
|
+
* Used by the plugin wave-guard to capture payloads before stubbing the
|
|
503
|
+
* transcript. Dedupes by content hash within the (agentId, sessionKey)
|
|
504
|
+
* scope — identical payloads bump ref_count on the existing row.
|
|
505
|
+
*/
|
|
506
|
+
async recordToolArtifact(agentId, sessionKey, input) {
|
|
507
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
508
|
+
this.dbManager.ensureAgent(agentId);
|
|
509
|
+
const { ToolArtifactStore } = await import('./tool-artifact-store.js');
|
|
510
|
+
const store = new ToolArtifactStore(db);
|
|
511
|
+
return store.put({ ...input, agentId, sessionKey });
|
|
512
|
+
}
|
|
513
|
+
/** Fetch a tool artifact by id. Returns null if unknown. */
|
|
514
|
+
async getToolArtifact(agentId, artifactId) {
|
|
515
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
516
|
+
const { ToolArtifactStore } = await import('./tool-artifact-store.js');
|
|
517
|
+
const store = new ToolArtifactStore(db);
|
|
518
|
+
const record = store.get(artifactId);
|
|
519
|
+
if (record)
|
|
520
|
+
store.touch(artifactId);
|
|
521
|
+
return record;
|
|
522
|
+
}
|
|
523
|
+
/** List tool artifacts for a specific turn. */
|
|
524
|
+
async listToolArtifactsByTurn(agentId, sessionKey, turnId) {
|
|
525
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
526
|
+
const { ToolArtifactStore } = await import('./tool-artifact-store.js');
|
|
527
|
+
const store = new ToolArtifactStore(db);
|
|
528
|
+
return store.listByTurn(sessionKey, turnId);
|
|
529
|
+
}
|
|
530
|
+
// ─── Archived Mining (L2: Messages) ─────────────────────────
|
|
531
|
+
/**
|
|
532
|
+
* List archived or forked contexts for an agent.
|
|
533
|
+
*
|
|
534
|
+
* operator-safe enumeration path. This is the approved archived-context
|
|
535
|
+
* listing surface. Active composition remains separate.
|
|
536
|
+
*/
|
|
537
|
+
listArchivedContexts(agentId, opts) {
|
|
538
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
539
|
+
return getArchivedContexts(db, agentId, opts);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Mine a single archived or forked context through the archived-mining
|
|
543
|
+
* surface. This does not widen active composition.
|
|
544
|
+
*/
|
|
545
|
+
mineArchivedContext(agentId, query) {
|
|
546
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
547
|
+
const store = new MessageStore(db);
|
|
548
|
+
return store.mineArchivedContext(query);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Mine multiple archived or forked contexts through the capped archived-
|
|
552
|
+
* mining surface. This does not expose raw DAG helpers and does not widen
|
|
553
|
+
* active composition.
|
|
554
|
+
*/
|
|
555
|
+
mineArchivedContexts(agentId, contextIds, opts) {
|
|
556
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
557
|
+
const store = new MessageStore(db);
|
|
558
|
+
return store.mineArchivedContexts(contextIds, opts);
|
|
559
|
+
}
|
|
270
560
|
/**
|
|
271
561
|
* Warm a session from SQLite into Redis.
|
|
272
562
|
*/
|
|
@@ -278,9 +568,9 @@ export class HyperMem {
|
|
|
278
568
|
/**
|
|
279
569
|
* Recompute the Redis hot history view from SQLite and re-apply tool gradient.
|
|
280
570
|
*/
|
|
281
|
-
async refreshRedisGradient(agentId, sessionKey, tokenBudget) {
|
|
571
|
+
async refreshRedisGradient(agentId, sessionKey, tokenBudget, historyDepth) {
|
|
282
572
|
const db = this.dbManager.getMessageDb(agentId);
|
|
283
|
-
await this.compositor.refreshRedisGradient(agentId, sessionKey, db, tokenBudget);
|
|
573
|
+
await this.compositor.refreshRedisGradient(agentId, sessionKey, db, tokenBudget, historyDepth);
|
|
284
574
|
}
|
|
285
575
|
/**
|
|
286
576
|
* Full-text search across all messages for an agent.
|
|
@@ -1010,7 +1300,93 @@ export class HyperMem {
|
|
|
1010
1300
|
const store = new DocChunkStore(db);
|
|
1011
1301
|
return store.listSources(opts);
|
|
1012
1302
|
}
|
|
1013
|
-
// ─── Fleet Cache Hydration
|
|
1303
|
+
// ─── Fleet Startup Seeding + Cache Hydration ────────────────
|
|
1304
|
+
/**
|
|
1305
|
+
* Seed fleet agents from known workspace identity files and existing
|
|
1306
|
+
* message-db agent directories, then optionally hydrate the Redis fleet cache.
|
|
1307
|
+
*
|
|
1308
|
+
* The sweep is idempotent: existing rows are only updated when startup-discovered
|
|
1309
|
+
* values differ, so repeated boots do not duplicate or churn fleet rows.
|
|
1310
|
+
*/
|
|
1311
|
+
async seedFleetAgentsOnStartup(opts = {}) {
|
|
1312
|
+
const db = this.dbManager.getLibraryDb();
|
|
1313
|
+
const store = new FleetStore(db);
|
|
1314
|
+
const discovered = discoverStartupFleetCandidates(this.dbManager, opts);
|
|
1315
|
+
let inserted = 0;
|
|
1316
|
+
let updated = 0;
|
|
1317
|
+
let skipped = 0;
|
|
1318
|
+
let orgsCreated = 0;
|
|
1319
|
+
for (const candidate of discovered) {
|
|
1320
|
+
const existing = store.getAgent(candidate.agentId);
|
|
1321
|
+
const mergedMetadata = {
|
|
1322
|
+
...(existing?.metadata ?? {}),
|
|
1323
|
+
...(candidate.metadata ?? {}),
|
|
1324
|
+
};
|
|
1325
|
+
if (!existing) {
|
|
1326
|
+
store.upsertAgent(candidate.agentId, {
|
|
1327
|
+
displayName: candidate.displayName,
|
|
1328
|
+
tier: candidate.tier,
|
|
1329
|
+
orgId: candidate.orgId ?? undefined,
|
|
1330
|
+
reportsTo: candidate.reportsTo ?? undefined,
|
|
1331
|
+
status: 'active',
|
|
1332
|
+
metadata: Object.keys(mergedMetadata).length > 0 ? mergedMetadata : undefined,
|
|
1333
|
+
});
|
|
1334
|
+
inserted++;
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
const patch = {};
|
|
1338
|
+
if (candidate.displayName && candidate.displayName !== existing.displayName) {
|
|
1339
|
+
patch.displayName = candidate.displayName;
|
|
1340
|
+
}
|
|
1341
|
+
if (candidate.tier && candidate.tier !== 'unknown' && candidate.tier !== existing.tier) {
|
|
1342
|
+
patch.tier = candidate.tier;
|
|
1343
|
+
}
|
|
1344
|
+
if (candidate.orgId && candidate.orgId !== existing.orgId) {
|
|
1345
|
+
patch.orgId = candidate.orgId;
|
|
1346
|
+
}
|
|
1347
|
+
if (candidate.reportsTo && candidate.reportsTo !== existing.reportsTo) {
|
|
1348
|
+
patch.reportsTo = candidate.reportsTo;
|
|
1349
|
+
}
|
|
1350
|
+
if (JSON.stringify(mergedMetadata) !== JSON.stringify(existing.metadata ?? {})) {
|
|
1351
|
+
patch.metadata = mergedMetadata;
|
|
1352
|
+
}
|
|
1353
|
+
if (Object.keys(patch).length === 0) {
|
|
1354
|
+
skipped++;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
store.upsertAgent(candidate.agentId, patch);
|
|
1358
|
+
updated++;
|
|
1359
|
+
}
|
|
1360
|
+
for (const candidate of discovered) {
|
|
1361
|
+
if (!candidate.orgId)
|
|
1362
|
+
continue;
|
|
1363
|
+
if (store.getOrg(candidate.orgId))
|
|
1364
|
+
continue;
|
|
1365
|
+
if (candidate.tier !== 'council')
|
|
1366
|
+
continue;
|
|
1367
|
+
store.upsertOrg(candidate.orgId, {
|
|
1368
|
+
name: `${candidate.displayName} Org`,
|
|
1369
|
+
leadAgentId: candidate.agentId,
|
|
1370
|
+
});
|
|
1371
|
+
orgsCreated++;
|
|
1372
|
+
}
|
|
1373
|
+
let hydratedAgents = 0;
|
|
1374
|
+
let hydratedSummary = false;
|
|
1375
|
+
if (opts.hydrateCache !== false) {
|
|
1376
|
+
const hydrated = await this.hydrateFleetCache();
|
|
1377
|
+
hydratedAgents = hydrated.agents;
|
|
1378
|
+
hydratedSummary = hydrated.summary;
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
discovered: discovered.length,
|
|
1382
|
+
inserted,
|
|
1383
|
+
updated,
|
|
1384
|
+
skipped,
|
|
1385
|
+
orgsCreated,
|
|
1386
|
+
hydratedAgents,
|
|
1387
|
+
hydratedSummary,
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1014
1390
|
/**
|
|
1015
1391
|
* Hydrate the Redis fleet cache from library.db.
|
|
1016
1392
|
* Call on gateway startup to warm the cache for dashboard queries.
|
package/dist/knowledge-lint.d.ts
CHANGED
|
@@ -10,8 +10,10 @@ import type { DatabaseSync } from 'node:sqlite';
|
|
|
10
10
|
export interface LintResult {
|
|
11
11
|
staleDecayed: number;
|
|
12
12
|
orphansFound: number;
|
|
13
|
+
orphansPruned: number;
|
|
13
14
|
coverageGaps: string[];
|
|
14
15
|
}
|
|
16
|
+
export declare const ORPHAN_PRUNE_DAYS = 14;
|
|
15
17
|
/**
|
|
16
18
|
* Run lint checks on the knowledge table.
|
|
17
19
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-lint.d.ts","sourceRoot":"","sources":["../src/knowledge-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"knowledge-lint.d.ts","sourceRoot":"","sources":["../src/knowledge-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAID,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAIpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,CA8IjE"}
|
package/dist/knowledge-lint.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* 3. Coverage gaps — topics with many messages but no synthesis
|
|
8
8
|
*/
|
|
9
9
|
import { LINT_STALE_DAYS } from './topic-synthesizer.js';
|
|
10
|
+
// Orphan topics older than this many days are eligible for pruning.
|
|
11
|
+
// 48h = report threshold, 14d = prune threshold (conservative).
|
|
12
|
+
export const ORPHAN_PRUNE_DAYS = 14;
|
|
10
13
|
// ─── lintKnowledge ──────────────────────────────────────────────
|
|
11
14
|
/**
|
|
12
15
|
* Run lint checks on the knowledge table.
|
|
@@ -25,6 +28,7 @@ export function lintKnowledge(libraryDb) {
|
|
|
25
28
|
const result = {
|
|
26
29
|
staleDecayed: 0,
|
|
27
30
|
orphansFound: 0,
|
|
31
|
+
orphansPruned: 0,
|
|
28
32
|
coverageGaps: [],
|
|
29
33
|
};
|
|
30
34
|
// ── 1. Stale syntheses ─────────────────────────────────────────
|
|
@@ -80,7 +84,42 @@ export function lintKnowledge(libraryDb) {
|
|
|
80
84
|
`).all();
|
|
81
85
|
result.orphansFound = orphans.length;
|
|
82
86
|
if (orphans.length > 0) {
|
|
83
|
-
|
|
87
|
+
const sample = orphans.slice(0, 10).map(o => o.name).join(', ');
|
|
88
|
+
const remainder = Math.max(0, orphans.length - 10);
|
|
89
|
+
console.log(`[lint] ${orphans.length} orphan topic(s) found (< 3 messages, stale > 48h)` +
|
|
90
|
+
(sample ? `; sample: ${sample}` : '') +
|
|
91
|
+
(remainder > 0 ? `; +${remainder} more` : ''));
|
|
92
|
+
}
|
|
93
|
+
// Prune orphan topics older than ORPHAN_PRUNE_DAYS (conservative cleanup).
|
|
94
|
+
// Safety: only prune topics with no knowledge-synthesis entries and no facts
|
|
95
|
+
// referencing them (via source_ref 'topic:<id>' pattern).
|
|
96
|
+
try {
|
|
97
|
+
const prunable = libraryDb.prepare(`
|
|
98
|
+
SELECT t.id, t.name FROM topics t
|
|
99
|
+
WHERE t.message_count < 3
|
|
100
|
+
AND t.updated_at < datetime('now', '-' || ? || ' days')
|
|
101
|
+
AND NOT EXISTS (
|
|
102
|
+
SELECT 1 FROM knowledge k
|
|
103
|
+
WHERE k.agent_id = t.agent_id
|
|
104
|
+
AND k.domain = 'topic-synthesis'
|
|
105
|
+
AND k.key = t.name
|
|
106
|
+
AND k.superseded_by IS NULL
|
|
107
|
+
)
|
|
108
|
+
AND NOT EXISTS (
|
|
109
|
+
SELECT 1 FROM facts f
|
|
110
|
+
WHERE f.source_ref LIKE 'topic:' || t.id || '%'
|
|
111
|
+
)
|
|
112
|
+
`).all(ORPHAN_PRUNE_DAYS);
|
|
113
|
+
if (prunable.length > 0) {
|
|
114
|
+
const ids = prunable.map(p => p.id);
|
|
115
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
116
|
+
libraryDb.prepare(`DELETE FROM topics WHERE id IN (${placeholders})`).run(...ids);
|
|
117
|
+
result.orphansPruned = prunable.length;
|
|
118
|
+
console.log(`[lint] pruned ${prunable.length} orphan topic(s) (< 3 messages, stale > ${ORPHAN_PRUNE_DAYS}d, no syntheses or facts)`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.warn('[lint] orphan prune failed (non-fatal):', err.message);
|
|
84
123
|
}
|
|
85
124
|
}
|
|
86
125
|
catch {
|
package/dist/library-schema.d.ts
CHANGED
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
* 9. Work items (fleet kanban)
|
|
17
17
|
* 10. Topics (cross-session thread tracking)
|
|
18
18
|
*/
|
|
19
|
-
import
|
|
20
|
-
export declare const LIBRARY_SCHEMA_VERSION =
|
|
19
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
20
|
+
export declare const LIBRARY_SCHEMA_VERSION = 19;
|
|
21
|
+
export declare function repairLibraryDb(dbPath: string): {
|
|
22
|
+
repaired: boolean;
|
|
23
|
+
backupPath?: string;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
21
26
|
export declare function migrateLibrary(db: DatabaseSync, engineVersion?: string): void;
|
|
22
27
|
//# sourceMappingURL=library-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"library-schema.d.ts","sourceRoot":"","sources":["../src/library-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"library-schema.d.ts","sourceRoot":"","sources":["../src/library-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAo6BzC,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAkF3G;AAID,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAuZ7E"}
|