@botbotgo/agent-harness 0.0.155 → 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.
package/README.md CHANGED
@@ -118,7 +118,7 @@ That means:
118
118
 
119
119
  The runtime provides:
120
120
 
121
- - `createAgentHarness(workspaceRoot)`, `run(...)`, `memorize(...)`, `recall(...)`, `resolveApproval(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
121
+ - `createAgentHarness(workspaceRoot)`, `run(...)`, `memorize(...)`, `recall(...)`, `listMemories(...)`, `updateMemory(...)`, `removeMemory(...)`, `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
@@ -414,10 +414,13 @@ const recalled = await recall(runtime, {
414
414
  });
415
415
  ```
416
416
 
417
- Use `memorize(...)` and `recall(...)` when an application needs a stable public runtime memory surface without importing internal `runtime/harness/system/*` modules.
417
+ Use `memorize(...)`, `recall(...)`, `listMemories(...)`, `updateMemory(...)`, and `removeMemory(...)` when an application needs a stable public runtime memory surface without importing internal `runtime/harness/system/*` modules.
418
418
 
419
419
  - `memorize(...)` returns stable `MemoryRecord` and `MemoryDecision` results while leaving merge, review, archive, and storage layout runtime-managed
420
420
  - `recall(...)` returns ranked `MemoryRecord` items filtered by runtime memory scope and kind
421
+ - `listMemories(...)` returns stable `MemoryRecord` items for inspection and admin workflows, defaulting to `active` records unless status filters are provided
422
+ - `updateMemory(...)` edits one durable memory record by `memoryId` without exposing internal store namespaces
423
+ - `removeMemory(...)` deletes one durable memory record by `memoryId` and rebuilds runtime-managed projections
421
424
  - app-specific knowledge taxonomy, review UI, and admin surfaces still belong in the application layer
422
425
 
423
426
  ### Let The Runtime Route
@@ -467,6 +470,7 @@ The runtime event stream includes:
467
470
 
468
471
  ```ts
469
472
  import {
473
+ deleteSession,
470
474
  getSession,
471
475
  getApproval,
472
476
  listSessions,
@@ -485,6 +489,10 @@ if (approval) {
485
489
  decision: "approve",
486
490
  });
487
491
  }
492
+
493
+ if (session && session.currentState === "completed") {
494
+ await deleteSession(runtime, session.sessionId);
495
+ }
488
496
  ```
489
497
 
490
498
  These methods return runtime-facing records, not raw checkpoint or backend objects.
@@ -548,6 +556,13 @@ Core workspace files:
548
556
  - `resources/tools/`
549
557
  - `resources/skills/`
550
558
 
559
+ Discovery rules:
560
+
561
+ - every YAML document under `config/**` is discovered recursively; filenames and subfolders are organizational only
562
+ - YAML object semantics come from `kind`, `metadata.name` or `id`, and object content rather than the file path
563
+ - local tools are auto-discovered from `resources/tools/**/*.js|*.mjs|*.cjs` when they export `tool({...})`
564
+ - skills are auto-discovered from `resources/skills/**/SKILL.md`
565
+
551
566
  Workspace-local tool modules in `resources/tools/` should be exported with `tool({...})`.
552
567
  Any other local module shape is not supported, and unsupported shapes are rejected at load time.
553
568
 
@@ -563,7 +578,7 @@ There are three main configuration layers:
563
578
 
564
579
  - runtime policy in `config/runtime/workspace.yaml`
565
580
  - reusable object catalogs in `config/catalogs/*.yaml`
566
- - agent assembly in `config/agents/*.yaml`
581
+ - agent assembly in `config/**/*.yaml`
567
582
 
568
583
  ### Backend Guidance
569
584
 
@@ -633,7 +648,7 @@ Use this file for shared bootstrap memory loaded at agent construction time.
633
648
 
634
649
  Keep stable project context here. Treat it as startup context, not mutable long-term memory.
635
650
 
636
- ### `config/catalogs/models.yaml`
651
+ ### Models Catalogs In `config/**`
637
652
 
638
653
  Use named chat-model presets:
639
654
 
@@ -649,7 +664,9 @@ spec:
649
664
 
650
665
  These load as `model/default`.
651
666
 
652
- ### `config/catalogs/embedding-models.yaml`
667
+ You can place `kind: Models` catalogs anywhere under `config/`; `config/catalogs/models.yaml` is only the recommended layout.
668
+
669
+ ### Embedding Model Catalogs In `config/**`
653
670
 
654
671
  Use named embedding-model presets for retrieval-oriented tools.
655
672
 
@@ -740,10 +757,12 @@ spec:
740
757
 
741
758
  `spec[].kind` can be omitted here; catalog entries default to `McpServer`.
742
759
 
743
- ### `config/agents/*.yaml`
760
+ ### Agents In `config/**`
744
761
 
745
762
  Agents always use `kind: Agent` plus `spec.backend`.
746
763
 
764
+ Agent YAML can live anywhere under `config/`; `config/agents/*.yaml` is the recommended layout, not a loader requirement.
765
+
747
766
  Use two sections:
748
767
 
749
768
  - `spec.runtime` for harness-owned runtime placement such as `spec.runtime.runRoot`
package/README.zh.md CHANGED
@@ -118,7 +118,7 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
118
118
 
119
119
  运行时提供:
120
120
 
121
- - `createAgentHarness(workspaceRoot)`、`run(...)`、`memorize(...)`、`recall(...)`、`resolveApproval(...)`、`subscribe(...)`、各类查询方法,以及 `stop(...)`
121
+ - `createAgentHarness(workspaceRoot)`、`run(...)`、`memorize(...)`、`recall(...)`、`listMemories(...)`、`updateMemory(...)`、`removeMemory(...)`、`resolveApproval(...)`、`subscribe(...)`、各类查询方法,以及 `stop(...)`
122
122
  - 以 YAML 描述的工作区装配:路由、模型、工具、存储、后端、MCP、恢复与维护等
123
123
  - 通过适配器对接当前的 LangChain v1 与 DeepAgents 执行
124
124
  - 本地 `resources/tools/` 中 `tool({...})` 工具模块与 `resources/skills/` 的发现
@@ -386,10 +386,13 @@ const recalled = await recall(runtime, {
386
386
  });
387
387
  ```
388
388
 
389
- 当应用需要稳定的公开 runtime memory 接口,而不想依赖内部 `runtime/harness/system/*` 模块时,使用 `memorize(...)` 与 `recall(...)`。
389
+ 当应用需要稳定的公开 runtime memory 接口,而不想依赖内部 `runtime/harness/system/*` 模块时,使用 `memorize(...)`、`recall(...)`、`listMemories(...)`、`updateMemory(...)` 与 `removeMemory(...)`。
390
390
 
391
391
  - `memorize(...)` 返回稳定的 `MemoryRecord` 与 `MemoryDecision` 结果,而 merge、review、archive 与存储布局仍由 runtime 内部托管
392
392
  - `recall(...)` 返回按相关性排序、并按 scope / kind 过滤后的 `MemoryRecord`
393
+ - `listMemories(...)` 返回稳定的 `MemoryRecord`,用于 inspection / admin 场景;如果不传 status 过滤器,默认只返回 `active`
394
+ - `updateMemory(...)` 通过 `memoryId` 更新单条 durable memory,而不暴露内部 store namespace
395
+ - `removeMemory(...)` 通过 `memoryId` 删除单条 durable memory,并重建运行时托管的 projection
393
396
  - 业务知识分类、review UI 与管理后台仍应留在应用层
394
397
 
395
398
  ### 由运行时路由
@@ -439,6 +442,7 @@ const result = await run(runtime, {
439
442
 
440
443
  ```ts
441
444
  import {
445
+ deleteSession,
442
446
  getApproval,
443
447
  getSession,
444
448
  listApprovals,
@@ -457,6 +461,10 @@ if (approval) {
457
461
  decision: "approve",
458
462
  });
459
463
  }
464
+
465
+ if (session && session.currentState === "completed") {
466
+ await deleteSession(runtime, session.sessionId);
467
+ }
460
468
  ```
461
469
 
462
470
  这些方法返回面向运行时的记录,而非原始 checkpoint 或后端对象。
@@ -520,6 +528,13 @@ await stop(runtime);
520
528
  - `resources/tools/`
521
529
  - `resources/skills/`
522
530
 
531
+ 发现规则:
532
+
533
+ - `config/**` 下的所有 YAML 文档都会被递归发现;文件名与子目录只用于组织,不参与语义
534
+ - YAML 对象语义由 `kind`、`metadata.name` 或 `id` 以及对象内容决定,而不是由文件路径决定
535
+ - 本地工具会从 `resources/tools/**/*.js|*.mjs|*.cjs` 中自动发现,前提是模块导出 `tool({...})`
536
+ - skills 会从 `resources/skills/**/SKILL.md` 自动发现
537
+
523
538
  `resources/tools/` 下的工作区本地工具模块应统一用 `tool({...})` 导出。
524
539
  不支持历史/兼容写法,任何不带该导出形式的工具模块都会在工作区加载时被拒绝。
525
540
 
@@ -529,7 +544,7 @@ await stop(runtime);
529
544
  - agent 再按名字白名单选择 tools 与 skills
530
545
  - `config/runtime/workspace.yaml` 中的运行时策略
531
546
  - `config/catalogs/*.yaml` 中的可复用对象目录
532
- - `config/agents/*.yaml` 中的 agent 装配
547
+ - `config/**/*.yaml` 中的 agent 装配
533
548
 
534
549
  ### Backend 选择建议
535
550
 
@@ -602,7 +617,7 @@ spec:
602
617
 
603
618
  在此放置稳定的项目上下文;视为启动上下文,而非可变长期记忆。
604
619
 
605
- ### `config/catalogs/models.yaml`
620
+ ### `config/**` 下的 Models Catalog
606
621
 
607
622
  命名聊天模型预设:
608
623
 
@@ -618,7 +633,9 @@ spec:
618
633
 
619
634
  加载为 `model/default`。
620
635
 
621
- ### `config/catalogs/embedding-models.yaml`
636
+ `kind: Models` catalog 可以放在 `config/` 下任意位置;`config/catalogs/models.yaml` 只是推荐布局。
637
+
638
+ ### `config/**` 下的 Embedding Model Catalog
622
639
 
623
640
  面向检索类工具的命名嵌入模型预设。
624
641
 
@@ -707,10 +724,12 @@ spec:
707
724
 
708
725
  这里可以省略 `spec[].kind`;catalog 项会默认按 `McpServer` 处理。
709
726
 
710
- ### `config/agents/*.yaml`
727
+ ### `config/**` 下的 Agent YAML
711
728
 
712
729
  Agent 始终使用 `kind: Agent` 以及 `spec.backend`。
713
730
 
731
+ Agent YAML 可以放在 `config/` 下任意位置;`config/agents/*.yaml` 是推荐布局,不是 loader 的硬性要求。
732
+
714
733
  两个区块:
715
734
 
716
735
  - `spec.runtime`:harness 侧运行时放置,例如 `spec.runtime.runRoot`
package/dist/api.d.ts CHANGED
@@ -1,11 +1,11 @@
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";
1
+ import type { CancelOptions, InvocationEnvelope, ListMemoriesInput, ListMemoriesResult, MemoryRecord, MemorizeInput, MemorizeResult, MessageContent, RecallInput, RecallResult, RemoveMemoryInput, RequestRecord, RequestSummary, ResumeOptions, RunDecisionOptions, RunResult, RunStartOptions, RuntimeHealthSnapshot, RuntimeAdapterOptions, SessionRecord, SessionSummary, UpdateMemoryInput, 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
+ export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, RecallInput, RecallResult, RemoveMemoryInput, UpdateMemoryInput, } from "./contracts/types.js";
9
9
  type PublicApprovalRecord = {
10
10
  approvalId: string;
11
11
  pendingActionId: string;
@@ -57,6 +57,9 @@ export declare function normalizeUserChatInput(input: UserChatInput, options?: N
57
57
  export declare function run(runtime: AgentHarnessRuntime, options: PublicRunOptions): Promise<PublicRunResult>;
58
58
  export declare function memorize(runtime: AgentHarnessRuntime, input: MemorizeInput): Promise<MemorizeResult>;
59
59
  export declare function recall(runtime: AgentHarnessRuntime, input: RecallInput): Promise<RecallResult>;
60
+ export declare function listMemories(runtime: AgentHarnessRuntime, input?: ListMemoriesInput): Promise<ListMemoriesResult>;
61
+ export declare function updateMemory(runtime: AgentHarnessRuntime, input: UpdateMemoryInput): Promise<MemoryRecord>;
62
+ export declare function removeMemory(runtime: AgentHarnessRuntime, input: RemoveMemoryInput): Promise<MemoryRecord>;
60
63
  export declare function subscribe(runtime: AgentHarnessRuntime, listener: Parameters<AgentHarnessRuntime["subscribe"]>[0]): () => void;
61
64
  export declare function listSessions(runtime: AgentHarnessRuntime, filter?: Parameters<AgentHarnessRuntime["listThreads"]>[0]): Promise<SessionSummary[]>;
62
65
  export declare function listRequests(runtime: AgentHarnessRuntime, filter?: {
package/dist/api.js CHANGED
@@ -134,6 +134,15 @@ export async function memorize(runtime, input) {
134
134
  export async function recall(runtime, input) {
135
135
  return runtime.recall(input);
136
136
  }
137
+ export async function listMemories(runtime, input = {}) {
138
+ return runtime.listMemories(input);
139
+ }
140
+ export async function updateMemory(runtime, input) {
141
+ return runtime.updateMemory(input);
142
+ }
143
+ export async function removeMemory(runtime, input) {
144
+ return runtime.removeMemory(input);
145
+ }
137
146
  export function subscribe(runtime, listener) {
138
147
  return runtime.subscribe(listener);
139
148
  }
@@ -195,6 +195,37 @@ export type RecallInput = {
195
195
  export type RecallResult = {
196
196
  items: MemoryRecord[];
197
197
  };
198
+ export type ListMemoriesInput = {
199
+ scopes?: MemoryScope[];
200
+ kinds?: MemoryKind[];
201
+ status?: MemoryRecordStatus[];
202
+ threadId?: string;
203
+ agentId?: string;
204
+ workspaceId?: string;
205
+ userId?: string;
206
+ projectId?: string;
207
+ limit?: number;
208
+ };
209
+ export type ListMemoriesResult = {
210
+ items: MemoryRecord[];
211
+ };
212
+ export type UpdateMemoryInput = {
213
+ memoryId: string;
214
+ content?: string;
215
+ summary?: string;
216
+ status?: MemoryRecordStatus;
217
+ confidence?: number;
218
+ expiresAt?: string | null;
219
+ sourceType?: string;
220
+ sourceRefs?: string[];
221
+ tags?: string[];
222
+ observedAt?: string;
223
+ lastConfirmedAt?: string;
224
+ provenance?: Record<string, unknown>;
225
+ };
226
+ export type RemoveMemoryInput = {
227
+ memoryId: string;
228
+ };
198
229
  /**
199
230
  * Operator-facing projection of tool execution policy already compiled into a binding.
200
231
  * 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, 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";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
+ export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, RecallInput, RecallResult, RemoveMemoryInput, UpdateMemoryInput, 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, memorize, normalizeUserChatInput, recall, resolveApproval, run, serveToolsOverStdio, subscribe, stop, } from "./api.js";
1
+ export { AgentHarnessRuntime, cancelRun, createAgentHarness, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.154";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.155";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.154";
1
+ export const AGENT_HARNESS_VERSION = "0.0.155";
@@ -15,6 +15,9 @@ export declare function renderMemoryRecordsMarkdown(title: string, records: Memo
15
15
  export declare function rebuildStructuredMemoryProjections(store: StoreLike, namespace: string[], title: string, scope: MemoryScope, maxEntries: number): Promise<void>;
16
16
  export declare function listMemoryRecordsForScopes(store: StoreLike, scopes: MemoryScope[]): Promise<MemoryRecord[]>;
17
17
  export declare function getMemoryRecord(store: StoreLike, scope: MemoryScope, recordId: string): Promise<MemoryRecord | null>;
18
+ export declare function findMemoryRecordById(store: StoreLike, recordId: string): Promise<MemoryRecord | null>;
19
+ export declare function updateMemoryRecord(store: StoreLike, record: MemoryRecord, updatedAt: string): Promise<MemoryRecord>;
20
+ export declare function removeMemoryRecord(store: StoreLike, scope: MemoryScope, recordId: string): Promise<MemoryRecord | null>;
18
21
  export declare function persistStructuredMemoryRecords(options: PersistMemoryRecordsOptions): Promise<{
19
22
  records: MemoryRecord[];
20
23
  decisions: MemoryDecision[];
@@ -34,6 +34,12 @@ function createCanonicalKey(candidate, kind, scope) {
34
34
  function buildScopeNamespace(scope) {
35
35
  return ["memories", "records", scope];
36
36
  }
37
+ function buildCanonicalIndexNamespace(scope) {
38
+ return ["memories", "indexes", "canonical", scope];
39
+ }
40
+ function buildSourceRefIndexNamespace(scope) {
41
+ return ["memories", "indexes", "source-ref", scope];
42
+ }
37
43
  function normalizeTextForCompare(value) {
38
44
  return value.toLowerCase().replace(/\s+/g, " ").trim();
39
45
  }
@@ -312,7 +318,7 @@ function evaluateDecision(existing, incoming, recordedAt) {
312
318
  async function putRecordWithIndexes(store, record, updatedAt) {
313
319
  const canonicalKeyId = createFingerprint(record.canonicalKey);
314
320
  await store.put(buildScopeNamespace(record.scope), `${record.id}.json`, record);
315
- await store.put(["memories", "indexes", "canonical", record.scope], `${canonicalKeyId}-${record.id}.json`, {
321
+ await store.put(buildCanonicalIndexNamespace(record.scope), `${canonicalKeyId}-${record.id}.json`, {
316
322
  canonicalKey: record.canonicalKey,
317
323
  recordId: record.id,
318
324
  scope: record.scope,
@@ -321,7 +327,7 @@ async function putRecordWithIndexes(store, record, updatedAt) {
321
327
  });
322
328
  await Promise.all(record.sourceRefs.map((sourceRef) => {
323
329
  const sourceRefId = createFingerprint(sourceRef);
324
- return store.put(["memories", "indexes", "source-ref", record.scope], `${sourceRefId}-${record.id}.json`, {
330
+ return store.put(buildSourceRefIndexNamespace(record.scope), `${sourceRefId}-${record.id}.json`, {
325
331
  sourceRef,
326
332
  recordId: record.id,
327
333
  scope: record.scope,
@@ -384,6 +390,46 @@ export async function getMemoryRecord(store, scope, recordId) {
384
390
  const candidate = record;
385
391
  return typeof candidate.id === "string" && typeof candidate.content === "string" ? candidate : null;
386
392
  }
393
+ export async function findMemoryRecordById(store, recordId) {
394
+ for (const scope of MEMORY_SCOPES) {
395
+ const record = await getMemoryRecord(store, scope, recordId);
396
+ if (record) {
397
+ return record;
398
+ }
399
+ }
400
+ return null;
401
+ }
402
+ async function deleteRecordIndexes(store, record) {
403
+ const [canonicalEntries, sourceRefEntries] = await Promise.all([
404
+ store.search(buildCanonicalIndexNamespace(record.scope)),
405
+ store.search(buildSourceRefIndexNamespace(record.scope)),
406
+ ]);
407
+ await Promise.all([
408
+ ...canonicalEntries
409
+ .filter((entry) => entry.value?.recordId === record.id)
410
+ .map((entry) => store.delete(entry.namespace, entry.key)),
411
+ ...sourceRefEntries
412
+ .filter((entry) => entry.value?.recordId === record.id)
413
+ .map((entry) => store.delete(entry.namespace, entry.key)),
414
+ ]);
415
+ }
416
+ export async function updateMemoryRecord(store, record, updatedAt) {
417
+ const existing = await getMemoryRecord(store, record.scope, record.id);
418
+ if (existing) {
419
+ await deleteRecordIndexes(store, existing);
420
+ }
421
+ await putRecordWithIndexes(store, record, updatedAt);
422
+ return record;
423
+ }
424
+ export async function removeMemoryRecord(store, scope, recordId) {
425
+ const existing = await getMemoryRecord(store, scope, recordId);
426
+ if (!existing) {
427
+ return null;
428
+ }
429
+ await deleteRecordIndexes(store, existing);
430
+ await store.delete(buildScopeNamespace(scope), `${recordId}.json`);
431
+ return existing;
432
+ }
387
433
  export async function persistStructuredMemoryRecords(options) {
388
434
  const existingRecords = await listStoredRecords(options.store);
389
435
  const persistedRecords = [];
@@ -1,4 +1,4 @@
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";
1
+ import type { ApprovalRecord, CancelOptions, HarnessEvent, HarnessStreamItem, RuntimeHealthSnapshot, ListMemoriesInput, ListMemoriesResult, MessageContent, RemoveMemoryInput, RunRecord, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, RunSummary, MemoryRecord, MemorizeInput, MemorizeResult, RecallInput, RecallResult, UpdateMemoryInput, 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";
@@ -70,6 +70,9 @@ export declare class AgentHarnessRuntime {
70
70
  }): Promise<RunSummary[]>;
71
71
  memorize(input: MemorizeInput): Promise<MemorizeResult>;
72
72
  recall(input: RecallInput): Promise<RecallResult>;
73
+ listMemories(input?: ListMemoriesInput): Promise<ListMemoriesResult>;
74
+ updateMemory(input: UpdateMemoryInput): Promise<MemoryRecord>;
75
+ removeMemory(input: RemoveMemoryInput): Promise<MemoryRecord>;
73
76
  getRun(runId: string): Promise<RunRecord | null>;
74
77
  private getSession;
75
78
  getThread(threadId: string): Promise<ThreadRecord | null>;
@@ -102,6 +105,8 @@ export declare class AgentHarnessRuntime {
102
105
  private invokeWithHistory;
103
106
  private resolveMemoryNamespace;
104
107
  private getWorkspaceId;
108
+ private summarizeMemoryContent;
109
+ private matchesMemoryFilters;
105
110
  private resolveRecallScopes;
106
111
  private matchesRecallScope;
107
112
  private getMemoryScopeBoost;
@@ -113,6 +118,7 @@ export declare class AgentHarnessRuntime {
113
118
  private createMem0MemoryRecord;
114
119
  private rankRecallCandidates;
115
120
  private rebuildRuntimeMemoryVectorIndex;
121
+ private refreshStructuredMemoryScope;
116
122
  private buildRuntimeMemoryContext;
117
123
  private persistRuntimeMemoryCandidates;
118
124
  private persistStructuredMemoryCandidates;
@@ -31,7 +31,7 @@ import { createDefaultHealthSnapshot, isInventoryEnabled, isThreadMemorySyncEnab
31
31
  import { Mem0IngestionSync, Mem0SemanticRecall, readMem0RuntimeConfig, } from "./harness/system/mem0-ingestion-sync.js";
32
32
  import { createRuntimeMemoryManager, RuntimeMemoryFormationSync, readRuntimeMemoryFormationConfig, } from "./harness/system/runtime-memory-manager.js";
33
33
  import { renderMemoryCandidatesMarkdown } from "./harness/system/runtime-memory-candidates.js";
34
- import { getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, } from "./harness/system/runtime-memory-records.js";
34
+ import { findMemoryRecordById, getMemoryRecord, listMemoryRecordsForScopes, persistStructuredMemoryRecords, removeMemoryRecord, updateMemoryRecord, } from "./harness/system/runtime-memory-records.js";
35
35
  import { consolidateStructuredMemoryScope } from "./harness/system/runtime-memory-consolidation.js";
36
36
  import { normalizeLangMemMemoryKind, readRuntimeMemoryMaintenanceConfig, readRuntimeMemoryPolicyConfig, resolveMemoryNamespace, scoreMemoryText, } from "./harness/system/runtime-memory-policy.js";
37
37
  import { resolveRuntimeAdapterOptions } from "./support/runtime-adapter-options.js";
@@ -361,6 +361,98 @@ export class AgentHarnessRuntime {
361
361
  .map(({ record }) => record);
362
362
  return { items };
363
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;
378
+ const items = (await listMemoryRecordsForScopes(this.runtimeMemoryStore, scopes))
379
+ .filter((record) => statuses.has(record.status))
380
+ .filter((record) => !kinds || kinds.has(record.kind))
381
+ .filter((record) => this.matchesMemoryFilters(record, {
382
+ threadId: input.threadId,
383
+ agentId: input.agentId,
384
+ workspaceId: input.workspaceId ?? workspaceId,
385
+ userId: input.userId,
386
+ projectId: input.projectId,
387
+ }))
388
+ .sort((left, right) => right.lastConfirmedAt.localeCompare(left.lastConfirmedAt))
389
+ .slice(0, limit);
390
+ return { items };
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
+ }
364
456
  async getRun(runId) {
365
457
  return this.persistence.getRun(runId);
366
458
  }
@@ -514,7 +606,7 @@ export class AgentHarnessRuntime {
514
606
  const template = this.runtimeMemoryPolicy?.namespaces[scope] ?? `memories/${scope}s/{${scope}Id}`;
515
607
  return resolveMemoryNamespace(template, {
516
608
  threadId: options.threadId,
517
- agentId: binding.agent.id,
609
+ agentId: options.agentId ?? binding.agent.id,
518
610
  workspaceId,
519
611
  userId: options.userId ?? "default",
520
612
  projectId: options.projectId ?? workspaceId,
@@ -524,6 +616,27 @@ export class AgentHarnessRuntime {
524
616
  const workspaceRoot = binding.harnessRuntime.workspaceRoot ?? this.workspace.workspaceRoot;
525
617
  return path.basename(workspaceRoot) || "default";
526
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
+ }
527
640
  resolveRecallScopes(input) {
528
641
  if (Array.isArray(input.scopes) && input.scopes.length > 0) {
529
642
  return Array.from(new Set(input.scopes));
@@ -755,6 +868,30 @@ export class AgentHarnessRuntime {
755
868
  // Fail open: vector indexing must not break durable memory writes.
756
869
  }
757
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
+ }
758
895
  async buildRuntimeMemoryContext(binding, threadId, input) {
759
896
  const query = typeof input === "string" ? input : JSON.stringify(input ?? "");
760
897
  const workspaceId = this.getWorkspaceId(binding);
@@ -6,7 +6,7 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
6
6
  import { isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
7
7
  import { discoverToolModuleDefinitions, isSupportedToolModulePath } from "../tool-modules.js";
8
8
  import { fileExists } from "../utils/fs.js";
9
- import { readNamedModelItems, readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
9
+ import { readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
10
10
  export { normalizeYamlItem, readYamlItems } from "./yaml-object-reader.js";
11
11
  const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
12
12
  const MODULE_AGENT_FILENAMES = ["agent.yaml", "agent.yml"];
@@ -39,16 +39,10 @@ function conventionalConfigRoot(root) {
39
39
  }
40
40
  function conventionalDirectoryRoots(root, relativeDir) {
41
41
  const resourceRoot = resolveResourcePackageRoot(root);
42
- const configRoot = conventionalConfigRoot(root);
43
- const candidates = relativeDir === "agents"
44
- ? [
45
- ...(configRoot ? [path.join(configRoot, "agents")] : []),
46
- path.join(root, "agents"),
47
- ]
48
- : [
49
- ...(resourceRoot ? [path.join(resourceRoot, relativeDir)] : []),
50
- path.join(root, relativeDir),
51
- ];
42
+ const candidates = [
43
+ ...(resourceRoot ? [path.join(resourceRoot, relativeDir)] : []),
44
+ path.join(root, relativeDir),
45
+ ];
52
46
  return Array.from(new Set(candidates));
53
47
  }
54
48
  export function conventionalPackageRoots(root, relativeDir) {
@@ -569,20 +563,6 @@ function mergeAgentRecord(records, item, sourcePath) {
569
563
  function mergeWorkspaceObjectRecord(records, workspaceObject, item, sourcePath) {
570
564
  mergeRawItemRecord(records, `${workspaceObject.kind}/${workspaceObject.id}`, item, sourcePath);
571
565
  }
572
- async function loadNamedModelsForRoot(configRoot, mergedObjects) {
573
- for (const { item, sourcePath } of await readNamedModelItems(configRoot)) {
574
- const workspaceObject = parseWorkspaceObject(item, sourcePath);
575
- if (!workspaceObject || workspaceObject.kind !== "model") {
576
- continue;
577
- }
578
- mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
579
- }
580
- }
581
- async function loadConfigAgentsForRoot(configRoot, mergedAgents) {
582
- for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
583
- mergeAgentRecord(mergedAgents, item, sourcePath);
584
- }
585
- }
586
566
  async function loadModuleAgentsForRoot(root, mergedAgents) {
587
567
  const modulesRoot = moduleCollectionRoot(root, "agents");
588
568
  if (!(await fileExists(modulesRoot))) {
@@ -675,7 +655,7 @@ async function loadModuleObjectsForRoot(root, mergedObjects) {
675
655
  mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
676
656
  }
677
657
  }
678
- async function loadConfigObjectsForRoot(root, configRoot, mergedObjects) {
658
+ async function loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects) {
679
659
  if (!conventionalConfigRoot(root)) {
680
660
  return;
681
661
  }
@@ -684,7 +664,8 @@ async function loadConfigObjectsForRoot(root, configRoot, mergedObjects) {
684
664
  if (!workspaceObject) {
685
665
  continue;
686
666
  }
687
- if (isAgentKind(workspaceObject.kind) || workspaceObject.kind === "model") {
667
+ if (isAgentKind(workspaceObject.kind)) {
668
+ mergeAgentRecord(mergedAgents, item, sourcePath);
688
669
  continue;
689
670
  }
690
671
  mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
@@ -708,20 +689,6 @@ async function loadRootObjects(root, mergedObjects) {
708
689
  function isAgentKind(kind) {
709
690
  return kind === "agent";
710
691
  }
711
- async function readConfigAgentItems(configRoot) {
712
- const records = await readYamlItems(configRoot, undefined, { recursive: true });
713
- return records.filter(({ item, sourcePath }) => {
714
- const kind = typeof item.kind === "string" ? item.kind : undefined;
715
- if (!isAgentKind(kind)) {
716
- return false;
717
- }
718
- const relativePath = path.relative(configRoot, sourcePath);
719
- if (!relativePath || relativePath.startsWith("..")) {
720
- return false;
721
- }
722
- return !relativePath.includes(path.sep) || relativePath.startsWith(`agents${path.sep}`);
723
- });
724
- }
725
692
  export async function readToolModuleItems(root) {
726
693
  if (!(await fileExists(root))) {
727
694
  return [];
@@ -792,11 +759,9 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
792
759
  const roots = [frameworkWorkspaceRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
793
760
  for (const root of roots) {
794
761
  const configRoot = conventionalConfigRoot(root) ?? root;
795
- await loadNamedModelsForRoot(configRoot, mergedObjects);
796
- await loadConfigAgentsForRoot(configRoot, mergedAgents);
762
+ await loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects);
797
763
  await loadModuleAgentsForRoot(root, mergedAgents);
798
764
  await loadConventionalObjectsForRoot(root, mergedObjects);
799
- await loadConfigObjectsForRoot(root, configRoot, mergedObjects);
800
765
  await loadModuleObjectsForRoot(root, mergedObjects);
801
766
  await loadRootObjects(root, mergedObjects);
802
767
  }
@@ -9,7 +9,3 @@ export declare function readNamedYamlItems(root: string, filenames: string[]): P
9
9
  item: Record<string, unknown>;
10
10
  sourcePath: string;
11
11
  }>>;
12
- export declare function readNamedModelItems(root: string): Promise<Array<{
13
- item: Record<string, unknown>;
14
- sourcePath: string;
15
- }>>;
@@ -2,7 +2,6 @@ import path from "node:path";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import { parseAllDocuments } from "yaml";
4
4
  import { fileExists, listFilesRecursive, readYamlOrJson } from "../utils/fs.js";
5
- const MODEL_FILENAMES = ["models.yaml", "models.yml"];
6
5
  const ENV_PLACEHOLDER_PATTERN = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
7
6
  function asObject(value) {
8
7
  return typeof value === "object" && value ? value : undefined;
@@ -165,7 +164,10 @@ export async function readYamlItems(root, relativeDir, options = {}) {
165
164
  return [];
166
165
  }
167
166
  const files = options.recursive
168
- ? await listFilesRecursive(targetRoot, ".yaml")
167
+ ? Array.from(new Set([
168
+ ...(await listFilesRecursive(targetRoot, ".yaml")),
169
+ ...(await listFilesRecursive(targetRoot, ".yml")),
170
+ ])).sort()
169
171
  : (await readdir(targetRoot, { withFileTypes: true }))
170
172
  .filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name))
171
173
  .map((entry) => path.join(targetRoot, entry.name))
@@ -199,33 +201,3 @@ export async function readNamedYamlItems(root, filenames) {
199
201
  }
200
202
  return records;
201
203
  }
202
- export async function readNamedModelItems(root) {
203
- const filePaths = new Set();
204
- for (const filename of MODEL_FILENAMES) {
205
- const directPath = path.join(root, filename);
206
- if (await fileExists(directPath)) {
207
- filePaths.add(directPath);
208
- }
209
- }
210
- for (const extension of [".yaml", ".yml"]) {
211
- for (const filePath of await listFilesRecursive(root, extension)) {
212
- if (MODEL_FILENAMES.includes(path.basename(filePath))) {
213
- filePaths.add(filePath);
214
- }
215
- }
216
- }
217
- const records = [];
218
- for (const filePath of [...filePaths].sort()) {
219
- const parsedDocuments = parseAllDocuments(await readYamlOrJson(filePath));
220
- for (const parsedDocument of parsedDocuments) {
221
- const resolvedDocument = interpolateEnvPlaceholders(parsedDocument.toJSON(), filePath);
222
- for (const item of await objectItemsFromDocument(resolvedDocument, filePath)) {
223
- const normalized = normalizeYamlItem(item);
224
- if (normalized.kind === "model" && typeof normalized.id === "string" && normalized.id.trim()) {
225
- records.push({ item: normalized, sourcePath: filePath });
226
- }
227
- }
228
- }
229
- }
230
- return records;
231
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.155",
3
+ "version": "0.0.156",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",