@botbotgo/agent-harness 0.0.153 → 0.0.155
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 +40 -3
- package/README.zh.md +47 -3
- package/dist/api.d.ts +4 -1
- package/dist/api.js +6 -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 +44 -1
- 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 +12 -0
- 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 +22 -1
- package/dist/runtime/harness.js +461 -41
- package/dist/runtime/support/runtime-factories.js +5 -1
- package/dist/runtime/support/vector-stores.js +97 -0
- package/dist/workspace/resource-compilers.js +19 -0
- package/dist/workspace/yaml-object-reader.js +2 -0
- 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 { getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, } 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);
|
|
@@ -243,6 +292,75 @@ export class AgentHarnessRuntime {
|
|
|
243
292
|
async listRuns(filter) {
|
|
244
293
|
return this.persistence.listRuns(filter);
|
|
245
294
|
}
|
|
295
|
+
async memorize(input) {
|
|
296
|
+
const binding = this.defaultRuntimeEntryBinding;
|
|
297
|
+
if (!binding) {
|
|
298
|
+
throw new Error("memorize requires a runtime entry binding.");
|
|
299
|
+
}
|
|
300
|
+
if (!Array.isArray(input.records)) {
|
|
301
|
+
throw new Error("memorize requires input.records to be an array.");
|
|
302
|
+
}
|
|
303
|
+
const candidates = input.records
|
|
304
|
+
.filter((record) => typeof record === "object" && record !== null)
|
|
305
|
+
.filter((record) => record.noStore !== true);
|
|
306
|
+
if (candidates.length === 0) {
|
|
307
|
+
return { records: [], decisions: [] };
|
|
308
|
+
}
|
|
309
|
+
if (candidates.some((record) => typeof record.content !== "string" || record.content.trim().length === 0)) {
|
|
310
|
+
throw new Error("memorize requires every record to include non-empty content.");
|
|
311
|
+
}
|
|
312
|
+
if (candidates.some((record) => (record.scope ?? "thread") === "thread") && !input.threadId) {
|
|
313
|
+
throw new Error("memorize requires threadId when storing thread-scoped memory.");
|
|
314
|
+
}
|
|
315
|
+
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
316
|
+
const runId = input.runId ?? createPersistentId(new Date(recordedAt));
|
|
317
|
+
const threadId = input.threadId ?? `memory-api-${runId}`;
|
|
318
|
+
return this.persistStructuredMemoryCandidates(binding, {
|
|
319
|
+
candidates: candidates.map((record) => ({ ...record, content: record.content.trim() })),
|
|
320
|
+
threadId,
|
|
321
|
+
runId,
|
|
322
|
+
agentId: input.agentId ?? binding.agent.id,
|
|
323
|
+
userId: input.userId,
|
|
324
|
+
projectId: input.projectId,
|
|
325
|
+
recordedAt,
|
|
326
|
+
storeCandidateLog: true,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async recall(input) {
|
|
330
|
+
const binding = this.defaultRuntimeEntryBinding;
|
|
331
|
+
if (!binding) {
|
|
332
|
+
throw new Error("recall requires a runtime entry binding.");
|
|
333
|
+
}
|
|
334
|
+
if (typeof input.query !== "string" || input.query.trim().length === 0) {
|
|
335
|
+
throw new Error("recall requires a non-empty query.");
|
|
336
|
+
}
|
|
337
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
338
|
+
const agentId = input.agentId ?? binding.agent.id;
|
|
339
|
+
const userId = input.userId ?? "default";
|
|
340
|
+
const projectId = input.projectId ?? workspaceId;
|
|
341
|
+
const scopes = this.resolveRecallScopes(input);
|
|
342
|
+
const topK = typeof input.topK === "number" && Number.isInteger(input.topK) && input.topK > 0
|
|
343
|
+
? input.topK
|
|
344
|
+
: (this.runtimeMemoryPolicy?.retrieval.defaultTopK ?? 5);
|
|
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
|
+
}
|
|
246
364
|
async getRun(runId) {
|
|
247
365
|
return this.persistence.getRun(runId);
|
|
248
366
|
}
|
|
@@ -392,8 +510,7 @@ export class AgentHarnessRuntime {
|
|
|
392
510
|
}
|
|
393
511
|
}
|
|
394
512
|
resolveMemoryNamespace(scope, binding, options = {}) {
|
|
395
|
-
const
|
|
396
|
-
const workspaceId = path.basename(workspaceRoot) || "default";
|
|
513
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
397
514
|
const template = this.runtimeMemoryPolicy?.namespaces[scope] ?? `memories/${scope}s/{${scope}Id}`;
|
|
398
515
|
return resolveMemoryNamespace(template, {
|
|
399
516
|
threadId: options.threadId,
|
|
@@ -403,36 +520,280 @@ export class AgentHarnessRuntime {
|
|
|
403
520
|
projectId: options.projectId ?? workspaceId,
|
|
404
521
|
});
|
|
405
522
|
}
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
523
|
+
getWorkspaceId(binding) {
|
|
524
|
+
const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
|
|
525
|
+
return path.basename(workspaceRoot) || "default";
|
|
526
|
+
}
|
|
527
|
+
resolveRecallScopes(input) {
|
|
528
|
+
if (Array.isArray(input.scopes) && input.scopes.length > 0) {
|
|
529
|
+
return Array.from(new Set(input.scopes));
|
|
530
|
+
}
|
|
531
|
+
const scopes = new Set(["thread", "agent", "workspace"]);
|
|
532
|
+
if (input.userId) {
|
|
533
|
+
scopes.add("user");
|
|
534
|
+
}
|
|
535
|
+
if (input.projectId) {
|
|
536
|
+
scopes.add("project");
|
|
537
|
+
}
|
|
538
|
+
return Array.from(scopes);
|
|
539
|
+
}
|
|
540
|
+
matchesRecallScope(record, filters) {
|
|
541
|
+
if (record.scope === "thread") {
|
|
542
|
+
return typeof filters.threadId === "string" && String(record.provenance.threadId ?? "") === filters.threadId;
|
|
543
|
+
}
|
|
544
|
+
if (record.scope === "agent") {
|
|
545
|
+
return String(record.provenance.agentId ?? "") === filters.agentId;
|
|
546
|
+
}
|
|
547
|
+
if (record.scope === "workspace") {
|
|
548
|
+
return String(record.provenance.workspaceId ?? filters.workspaceId) === filters.workspaceId;
|
|
549
|
+
}
|
|
550
|
+
if (record.scope === "user") {
|
|
551
|
+
return String(record.provenance.userId ?? "default") === filters.userId;
|
|
552
|
+
}
|
|
553
|
+
return String(record.provenance.projectId ?? filters.workspaceId) === filters.projectId;
|
|
554
|
+
}
|
|
555
|
+
getMemoryScopeBoost(scope) {
|
|
556
|
+
if (scope === "thread") {
|
|
557
|
+
return 4;
|
|
558
|
+
}
|
|
559
|
+
if (scope === "agent") {
|
|
560
|
+
return 2;
|
|
561
|
+
}
|
|
562
|
+
if (scope === "workspace") {
|
|
563
|
+
return 1;
|
|
564
|
+
}
|
|
565
|
+
return 0;
|
|
566
|
+
}
|
|
567
|
+
memoryFreshnessBoost(value) {
|
|
568
|
+
return Math.max(0, 1 - ((Date.now() - Date.parse(value)) / (1000 * 60 * 60 * 24 * 365)));
|
|
569
|
+
}
|
|
570
|
+
scoreStructuredRecord(query, record) {
|
|
571
|
+
return (scoreMemoryText(query, `${record.summary}\n${record.content}`, this.getMemoryScopeBoost(record.scope)) +
|
|
572
|
+
this.memoryFreshnessBoost(record.lastConfirmedAt) +
|
|
573
|
+
record.confidence);
|
|
574
|
+
}
|
|
575
|
+
normalizeMemoryDedupKey(record) {
|
|
576
|
+
return `${record.scope}::${record.summary.toLowerCase().replace(/\s+/g, " ").trim()}::${record.content.toLowerCase().replace(/\s+/g, " ").trim()}`;
|
|
577
|
+
}
|
|
578
|
+
inferMem0MemoryKind(hit) {
|
|
579
|
+
const candidates = [
|
|
580
|
+
...hit.categories,
|
|
581
|
+
typeof hit.metadata.kind === "string" ? hit.metadata.kind : undefined,
|
|
582
|
+
typeof hit.metadata.memoryType === "string" ? hit.metadata.memoryType : undefined,
|
|
583
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
584
|
+
for (const candidate of candidates) {
|
|
585
|
+
const kind = normalizeLangMemMemoryKind(candidate);
|
|
586
|
+
if (kind === "semantic" || kind === "episodic" || kind === "procedural") {
|
|
587
|
+
return kind;
|
|
412
588
|
}
|
|
413
|
-
|
|
414
|
-
|
|
589
|
+
}
|
|
590
|
+
return "semantic";
|
|
591
|
+
}
|
|
592
|
+
inferMem0MemoryScope(hit, filters) {
|
|
593
|
+
const metadataScope = typeof hit.metadata.scope === "string" ? hit.metadata.scope : undefined;
|
|
594
|
+
if (metadataScope === "thread" || metadataScope === "agent" || metadataScope === "workspace" || metadataScope === "user" || metadataScope === "project") {
|
|
595
|
+
return metadataScope;
|
|
596
|
+
}
|
|
597
|
+
if (typeof hit.metadata.threadId === "string" && filters.threadId && hit.metadata.threadId === filters.threadId) {
|
|
598
|
+
return "thread";
|
|
599
|
+
}
|
|
600
|
+
if (hit.agentId === filters.agentId || hit.metadata.agentId === filters.agentId) {
|
|
601
|
+
return "agent";
|
|
602
|
+
}
|
|
603
|
+
return "workspace";
|
|
604
|
+
}
|
|
605
|
+
createMem0MemoryRecord(hit, filters) {
|
|
606
|
+
const scope = this.inferMem0MemoryScope(hit, filters);
|
|
607
|
+
const kind = this.inferMem0MemoryKind(hit);
|
|
608
|
+
const summary = hit.memory.split("\n")[0]?.trim() || hit.memory;
|
|
609
|
+
return {
|
|
610
|
+
id: `mem0:${hit.id}`,
|
|
611
|
+
canonicalKey: `mem0:${hit.id}`,
|
|
612
|
+
kind,
|
|
613
|
+
scope,
|
|
614
|
+
content: hit.memory,
|
|
615
|
+
summary: summary.slice(0, 240),
|
|
616
|
+
status: "active",
|
|
617
|
+
confidence: Math.max(0, Math.min(1, hit.score || 0.5)),
|
|
618
|
+
createdAt: hit.createdAt,
|
|
619
|
+
observedAt: hit.createdAt,
|
|
620
|
+
lastConfirmedAt: hit.updatedAt,
|
|
621
|
+
sourceType: "mem0-search",
|
|
622
|
+
sourceRefs: [`mem0:${hit.id}`],
|
|
623
|
+
tags: hit.categories,
|
|
624
|
+
provenance: {
|
|
625
|
+
source: "mem0",
|
|
626
|
+
threadId: typeof hit.metadata.threadId === "string" ? hit.metadata.threadId : filters.threadId,
|
|
627
|
+
runId: hit.runId ?? (typeof hit.metadata.runId === "string" ? hit.metadata.runId : undefined),
|
|
628
|
+
agentId: hit.agentId ?? (typeof hit.metadata.agentId === "string" ? hit.metadata.agentId : filters.agentId),
|
|
629
|
+
workspaceId: filters.workspaceId,
|
|
630
|
+
userId: filters.userId,
|
|
631
|
+
projectId: filters.projectId,
|
|
632
|
+
...hit.metadata,
|
|
633
|
+
},
|
|
634
|
+
revision: 1,
|
|
635
|
+
supersedes: [],
|
|
636
|
+
conflictsWith: [],
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
async rankRecallCandidates(binding, input) {
|
|
640
|
+
const structuredRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, input.scopes);
|
|
641
|
+
const ranked = structuredRecords
|
|
642
|
+
.filter((record) => this.matchesRecallScope(record, input.filters))
|
|
643
|
+
.filter((record) => (input.includeStale ? record.status === "active" || record.status === "stale" : record.status === "active"))
|
|
644
|
+
.filter((record) => (input.kinds ? input.kinds.has(record.kind) : true))
|
|
645
|
+
.map((record) => ({
|
|
646
|
+
record,
|
|
647
|
+
score: this.scoreStructuredRecord(input.query, record),
|
|
648
|
+
}));
|
|
649
|
+
const deduped = new Map();
|
|
650
|
+
for (const item of ranked) {
|
|
651
|
+
deduped.set(this.normalizeMemoryDedupKey(item.record), item);
|
|
652
|
+
}
|
|
653
|
+
const vectorStore = await this.resolveRuntimeMemoryVectorStore();
|
|
654
|
+
if (vectorStore) {
|
|
655
|
+
try {
|
|
656
|
+
const vectorHits = await vectorStore.similaritySearch(input.query, Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK));
|
|
657
|
+
for (const hit of vectorHits) {
|
|
658
|
+
const metadata = typeof hit.metadata === "object" && hit.metadata && !Array.isArray(hit.metadata)
|
|
659
|
+
? hit.metadata
|
|
660
|
+
: {};
|
|
661
|
+
const recordId = typeof metadata.recordId === "string" ? metadata.recordId : undefined;
|
|
662
|
+
const scope = metadata.scope;
|
|
663
|
+
if (!recordId || (scope !== "thread" && scope !== "agent" && scope !== "workspace" && scope !== "user" && scope !== "project")) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const canonical = await getMemoryRecord(this.runtimeMemoryStore, scope, recordId);
|
|
667
|
+
if (!canonical) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (!this.matchesRecallScope(canonical, input.filters)) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (input.includeStale ? canonical.status !== "active" && canonical.status !== "stale" : canonical.status !== "active") {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (input.kinds && !input.kinds.has(canonical.kind)) {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const key = this.normalizeMemoryDedupKey(canonical);
|
|
680
|
+
const score = this.scoreStructuredRecord(input.query, canonical) + (typeof hit.score === "number" ? hit.score : 0);
|
|
681
|
+
const existing = deduped.get(key);
|
|
682
|
+
if (!existing || score > existing.score) {
|
|
683
|
+
deduped.set(key, { record: canonical, score });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
415
686
|
}
|
|
416
|
-
|
|
417
|
-
|
|
687
|
+
catch {
|
|
688
|
+
// Fail open to lexical/runtime ranking.
|
|
418
689
|
}
|
|
419
|
-
|
|
420
|
-
|
|
690
|
+
}
|
|
691
|
+
const supportsMem0Scope = input.scopes.some((scope) => scope === "thread" || scope === "agent" || scope === "workspace");
|
|
692
|
+
if (!this.mem0SemanticRecall || !supportsMem0Scope) {
|
|
693
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
const hits = await this.mem0SemanticRecall.search({
|
|
697
|
+
query: input.query,
|
|
698
|
+
topK: Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK),
|
|
699
|
+
agentId: input.filters.agentId,
|
|
700
|
+
threadId: input.filters.threadId,
|
|
701
|
+
});
|
|
702
|
+
for (const hit of hits) {
|
|
703
|
+
const record = this.createMem0MemoryRecord(hit, input.filters);
|
|
704
|
+
if (!input.scopes.includes(record.scope)) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (input.kinds && !input.kinds.has(record.kind)) {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const key = this.normalizeMemoryDedupKey(record);
|
|
711
|
+
if (deduped.has(key)) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
deduped.set(key, {
|
|
715
|
+
record,
|
|
716
|
+
score: this.scoreStructuredRecord(input.query, record) + Math.max(0, Math.min(1, hit.score)) * 4,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async rebuildRuntimeMemoryVectorIndex() {
|
|
726
|
+
const vectorStore = await this.resolveRuntimeMemoryVectorStore();
|
|
727
|
+
if (!vectorStore) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const records = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]))
|
|
731
|
+
.filter((record) => record.status === "active");
|
|
732
|
+
try {
|
|
733
|
+
await vectorStore.delete({ deleteAll: true });
|
|
734
|
+
if (records.length === 0) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
await vectorStore.addDocuments(records.map((record) => ({
|
|
738
|
+
pageContent: `${record.summary}\n${record.content}`,
|
|
739
|
+
metadata: {
|
|
740
|
+
recordId: record.id,
|
|
741
|
+
scope: record.scope,
|
|
742
|
+
kind: record.kind,
|
|
743
|
+
status: record.status,
|
|
744
|
+
threadId: record.provenance.threadId,
|
|
745
|
+
agentId: record.provenance.agentId,
|
|
746
|
+
workspaceId: record.provenance.workspaceId,
|
|
747
|
+
userId: record.provenance.userId,
|
|
748
|
+
projectId: record.provenance.projectId,
|
|
749
|
+
confidence: record.confidence,
|
|
750
|
+
lastConfirmedAt: record.lastConfirmedAt,
|
|
751
|
+
},
|
|
752
|
+
})));
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
// Fail open: vector indexing must not break durable memory writes.
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async buildRuntimeMemoryContext(binding, threadId, input) {
|
|
759
|
+
const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
|
|
760
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
761
|
+
const ranked = (await this.rankRecallCandidates(binding, {
|
|
762
|
+
query,
|
|
763
|
+
scopes: ["thread", "agent", "workspace", "user", "project"],
|
|
764
|
+
kinds: null,
|
|
765
|
+
topK: this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? 8,
|
|
766
|
+
includeStale: false,
|
|
767
|
+
filters: {
|
|
768
|
+
threadId,
|
|
769
|
+
agentId: binding.agent.id,
|
|
770
|
+
workspaceId,
|
|
771
|
+
userId: "default",
|
|
772
|
+
projectId: workspaceId,
|
|
773
|
+
},
|
|
774
|
+
}))
|
|
421
775
|
.map((record) => {
|
|
422
|
-
const scopeBoost = record.
|
|
423
|
-
|
|
776
|
+
const scopeBoost = record.record.scope === "thread"
|
|
777
|
+
? 4
|
|
778
|
+
: record.record.scope === "agent"
|
|
779
|
+
? 3
|
|
780
|
+
: record.record.scope === "user"
|
|
781
|
+
? 2
|
|
782
|
+
: 1;
|
|
424
783
|
return {
|
|
425
784
|
content: [
|
|
426
|
-
`# Durable ${record.scope[0].toUpperCase()}${record.scope.slice(1)} Memory`,
|
|
785
|
+
`# Durable ${record.record.scope[0].toUpperCase()}${record.record.scope.slice(1)} Memory`,
|
|
427
786
|
"",
|
|
428
|
-
`- summary: ${record.summary}`,
|
|
429
|
-
`- kind: ${record.kind}`,
|
|
430
|
-
`- scope: ${record.scope}`,
|
|
431
|
-
`- confidence: ${record.confidence.toFixed(2)}`,
|
|
787
|
+
`- summary: ${record.record.summary}`,
|
|
788
|
+
`- kind: ${record.record.kind}`,
|
|
789
|
+
`- scope: ${record.record.scope}`,
|
|
790
|
+
`- confidence: ${record.record.confidence.toFixed(2)}`,
|
|
432
791
|
"",
|
|
433
|
-
record.content,
|
|
792
|
+
record.record.content,
|
|
434
793
|
].join("\n"),
|
|
435
|
-
score: scoreMemoryText(query, `${record.summary}\n${record.content}`, scopeBoost) +
|
|
794
|
+
score: scoreMemoryText(query, `${record.record.summary}\n${record.record.content}`, scopeBoost) +
|
|
795
|
+
this.memoryFreshnessBoost(record.record.lastConfirmedAt) +
|
|
796
|
+
record.record.confidence,
|
|
436
797
|
};
|
|
437
798
|
})
|
|
438
799
|
.sort((left, right) => right.score - left.score)
|
|
@@ -446,36 +807,70 @@ export class AgentHarnessRuntime {
|
|
|
446
807
|
if (!Array.isArray(value) || value.length === 0) {
|
|
447
808
|
return;
|
|
448
809
|
}
|
|
810
|
+
if (this.runtimeMemoryFormationConfig?.hotPath.enabled === false) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
449
813
|
const candidates = value
|
|
450
814
|
.filter((candidate) => typeof candidate === "object" && candidate !== null)
|
|
451
815
|
.filter((candidate) => candidate.noStore !== true && typeof candidate.content === "string" && candidate.content.trim().length > 0);
|
|
452
816
|
if (candidates.length === 0) {
|
|
453
817
|
return;
|
|
454
818
|
}
|
|
455
|
-
|
|
456
|
-
const workspaceCandidates = candidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
|
|
457
|
-
const agentCandidates = candidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
|
|
458
|
-
const recordedAt = new Date().toISOString();
|
|
459
|
-
await this.runtimeMemoryStore.put(["memories", "candidates", threadId], `${runId}.json`, {
|
|
460
|
-
runId,
|
|
461
|
-
threadId,
|
|
462
|
-
storedAt: recordedAt,
|
|
463
|
-
candidates,
|
|
464
|
-
});
|
|
465
|
-
await persistStructuredMemoryRecords({
|
|
466
|
-
store: this.runtimeMemoryStore,
|
|
819
|
+
await this.persistStructuredMemoryCandidates(binding, {
|
|
467
820
|
candidates,
|
|
468
821
|
threadId,
|
|
469
822
|
runId,
|
|
470
823
|
agentId: binding.agent.id,
|
|
471
|
-
recordedAt,
|
|
824
|
+
recordedAt: new Date().toISOString(),
|
|
825
|
+
storeCandidateLog: true,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
async persistStructuredMemoryCandidates(binding, input) {
|
|
829
|
+
const workspaceId = this.getWorkspaceId(binding);
|
|
830
|
+
const userId = input.userId ?? "default";
|
|
831
|
+
const projectId = input.projectId ?? workspaceId;
|
|
832
|
+
const existingRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]);
|
|
833
|
+
const transformedCandidates = this.runtimeMemoryManager
|
|
834
|
+
? await this.runtimeMemoryManager.transform({
|
|
835
|
+
candidates: input.candidates,
|
|
836
|
+
binding,
|
|
837
|
+
threadId: input.threadId,
|
|
838
|
+
runId: input.runId,
|
|
839
|
+
recordedAt: input.recordedAt,
|
|
840
|
+
existingRecords,
|
|
841
|
+
})
|
|
842
|
+
: input.candidates;
|
|
843
|
+
const threadCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "thread");
|
|
844
|
+
const workspaceCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
|
|
845
|
+
const agentCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
|
|
846
|
+
const userCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "user");
|
|
847
|
+
const projectCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "project");
|
|
848
|
+
if (input.storeCandidateLog) {
|
|
849
|
+
await this.runtimeMemoryStore.put(["memories", "candidates", input.threadId], `${input.runId}.json`, {
|
|
850
|
+
runId: input.runId,
|
|
851
|
+
threadId: input.threadId,
|
|
852
|
+
storedAt: input.recordedAt,
|
|
853
|
+
candidates: transformedCandidates,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const persisted = await persistStructuredMemoryRecords({
|
|
857
|
+
store: this.runtimeMemoryStore,
|
|
858
|
+
candidates: transformedCandidates,
|
|
859
|
+
threadId: input.threadId,
|
|
860
|
+
runId: input.runId,
|
|
861
|
+
agentId: input.agentId,
|
|
862
|
+
workspaceId,
|
|
863
|
+
userId,
|
|
864
|
+
projectId,
|
|
865
|
+
recordedAt: input.recordedAt,
|
|
472
866
|
});
|
|
867
|
+
await this.rebuildRuntimeMemoryVectorIndex();
|
|
473
868
|
const writes = [];
|
|
474
869
|
if (threadCandidates.length > 0) {
|
|
475
|
-
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
|
|
870
|
+
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
|
|
476
871
|
writes.push(consolidateStructuredMemoryScope({
|
|
477
872
|
store: this.runtimeMemoryStore,
|
|
478
|
-
namespace: this.resolveMemoryNamespace("thread", binding, { threadId }),
|
|
873
|
+
namespace: this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }),
|
|
479
874
|
title: "Thread Structured Memory",
|
|
480
875
|
scope: "thread",
|
|
481
876
|
maxEntries: 12,
|
|
@@ -504,7 +899,30 @@ export class AgentHarnessRuntime {
|
|
|
504
899
|
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
505
900
|
}));
|
|
506
901
|
}
|
|
902
|
+
if (userCandidates.length > 0) {
|
|
903
|
+
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("user", binding, { userId }), "tool-memory.md", userCandidates, 20, "User Tool Memory"));
|
|
904
|
+
writes.push(consolidateStructuredMemoryScope({
|
|
905
|
+
store: this.runtimeMemoryStore,
|
|
906
|
+
namespace: this.resolveMemoryNamespace("user", binding, { userId }),
|
|
907
|
+
title: "User Structured Memory",
|
|
908
|
+
scope: "user",
|
|
909
|
+
maxEntries: 20,
|
|
910
|
+
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
911
|
+
}));
|
|
912
|
+
}
|
|
913
|
+
if (projectCandidates.length > 0) {
|
|
914
|
+
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("project", binding, { projectId }), "tool-memory.md", projectCandidates, 20, "Project Tool Memory"));
|
|
915
|
+
writes.push(consolidateStructuredMemoryScope({
|
|
916
|
+
store: this.runtimeMemoryStore,
|
|
917
|
+
namespace: this.resolveMemoryNamespace("project", binding, { projectId }),
|
|
918
|
+
title: "Project Structured Memory",
|
|
919
|
+
scope: "project",
|
|
920
|
+
maxEntries: 20,
|
|
921
|
+
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
507
924
|
await Promise.all(writes);
|
|
925
|
+
return persisted;
|
|
508
926
|
}
|
|
509
927
|
async appendMemoryDigest(namespace, key, candidates, maxEntries, title) {
|
|
510
928
|
const existing = await this.runtimeMemoryStore.get(namespace, key);
|
|
@@ -776,10 +1194,12 @@ export class AgentHarnessRuntime {
|
|
|
776
1194
|
this.unregisterThreadMemorySync();
|
|
777
1195
|
this.unregisterRuntimeMemorySync();
|
|
778
1196
|
this.unregisterMem0IngestionSync();
|
|
1197
|
+
this.unregisterRuntimeMemoryFormationSync();
|
|
779
1198
|
await Promise.allSettled(Array.from(this.backgroundTasks));
|
|
780
1199
|
await this.threadMemorySync?.close();
|
|
781
1200
|
await this.runtimeMemorySync?.close();
|
|
782
1201
|
await this.mem0IngestionSync?.close();
|
|
1202
|
+
await this.runtimeMemoryFormationSync?.close();
|
|
783
1203
|
await closeMcpClientsForWorkspace(this.workspace);
|
|
784
1204
|
this.initialized = false;
|
|
785
1205
|
}
|
|
@@ -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":
|