@botbotgo/agent-harness 0.0.138 → 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 +4 -0
- package/README.zh.md +1 -0
- package/dist/api.js +8 -0
- package/dist/contracts/runtime.d.ts +30 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +13 -1
- package/dist/persistence/file-store.js +56 -4
- package/dist/persistence/sqlite-store.d.ts +17 -1
- package/dist/persistence/sqlite-store.js +133 -16
- package/dist/persistence/types.d.ts +25 -1
- package/dist/runtime/harness/events/events.d.ts +1 -1
- package/dist/runtime/harness/events/events.js +5 -0
- package/dist/runtime/harness/events/listener-runtime.d.ts +1 -0
- package/dist/runtime/harness/events/streaming.d.ts +1 -0
- package/dist/runtime/harness/events/streaming.js +1 -1
- package/dist/runtime/harness/run/inspection.d.ts +11 -0
- package/dist/runtime/harness/run/inspection.js +97 -0
- package/dist/runtime/harness/run/start-run.d.ts +8 -0
- package/dist/runtime/harness/run/start-run.js +13 -0
- package/dist/runtime/harness/run/stream-run.d.ts +8 -1
- package/dist/runtime/harness/run/stream-run.js +20 -5
- package/dist/runtime/harness/run/thread-records.js +1 -0
- 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 +15 -0
- package/dist/runtime/maintenance/runtime-record-maintenance.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,10 +135,14 @@ Boundary documents live in:
|
|
|
135
135
|
- `docs/acp-support-plan.md`
|
|
136
136
|
- `docs/recovery-policy-matrix.md`
|
|
137
137
|
- `docs/operator-timeline.md`
|
|
138
|
+
- `docs/runtime-inspection-contract.md`
|
|
138
139
|
- `docs/queue-concurrency-inspection.md`
|
|
139
140
|
- `docs/tool-execution-policy.md`
|
|
140
141
|
- `docs/feature-checklist.md`
|
|
141
142
|
- `docs/long-term-memory.md`
|
|
143
|
+
- `docs/memory-runtime-design.md`
|
|
144
|
+
- `docs/memory-runtime-usage.md`
|
|
145
|
+
- `docs/memory-policy-reference.md`
|
|
142
146
|
- `docs/app-task-pattern.md`
|
|
143
147
|
- `docs/model-layering.md`
|
|
144
148
|
- `docs/coding-agent-guide.md`
|
package/README.zh.md
CHANGED
|
@@ -135,6 +135,7 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正变
|
|
|
135
135
|
- `docs/acp-support-plan.md`
|
|
136
136
|
- `docs/recovery-policy-matrix.md`
|
|
137
137
|
- `docs/operator-timeline.md`
|
|
138
|
+
- `docs/runtime-inspection-contract.md`
|
|
138
139
|
- `docs/queue-concurrency-inspection.md`
|
|
139
140
|
- `docs/tool-execution-policy.md`
|
|
140
141
|
- `docs/feature-checklist.md`
|
package/dist/api.js
CHANGED
|
@@ -10,6 +10,7 @@ function toSessionSummary(summary) {
|
|
|
10
10
|
createdAt: summary.createdAt,
|
|
11
11
|
updatedAt: summary.updatedAt,
|
|
12
12
|
status: summary.status,
|
|
13
|
+
currentAgentId: summary.currentAgentId,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
function toRequestSummary(summary) {
|
|
@@ -24,12 +25,19 @@ function toRequestSummary(summary) {
|
|
|
24
25
|
state: summary.state,
|
|
25
26
|
checkpointRef: summary.checkpointRef,
|
|
26
27
|
resumable: summary.resumable,
|
|
28
|
+
startedAt: summary.startedAt,
|
|
29
|
+
endedAt: summary.endedAt,
|
|
30
|
+
lastActivityAt: summary.lastActivityAt,
|
|
31
|
+
currentAgentId: summary.currentAgentId,
|
|
32
|
+
delegationChain: summary.delegationChain,
|
|
33
|
+
runtimeSnapshot: summary.runtimeSnapshot,
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
function toSessionRecord(record) {
|
|
30
37
|
return {
|
|
31
38
|
sessionId: record.threadId,
|
|
32
39
|
entryAgentId: record.entryAgentId,
|
|
40
|
+
currentAgentId: record.currentAgentId,
|
|
33
41
|
currentState: record.currentState,
|
|
34
42
|
latestRequestId: record.latestRunId,
|
|
35
43
|
createdAt: record.createdAt,
|
|
@@ -12,6 +12,7 @@ export type ThreadSummary = {
|
|
|
12
12
|
createdAt: string;
|
|
13
13
|
updatedAt: string;
|
|
14
14
|
status: RunState;
|
|
15
|
+
currentAgentId?: string;
|
|
15
16
|
};
|
|
16
17
|
export type SessionSummary = Omit<ThreadSummary, "threadId" | "latestRunId"> & {
|
|
17
18
|
sessionId: string;
|
|
@@ -74,6 +75,28 @@ export type RuntimeToolExecutionToolPolicy = {
|
|
|
74
75
|
hasInputSchema: boolean;
|
|
75
76
|
requiresApproval: boolean;
|
|
76
77
|
};
|
|
78
|
+
export type RuntimeSnapshotModel = {
|
|
79
|
+
id: string;
|
|
80
|
+
provider: string;
|
|
81
|
+
model: string;
|
|
82
|
+
baseUrl?: string;
|
|
83
|
+
};
|
|
84
|
+
export type RuntimeSnapshotTool = {
|
|
85
|
+
name: string;
|
|
86
|
+
description: string;
|
|
87
|
+
};
|
|
88
|
+
export type RuntimeSnapshotSkill = {
|
|
89
|
+
name: string;
|
|
90
|
+
path: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
};
|
|
93
|
+
export type RuntimeSnapshot = {
|
|
94
|
+
agentId: string;
|
|
95
|
+
model?: RuntimeSnapshotModel;
|
|
96
|
+
tools: RuntimeSnapshotTool[];
|
|
97
|
+
skills: RuntimeSnapshotSkill[];
|
|
98
|
+
memory: string[];
|
|
99
|
+
};
|
|
77
100
|
/**
|
|
78
101
|
* Operator-facing projection of tool execution policy already compiled into a binding.
|
|
79
102
|
* This summarizes existing timeout, retry, validation, and retry-safety hints without
|
|
@@ -228,6 +251,12 @@ export type ThreadRunRecord = {
|
|
|
228
251
|
state: RunState;
|
|
229
252
|
checkpointRef: string | null;
|
|
230
253
|
resumable: boolean;
|
|
254
|
+
startedAt: string;
|
|
255
|
+
endedAt?: string;
|
|
256
|
+
lastActivityAt: string;
|
|
257
|
+
currentAgentId?: string;
|
|
258
|
+
delegationChain: string[];
|
|
259
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
231
260
|
};
|
|
232
261
|
/**
|
|
233
262
|
* Persisted run summary projected from upstream execution state plus runtime lifecycle metadata.
|
|
@@ -246,6 +275,7 @@ export type RequestRecord = RequestSummary;
|
|
|
246
275
|
export type ThreadRecord = {
|
|
247
276
|
threadId: string;
|
|
248
277
|
entryAgentId: string;
|
|
278
|
+
currentAgentId?: string;
|
|
249
279
|
currentState: RunState;
|
|
250
280
|
latestRunId: string;
|
|
251
281
|
createdAt: string;
|
|
@@ -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";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, RunSummary, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
|
|
2
|
-
import type { ApprovalFilter, PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, RunSummaryFilter, ThreadSummaryFilter, PersistenceThreadMeta as ThreadMeta } from "./types.js";
|
|
2
|
+
import type { ApprovalFilter, PersistedRunRequest, PersistedRunControlRecord, PersistedRunInspection, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, RunSummaryFilter, ThreadSummaryFilter, PersistenceThreadMeta as ThreadMeta } from "./types.js";
|
|
3
3
|
type RunIndexRecord = {
|
|
4
4
|
runId: string;
|
|
5
5
|
threadId: string;
|
|
@@ -32,6 +32,10 @@ export declare class FilePersistence implements RuntimePersistence {
|
|
|
32
32
|
executionMode: string;
|
|
33
33
|
adapterKind?: string;
|
|
34
34
|
createdAt: string;
|
|
35
|
+
startedAt?: string;
|
|
36
|
+
currentAgentId?: string;
|
|
37
|
+
delegationChain?: string[];
|
|
38
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
35
39
|
}): Promise<void>;
|
|
36
40
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
37
41
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -49,6 +53,14 @@ export declare class FilePersistence implements RuntimePersistence {
|
|
|
49
53
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
50
54
|
getRunMeta(threadId: string, runId: string): Promise<RunMeta>;
|
|
51
55
|
getRunLifecycle(threadId: string, runId: string): Promise<Lifecycle>;
|
|
56
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
57
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
58
|
+
endedAt?: string | null;
|
|
59
|
+
lastActivityAt?: string;
|
|
60
|
+
currentAgentId?: string | null;
|
|
61
|
+
delegationChain?: string[];
|
|
62
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"] | null;
|
|
63
|
+
}): Promise<void>;
|
|
52
64
|
deleteThread(threadId: string): Promise<boolean>;
|
|
53
65
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
54
66
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -84,9 +84,18 @@ export class FilePersistence {
|
|
|
84
84
|
resumable: false,
|
|
85
85
|
checkpointRef: null,
|
|
86
86
|
};
|
|
87
|
+
const inspection = {
|
|
88
|
+
startedAt: input.startedAt ?? input.createdAt,
|
|
89
|
+
endedAt: null,
|
|
90
|
+
lastActivityAt: input.createdAt,
|
|
91
|
+
currentAgentId: input.currentAgentId ?? input.agentId,
|
|
92
|
+
delegationChain: input.delegationChain ?? [input.currentAgentId ?? input.agentId],
|
|
93
|
+
runtimeSnapshot: input.runtimeSnapshot ?? null,
|
|
94
|
+
};
|
|
87
95
|
await Promise.all([
|
|
88
96
|
writeJson(path.join(runDir, "meta.json"), meta),
|
|
89
97
|
writeJson(path.join(runDir, "lifecycle.json"), lifecycle),
|
|
98
|
+
writeJson(path.join(runDir, "inspection.json"), inspection),
|
|
90
99
|
writeJson(path.join(runDir, "checkpoint-ref.json"), {
|
|
91
100
|
threadId: input.threadId,
|
|
92
101
|
runId: input.runId,
|
|
@@ -124,9 +133,11 @@ export class FilePersistence {
|
|
|
124
133
|
async setRunState(threadId, runId, state, checkpointRef) {
|
|
125
134
|
const lifecyclePath = path.join(this.runDir(threadId, runId), "lifecycle.json");
|
|
126
135
|
const runMetaPath = path.join(this.runDir(threadId, runId), "meta.json");
|
|
127
|
-
const
|
|
136
|
+
const inspectionPath = path.join(this.runDir(threadId, runId), "inspection.json");
|
|
137
|
+
const [lifecycle, runMeta, inspection] = await Promise.all([
|
|
128
138
|
readJson(lifecyclePath),
|
|
129
139
|
readJson(runMetaPath),
|
|
140
|
+
readJson(inspectionPath),
|
|
130
141
|
]);
|
|
131
142
|
const now = nowIso();
|
|
132
143
|
const next = {
|
|
@@ -143,6 +154,11 @@ export class FilePersistence {
|
|
|
143
154
|
...runMeta,
|
|
144
155
|
updatedAt: now,
|
|
145
156
|
}),
|
|
157
|
+
writeJson(inspectionPath, {
|
|
158
|
+
...inspection,
|
|
159
|
+
endedAt: state === "completed" || state === "failed" || state === "cancelled" ? now : inspection.endedAt,
|
|
160
|
+
lastActivityAt: now,
|
|
161
|
+
}),
|
|
146
162
|
]);
|
|
147
163
|
if (checkpointRef !== undefined) {
|
|
148
164
|
await writeJson(path.join(this.runDir(threadId, runId), "checkpoint-ref.json"), {
|
|
@@ -175,7 +191,15 @@ export class FilePersistence {
|
|
|
175
191
|
}
|
|
176
192
|
async appendEvent(event) {
|
|
177
193
|
const sequenceId = String(event.sequence).padStart(6, "0");
|
|
178
|
-
|
|
194
|
+
const inspectionPath = path.join(this.runDir(event.threadId, event.runId), "inspection.json");
|
|
195
|
+
const inspection = await readJson(inspectionPath);
|
|
196
|
+
await Promise.all([
|
|
197
|
+
writeJson(path.join(this.runDir(event.threadId, event.runId), "events", `${sequenceId}.json`), event),
|
|
198
|
+
writeJson(inspectionPath, {
|
|
199
|
+
...inspection,
|
|
200
|
+
lastActivityAt: event.timestamp,
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
179
203
|
}
|
|
180
204
|
async listSessions(filter = {}) {
|
|
181
205
|
const threadIndexDir = path.join(this.runRoot, "indexes", "threads");
|
|
@@ -185,7 +209,10 @@ export class FilePersistence {
|
|
|
185
209
|
const entries = await readdir(threadIndexDir);
|
|
186
210
|
const records = await Promise.all(entries.map(async (entry) => {
|
|
187
211
|
const index = await readJson(path.join(threadIndexDir, entry));
|
|
188
|
-
const meta = await
|
|
212
|
+
const [meta, runInspection] = await Promise.all([
|
|
213
|
+
readJson(path.join(this.threadDir(index.threadId), "meta.json")),
|
|
214
|
+
readJson(path.join(this.runDir(index.threadId, index.latestRunId), "inspection.json")).catch(() => null),
|
|
215
|
+
]);
|
|
189
216
|
return {
|
|
190
217
|
agentId: meta.entryAgentId,
|
|
191
218
|
threadId: index.threadId,
|
|
@@ -193,6 +220,7 @@ export class FilePersistence {
|
|
|
193
220
|
createdAt: meta.createdAt,
|
|
194
221
|
updatedAt: index.updatedAt,
|
|
195
222
|
status: index.status,
|
|
223
|
+
currentAgentId: runInspection?.currentAgentId ?? undefined,
|
|
196
224
|
};
|
|
197
225
|
}));
|
|
198
226
|
return records
|
|
@@ -209,9 +237,10 @@ export class FilePersistence {
|
|
|
209
237
|
}
|
|
210
238
|
async readRunSummary(threadId, runId) {
|
|
211
239
|
const runDir = this.runDir(threadId, runId);
|
|
212
|
-
const [meta, lifecycle] = await Promise.all([
|
|
240
|
+
const [meta, lifecycle, inspection] = await Promise.all([
|
|
213
241
|
readJson(path.join(runDir, "meta.json")),
|
|
214
242
|
readJson(path.join(runDir, "lifecycle.json")),
|
|
243
|
+
readJson(path.join(runDir, "inspection.json")),
|
|
215
244
|
]);
|
|
216
245
|
return {
|
|
217
246
|
runId: meta.runId,
|
|
@@ -224,6 +253,12 @@ export class FilePersistence {
|
|
|
224
253
|
state: lifecycle.state,
|
|
225
254
|
checkpointRef: lifecycle.checkpointRef,
|
|
226
255
|
resumable: lifecycle.resumable,
|
|
256
|
+
startedAt: inspection.startedAt,
|
|
257
|
+
...(inspection.endedAt ? { endedAt: inspection.endedAt } : {}),
|
|
258
|
+
lastActivityAt: inspection.lastActivityAt,
|
|
259
|
+
...(inspection.currentAgentId ? { currentAgentId: inspection.currentAgentId } : {}),
|
|
260
|
+
delegationChain: inspection.delegationChain,
|
|
261
|
+
...(inspection.runtimeSnapshot ? { runtimeSnapshot: inspection.runtimeSnapshot } : {}),
|
|
227
262
|
};
|
|
228
263
|
}
|
|
229
264
|
async listRuns(filter = {}) {
|
|
@@ -261,6 +296,7 @@ export class FilePersistence {
|
|
|
261
296
|
readJson(filePath),
|
|
262
297
|
readJson(path.join(this.threadDir(threadId), "meta.json")),
|
|
263
298
|
]);
|
|
299
|
+
const runInspection = await readJson(path.join(this.runDir(threadId, index.latestRunId), "inspection.json")).catch(() => null);
|
|
264
300
|
return {
|
|
265
301
|
agentId: meta.entryAgentId,
|
|
266
302
|
threadId,
|
|
@@ -268,6 +304,7 @@ export class FilePersistence {
|
|
|
268
304
|
createdAt: meta.createdAt,
|
|
269
305
|
updatedAt: index.updatedAt,
|
|
270
306
|
status: index.status,
|
|
307
|
+
currentAgentId: runInspection?.currentAgentId ?? undefined,
|
|
271
308
|
};
|
|
272
309
|
}
|
|
273
310
|
async getThreadMeta(threadId) {
|
|
@@ -335,6 +372,21 @@ export class FilePersistence {
|
|
|
335
372
|
async getRunLifecycle(threadId, runId) {
|
|
336
373
|
return readJson(path.join(this.runDir(threadId, runId), "lifecycle.json"));
|
|
337
374
|
}
|
|
375
|
+
async getRunInspection(threadId, runId) {
|
|
376
|
+
return readJson(path.join(this.runDir(threadId, runId), "inspection.json"));
|
|
377
|
+
}
|
|
378
|
+
async updateRunInspection(threadId, runId, patch) {
|
|
379
|
+
const inspectionPath = path.join(this.runDir(threadId, runId), "inspection.json");
|
|
380
|
+
const current = await readJson(inspectionPath);
|
|
381
|
+
await writeJson(inspectionPath, {
|
|
382
|
+
...current,
|
|
383
|
+
...(patch.endedAt !== undefined ? { endedAt: patch.endedAt } : {}),
|
|
384
|
+
...(patch.lastActivityAt ? { lastActivityAt: patch.lastActivityAt } : {}),
|
|
385
|
+
...(patch.currentAgentId !== undefined ? { currentAgentId: patch.currentAgentId } : {}),
|
|
386
|
+
...(patch.delegationChain ? { delegationChain: patch.delegationChain } : {}),
|
|
387
|
+
...(patch.runtimeSnapshot !== undefined ? { runtimeSnapshot: patch.runtimeSnapshot } : {}),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
338
390
|
async deleteThread(threadId) {
|
|
339
391
|
const threadDir = this.threadDir(threadId);
|
|
340
392
|
const threadIndexPath = this.threadIndexPath(threadId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
2
|
-
import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence, ApprovalFilter, RunSummaryFilter, ThreadSummaryFilter } from "./types.js";
|
|
2
|
+
import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunInspection, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence, ApprovalFilter, RunSummaryFilter, ThreadSummaryFilter } from "./types.js";
|
|
3
3
|
export declare function listProtectedCheckpointThreadIds(dbPath: string): Promise<Set<string>>;
|
|
4
4
|
export declare class SqlitePersistence implements RuntimePersistence {
|
|
5
5
|
private readonly runRoot;
|
|
@@ -43,6 +43,10 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
43
43
|
userMessage: TranscriptMessage;
|
|
44
44
|
runRequest: PersistedRunRequest;
|
|
45
45
|
createThread: boolean;
|
|
46
|
+
startedAt?: string;
|
|
47
|
+
currentAgentId?: string;
|
|
48
|
+
delegationChain?: string[];
|
|
49
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
46
50
|
}): Promise<void>;
|
|
47
51
|
createRun(input: {
|
|
48
52
|
threadId: string;
|
|
@@ -51,6 +55,10 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
51
55
|
executionMode: string;
|
|
52
56
|
adapterKind?: string;
|
|
53
57
|
createdAt: string;
|
|
58
|
+
startedAt?: string;
|
|
59
|
+
currentAgentId?: string;
|
|
60
|
+
delegationChain?: string[];
|
|
61
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
54
62
|
}): Promise<void>;
|
|
55
63
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
56
64
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -66,6 +74,14 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
66
74
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
67
75
|
getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
|
|
68
76
|
getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
|
|
77
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
78
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
79
|
+
endedAt?: string | null;
|
|
80
|
+
lastActivityAt?: string;
|
|
81
|
+
currentAgentId?: string | null;
|
|
82
|
+
delegationChain?: string[];
|
|
83
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"] | null;
|
|
84
|
+
}): Promise<void>;
|
|
69
85
|
deleteThread(threadId: string): Promise<boolean>;
|
|
70
86
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
71
87
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -4,7 +4,7 @@ import { createClient } from "@libsql/client";
|
|
|
4
4
|
import { fileExists, readJson, writeJson } from "../utils/fs.js";
|
|
5
5
|
import { SqliteRunContextStore } from "./sqlite-run-context-store.js";
|
|
6
6
|
import { SqliteRunQueueStore } from "./sqlite-run-queue-store.js";
|
|
7
|
-
const RUNTIME_SQLITE_SCHEMA_VERSION =
|
|
7
|
+
const RUNTIME_SQLITE_SCHEMA_VERSION = 3;
|
|
8
8
|
const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
|
|
9
9
|
function asRow(value) {
|
|
10
10
|
return value;
|
|
@@ -143,10 +143,25 @@ export class SqlitePersistence {
|
|
|
143
143
|
checkpoint_ref TEXT,
|
|
144
144
|
FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
|
|
145
145
|
)
|
|
146
|
+
`);
|
|
147
|
+
await this.rawExecute(`
|
|
148
|
+
CREATE TABLE IF NOT EXISTS run_inspection (
|
|
149
|
+
run_id TEXT PRIMARY KEY,
|
|
150
|
+
thread_id TEXT NOT NULL,
|
|
151
|
+
started_at TEXT NOT NULL,
|
|
152
|
+
ended_at TEXT,
|
|
153
|
+
last_activity_at TEXT NOT NULL,
|
|
154
|
+
current_agent_id TEXT,
|
|
155
|
+
delegation_chain_json TEXT NOT NULL,
|
|
156
|
+
runtime_snapshot_json TEXT,
|
|
157
|
+
FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
|
|
158
|
+
FOREIGN KEY (run_id) REFERENCES runs(run_id)
|
|
159
|
+
)
|
|
146
160
|
`);
|
|
147
161
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_updated_idx ON runs(thread_id, updated_at DESC)");
|
|
148
162
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_state_updated_idx ON runs(state, updated_at DESC)");
|
|
149
163
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_agent_updated_idx ON runs(agent_id, updated_at DESC)");
|
|
164
|
+
await this.rawExecute("CREATE INDEX IF NOT EXISTS run_inspection_thread_activity_idx ON run_inspection(thread_id, last_activity_at DESC)");
|
|
150
165
|
await this.rawExecute(`
|
|
151
166
|
CREATE TABLE IF NOT EXISTS thread_messages (
|
|
152
167
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -323,9 +338,11 @@ export class SqlitePersistence {
|
|
|
323
338
|
createdAt: asString(row.created_at),
|
|
324
339
|
updatedAt: asString(row.updated_at),
|
|
325
340
|
status: asString(row.status),
|
|
341
|
+
currentAgentId: asNullableString(row.current_agent_id) ?? undefined,
|
|
326
342
|
};
|
|
327
343
|
}
|
|
328
344
|
mapRunSummary(row) {
|
|
345
|
+
const runtimeSnapshot = row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null;
|
|
329
346
|
return {
|
|
330
347
|
runId: asString(row.run_id),
|
|
331
348
|
threadId: asString(row.thread_id),
|
|
@@ -337,6 +354,12 @@ export class SqlitePersistence {
|
|
|
337
354
|
state: asString(row.state),
|
|
338
355
|
checkpointRef: asNullableString(row.checkpoint_ref),
|
|
339
356
|
resumable: asBoolean(row.resumable),
|
|
357
|
+
startedAt: asNullableString(row.started_at) ?? asString(row.created_at),
|
|
358
|
+
endedAt: asNullableString(row.ended_at) ?? undefined,
|
|
359
|
+
lastActivityAt: asNullableString(row.last_activity_at) ?? asString(row.updated_at),
|
|
360
|
+
currentAgentId: asNullableString(row.current_agent_id) ?? undefined,
|
|
361
|
+
delegationChain: row.delegation_chain_json ? parseJson(row.delegation_chain_json) : [asString(row.agent_id)],
|
|
362
|
+
...(runtimeSnapshot ? { runtimeSnapshot } : {}),
|
|
340
363
|
};
|
|
341
364
|
}
|
|
342
365
|
mapApproval(row) {
|
|
@@ -372,7 +395,27 @@ export class SqlitePersistence {
|
|
|
372
395
|
if (family !== RUNTIME_SQLITE_SCHEMA_FAMILY) {
|
|
373
396
|
throw new Error(`Unsupported runtime sqlite schema family ${JSON.stringify(family)} in ${this.dbPath}. Expected ${JSON.stringify(RUNTIME_SQLITE_SCHEMA_FAMILY)}.`);
|
|
374
397
|
}
|
|
375
|
-
if (version === "1") {
|
|
398
|
+
if (version === "1" || version === "2") {
|
|
399
|
+
await this.rawExecute(`
|
|
400
|
+
CREATE TABLE IF NOT EXISTS run_inspection (
|
|
401
|
+
run_id TEXT PRIMARY KEY,
|
|
402
|
+
thread_id TEXT NOT NULL,
|
|
403
|
+
started_at TEXT NOT NULL,
|
|
404
|
+
ended_at TEXT,
|
|
405
|
+
last_activity_at TEXT NOT NULL,
|
|
406
|
+
current_agent_id TEXT,
|
|
407
|
+
delegation_chain_json TEXT NOT NULL,
|
|
408
|
+
runtime_snapshot_json TEXT,
|
|
409
|
+
FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
|
|
410
|
+
FOREIGN KEY (run_id) REFERENCES runs(run_id)
|
|
411
|
+
)
|
|
412
|
+
`);
|
|
413
|
+
await this.rawExecute(`
|
|
414
|
+
INSERT OR IGNORE INTO run_inspection
|
|
415
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
416
|
+
SELECT run_id, thread_id, created_at, NULL, updated_at, agent_id, json_array(agent_id), NULL
|
|
417
|
+
FROM runs
|
|
418
|
+
`);
|
|
376
419
|
await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
|
|
377
420
|
return;
|
|
378
421
|
}
|
|
@@ -422,6 +465,19 @@ export class SqlitePersistence {
|
|
|
422
465
|
(run_id, cancel_requested, cancel_reason, cancel_requested_at, heartbeat_at, worker_id, worker_started_at)
|
|
423
466
|
VALUES (?, 0, NULL, NULL, NULL, NULL, NULL)`,
|
|
424
467
|
args: [input.runId],
|
|
468
|
+
}, {
|
|
469
|
+
sql: `INSERT OR REPLACE INTO run_inspection
|
|
470
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
471
|
+
VALUES (?, ?, ?, NULL, ?, ?, ?, ?)`,
|
|
472
|
+
args: [
|
|
473
|
+
input.runId,
|
|
474
|
+
input.threadId,
|
|
475
|
+
input.startedAt ?? input.createdAt,
|
|
476
|
+
input.createdAt,
|
|
477
|
+
input.currentAgentId ?? input.agentId,
|
|
478
|
+
JSON.stringify(input.delegationChain ?? [input.currentAgentId ?? input.agentId]),
|
|
479
|
+
input.runtimeSnapshot ? JSON.stringify(input.runtimeSnapshot) : null,
|
|
480
|
+
],
|
|
425
481
|
}, {
|
|
426
482
|
sql: `INSERT INTO thread_messages
|
|
427
483
|
(thread_id, role, content_json, run_id, created_at)
|
|
@@ -463,6 +519,17 @@ export class SqlitePersistence {
|
|
|
463
519
|
await this.execute(`INSERT OR REPLACE INTO run_control
|
|
464
520
|
(run_id, cancel_requested, cancel_reason, cancel_requested_at, heartbeat_at, worker_id, worker_started_at)
|
|
465
521
|
VALUES (?, 0, NULL, NULL, NULL, NULL, NULL)`, [input.runId]);
|
|
522
|
+
await this.execute(`INSERT OR REPLACE INTO run_inspection
|
|
523
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
524
|
+
VALUES (?, ?, ?, NULL, ?, ?, ?, ?)`, [
|
|
525
|
+
input.runId,
|
|
526
|
+
input.threadId,
|
|
527
|
+
input.startedAt ?? input.createdAt,
|
|
528
|
+
input.createdAt,
|
|
529
|
+
input.currentAgentId ?? input.agentId,
|
|
530
|
+
JSON.stringify(input.delegationChain ?? [input.currentAgentId ?? input.agentId]),
|
|
531
|
+
input.runtimeSnapshot ? JSON.stringify(input.runtimeSnapshot) : null,
|
|
532
|
+
]);
|
|
466
533
|
}
|
|
467
534
|
async setRunState(threadId, runId, state, checkpointRef) {
|
|
468
535
|
const current = await this.getRunLifecycle(threadId, runId);
|
|
@@ -475,42 +542,59 @@ export class SqlitePersistence {
|
|
|
475
542
|
await this.execute(`UPDATE threads
|
|
476
543
|
SET status = ?, latest_run_id = ?, updated_at = ?
|
|
477
544
|
WHERE thread_id = ?`, [state, runId, now, threadId]);
|
|
545
|
+
await this.execute(`UPDATE run_inspection
|
|
546
|
+
SET last_activity_at = ?, ended_at = CASE
|
|
547
|
+
WHEN ? IN ('completed', 'failed', 'cancelled') THEN ?
|
|
548
|
+
ELSE ended_at
|
|
549
|
+
END
|
|
550
|
+
WHERE run_id = ? AND thread_id = ?`, [now, state, now, runId, threadId]);
|
|
478
551
|
}
|
|
479
552
|
async appendEvent(event) {
|
|
480
553
|
await this.execute(`INSERT OR REPLACE INTO events
|
|
481
554
|
(thread_id, run_id, sequence, event_json, created_at)
|
|
482
555
|
VALUES (?, ?, ?, ?, ?)`, [event.threadId, event.runId, event.sequence, JSON.stringify(event), event.timestamp]);
|
|
556
|
+
await this.execute(`UPDATE run_inspection
|
|
557
|
+
SET last_activity_at = ?
|
|
558
|
+
WHERE run_id = ? AND thread_id = ?`, [event.timestamp, event.runId, event.threadId]);
|
|
483
559
|
}
|
|
484
560
|
async listSessions(filter = {}) {
|
|
485
561
|
const { clause, args } = buildWhereClause([
|
|
486
|
-
["entry_agent_id = ?", filter.agentId],
|
|
562
|
+
["threads.entry_agent_id = ?", filter.agentId],
|
|
487
563
|
]);
|
|
488
|
-
const rows = await this.selectAll(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
|
|
489
|
-
FROM threads
|
|
564
|
+
const rows = await this.selectAll(`SELECT threads.thread_id, threads.entry_agent_id, threads.latest_run_id, threads.created_at, threads.updated_at, threads.status, run_inspection.current_agent_id
|
|
565
|
+
FROM threads
|
|
566
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = threads.latest_run_id
|
|
567
|
+
${clause}
|
|
490
568
|
ORDER BY updated_at DESC`, args);
|
|
491
569
|
return rows.map((row) => this.mapThreadSummary(row));
|
|
492
570
|
}
|
|
493
571
|
async listRuns(filter = {}) {
|
|
494
572
|
const { clause, args } = buildWhereClause([
|
|
495
|
-
["agent_id = ?", filter.agentId],
|
|
496
|
-
["thread_id = ?", filter.threadId],
|
|
497
|
-
["state = ?", filter.state],
|
|
573
|
+
["runs.agent_id = ?", filter.agentId],
|
|
574
|
+
["runs.thread_id = ?", filter.threadId],
|
|
575
|
+
["runs.state = ?", filter.state],
|
|
498
576
|
]);
|
|
499
|
-
const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
500
|
-
|
|
577
|
+
const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
578
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
579
|
+
FROM runs
|
|
580
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
581
|
+
${clause}
|
|
501
582
|
ORDER BY updated_at DESC`, args);
|
|
502
583
|
return rows.map((row) => this.mapRunSummary(row));
|
|
503
584
|
}
|
|
504
585
|
async getRun(runId) {
|
|
505
|
-
const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
586
|
+
const row = await this.selectOne(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
587
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
506
588
|
FROM runs
|
|
507
|
-
|
|
589
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
590
|
+
WHERE runs.run_id = ?`, [runId]);
|
|
508
591
|
return row ? this.mapRunSummary(row) : null;
|
|
509
592
|
}
|
|
510
593
|
async getSession(threadId) {
|
|
511
|
-
const row = await this.selectOne(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
|
|
594
|
+
const row = await this.selectOne(`SELECT threads.thread_id, threads.entry_agent_id, threads.latest_run_id, threads.created_at, threads.updated_at, threads.status, run_inspection.current_agent_id
|
|
512
595
|
FROM threads
|
|
513
|
-
|
|
596
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = threads.latest_run_id
|
|
597
|
+
WHERE threads.thread_id = ?`, [threadId]);
|
|
514
598
|
return row ? this.mapThreadSummary(row) : null;
|
|
515
599
|
}
|
|
516
600
|
async getThreadMeta(threadId) {
|
|
@@ -529,9 +613,11 @@ export class SqlitePersistence {
|
|
|
529
613
|
};
|
|
530
614
|
}
|
|
531
615
|
async listThreadRuns(threadId) {
|
|
532
|
-
const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
616
|
+
const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
617
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
533
618
|
FROM runs
|
|
534
|
-
|
|
619
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
620
|
+
WHERE runs.thread_id = ?
|
|
535
621
|
ORDER BY created_at DESC`, [threadId]);
|
|
536
622
|
return rows.map((row) => this.mapRunSummary(row));
|
|
537
623
|
}
|
|
@@ -592,6 +678,36 @@ export class SqlitePersistence {
|
|
|
592
678
|
checkpointRef: asNullableString(row.checkpoint_ref),
|
|
593
679
|
};
|
|
594
680
|
}
|
|
681
|
+
async getRunInspection(threadId, runId) {
|
|
682
|
+
const row = await this.selectOne(`SELECT started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json
|
|
683
|
+
FROM run_inspection
|
|
684
|
+
WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
|
|
685
|
+
if (!row) {
|
|
686
|
+
throw new Error(`Missing run inspection ${runId} for thread ${threadId}`);
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
startedAt: asString(row.started_at),
|
|
690
|
+
endedAt: asNullableString(row.ended_at),
|
|
691
|
+
lastActivityAt: asString(row.last_activity_at),
|
|
692
|
+
currentAgentId: asNullableString(row.current_agent_id),
|
|
693
|
+
delegationChain: parseJson(row.delegation_chain_json),
|
|
694
|
+
runtimeSnapshot: row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
async updateRunInspection(threadId, runId, patch) {
|
|
698
|
+
const current = await this.getRunInspection(threadId, runId);
|
|
699
|
+
await this.execute(`UPDATE run_inspection
|
|
700
|
+
SET ended_at = ?, last_activity_at = ?, current_agent_id = ?, delegation_chain_json = ?, runtime_snapshot_json = ?
|
|
701
|
+
WHERE run_id = ? AND thread_id = ?`, [
|
|
702
|
+
patch.endedAt === undefined ? current.endedAt : patch.endedAt,
|
|
703
|
+
patch.lastActivityAt ?? current.lastActivityAt,
|
|
704
|
+
patch.currentAgentId === undefined ? current.currentAgentId : patch.currentAgentId,
|
|
705
|
+
JSON.stringify(patch.delegationChain ?? current.delegationChain),
|
|
706
|
+
JSON.stringify(patch.runtimeSnapshot === undefined ? current.runtimeSnapshot : patch.runtimeSnapshot),
|
|
707
|
+
runId,
|
|
708
|
+
threadId,
|
|
709
|
+
]);
|
|
710
|
+
}
|
|
595
711
|
async deleteThread(threadId) {
|
|
596
712
|
const exists = await this.getSession(threadId);
|
|
597
713
|
if (!exists) {
|
|
@@ -600,6 +716,7 @@ export class SqlitePersistence {
|
|
|
600
716
|
await this.execute("DELETE FROM artifacts WHERE thread_id = ?", [threadId]);
|
|
601
717
|
await this.execute("DELETE FROM approvals WHERE thread_id = ?", [threadId]);
|
|
602
718
|
await this.execute("DELETE FROM events WHERE thread_id = ?", [threadId]);
|
|
719
|
+
await this.execute("DELETE FROM run_inspection WHERE thread_id = ?", [threadId]);
|
|
603
720
|
await this.execute("DELETE FROM run_queue WHERE thread_id = ?", [threadId]);
|
|
604
721
|
await this.execute("DELETE FROM run_requests WHERE thread_id = ?", [threadId]);
|
|
605
722
|
await this.execute("DELETE FROM recovery_intents WHERE thread_id = ?", [threadId]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
1
|
+
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RuntimeSnapshot, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
2
2
|
export type PersistenceThreadMeta = {
|
|
3
3
|
threadId: string;
|
|
4
4
|
workspaceId: string;
|
|
@@ -25,6 +25,14 @@ export type PersistenceLifecycle = {
|
|
|
25
25
|
resumable: boolean;
|
|
26
26
|
checkpointRef: string | null;
|
|
27
27
|
};
|
|
28
|
+
export type PersistedRunInspection = {
|
|
29
|
+
startedAt: string;
|
|
30
|
+
endedAt: string | null;
|
|
31
|
+
lastActivityAt: string;
|
|
32
|
+
currentAgentId: string | null;
|
|
33
|
+
delegationChain: string[];
|
|
34
|
+
runtimeSnapshot: RuntimeSnapshot | null;
|
|
35
|
+
};
|
|
28
36
|
export type PersistedRunRequest = {
|
|
29
37
|
input: MessageContent;
|
|
30
38
|
priority?: number;
|
|
@@ -86,6 +94,10 @@ export interface RuntimePersistence {
|
|
|
86
94
|
userMessage: TranscriptMessage;
|
|
87
95
|
runRequest: PersistedRunRequest;
|
|
88
96
|
createThread: boolean;
|
|
97
|
+
startedAt?: string;
|
|
98
|
+
currentAgentId?: string;
|
|
99
|
+
delegationChain?: string[];
|
|
100
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
89
101
|
}): Promise<void>;
|
|
90
102
|
createThread(input: {
|
|
91
103
|
threadId: string;
|
|
@@ -101,6 +113,10 @@ export interface RuntimePersistence {
|
|
|
101
113
|
executionMode: string;
|
|
102
114
|
adapterKind?: string;
|
|
103
115
|
createdAt: string;
|
|
116
|
+
startedAt?: string;
|
|
117
|
+
currentAgentId?: string;
|
|
118
|
+
delegationChain?: string[];
|
|
119
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
104
120
|
}): Promise<void>;
|
|
105
121
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
106
122
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -115,6 +131,14 @@ export interface RuntimePersistence {
|
|
|
115
131
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
116
132
|
getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
|
|
117
133
|
getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
|
|
134
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
135
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
136
|
+
endedAt?: string | null;
|
|
137
|
+
lastActivityAt?: string;
|
|
138
|
+
currentAgentId?: string | null;
|
|
139
|
+
delegationChain?: string[];
|
|
140
|
+
runtimeSnapshot?: RuntimeSnapshot | null;
|
|
141
|
+
}): Promise<void>;
|
|
118
142
|
deleteThread(threadId: string): Promise<boolean>;
|
|
119
143
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
120
144
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HarnessEvent, InternalApprovalRecord, MessageContent, RunResult } from "../../../contracts/types.js";
|
|
2
2
|
import type { RuntimePersistence } from "../../../persistence/types.js";
|
|
3
|
-
type EventPersistence = Pick<RuntimePersistence, "appendEvent" | "setRunState" | "createApproval" | "createArtifact">;
|
|
3
|
+
type EventPersistence = Pick<RuntimePersistence, "appendEvent" | "setRunState" | "createApproval" | "createArtifact" | "updateRunInspection">;
|
|
4
4
|
type EventRuntime = {
|
|
5
5
|
persistence: EventPersistence;
|
|
6
6
|
publishEvent: (event: HarnessEvent) => void;
|
|
@@ -18,6 +18,11 @@ export async function emitRunCreatedEvent(runtime, threadId, runId, payload) {
|
|
|
18
18
|
}
|
|
19
19
|
export async function setRunStateAndEmitEvent(runtime, threadId, runId, sequence, state, options) {
|
|
20
20
|
await runtime.persistence.setRunState(threadId, runId, state, options.checkpointRef ?? null);
|
|
21
|
+
const now = new Date(Date.now()).toISOString();
|
|
22
|
+
await runtime.persistence.updateRunInspection(threadId, runId, {
|
|
23
|
+
lastActivityAt: now,
|
|
24
|
+
...(state === "completed" || state === "failed" || state === "cancelled" ? { endedAt: now } : {}),
|
|
25
|
+
});
|
|
21
26
|
return emitHarnessEvent(runtime, threadId, runId, sequence, "run.state.changed", {
|
|
22
27
|
previousState: options.previousState,
|
|
23
28
|
state,
|
|
@@ -68,7 +68,7 @@ export async function dispatchRunListeners(stream, listeners, options) {
|
|
|
68
68
|
currentState: thread.currentState,
|
|
69
69
|
latestRunId: thread.latestRunId,
|
|
70
70
|
entryAgentId: thread.entryAgentId,
|
|
71
|
-
latestAgentId: thread.runs[0]?.agentId,
|
|
71
|
+
latestAgentId: thread.currentAgentId ?? thread.runs[0]?.agentId,
|
|
72
72
|
approvalId: thread.pendingDecision?.approvalId,
|
|
73
73
|
pendingActionId: thread.pendingDecision?.pendingActionId,
|
|
74
74
|
output,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CompiledAgentBinding, RuntimeSnapshot } from "../../../contracts/types.js";
|
|
2
|
+
export declare function buildRunRuntimeSnapshot(binding: CompiledAgentBinding): RuntimeSnapshot;
|
|
3
|
+
export declare function consumeRunInspectionUpstreamEvent(input: {
|
|
4
|
+
event: unknown;
|
|
5
|
+
currentAgentId: string;
|
|
6
|
+
delegationChain: string[];
|
|
7
|
+
binding: CompiledAgentBinding;
|
|
8
|
+
}): {
|
|
9
|
+
currentAgentId: string;
|
|
10
|
+
delegationChain: string[];
|
|
11
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readSkillMetadata } from "../../support/skill-metadata.js";
|
|
2
|
+
import { getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, } from "../../support/compiled-binding.js";
|
|
3
|
+
function asObject(value) {
|
|
4
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function readStringArray(value) {
|
|
7
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
8
|
+
}
|
|
9
|
+
export function buildRunRuntimeSnapshot(binding) {
|
|
10
|
+
const model = getBindingPrimaryModel(binding);
|
|
11
|
+
const init = typeof model?.init === "object" && model.init ? model.init : {};
|
|
12
|
+
const baseUrl = typeof init.baseUrl === "string" && init.baseUrl.trim().length > 0
|
|
13
|
+
? init.baseUrl.trim()
|
|
14
|
+
: typeof init.baseURL === "string" && init.baseURL.trim().length > 0
|
|
15
|
+
? init.baseURL.trim()
|
|
16
|
+
: undefined;
|
|
17
|
+
return {
|
|
18
|
+
agentId: binding.agent.id,
|
|
19
|
+
...(model ? {
|
|
20
|
+
model: {
|
|
21
|
+
id: model.id,
|
|
22
|
+
provider: model.provider,
|
|
23
|
+
model: model.model,
|
|
24
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
25
|
+
},
|
|
26
|
+
} : {}),
|
|
27
|
+
tools: getBindingPrimaryTools(binding).map((tool) => ({
|
|
28
|
+
name: tool.name,
|
|
29
|
+
description: tool.description,
|
|
30
|
+
})),
|
|
31
|
+
skills: getBindingSkills(binding).map((skillPath) => {
|
|
32
|
+
const metadata = readSkillMetadata(skillPath);
|
|
33
|
+
return {
|
|
34
|
+
name: metadata.name,
|
|
35
|
+
path: skillPath,
|
|
36
|
+
...(metadata.description ? { description: metadata.description } : {}),
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
memory: getBindingMemorySources(binding),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function maybeAppendAgent(chain, agentId) {
|
|
43
|
+
if (!agentId) {
|
|
44
|
+
return chain;
|
|
45
|
+
}
|
|
46
|
+
if (chain[chain.length - 1] === agentId) {
|
|
47
|
+
return chain;
|
|
48
|
+
}
|
|
49
|
+
if (chain.includes(agentId)) {
|
|
50
|
+
return [...chain.filter((entry) => entry !== agentId), agentId];
|
|
51
|
+
}
|
|
52
|
+
return [...chain, agentId];
|
|
53
|
+
}
|
|
54
|
+
function extractSubagentFromTaskToolEvent(event) {
|
|
55
|
+
const eventName = typeof event.event === "string" ? event.event : "";
|
|
56
|
+
const toolName = typeof event.name === "string" ? event.name : "";
|
|
57
|
+
if (toolName !== "task" || (eventName !== "on_tool_start" && !(eventName === "on_chain_start" && event.run_type === "tool"))) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const data = asObject(event.data);
|
|
61
|
+
const input = asObject(data?.input);
|
|
62
|
+
const subagentType = typeof input?.subagent_type === "string"
|
|
63
|
+
? input.subagent_type
|
|
64
|
+
: typeof input?.subagentType === "string"
|
|
65
|
+
? input.subagentType
|
|
66
|
+
: null;
|
|
67
|
+
return subagentType && subagentType.trim().length > 0 ? subagentType.trim() : null;
|
|
68
|
+
}
|
|
69
|
+
function extractKnownSubagentFromEvent(event, knownSubagentIds) {
|
|
70
|
+
const names = [
|
|
71
|
+
typeof event.name === "string" ? event.name : "",
|
|
72
|
+
...readStringArray(event.tags),
|
|
73
|
+
...readStringArray(event.ns),
|
|
74
|
+
];
|
|
75
|
+
return names.find((name) => knownSubagentIds.has(name)) ?? null;
|
|
76
|
+
}
|
|
77
|
+
export function consumeRunInspectionUpstreamEvent(input) {
|
|
78
|
+
const typed = asObject(input.event);
|
|
79
|
+
if (!typed) {
|
|
80
|
+
return {
|
|
81
|
+
currentAgentId: input.currentAgentId,
|
|
82
|
+
delegationChain: input.delegationChain,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const knownSubagentIds = new Set(getBindingSubagents(input.binding).map((subagent) => subagent.name));
|
|
86
|
+
const delegatedAgentId = extractSubagentFromTaskToolEvent(typed) ?? extractKnownSubagentFromEvent(typed, knownSubagentIds);
|
|
87
|
+
if (!delegatedAgentId) {
|
|
88
|
+
return {
|
|
89
|
+
currentAgentId: input.currentAgentId,
|
|
90
|
+
delegationChain: input.delegationChain,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
currentAgentId: delegatedAgentId,
|
|
95
|
+
delegationChain: maybeAppendAgent(input.delegationChain, delegatedAgentId),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -20,6 +20,10 @@ type EnsureThreadStartedRuntime = {
|
|
|
20
20
|
};
|
|
21
21
|
runRequest: PersistedRunRequest;
|
|
22
22
|
createThread: boolean;
|
|
23
|
+
startedAt?: string;
|
|
24
|
+
currentAgentId?: string;
|
|
25
|
+
delegationChain?: string[];
|
|
26
|
+
runtimeSnapshot?: import("../../../contracts/types.js").RuntimeSnapshot;
|
|
23
27
|
}) => Promise<void>;
|
|
24
28
|
createThread: (input: {
|
|
25
29
|
threadId: string;
|
|
@@ -41,6 +45,10 @@ type EnsureThreadStartedRuntime = {
|
|
|
41
45
|
executionMode: string;
|
|
42
46
|
adapterKind: string;
|
|
43
47
|
createdAt: string;
|
|
48
|
+
startedAt?: string;
|
|
49
|
+
currentAgentId?: string;
|
|
50
|
+
delegationChain?: string[];
|
|
51
|
+
runtimeSnapshot?: import("../../../contracts/types.js").RuntimeSnapshot;
|
|
44
52
|
}) => Promise<void>;
|
|
45
53
|
saveRunRequest: (threadId: string, runId: string, runRequest: PersistedRunRequest) => Promise<void>;
|
|
46
54
|
};
|
|
@@ -4,12 +4,17 @@ import { normalizeMessageContent } from "../../../utils/message-content.js";
|
|
|
4
4
|
import { buildPersistedRunRequest, normalizeRunPriority } from "./helpers.js";
|
|
5
5
|
import { getRequiredWorkspaceBinding } from "../bindings.js";
|
|
6
6
|
import { getBindingAdapterKind, getBindingRuntimeExecutionMode } from "../../support/compiled-binding.js";
|
|
7
|
+
import { buildRunRuntimeSnapshot } from "./inspection.js";
|
|
7
8
|
export async function ensureThreadStarted(runtime, input) {
|
|
8
9
|
const { selectedAgentId, binding, message, runRequest, existingThreadId } = input;
|
|
9
10
|
const threadId = existingThreadId ?? createPersistentId();
|
|
10
11
|
const runId = createPersistentId();
|
|
11
12
|
const createdAt = new Date().toISOString();
|
|
12
13
|
const isNewThread = !existingThreadId;
|
|
14
|
+
const startedAt = createdAt;
|
|
15
|
+
const currentAgentId = selectedAgentId;
|
|
16
|
+
const delegationChain = [selectedAgentId];
|
|
17
|
+
const runtimeSnapshot = buildRunRuntimeSnapshot(binding);
|
|
13
18
|
const userMessage = {
|
|
14
19
|
role: "user",
|
|
15
20
|
content: normalizeMessageContent(message),
|
|
@@ -28,6 +33,10 @@ export async function ensureThreadStarted(runtime, input) {
|
|
|
28
33
|
userMessage,
|
|
29
34
|
runRequest,
|
|
30
35
|
createThread: isNewThread,
|
|
36
|
+
startedAt,
|
|
37
|
+
currentAgentId,
|
|
38
|
+
delegationChain,
|
|
39
|
+
runtimeSnapshot,
|
|
31
40
|
});
|
|
32
41
|
}
|
|
33
42
|
else {
|
|
@@ -49,6 +58,10 @@ export async function ensureThreadStarted(runtime, input) {
|
|
|
49
58
|
executionMode: getBindingRuntimeExecutionMode(binding),
|
|
50
59
|
adapterKind: getBindingAdapterKind(binding),
|
|
51
60
|
createdAt,
|
|
61
|
+
startedAt,
|
|
62
|
+
currentAgentId,
|
|
63
|
+
delegationChain,
|
|
64
|
+
runtimeSnapshot,
|
|
52
65
|
}),
|
|
53
66
|
runtime.persistence.saveRunRequest(threadId, runId, runRequest),
|
|
54
67
|
]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompiledAgentBinding, HarnessEvent, MessageContent, RunResult, TranscriptMessage } from "../../../contracts/types.js";
|
|
1
|
+
import type { CompiledAgentBinding, HarnessEvent, MessageContent, RunResult, RuntimeSnapshot, TranscriptMessage } from "../../../contracts/types.js";
|
|
2
2
|
import { type InternalHarnessStreamItem } from "../events/streaming.js";
|
|
3
3
|
type RuntimeStreamChunk = string | {
|
|
4
4
|
kind: "content" | "interrupt" | "reasoning" | "step" | "tool-result" | "upstream-event";
|
|
@@ -45,6 +45,13 @@ type StreamRunOptions = {
|
|
|
45
45
|
}>;
|
|
46
46
|
appendAssistantMessage: (threadId: string, runId: string, content?: string) => Promise<void>;
|
|
47
47
|
clearRunRequest: (threadId: string, runId: string) => Promise<void>;
|
|
48
|
+
updateRunInspection: (threadId: string, runId: string, patch: {
|
|
49
|
+
endedAt?: string | null;
|
|
50
|
+
lastActivityAt?: string;
|
|
51
|
+
currentAgentId?: string | null;
|
|
52
|
+
delegationChain?: string[];
|
|
53
|
+
runtimeSnapshot?: RuntimeSnapshot | null;
|
|
54
|
+
}) => Promise<void>;
|
|
48
55
|
emitSyntheticFallback: (threadId: string, runId: string, selectedAgentId: string, error: unknown) => Promise<void>;
|
|
49
56
|
};
|
|
50
57
|
export declare function streamHarnessRun(options: StreamRunOptions): AsyncGenerator<InternalHarnessStreamItem>;
|
|
@@ -2,6 +2,7 @@ import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from ".
|
|
|
2
2
|
import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
|
|
3
3
|
import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
|
|
4
4
|
import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
|
|
5
|
+
import { consumeRunInspectionUpstreamEvent } from "./inspection.js";
|
|
5
6
|
function normalizeStreamChunk(chunk) {
|
|
6
7
|
if (typeof chunk === "string") {
|
|
7
8
|
if (chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)) {
|
|
@@ -43,6 +44,8 @@ export async function* streamHarnessRun(options) {
|
|
|
43
44
|
let emitted = false;
|
|
44
45
|
let streamActivityObserved = false;
|
|
45
46
|
let nonUpstreamStreamActivityObserved = false;
|
|
47
|
+
let currentAgentId = options.selectedAgentId;
|
|
48
|
+
let delegationChain = [options.selectedAgentId];
|
|
46
49
|
try {
|
|
47
50
|
const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
|
|
48
51
|
priorHistoryPromise,
|
|
@@ -64,6 +67,18 @@ export async function* streamHarnessRun(options) {
|
|
|
64
67
|
streamActivityObserved = true;
|
|
65
68
|
const normalizedChunk = normalizeStreamChunk(rawChunk);
|
|
66
69
|
if (normalizedChunk.kind === "upstream-event") {
|
|
70
|
+
const inspection = consumeRunInspectionUpstreamEvent({
|
|
71
|
+
event: normalizedChunk.event,
|
|
72
|
+
currentAgentId,
|
|
73
|
+
delegationChain,
|
|
74
|
+
binding: options.binding,
|
|
75
|
+
});
|
|
76
|
+
currentAgentId = inspection.currentAgentId;
|
|
77
|
+
delegationChain = inspection.delegationChain;
|
|
78
|
+
await options.updateRunInspection(options.threadId, options.runId, {
|
|
79
|
+
currentAgentId,
|
|
80
|
+
delegationChain,
|
|
81
|
+
});
|
|
67
82
|
yield {
|
|
68
83
|
type: "upstream-event",
|
|
69
84
|
threadId: options.threadId,
|
|
@@ -88,7 +103,7 @@ export async function* streamHarnessRun(options) {
|
|
|
88
103
|
result: {
|
|
89
104
|
threadId: options.threadId,
|
|
90
105
|
runId: options.runId,
|
|
91
|
-
agentId:
|
|
106
|
+
agentId: currentAgentId,
|
|
92
107
|
state: "waiting_for_approval",
|
|
93
108
|
output: assistantOutput,
|
|
94
109
|
finalMessageText: assistantOutput,
|
|
@@ -145,7 +160,7 @@ export async function* streamHarnessRun(options) {
|
|
|
145
160
|
result: {
|
|
146
161
|
threadId: options.threadId,
|
|
147
162
|
runId: options.runId,
|
|
148
|
-
agentId:
|
|
163
|
+
agentId: currentAgentId,
|
|
149
164
|
state: "completed",
|
|
150
165
|
output: assistantOutput,
|
|
151
166
|
finalMessageText: assistantOutput,
|
|
@@ -183,7 +198,7 @@ export async function* streamHarnessRun(options) {
|
|
|
183
198
|
result: {
|
|
184
199
|
threadId: options.threadId,
|
|
185
200
|
runId: options.runId,
|
|
186
|
-
agentId:
|
|
201
|
+
agentId: currentAgentId,
|
|
187
202
|
state: "failed",
|
|
188
203
|
output: runtimeFailure,
|
|
189
204
|
finalMessageText: runtimeFailure,
|
|
@@ -232,7 +247,7 @@ export async function* streamHarnessRun(options) {
|
|
|
232
247
|
...actual,
|
|
233
248
|
threadId: options.threadId,
|
|
234
249
|
runId: options.runId,
|
|
235
|
-
agentId:
|
|
250
|
+
agentId: currentAgentId,
|
|
236
251
|
},
|
|
237
252
|
};
|
|
238
253
|
yield {
|
|
@@ -265,7 +280,7 @@ export async function* streamHarnessRun(options) {
|
|
|
265
280
|
result: {
|
|
266
281
|
threadId: options.threadId,
|
|
267
282
|
runId: options.runId,
|
|
268
|
-
agentId:
|
|
283
|
+
agentId: currentAgentId,
|
|
269
284
|
state: "failed",
|
|
270
285
|
output: runtimeFailure,
|
|
271
286
|
finalMessageText: runtimeFailure,
|
|
@@ -20,6 +20,7 @@ export async function buildThreadInspectionRecord(input, threadId) {
|
|
|
20
20
|
return {
|
|
21
21
|
threadId,
|
|
22
22
|
entryAgentId: meta.entryAgentId,
|
|
23
|
+
currentAgentId: threadSummary.currentAgentId,
|
|
23
24
|
currentState: threadSummary.status,
|
|
24
25
|
latestRunId,
|
|
25
26
|
createdAt: meta.createdAt,
|
|
@@ -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);
|
|
@@ -517,6 +529,7 @@ export class AgentHarnessRuntime {
|
|
|
517
529
|
requestApprovalAndEmit: (threadId, runId, input, interruptContent, checkpointRef, sequence) => this.requestApprovalAndEmit(threadId, runId, input, interruptContent, checkpointRef, sequence),
|
|
518
530
|
appendAssistantMessage: (threadId, runId, content) => appendLifecycleAssistantMessage(this.persistence, threadId, runId, content),
|
|
519
531
|
clearRunRequest: (threadId, runId) => this.persistence.clearRunRequest(threadId, runId),
|
|
532
|
+
updateRunInspection: (threadId, runId, patch) => this.persistence.updateRunInspection(threadId, runId, patch),
|
|
520
533
|
emitSyntheticFallback: (threadId, runId, selectedAgentId, error) => this.runtimeEventOperations.emitSyntheticFallback(threadId, runId, selectedAgentId, error),
|
|
521
534
|
});
|
|
522
535
|
for await (const item of stream) {
|
|
@@ -581,9 +594,11 @@ export class AgentHarnessRuntime {
|
|
|
581
594
|
this.closed = true;
|
|
582
595
|
await this.healthMonitor?.stop();
|
|
583
596
|
this.unregisterThreadMemorySync();
|
|
597
|
+
this.unregisterRuntimeMemorySync();
|
|
584
598
|
this.unregisterMem0IngestionSync();
|
|
585
599
|
await Promise.allSettled(Array.from(this.backgroundTasks));
|
|
586
600
|
await this.threadMemorySync?.close();
|
|
601
|
+
await this.runtimeMemorySync?.close();
|
|
587
602
|
await this.mem0IngestionSync?.close();
|
|
588
603
|
await closeMcpClientsForWorkspace(this.workspace);
|
|
589
604
|
this.initialized = false;
|
|
@@ -91,6 +91,7 @@ export async function maintainSqliteRuntimeRecords(dbPath, config, nowMs = Date.
|
|
|
91
91
|
"DELETE FROM artifacts WHERE thread_id = ?",
|
|
92
92
|
"DELETE FROM approvals WHERE thread_id = ?",
|
|
93
93
|
"DELETE FROM events WHERE thread_id = ?",
|
|
94
|
+
"DELETE FROM run_inspection WHERE thread_id = ?",
|
|
94
95
|
"DELETE FROM run_queue WHERE thread_id = ?",
|
|
95
96
|
"DELETE FROM run_requests WHERE thread_id = ?",
|
|
96
97
|
"DELETE FROM recovery_intents WHERE thread_id = ?",
|