@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 +3 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/harness/system/runtime-memory-sync.d.ts +24 -0
- package/dist/runtime/harness/system/runtime-memory-sync.js +193 -0
- package/dist/runtime/harness.d.ts +2 -0
- package/dist/runtime/harness.js +14 -0
- package/package.json +1 -1
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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.140";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
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;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -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;
|