@sentry/junior-memory 0.106.0 → 0.107.0
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/dist/agent.d.ts +2 -2
- package/dist/index.js +1638 -1636
- package/dist/index.js.map +1 -1
- package/dist/store.d.ts +66 -24
- package/package.json +2 -2
- package/src/agent.ts +27 -61
- package/src/store.ts +173 -138
package/src/store.ts
CHANGED
|
@@ -50,24 +50,14 @@ const DEFAULT_LIST_LIMIT = 50;
|
|
|
50
50
|
const DEFAULT_SEARCH_LIMIT = 10;
|
|
51
51
|
const DEFAULT_EXPIRED_ARCHIVE_LIMIT = 100;
|
|
52
52
|
const HIGH_CONFIDENCE_DUPLICATE_DISTANCE = 0.015;
|
|
53
|
-
const
|
|
53
|
+
const PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT = 10;
|
|
54
|
+
const PREFERENCE_ADJUDICATION_VECTOR_LIMIT = 5;
|
|
54
55
|
const VECTOR_SEARCH_OVERFETCH = 4;
|
|
55
56
|
const MAX_MEMORY_CONTENT_CHARS = 4_000;
|
|
56
57
|
const EMBEDDING_METRIC = "cosine";
|
|
57
58
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
58
59
|
const RELEVANCE_NEAR_TIE_DELTA = 0.01;
|
|
59
60
|
const SOURCE_CHANNEL_BOOST = 0.05;
|
|
60
|
-
const SUPERSESSION_STOP_TERMS = new Set([
|
|
61
|
-
"and",
|
|
62
|
-
"for",
|
|
63
|
-
"prefer",
|
|
64
|
-
"prefers",
|
|
65
|
-
"preference",
|
|
66
|
-
"the",
|
|
67
|
-
"use",
|
|
68
|
-
"uses",
|
|
69
|
-
"with",
|
|
70
|
-
]);
|
|
71
61
|
|
|
72
62
|
export type MemoryDb = PgDatabase<PgQueryResultHKT, typeof memorySqlSchema>;
|
|
73
63
|
|
|
@@ -206,6 +196,61 @@ const embeddingResultSchema = z
|
|
|
206
196
|
vectors: z.array(embeddingVectorSchema),
|
|
207
197
|
})
|
|
208
198
|
.strict();
|
|
199
|
+
const memorySupersessionCandidateSchema = z
|
|
200
|
+
.object({
|
|
201
|
+
content: z.string().min(1),
|
|
202
|
+
id: z.string().min(1),
|
|
203
|
+
})
|
|
204
|
+
.strict();
|
|
205
|
+
const memorySupersessionCandidatesSchema = z
|
|
206
|
+
.array(memorySupersessionCandidateSchema)
|
|
207
|
+
.min(1)
|
|
208
|
+
.max(PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT);
|
|
209
|
+
const supersededIdsSchema = z
|
|
210
|
+
.array(z.string().min(1))
|
|
211
|
+
.min(1)
|
|
212
|
+
.max(PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT);
|
|
213
|
+
|
|
214
|
+
/** Validated preference comparison input supplied to a supersession decider. */
|
|
215
|
+
export const memorySupersessionInputSchema = z
|
|
216
|
+
.object({
|
|
217
|
+
candidate: z
|
|
218
|
+
.object({
|
|
219
|
+
content: z.string().min(1),
|
|
220
|
+
kind: z.literal("preference"),
|
|
221
|
+
})
|
|
222
|
+
.strict(),
|
|
223
|
+
existingMemories: memorySupersessionCandidatesSchema,
|
|
224
|
+
runtimeContext: memoryRuntimeContextSchema,
|
|
225
|
+
})
|
|
226
|
+
.strict();
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validated preference decision whose referenced ids must come from the
|
|
230
|
+
* supplied existing memories.
|
|
231
|
+
*/
|
|
232
|
+
export const memorySupersessionDecisionSchema = z.discriminatedUnion(
|
|
233
|
+
"decision",
|
|
234
|
+
[
|
|
235
|
+
z
|
|
236
|
+
.object({
|
|
237
|
+
decision: z.literal("duplicate"),
|
|
238
|
+
duplicateId: z.string().min(1),
|
|
239
|
+
})
|
|
240
|
+
.strict(),
|
|
241
|
+
z
|
|
242
|
+
.object({
|
|
243
|
+
decision: z.literal("supersedes_old"),
|
|
244
|
+
supersededIds: supersededIdsSchema,
|
|
245
|
+
})
|
|
246
|
+
.strict(),
|
|
247
|
+
z
|
|
248
|
+
.object({
|
|
249
|
+
decision: z.enum(["distinct", "uncertain"]),
|
|
250
|
+
})
|
|
251
|
+
.strict(),
|
|
252
|
+
],
|
|
253
|
+
);
|
|
209
254
|
|
|
210
255
|
export type MemoryRecord = z.output<typeof memoryRecordSchema>;
|
|
211
256
|
export type CreateMemoryInput = z.output<typeof createMemoryInputSchema>;
|
|
@@ -240,35 +285,16 @@ export interface MemoryEmbeddingProvider {
|
|
|
240
285
|
}>;
|
|
241
286
|
}
|
|
242
287
|
|
|
243
|
-
export
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
kind: "preference";
|
|
247
|
-
};
|
|
248
|
-
existingMemories: [
|
|
249
|
-
{
|
|
250
|
-
content: string;
|
|
251
|
-
id: string;
|
|
252
|
-
},
|
|
253
|
-
...Array<{
|
|
254
|
-
content: string;
|
|
255
|
-
id: string;
|
|
256
|
-
}>,
|
|
257
|
-
];
|
|
258
|
-
runtimeContext: MemoryRuntimeContext;
|
|
259
|
-
}
|
|
288
|
+
export type MemorySupersessionInput = z.output<
|
|
289
|
+
typeof memorySupersessionInputSchema
|
|
290
|
+
>;
|
|
260
291
|
|
|
261
|
-
export type MemorySupersessionDecision =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
supersededIds: [string, ...string[]];
|
|
265
|
-
}
|
|
266
|
-
| {
|
|
267
|
-
decision: "distinct" | "uncertain";
|
|
268
|
-
};
|
|
292
|
+
export type MemorySupersessionDecision = z.output<
|
|
293
|
+
typeof memorySupersessionDecisionSchema
|
|
294
|
+
>;
|
|
269
295
|
|
|
270
296
|
export interface MemorySupersessionDecider {
|
|
271
|
-
/**
|
|
297
|
+
/** Classify a new preference against related active preferences. */
|
|
272
298
|
adjudicateSupersession(
|
|
273
299
|
input: MemorySupersessionInput,
|
|
274
300
|
): Promise<MemorySupersessionDecision> | MemorySupersessionDecision;
|
|
@@ -792,74 +818,52 @@ async function rememberDuplicateIdempotency(args: {
|
|
|
792
818
|
.onConflictDoNothing();
|
|
793
819
|
}
|
|
794
820
|
|
|
795
|
-
|
|
796
|
-
|
|
821
|
+
/** Select semantic preferences, then fill the window by recency for unembedded records. */
|
|
822
|
+
async function listPreferenceAdjudicationCandidates(args: {
|
|
797
823
|
db: MemoryDb;
|
|
798
824
|
embedding?: MemoryEmbedding;
|
|
799
|
-
kind: MemoryRecord["kind"];
|
|
800
825
|
nowMs: number;
|
|
801
826
|
scope: ResolvedMemoryScope;
|
|
802
827
|
subject: ResolvedMemorySubject;
|
|
803
828
|
}): Promise<MemoryRecord[]> {
|
|
804
|
-
const predicate = activeScopedSubjectPredicate(args);
|
|
805
|
-
const terms = searchTerms(args.content).filter(
|
|
806
|
-
(term) => !SUPERSESSION_STOP_TERMS.has(term),
|
|
807
|
-
);
|
|
808
|
-
const lexicalRows =
|
|
809
|
-
terms.length > 0
|
|
810
|
-
? await args.db
|
|
811
|
-
.select()
|
|
812
|
-
.from(juniorMemoryMemories)
|
|
813
|
-
.where(
|
|
814
|
-
and(
|
|
815
|
-
predicate,
|
|
816
|
-
or(
|
|
817
|
-
...terms.map((term) =>
|
|
818
|
-
ilike(juniorMemoryMemories.content, `%${term}%`),
|
|
819
|
-
),
|
|
820
|
-
),
|
|
821
|
-
),
|
|
822
|
-
)
|
|
823
|
-
: [];
|
|
824
|
-
const lexicalCandidates = lexicalRows
|
|
825
|
-
.map(parseMemoryRow)
|
|
826
|
-
.map((memory) => ({
|
|
827
|
-
memory,
|
|
828
|
-
score: searchScore(memory, terms),
|
|
829
|
-
}))
|
|
830
|
-
.filter((candidate) => candidate.score > 1)
|
|
831
|
-
.sort(
|
|
832
|
-
(left, right) =>
|
|
833
|
-
right.score - left.score ||
|
|
834
|
-
right.memory.createdAtMs - left.memory.createdAtMs ||
|
|
835
|
-
left.memory.id.localeCompare(right.memory.id),
|
|
836
|
-
)
|
|
837
|
-
.map((candidate) => candidate.memory);
|
|
838
|
-
|
|
839
829
|
const vectorCandidates = args.embedding
|
|
840
|
-
? await
|
|
830
|
+
? await listVectorPreferenceAdjudicationCandidates({
|
|
841
831
|
db: args.db,
|
|
842
832
|
embedding: args.embedding,
|
|
843
|
-
kind: args.kind,
|
|
844
833
|
nowMs: args.nowMs,
|
|
845
834
|
scope: args.scope,
|
|
846
835
|
subject: args.subject,
|
|
847
836
|
})
|
|
848
837
|
: [];
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
838
|
+
const recentCandidates = (
|
|
839
|
+
await args.db
|
|
840
|
+
.select()
|
|
841
|
+
.from(juniorMemoryMemories)
|
|
842
|
+
.where(
|
|
843
|
+
activeScopedSubjectPredicate({
|
|
844
|
+
...args,
|
|
845
|
+
kind: "preference",
|
|
846
|
+
}),
|
|
847
|
+
)
|
|
848
|
+
.orderBy(
|
|
849
|
+
desc(juniorMemoryMemories.createdAtMs),
|
|
850
|
+
asc(juniorMemoryMemories.id),
|
|
851
|
+
)
|
|
852
|
+
.limit(PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT)
|
|
853
|
+
).map(parseMemoryRow);
|
|
854
|
+
return [
|
|
855
|
+
...new Map(
|
|
856
|
+
[...vectorCandidates, ...recentCandidates].map((memory) => [
|
|
857
|
+
memory.id,
|
|
858
|
+
memory,
|
|
859
|
+
]),
|
|
860
|
+
).values(),
|
|
861
|
+
].slice(0, PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT);
|
|
857
862
|
}
|
|
858
863
|
|
|
859
|
-
async function
|
|
864
|
+
async function listVectorPreferenceAdjudicationCandidates(args: {
|
|
860
865
|
db: MemoryDb;
|
|
861
866
|
embedding: MemoryEmbedding;
|
|
862
|
-
kind: MemoryRecord["kind"];
|
|
863
867
|
nowMs: number;
|
|
864
868
|
scope: ResolvedMemoryScope;
|
|
865
869
|
subject: ResolvedMemorySubject;
|
|
@@ -881,7 +885,7 @@ async function listVectorSupersessionCandidates(args: {
|
|
|
881
885
|
)
|
|
882
886
|
.where(
|
|
883
887
|
and(
|
|
884
|
-
activeScopedSubjectPredicate(args),
|
|
888
|
+
activeScopedSubjectPredicate({ ...args, kind: "preference" }),
|
|
885
889
|
eq(juniorMemoryEmbeddings.provider, args.embedding.provider),
|
|
886
890
|
eq(juniorMemoryEmbeddings.model, args.embedding.model),
|
|
887
891
|
eq(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
|
|
@@ -893,7 +897,7 @@ async function listVectorSupersessionCandidates(args: {
|
|
|
893
897
|
desc(juniorMemoryMemories.createdAtMs),
|
|
894
898
|
asc(juniorMemoryMemories.id),
|
|
895
899
|
)
|
|
896
|
-
.limit(
|
|
900
|
+
.limit(PREFERENCE_ADJUDICATION_VECTOR_LIMIT);
|
|
897
901
|
return rows.flatMap((row) => {
|
|
898
902
|
if (hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
899
903
|
return [];
|
|
@@ -902,26 +906,32 @@ async function listVectorSupersessionCandidates(args: {
|
|
|
902
906
|
});
|
|
903
907
|
}
|
|
904
908
|
|
|
905
|
-
|
|
909
|
+
type PreferenceAdjudicationResult =
|
|
910
|
+
| { decision: "create" }
|
|
911
|
+
| { decision: "duplicate"; memory: MemoryRecord }
|
|
912
|
+
| { decision: "supersede"; ids: [string, ...string[]] };
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Normalize a preference decision to known duplicate or supersession targets.
|
|
916
|
+
* Uncertainty, invalid ids, and model failure leave existing memories active.
|
|
917
|
+
*/
|
|
918
|
+
async function adjudicatePreferenceCandidate(args: {
|
|
906
919
|
candidates: MemoryRecord[];
|
|
907
920
|
content: string;
|
|
908
|
-
decider: MemorySupersessionDecider
|
|
909
|
-
kind: MemoryRecord["kind"];
|
|
921
|
+
decider: MemorySupersessionDecider;
|
|
910
922
|
runtimeContext: MemoryRuntimeContext;
|
|
911
|
-
}): Promise<
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
args.kind !== "preference" ||
|
|
916
|
-
!args.decider ||
|
|
917
|
-
args.candidates.length === 0
|
|
918
|
-
) {
|
|
919
|
-
return [];
|
|
923
|
+
}): Promise<PreferenceAdjudicationResult> {
|
|
924
|
+
const [firstCandidate, ...remainingCandidates] = args.candidates;
|
|
925
|
+
if (!firstCandidate) {
|
|
926
|
+
return { decision: "create" };
|
|
920
927
|
}
|
|
921
|
-
const existingMemories =
|
|
922
|
-
content:
|
|
923
|
-
|
|
924
|
-
|
|
928
|
+
const existingMemories = [
|
|
929
|
+
{ content: firstCandidate.content, id: firstCandidate.id },
|
|
930
|
+
...remainingCandidates.map((memory) => ({
|
|
931
|
+
content: memory.content,
|
|
932
|
+
id: memory.id,
|
|
933
|
+
})),
|
|
934
|
+
];
|
|
925
935
|
const candidateIds = new Set(args.candidates.map((memory) => memory.id));
|
|
926
936
|
try {
|
|
927
937
|
const decision = await args.decider.adjudicateSupersession({
|
|
@@ -932,12 +942,24 @@ async function decideSupersededIds(args: {
|
|
|
932
942
|
existingMemories,
|
|
933
943
|
runtimeContext: args.runtimeContext,
|
|
934
944
|
});
|
|
935
|
-
if (decision.decision
|
|
936
|
-
|
|
945
|
+
if (decision.decision === "duplicate") {
|
|
946
|
+
const memory = args.candidates.find(
|
|
947
|
+
(candidate) => candidate.id === decision.duplicateId,
|
|
948
|
+
);
|
|
949
|
+
return memory
|
|
950
|
+
? { decision: "duplicate", memory }
|
|
951
|
+
: { decision: "create" };
|
|
952
|
+
}
|
|
953
|
+
if (decision.decision === "supersedes_old") {
|
|
954
|
+
const ids = decision.supersededIds.filter((id) => candidateIds.has(id));
|
|
955
|
+
const [firstId, ...remainingIds] = ids;
|
|
956
|
+
return firstId
|
|
957
|
+
? { decision: "supersede", ids: [firstId, ...remainingIds] }
|
|
958
|
+
: { decision: "create" };
|
|
937
959
|
}
|
|
938
|
-
return decision
|
|
960
|
+
return { decision: "create" };
|
|
939
961
|
} catch {
|
|
940
|
-
return
|
|
962
|
+
return { decision: "create" };
|
|
941
963
|
}
|
|
942
964
|
}
|
|
943
965
|
|
|
@@ -1176,6 +1198,29 @@ export function createMemoryStore(
|
|
|
1176
1198
|
});
|
|
1177
1199
|
}
|
|
1178
1200
|
|
|
1201
|
+
async function reuseDuplicateMemory(args: {
|
|
1202
|
+
content: string;
|
|
1203
|
+
duplicate: MemoryRecord;
|
|
1204
|
+
idempotencyKey?: string;
|
|
1205
|
+
nowMs: number;
|
|
1206
|
+
scope: ResolvedMemoryScope;
|
|
1207
|
+
subject: ResolvedMemorySubject;
|
|
1208
|
+
}): Promise<CreateMemoryResult> {
|
|
1209
|
+
await rememberDuplicateIdempotency({
|
|
1210
|
+
...args,
|
|
1211
|
+
db,
|
|
1212
|
+
runtimeContext,
|
|
1213
|
+
});
|
|
1214
|
+
await storeEmbedding({
|
|
1215
|
+
content: args.duplicate.content,
|
|
1216
|
+
db,
|
|
1217
|
+
embedder,
|
|
1218
|
+
memoryId: args.duplicate.id,
|
|
1219
|
+
nowMs: args.nowMs,
|
|
1220
|
+
});
|
|
1221
|
+
return { created: false, memory: args.duplicate };
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1179
1224
|
/** Persist a memory under the plugin-derived scope and subject. */
|
|
1180
1225
|
async function createScopedMemory(
|
|
1181
1226
|
rawInput: CreateMemoryInput,
|
|
@@ -1229,24 +1274,14 @@ export function createMemoryStore(
|
|
|
1229
1274
|
subject,
|
|
1230
1275
|
});
|
|
1231
1276
|
if (exactDuplicate) {
|
|
1232
|
-
await
|
|
1277
|
+
return await reuseDuplicateMemory({
|
|
1233
1278
|
content,
|
|
1234
|
-
db,
|
|
1235
1279
|
duplicate: exactDuplicate,
|
|
1236
1280
|
idempotencyKey: input.idempotencyKey,
|
|
1237
1281
|
nowMs,
|
|
1238
|
-
runtimeContext,
|
|
1239
1282
|
scope,
|
|
1240
1283
|
subject,
|
|
1241
1284
|
});
|
|
1242
|
-
await storeEmbedding({
|
|
1243
|
-
content: exactDuplicate.content,
|
|
1244
|
-
db,
|
|
1245
|
-
embedder,
|
|
1246
|
-
memoryId: exactDuplicate.id,
|
|
1247
|
-
nowMs,
|
|
1248
|
-
});
|
|
1249
|
-
return { created: false, memory: exactDuplicate };
|
|
1250
1285
|
}
|
|
1251
1286
|
|
|
1252
1287
|
let candidateEmbedding: MemoryEmbedding | undefined;
|
|
@@ -1268,24 +1303,14 @@ export function createMemoryStore(
|
|
|
1268
1303
|
})
|
|
1269
1304
|
: undefined;
|
|
1270
1305
|
if (vectorDuplicate) {
|
|
1271
|
-
await
|
|
1306
|
+
return await reuseDuplicateMemory({
|
|
1272
1307
|
content,
|
|
1273
|
-
db,
|
|
1274
1308
|
duplicate: vectorDuplicate,
|
|
1275
1309
|
idempotencyKey: input.idempotencyKey,
|
|
1276
1310
|
nowMs,
|
|
1277
|
-
runtimeContext,
|
|
1278
1311
|
scope,
|
|
1279
1312
|
subject,
|
|
1280
1313
|
});
|
|
1281
|
-
await storeEmbedding({
|
|
1282
|
-
content: vectorDuplicate.content,
|
|
1283
|
-
db,
|
|
1284
|
-
embedder,
|
|
1285
|
-
memoryId: vectorDuplicate.id,
|
|
1286
|
-
nowMs,
|
|
1287
|
-
});
|
|
1288
|
-
return { created: false, memory: vectorDuplicate };
|
|
1289
1314
|
}
|
|
1290
1315
|
|
|
1291
1316
|
let supersededIds: string[] = [];
|
|
@@ -1295,22 +1320,32 @@ export function createMemoryStore(
|
|
|
1295
1320
|
supersessionDecider &&
|
|
1296
1321
|
(input.expiresAtMs === undefined || input.expiresAtMs > nowMs)
|
|
1297
1322
|
) {
|
|
1298
|
-
const
|
|
1299
|
-
content,
|
|
1323
|
+
const preferenceCandidates = await listPreferenceAdjudicationCandidates({
|
|
1300
1324
|
db,
|
|
1301
1325
|
...(candidateEmbedding ? { embedding: candidateEmbedding } : {}),
|
|
1302
|
-
kind: input.kind,
|
|
1303
1326
|
nowMs,
|
|
1304
1327
|
scope,
|
|
1305
1328
|
subject,
|
|
1306
1329
|
});
|
|
1307
|
-
|
|
1308
|
-
candidates:
|
|
1330
|
+
const adjudication = await adjudicatePreferenceCandidate({
|
|
1331
|
+
candidates: preferenceCandidates,
|
|
1309
1332
|
content,
|
|
1310
1333
|
decider: supersessionDecider,
|
|
1311
|
-
kind: input.kind,
|
|
1312
1334
|
runtimeContext,
|
|
1313
1335
|
});
|
|
1336
|
+
if (adjudication.decision === "duplicate") {
|
|
1337
|
+
return await reuseDuplicateMemory({
|
|
1338
|
+
content,
|
|
1339
|
+
duplicate: adjudication.memory,
|
|
1340
|
+
idempotencyKey: input.idempotencyKey,
|
|
1341
|
+
nowMs,
|
|
1342
|
+
scope,
|
|
1343
|
+
subject,
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
if (adjudication.decision === "supersede") {
|
|
1347
|
+
supersededIds = adjudication.ids;
|
|
1348
|
+
}
|
|
1314
1349
|
}
|
|
1315
1350
|
|
|
1316
1351
|
const id = randomUUID();
|