@botbotgo/agent-harness 0.0.154 → 0.0.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ import type { CompiledAgentBinding, CompiledModel, HarnessEvent, HarnessEventProjection, InternalApprovalRecord, MemoryCandidate, MemoryRecord, MemoryScope, ThreadSummary, TranscriptMessage, WorkspaceBundle } from "../../../contracts/types.js";
2
+ import type { RuntimePersistence } from "../../../persistence/types.js";
3
+ import { type StoreLike } from "./store.js";
4
+ export type ResolvedRuntimeMemoryFormationConfig = {
5
+ enabled: true;
6
+ hotPath: {
7
+ enabled: boolean;
8
+ };
9
+ manager: {
10
+ enabled: boolean;
11
+ strategy: "rules" | "model";
12
+ modelRef?: string;
13
+ maxContextRecords: number;
14
+ };
15
+ background: {
16
+ enabled: boolean;
17
+ maxMessagesPerRun: number;
18
+ scopes: MemoryScope[];
19
+ stateStorePath: string;
20
+ writeOnApprovalResolution: boolean;
21
+ writeOnRunCompletion: boolean;
22
+ };
23
+ };
24
+ type RuntimeMemoryFormationWriter = (input: {
25
+ candidates: MemoryCandidate[];
26
+ threadId: string;
27
+ runId: string;
28
+ agentId: string;
29
+ userId?: string;
30
+ projectId?: string;
31
+ recordedAt: string;
32
+ }) => Promise<void>;
33
+ type RuntimeMemoryFormationOptions = {
34
+ userId?: string;
35
+ projectId?: string;
36
+ };
37
+ type RuntimeMemoryManagerLike = {
38
+ transform(input: {
39
+ candidates: MemoryCandidate[];
40
+ binding: CompiledAgentBinding;
41
+ threadId: string;
42
+ runId: string;
43
+ recordedAt: string;
44
+ existingRecords: MemoryRecord[];
45
+ }): Promise<MemoryCandidate[]>;
46
+ };
47
+ export declare function readRuntimeMemoryFormationConfig(runtimeMemory: Record<string, unknown> | undefined, workspaceRoot: string): ResolvedRuntimeMemoryFormationConfig | undefined;
48
+ export declare function createBackgroundMemoryCandidates(input: {
49
+ thread: ThreadSummary;
50
+ runId: string;
51
+ agentId: string;
52
+ trigger: "approval.resolved" | "run.completed";
53
+ recordedAt: string;
54
+ messages: TranscriptMessage[];
55
+ approvals: InternalApprovalRecord[];
56
+ scopes: MemoryScope[];
57
+ }): MemoryCandidate[];
58
+ export declare function runModelMemoryManager(input: {
59
+ workspace: WorkspaceBundle;
60
+ binding: CompiledAgentBinding;
61
+ model: CompiledModel;
62
+ candidates: MemoryCandidate[];
63
+ threadId: string;
64
+ runId: string;
65
+ recordedAt: string;
66
+ existingRecords: MemoryRecord[];
67
+ modelResolver?: (modelId: string) => unknown;
68
+ }): Promise<MemoryCandidate[]>;
69
+ export declare function createRuntimeMemoryManager(input: {
70
+ workspace: WorkspaceBundle;
71
+ binding: CompiledAgentBinding;
72
+ config: ResolvedRuntimeMemoryFormationConfig;
73
+ modelResolver?: (modelId: string) => unknown;
74
+ }): RuntimeMemoryManagerLike;
75
+ export declare class RuntimeMemoryFormationSync implements HarnessEventProjection {
76
+ private readonly persistence;
77
+ private readonly config;
78
+ private readonly writer;
79
+ private readonly stateStore;
80
+ private readonly options;
81
+ private readonly pending;
82
+ private syncChain;
83
+ readonly name = "runtime-memory-formation-sync";
84
+ constructor(persistence: RuntimePersistence, config: ResolvedRuntimeMemoryFormationConfig, writer: RuntimeMemoryFormationWriter, runRoot: string, stateStore?: StoreLike, options?: RuntimeMemoryFormationOptions);
85
+ shouldHandle(event: HarnessEvent): boolean;
86
+ handleEvent(event: HarnessEvent): Promise<void>;
87
+ private reflectRun;
88
+ close(): Promise<void>;
89
+ }
90
+ export {};
@@ -0,0 +1,371 @@
1
+ import path from "node:path";
2
+ import { extractMessageText } from "../../../utils/message-content.js";
3
+ import { createResolvedModel } from "../../adapter/model/model-providers.js";
4
+ import { FileBackedStore } from "./store.js";
5
+ import { compileModel } from "../../../workspace/resource-compilers.js";
6
+ import { resolveRefId } from "../../../workspace/support/workspace-ref-utils.js";
7
+ const FORMATION_EVENT_TYPES = new Set(["run.state.changed", "approval.resolved"]);
8
+ function asRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
10
+ }
11
+ function asBoolean(value) {
12
+ return typeof value === "boolean" ? value : undefined;
13
+ }
14
+ function asPositiveInteger(value) {
15
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
16
+ }
17
+ function asNonEmptyString(value) {
18
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
19
+ }
20
+ function asMemoryScopes(value) {
21
+ if (!Array.isArray(value)) {
22
+ return undefined;
23
+ }
24
+ const scopes = value
25
+ .filter((item) => typeof item === "string")
26
+ .filter((item) => ["thread", "agent", "workspace", "user", "project"].includes(item));
27
+ return scopes.length > 0 ? Array.from(new Set(scopes)) : undefined;
28
+ }
29
+ function normalizeScopeList(scopes) {
30
+ return Array.from(new Set(scopes));
31
+ }
32
+ function asStrategy(value) {
33
+ return value === "rules" || value === "model" ? value : undefined;
34
+ }
35
+ export function readRuntimeMemoryFormationConfig(runtimeMemory, workspaceRoot) {
36
+ if (runtimeMemory?.enabled !== true) {
37
+ return undefined;
38
+ }
39
+ const formation = asRecord(runtimeMemory.formation);
40
+ const hotPath = asRecord(formation?.hotPath);
41
+ const manager = asRecord(formation?.manager);
42
+ const background = asRecord(formation?.background);
43
+ const ingestion = asRecord(runtimeMemory.ingestion);
44
+ const workspaceBaseName = path.basename(workspaceRoot) || "agent-harness";
45
+ const resolved = {
46
+ enabled: true,
47
+ hotPath: {
48
+ enabled: asBoolean(hotPath?.enabled) ?? true,
49
+ },
50
+ manager: {
51
+ enabled: asBoolean(manager?.enabled) ?? true,
52
+ strategy: asStrategy(manager?.strategy) ?? "rules",
53
+ modelRef: asNonEmptyString(manager?.modelRef),
54
+ maxContextRecords: asPositiveInteger(manager?.maxContextRecords) ?? 12,
55
+ },
56
+ background: {
57
+ enabled: asBoolean(background?.enabled) ?? true,
58
+ maxMessagesPerRun: asPositiveInteger(background?.maxMessagesPerRun) ?? asPositiveInteger(ingestion?.maxMessagesPerRun) ?? 40,
59
+ scopes: normalizeScopeList(asMemoryScopes(background?.scopes) ?? ["thread"]),
60
+ stateStorePath: asNonEmptyString(background?.stateStorePath) ?? `${workspaceBaseName}-memory-formation-state.json`,
61
+ writeOnApprovalResolution: asBoolean(background?.writeOnApprovalResolution) ?? asBoolean(ingestion?.writeOnApprovalResolution) ?? true,
62
+ writeOnRunCompletion: asBoolean(background?.writeOnRunCompletion) ?? asBoolean(ingestion?.writeOnRunCompletion) ?? true,
63
+ },
64
+ };
65
+ if (!resolved.hotPath.enabled && !resolved.background.enabled) {
66
+ return undefined;
67
+ }
68
+ return resolved;
69
+ }
70
+ function excerpt(message) {
71
+ if (!message?.content) {
72
+ return undefined;
73
+ }
74
+ const normalized = extractMessageText(message.content).replace(/\s+/g, " ").trim();
75
+ if (!normalized) {
76
+ return undefined;
77
+ }
78
+ return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
79
+ }
80
+ function summarizeApprovals(approvals) {
81
+ if (approvals.length === 0) {
82
+ return "No approvals were recorded in this run.";
83
+ }
84
+ return approvals
85
+ .map((approval) => `${approval.toolName} (${approval.status})`)
86
+ .join(", ");
87
+ }
88
+ export function createBackgroundMemoryCandidates(input) {
89
+ const latestUser = excerpt(input.messages.filter((message) => message.role === "user").at(-1));
90
+ const latestAssistant = excerpt(input.messages.filter((message) => message.role === "assistant").at(-1));
91
+ const userContext = latestUser ?? "No durable user context captured.";
92
+ const assistantContext = latestAssistant ?? "No durable assistant response captured.";
93
+ const approvals = summarizeApprovals(input.approvals);
94
+ const summarySeed = latestUser ?? latestAssistant ?? `Run ${input.runId}`;
95
+ const baseSummary = summarySeed.length > 120 ? `${summarySeed.slice(0, 117)}...` : summarySeed;
96
+ const sourceRefBase = `runtime://threads/${input.thread.threadId}/runs/${input.runId}/background-reflection`;
97
+ return input.scopes.map((scope) => ({
98
+ kind: "episodic",
99
+ scope,
100
+ sourceType: "runtime-reflection",
101
+ sourceRef: `${sourceRefBase}#${scope}`,
102
+ summary: `${baseSummary} (${scope})`,
103
+ content: [
104
+ `Run ${input.runId} completed for thread ${input.thread.threadId}.`,
105
+ `Trigger: ${input.trigger}.`,
106
+ `Latest user context: ${userContext}`,
107
+ `Latest assistant context: ${assistantContext}`,
108
+ `Approvals: ${approvals}`,
109
+ ].join("\n"),
110
+ confidence: 0.72,
111
+ observedAt: input.recordedAt,
112
+ tags: ["background-reflection", "langmem-aligned", scope, input.trigger],
113
+ }));
114
+ }
115
+ function normalizeKind(value, fallback) {
116
+ if (value === "semantic" || value === "episodic" || value === "procedural") {
117
+ return value;
118
+ }
119
+ return fallback;
120
+ }
121
+ function normalizeScope(value, fallback) {
122
+ if (value === "thread" || value === "agent" || value === "workspace" || value === "user" || value === "project") {
123
+ return value;
124
+ }
125
+ return fallback;
126
+ }
127
+ function normalizeTags(value, fallback) {
128
+ if (!Array.isArray(value)) {
129
+ return fallback;
130
+ }
131
+ const tags = value.filter((item) => typeof item === "string" && item.trim().length > 0);
132
+ return tags.length > 0 ? tags : fallback;
133
+ }
134
+ function normalizeConfidence(value, fallback) {
135
+ if (typeof value !== "number" || Number.isNaN(value)) {
136
+ return fallback;
137
+ }
138
+ return Math.min(1, Math.max(0, value));
139
+ }
140
+ function extractText(value) {
141
+ if (typeof value === "string") {
142
+ return value;
143
+ }
144
+ if (typeof value === "object" && value !== null && "content" in value && typeof value.content === "string") {
145
+ return String(value.content);
146
+ }
147
+ return undefined;
148
+ }
149
+ function tryParseJsonObject(text) {
150
+ try {
151
+ const parsed = JSON.parse(text);
152
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
153
+ }
154
+ catch {
155
+ const match = text.match(/\{[\s\S]*\}/);
156
+ if (!match) {
157
+ return null;
158
+ }
159
+ try {
160
+ const parsed = JSON.parse(match[0]);
161
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
167
+ }
168
+ function renderManagerPrompt(input) {
169
+ const existing = input.existingRecords.length === 0
170
+ ? "(none)"
171
+ : input.existingRecords
172
+ .map((record) => `- scope=${record.scope}; kind=${record.kind}; summary=${record.summary}; status=${record.status}; content=${record.content.replace(/\s+/g, " ").slice(0, 220)}`)
173
+ .join("\n");
174
+ return [
175
+ "You are the runtime memory manager.",
176
+ "Decide whether a candidate should be stored as durable memory and refine it if appropriate.",
177
+ "Return JSON only.",
178
+ "",
179
+ "Rules:",
180
+ '- Store only durable reusable knowledge. Reject transient chatter, scratchpad, or duplication without added value.',
181
+ '- Prefer semantic/episodic/procedural kinds only.',
182
+ '- Prefer scopes thread/agent/workspace/user/project only.',
183
+ '- If the candidate should not be stored, return {"store": false, "reason": "..."}',
184
+ '- If it should be stored, return {"store": true, "content": "...", "summary": "...", "kind": "...", "scope": "...", "tags": ["..."], "confidence": 0.0}',
185
+ "",
186
+ `threadId=${input.threadId}`,
187
+ `runId=${input.runId}`,
188
+ "",
189
+ "Candidate:",
190
+ JSON.stringify(input.candidate, null, 2),
191
+ "",
192
+ "Existing relevant records:",
193
+ existing,
194
+ ].join("\n");
195
+ }
196
+ export async function runModelMemoryManager(input) {
197
+ const resolvedModel = await createResolvedModel(input.model, input.modelResolver);
198
+ const invoker = resolvedModel;
199
+ if (typeof invoker.invoke !== "function") {
200
+ return input.candidates;
201
+ }
202
+ const transformed = [];
203
+ for (const candidate of input.candidates) {
204
+ const prompt = renderManagerPrompt({
205
+ candidate,
206
+ threadId: input.threadId,
207
+ runId: input.runId,
208
+ existingRecords: input.existingRecords,
209
+ });
210
+ const response = await invoker.invoke(prompt, {});
211
+ const parsed = tryParseJsonObject(extractText(response) ?? "");
212
+ if (!parsed) {
213
+ transformed.push(candidate);
214
+ continue;
215
+ }
216
+ if (parsed.store === false) {
217
+ continue;
218
+ }
219
+ transformed.push({
220
+ ...candidate,
221
+ content: typeof parsed.content === "string" && parsed.content.trim().length > 0 ? parsed.content.trim() : candidate.content,
222
+ summary: typeof parsed.summary === "string" && parsed.summary.trim().length > 0 ? parsed.summary.trim() : candidate.summary,
223
+ kind: normalizeKind(parsed.kind, normalizeKind(candidate.kind, undefined)),
224
+ scope: normalizeScope(parsed.scope, normalizeScope(candidate.scope, undefined)),
225
+ tags: normalizeTags(parsed.tags, candidate.tags),
226
+ confidence: normalizeConfidence(parsed.confidence, candidate.confidence),
227
+ observedAt: candidate.observedAt ?? input.recordedAt,
228
+ });
229
+ }
230
+ return transformed;
231
+ }
232
+ export function createRuntimeMemoryManager(input) {
233
+ return {
234
+ async transform({ candidates, binding, threadId, runId, recordedAt, existingRecords }) {
235
+ if (input.config.manager.enabled !== true || candidates.length === 0) {
236
+ return candidates;
237
+ }
238
+ const contextRecords = existingRecords
239
+ .filter((record) => record.status === "active")
240
+ .slice(-input.config.manager.maxContextRecords);
241
+ if (input.config.manager.strategy !== "model") {
242
+ return candidates;
243
+ }
244
+ if (!binding.langchainAgentParams?.model) {
245
+ return candidates;
246
+ }
247
+ const primaryModel = (() => {
248
+ if (!input.config.manager.modelRef) {
249
+ return binding.langchainAgentParams.model;
250
+ }
251
+ const configured = input.workspace.models.get(resolveRefId(input.config.manager.modelRef));
252
+ return configured ? compileModel(configured) : binding.langchainAgentParams.model;
253
+ })();
254
+ return runModelMemoryManager({
255
+ workspace: input.workspace,
256
+ binding,
257
+ model: primaryModel,
258
+ candidates,
259
+ threadId,
260
+ runId,
261
+ recordedAt,
262
+ existingRecords: contextRecords,
263
+ modelResolver: input.modelResolver,
264
+ });
265
+ },
266
+ };
267
+ }
268
+ function fingerprintMessages(messages, scopes) {
269
+ const serialized = messages
270
+ .map((message) => `${message.role}\n${message.createdAt}\n${extractMessageText(message.content)}`)
271
+ .join("\n---\n");
272
+ return `${scopes.join(",")}::${serialized}`;
273
+ }
274
+ export class RuntimeMemoryFormationSync {
275
+ persistence;
276
+ config;
277
+ writer;
278
+ stateStore;
279
+ options;
280
+ pending = new Set();
281
+ syncChain = Promise.resolve();
282
+ name = "runtime-memory-formation-sync";
283
+ constructor(persistence, config, writer, runRoot, stateStore = new FileBackedStore(path.join(runRoot, config.background.stateStorePath)), options = {}) {
284
+ this.persistence = persistence;
285
+ this.config = config;
286
+ this.writer = writer;
287
+ this.stateStore = stateStore;
288
+ this.options = options;
289
+ }
290
+ shouldHandle(event) {
291
+ if (!this.config.background.enabled || !FORMATION_EVENT_TYPES.has(event.eventType)) {
292
+ return false;
293
+ }
294
+ if (event.eventType === "approval.resolved") {
295
+ return this.config.background.writeOnApprovalResolution;
296
+ }
297
+ return this.config.background.writeOnRunCompletion && event.payload.state === "completed";
298
+ }
299
+ async handleEvent(event) {
300
+ if (!this.shouldHandle(event)) {
301
+ return;
302
+ }
303
+ const trigger = event.eventType === "approval.resolved" ? "approval.resolved" : "run.completed";
304
+ const task = this.syncChain
305
+ .then(() => this.reflectRun(event.threadId, event.runId, trigger, event.timestamp))
306
+ .catch(() => {
307
+ // Fail open: reflection should not block runtime progress.
308
+ });
309
+ this.syncChain = task
310
+ .catch(() => {
311
+ // Fail open: reflection should not block runtime progress.
312
+ })
313
+ .finally(() => {
314
+ this.pending.delete(task);
315
+ });
316
+ this.pending.add(task);
317
+ }
318
+ async reflectRun(threadId, runId, trigger, recordedAt) {
319
+ const [thread, run, allMessages, approvals] = await Promise.all([
320
+ this.persistence.getSession(threadId),
321
+ this.persistence.getRun(runId),
322
+ this.persistence.listThreadMessages(threadId, this.config.background.maxMessagesPerRun),
323
+ this.persistence.getRunApprovals(threadId, runId),
324
+ ]);
325
+ if (!thread || !run) {
326
+ return;
327
+ }
328
+ const messages = allMessages.filter((message) => message.runId === runId);
329
+ if (messages.length === 0) {
330
+ return;
331
+ }
332
+ const fingerprint = fingerprintMessages(messages, this.config.background.scopes);
333
+ const namespace = ["memories", "formation", "threads", threadId, "runs"];
334
+ const cursor = await this.stateStore.get(namespace, runId);
335
+ const existing = cursor?.value;
336
+ if (existing?.fingerprint === fingerprint && existing.trigger === trigger) {
337
+ return;
338
+ }
339
+ const candidates = createBackgroundMemoryCandidates({
340
+ thread,
341
+ runId,
342
+ agentId: run.agentId ?? thread.agentId,
343
+ trigger,
344
+ recordedAt,
345
+ messages,
346
+ approvals,
347
+ scopes: this.config.background.scopes,
348
+ });
349
+ if (candidates.length === 0) {
350
+ return;
351
+ }
352
+ await this.writer({
353
+ candidates,
354
+ threadId,
355
+ runId,
356
+ agentId: run.agentId ?? thread.agentId,
357
+ userId: this.options.userId,
358
+ projectId: this.options.projectId,
359
+ recordedAt,
360
+ });
361
+ await this.stateStore.put(namespace, runId, {
362
+ fingerprint,
363
+ candidateCount: candidates.length,
364
+ syncedAt: new Date().toISOString(),
365
+ trigger,
366
+ });
367
+ }
368
+ async close() {
369
+ await Promise.allSettled(Array.from(this.pending));
370
+ }
371
+ }
@@ -14,6 +14,10 @@ type PersistMemoryRecordsOptions = {
14
14
  export declare function renderMemoryRecordsMarkdown(title: string, records: MemoryRecord[]): string;
15
15
  export declare function rebuildStructuredMemoryProjections(store: StoreLike, namespace: string[], title: string, scope: MemoryScope, maxEntries: number): Promise<void>;
16
16
  export declare function listMemoryRecordsForScopes(store: StoreLike, scopes: MemoryScope[]): Promise<MemoryRecord[]>;
17
+ export declare function getMemoryRecord(store: StoreLike, scope: MemoryScope, recordId: string): Promise<MemoryRecord | null>;
18
+ export declare function findMemoryRecordById(store: StoreLike, recordId: string): Promise<MemoryRecord | null>;
19
+ export declare function updateMemoryRecord(store: StoreLike, record: MemoryRecord, updatedAt: string): Promise<MemoryRecord>;
20
+ export declare function removeMemoryRecord(store: StoreLike, scope: MemoryScope, recordId: string): Promise<MemoryRecord | null>;
17
21
  export declare function persistStructuredMemoryRecords(options: PersistMemoryRecordsOptions): Promise<{
18
22
  records: MemoryRecord[];
19
23
  decisions: MemoryDecision[];
@@ -34,6 +34,12 @@ function createCanonicalKey(candidate, kind, scope) {
34
34
  function buildScopeNamespace(scope) {
35
35
  return ["memories", "records", scope];
36
36
  }
37
+ function buildCanonicalIndexNamespace(scope) {
38
+ return ["memories", "indexes", "canonical", scope];
39
+ }
40
+ function buildSourceRefIndexNamespace(scope) {
41
+ return ["memories", "indexes", "source-ref", scope];
42
+ }
37
43
  function normalizeTextForCompare(value) {
38
44
  return value.toLowerCase().replace(/\s+/g, " ").trim();
39
45
  }
@@ -312,7 +318,7 @@ function evaluateDecision(existing, incoming, recordedAt) {
312
318
  async function putRecordWithIndexes(store, record, updatedAt) {
313
319
  const canonicalKeyId = createFingerprint(record.canonicalKey);
314
320
  await store.put(buildScopeNamespace(record.scope), `${record.id}.json`, record);
315
- await store.put(["memories", "indexes", "canonical", record.scope], `${canonicalKeyId}-${record.id}.json`, {
321
+ await store.put(buildCanonicalIndexNamespace(record.scope), `${canonicalKeyId}-${record.id}.json`, {
316
322
  canonicalKey: record.canonicalKey,
317
323
  recordId: record.id,
318
324
  scope: record.scope,
@@ -321,7 +327,7 @@ async function putRecordWithIndexes(store, record, updatedAt) {
321
327
  });
322
328
  await Promise.all(record.sourceRefs.map((sourceRef) => {
323
329
  const sourceRefId = createFingerprint(sourceRef);
324
- return store.put(["memories", "indexes", "source-ref", record.scope], `${sourceRefId}-${record.id}.json`, {
330
+ return store.put(buildSourceRefIndexNamespace(record.scope), `${sourceRefId}-${record.id}.json`, {
325
331
  sourceRef,
326
332
  recordId: record.id,
327
333
  scope: record.scope,
@@ -375,6 +381,55 @@ export async function listMemoryRecordsForScopes(store, scopes) {
375
381
  const all = await Promise.all(scopes.map((scope) => listStoredRecords(store, scope)));
376
382
  return all.flat();
377
383
  }
384
+ export async function getMemoryRecord(store, scope, recordId) {
385
+ const entry = await store.get(buildScopeNamespace(scope), `${recordId}.json`);
386
+ const record = entry?.value;
387
+ if (typeof record !== "object" || !record || Array.isArray(record)) {
388
+ return null;
389
+ }
390
+ const candidate = record;
391
+ return typeof candidate.id === "string" && typeof candidate.content === "string" ? candidate : null;
392
+ }
393
+ export async function findMemoryRecordById(store, recordId) {
394
+ for (const scope of MEMORY_SCOPES) {
395
+ const record = await getMemoryRecord(store, scope, recordId);
396
+ if (record) {
397
+ return record;
398
+ }
399
+ }
400
+ return null;
401
+ }
402
+ async function deleteRecordIndexes(store, record) {
403
+ const [canonicalEntries, sourceRefEntries] = await Promise.all([
404
+ store.search(buildCanonicalIndexNamespace(record.scope)),
405
+ store.search(buildSourceRefIndexNamespace(record.scope)),
406
+ ]);
407
+ await Promise.all([
408
+ ...canonicalEntries
409
+ .filter((entry) => entry.value?.recordId === record.id)
410
+ .map((entry) => store.delete(entry.namespace, entry.key)),
411
+ ...sourceRefEntries
412
+ .filter((entry) => entry.value?.recordId === record.id)
413
+ .map((entry) => store.delete(entry.namespace, entry.key)),
414
+ ]);
415
+ }
416
+ export async function updateMemoryRecord(store, record, updatedAt) {
417
+ const existing = await getMemoryRecord(store, record.scope, record.id);
418
+ if (existing) {
419
+ await deleteRecordIndexes(store, existing);
420
+ }
421
+ await putRecordWithIndexes(store, record, updatedAt);
422
+ return record;
423
+ }
424
+ export async function removeMemoryRecord(store, scope, recordId) {
425
+ const existing = await getMemoryRecord(store, scope, recordId);
426
+ if (!existing) {
427
+ return null;
428
+ }
429
+ await deleteRecordIndexes(store, existing);
430
+ await store.delete(buildScopeNamespace(scope), `${recordId}.json`);
431
+ return existing;
432
+ }
378
433
  export async function persistStructuredMemoryRecords(options) {
379
434
  const existingRecords = await listStoredRecords(options.store);
380
435
  const persistedRecords = [];
@@ -49,4 +49,31 @@ export declare class FileBackedStore {
49
49
  delete(namespace: string[], key: string): Promise<void>;
50
50
  listNamespaces(options?: Parameters<InMemoryStore["listNamespaces"]>[0]): Promise<string[][]>;
51
51
  }
52
+ export declare class SqliteBackedStore {
53
+ readonly filePath: string;
54
+ private readonly db;
55
+ constructor(filePath: string);
56
+ batch(operations: Array<Record<string, unknown>>): Promise<readonly unknown[]>;
57
+ get(namespace: string[], key: string): Promise<{
58
+ value: unknown;
59
+ key: string;
60
+ namespace: string[];
61
+ createdAt: Date;
62
+ updatedAt: Date;
63
+ } | null>;
64
+ private getSync;
65
+ search(namespacePrefix: string[]): Promise<Array<{
66
+ value: unknown;
67
+ key: string;
68
+ namespace: string[];
69
+ createdAt: Date;
70
+ updatedAt: Date;
71
+ score?: number;
72
+ }>>;
73
+ put(namespace: string[], key: string, value: Record<string, any>, _index?: false | string[]): Promise<void>;
74
+ private putSync;
75
+ delete(namespace: string[], key: string): Promise<void>;
76
+ private deleteSync;
77
+ listNamespaces(): Promise<string[][]>;
78
+ }
52
79
  export declare function createInMemoryStore(): StoreLike;