@botbotgo/agent-harness 0.0.281 → 0.0.283

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +20 -9
  2. package/README.zh.md +20 -9
  3. package/dist/config/catalogs/stores.yaml +1 -1
  4. package/dist/config/catalogs/vector-stores.yaml +1 -1
  5. package/dist/config/knowledge/knowledge-runtime.yaml +60 -0
  6. package/dist/config/runtime/runtime-memory.yaml +3 -3
  7. package/dist/contracts/runtime.d.ts +9 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +1 -0
  10. package/dist/init-project.js +50 -4
  11. package/dist/knowledge/config.d.ts +11 -0
  12. package/dist/knowledge/config.js +32 -0
  13. package/dist/knowledge/contracts.d.ts +45 -0
  14. package/dist/knowledge/contracts.js +1 -0
  15. package/dist/knowledge/index.d.ts +4 -0
  16. package/dist/knowledge/index.js +2 -0
  17. package/dist/knowledge/module.d.ts +21 -0
  18. package/dist/knowledge/module.js +594 -0
  19. package/dist/package-version.d.ts +1 -1
  20. package/dist/package-version.js +1 -1
  21. package/dist/persistence/file-store.d.ts +1 -0
  22. package/dist/persistence/file-store.js +29 -25
  23. package/dist/persistence/sqlite-store.js +1 -1
  24. package/dist/runtime/harness/run/resources.js +4 -2
  25. package/dist/runtime/harness/system/runtime-memory-policy.js +1 -1
  26. package/dist/runtime/harness/system/runtime-memory-records.js +4 -0
  27. package/dist/runtime/harness/system/runtime-memory-sync.js +2 -2
  28. package/dist/runtime/harness/system/thread-memory-sync.js +1 -1
  29. package/dist/runtime/harness.d.ts +1 -16
  30. package/dist/runtime/harness.js +95 -613
  31. package/dist/runtime/maintenance/checkpoint-maintenance.js +2 -1
  32. package/dist/runtime/maintenance/runtime-record-maintenance.js +2 -1
  33. package/dist/runtime/support/llamaindex.js +1 -1
  34. package/dist/runtime/support/runtime-factories.js +6 -3
  35. package/dist/runtime/support/runtime-layout.d.ts +6 -0
  36. package/dist/runtime/support/runtime-layout.js +19 -0
  37. package/package.json +1 -1
@@ -10,7 +10,7 @@ import { getConcurrencyConfig, getRecoveryConfig, getRoutingDefaultAgentId, getR
10
10
  import { createHarnessEvent, inferRoutingBindings, renderRuntimeFailure, } from "./support/harness-support.js";
11
11
  import { ThreadMemorySync } from "./harness/system/thread-memory-sync.js";
12
12
  import { RuntimeMemorySync, readRuntimeMemorySyncConfig } from "./harness/system/runtime-memory-sync.js";
13
- import { FileBackedStore } from "./harness/system/store.js";
13
+ import { SqliteBackedStore } from "./harness/system/store.js";
14
14
  import { HealthMonitor, readHealthMonitorConfig, } from "./harness/system/health-monitor.js";
15
15
  import { normalizeInvocationEnvelope, normalizeRunPriority, resolveRunListeners, } from "./harness/run/helpers.js";
16
16
  import { emitHarnessEvent, } from "./harness/events/events.js";
@@ -32,17 +32,16 @@ import { describeWorkspaceInventory, getAgentInventoryRecord, listAgentSkills as
32
32
  import { createDefaultHealthSnapshot, isInventoryEnabled, isThreadMemorySyncEnabled, } from "./harness/runtime-defaults.js";
33
33
  import { Mem0IngestionSync, Mem0SemanticRecall, readMem0RuntimeConfig, } from "./harness/system/mem0-ingestion-sync.js";
34
34
  import { createRuntimeMemoryManager, RuntimeMemoryFormationSync, readRuntimeMemoryFormationConfig, } from "./harness/system/runtime-memory-manager.js";
35
- import { renderMemoryCandidatesMarkdown } from "./harness/system/runtime-memory-candidates.js";
36
- import { findMemoryRecordById, getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, removeMemoryRecord, updateMemoryRecord, } from "./harness/system/runtime-memory-records.js";
37
- import { consolidateStructuredMemoryScope } from "./harness/system/runtime-memory-consolidation.js";
38
- import { normalizeLangMemMemoryKind, readRuntimeMemoryMaintenanceConfig, readRuntimeMemoryPolicyConfig, resolveMemoryNamespace, scoreMemoryText, } from "./harness/system/runtime-memory-policy.js";
35
+ import { readRuntimeMemoryMaintenanceConfig, readRuntimeMemoryPolicyConfig, resolveMemoryNamespace, } from "./harness/system/runtime-memory-policy.js";
39
36
  import { resolveRuntimeAdapterOptions } from "./support/runtime-adapter-options.js";
37
+ import { resolveKnowledgeStorePath } from "./support/runtime-layout.js";
40
38
  import { initializeHarnessRuntime, reclaimExpiredClaimedRuns as reclaimHarnessExpiredClaimedRuns, recoverStartupRuns as recoverHarnessStartupRuns, isStaleRunningRun as isHarnessStaleRunningRun, } from "./harness/run/startup-runtime.js";
41
39
  import { traceStartupStage } from "./startup-tracing.js";
42
40
  import { normalizeProcessExecutablePath } from "./support/runtime-env.js";
43
41
  import { streamHarnessRun } from "./harness/run/stream-run.js";
44
42
  import { defaultRequestedAgentId, prepareRunStart } from "./harness/run/start-run.js";
45
43
  import { buildRequestInspectionRecord, buildSessionInspectionRecord, deleteSessionRecord, deleteThreadRecord, getPublicApproval, listPublicApprovals, } from "./harness/run/thread-records.js";
44
+ import { createKnowledgeModule } from "../knowledge/index.js";
46
45
  const ACTIVE_RUN_STATES = [
47
46
  "queued",
48
47
  "claimed",
@@ -196,6 +195,7 @@ export class AgentHarnessRuntime {
196
195
  mem0SemanticRecall;
197
196
  runtimeMemoryFormationConfig;
198
197
  runtimeMemoryManager;
198
+ knowledgeModule;
199
199
  runtimeMemoryFormationSync;
200
200
  unregisterRuntimeMemoryFormationSync;
201
201
  resolvedRuntimeAdapterOptions;
@@ -257,11 +257,11 @@ export class AgentHarnessRuntime {
257
257
  normalizeProcessExecutablePath();
258
258
  this.runtimeEntryBindings = inferRoutingBindings(this.workspace).runtimeEntryBindings;
259
259
  this.defaultRuntimeEntryBinding = this.runtimeEntryBindings[0];
260
- this.defaultRunRootValue = this.defaultRuntimeEntryBinding?.harnessRuntime.runRoot ?? `${this.workspace.workspaceRoot}/run-data`;
260
+ this.defaultRunRootValue = this.defaultRuntimeEntryBinding?.harnessRuntime.runRoot ?? `${this.workspace.workspaceRoot}/.agent`;
261
261
  const runRoot = this.defaultRunRoot();
262
262
  this.persistence = new SqlitePersistence(runRoot);
263
263
  const defaultStoreConfig = this.defaultRuntimeEntryBinding?.harnessRuntime.store;
264
- this.defaultStore = resolveStoreFromConfig(this.stores, defaultStoreConfig, runRoot) ?? new FileBackedStore(`${runRoot}/store.json`);
264
+ this.defaultStore = resolveStoreFromConfig(this.stores, defaultStoreConfig, runRoot) ?? new SqliteBackedStore(resolveKnowledgeStorePath(runRoot));
265
265
  const runtimeMemoryStoreConfig = typeof this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory?.store === "object" &&
266
266
  this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory?.store
267
267
  ? this.defaultRuntimeEntryBinding.harnessRuntime.runtimeMemory.store
@@ -292,12 +292,12 @@ export class AgentHarnessRuntime {
292
292
  this.routingDefaultAgentId = getRoutingDefaultAgentId(workspace.refs);
293
293
  if (isThreadMemorySyncEnabled(workspace)) {
294
294
  this.threadMemorySync = new ThreadMemorySync(this.persistence, this.runtimeMemoryStore, {
295
- resolveThreadNamespace: (threadId) => {
295
+ resolveThreadNamespace: (sessionId) => {
296
296
  const binding = this.defaultRuntimeEntryBinding;
297
297
  if (!binding) {
298
- return ["memories", "threads", threadId];
298
+ return ["memories", "sessions", sessionId];
299
299
  }
300
- return this.resolveMemoryNamespace("thread", binding, { threadId });
300
+ return this.resolveMemoryNamespace("thread", binding, { sessionId });
301
301
  },
302
302
  });
303
303
  this.unregisterThreadMemorySync = this.eventBus.registerProjection(this.threadMemorySync);
@@ -309,12 +309,12 @@ export class AgentHarnessRuntime {
309
309
  const runtimeMemorySyncConfig = readRuntimeMemorySyncConfig(this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory);
310
310
  if (runtimeMemorySyncConfig) {
311
311
  this.runtimeMemorySync = new RuntimeMemorySync(this.persistence, this.runtimeMemoryStore, runtimeMemorySyncConfig, {
312
- resolveThreadNamespace: (threadId) => {
312
+ resolveThreadNamespace: (sessionId) => {
313
313
  const binding = this.defaultRuntimeEntryBinding;
314
314
  if (!binding) {
315
- return ["memories", "threads", threadId];
315
+ return ["memories", "sessions", sessionId];
316
316
  }
317
- return this.resolveMemoryNamespace("thread", binding, { threadId });
317
+ return this.resolveMemoryNamespace("thread", binding, { sessionId });
318
318
  },
319
319
  });
320
320
  this.unregisterRuntimeMemorySync = this.eventBus.registerProjection(this.runtimeMemorySync);
@@ -345,17 +345,54 @@ export class AgentHarnessRuntime {
345
345
  modelResolver: this.resolvedRuntimeAdapterOptions.modelResolver,
346
346
  })
347
347
  : null;
348
+ this.knowledgeModule = createKnowledgeModule({
349
+ store: this.runtimeMemoryStore,
350
+ policy: this.runtimeMemoryPolicy,
351
+ maintenanceConfig: this.runtimeMemoryMaintenanceConfig,
352
+ resolveNamespace: (scope, context) => {
353
+ const binding = this.defaultRuntimeEntryBinding;
354
+ if (!binding) {
355
+ const identifier = scope === "thread"
356
+ ? context.sessionId
357
+ : scope === "agent"
358
+ ? context.agentId
359
+ : scope === "workspace"
360
+ ? context.workspaceId
361
+ : scope === "user"
362
+ ? context.userId ?? "default"
363
+ : context.projectId ?? context.workspaceId;
364
+ return ["memories", `${scope}s`, identifier ?? "default"];
365
+ }
366
+ return this.resolveMemoryNamespace(scope, binding, {
367
+ sessionId: context.sessionId,
368
+ agentId: context.agentId,
369
+ userId: context.userId,
370
+ projectId: context.projectId,
371
+ });
372
+ },
373
+ resolveVectorStore: () => this.resolveRuntimeMemoryVectorStore(),
374
+ transformCandidates: this.runtimeMemoryManager && this.defaultRuntimeEntryBinding
375
+ ? ({ candidates, context, existingRecords }) => this.runtimeMemoryManager.transform({
376
+ candidates,
377
+ binding: this.defaultRuntimeEntryBinding,
378
+ threadId: context.sessionId ?? `memory-api-${context.requestId ?? "unknown"}`,
379
+ runId: context.requestId ?? createPersistentId(new Date(context.recordedAt ?? new Date().toISOString())),
380
+ recordedAt: context.recordedAt ?? new Date().toISOString(),
381
+ existingRecords,
382
+ })
383
+ : undefined,
384
+ getMem0SemanticRecall: () => this.mem0SemanticRecall,
385
+ });
348
386
  if (runtimeMemoryFormationConfig) {
349
- this.runtimeMemoryFormationSync = new RuntimeMemoryFormationSync(this.persistence, runtimeMemoryFormationConfig, (input) => this.persistStructuredMemoryCandidates(this.defaultRuntimeEntryBinding, {
350
- candidates: input.candidates,
351
- threadId: input.threadId,
352
- runId: input.runId,
387
+ this.runtimeMemoryFormationSync = new RuntimeMemoryFormationSync(this.persistence, runtimeMemoryFormationConfig, (input) => this.knowledgeModule.memorizeCandidates(input.candidates, {
388
+ sessionId: input.threadId,
389
+ requestId: input.runId,
353
390
  agentId: input.agentId,
391
+ workspaceId: this.getWorkspaceId(this.defaultRuntimeEntryBinding),
354
392
  userId: input.userId,
355
393
  projectId: input.projectId,
356
394
  recordedAt: input.recordedAt,
357
- storeCandidateLog: false,
358
- }).then(() => undefined), runRoot);
395
+ }, { storeCandidateLog: false }).then(() => undefined), runRoot);
359
396
  this.unregisterRuntimeMemoryFormationSync = this.eventBus.registerProjection(this.runtimeMemoryFormationSync);
360
397
  }
361
398
  else {
@@ -480,35 +517,17 @@ export class AgentHarnessRuntime {
480
517
  if (!binding) {
481
518
  throw new Error("memorize requires a runtime entry binding.");
482
519
  }
483
- if (!Array.isArray(input.records)) {
484
- throw new Error("memorize requires input.records to be an array.");
485
- }
486
- const candidates = input.records
487
- .filter((record) => typeof record === "object" && record !== null)
488
- .filter((record) => record.noStore !== true);
489
- if (candidates.length === 0) {
490
- return { records: [], decisions: [] };
491
- }
492
- if (candidates.some((record) => typeof record.content !== "string" || record.content.trim().length === 0)) {
493
- throw new Error("memorize requires every record to include non-empty content.");
494
- }
495
- const sessionId = input.sessionId;
496
- const requestId = input.requestId;
497
- if (candidates.some((record) => (record.scope ?? "thread") === "thread") && !sessionId) {
498
- throw new Error("memorize requires sessionId when storing thread-scoped memory.");
499
- }
500
- const recordedAt = input.recordedAt ?? new Date().toISOString();
501
- const runId = requestId ?? createPersistentId(new Date(recordedAt));
502
- const threadId = sessionId ?? `memory-api-${runId}`;
503
- return this.persistStructuredMemoryCandidates(binding, {
504
- candidates: candidates.map((record) => ({ ...record, content: record.content.trim() })),
505
- threadId,
506
- runId,
520
+ return this.knowledgeModule.memorize({
521
+ records: input.records,
522
+ storeCandidateLog: true,
523
+ }, {
524
+ sessionId: input.sessionId,
525
+ requestId: input.requestId,
507
526
  agentId: input.agentId ?? binding.agent.id,
527
+ workspaceId: this.getWorkspaceId(binding),
508
528
  userId: input.userId,
509
529
  projectId: input.projectId,
510
- recordedAt,
511
- storeCandidateLog: true,
530
+ recordedAt: input.recordedAt,
512
531
  });
513
532
  }
514
533
  async recall(input) {
@@ -516,129 +535,46 @@ export class AgentHarnessRuntime {
516
535
  if (!binding) {
517
536
  throw new Error("recall requires a runtime entry binding.");
518
537
  }
519
- if (typeof input.query !== "string" || input.query.trim().length === 0) {
520
- throw new Error("recall requires a non-empty query.");
521
- }
522
- const sessionId = input.sessionId;
523
- const workspaceId = this.getWorkspaceId(binding);
524
- const agentId = input.agentId ?? binding.agent.id;
525
- const userId = input.userId ?? "default";
526
- const projectId = input.projectId ?? workspaceId;
527
- const scopes = this.resolveRecallScopes(input);
528
- const topK = typeof input.topK === "number" && Number.isInteger(input.topK) && input.topK > 0
529
- ? input.topK
530
- : (this.runtimeMemoryPolicy?.retrieval.defaultTopK ?? 5);
531
- const kinds = input.kinds?.length ? new Set(input.kinds) : null;
532
- const items = (await this.rankRecallCandidates(binding, {
533
- query: input.query.trim(),
534
- scopes,
535
- kinds,
536
- topK,
537
- includeStale: input.includeStale === true,
538
- filters: {
539
- threadId: sessionId,
540
- agentId,
541
- workspaceId: input.workspaceId ?? workspaceId,
542
- userId,
543
- projectId,
544
- },
545
- }))
546
- .slice(0, topK)
547
- .map(({ record }) => record);
548
- return { items };
538
+ return this.knowledgeModule.recall(input, {
539
+ sessionId: input.sessionId,
540
+ agentId: input.agentId ?? binding.agent.id,
541
+ workspaceId: input.workspaceId ?? this.getWorkspaceId(binding),
542
+ userId: input.userId,
543
+ projectId: input.projectId,
544
+ });
549
545
  }
550
546
  async listMemories(input = {}) {
551
547
  const binding = this.defaultRuntimeEntryBinding;
552
548
  if (!binding) {
553
549
  throw new Error("listMemories requires a runtime entry binding.");
554
550
  }
555
- const sessionId = input.sessionId;
556
- const workspaceId = this.getWorkspaceId(binding);
557
- const scopes = Array.isArray(input.scopes) && input.scopes.length > 0
558
- ? Array.from(new Set(input.scopes))
559
- : ["thread", "agent", "workspace", "user", "project"];
560
- const kinds = input.kinds?.length ? new Set(input.kinds) : null;
561
- const statuses = input.status?.length ? new Set(input.status) : new Set(["active"]);
562
- const limit = typeof input.limit === "number" && Number.isInteger(input.limit) && input.limit > 0
563
- ? input.limit
564
- : Number.POSITIVE_INFINITY;
565
- const items = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, scopes))
566
- .filter((record) => statuses.has(record.status))
567
- .filter((record) => !kinds || kinds.has(record.kind))
568
- .filter((record) => this.matchesMemoryFilters(record, {
569
- threadId: sessionId,
551
+ return this.knowledgeModule.list(input, {
552
+ sessionId: input.sessionId,
570
553
  agentId: input.agentId,
571
- workspaceId: input.workspaceId ?? workspaceId,
554
+ workspaceId: input.workspaceId ?? this.getWorkspaceId(binding),
572
555
  userId: input.userId,
573
556
  projectId: input.projectId,
574
- }))
575
- .sort((left, right) => right.lastConfirmedAt.localeCompare(left.lastConfirmedAt))
576
- .slice(0, limit);
577
- return { items };
557
+ });
578
558
  }
579
559
  async updateMemory(input) {
580
560
  const binding = this.defaultRuntimeEntryBinding;
581
561
  if (!binding) {
582
562
  throw new Error("updateMemory requires a runtime entry binding.");
583
563
  }
584
- if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
585
- throw new Error("updateMemory requires a non-empty memoryId.");
586
- }
587
- const existing = await findMemoryRecordById(this.runtimeMemoryStore, input.memoryId.trim());
588
- if (!existing) {
589
- throw new Error(`Memory record not found: ${input.memoryId}`);
590
- }
591
- const updatedAt = new Date().toISOString();
592
- const nextContent = typeof input.content === "string"
593
- ? input.content.trim()
594
- : existing.content;
595
- if (nextContent.length === 0) {
596
- throw new Error("updateMemory requires content to remain non-empty.");
597
- }
598
- const next = {
599
- ...existing,
600
- content: nextContent,
601
- summary: typeof input.summary === "string"
602
- ? input.summary.trim() || this.summarizeMemoryContent(nextContent)
603
- : (typeof input.content === "string" ? this.summarizeMemoryContent(nextContent) : existing.summary),
604
- status: input.status ?? existing.status,
605
- confidence: typeof input.confidence === "number" ? Math.max(0, Math.min(1, input.confidence)) : existing.confidence,
606
- expiresAt: input.expiresAt === undefined ? existing.expiresAt : (input.expiresAt ?? undefined),
607
- sourceType: typeof input.sourceType === "string" && input.sourceType.trim().length > 0 ? input.sourceType.trim() : existing.sourceType,
608
- sourceRefs: Array.isArray(input.sourceRefs)
609
- ? Array.from(new Set(input.sourceRefs.map((item) => item.trim()).filter((item) => item.length > 0)))
610
- : existing.sourceRefs,
611
- tags: Array.isArray(input.tags)
612
- ? Array.from(new Set(input.tags.map((item) => item.trim()).filter((item) => item.length > 0)))
613
- : existing.tags,
614
- observedAt: typeof input.observedAt === "string" && input.observedAt.trim().length > 0 ? input.observedAt : existing.observedAt,
615
- lastConfirmedAt: typeof input.lastConfirmedAt === "string" && input.lastConfirmedAt.trim().length > 0
616
- ? input.lastConfirmedAt
617
- : updatedAt,
618
- provenance: input.provenance ? { ...existing.provenance, ...input.provenance } : existing.provenance,
619
- revision: existing.revision + 1,
620
- };
621
- await updateMemoryRecord(this.runtimeMemoryStore, next, updatedAt);
622
- await this.rebuildRuntimeMemoryVectorIndex();
623
- await this.refreshStructuredMemoryScope(binding, next);
624
- return next;
564
+ return this.knowledgeModule.update(input, {
565
+ agentId: binding.agent.id,
566
+ workspaceId: this.getWorkspaceId(binding),
567
+ });
625
568
  }
626
569
  async removeMemory(input) {
627
570
  const binding = this.defaultRuntimeEntryBinding;
628
571
  if (!binding) {
629
572
  throw new Error("removeMemory requires a runtime entry binding.");
630
573
  }
631
- if (typeof input.memoryId !== "string" || input.memoryId.trim().length === 0) {
632
- throw new Error("removeMemory requires a non-empty memoryId.");
633
- }
634
- const existing = await findMemoryRecordById(this.runtimeMemoryStore, input.memoryId.trim());
635
- if (!existing) {
636
- throw new Error(`Memory record not found: ${input.memoryId}`);
637
- }
638
- await removeMemoryRecord(this.runtimeMemoryStore, existing.scope, existing.id);
639
- await this.rebuildRuntimeMemoryVectorIndex();
640
- await this.refreshStructuredMemoryScope(binding, existing);
641
- return existing;
574
+ return this.knowledgeModule.remove(input, {
575
+ agentId: binding.agent.id,
576
+ workspaceId: this.getWorkspaceId(binding),
577
+ });
642
578
  }
643
579
  async getRequest(requestId) {
644
580
  const request = await this.persistence.getRun(requestId);
@@ -1002,7 +938,7 @@ export class AgentHarnessRuntime {
1002
938
  const workspaceId = this.getWorkspaceId(binding);
1003
939
  const template = this.runtimeMemoryPolicy?.namespaces[scope] ?? `memories/${scope}s/{${scope}Id}`;
1004
940
  return resolveMemoryNamespace(template, {
1005
- threadId: options.threadId,
941
+ sessionId: options.sessionId,
1006
942
  agentId: options.agentId ?? binding.agent.id,
1007
943
  workspaceId,
1008
944
  userId: options.userId ?? "default",
@@ -1013,339 +949,16 @@ export class AgentHarnessRuntime {
1013
949
  const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
1014
950
  return path.basename(workspaceRoot) || "default";
1015
951
  }
1016
- summarizeMemoryContent(content) {
1017
- return (content.trim().split("\n")[0] || content.trim()).slice(0, 240);
1018
- }
1019
- matchesMemoryFilters(record, filters) {
1020
- const sessionId = String(record.provenance.sessionId ?? record.provenance.threadId ?? "");
1021
- if (filters.threadId && sessionId !== filters.threadId) {
1022
- return false;
1023
- }
1024
- if (filters.agentId && record.provenance.agentId !== filters.agentId) {
1025
- return false;
1026
- }
1027
- if (filters.workspaceId && record.provenance.workspaceId !== filters.workspaceId) {
1028
- return false;
1029
- }
1030
- if (filters.userId && record.provenance.userId !== filters.userId) {
1031
- return false;
1032
- }
1033
- if (filters.projectId && record.provenance.projectId !== filters.projectId) {
1034
- return false;
1035
- }
1036
- return true;
1037
- }
1038
- resolveRecallScopes(input) {
1039
- if (Array.isArray(input.scopes) && input.scopes.length > 0) {
1040
- return Array.from(new Set(input.scopes));
1041
- }
1042
- const scopes = new Set(["thread", "agent", "workspace"]);
1043
- if (input.userId) {
1044
- scopes.add("user");
1045
- }
1046
- if (input.projectId) {
1047
- scopes.add("project");
1048
- }
1049
- return Array.from(scopes);
1050
- }
1051
- matchesRecallScope(record, filters) {
1052
- if (record.scope === "thread") {
1053
- return typeof filters.threadId === "string"
1054
- && String(record.provenance.sessionId ?? record.provenance.threadId ?? "") === filters.threadId;
1055
- }
1056
- if (record.scope === "agent") {
1057
- return String(record.provenance.agentId ?? "") === filters.agentId;
1058
- }
1059
- if (record.scope === "workspace") {
1060
- return String(record.provenance.workspaceId ?? filters.workspaceId) === filters.workspaceId;
1061
- }
1062
- if (record.scope === "user") {
1063
- return String(record.provenance.userId ?? "default") === filters.userId;
1064
- }
1065
- return String(record.provenance.projectId ?? filters.workspaceId) === filters.projectId;
1066
- }
1067
- getMemoryScopeBoost(scope) {
1068
- if (scope === "thread") {
1069
- return 4;
1070
- }
1071
- if (scope === "agent") {
1072
- return 2;
1073
- }
1074
- if (scope === "workspace") {
1075
- return 1;
1076
- }
1077
- return 0;
1078
- }
1079
- memoryFreshnessBoost(value) {
1080
- return Math.max(0, 1 - ((Date.now() - Date.parse(value)) / (1000 * 60 * 60 * 24 * 365)));
1081
- }
1082
- scoreStructuredRecord(query, record) {
1083
- return (scoreMemoryText(query, `${record.summary}\n${record.content}`, this.getMemoryScopeBoost(record.scope)) +
1084
- this.memoryFreshnessBoost(record.lastConfirmedAt) +
1085
- record.confidence);
1086
- }
1087
- normalizeMemoryDedupKey(record) {
1088
- return `${record.scope}::${record.summary.toLowerCase().replace(/\s+/g, " ").trim()}::${record.content.toLowerCase().replace(/\s+/g, " ").trim()}`;
1089
- }
1090
- inferMem0MemoryKind(hit) {
1091
- const candidates = [
1092
- ...hit.categories,
1093
- typeof hit.metadata.kind === "string" ? hit.metadata.kind : undefined,
1094
- typeof hit.metadata.memoryType === "string" ? hit.metadata.memoryType : undefined,
1095
- ].filter((value) => typeof value === "string" && value.trim().length > 0);
1096
- for (const candidate of candidates) {
1097
- const kind = normalizeLangMemMemoryKind(candidate);
1098
- if (kind === "semantic" || kind === "episodic" || kind === "procedural") {
1099
- return kind;
1100
- }
1101
- }
1102
- return "semantic";
1103
- }
1104
- inferMem0MemoryScope(hit, filters) {
1105
- const metadataScope = typeof hit.metadata.scope === "string" ? hit.metadata.scope : undefined;
1106
- if (metadataScope === "thread" || metadataScope === "agent" || metadataScope === "workspace" || metadataScope === "user" || metadataScope === "project") {
1107
- return metadataScope;
1108
- }
1109
- if (typeof hit.metadata.threadId === "string" && filters.threadId && hit.metadata.threadId === filters.threadId) {
1110
- return "thread";
1111
- }
1112
- if (hit.agentId === filters.agentId || hit.metadata.agentId === filters.agentId) {
1113
- return "agent";
1114
- }
1115
- return "workspace";
1116
- }
1117
- createMem0MemoryRecord(hit, filters) {
1118
- const scope = this.inferMem0MemoryScope(hit, filters);
1119
- const kind = this.inferMem0MemoryKind(hit);
1120
- const summary = hit.memory.split("\n")[0]?.trim() || hit.memory;
1121
- return {
1122
- id: `mem0:${hit.id}`,
1123
- canonicalKey: `mem0:${hit.id}`,
1124
- kind,
1125
- scope,
1126
- content: hit.memory,
1127
- summary: summary.slice(0, 240),
1128
- status: "active",
1129
- confidence: Math.max(0, Math.min(1, hit.score || 0.5)),
1130
- createdAt: hit.createdAt,
1131
- observedAt: hit.createdAt,
1132
- lastConfirmedAt: hit.updatedAt,
1133
- sourceType: "mem0-search",
1134
- sourceRefs: [`mem0:${hit.id}`],
1135
- tags: hit.categories,
1136
- provenance: {
1137
- source: "mem0",
1138
- sessionId: typeof hit.metadata.sessionId === "string"
1139
- ? hit.metadata.sessionId
1140
- : typeof hit.metadata.threadId === "string"
1141
- ? hit.metadata.threadId
1142
- : filters.threadId,
1143
- requestId: (typeof hit.metadata.requestId === "string"
1144
- ? hit.metadata.requestId
1145
- : typeof hit.metadata.runId === "string"
1146
- ? hit.metadata.runId
1147
- : undefined),
1148
- agentId: hit.agentId ?? (typeof hit.metadata.agentId === "string" ? hit.metadata.agentId : filters.agentId),
1149
- workspaceId: filters.workspaceId,
1150
- userId: filters.userId,
1151
- projectId: filters.projectId,
1152
- ...hit.metadata,
1153
- },
1154
- revision: 1,
1155
- supersedes: [],
1156
- conflictsWith: [],
1157
- };
1158
- }
1159
- async rankRecallCandidates(binding, input) {
1160
- const structuredRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, input.scopes);
1161
- const ranked = structuredRecords
1162
- .filter((record) => this.matchesRecallScope(record, input.filters))
1163
- .filter((record) => (input.includeStale ? record.status === "active" || record.status === "stale" : record.status === "active"))
1164
- .filter((record) => (input.kinds ? input.kinds.has(record.kind) : true))
1165
- .map((record) => ({
1166
- record,
1167
- score: this.scoreStructuredRecord(input.query, record),
1168
- }));
1169
- const deduped = new Map();
1170
- for (const item of ranked) {
1171
- deduped.set(this.normalizeMemoryDedupKey(item.record), item);
1172
- }
1173
- const vectorStore = await this.resolveRuntimeMemoryVectorStore();
1174
- if (vectorStore) {
1175
- try {
1176
- const vectorHits = await vectorStore.similaritySearch(input.query, Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK));
1177
- for (const hit of vectorHits) {
1178
- const metadata = typeof hit.metadata === "object" && hit.metadata && !Array.isArray(hit.metadata)
1179
- ? hit.metadata
1180
- : {};
1181
- const recordId = typeof metadata.recordId === "string" ? metadata.recordId : undefined;
1182
- const scope = metadata.scope;
1183
- if (!recordId || (scope !== "thread" && scope !== "agent" && scope !== "workspace" && scope !== "user" && scope !== "project")) {
1184
- continue;
1185
- }
1186
- const canonical = await getMemoryRecord(this.runtimeMemoryStore, scope, recordId);
1187
- if (!canonical) {
1188
- continue;
1189
- }
1190
- if (!this.matchesRecallScope(canonical, input.filters)) {
1191
- continue;
1192
- }
1193
- if (input.includeStale ? canonical.status !== "active" && canonical.status !== "stale" : canonical.status !== "active") {
1194
- continue;
1195
- }
1196
- if (input.kinds && !input.kinds.has(canonical.kind)) {
1197
- continue;
1198
- }
1199
- const key = this.normalizeMemoryDedupKey(canonical);
1200
- const score = this.scoreStructuredRecord(input.query, canonical) + (typeof hit.score === "number" ? hit.score : 0);
1201
- const existing = deduped.get(key);
1202
- if (!existing || score > existing.score) {
1203
- deduped.set(key, { record: canonical, score });
1204
- }
1205
- }
1206
- }
1207
- catch {
1208
- // Fail open to lexical/runtime ranking.
1209
- }
1210
- }
1211
- const supportsMem0Scope = input.scopes.some((scope) => scope === "thread" || scope === "agent" || scope === "workspace");
1212
- if (!this.mem0SemanticRecall || !supportsMem0Scope) {
1213
- return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
1214
- }
1215
- try {
1216
- const hits = await this.mem0SemanticRecall.search({
1217
- query: input.query,
1218
- topK: Math.max(input.topK, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? input.topK),
1219
- agentId: input.filters.agentId,
1220
- threadId: input.filters.threadId,
1221
- });
1222
- for (const hit of hits) {
1223
- const record = this.createMem0MemoryRecord(hit, input.filters);
1224
- if (!input.scopes.includes(record.scope)) {
1225
- continue;
1226
- }
1227
- if (input.kinds && !input.kinds.has(record.kind)) {
1228
- continue;
1229
- }
1230
- const key = this.normalizeMemoryDedupKey(record);
1231
- if (deduped.has(key)) {
1232
- continue;
1233
- }
1234
- deduped.set(key, {
1235
- record,
1236
- score: this.scoreStructuredRecord(input.query, record) + Math.max(0, Math.min(1, hit.score)) * 4,
1237
- });
1238
- }
1239
- return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
1240
- }
1241
- catch {
1242
- return Array.from(deduped.values()).sort((left, right) => right.score - left.score);
1243
- }
1244
- }
1245
- async rebuildRuntimeMemoryVectorIndex() {
1246
- const vectorStore = await this.resolveRuntimeMemoryVectorStore();
1247
- if (!vectorStore) {
1248
- return;
1249
- }
1250
- const records = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]))
1251
- .filter((record) => record.status === "active");
1252
- try {
1253
- await vectorStore.delete({ deleteAll: true });
1254
- if (records.length === 0) {
1255
- return;
1256
- }
1257
- await vectorStore.addDocuments(records.map((record) => ({
1258
- pageContent: `${record.summary}\n${record.content}`,
1259
- metadata: {
1260
- recordId: record.id,
1261
- scope: record.scope,
1262
- kind: record.kind,
1263
- status: record.status,
1264
- sessionId: record.provenance.sessionId ?? record.provenance.threadId,
1265
- agentId: record.provenance.agentId,
1266
- workspaceId: record.provenance.workspaceId,
1267
- userId: record.provenance.userId,
1268
- projectId: record.provenance.projectId,
1269
- confidence: record.confidence,
1270
- lastConfirmedAt: record.lastConfirmedAt,
1271
- },
1272
- })));
1273
- }
1274
- catch {
1275
- // Fail open: vector indexing must not break durable memory writes.
1276
- }
1277
- }
1278
- async refreshStructuredMemoryScope(binding, record) {
1279
- const provenance = record.provenance;
1280
- const maxEntries = record.scope === "thread" ? 12 : 20;
1281
- const titleByScope = {
1282
- thread: "Thread Structured Memory",
1283
- agent: "Agent Structured Memory",
1284
- workspace: "Workspace Structured Memory",
1285
- user: "User Structured Memory",
1286
- project: "Project Structured Memory",
1287
- };
1288
- await consolidateStructuredMemoryScope({
1289
- store: this.runtimeMemoryStore,
1290
- namespace: this.resolveMemoryNamespace(record.scope, binding, {
1291
- threadId: provenance.sessionId,
1292
- agentId: provenance.agentId,
1293
- userId: provenance.userId,
1294
- projectId: provenance.projectId,
1295
- }),
1296
- title: titleByScope[record.scope],
1297
- scope: record.scope,
1298
- maxEntries,
1299
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1300
- });
1301
- }
1302
952
  async buildRuntimeMemoryContext(binding, threadId, input) {
1303
953
  const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
1304
- const workspaceId = this.getWorkspaceId(binding);
1305
- const ranked = (await this.rankRecallCandidates(binding, {
954
+ return this.knowledgeModule.buildPromptContext({
1306
955
  query,
1307
- scopes: ["thread", "agent", "workspace", "user", "project"],
1308
- kinds: null,
1309
956
  topK: this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? 8,
1310
- includeStale: false,
1311
- filters: {
1312
- threadId,
1313
- agentId: binding.agent.id,
1314
- workspaceId,
1315
- userId: "default",
1316
- projectId: workspaceId,
1317
- },
1318
- }))
1319
- .map((record) => {
1320
- const scopeBoost = record.record.scope === "thread"
1321
- ? 4
1322
- : record.record.scope === "agent"
1323
- ? 3
1324
- : record.record.scope === "user"
1325
- ? 2
1326
- : 1;
1327
- return {
1328
- content: [
1329
- `# Durable ${record.record.scope[0].toUpperCase()}${record.record.scope.slice(1)} Memory`,
1330
- "",
1331
- `- summary: ${record.record.summary}`,
1332
- `- kind: ${record.record.kind}`,
1333
- `- scope: ${record.record.scope}`,
1334
- `- confidence: ${record.record.confidence.toFixed(2)}`,
1335
- "",
1336
- record.record.content,
1337
- ].join("\n"),
1338
- score: scoreMemoryText(query, `${record.record.summary}\n${record.record.content}`, scopeBoost) +
1339
- this.memoryFreshnessBoost(record.record.lastConfirmedAt) +
1340
- record.record.confidence,
1341
- };
1342
- })
1343
- .sort((left, right) => right.score - left.score)
1344
- .slice(0, this.runtimeMemoryPolicy?.retrieval.maxPromptMemories ?? 8);
1345
- if (ranked.length === 0) {
1346
- return undefined;
1347
- }
1348
- return ranked.map((entry) => entry.content).join("\n\n");
957
+ }, {
958
+ sessionId: threadId,
959
+ agentId: binding.agent.id,
960
+ workspaceId: this.getWorkspaceId(binding),
961
+ });
1349
962
  }
1350
963
  async persistRuntimeMemoryCandidates(binding, threadId, runId, value) {
1351
964
  if (!Array.isArray(value) || value.length === 0) {
@@ -1360,144 +973,13 @@ export class AgentHarnessRuntime {
1360
973
  if (candidates.length === 0) {
1361
974
  return;
1362
975
  }
1363
- await this.persistStructuredMemoryCandidates(binding, {
1364
- candidates,
1365
- threadId,
1366
- runId,
976
+ await this.knowledgeModule.memorizeCandidates(candidates, {
977
+ sessionId: threadId,
978
+ requestId: runId,
1367
979
  agentId: binding.agent.id,
980
+ workspaceId: this.getWorkspaceId(binding),
1368
981
  recordedAt: new Date().toISOString(),
1369
- storeCandidateLog: true,
1370
- });
1371
- }
1372
- async persistStructuredMemoryCandidates(binding, input) {
1373
- const workspaceId = this.getWorkspaceId(binding);
1374
- const userId = input.userId ?? "default";
1375
- const projectId = input.projectId ?? workspaceId;
1376
- const existingRecords = await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace", "user", "project"]);
1377
- const transformedCandidates = this.runtimeMemoryManager
1378
- ? await this.runtimeMemoryManager.transform({
1379
- candidates: input.candidates,
1380
- binding,
1381
- threadId: input.threadId,
1382
- runId: input.runId,
1383
- recordedAt: input.recordedAt,
1384
- existingRecords,
1385
- })
1386
- : input.candidates;
1387
- const threadCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "thread");
1388
- const workspaceCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
1389
- const agentCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
1390
- const userCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "user");
1391
- const projectCandidates = transformedCandidates.filter((candidate) => (candidate.scope ?? "thread") === "project");
1392
- if (input.storeCandidateLog) {
1393
- await this.runtimeMemoryStore.put(["memories", "candidates", input.threadId], `${input.runId}.json`, {
1394
- runId: input.runId,
1395
- threadId: input.threadId,
1396
- storedAt: input.recordedAt,
1397
- candidates: transformedCandidates,
1398
- });
1399
- }
1400
- const persisted = await persistStructuredMemoryRecords({
1401
- store: this.runtimeMemoryStore,
1402
- candidates: transformedCandidates,
1403
- threadId: input.threadId,
1404
- runId: input.runId,
1405
- agentId: input.agentId,
1406
- workspaceId,
1407
- userId,
1408
- projectId,
1409
- recordedAt: input.recordedAt,
1410
- });
1411
- await this.rebuildRuntimeMemoryVectorIndex();
1412
- const writes = [];
1413
- if (threadCandidates.length > 0) {
1414
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
1415
- writes.push(consolidateStructuredMemoryScope({
1416
- store: this.runtimeMemoryStore,
1417
- namespace: this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }),
1418
- title: "Thread Structured Memory",
1419
- scope: "thread",
1420
- maxEntries: 12,
1421
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1422
- }));
1423
- }
1424
- if (workspaceCandidates.length > 0) {
1425
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("workspace", binding), "tool-memory.md", workspaceCandidates, 20, "Workspace Tool Memory"));
1426
- writes.push(consolidateStructuredMemoryScope({
1427
- store: this.runtimeMemoryStore,
1428
- namespace: this.resolveMemoryNamespace("workspace", binding),
1429
- title: "Workspace Structured Memory",
1430
- scope: "workspace",
1431
- maxEntries: 20,
1432
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1433
- }));
1434
- }
1435
- if (agentCandidates.length > 0) {
1436
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("agent", binding), "tool-memory.md", agentCandidates, 20, "Agent Tool Memory"));
1437
- writes.push(consolidateStructuredMemoryScope({
1438
- store: this.runtimeMemoryStore,
1439
- namespace: this.resolveMemoryNamespace("agent", binding),
1440
- title: "Agent Structured Memory",
1441
- scope: "agent",
1442
- maxEntries: 20,
1443
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1444
- }));
1445
- }
1446
- if (userCandidates.length > 0) {
1447
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("user", binding, { userId }), "tool-memory.md", userCandidates, 20, "User Tool Memory"));
1448
- writes.push(consolidateStructuredMemoryScope({
1449
- store: this.runtimeMemoryStore,
1450
- namespace: this.resolveMemoryNamespace("user", binding, { userId }),
1451
- title: "User Structured Memory",
1452
- scope: "user",
1453
- maxEntries: 20,
1454
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1455
- }));
1456
- }
1457
- if (projectCandidates.length > 0) {
1458
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("project", binding, { projectId }), "tool-memory.md", projectCandidates, 20, "Project Tool Memory"));
1459
- writes.push(consolidateStructuredMemoryScope({
1460
- store: this.runtimeMemoryStore,
1461
- namespace: this.resolveMemoryNamespace("project", binding, { projectId }),
1462
- title: "Project Structured Memory",
1463
- scope: "project",
1464
- maxEntries: 20,
1465
- config: this.runtimeMemoryMaintenanceConfig ?? undefined,
1466
- }));
1467
- }
1468
- await Promise.all(writes);
1469
- return persisted;
1470
- }
1471
- async appendMemoryDigest(namespace, key, candidates, maxEntries, title) {
1472
- const existing = await this.runtimeMemoryStore.get(namespace, key);
1473
- const existingItems = Array.isArray(existing?.value?.items)
1474
- ? ((existing?.value).items ?? [])
1475
- : [];
1476
- const merged = [...existingItems];
1477
- for (const candidate of candidates) {
1478
- if (merged.some((entry) => entry.content === candidate.content && (entry.scope ?? "thread") === (candidate.scope ?? "thread"))) {
1479
- continue;
1480
- }
1481
- merged.push(candidate);
1482
- }
1483
- const recent = merged.slice(-maxEntries);
1484
- const taxonomyGroups = new Map();
1485
- for (const candidate of recent) {
1486
- const kind = normalizeLangMemMemoryKind(candidate.kind);
1487
- const existing = taxonomyGroups.get(kind) ?? [];
1488
- existing.push({ ...candidate, kind });
1489
- taxonomyGroups.set(kind, existing);
1490
- }
1491
- await this.runtimeMemoryStore.put(namespace, key, {
1492
- content: `${renderMemoryCandidatesMarkdown(title, recent)}\n`,
1493
- items: recent,
1494
- });
1495
- await Promise.all(Array.from(taxonomyGroups.entries()).map(async ([kind, items]) => {
1496
- await this.runtimeMemoryStore.put(namespace, `${kind}.md`, {
1497
- content: `${renderMemoryCandidatesMarkdown(`${title} (${kind})`, items)}\n`,
1498
- items,
1499
- });
1500
- }));
982
+ }, { storeCandidateLog: true });
1501
983
  }
1502
984
  async resolvePersistedRunPriority(threadId, runId) {
1503
985
  const persisted = await this.persistence.getRunRequest(threadId, runId);