@botbotgo/agent-harness 0.0.154 → 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 +9 -2
- package/README.zh.md +16 -2
- 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/workspace.d.ts +6 -0
- 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 +1 -0
- package/dist/runtime/harness/system/runtime-memory-records.js +9 -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 +14 -0
- package/dist/runtime/harness.js +332 -45
- 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);
|
|
@@ -294,23 +343,20 @@ 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;
|
|
297
|
-
const items = (await
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
Math.max(0, 1 - ((Date.now() - Date.parse(record.lastConfirmedAt)) / (1000 * 60 * 60 * 24 * 365))) +
|
|
311
|
-
record.confidence,
|
|
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
|
+
},
|
|
312
359
|
}))
|
|
313
|
-
.sort((left, right) => right.score - left.score)
|
|
314
360
|
.slice(0, topK)
|
|
315
361
|
.map(({ record }) => record);
|
|
316
362
|
return { items };
|
|
@@ -518,36 +564,236 @@ export class AgentHarnessRuntime {
|
|
|
518
564
|
}
|
|
519
565
|
return 0;
|
|
520
566
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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;
|
|
527
588
|
}
|
|
528
|
-
|
|
529
|
-
|
|
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
|
+
}
|
|
530
686
|
}
|
|
531
|
-
|
|
532
|
-
|
|
687
|
+
catch {
|
|
688
|
+
// Fail open to lexical/runtime ranking.
|
|
533
689
|
}
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
}))
|
|
536
775
|
.map((record) => {
|
|
537
|
-
const scopeBoost = record.
|
|
538
|
-
|
|
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;
|
|
539
783
|
return {
|
|
540
784
|
content: [
|
|
541
|
-
`# Durable ${record.scope[0].toUpperCase()}${record.scope.slice(1)} Memory`,
|
|
785
|
+
`# Durable ${record.record.scope[0].toUpperCase()}${record.record.scope.slice(1)} Memory`,
|
|
542
786
|
"",
|
|
543
|
-
`- summary: ${record.summary}`,
|
|
544
|
-
`- kind: ${record.kind}`,
|
|
545
|
-
`- scope: ${record.scope}`,
|
|
546
|
-
`- 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)}`,
|
|
547
791
|
"",
|
|
548
|
-
record.content,
|
|
792
|
+
record.record.content,
|
|
549
793
|
].join("\n"),
|
|
550
|
-
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,
|
|
551
797
|
};
|
|
552
798
|
})
|
|
553
799
|
.sort((left, right) => right.score - left.score)
|
|
@@ -561,6 +807,9 @@ export class AgentHarnessRuntime {
|
|
|
561
807
|
if (!Array.isArray(value) || value.length === 0) {
|
|
562
808
|
return;
|
|
563
809
|
}
|
|
810
|
+
if (this.runtimeMemoryFormationConfig?.hotPath.enabled === false) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
564
813
|
const candidates = value
|
|
565
814
|
.filter((candidate) => typeof candidate === "object" && candidate !== null)
|
|
566
815
|
.filter((candidate) => candidate.noStore !== true && typeof candidate.content === "string" && candidate.content.trim().length > 0);
|
|
@@ -580,20 +829,33 @@ export class AgentHarnessRuntime {
|
|
|
580
829
|
const workspaceId = this.getWorkspaceId(binding);
|
|
581
830
|
const userId = input.userId ?? "default";
|
|
582
831
|
const projectId = input.projectId ?? workspaceId;
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
|
|
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");
|
|
586
848
|
if (input.storeCandidateLog) {
|
|
587
849
|
await this.runtimeMemoryStore.put(["memories", "candidates", input.threadId], `${input.runId}.json`, {
|
|
588
850
|
runId: input.runId,
|
|
589
851
|
threadId: input.threadId,
|
|
590
852
|
storedAt: input.recordedAt,
|
|
591
|
-
candidates:
|
|
853
|
+
candidates: transformedCandidates,
|
|
592
854
|
});
|
|
593
855
|
}
|
|
594
856
|
const persisted = await persistStructuredMemoryRecords({
|
|
595
857
|
store: this.runtimeMemoryStore,
|
|
596
|
-
candidates:
|
|
858
|
+
candidates: transformedCandidates,
|
|
597
859
|
threadId: input.threadId,
|
|
598
860
|
runId: input.runId,
|
|
599
861
|
agentId: input.agentId,
|
|
@@ -602,6 +864,7 @@ export class AgentHarnessRuntime {
|
|
|
602
864
|
projectId,
|
|
603
865
|
recordedAt: input.recordedAt,
|
|
604
866
|
});
|
|
867
|
+
await this.rebuildRuntimeMemoryVectorIndex();
|
|
605
868
|
const writes = [];
|
|
606
869
|
if (threadCandidates.length > 0) {
|
|
607
870
|
writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
|
|
@@ -636,6 +899,28 @@ export class AgentHarnessRuntime {
|
|
|
636
899
|
config: this.runtimeMemoryMaintenanceConfig ?? undefined,
|
|
637
900
|
}));
|
|
638
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
|
+
}
|
|
639
924
|
await Promise.all(writes);
|
|
640
925
|
return persisted;
|
|
641
926
|
}
|
|
@@ -909,10 +1194,12 @@ export class AgentHarnessRuntime {
|
|
|
909
1194
|
this.unregisterThreadMemorySync();
|
|
910
1195
|
this.unregisterRuntimeMemorySync();
|
|
911
1196
|
this.unregisterMem0IngestionSync();
|
|
1197
|
+
this.unregisterRuntimeMemoryFormationSync();
|
|
912
1198
|
await Promise.allSettled(Array.from(this.backgroundTasks));
|
|
913
1199
|
await this.threadMemorySync?.close();
|
|
914
1200
|
await this.runtimeMemorySync?.close();
|
|
915
1201
|
await this.mem0IngestionSync?.close();
|
|
1202
|
+
await this.runtimeMemoryFormationSync?.close();
|
|
916
1203
|
await closeMcpClientsForWorkspace(this.workspace);
|
|
917
1204
|
this.initialized = false;
|
|
918
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":
|
|
@@ -3,6 +3,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
3
3
|
import { Document } from "@langchain/core/documents";
|
|
4
4
|
import { createClient } from "@libsql/client";
|
|
5
5
|
import { LibSQLVectorStore } from "@langchain/community/vectorstores/libsql";
|
|
6
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
6
7
|
import { compileVectorStore } from "../../workspace/resource-compilers.js";
|
|
7
8
|
import { resolveRefId } from "../../workspace/support/workspace-ref-utils.js";
|
|
8
9
|
import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./embedding-models.js";
|
|
@@ -52,6 +53,27 @@ async function ensureLibSqlSchema(db, table, column, dimensions) {
|
|
|
52
53
|
ON ${safeTable}(libsql_vector_idx(${safeColumn}))
|
|
53
54
|
`);
|
|
54
55
|
}
|
|
56
|
+
function createQdrantClientForStore(vectorStore) {
|
|
57
|
+
return new QdrantClient({
|
|
58
|
+
...(vectorStore.url ? { url: vectorStore.url } : {}),
|
|
59
|
+
...(vectorStore.host ? { host: vectorStore.host } : {}),
|
|
60
|
+
...(typeof vectorStore.port === "number" ? { port: vectorStore.port } : {}),
|
|
61
|
+
...(vectorStore.authToken ? { apiKey: vectorStore.authToken } : {}),
|
|
62
|
+
checkCompatibility: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function ensureQdrantCollection(client, collection, dimensions) {
|
|
66
|
+
const collections = await client.getCollections();
|
|
67
|
+
if (collections.collections.some((entry) => entry.name === collection)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await client.createCollection(collection, {
|
|
71
|
+
vectors: {
|
|
72
|
+
size: dimensions,
|
|
73
|
+
distance: "Cosine",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
55
77
|
export function resolveCompiledVectorStoreRef(workspace, vectorStoreRef) {
|
|
56
78
|
const resolvedId = vectorStoreRef ? resolveRefId(vectorStoreRef) : "default";
|
|
57
79
|
const vectorStore = workspace.vectorStores.get(resolvedId);
|
|
@@ -75,6 +97,81 @@ export async function resolveCompiledVectorStore(workspace, vectorStore, options
|
|
|
75
97
|
const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
|
|
76
98
|
return createLlamaIndexVectorStore(workspace.workspaceRoot, vectorStore, embeddings);
|
|
77
99
|
}
|
|
100
|
+
if (vectorStore.kind === "QdrantVectorStore") {
|
|
101
|
+
const embeddingModel = resolveCompiledEmbeddingModelRef(workspace, vectorStore.embeddingModelRef);
|
|
102
|
+
const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
|
|
103
|
+
const client = createQdrantClientForStore(vectorStore);
|
|
104
|
+
const collection = vectorStore.collection ?? "vectors";
|
|
105
|
+
const ensureCollectionForTexts = async (texts) => {
|
|
106
|
+
const seedText = texts.find((text) => text.trim().length > 0) ?? "seed";
|
|
107
|
+
const sample = await embeddings.embedQuery(seedText);
|
|
108
|
+
await ensureQdrantCollection(client, collection, sample.length);
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
kind: vectorStore.kind,
|
|
112
|
+
embeddingModelRef: vectorStore.embeddingModelRef,
|
|
113
|
+
addDocuments: async (documents) => {
|
|
114
|
+
if (documents.length === 0) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
await ensureCollectionForTexts(documents.map((document) => document.pageContent));
|
|
118
|
+
const vectors = await embeddings.embedDocuments(documents.map((document) => document.pageContent));
|
|
119
|
+
const ids = documents.map((_, index) => `${Date.now()}-${index}-${Math.random().toString(36).slice(2, 10)}`);
|
|
120
|
+
await client.upsert(collection, {
|
|
121
|
+
wait: true,
|
|
122
|
+
points: documents.map((document, index) => ({
|
|
123
|
+
id: ids[index] ?? index,
|
|
124
|
+
vector: vectors[index] ?? [],
|
|
125
|
+
payload: {
|
|
126
|
+
pageContent: document.pageContent,
|
|
127
|
+
...(document.metadata ? { metadata: document.metadata } : {}),
|
|
128
|
+
},
|
|
129
|
+
})),
|
|
130
|
+
});
|
|
131
|
+
return ids;
|
|
132
|
+
},
|
|
133
|
+
similaritySearch: async (query, limit) => {
|
|
134
|
+
const collections = await client.getCollections();
|
|
135
|
+
if (!collections.collections.some((entry) => entry.name === collection)) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const queryVector = await embeddings.embedQuery(query);
|
|
139
|
+
const rows = await client.search(collection, {
|
|
140
|
+
vector: queryVector,
|
|
141
|
+
limit,
|
|
142
|
+
with_payload: true,
|
|
143
|
+
});
|
|
144
|
+
return rows.map((row) => {
|
|
145
|
+
const payload = (row.payload ?? {});
|
|
146
|
+
return {
|
|
147
|
+
pageContent: typeof payload.pageContent === "string" ? payload.pageContent : "",
|
|
148
|
+
metadata: typeof payload.metadata === "object" && payload.metadata && !Array.isArray(payload.metadata)
|
|
149
|
+
? payload.metadata
|
|
150
|
+
: {},
|
|
151
|
+
score: typeof row.score === "number" ? row.score : undefined,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
delete: async (params) => {
|
|
156
|
+
const collections = await client.getCollections();
|
|
157
|
+
if (!collections.collections.some((entry) => entry.name === collection)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (params.deleteAll) {
|
|
161
|
+
await client.deleteCollection(collection);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const pointIds = (params.ids ?? []).filter((id) => typeof id === "string" || typeof id === "number");
|
|
165
|
+
if (pointIds.length === 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await client.delete(collection, {
|
|
169
|
+
wait: true,
|
|
170
|
+
points: pointIds,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
78
175
|
if (vectorStore.kind !== "LibSQLVectorStore") {
|
|
79
176
|
throw new Error(`Vector store kind ${vectorStore.kind} is not supported by the built-in runtime.`);
|
|
80
177
|
}
|