@botbotgo/agent-harness 0.0.153 → 0.0.154

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 CHANGED
@@ -118,7 +118,7 @@ That means:
118
118
 
119
119
  The runtime provides:
120
120
 
121
- - `createAgentHarness(workspaceRoot)`, `run(...)`, `resolveApproval(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
121
+ - `createAgentHarness(workspaceRoot)`, `run(...)`, `memorize(...)`, `recall(...)`, `resolveApproval(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
122
122
  - YAML-defined workspace assembly for routing, models, tools, stores, backends, MCP, recovery, and maintenance
123
123
  - backend-adapted execution with current LangChain v1 and DeepAgents adapters
124
124
  - local `resources/tools/` `tool({...})` modules and `resources/skills/` discovery
@@ -389,6 +389,36 @@ const result = await run(
389
389
 
390
390
  Use `normalizeUserChatInput(...)` when a product already has chat-style user messages and wants to project one user turn onto the stable `run(..., { input, invocation })` surface without introducing a separate harness-owned chat API.
391
391
 
392
+ ### Store And Recall Durable Runtime Memory
393
+
394
+ ```ts
395
+ import { memorize, recall } from "@botbotgo/agent-harness";
396
+
397
+ await memorize(runtime, {
398
+ threadId: "thread-123",
399
+ records: [
400
+ {
401
+ content: "The release checklist requires a smoke test before publish.",
402
+ summary: "Run a smoke test before publish",
403
+ scope: "workspace",
404
+ kind: "procedural",
405
+ sourceRef: "docs/release-checklist.md",
406
+ },
407
+ ],
408
+ });
409
+
410
+ const recalled = await recall(runtime, {
411
+ query: "What does the release checklist require?",
412
+ scopes: ["workspace"],
413
+ });
414
+ ```
415
+
416
+ Use `memorize(...)` and `recall(...)` when an application needs a stable public runtime memory surface without importing internal `runtime/harness/system/*` modules.
417
+
418
+ - `memorize(...)` returns stable `MemoryRecord` and `MemoryDecision` results while leaving merge, review, archive, and storage layout runtime-managed
419
+ - `recall(...)` returns ranked `MemoryRecord` items filtered by runtime memory scope and kind
420
+ - app-specific knowledge taxonomy, review UI, and admin surfaces still belong in the application layer
421
+
392
422
  ### Let The Runtime Route
393
423
 
394
424
  ```ts
package/README.zh.md CHANGED
@@ -118,7 +118,7 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
118
118
 
119
119
  运行时提供:
120
120
 
121
- - `createAgentHarness(workspaceRoot)`、`run(...)`、`resolveApproval(...)`、`subscribe(...)`、各类查询方法,以及 `stop(...)`
121
+ - `createAgentHarness(workspaceRoot)`、`run(...)`、`memorize(...)`、`recall(...)`、`resolveApproval(...)`、`subscribe(...)`、各类查询方法,以及 `stop(...)`
122
122
  - 以 YAML 描述的工作区装配:路由、模型、工具、存储、后端、MCP、恢复与维护等
123
123
  - 通过适配器对接当前的 LangChain v1 与 DeepAgents 执行
124
124
  - 本地 `resources/tools/` 中 `tool({...})` 工具模块与 `resources/skills/` 的发现
@@ -360,6 +360,36 @@ const result = await run(runtime, {
360
360
  - `invocation.inputs`:结构化运行时输入
361
361
  - `invocation.attachments`:当前后端可解释的类附件负载
362
362
 
363
+ ### 写入与召回 durable runtime memory
364
+
365
+ ```ts
366
+ import { memorize, recall } from "@botbotgo/agent-harness";
367
+
368
+ await memorize(runtime, {
369
+ threadId: "thread-123",
370
+ records: [
371
+ {
372
+ content: "The release checklist requires a smoke test before publish.",
373
+ summary: "Run a smoke test before publish",
374
+ scope: "workspace",
375
+ kind: "procedural",
376
+ sourceRef: "docs/release-checklist.md",
377
+ },
378
+ ],
379
+ });
380
+
381
+ const recalled = await recall(runtime, {
382
+ query: "What does the release checklist require?",
383
+ scopes: ["workspace"],
384
+ });
385
+ ```
386
+
387
+ 当应用需要稳定的公开 runtime memory 接口,而不想依赖内部 `runtime/harness/system/*` 模块时,使用 `memorize(...)` 与 `recall(...)`。
388
+
389
+ - `memorize(...)` 返回稳定的 `MemoryRecord` 与 `MemoryDecision` 结果,而 merge、review、archive 与存储布局仍由 runtime 内部托管
390
+ - `recall(...)` 返回按相关性排序、并按 scope / kind 过滤后的 `MemoryRecord`
391
+ - 业务知识分类、review UI 与管理后台仍应留在应用层
392
+
363
393
  ### 由运行时路由
364
394
 
365
395
  ```ts
package/dist/api.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { CancelOptions, InvocationEnvelope, MessageContent, RequestRecord, RequestSummary, ResumeOptions, RunDecisionOptions, RunResult, RunStartOptions, RuntimeHealthSnapshot, RuntimeAdapterOptions, SessionRecord, SessionSummary, WorkspaceLoadOptions } from "./contracts/types.js";
1
+ import type { CancelOptions, InvocationEnvelope, MemorizeInput, MemorizeResult, MessageContent, RecallInput, RecallResult, RequestRecord, RequestSummary, ResumeOptions, RunDecisionOptions, RunResult, RunStartOptions, RuntimeHealthSnapshot, RuntimeAdapterOptions, SessionRecord, SessionSummary, WorkspaceLoadOptions } from "./contracts/types.js";
2
2
  import { AgentHarnessRuntime } from "./runtime/harness.js";
3
3
  import type { InventoryAgentRecord, InventorySkillRecord } from "./runtime/harness/system/inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./runtime/harness/system/skill-requirements.js";
5
5
  import type { ToolMcpServerOptions } from "./mcp.js";
6
6
  export { AgentHarnessRuntime } from "./runtime/harness.js";
7
7
  export { createUpstreamTimelineReducer } from "./upstream-events.js";
8
+ export type { MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, RecallInput, RecallResult, } from "./contracts/types.js";
8
9
  type PublicApprovalRecord = {
9
10
  approvalId: string;
10
11
  pendingActionId: string;
@@ -54,6 +55,8 @@ export declare function createAgentHarness(): Promise<AgentHarnessRuntime>;
54
55
  export declare function createAgentHarness(workspaceRoot: string, options?: CreateAgentHarnessOptions): Promise<AgentHarnessRuntime>;
55
56
  export declare function normalizeUserChatInput(input: UserChatInput, options?: NormalizeUserChatInputOptions): Pick<RunStartOptions, "input" | "invocation">;
56
57
  export declare function run(runtime: AgentHarnessRuntime, options: PublicRunOptions): Promise<PublicRunResult>;
58
+ export declare function memorize(runtime: AgentHarnessRuntime, input: MemorizeInput): Promise<MemorizeResult>;
59
+ export declare function recall(runtime: AgentHarnessRuntime, input: RecallInput): Promise<RecallResult>;
57
60
  export declare function subscribe(runtime: AgentHarnessRuntime, listener: Parameters<AgentHarnessRuntime["subscribe"]>[0]): () => void;
58
61
  export declare function listSessions(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listThreads"]>[0]): Promise<SessionSummary[]>;
59
62
  export declare function listRequests(runtime: AgentHarnessRuntime, filter?: {
package/dist/api.js CHANGED
@@ -128,6 +128,12 @@ export function normalizeUserChatInput(input, options = {}) {
128
128
  export async function run(runtime, options) {
129
129
  return toPublicRunResult(await runtime.run(toInternalRunOptions(options)));
130
130
  }
131
+ export async function memorize(runtime, input) {
132
+ return runtime.memorize(input);
133
+ }
134
+ export async function recall(runtime, input) {
135
+ return runtime.recall(input);
136
+ }
131
137
  export function subscribe(runtime, listener) {
132
138
  return runtime.subscribe(listener);
133
139
  }
@@ -118,13 +118,14 @@ export type MemoryCandidate = {
118
118
  noStore?: boolean;
119
119
  provenance?: Record<string, unknown>;
120
120
  };
121
+ export type MemoryKind = "semantic" | "episodic" | "procedural";
121
122
  export type MemoryScope = "thread" | "agent" | "workspace" | "user" | "project";
122
123
  export type MemoryRecordStatus = "active" | "stale" | "conflicted" | "archived" | "pending_review";
123
124
  export type MemoryDecisionAction = "reject" | "store" | "merge" | "refresh" | "supersede" | "archive" | "review";
124
125
  export type MemoryRecord = {
125
126
  id: string;
126
127
  canonicalKey: string;
127
- kind: "semantic" | "episodic" | "procedural";
128
+ kind: MemoryKind;
128
129
  scope: MemoryScope;
129
130
  content: string;
130
131
  summary: string;
@@ -152,6 +153,48 @@ export type MemoryDecision = {
152
153
  maintenance?: "none" | "dedupe" | "merge" | "review";
153
154
  reviewRequired?: boolean;
154
155
  };
156
+ export type MemorizeInputRecord = {
157
+ content: string;
158
+ summary?: string;
159
+ kind?: MemoryKind;
160
+ scope?: MemoryScope;
161
+ confidence?: number;
162
+ tags?: string[];
163
+ sourceType?: string;
164
+ sourceRef?: string;
165
+ observedAt?: string;
166
+ sensitivity?: string;
167
+ provenance?: Record<string, unknown>;
168
+ noStore?: boolean;
169
+ };
170
+ export type MemorizeInput = {
171
+ records: MemorizeInputRecord[];
172
+ threadId?: string;
173
+ runId?: string;
174
+ agentId?: string;
175
+ userId?: string;
176
+ projectId?: string;
177
+ recordedAt?: string;
178
+ };
179
+ export type MemorizeResult = {
180
+ records: MemoryRecord[];
181
+ decisions: MemoryDecision[];
182
+ };
183
+ export type RecallInput = {
184
+ query: string;
185
+ scopes?: MemoryScope[];
186
+ kinds?: MemoryKind[];
187
+ topK?: number;
188
+ includeStale?: boolean;
189
+ threadId?: string;
190
+ agentId?: string;
191
+ workspaceId?: string;
192
+ userId?: string;
193
+ projectId?: string;
194
+ };
195
+ export type RecallResult = {
196
+ items: MemoryRecord[];
197
+ };
155
198
  /**
156
199
  * Operator-facing projection of tool execution policy already compiled into a binding.
157
200
  * This summarizes existing timeout, retry, validation, and retry-safety hints without
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, normalizeUserChatInput, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
- export type { NormalizeUserChatInputOptions, UserChatInput, UserChatMessage } from "./api.js";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
+ export type { MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, RecallInput, RecallResult, UserChatInput, UserChatMessage, } from "./api.js";
3
3
  export type { ToolMcpServerOptions } from "./mcp.js";
4
4
  export { tool } from "./tools.js";
5
5
  export type { UpstreamTimelineProjection, UpstreamTimelineReducer } from "./upstream-events.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, normalizeUserChatInput, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, getSession, listAgentSkills, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.152";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.153";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.152";
1
+ export const AGENT_HARNESS_VERSION = "0.0.153";
@@ -6,6 +6,9 @@ type PersistMemoryRecordsOptions = {
6
6
  threadId: string;
7
7
  runId: string;
8
8
  agentId: string;
9
+ workspaceId: string;
10
+ userId: string;
11
+ projectId: string;
9
12
  recordedAt: string;
10
13
  };
11
14
  export declare function renderMemoryRecordsMarkdown(title: string, records: MemoryRecord[]): string;
@@ -118,6 +118,9 @@ function createMemoryRecord(candidate, options) {
118
118
  threadId: options.threadId,
119
119
  runId: options.runId,
120
120
  agentId: options.agentId,
121
+ workspaceId: options.workspaceId,
122
+ userId: options.userId,
123
+ projectId: options.projectId,
121
124
  ...(candidate.provenance ?? {}),
122
125
  },
123
126
  revision: 1,
@@ -1,4 +1,4 @@
1
- import type { ApprovalRecord, CancelOptions, HarnessEvent, HarnessStreamItem, RuntimeHealthSnapshot, MessageContent, RunRecord, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, RunSummary, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
1
+ import type { ApprovalRecord, CancelOptions, HarnessEvent, HarnessStreamItem, RuntimeHealthSnapshot, MessageContent, RunRecord, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, RunSummary, MemorizeInput, MemorizeResult, RecallInput, RecallResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
2
2
  import { type ToolMcpServerOptions } from "../mcp.js";
3
3
  import { type InventoryAgentRecord, type InventorySkillRecord } from "./harness/system/inventory.js";
4
4
  import type { RequirementAssessmentOptions } from "./harness/system/skill-requirements.js";
@@ -62,6 +62,8 @@ export declare class AgentHarnessRuntime {
62
62
  threadId?: string;
63
63
  state?: RunSummary["state"];
64
64
  }): Promise<RunSummary[]>;
65
+ memorize(input: MemorizeInput): Promise<MemorizeResult>;
66
+ recall(input: RecallInput): Promise<RecallResult>;
65
67
  getRun(runId: string): Promise<RunRecord | null>;
66
68
  private getSession;
67
69
  getThread(threadId: string): Promise<ThreadRecord | null>;
@@ -93,8 +95,13 @@ export declare class AgentHarnessRuntime {
93
95
  private finalizeCancelledRun;
94
96
  private invokeWithHistory;
95
97
  private resolveMemoryNamespace;
98
+ private getWorkspaceId;
99
+ private resolveRecallScopes;
100
+ private matchesRecallScope;
101
+ private getMemoryScopeBoost;
96
102
  private buildRuntimeMemoryContext;
97
103
  private persistRuntimeMemoryCandidates;
104
+ private persistStructuredMemoryCandidates;
98
105
  private appendMemoryDigest;
99
106
  private resolvePersistedRunPriority;
100
107
  private enqueuePendingRunSlot;
@@ -243,6 +243,78 @@ export class AgentHarnessRuntime {
243
243
  async listRuns(filter) {
244
244
  return this.persistence.listRuns(filter);
245
245
  }
246
+ async memorize(input) {
247
+ const binding = this.defaultRuntimeEntryBinding;
248
+ if (!binding) {
249
+ throw new Error("memorize requires a runtime entry binding.");
250
+ }
251
+ if (!Array.isArray(input.records)) {
252
+ throw new Error("memorize requires input.records to be an array.");
253
+ }
254
+ const candidates = input.records
255
+ .filter((record) => typeof record === "object" && record !== null)
256
+ .filter((record) => record.noStore !== true);
257
+ if (candidates.length === 0) {
258
+ return { records: [], decisions: [] };
259
+ }
260
+ if (candidates.some((record) => typeof record.content !== "string" || record.content.trim().length === 0)) {
261
+ throw new Error("memorize requires every record to include non-empty content.");
262
+ }
263
+ if (candidates.some((record) => (record.scope ?? "thread") === "thread") && !input.threadId) {
264
+ throw new Error("memorize requires threadId when storing thread-scoped memory.");
265
+ }
266
+ const recordedAt = input.recordedAt ?? new Date().toISOString();
267
+ const runId = input.runId ?? createPersistentId(new Date(recordedAt));
268
+ const threadId = input.threadId ?? `memory-api-${runId}`;
269
+ return this.persistStructuredMemoryCandidates(binding, {
270
+ candidates: candidates.map((record) => ({ ...record, content: record.content.trim() })),
271
+ threadId,
272
+ runId,
273
+ agentId: input.agentId ?? binding.agent.id,
274
+ userId: input.userId,
275
+ projectId: input.projectId,
276
+ recordedAt,
277
+ storeCandidateLog: true,
278
+ });
279
+ }
280
+ async recall(input) {
281
+ const binding = this.defaultRuntimeEntryBinding;
282
+ if (!binding) {
283
+ throw new Error("recall requires a runtime entry binding.");
284
+ }
285
+ if (typeof input.query !== "string" || input.query.trim().length === 0) {
286
+ throw new Error("recall requires a non-empty query.");
287
+ }
288
+ const workspaceId = this.getWorkspaceId(binding);
289
+ const agentId = input.agentId ?? binding.agent.id;
290
+ const userId = input.userId ?? "default";
291
+ const projectId = input.projectId ?? workspaceId;
292
+ const scopes = this.resolveRecallScopes(input);
293
+ const topK = typeof input.topK === "number" && Number.isInteger(input.topK) && input.topK > 0
294
+ ? input.topK
295
+ : (this.runtimeMemoryPolicy?.retrieval.defaultTopK ?? 5);
296
+ const kinds = input.kinds?.length ? new Set(input.kinds) : null;
297
+ const items = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, scopes))
298
+ .filter((record) => this.matchesRecallScope(record, {
299
+ threadId: input.threadId,
300
+ agentId,
301
+ 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,
312
+ }))
313
+ .sort((left, right) => right.score - left.score)
314
+ .slice(0, topK)
315
+ .map(({ record }) => record);
316
+ return { items };
317
+ }
246
318
  async getRun(runId) {
247
319
  return this.persistence.getRun(runId);
248
320
  }
@@ -392,8 +464,7 @@ export class AgentHarnessRuntime {
392
464
  }
393
465
  }
394
466
  resolveMemoryNamespace(scope, binding, options = {}) {
395
- const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
396
- const workspaceId = path.basename(workspaceRoot) || "default";
467
+ const workspaceId = this.getWorkspaceId(binding);
397
468
  const template = this.runtimeMemoryPolicy?.namespaces[scope] ?? `memories/${scope}s/{${scope}Id}`;
398
469
  return resolveMemoryNamespace(template, {
399
470
  threadId: options.threadId,
@@ -403,6 +474,50 @@ export class AgentHarnessRuntime {
403
474
  projectId: options.projectId ?? workspaceId,
404
475
  });
405
476
  }
477
+ getWorkspaceId(binding) {
478
+ const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
479
+ return path.basename(workspaceRoot) || "default";
480
+ }
481
+ resolveRecallScopes(input) {
482
+ if (Array.isArray(input.scopes) && input.scopes.length > 0) {
483
+ return Array.from(new Set(input.scopes));
484
+ }
485
+ const scopes = new Set(["thread", "agent", "workspace"]);
486
+ if (input.userId) {
487
+ scopes.add("user");
488
+ }
489
+ if (input.projectId) {
490
+ scopes.add("project");
491
+ }
492
+ return Array.from(scopes);
493
+ }
494
+ matchesRecallScope(record, filters) {
495
+ if (record.scope === "thread") {
496
+ return typeof filters.threadId === "string" && String(record.provenance.threadId ?? "") === filters.threadId;
497
+ }
498
+ if (record.scope === "agent") {
499
+ return String(record.provenance.agentId ?? "") === filters.agentId;
500
+ }
501
+ if (record.scope === "workspace") {
502
+ return String(record.provenance.workspaceId ?? filters.workspaceId) === filters.workspaceId;
503
+ }
504
+ if (record.scope === "user") {
505
+ return String(record.provenance.userId ?? "default") === filters.userId;
506
+ }
507
+ return String(record.provenance.projectId ?? filters.workspaceId) === filters.projectId;
508
+ }
509
+ getMemoryScopeBoost(scope) {
510
+ if (scope === "thread") {
511
+ return 4;
512
+ }
513
+ if (scope === "agent") {
514
+ return 2;
515
+ }
516
+ if (scope === "workspace") {
517
+ return 1;
518
+ }
519
+ return 0;
520
+ }
406
521
  async buildRuntimeMemoryContext(binding, threadId, input) {
407
522
  const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
408
523
  const ranked = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, ["thread", "agent", "workspace"]))
@@ -452,30 +567,47 @@ export class AgentHarnessRuntime {
452
567
  if (candidates.length === 0) {
453
568
  return;
454
569
  }
455
- const threadCandidates = candidates.filter((candidate) => (candidate.scope ?? "thread") === "thread");
456
- const workspaceCandidates = candidates.filter((candidate) => (candidate.scope ?? "thread") === "workspace");
457
- const agentCandidates = candidates.filter((candidate) => (candidate.scope ?? "thread") === "agent");
458
- const recordedAt = new Date().toISOString();
459
- await this.runtimeMemoryStore.put(["memories", "candidates", threadId], `${runId}.json`, {
460
- runId,
461
- threadId,
462
- storedAt: recordedAt,
463
- candidates,
464
- });
465
- await persistStructuredMemoryRecords({
466
- store: this.runtimeMemoryStore,
570
+ await this.persistStructuredMemoryCandidates(binding, {
467
571
  candidates,
468
572
  threadId,
469
573
  runId,
470
574
  agentId: binding.agent.id,
471
- recordedAt,
575
+ recordedAt: new Date().toISOString(),
576
+ storeCandidateLog: true,
577
+ });
578
+ }
579
+ async persistStructuredMemoryCandidates(binding, input) {
580
+ const workspaceId = this.getWorkspaceId(binding);
581
+ const userId = input.userId ?? "default";
582
+ 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");
586
+ if (input.storeCandidateLog) {
587
+ await this.runtimeMemoryStore.put(["memories", "candidates", input.threadId], `${input.runId}.json`, {
588
+ runId: input.runId,
589
+ threadId: input.threadId,
590
+ storedAt: input.recordedAt,
591
+ candidates: input.candidates,
592
+ });
593
+ }
594
+ const persisted = await persistStructuredMemoryRecords({
595
+ store: this.runtimeMemoryStore,
596
+ candidates: input.candidates,
597
+ threadId: input.threadId,
598
+ runId: input.runId,
599
+ agentId: input.agentId,
600
+ workspaceId,
601
+ userId,
602
+ projectId,
603
+ recordedAt: input.recordedAt,
472
604
  });
473
605
  const writes = [];
474
606
  if (threadCandidates.length > 0) {
475
- writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
607
+ writes.push(this.appendMemoryDigest(this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }), "tool-memory.md", threadCandidates, 12, "Thread Tool Memory"));
476
608
  writes.push(consolidateStructuredMemoryScope({
477
609
  store: this.runtimeMemoryStore,
478
- namespace: this.resolveMemoryNamespace("thread", binding, { threadId }),
610
+ namespace: this.resolveMemoryNamespace("thread", binding, { threadId: input.threadId }),
479
611
  title: "Thread Structured Memory",
480
612
  scope: "thread",
481
613
  maxEntries: 12,
@@ -505,6 +637,7 @@ export class AgentHarnessRuntime {
505
637
  }));
506
638
  }
507
639
  await Promise.all(writes);
640
+ return persisted;
508
641
  }
509
642
  async appendMemoryDigest(namespace, key, candidates, maxEntries, title) {
510
643
  const existing = await this.runtimeMemoryStore.get(namespace, key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.153",
3
+ "version": "0.0.154",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",