@botbotgo/agent-harness 0.0.281 → 0.0.283

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +20 -9
  2. package/README.zh.md +20 -9
  3. package/dist/config/catalogs/stores.yaml +1 -1
  4. package/dist/config/catalogs/vector-stores.yaml +1 -1
  5. package/dist/config/knowledge/knowledge-runtime.yaml +60 -0
  6. package/dist/config/runtime/runtime-memory.yaml +3 -3
  7. package/dist/contracts/runtime.d.ts +9 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +1 -0
  10. package/dist/init-project.js +50 -4
  11. package/dist/knowledge/config.d.ts +11 -0
  12. package/dist/knowledge/config.js +32 -0
  13. package/dist/knowledge/contracts.d.ts +45 -0
  14. package/dist/knowledge/contracts.js +1 -0
  15. package/dist/knowledge/index.d.ts +4 -0
  16. package/dist/knowledge/index.js +2 -0
  17. package/dist/knowledge/module.d.ts +21 -0
  18. package/dist/knowledge/module.js +594 -0
  19. package/dist/package-version.d.ts +1 -1
  20. package/dist/package-version.js +1 -1
  21. package/dist/persistence/file-store.d.ts +1 -0
  22. package/dist/persistence/file-store.js +29 -25
  23. package/dist/persistence/sqlite-store.js +1 -1
  24. package/dist/runtime/harness/run/resources.js +4 -2
  25. package/dist/runtime/harness/system/runtime-memory-policy.js +1 -1
  26. package/dist/runtime/harness/system/runtime-memory-records.js +4 -0
  27. package/dist/runtime/harness/system/runtime-memory-sync.js +2 -2
  28. package/dist/runtime/harness/system/thread-memory-sync.js +1 -1
  29. package/dist/runtime/harness.d.ts +1 -16
  30. package/dist/runtime/harness.js +95 -613
  31. package/dist/runtime/maintenance/checkpoint-maintenance.js +2 -1
  32. package/dist/runtime/maintenance/runtime-record-maintenance.js +2 -1
  33. package/dist/runtime/support/llamaindex.js +1 -1
  34. package/dist/runtime/support/runtime-factories.js +6 -3
  35. package/dist/runtime/support/runtime-layout.d.ts +6 -0
  36. package/dist/runtime/support/runtime-layout.js +19 -0
  37. package/package.json +1 -1
@@ -0,0 +1,594 @@
1
+ import { createPersistentId } from "../utils/id.js";
2
+ import { findMemoryRecordById, getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, removeMemoryRecord, updateMemoryRecord, } from "../runtime/harness/system/runtime-memory-records.js";
3
+ import { consolidateStructuredMemoryScope } from "../runtime/harness/system/runtime-memory-consolidation.js";
4
+ import { renderMemoryCandidatesMarkdown } from "../runtime/harness/system/runtime-memory-candidates.js";
5
+ import { normalizeLangMemMemoryKind, scoreMemoryText, } from "../runtime/harness/system/runtime-memory-policy.js";
6
+ const ALL_SCOPES = ["thread", "agent", "workspace", "user", "project"];
7
+ const TITLE_BY_SCOPE = {
8
+ thread: "Thread Structured Memory",
9
+ agent: "Agent Structured Memory",
10
+ workspace: "Workspace Structured Memory",
11
+ user: "User Structured Memory",
12
+ project: "Project Structured Memory",
13
+ };
14
+ function normalizeRuntimeContext(context) {
15
+ if (typeof context.agentId !== "string" || context.agentId.trim().length === 0) {
16
+ throw new Error("knowledge operations require agentId in runtime context.");
17
+ }
18
+ return {
19
+ ...context,
20
+ agentId: context.agentId,
21
+ userId: context.userId ?? "default",
22
+ projectId: context.projectId ?? context.workspaceId,
23
+ };
24
+ }
25
+ function summarizeMemoryContent(content) {
26
+ return (content.trim().split("\n")[0] || content.trim()).slice(0, 240);
27
+ }
28
+ function resolveRecallScopes(input, context) {
29
+ if (Array.isArray(input.scopes) && input.scopes.length > 0) {
30
+ return Array.from(new Set(input.scopes));
31
+ }
32
+ const scopes = new Set(["thread", "agent", "workspace"]);
33
+ if (context.userId) {
34
+ scopes.add("user");
35
+ }
36
+ if (context.projectId) {
37
+ scopes.add("project");
38
+ }
39
+ return Array.from(scopes);
40
+ }
41
+ function matchesMemoryFilters(record, filters) {
42
+ const sessionId = String(record.provenance.sessionId ?? record.provenance.threadId ?? "");
43
+ if (filters.threadId && sessionId !== filters.threadId) {
44
+ return false;
45
+ }
46
+ if (filters.agentId && record.provenance.agentId !== filters.agentId) {
47
+ return false;
48
+ }
49
+ if (filters.workspaceId && record.provenance.workspaceId !== filters.workspaceId) {
50
+ return false;
51
+ }
52
+ if (filters.userId && record.provenance.userId !== filters.userId) {
53
+ return false;
54
+ }
55
+ if (filters.projectId && record.provenance.projectId !== filters.projectId) {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ function matchesRecallScope(record, filters) {
61
+ if (record.scope === "thread") {
62
+ return typeof filters.threadId === "string"
63
+ && String(record.provenance.sessionId ?? record.provenance.threadId ?? "") === filters.threadId;
64
+ }
65
+ if (record.scope === "agent") {
66
+ return String(record.provenance.agentId ?? "") === filters.agentId;
67
+ }
68
+ if (record.scope === "workspace") {
69
+ return String(record.provenance.workspaceId ?? filters.workspaceId) === filters.workspaceId;
70
+ }
71
+ if (record.scope === "user") {
72
+ return String(record.provenance.userId ?? "default") === filters.userId;
73
+ }
74
+ return String(record.provenance.projectId ?? filters.workspaceId) === filters.projectId;
75
+ }
76
+ function getMemoryScopeBoost(scope) {
77
+ if (scope === "thread") {
78
+ return 4;
79
+ }
80
+ if (scope === "agent") {
81
+ return 2;
82
+ }
83
+ if (scope === "workspace") {
84
+ return 1;
85
+ }
86
+ return 0;
87
+ }
88
+ function memoryFreshnessBoost(value) {
89
+ return Math.max(0, 1 - ((Date.now() - Date.parse(value)) / (1000 * 60 * 60 * 24 * 365)));
90
+ }
91
+ function scoreStructuredRecord(query, record) {
92
+ return (scoreMemoryText(query, `${record.summary}\n${record.content}`, getMemoryScopeBoost(record.scope)) +
93
+ memoryFreshnessBoost(record.lastConfirmedAt) +
94
+ record.confidence);
95
+ }
96
+ function normalizeMemoryDedupKey(record) {
97
+ return `${record.scope}::${record.summary.toLowerCase().replace(/\s+/g, " ").trim()}::${record.content.toLowerCase().replace(/\s+/g, " ").trim()}`;
98
+ }
99
+ function inferMem0MemoryKind(hit) {
100
+ const candidates = [
101
+ ...hit.categories,
102
+ typeof hit.metadata.kind === "string" ? hit.metadata.kind : undefined,
103
+ typeof hit.metadata.memoryType === "string" ? hit.metadata.memoryType : undefined,
104
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
105
+ for (const candidate of candidates) {
106
+ const kind = normalizeLangMemMemoryKind(candidate);
107
+ if (kind === "semantic" || kind === "episodic" || kind === "procedural") {
108
+ return kind;
109
+ }
110
+ }
111
+ return "semantic";
112
+ }
113
+ function inferMem0MemoryScope(hit, filters) {
114
+ const metadataScope = typeof hit.metadata.scope === "string" ? hit.metadata.scope : undefined;
115
+ if (metadataScope === "thread" || metadataScope === "agent" || metadataScope === "workspace" || metadataScope === "user" || metadataScope === "project") {
116
+ return metadataScope;
117
+ }
118
+ if (typeof hit.metadata.threadId === "string" && filters.threadId && hit.metadata.threadId === filters.threadId) {
119
+ return "thread";
120
+ }
121
+ if (hit.agentId === filters.agentId || hit.metadata.agentId === filters.agentId) {
122
+ return "agent";
123
+ }
124
+ return "workspace";
125
+ }
126
+ function createMem0MemoryRecord(hit, filters) {
127
+ const scope = inferMem0MemoryScope(hit, filters);
128
+ const kind = inferMem0MemoryKind(hit);
129
+ const summary = hit.memory.split("\n")[0]?.trim() || hit.memory;
130
+ return {
131
+ id: `mem0:${hit.id}`,
132
+ canonicalKey: `mem0:${hit.id}`,
133
+ kind,
134
+ scope,
135
+ content: hit.memory,
136
+ summary: summary.slice(0, 240),
137
+ status: "active",
138
+ confidence: Math.max(0, Math.min(1, hit.score || 0.5)),
139
+ createdAt: hit.createdAt,
140
+ observedAt: hit.createdAt,
141
+ lastConfirmedAt: hit.updatedAt,
142
+ sourceType: "mem0-search",
143
+ sourceRefs: [`mem0:${hit.id}`],
144
+ tags: hit.categories,
145
+ provenance: {
146
+ source: "mem0",
147
+ sessionId: typeof hit.metadata.sessionId === "string"
148
+ ? hit.metadata.sessionId
149
+ : typeof hit.metadata.threadId === "string"
150
+ ? hit.metadata.threadId
151
+ : filters.threadId,
152
+ requestId: (typeof hit.metadata.requestId === "string"
153
+ ? hit.metadata.requestId
154
+ : typeof hit.metadata.runId === "string"
155
+ ? hit.metadata.runId
156
+ : undefined),
157
+ agentId: hit.agentId ?? (typeof hit.metadata.agentId === "string" ? hit.metadata.agentId : filters.agentId),
158
+ workspaceId: filters.workspaceId,
159
+ userId: filters.userId,
160
+ projectId: filters.projectId,
161
+ ...hit.metadata,
162
+ },
163
+ revision: 1,
164
+ supersedes: [],
165
+ conflictsWith: [],
166
+ };
167
+ }
168
+ export class DefaultKnowledgeModule {
169
+ deps;
170
+ constructor(deps) {
171
+ this.deps = deps;
172
+ }
173
+ async memorize(input, context) {
174
+ if (!Array.isArray(input.records)) {
175
+ throw new Error("memorize requires input.records to be an array.");
176
+ }
177
+ const normalizedContext = normalizeRuntimeContext(context);
178
+ const candidates = input.records
179
+ .filter((record) => typeof record === "object" && record !== null)
180
+ .filter((record) => record.noStore !== true);
181
+ if (candidates.length === 0) {
182
+ return { records: [], decisions: [] };
183
+ }
184
+ if (candidates.some((record) => typeof record.content !== "string" || record.content.trim().length === 0)) {
185
+ throw new Error("memorize requires every record to include non-empty content.");
186
+ }
187
+ if (candidates.some((record) => (record.scope ?? "thread") === "thread") && !normalizedContext.sessionId) {
188
+ throw new Error("memorize requires sessionId when storing thread-scoped memory.");
189
+ }
190
+ const recordedAt = normalizedContext.recordedAt ?? new Date().toISOString();
191
+ const requestId = normalizedContext.requestId ?? createPersistentId(new Date(recordedAt));
192
+ const sessionId = normalizedContext.sessionId ?? `memory-api-${requestId}`;
193
+ return this.memorizeCandidates(candidates.map((record) => ({ ...record, content: record.content.trim() })), {
194
+ ...normalizedContext,
195
+ requestId,
196
+ sessionId,
197
+ recordedAt,
198
+ }, { storeCandidateLog: input.storeCandidateLog !== false });
199
+ }
200
+ async recall(input, context) {
201
+ if (typeof input.query !== "string" || input.query.trim().length === 0) {
202
+ throw new Error("recall requires a non-empty query.");
203
+ }
204
+ const normalizedContext = normalizeRuntimeContext(context);
205
+ const scopes = resolveRecallScopes(input, normalizedContext);
206
+ const topK = typeof input.topK === "number" && Number.isInteger(input.topK) && input.topK > 0
207
+ ? input.topK
208
+ : (this.deps.policy?.retrieval.defaultTopK ?? 5);
209
+ const kinds = input.kinds?.length ? new Set(input.kinds) : null;
210
+ const items = (await this.rankRecallCandidates({
211
+ query: input.query.trim(),
212
+ scopes,
213
+ kinds,
214
+ topK,
215
+ includeStale: input.includeStale === true,
216
+ context: normalizedContext,
217
+ }))
218
+ .slice(0, topK)
219
+ .map(({ record }) => record);
220
+ return { items };
221
+ }
222
+ async buildPromptContext(input, context) {
223
+ const normalizedContext = normalizeRuntimeContext(context);
224
+ const ranked = (await this.rankRecallCandidates({
225
+ query: input.query,
226
+ scopes: input.scopes ?? ALL_SCOPES,
227
+ kinds: input.kinds?.length ? new Set(input.kinds) : null,
228
+ topK: input.topK ?? this.deps.policy?.retrieval.maxPromptMemories ?? 8,
229
+ includeStale: input.includeStale === true,
230
+ context: normalizedContext,
231
+ }))
232
+ .map((item) => {
233
+ const scopeBoost = item.record.scope === "thread"
234
+ ? 4
235
+ : item.record.scope === "agent"
236
+ ? 3
237
+ : item.record.scope === "user"
238
+ ? 2
239
+ : 1;
240
+ return {
241
+ content: [
242
+ `# Durable ${item.record.scope[0].toUpperCase()}${item.record.scope.slice(1)} Memory`,
243
+ "",
244
+ `- summary: ${item.record.summary}`,
245
+ `- kind: ${item.record.kind}`,
246
+ `- scope: ${item.record.scope}`,
247
+ `- confidence: ${item.record.confidence.toFixed(2)}`,
248
+ "",
249
+ item.record.content,
250
+ ].join("\n"),
251
+ score: scoreMemoryText(input.query, `${item.record.summary}\n${item.record.content}`, scopeBoost) +
252
+ memoryFreshnessBoost(item.record.lastConfirmedAt) +
253
+ item.record.confidence,
254
+ };
255
+ })
256
+ .sort((left, right) => right.score - left.score)
257
+ .slice(0, this.deps.policy?.retrieval.maxPromptMemories ?? 8);
258
+ if (ranked.length === 0) {
259
+ return undefined;
260
+ }
261
+ return ranked.map((entry) => entry.content).join("\n\n");
262
+ }
263
+ async list(input = {}, context) {
264
+ const normalizedContext = { ...context };
265
+ const scopes = Array.isArray(input.scopes) && input.scopes.length > 0
266
+ ? Array.from(new Set(input.scopes))
267
+ : ALL_SCOPES;
268
+ const kinds = input.kinds?.length ? new Set(input.kinds) : null;
269
+ const statuses = input.status?.length ? new Set(input.status) : new Set(["active"]);
270
+ const limit = typeof input.limit === "number" && Number.isInteger(input.limit) && input.limit > 0
271
+ ? input.limit
272
+ : Number.POSITIVE_INFINITY;
273
+ const items = (await listMemoryRecordsForScopes(this.deps.store, scopes))
274
+ .filter((record) => statuses.has(record.status))
275
+ .filter((record) => !kinds || kinds.has(record.kind))
276
+ .filter((record) => matchesMemoryFilters(record, {
277
+ threadId: normalizedContext.sessionId,
278
+ agentId: normalizedContext.agentId,
279
+ workspaceId: normalizedContext.workspaceId,
280
+ userId: normalizedContext.userId,
281
+ projectId: normalizedContext.projectId,
282
+ }))
283
+ .sort((left, right) => right.lastConfirmedAt.localeCompare(left.lastConfirmedAt))
284
+ .slice(0, limit);
285
+ return { items };
286
+ }
287
+ async update(input, context) {
288
+ if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
289
+ throw new Error("updateMemory requires a non-empty memoryId.");
290
+ }
291
+ const existing = await findMemoryRecordById(this.deps.store, input.memoryId.trim());
292
+ if (!existing) {
293
+ throw new Error(`Memory record not found: ${input.memoryId}`);
294
+ }
295
+ const updatedAt = new Date().toISOString();
296
+ const nextContent = typeof input.content === "string" ? input.content.trim() : existing.content;
297
+ if (nextContent.length === 0) {
298
+ throw new Error("updateMemory requires content to remain non-empty.");
299
+ }
300
+ const next = {
301
+ ...existing,
302
+ content: nextContent,
303
+ summary: typeof input.summary === "string"
304
+ ? input.summary.trim() || summarizeMemoryContent(nextContent)
305
+ : (typeof input.content === "string" ? summarizeMemoryContent(nextContent) : existing.summary),
306
+ status: input.status ?? existing.status,
307
+ confidence: typeof input.confidence === "number" ? Math.max(0, Math.min(1, input.confidence)) : existing.confidence,
308
+ expiresAt: input.expiresAt === undefined ? existing.expiresAt : (input.expiresAt ?? undefined),
309
+ sourceType: typeof input.sourceType === "string" && input.sourceType.trim().length > 0 ? input.sourceType.trim() : existing.sourceType,
310
+ sourceRefs: Array.isArray(input.sourceRefs)
311
+ ? Array.from(new Set(input.sourceRefs.map((item) => item.trim()).filter((item) => item.length > 0)))
312
+ : existing.sourceRefs,
313
+ tags: Array.isArray(input.tags)
314
+ ? Array.from(new Set(input.tags.map((item) => item.trim()).filter((item) => item.length > 0)))
315
+ : existing.tags,
316
+ observedAt: typeof input.observedAt === "string" && input.observedAt.trim().length > 0 ? input.observedAt : existing.observedAt,
317
+ lastConfirmedAt: typeof input.lastConfirmedAt === "string" && input.lastConfirmedAt.trim().length > 0
318
+ ? input.lastConfirmedAt
319
+ : updatedAt,
320
+ provenance: input.provenance ? { ...existing.provenance, ...input.provenance } : existing.provenance,
321
+ revision: existing.revision + 1,
322
+ };
323
+ await updateMemoryRecord(this.deps.store, next, updatedAt);
324
+ await this.rebuildVectorIndex();
325
+ await this.refreshStructuredMemoryScope(next, context);
326
+ return next;
327
+ }
328
+ async remove(input, context) {
329
+ if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
330
+ throw new Error("removeMemory requires a non-empty memoryId.");
331
+ }
332
+ const existing = await findMemoryRecordById(this.deps.store, input.memoryId.trim());
333
+ if (!existing) {
334
+ throw new Error(`Memory record not found: ${input.memoryId}`);
335
+ }
336
+ await removeMemoryRecord(this.deps.store, existing.scope, existing.id);
337
+ await this.rebuildVectorIndex();
338
+ await this.refreshStructuredMemoryScope(existing, context);
339
+ return existing;
340
+ }
341
+ async memorizeCandidates(candidates, context, options) {
342
+ const normalizedContext = normalizeRuntimeContext(context);
343
+ const validCandidates = candidates
344
+ .filter((candidate) => candidate.noStore !== true)
345
+ .filter((candidate) => typeof candidate.content === "string" && candidate.content.trim().length > 0)
346
+ .map((candidate) => ({ ...candidate, content: candidate.content.trim() }));
347
+ if (validCandidates.length === 0) {
348
+ return { records: [], decisions: [] };
349
+ }
350
+ const existingRecords = await listMemoryRecordsForScopes(this.deps.store, ALL_SCOPES);
351
+ const transformedCandidates = this.deps.transformCandidates
352
+ ? await this.deps.transformCandidates({ candidates: validCandidates, context: normalizedContext, existingRecords })
353
+ : validCandidates;
354
+ if (options.storeCandidateLog) {
355
+ await this.deps.store.put(["memories", "candidates", normalizedContext.sessionId ?? `memory-api-${normalizedContext.requestId ?? "unknown"}`], `${normalizedContext.requestId}.json`, {
356
+ runId: normalizedContext.requestId,
357
+ threadId: normalizedContext.sessionId,
358
+ storedAt: normalizedContext.recordedAt,
359
+ candidates: transformedCandidates,
360
+ });
361
+ }
362
+ const persisted = await persistStructuredMemoryRecords({
363
+ store: this.deps.store,
364
+ candidates: transformedCandidates,
365
+ threadId: normalizedContext.sessionId ?? `memory-api-${normalizedContext.requestId ?? "unknown"}`,
366
+ runId: normalizedContext.requestId ?? createPersistentId(new Date(normalizedContext.recordedAt ?? new Date().toISOString())),
367
+ agentId: normalizedContext.agentId,
368
+ workspaceId: normalizedContext.workspaceId,
369
+ userId: normalizedContext.userId ?? "default",
370
+ projectId: normalizedContext.projectId ?? normalizedContext.workspaceId,
371
+ recordedAt: normalizedContext.recordedAt ?? new Date().toISOString(),
372
+ });
373
+ await this.rebuildVectorIndex();
374
+ await this.refreshAfterWrite(transformedCandidates, normalizedContext);
375
+ return persisted;
376
+ }
377
+ async refreshAfterWrite(candidates, context) {
378
+ const buckets = {
379
+ thread: candidates.filter((candidate) => (candidate.scope ?? "thread") === "thread"),
380
+ agent: candidates.filter((candidate) => (candidate.scope ?? "thread") === "agent"),
381
+ workspace: candidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace"),
382
+ user: candidates.filter((candidate) => (candidate.scope ?? "thread") === "user"),
383
+ project: candidates.filter((candidate) => (candidate.scope ?? "thread") === "project"),
384
+ };
385
+ const writes = [];
386
+ for (const scope of ALL_SCOPES) {
387
+ if (buckets[scope].length === 0) {
388
+ continue;
389
+ }
390
+ const maxEntries = scope === "thread" ? 12 : 20;
391
+ writes.push(this.appendMemoryDigest(this.deps.resolveNamespace(scope, context), "tool-memory.md", buckets[scope], maxEntries, TITLE_BY_SCOPE[scope].replace("Structured", "Tool")));
392
+ writes.push(consolidateStructuredMemoryScope({
393
+ store: this.deps.store,
394
+ namespace: this.deps.resolveNamespace(scope, context),
395
+ title: TITLE_BY_SCOPE[scope],
396
+ scope,
397
+ maxEntries,
398
+ config: this.deps.maintenanceConfig ?? undefined,
399
+ }));
400
+ }
401
+ await Promise.all(writes);
402
+ }
403
+ async appendMemoryDigest(namespace, key, candidates, maxEntries, title) {
404
+ const existing = await this.deps.store.get(namespace, key);
405
+ const existingItems = Array.isArray(existing?.value?.items)
406
+ ? ((existing?.value).items ?? [])
407
+ : [];
408
+ const merged = [...existingItems];
409
+ for (const candidate of candidates) {
410
+ if (merged.some((entry) => entry.content === candidate.content && (entry.scope ?? "thread") === (candidate.scope ?? "thread"))) {
411
+ continue;
412
+ }
413
+ merged.push(candidate);
414
+ }
415
+ const recent = merged.slice(-maxEntries);
416
+ const taxonomyGroups = new Map();
417
+ for (const candidate of recent) {
418
+ const kind = normalizeLangMemMemoryKind(candidate.kind);
419
+ const current = taxonomyGroups.get(kind) ?? [];
420
+ current.push({ ...candidate, kind });
421
+ taxonomyGroups.set(kind, current);
422
+ }
423
+ await this.deps.store.put(namespace, key, {
424
+ content: `${renderMemoryCandidatesMarkdown(title, recent)}\n`,
425
+ items: recent,
426
+ });
427
+ await Promise.all(Array.from(taxonomyGroups.entries()).map(async ([kind, items]) => {
428
+ await this.deps.store.put(namespace, `${kind}.md`, {
429
+ content: `${renderMemoryCandidatesMarkdown(`${title} (${kind})`, items)}\n`,
430
+ items,
431
+ });
432
+ }));
433
+ }
434
+ async refreshStructuredMemoryScope(record, context) {
435
+ const provenance = record.provenance;
436
+ const namespaceContext = {
437
+ sessionId: provenance.sessionId,
438
+ agentId: provenance.agentId ?? context.agentId,
439
+ workspaceId: provenance.workspaceId ?? context.workspaceId,
440
+ userId: provenance.userId ?? context.userId,
441
+ projectId: provenance.projectId ?? context.projectId,
442
+ };
443
+ const maxEntries = record.scope === "thread" ? 12 : 20;
444
+ await consolidateStructuredMemoryScope({
445
+ store: this.deps.store,
446
+ namespace: this.deps.resolveNamespace(record.scope, normalizeRuntimeContext(namespaceContext)),
447
+ title: TITLE_BY_SCOPE[record.scope],
448
+ scope: record.scope,
449
+ maxEntries,
450
+ config: this.deps.maintenanceConfig ?? undefined,
451
+ });
452
+ }
453
+ async rebuildVectorIndex() {
454
+ const vectorStore = await this.deps.resolveVectorStore();
455
+ if (!vectorStore) {
456
+ return;
457
+ }
458
+ const records = (await listMemoryRecordsForScopes(this.deps.store, ALL_SCOPES))
459
+ .filter((record) => record.status === "active");
460
+ try {
461
+ await vectorStore.delete({ deleteAll: true });
462
+ if (records.length === 0) {
463
+ return;
464
+ }
465
+ await vectorStore.addDocuments(records.map((record) => ({
466
+ pageContent: `${record.summary}\n${record.content}`,
467
+ metadata: {
468
+ recordId: record.id,
469
+ scope: record.scope,
470
+ kind: record.kind,
471
+ status: record.status,
472
+ sessionId: record.provenance.sessionId ?? record.provenance.threadId,
473
+ agentId: record.provenance.agentId,
474
+ workspaceId: record.provenance.workspaceId,
475
+ userId: record.provenance.userId,
476
+ projectId: record.provenance.projectId,
477
+ confidence: record.confidence,
478
+ lastConfirmedAt: record.lastConfirmedAt,
479
+ },
480
+ })));
481
+ }
482
+ catch {
483
+ // Fail open: vector indexing must not break durable memory writes.
484
+ }
485
+ }
486
+ async rankRecallCandidates(input) {
487
+ const structuredRecords = await listMemoryRecordsForScopes(this.deps.store, input.scopes);
488
+ const ranked = structuredRecords
489
+ .filter((record) => matchesRecallScope(record, {
490
+ threadId: input.context.sessionId,
491
+ agentId: input.context.agentId,
492
+ workspaceId: input.context.workspaceId,
493
+ userId: input.context.userId ?? "default",
494
+ projectId: input.context.projectId ?? input.context.workspaceId,
495
+ }))
496
+ .filter((record) => (input.includeStale ? record.status === "active" || record.status === "stale" : record.status === "active"))
497
+ .filter((record) => (input.kinds ? input.kinds.has(record.kind) : true))
498
+ .map((record) => ({
499
+ record,
500
+ score: scoreStructuredRecord(input.query, record),
501
+ }));
502
+ const deduped = new Map();
503
+ for (const item of ranked) {
504
+ deduped.set(normalizeMemoryDedupKey(item.record), item);
505
+ }
506
+ const vectorStore = await this.deps.resolveVectorStore();
507
+ if (vectorStore) {
508
+ try {
509
+ const vectorHits = await vectorStore.similaritySearch(input.query, Math.max(input.topK, this.deps.policy?.retrieval.maxPromptMemories ?? input.topK));
510
+ for (const hit of vectorHits) {
511
+ const metadata = typeof hit.metadata === "object" && hit.metadata && !Array.isArray(hit.metadata)
512
+ ? hit.metadata
513
+ : {};
514
+ const recordId = typeof metadata.recordId === "string" ? metadata.recordId : undefined;
515
+ const scope = metadata.scope;
516
+ if (!recordId || (scope !== "thread" && scope !== "agent" && scope !== "workspace" && scope !== "user" && scope !== "project")) {
517
+ continue;
518
+ }
519
+ const canonical = await getMemoryRecord(this.deps.store, scope, recordId);
520
+ if (!canonical) {
521
+ continue;
522
+ }
523
+ if (!matchesRecallScope(canonical, {
524
+ threadId: input.context.sessionId,
525
+ agentId: input.context.agentId,
526
+ workspaceId: input.context.workspaceId,
527
+ userId: input.context.userId ?? "default",
528
+ projectId: input.context.projectId ?? input.context.workspaceId,
529
+ })) {
530
+ continue;
531
+ }
532
+ if (input.includeStale ? canonical.status !== "active" && canonical.status !== "stale" : canonical.status !== "active") {
533
+ continue;
534
+ }
535
+ if (input.kinds && !input.kinds.has(canonical.kind)) {
536
+ continue;
537
+ }
538
+ const key = normalizeMemoryDedupKey(canonical);
539
+ const score = scoreStructuredRecord(input.query, canonical) + (typeof hit.score === "number" ? hit.score : 0);
540
+ const existing = deduped.get(key);
541
+ if (!existing || score > existing.score) {
542
+ deduped.set(key, { record: canonical, score });
543
+ }
544
+ }
545
+ }
546
+ catch {
547
+ // Fail open to lexical/runtime ranking.
548
+ }
549
+ }
550
+ const supportsMem0Scope = input.scopes.some((scope) => scope === "thread" || scope === "agent" || scope === "workspace");
551
+ const mem0SemanticRecall = this.deps.getMem0SemanticRecall?.() ?? null;
552
+ if (!mem0SemanticRecall || !supportsMem0Scope) {
553
+ return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
554
+ }
555
+ try {
556
+ const hits = await mem0SemanticRecall.search({
557
+ query: input.query,
558
+ topK: Math.max(input.topK, this.deps.policy?.retrieval.maxPromptMemories ?? input.topK),
559
+ agentId: input.context.agentId,
560
+ threadId: input.context.sessionId,
561
+ });
562
+ for (const hit of hits) {
563
+ const record = createMem0MemoryRecord(hit, {
564
+ threadId: input.context.sessionId,
565
+ agentId: input.context.agentId,
566
+ workspaceId: input.context.workspaceId,
567
+ userId: input.context.userId ?? "default",
568
+ projectId: input.context.projectId ?? input.context.workspaceId,
569
+ });
570
+ if (!input.scopes.includes(record.scope)) {
571
+ continue;
572
+ }
573
+ if (input.kinds && !input.kinds.has(record.kind)) {
574
+ continue;
575
+ }
576
+ const key = normalizeMemoryDedupKey(record);
577
+ if (deduped.has(key)) {
578
+ continue;
579
+ }
580
+ deduped.set(key, {
581
+ record,
582
+ score: scoreStructuredRecord(input.query, record) + Math.max(0, Math.min(1, hit.score)) * 4,
583
+ });
584
+ }
585
+ return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
586
+ }
587
+ catch {
588
+ return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
589
+ }
590
+ }
591
+ }
592
+ export function createKnowledgeModule(dependencies) {
593
+ return new DefaultKnowledgeModule(dependencies);
594
+ }
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.280";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.282";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.280";
1
+ export const AGENT_HARNESS_VERSION = "0.0.282";
@@ -10,6 +10,7 @@ type RunIndexRecord = {
10
10
  export declare class FilePersistence implements RuntimePersistence {
11
11
  private readonly runRoot;
12
12
  constructor(runRoot: string);
13
+ private artifactsRoot;
13
14
  private threadIndexPath;
14
15
  private runIndexPath;
15
16
  private approvalIndexPath;