@botbotgo/agent-harness 0.0.139 → 0.0.141

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
@@ -140,6 +140,9 @@ Boundary documents live in:
140
140
  - `docs/tool-execution-policy.md`
141
141
  - `docs/feature-checklist.md`
142
142
  - `docs/long-term-memory.md`
143
+ - `docs/memory-runtime-design.md`
144
+ - `docs/memory-runtime-usage.md`
145
+ - `docs/memory-policy-reference.md`
143
146
  - `docs/app-task-pattern.md`
144
147
  - `docs/model-layering.md`
145
148
  - `docs/coding-agent-guide.md`
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.138";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.140";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.138";
1
+ export const AGENT_HARNESS_VERSION = "0.0.140";
@@ -0,0 +1,24 @@
1
+ import type { HarnessEvent, HarnessEventProjection } from "../../../contracts/types.js";
2
+ import type { RuntimePersistence } from "../../../persistence/types.js";
3
+ import type { StoreLike } from "./store.js";
4
+ export type ResolvedRuntimeMemorySyncConfig = {
5
+ enabled: true;
6
+ writeOnApprovalResolution: boolean;
7
+ writeOnRunCompletion: boolean;
8
+ backgroundConsolidation: boolean;
9
+ maxMessagesPerRun: number;
10
+ };
11
+ export declare function readRuntimeMemorySyncConfig(runtimeMemory: Record<string, unknown> | undefined): ResolvedRuntimeMemorySyncConfig | undefined;
12
+ export declare class RuntimeMemorySync implements HarnessEventProjection {
13
+ private readonly persistence;
14
+ readonly store: StoreLike;
15
+ private readonly config;
16
+ private readonly pending;
17
+ private syncChain;
18
+ readonly name = "runtime-memory-sync";
19
+ constructor(persistence: RuntimePersistence, store: StoreLike, config: ResolvedRuntimeMemorySyncConfig);
20
+ shouldHandle(event: HarnessEvent): boolean;
21
+ handleEvent(event: HarnessEvent): Promise<void>;
22
+ private syncRun;
23
+ close(): Promise<void>;
24
+ }
@@ -0,0 +1,193 @@
1
+ import { extractMessageText } from "../../../utils/message-content.js";
2
+ function asRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
4
+ }
5
+ function asBoolean(value) {
6
+ return typeof value === "boolean" ? value : undefined;
7
+ }
8
+ function asPositiveInteger(value) {
9
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
10
+ }
11
+ function excerpt(message) {
12
+ if (!message?.content) {
13
+ return "(none)";
14
+ }
15
+ const normalized = extractMessageText(message.content).replace(/\s+/g, " ").trim();
16
+ return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
17
+ }
18
+ function formatMessageSection(title, messages) {
19
+ const lines = [`## ${title}`, ""];
20
+ if (messages.length === 0) {
21
+ lines.push("(none)", "");
22
+ return lines;
23
+ }
24
+ for (const message of messages) {
25
+ lines.push(`- ${excerpt(message)}`);
26
+ }
27
+ lines.push("");
28
+ return lines;
29
+ }
30
+ function formatApprovalSection(approvals) {
31
+ const lines = ["## Approval Snapshot", ""];
32
+ if (approvals.length === 0) {
33
+ lines.push("(none)", "");
34
+ return lines;
35
+ }
36
+ for (const approval of approvals) {
37
+ lines.push(`- ${approval.approvalId}: ${approval.toolName} (${approval.status})`);
38
+ }
39
+ lines.push("");
40
+ return lines;
41
+ }
42
+ function renderRunSummaryMarkdown(input) {
43
+ const userMessages = input.messages.filter((message) => message.role === "user").slice(-3);
44
+ const assistantMessages = input.messages.filter((message) => message.role === "assistant").slice(-3);
45
+ return [
46
+ "# Run Memory Summary",
47
+ "",
48
+ `- thread_id: ${input.thread.threadId}`,
49
+ `- run_id: ${input.runId}`,
50
+ `- agent_id: ${input.agentId}`,
51
+ `- status: ${input.thread.status}`,
52
+ `- trigger: ${input.trigger}`,
53
+ `- captured_at: ${input.capturedAt}`,
54
+ "",
55
+ ...formatMessageSection("Recent User Messages", userMessages),
56
+ ...formatMessageSection("Recent Assistant Messages", assistantMessages),
57
+ ...formatApprovalSection(input.approvals),
58
+ ].join("\n");
59
+ }
60
+ function renderThreadDigestMarkdown(input) {
61
+ const latestUser = input.messages.filter((message) => message.role === "user").at(-1);
62
+ const latestAssistant = input.messages.filter((message) => message.role === "assistant").at(-1);
63
+ return [
64
+ "# Durable Thread Digest",
65
+ "",
66
+ `- thread_id: ${input.thread.threadId}`,
67
+ `- latest_run_id: ${input.runId}`,
68
+ `- status: ${input.thread.status}`,
69
+ `- last_memory_trigger: ${input.trigger}`,
70
+ `- updated_at: ${input.capturedAt}`,
71
+ "",
72
+ "## Latest Durable User Context",
73
+ excerpt(latestUser),
74
+ "",
75
+ "## Latest Durable Assistant Context",
76
+ excerpt(latestAssistant),
77
+ "",
78
+ ].join("\n");
79
+ }
80
+ const RUNTIME_MEMORY_EVENT_TYPES = new Set(["run.state.changed", "approval.resolved"]);
81
+ export function readRuntimeMemorySyncConfig(runtimeMemory) {
82
+ if (runtimeMemory?.enabled !== true) {
83
+ return undefined;
84
+ }
85
+ const ingestion = asRecord(runtimeMemory.ingestion);
86
+ const writeOnApprovalResolution = asBoolean(ingestion?.writeOnApprovalResolution) ?? true;
87
+ const writeOnRunCompletion = asBoolean(ingestion?.writeOnRunCompletion) ?? true;
88
+ if (!writeOnApprovalResolution && !writeOnRunCompletion) {
89
+ return undefined;
90
+ }
91
+ return {
92
+ enabled: true,
93
+ writeOnApprovalResolution,
94
+ writeOnRunCompletion,
95
+ backgroundConsolidation: asBoolean(ingestion?.backgroundConsolidation) ?? true,
96
+ maxMessagesPerRun: asPositiveInteger(ingestion?.maxMessagesPerRun) ?? 40,
97
+ };
98
+ }
99
+ export class RuntimeMemorySync {
100
+ persistence;
101
+ store;
102
+ config;
103
+ pending = new Set();
104
+ syncChain = Promise.resolve();
105
+ name = "runtime-memory-sync";
106
+ constructor(persistence, store, config) {
107
+ this.persistence = persistence;
108
+ this.store = store;
109
+ this.config = config;
110
+ }
111
+ shouldHandle(event) {
112
+ if (!RUNTIME_MEMORY_EVENT_TYPES.has(event.eventType)) {
113
+ return false;
114
+ }
115
+ if (event.eventType === "approval.resolved") {
116
+ return this.config.writeOnApprovalResolution;
117
+ }
118
+ return this.config.writeOnRunCompletion && event.payload.state === "completed";
119
+ }
120
+ async handleEvent(event) {
121
+ if (!this.shouldHandle(event)) {
122
+ return;
123
+ }
124
+ const trigger = event.eventType === "approval.resolved" ? "approval.resolved" : "run.completed";
125
+ const task = this.syncChain
126
+ .then(() => this.syncRun(event.threadId, event.runId, trigger, event.timestamp))
127
+ .catch(() => {
128
+ // Fail open: runtime memory sync must not break the hot path.
129
+ });
130
+ this.syncChain = task
131
+ .catch(() => {
132
+ // Fail open: runtime memory sync must not break the hot path.
133
+ })
134
+ .finally(() => {
135
+ this.pending.delete(task);
136
+ });
137
+ this.pending.add(task);
138
+ }
139
+ async syncRun(threadId, runId, trigger, capturedAt) {
140
+ const [thread, run, allMessages, approvals] = await Promise.all([
141
+ this.persistence.getSession(threadId),
142
+ this.persistence.getRun(runId),
143
+ this.persistence.listThreadMessages(threadId, this.config.maxMessagesPerRun),
144
+ this.persistence.getRunApprovals(threadId, runId),
145
+ ]);
146
+ if (!thread || !run) {
147
+ return;
148
+ }
149
+ const messages = allMessages.filter((message) => message.runId === runId);
150
+ if (messages.length === 0) {
151
+ return;
152
+ }
153
+ const agentId = run.agentId ?? thread.agentId;
154
+ const summaryMarkdown = renderRunSummaryMarkdown({
155
+ thread,
156
+ runId,
157
+ agentId,
158
+ trigger,
159
+ capturedAt,
160
+ messages,
161
+ approvals,
162
+ });
163
+ await Promise.all([
164
+ this.store.put(["memories", "runs", threadId], `${runId}.summary.md`, { content: `${summaryMarkdown}\n` }),
165
+ this.store.put(["memories", "runs", threadId], `${runId}.record.json`, {
166
+ kind: "summary",
167
+ scope: "thread",
168
+ threadId,
169
+ runId,
170
+ agentId,
171
+ trigger,
172
+ capturedAt,
173
+ status: thread.status,
174
+ messageCount: messages.length,
175
+ approvalCount: approvals.length,
176
+ }),
177
+ ]);
178
+ if (!this.config.backgroundConsolidation) {
179
+ return;
180
+ }
181
+ const digestMarkdown = renderThreadDigestMarkdown({
182
+ thread,
183
+ runId,
184
+ trigger,
185
+ capturedAt,
186
+ messages,
187
+ });
188
+ await this.store.put(["memories", "threads", threadId], "durable-summary.md", { content: `${digestMarkdown}\n` });
189
+ }
190
+ async close() {
191
+ await Promise.allSettled(Array.from(this.pending));
192
+ }
193
+ }
@@ -23,6 +23,8 @@ export declare class AgentHarnessRuntime {
23
23
  private readonly routingDefaultAgentId?;
24
24
  private readonly threadMemorySync;
25
25
  private readonly unregisterThreadMemorySync;
26
+ private readonly runtimeMemorySync;
27
+ private readonly unregisterRuntimeMemorySync;
26
28
  private readonly mem0IngestionSync;
27
29
  private readonly unregisterMem0IngestionSync;
28
30
  private readonly resolvedRuntimeAdapterOptions;
@@ -7,6 +7,7 @@ import { PolicyEngine } from "./harness/system/policy-engine.js";
7
7
  import { getConcurrencyConfig, getRecoveryConfig, getRoutingDefaultAgentId, getRoutingRules, } from "../workspace/support/workspace-ref-utils.js";
8
8
  import { createHarnessEvent, inferRoutingBindings, renderRuntimeFailure, } from "./support/harness-support.js";
9
9
  import { ThreadMemorySync } from "./harness/system/thread-memory-sync.js";
10
+ import { RuntimeMemorySync, readRuntimeMemorySyncConfig } from "./harness/system/runtime-memory-sync.js";
10
11
  import { FileBackedStore } from "./harness/system/store.js";
11
12
  import { HealthMonitor, readHealthMonitorConfig, } from "./harness/system/health-monitor.js";
12
13
  import { normalizeInvocationEnvelope, normalizeRunPriority, resolveRunListeners, } from "./harness/run/helpers.js";
@@ -58,6 +59,8 @@ export class AgentHarnessRuntime {
58
59
  routingDefaultAgentId;
59
60
  threadMemorySync;
60
61
  unregisterThreadMemorySync;
62
+ runtimeMemorySync;
63
+ unregisterRuntimeMemorySync;
61
64
  mem0IngestionSync;
62
65
  unregisterMem0IngestionSync;
63
66
  resolvedRuntimeAdapterOptions;
@@ -143,6 +146,15 @@ export class AgentHarnessRuntime {
143
146
  this.threadMemorySync = null;
144
147
  this.unregisterThreadMemorySync = () => { };
145
148
  }
149
+ const runtimeMemorySyncConfig = readRuntimeMemorySyncConfig(this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory);
150
+ if (runtimeMemorySyncConfig) {
151
+ this.runtimeMemorySync = new RuntimeMemorySync(this.persistence, this.runtimeMemoryStore, runtimeMemorySyncConfig);
152
+ this.unregisterRuntimeMemorySync = this.eventBus.registerProjection(this.runtimeMemorySync);
153
+ }
154
+ else {
155
+ this.runtimeMemorySync = null;
156
+ this.unregisterRuntimeMemorySync = () => { };
157
+ }
146
158
  const mem0RuntimeConfig = readMem0RuntimeConfig(this.defaultRuntimeEntryBinding?.harnessRuntime.runtimeMemory, this.workspace.workspaceRoot);
147
159
  if (mem0RuntimeConfig) {
148
160
  this.mem0IngestionSync = new Mem0IngestionSync(this.persistence, mem0RuntimeConfig, runRoot);
@@ -582,9 +594,11 @@ export class AgentHarnessRuntime {
582
594
  this.closed = true;
583
595
  await this.healthMonitor?.stop();
584
596
  this.unregisterThreadMemorySync();
597
+ this.unregisterRuntimeMemorySync();
585
598
  this.unregisterMem0IngestionSync();
586
599
  await Promise.allSettled(Array.from(this.backgroundTasks));
587
600
  await this.threadMemorySync?.close();
601
+ await this.runtimeMemorySync?.close();
588
602
  await this.mem0IngestionSync?.close();
589
603
  await closeMcpClientsForWorkspace(this.workspace);
590
604
  this.initialized = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.139",
3
+ "version": "0.0.141",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",