@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.
@@ -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) => this.matchesRecallScope(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.score - left.score)
314
- .slice(0, topK)
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
- async buildRuntimeMemoryContext(binding, threadId, input) {
522
- const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
523
- const ranked = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace"]))
524
- .filter((record) => {
525
- if (record.status !== "active") {
526
- return false;
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
- if (record.scope === "thread") {
529
- return String(record.provenance.threadId ?? "") === threadId;
800
+ catch {
801
+ // Fail open to lexical/runtime ranking.
530
802
  }
531
- if (record.scope === "agent") {
532
- return String(record.provenance.agentId ?? "") === binding.agent.id;
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 true;
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.scope === "thread" ? 4 : record.scope === "agent" ? 2 : 1;
538
- const freshnessBoost = Math.max(0, 1 - ((Date.now() - Date.parse(record.lastConfirmedAt)) / (1000 * 60 * 60 * 24 * 365)));
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) + freshnessBoost + record.confidence,
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 threadCandidates = input.candidates.filter((candidate) => (candidate.scope ?? "thread") === "thread");
584
- const workspaceCandidates = input.candidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
585
- const agentCandidates = input.candidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
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: input.candidates,
990
+ candidates: transformedCandidates,
592
991
  });
593
992
  }
594
993
  const persisted = await persistStructuredMemoryRecords({
595
994
  store: this.runtimeMemoryStore,
596
- candidates: input.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":