@botbotgo/agent-harness 0.0.154 → 0.0.156
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/README.md +34 -8
- package/README.zh.md +41 -8
- package/dist/api.d.ts +5 -2
- package/dist/api.js +9 -0
- package/dist/config/catalogs/stores.yaml +3 -3
- package/dist/config/catalogs/vector-stores.yaml +8 -1
- package/dist/config/runtime/runtime-memory.yaml +17 -0
- package/dist/contracts/runtime.d.ts +31 -0
- package/dist/contracts/workspace.d.ts +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/init-project.js +18 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/harness/system/mem0-ingestion-sync.d.ts +28 -2
- package/dist/runtime/harness/system/mem0-ingestion-sync.js +112 -1
- package/dist/runtime/harness/system/runtime-memory-manager.d.ts +90 -0
- package/dist/runtime/harness/system/runtime-memory-manager.js +371 -0
- package/dist/runtime/harness/system/runtime-memory-records.d.ts +4 -0
- package/dist/runtime/harness/system/runtime-memory-records.js +57 -2
- package/dist/runtime/harness/system/store.d.ts +27 -0
- package/dist/runtime/harness/system/store.js +96 -0
- package/dist/runtime/harness.d.ts +21 -1
- package/dist/runtime/harness.js +469 -45
- package/dist/runtime/support/runtime-factories.js +5 -1
- package/dist/runtime/support/vector-stores.js +97 -0
- package/dist/workspace/object-loader.js +9 -44
- package/dist/workspace/resource-compilers.js +19 -0
- package/dist/workspace/yaml-object-reader.d.ts +0 -4
- package/dist/workspace/yaml-object-reader.js +6 -32
- package/package.json +1 -1
package/dist/runtime/harness.js
CHANGED
|
@@ -21,16 +21,17 @@ import { cancelRunOperation, executeQueuedRunOperation, resumeRun } from "./harn
|
|
|
21
21
|
import { acquireRunSlot as acquireHarnessRunSlot } from "./harness/run/run-slot-acquisition.js";
|
|
22
22
|
import { dropPendingRunSlot, enqueuePendingRunSlot } from "./harness/run/run-queue.js";
|
|
23
23
|
import { getDefaultRuntimeEntryAgentId, resolveSelectedAgentId, routeAgentId } from "./harness/run/routing.js";
|
|
24
|
-
import { resolveStoreFromConfig, } from "./harness/run/resources.js";
|
|
24
|
+
import { resolveStoreFromConfig, resolveVectorStore, } from "./harness/run/resources.js";
|
|
25
25
|
import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
|
|
26
26
|
import { closeMcpClientsForWorkspace } from "../resource/mcp-tool-support.js";
|
|
27
27
|
import { getBindingRuntimeExecutionMode, } from "./support/compiled-binding.js";
|
|
28
28
|
import { bindingSupportsRunningReplay, getWorkspaceBinding, resolveWorkspaceAgentTools, } from "./harness/bindings.js";
|
|
29
29
|
import { describeWorkspaceInventory, getAgentInventoryRecord, listAgentSkills as listWorkspaceAgentSkills, } from "./harness/system/inventory.js";
|
|
30
30
|
import { createDefaultHealthSnapshot, isInventoryEnabled, isThreadMemorySyncEnabled, } from "./harness/runtime-defaults.js";
|
|
31
|
-
import { Mem0IngestionSync, readMem0RuntimeConfig } from "./harness/system/mem0-ingestion-sync.js";
|
|
31
|
+
import { Mem0IngestionSync, Mem0SemanticRecall, readMem0RuntimeConfig, } from "./harness/system/mem0-ingestion-sync.js";
|
|
32
|
+
import { createRuntimeMemoryManager, RuntimeMemoryFormationSync, readRuntimeMemoryFormationConfig, } from "./harness/system/runtime-memory-manager.js";
|
|
32
33
|
import { renderMemoryCandidatesMarkdown } from "./harness/system/runtime-memory-candidates.js";
|
|
33
|
-
import { listMemoryRecordsForScopes, persistStructuredMemoryRecords, } from "./harness/system/runtime-memory-records.js";
|
|
34
|
+
import { findMemoryRecordById, getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, removeMemoryRecord, updateMemoryRecord, } from "./harness/system/runtime-memory-records.js";
|
|
34
35
|
import { consolidateStructuredMemoryScope } from "./harness/system/runtime-memory-consolidation.js";
|
|
35
36
|
import { normalizeLangMemMemoryKind, readRuntimeMemoryMaintenanceConfig, readRuntimeMemoryPolicyConfig, resolveMemoryNamespace, scoreMemoryText, } from "./harness/system/runtime-memory-policy.js";
|
|
36
37
|
import { resolveRuntimeAdapterOptions } from "./support/runtime-adapter-options.js";
|
|
@@ -70,6 +71,11 @@ export class AgentHarnessRuntime {
|
|
|
70
71
|
unregisterRuntimeMemorySync;
|
|
71
72
|
mem0IngestionSync;
|
|
72
73
|
unregisterMem0IngestionSync;
|
|
74
|
+
mem0SemanticRecall;
|
|
75
|
+
runtimeMemoryFormationConfig;
|
|
76
|
+
runtimeMemoryManager;
|
|
77
|
+
runtimeMemoryFormationSync;
|
|
78
|
+
unregisterRuntimeMemoryFormationSync;
|
|
73
79
|
resolvedRuntimeAdapterOptions;
|
|
74
80
|
healthMonitor;
|
|
75
81
|
recoveryConfig;
|
|
@@ -84,6 +90,19 @@ export class AgentHarnessRuntime {
|
|
|
84
90
|
closed = false;
|
|
85
91
|
backgroundEventRuntime;
|
|
86
92
|
runtimeEventOperations;
|
|
93
|
+
async resolveRuntimeMemoryVectorStore() {
|
|
94
|
+
const vectorStoreConfig = typeof this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory?.vectorStore === "object" &&
|
|
95
|
+
this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory?.vectorStore
|
|
96
|
+
? this.defaultRuntimeEntryBinding.harnessRuntime.runtimeMemory.vectorStore
|
|
97
|
+
: undefined;
|
|
98
|
+
const vectorStoreRef = typeof vectorStoreConfig?.ref === "string" && vectorStoreConfig.ref.trim().length > 0 ? vectorStoreConfig.ref : undefined;
|
|
99
|
+
try {
|
|
100
|
+
return await resolveVectorStore(this.workspace, this.vectorStores, vectorStoreRef, this.resolvedRuntimeAdapterOptions);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
87
106
|
defaultRunRoot() {
|
|
88
107
|
return this.defaultRunRootValue;
|
|
89
108
|
}
|
|
@@ -185,10 +204,40 @@ export class AgentHarnessRuntime {
|
|
|
185
204
|
if (mem0RuntimeConfig) {
|
|
186
205
|
this.mem0IngestionSync = new Mem0IngestionSync(this.persistence, mem0RuntimeConfig, runRoot);
|
|
187
206
|
this.unregisterMem0IngestionSync = this.eventBus.registerProjection(this.mem0IngestionSync);
|
|
207
|
+
this.mem0SemanticRecall = new Mem0SemanticRecall(mem0RuntimeConfig);
|
|
188
208
|
}
|
|
189
209
|
else {
|
|
190
210
|
this.mem0IngestionSync = null;
|
|
191
211
|
this.unregisterMem0IngestionSync = () => { };
|
|
212
|
+
this.mem0SemanticRecall = null;
|
|
213
|
+
}
|
|
214
|
+
const runtimeMemoryFormationConfig = readRuntimeMemoryFormationConfig(this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory, this.workspace.workspaceRoot);
|
|
215
|
+
this.runtimeMemoryFormationConfig = runtimeMemoryFormationConfig ?? null;
|
|
216
|
+
this.runtimeMemoryManager =
|
|
217
|
+
this.defaultRuntimeEntryBinding && runtimeMemoryFormationConfig
|
|
218
|
+
? createRuntimeMemoryManager({
|
|
219
|
+
workspace: this.workspace,
|
|
220
|
+
binding: this.defaultRuntimeEntryBinding,
|
|
221
|
+
config: runtimeMemoryFormationConfig,
|
|
222
|
+
modelResolver: this.resolvedRuntimeAdapterOptions.modelResolver,
|
|
223
|
+
})
|
|
224
|
+
: null;
|
|
225
|
+
if (runtimeMemoryFormationConfig) {
|
|
226
|
+
this.runtimeMemoryFormationSync = new RuntimeMemoryFormationSync(this.persistence, runtimeMemoryFormationConfig, (input) => this.persistStructuredMemoryCandidates(this.defaultRuntimeEntryBinding, {
|
|
227
|
+
candidates: input.candidates,
|
|
228
|
+
threadId: input.threadId,
|
|
229
|
+
runId: input.runId,
|
|
230
|
+
agentId: input.agentId,
|
|
231
|
+
userId: input.userId,
|
|
232
|
+
projectId: input.projectId,
|
|
233
|
+
recordedAt: input.recordedAt,
|
|
234
|
+
storeCandidateLog: false,
|
|
235
|
+
}).then(() => undefined), runRoot);
|
|
236
|
+
this.unregisterRuntimeMemoryFormationSync = this.eventBus.registerProjection(this.runtimeMemoryFormationSync);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.runtimeMemoryFormationSync = null;
|
|
240
|
+
this.unregisterRuntimeMemoryFormationSync = () => { };
|
|
192
241
|
}
|
|
193
242
|
this.recoveryConfig = getRecoveryConfig(workspace.refs);
|
|
194
243
|
this.concurrencyConfig = getConcurrencyConfig(workspace.refs);
|
|
@@ -294,27 +343,116 @@ export class AgentHarnessRuntime {
|
|
|
294
343
|
? input.topK
|
|
295
344
|
: (this.runtimeMemoryPolicy?.retrieval.defaultTopK ?? 5);
|
|
296
345
|
const kinds = input.kinds?.length ? new Set(input.kinds) : null;
|
|
346
|
+
const items = (await this.rankRecallCandidates(binding, {
|
|
347
|
+
query: input.query.trim(),
|
|
348
|
+
scopes,
|
|
349
|
+
kinds,
|
|
350
|
+
topK,
|
|
351
|
+
includeStale: input.includeStale === true,
|
|
352
|
+
filters: {
|
|
353
|
+
threadId: input.threadId,
|
|
354
|
+
agentId,
|
|
355
|
+
workspaceId: input.workspaceId ?? workspaceId,
|
|
356
|
+
userId,
|
|
357
|
+
projectId,
|
|
358
|
+
},
|
|
359
|
+
}))
|
|
360
|
+
.slice(0, topK)
|
|
361
|
+
.map(({ record }) => record);
|
|
362
|
+
return { items };
|
|
363
|
+
}
|
|
364
|
+
async listMemories(input = {}) {
|
|
365
|
+
const binding = this.defaultRuntimeEntryBinding;
|
|
366
|
+
if (!binding) {
|
|
367
|
+
throw new Error("listMemories requires a runtime entry binding.");
|
|
368
|
+
}
|
|
369
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
370
|
+
const scopes = Array.isArray(input.scopes) && input.scopes.length > 0
|
|
371
|
+
? Array.from(new Set(input.scopes))
|
|
372
|
+
: ["thread", "agent", "workspace", "user", "project"];
|
|
373
|
+
const kinds = input.kinds?.length ? new Set(input.kinds) : null;
|
|
374
|
+
const statuses = input.status?.length ? new Set(input.status) : new Set(["active"]);
|
|
375
|
+
const limit = typeof input.limit === "number" && Number.isInteger(input.limit) && input.limit > 0
|
|
376
|
+
? input.limit
|
|
377
|
+
: Number.POSITIVE_INFINITY;
|
|
297
378
|
const items = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, scopes))
|
|
298
|
-
.filter((record) =>
|
|
379
|
+
.filter((record) => statuses.has(record.status))
|
|
380
|
+
.filter((record) => !kinds || kinds.has(record.kind))
|
|
381
|
+
.filter((record) => this.matchesMemoryFilters(record, {
|
|
299
382
|
threadId: input.threadId,
|
|
300
|
-
agentId,
|
|
383
|
+
agentId: input.agentId,
|
|
301
384
|
workspaceId: input.workspaceId ?? workspaceId,
|
|
302
|
-
userId,
|
|
303
|
-
projectId,
|
|
304
|
-
}))
|
|
305
|
-
.filter((record) => (input.includeStale ? record.status === "active" || record.status === "stale" : record.status === "active"))
|
|
306
|
-
.filter((record) => (kinds ? kinds.has(record.kind) : true))
|
|
307
|
-
.map((record) => ({
|
|
308
|
-
record,
|
|
309
|
-
score: scoreMemoryText(input.query.trim(), `${record.summary}\n${record.content}`, this.getMemoryScopeBoost(record.scope)) +
|
|
310
|
-
Math.max(0, 1 - ((Date.now() - Date.parse(record.lastConfirmedAt)) / (1000 * 60 * 60 * 24 * 365))) +
|
|
311
|
-
record.confidence,
|
|
385
|
+
userId: input.userId,
|
|
386
|
+
projectId: input.projectId,
|
|
312
387
|
}))
|
|
313
|
-
.sort((left, right) => right.
|
|
314
|
-
.slice(0,
|
|
315
|
-
.map(({ record }) => record);
|
|
388
|
+
.sort((left, right) => right.lastConfirmedAt.localeCompare(left.lastConfirmedAt))
|
|
389
|
+
.slice(0, limit);
|
|
316
390
|
return { items };
|
|
317
391
|
}
|
|
392
|
+
async updateMemory(input) {
|
|
393
|
+
const binding = this.defaultRuntimeEntryBinding;
|
|
394
|
+
if (!binding) {
|
|
395
|
+
throw new Error("updateMemory requires a runtime entry binding.");
|
|
396
|
+
}
|
|
397
|
+
if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
|
|
398
|
+
throw new Error("updateMemory requires a non-empty memoryId.");
|
|
399
|
+
}
|
|
400
|
+
const existing = await findMemoryRecordById(this.runtimeMemoryStore, input.memoryId.trim());
|
|
401
|
+
if (!existing) {
|
|
402
|
+
throw new Error(`Memory record not found: ${input.memoryId}`);
|
|
403
|
+
}
|
|
404
|
+
const updatedAt = new Date().toISOString();
|
|
405
|
+
const nextContent = typeof input.content === "string"
|
|
406
|
+
? input.content.trim()
|
|
407
|
+
: existing.content;
|
|
408
|
+
if (nextContent.length === 0) {
|
|
409
|
+
throw new Error("updateMemory requires content to remain non-empty.");
|
|
410
|
+
}
|
|
411
|
+
const next = {
|
|
412
|
+
...existing,
|
|
413
|
+
content: nextContent,
|
|
414
|
+
summary: typeof input.summary === "string"
|
|
415
|
+
? input.summary.trim() || this.summarizeMemoryContent(nextContent)
|
|
416
|
+
: (typeof input.content === "string" ? this.summarizeMemoryContent(nextContent) : existing.summary),
|
|
417
|
+
status: input.status ?? existing.status,
|
|
418
|
+
confidence: typeof input.confidence === "number" ? Math.max(0, Math.min(1, input.confidence)) : existing.confidence,
|
|
419
|
+
expiresAt: input.expiresAt === undefined ? existing.expiresAt : (input.expiresAt ?? undefined),
|
|
420
|
+
sourceType: typeof input.sourceType === "string" && input.sourceType.trim().length > 0 ? input.sourceType.trim() : existing.sourceType,
|
|
421
|
+
sourceRefs: Array.isArray(input.sourceRefs)
|
|
422
|
+
? Array.from(new Set(input.sourceRefs.map((item) => item.trim()).filter((item) => item.length > 0)))
|
|
423
|
+
: existing.sourceRefs,
|
|
424
|
+
tags: Array.isArray(input.tags)
|
|
425
|
+
? Array.from(new Set(input.tags.map((item) => item.trim()).filter((item) => item.length > 0)))
|
|
426
|
+
: existing.tags,
|
|
427
|
+
observedAt: typeof input.observedAt === "string" && input.observedAt.trim().length > 0 ? input.observedAt : existing.observedAt,
|
|
428
|
+
lastConfirmedAt: typeof input.lastConfirmedAt === "string" && input.lastConfirmedAt.trim().length > 0
|
|
429
|
+
? input.lastConfirmedAt
|
|
430
|
+
: updatedAt,
|
|
431
|
+
provenance: input.provenance ? { ...existing.provenance, ...input.provenance } : existing.provenance,
|
|
432
|
+
revision: existing.revision + 1,
|
|
433
|
+
};
|
|
434
|
+
await updateMemoryRecord(this.runtimeMemoryStore, next, updatedAt);
|
|
435
|
+
await this.rebuildRuntimeMemoryVectorIndex();
|
|
436
|
+
await this.refreshStructuredMemoryScope(binding, next);
|
|
437
|
+
return next;
|
|
438
|
+
}
|
|
439
|
+
async removeMemory(input) {
|
|
440
|
+
const binding = this.defaultRuntimeEntryBinding;
|
|
441
|
+
if (!binding) {
|
|
442
|
+
throw new Error("removeMemory requires a runtime entry binding.");
|
|
443
|
+
}
|
|
444
|
+
if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
|
|
445
|
+
throw new Error("removeMemory requires a non-empty memoryId.");
|
|
446
|
+
}
|
|
447
|
+
const existing = await findMemoryRecordById(this.runtimeMemoryStore, input.memoryId.trim());
|
|
448
|
+
if (!existing) {
|
|
449
|
+
throw new Error(`Memory record not found: ${input.memoryId}`);
|
|
450
|
+
}
|
|
451
|
+
await removeMemoryRecord(this.runtimeMemoryStore, existing.scope, existing.id);
|
|
452
|
+
await this.rebuildRuntimeMemoryVectorIndex();
|
|
453
|
+
await this.refreshStructuredMemoryScope(binding, existing);
|
|
454
|
+
return existing;
|
|
455
|
+
}
|
|
318
456
|
async getRun(runId) {
|
|
319
457
|
return this.persistence.getRun(runId);
|
|
320
458
|
}
|
|
@@ -468,7 +606,7 @@ export class AgentHarnessRuntime {
|
|
|
468
606
|
const template = this.runtimeMemoryPolicy?.namespaces[scope] ?? `memories/${scope}s/{${scope}Id}`;
|
|
469
607
|
return resolveMemoryNamespace(template, {
|
|
470
608
|
threadId: options.threadId,
|
|
471
|
-
agentId: binding.agent.id,
|
|
609
|
+
agentId: options.agentId ?? binding.agent.id,
|
|
472
610
|
workspaceId,
|
|
473
611
|
userId: options.userId ?? "default",
|
|
474
612
|
projectId: options.projectId ?? workspaceId,
|
|
@@ -478,6 +616,27 @@ export class AgentHarnessRuntime {
|
|
|
478
616
|
const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
|
|
479
617
|
return path.basename(workspaceRoot) || "default";
|
|
480
618
|
}
|
|
619
|
+
summarizeMemoryContent(content) {
|
|
620
|
+
return (content.trim().split("\n")[0] || content.trim()).slice(0, 240);
|
|
621
|
+
}
|
|
622
|
+
matchesMemoryFilters(record, filters) {
|
|
623
|
+
if (filters.threadId && record.provenance.threadId !== filters.threadId) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
if (filters.agentId && record.provenance.agentId !== filters.agentId) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
if (filters.workspaceId && record.provenance.workspaceId !== filters.workspaceId) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
if (filters.userId && record.provenance.userId !== filters.userId) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
if (filters.projectId && record.provenance.projectId !== filters.projectId) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
481
640
|
resolveRecallScopes(input) {
|
|
482
641
|
if (Array.isArray(input.scopes) && input.scopes.length > 0) {
|
|
483
642
|
return Array.from(new Set(input.scopes));
|
|
@@ -518,36 +677,260 @@ export class AgentHarnessRuntime {
|
|
|
518
677
|
}
|
|
519
678
|
return 0;
|
|
520
679
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
680
|
+
memoryFreshnessBoost(value) {
|
|
681
|
+
return Math.max(0, 1 - ((Date.now() - Date.parse(value)) / (1000 * 60 * 60 * 24 * 365)));
|
|
682
|
+
}
|
|
683
|
+
scoreStructuredRecord(query, record) {
|
|
684
|
+
return (scoreMemoryText(query, `${record.summary}\n${record.content}`, this.getMemoryScopeBoost(record.scope)) +
|
|
685
|
+
this.memoryFreshnessBoost(record.lastConfirmedAt) +
|
|
686
|
+
record.confidence);
|
|
687
|
+
}
|
|
688
|
+
normalizeMemoryDedupKey(record) {
|
|
689
|
+
return `${record.scope}::${record.summary.toLowerCase().replace(/\s+/g, " ").trim()}::${record.content.toLowerCase().replace(/\s+/g, " ").trim()}`;
|
|
690
|
+
}
|
|
691
|
+
inferMem0MemoryKind(hit) {
|
|
692
|
+
const candidates = [
|
|
693
|
+
...hit.categories,
|
|
694
|
+
typeof hit.metadata.kind === "string" ? hit.metadata.kind : undefined,
|
|
695
|
+
typeof hit.metadata.memoryType === "string" ? hit.metadata.memoryType : undefined,
|
|
696
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
697
|
+
for (const candidate of candidates) {
|
|
698
|
+
const kind = normalizeLangMemMemoryKind(candidate);
|
|
699
|
+
if (kind === "semantic" || kind === "episodic" || kind === "procedural") {
|
|
700
|
+
return kind;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return "semantic";
|
|
704
|
+
}
|
|
705
|
+
inferMem0MemoryScope(hit, filters) {
|
|
706
|
+
const metadataScope = typeof hit.metadata.scope === "string" ? hit.metadata.scope : undefined;
|
|
707
|
+
if (metadataScope === "thread" || metadataScope === "agent" || metadataScope === "workspace" || metadataScope === "user" || metadataScope === "project") {
|
|
708
|
+
return metadataScope;
|
|
709
|
+
}
|
|
710
|
+
if (typeof hit.metadata.threadId === "string" && filters.threadId && hit.metadata.threadId === filters.threadId) {
|
|
711
|
+
return "thread";
|
|
712
|
+
}
|
|
713
|
+
if (hit.agentId === filters.agentId || hit.metadata.agentId === filters.agentId) {
|
|
714
|
+
return "agent";
|
|
715
|
+
}
|
|
716
|
+
return "workspace";
|
|
717
|
+
}
|
|
718
|
+
createMem0MemoryRecord(hit, filters) {
|
|
719
|
+
const scope = this.inferMem0MemoryScope(hit, filters);
|
|
720
|
+
const kind = this.inferMem0MemoryKind(hit);
|
|
721
|
+
const summary = hit.memory.split("\n")[0]?.trim() || hit.memory;
|
|
722
|
+
return {
|
|
723
|
+
id: `mem0:${hit.id}`,
|
|
724
|
+
canonicalKey: `mem0:${hit.id}`,
|
|
725
|
+
kind,
|
|
726
|
+
scope,
|
|
727
|
+
content: hit.memory,
|
|
728
|
+
summary: summary.slice(0, 240),
|
|
729
|
+
status: "active",
|
|
730
|
+
confidence: Math.max(0, Math.min(1, hit.score || 0.5)),
|
|
731
|
+
createdAt: hit.createdAt,
|
|
732
|
+
observedAt: hit.createdAt,
|
|
733
|
+
lastConfirmedAt: hit.updatedAt,
|
|
734
|
+
sourceType: "mem0-search",
|
|
735
|
+
sourceRefs: [`mem0:${hit.id}`],
|
|
736
|
+
tags: hit.categories,
|
|
737
|
+
provenance: {
|
|
738
|
+
source: "mem0",
|
|
739
|
+
threadId: typeof hit.metadata.threadId === "string" ? hit.metadata.threadId : filters.threadId,
|
|
740
|
+
runId: hit.runId ?? (typeof hit.metadata.runId === "string" ? hit.metadata.runId : undefined),
|
|
741
|
+
agentId: hit.agentId ?? (typeof hit.metadata.agentId === "string" ? hit.metadata.agentId : filters.agentId),
|
|
742
|
+
workspaceId: filters.workspaceId,
|
|
743
|
+
userId: filters.userId,
|
|
744
|
+
projectId: filters.projectId,
|
|
745
|
+
...hit.metadata,
|
|
746
|
+
},
|
|
747
|
+
revision: 1,
|
|
748
|
+
supersedes: [],
|
|
749
|
+
conflictsWith: [],
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
async rankRecallCandidates(binding, input) {
|
|
753
|
+
const structuredRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, input.scopes);
|
|
754
|
+
const ranked = structuredRecords
|
|
755
|
+
.filter((record) => this.matchesRecallScope(record, input.filters))
|
|
756
|
+
.filter((record) => (input.includeStale ? record.status === "active" || record.status === "stale" : record.status === "active"))
|
|
757
|
+
.filter((record) => (input.kinds ? input.kinds.has(record.kind) : true))
|
|
758
|
+
.map((record) => ({
|
|
759
|
+
record,
|
|
760
|
+
score: this.scoreStructuredRecord(input.query, record),
|
|
761
|
+
}));
|
|
762
|
+
const deduped = new Map();
|
|
763
|
+
for (const item of ranked) {
|
|
764
|
+
deduped.set(this.normalizeMemoryDedupKey(item.record), item);
|
|
765
|
+
}
|
|
766
|
+
const vectorStore = await this.resolveRuntimeMemoryVectorStore();
|
|
767
|
+
if (vectorStore) {
|
|
768
|
+
try {
|
|
769
|
+
const vectorHits = await vectorStore.similaritySearch(input.query, Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK));
|
|
770
|
+
for (const hit of vectorHits) {
|
|
771
|
+
const metadata = typeof hit.metadata === "object" && hit.metadata && !Array.isArray(hit.metadata)
|
|
772
|
+
? hit.metadata
|
|
773
|
+
: {};
|
|
774
|
+
const recordId = typeof metadata.recordId === "string" ? metadata.recordId : undefined;
|
|
775
|
+
const scope = metadata.scope;
|
|
776
|
+
if (!recordId || (scope !== "thread" && scope !== "agent" && scope !== "workspace" && scope !== "user" && scope !== "project")) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const canonical = await getMemoryRecord(this.runtimeMemoryStore, scope, recordId);
|
|
780
|
+
if (!canonical) {
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (!this.matchesRecallScope(canonical, input.filters)) {
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (input.includeStale ? canonical.status !== "active" && canonical.status !== "stale" : canonical.status !== "active") {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (input.kinds && !input.kinds.has(canonical.kind)) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const key = this.normalizeMemoryDedupKey(canonical);
|
|
793
|
+
const score = this.scoreStructuredRecord(input.query, canonical) + (typeof hit.score === "number" ? hit.score : 0);
|
|
794
|
+
const existing = deduped.get(key);
|
|
795
|
+
if (!existing || score > existing.score) {
|
|
796
|
+
deduped.set(key, { record: canonical, score });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
527
799
|
}
|
|
528
|
-
|
|
529
|
-
|
|
800
|
+
catch {
|
|
801
|
+
// Fail open to lexical/runtime ranking.
|
|
530
802
|
}
|
|
531
|
-
|
|
532
|
-
|
|
803
|
+
}
|
|
804
|
+
const supportsMem0Scope = input.scopes.some((scope) => scope === "thread" || scope === "agent" || scope === "workspace");
|
|
805
|
+
if (!this.mem0SemanticRecall || !supportsMem0Scope) {
|
|
806
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
const hits = await this.mem0SemanticRecall.search({
|
|
810
|
+
query: input.query,
|
|
811
|
+
topK: Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK),
|
|
812
|
+
agentId: input.filters.agentId,
|
|
813
|
+
threadId: input.filters.threadId,
|
|
814
|
+
});
|
|
815
|
+
for (const hit of hits) {
|
|
816
|
+
const record = this.createMem0MemoryRecord(hit, input.filters);
|
|
817
|
+
if (!input.scopes.includes(record.scope)) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (input.kinds && !input.kinds.has(record.kind)) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const key = this.normalizeMemoryDedupKey(record);
|
|
824
|
+
if (deduped.has(key)) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
deduped.set(key, {
|
|
828
|
+
record,
|
|
829
|
+
score: this.scoreStructuredRecord(input.query, record) + Math.max(0, Math.min(1, hit.score)) * 4,
|
|
830
|
+
});
|
|
533
831
|
}
|
|
534
|
-
return
|
|
535
|
-
}
|
|
832
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async rebuildRuntimeMemoryVectorIndex() {
|
|
839
|
+
const vectorStore = await this.resolveRuntimeMemoryVectorStore();
|
|
840
|
+
if (!vectorStore) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const records = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]))
|
|
844
|
+
.filter((record) => record.status === "active");
|
|
845
|
+
try {
|
|
846
|
+
await vectorStore.delete({ deleteAll: true });
|
|
847
|
+
if (records.length === 0) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
await vectorStore.addDocuments(records.map((record) => ({
|
|
851
|
+
pageContent: `${record.summary}\n${record.content}`,
|
|
852
|
+
metadata: {
|
|
853
|
+
recordId: record.id,
|
|
854
|
+
scope: record.scope,
|
|
855
|
+
kind: record.kind,
|
|
856
|
+
status: record.status,
|
|
857
|
+
threadId: record.provenance.threadId,
|
|
858
|
+
agentId: record.provenance.agentId,
|
|
859
|
+
workspaceId: record.provenance.workspaceId,
|
|
860
|
+
userId: record.provenance.userId,
|
|
861
|
+
projectId: record.provenance.projectId,
|
|
862
|
+
confidence: record.confidence,
|
|
863
|
+
lastConfirmedAt: record.lastConfirmedAt,
|
|
864
|
+
},
|
|
865
|
+
})));
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
// Fail open: vector indexing must not break durable memory writes.
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async refreshStructuredMemoryScope(binding, record) {
|
|
872
|
+
const provenance = record.provenance;
|
|
873
|
+
const maxEntries = record.scope === "thread" ? 12 : 20;
|
|
874
|
+
const titleByScope = {
|
|
875
|
+
thread: "Thread Structured Memory",
|
|
876
|
+
agent: "Agent Structured Memory",
|
|
877
|
+
workspace: "Workspace Structured Memory",
|
|
878
|
+
user: "User Structured Memory",
|
|
879
|
+
project: "Project Structured Memory",
|
|
880
|
+
};
|
|
881
|
+
await consolidateStructuredMemoryScope({
|
|
882
|
+
store: this.runtimeMemoryStore,
|
|
883
|
+
namespace: this.resolveMemoryNamespace(record.scope, binding, {
|
|
884
|
+
threadId: provenance.threadId,
|
|
885
|
+
agentId: provenance.agentId,
|
|
886
|
+
userId: provenance.userId,
|
|
887
|
+
projectId: provenance.projectId,
|
|
888
|
+
}),
|
|
889
|
+
title: titleByScope[record.scope],
|
|
890
|
+
scope: record.scope,
|
|
891
|
+
maxEntries,
|
|
892
|
+
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
async buildRuntimeMemoryContext(binding, threadId, input) {
|
|
896
|
+
const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
|
|
897
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
898
|
+
const ranked = (await this.rankRecallCandidates(binding, {
|
|
899
|
+
query,
|
|
900
|
+
scopes: ["thread", "agent", "workspace", "user", "project"],
|
|
901
|
+
kinds: null,
|
|
902
|
+
topK: this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? 8,
|
|
903
|
+
includeStale: false,
|
|
904
|
+
filters: {
|
|
905
|
+
threadId,
|
|
906
|
+
agentId: binding.agent.id,
|
|
907
|
+
workspaceId,
|
|
908
|
+
userId: "default",
|
|
909
|
+
projectId: workspaceId,
|
|
910
|
+
},
|
|
911
|
+
}))
|
|
536
912
|
.map((record) => {
|
|
537
|
-
const scopeBoost = record.
|
|
538
|
-
|
|
913
|
+
const scopeBoost = record.record.scope === "thread"
|
|
914
|
+
? 4
|
|
915
|
+
: record.record.scope === "agent"
|
|
916
|
+
? 3
|
|
917
|
+
: record.record.scope === "user"
|
|
918
|
+
? 2
|
|
919
|
+
: 1;
|
|
539
920
|
return {
|
|
540
921
|
content: [
|
|
541
|
-
`# Durable ${record.scope[0].toUpperCase()}${record.scope.slice(1)} Memory`,
|
|
922
|
+
`# Durable ${record.record.scope[0].toUpperCase()}${record.record.scope.slice(1)} Memory`,
|
|
542
923
|
"",
|
|
543
|
-
`- summary: ${record.summary}`,
|
|
544
|
-
`- kind: ${record.kind}`,
|
|
545
|
-
`- scope: ${record.scope}`,
|
|
546
|
-
`- confidence: ${record.confidence.toFixed(2)}`,
|
|
924
|
+
`- summary: ${record.record.summary}`,
|
|
925
|
+
`- kind: ${record.record.kind}`,
|
|
926
|
+
`- scope: ${record.record.scope}`,
|
|
927
|
+
`- confidence: ${record.record.confidence.toFixed(2)}`,
|
|
547
928
|
"",
|
|
548
|
-
record.content,
|
|
929
|
+
record.record.content,
|
|
549
930
|
].join("\n"),
|
|
550
|
-
score: scoreMemoryText(query, `${record.summary}\n${record.content}`, scopeBoost) +
|
|
931
|
+
score: scoreMemoryText(query, `${record.record.summary}\n${record.record.content}`, scopeBoost) +
|
|
932
|
+
this.memoryFreshnessBoost(record.record.lastConfirmedAt) +
|
|
933
|
+
record.record.confidence,
|
|
551
934
|
};
|
|
552
935
|
})
|
|
553
936
|
.sort((left, right) => right.score - left.score)
|
|
@@ -561,6 +944,9 @@ export class AgentHarnessRuntime {
|
|
|
561
944
|
if (!Array.isArray(value) || value.length === 0) {
|
|
562
945
|
return;
|
|
563
946
|
}
|
|
947
|
+
if (this.runtimeMemoryFormationConfig?.hotPath.enabled === false) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
564
950
|
const candidates = value
|
|
565
951
|
.filter((candidate) => typeof candidate === "object" && candidate !== null)
|
|
566
952
|
.filter((candidate) => candidate.noStore !== true && typeof candidate.content === "string" && candidate.content.trim().length > 0);
|
|
@@ -580,20 +966,33 @@ export class AgentHarnessRuntime {
|
|
|
580
966
|
const workspaceId = this.getWorkspaceId(binding);
|
|
581
967
|
const userId = input.userId ?? "default";
|
|
582
968
|
const projectId = input.projectId ?? workspaceId;
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
|
|
969
|
+
const existingRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]);
|
|
970
|
+
const transformedCandidates = this.runtimeMemoryManager
|
|
971
|
+
? await this.runtimeMemoryManager.transform({
|
|
972
|
+
candidates: input.candidates,
|
|
973
|
+
binding,
|
|
974
|
+
threadId: input.threadId,
|
|
975
|
+
runId: input.runId,
|
|
976
|
+
recordedAt: input.recordedAt,
|
|
977
|
+
existingRecords,
|
|
978
|
+
})
|
|
979
|
+
: input.candidates;
|
|
980
|
+
const threadCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "thread");
|
|
981
|
+
const workspaceCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
|
|
982
|
+
const agentCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
|
|
983
|
+
const userCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "user");
|
|
984
|
+
const projectCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "project");
|
|
586
985
|
if (input.storeCandidateLog) {
|
|
587
986
|
await this.runtimeMemoryStore.put(["memories", "candidates", input.threadId], `${input.runId}.json`, {
|
|
588
987
|
runId: input.runId,
|
|
589
988
|
threadId: input.threadId,
|
|
590
989
|
storedAt: input.recordedAt,
|
|
591
|
-
candidates:
|
|
990
|
+
candidates: transformedCandidates,
|
|
592
991
|
});
|
|
593
992
|
}
|
|
594
993
|
const persisted = await persistStructuredMemoryRecords({
|
|
595
994
|
store: this.runtimeMemoryStore,
|
|
596
|
-
candidates:
|
|
995
|
+
candidates: transformedCandidates,
|
|
597
996
|
threadId: input.threadId,
|
|
598
997
|
runId: input.runId,
|
|
599
998
|
agentId: input.agentId,
|
|
@@ -602,6 +1001,7 @@ export class AgentHarnessRuntime {
|
|
|
602
1001
|
projectId,
|
|
603
1002
|
recordedAt: input.recordedAt,
|
|
604
1003
|
});
|
|
1004
|
+
await this.rebuildRuntimeMemoryVectorIndex();
|
|
605
1005
|
const writes = [];
|
|
606
1006
|
if (threadCandidates.length > 0) {
|
|
607
1007
|
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
|
|
@@ -636,6 +1036,28 @@ export class AgentHarnessRuntime {
|
|
|
636
1036
|
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
637
1037
|
}));
|
|
638
1038
|
}
|
|
1039
|
+
if (userCandidates.length > 0) {
|
|
1040
|
+
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("user", binding, { userId }), "tool-memory.md", userCandidates, 20, "User Tool Memory"));
|
|
1041
|
+
writes.push(consolidateStructuredMemoryScope({
|
|
1042
|
+
store: this.runtimeMemoryStore,
|
|
1043
|
+
namespace: this.resolveMemoryNamespace("user", binding, { userId }),
|
|
1044
|
+
title: "User Structured Memory",
|
|
1045
|
+
scope: "user",
|
|
1046
|
+
maxEntries: 20,
|
|
1047
|
+
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
1048
|
+
}));
|
|
1049
|
+
}
|
|
1050
|
+
if (projectCandidates.length > 0) {
|
|
1051
|
+
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("project", binding, { projectId }), "tool-memory.md", projectCandidates, 20, "Project Tool Memory"));
|
|
1052
|
+
writes.push(consolidateStructuredMemoryScope({
|
|
1053
|
+
store: this.runtimeMemoryStore,
|
|
1054
|
+
namespace: this.resolveMemoryNamespace("project", binding, { projectId }),
|
|
1055
|
+
title: "Project Structured Memory",
|
|
1056
|
+
scope: "project",
|
|
1057
|
+
maxEntries: 20,
|
|
1058
|
+
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
1059
|
+
}));
|
|
1060
|
+
}
|
|
639
1061
|
await Promise.all(writes);
|
|
640
1062
|
return persisted;
|
|
641
1063
|
}
|
|
@@ -909,10 +1331,12 @@ export class AgentHarnessRuntime {
|
|
|
909
1331
|
this.unregisterThreadMemorySync();
|
|
910
1332
|
this.unregisterRuntimeMemorySync();
|
|
911
1333
|
this.unregisterMem0IngestionSync();
|
|
1334
|
+
this.unregisterRuntimeMemoryFormationSync();
|
|
912
1335
|
await Promise.allSettled(Array.from(this.backgroundTasks));
|
|
913
1336
|
await this.threadMemorySync?.close();
|
|
914
1337
|
await this.runtimeMemorySync?.close();
|
|
915
1338
|
await this.mem0IngestionSync?.close();
|
|
1339
|
+
await this.runtimeMemoryFormationSync?.close();
|
|
916
1340
|
await closeMcpClientsForWorkspace(this.workspace);
|
|
917
1341
|
this.initialized = false;
|
|
918
1342
|
}
|
|
@@ -3,7 +3,7 @@ import { MemorySaver } from "@langchain/langgraph";
|
|
|
3
3
|
import { SqliteSaver } from "@langchain/langgraph-checkpoint-sqlite";
|
|
4
4
|
import { FileCheckpointSaver } from "../maintenance/file-checkpoint-saver.js";
|
|
5
5
|
import { ManagedSqliteSaver } from "../maintenance/sqlite-maintained-checkpoint-saver.js";
|
|
6
|
-
import { createInMemoryStore, FileBackedStore } from "../harness/system/store.js";
|
|
6
|
+
import { createInMemoryStore, FileBackedStore, SqliteBackedStore } from "../harness/system/store.js";
|
|
7
7
|
export function createStoreForConfig(storeConfig, runRoot) {
|
|
8
8
|
const kind = typeof storeConfig.kind === "string" ? storeConfig.kind : "FileStore";
|
|
9
9
|
switch (kind) {
|
|
@@ -11,6 +11,10 @@ export function createStoreForConfig(storeConfig, runRoot) {
|
|
|
11
11
|
const configuredPath = typeof storeConfig.path === "string" ? storeConfig.path : "store.json";
|
|
12
12
|
return new FileBackedStore(path.isAbsolute(configuredPath) ? configuredPath : path.join(runRoot, configuredPath));
|
|
13
13
|
}
|
|
14
|
+
case "SqliteStore": {
|
|
15
|
+
const configuredPath = typeof storeConfig.path === "string" ? storeConfig.path : "store.sqlite";
|
|
16
|
+
return new SqliteBackedStore(path.isAbsolute(configuredPath) ? configuredPath : path.join(runRoot, configuredPath));
|
|
17
|
+
}
|
|
14
18
|
case "InMemoryStore":
|
|
15
19
|
return createInMemoryStore();
|
|
16
20
|
case "RedisStore":
|