@sentry/junior-memory 0.80.1 → 0.81.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 +3 -0
- package/dist/index.js +553 -47
- package/dist/index.js.map +1 -1
- package/dist/store.d.ts +28 -0
- package/dist/tools.d.ts +9 -2
- package/package.json +2 -2
- package/src/agent.ts +92 -0
- package/src/plugin.ts +28 -2
- package/src/process-session.ts +2 -1
- package/src/recall.ts +8 -1
- package/src/store.ts +633 -48
- package/src/tools.ts +33 -7
package/dist/index.js
CHANGED
|
@@ -84,6 +84,19 @@ var extractSessionRequestSchema = z2.object({
|
|
|
84
84
|
])
|
|
85
85
|
).min(1)
|
|
86
86
|
}).strict();
|
|
87
|
+
var supersessionRequestSchema = z2.object({
|
|
88
|
+
candidate: z2.object({
|
|
89
|
+
content: z2.string().min(1),
|
|
90
|
+
kind: z2.literal("preference")
|
|
91
|
+
}).strict(),
|
|
92
|
+
existingMemories: z2.array(
|
|
93
|
+
z2.object({
|
|
94
|
+
content: z2.string().min(1),
|
|
95
|
+
id: z2.string().min(1)
|
|
96
|
+
}).strict()
|
|
97
|
+
).min(1).max(10),
|
|
98
|
+
runtimeContext: memoryRuntimeContextSchema
|
|
99
|
+
}).strict();
|
|
87
100
|
var expiresAtMsSchema = z2.number().finite().nullable().describe(
|
|
88
101
|
"Expiration timestamp when the fact should expire, otherwise null."
|
|
89
102
|
);
|
|
@@ -134,6 +147,15 @@ var extractMemoriesResponseSchema = z2.object({
|
|
|
134
147
|
"Accepted public/shareable durable memories from the completed run. Return one object per distinct source assertion and classify it with kind."
|
|
135
148
|
)
|
|
136
149
|
}).strict();
|
|
150
|
+
var supersessionDecisionResponseSchema = z2.discriminatedUnion("decision", [
|
|
151
|
+
z2.object({
|
|
152
|
+
decision: z2.literal("supersedes_old"),
|
|
153
|
+
supersededIds: z2.array(z2.string().min(1)).min(1).max(10)
|
|
154
|
+
}).strict(),
|
|
155
|
+
z2.object({
|
|
156
|
+
decision: z2.enum(["distinct", "uncertain"])
|
|
157
|
+
}).strict()
|
|
158
|
+
]);
|
|
137
159
|
var MEMORY_REVIEW_SYSTEM = [
|
|
138
160
|
"You are Junior's memory review agent.",
|
|
139
161
|
"Review one memory candidate and return one structured review decision.",
|
|
@@ -148,6 +170,12 @@ var MEMORY_EXTRACTION_SYSTEM = [
|
|
|
148
170
|
"Reject secrets, credentials, private or sensitive personal details, gossip, speculative claims about other people, assistant/system implementation details, vague references, and low-durability chatter.",
|
|
149
171
|
"If no public, durable, self-contained memory remains after rewriting, return an empty memories array."
|
|
150
172
|
].join("\n");
|
|
173
|
+
var MEMORY_SUPERSESSION_SYSTEM = [
|
|
174
|
+
"You are Junior's memory supersession agent.",
|
|
175
|
+
"Decide whether a new requester preference clearly replaces existing active requester preferences.",
|
|
176
|
+
"Return supersedes_old only for obvious changed preferences about the same mutable slot.",
|
|
177
|
+
"If the facts are additive, different topics, duplicate, broader/narrower without direct replacement, or uncertain, do not supersede."
|
|
178
|
+
].join("\n");
|
|
151
179
|
var CANONICAL_CONTENT_RULES = [
|
|
152
180
|
"- Stored memory text must be a rewritten fact, not copied user wording or a sentence about who said it.",
|
|
153
181
|
"- Store the minimum useful assertion supported by source evidence; do not add adjacent steps, caveats, or generalized advice.",
|
|
@@ -302,8 +330,50 @@ function sessionExtractionPrompt(request) {
|
|
|
302
330
|
"</memory-extraction-input>"
|
|
303
331
|
].join("\n");
|
|
304
332
|
}
|
|
333
|
+
function supersessionPrompt(request) {
|
|
334
|
+
return [
|
|
335
|
+
"<memory-supersession-input>",
|
|
336
|
+
"Decide whether the candidate preference clearly replaces one or more existing active preferences.",
|
|
337
|
+
"",
|
|
338
|
+
runtimeDescription({
|
|
339
|
+
runtimeContext: request.runtimeContext
|
|
340
|
+
}),
|
|
341
|
+
"",
|
|
342
|
+
"<candidate>",
|
|
343
|
+
escapeXml(JSON.stringify(request.candidate)),
|
|
344
|
+
"</candidate>",
|
|
345
|
+
"",
|
|
346
|
+
"<existing-memories>",
|
|
347
|
+
escapeXml(JSON.stringify(request.existingMemories)),
|
|
348
|
+
"</existing-memories>",
|
|
349
|
+
"",
|
|
350
|
+
"<rules>",
|
|
351
|
+
"- Return supersedes_old only when the candidate and old memory describe the same mutable preference slot and the candidate is the newer value.",
|
|
352
|
+
"- Examples of same mutable slot: preferred programming language, preferred review style, preferred notification cadence, preferred tool for a task.",
|
|
353
|
+
"- Do not supersede when the candidate is just more specific, an additional preference, a different task/context, or the same value phrased differently.",
|
|
354
|
+
"- Do not supersede memories from different topics even if they are both preferences.",
|
|
355
|
+
"- Only return ids that appear in existing-memories.",
|
|
356
|
+
"- If unsure, return uncertain.",
|
|
357
|
+
"</rules>",
|
|
358
|
+
"</memory-supersession-input>"
|
|
359
|
+
].join("\n");
|
|
360
|
+
}
|
|
305
361
|
function createMemoryAgent(model) {
|
|
306
362
|
return {
|
|
363
|
+
async adjudicateSupersession(rawRequest) {
|
|
364
|
+
const request = supersessionRequestSchema.parse(
|
|
365
|
+
rawRequest
|
|
366
|
+
);
|
|
367
|
+
const result = await model.completeObject({
|
|
368
|
+
schema: supersessionDecisionResponseSchema,
|
|
369
|
+
system: MEMORY_SUPERSESSION_SYSTEM,
|
|
370
|
+
prompt: supersessionPrompt(request),
|
|
371
|
+
maxTokens: 400
|
|
372
|
+
});
|
|
373
|
+
return supersessionDecisionResponseSchema.parse(
|
|
374
|
+
result.object
|
|
375
|
+
);
|
|
376
|
+
},
|
|
307
377
|
async extractSessionMemories(rawRequest) {
|
|
308
378
|
const request = extractSessionRequestSchema.parse(rawRequest);
|
|
309
379
|
const result = await model.completeObject({
|
|
@@ -612,6 +682,7 @@ import {
|
|
|
612
682
|
ilike as ilike2,
|
|
613
683
|
inArray,
|
|
614
684
|
isNull as isNull2,
|
|
685
|
+
isNotNull,
|
|
615
686
|
like,
|
|
616
687
|
lte,
|
|
617
688
|
or as or2,
|
|
@@ -692,9 +763,24 @@ function deriveVisibleMemoryScopes(ctx) {
|
|
|
692
763
|
var DEFAULT_LIST_LIMIT = 50;
|
|
693
764
|
var DEFAULT_SEARCH_LIMIT = 10;
|
|
694
765
|
var DEFAULT_EXPIRED_ARCHIVE_LIMIT = 100;
|
|
766
|
+
var HIGH_CONFIDENCE_DUPLICATE_DISTANCE = 0.015;
|
|
767
|
+
var SUPERSESSION_CANDIDATE_LIMIT = 10;
|
|
695
768
|
var VECTOR_SEARCH_OVERFETCH = 4;
|
|
696
769
|
var MAX_MEMORY_CONTENT_CHARS = 4e3;
|
|
697
770
|
var EMBEDDING_METRIC = "cosine";
|
|
771
|
+
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
772
|
+
var RELEVANCE_NEAR_TIE_DELTA = 0.01;
|
|
773
|
+
var SUPERSESSION_STOP_TERMS = /* @__PURE__ */ new Set([
|
|
774
|
+
"and",
|
|
775
|
+
"for",
|
|
776
|
+
"prefer",
|
|
777
|
+
"prefers",
|
|
778
|
+
"preference",
|
|
779
|
+
"the",
|
|
780
|
+
"use",
|
|
781
|
+
"uses",
|
|
782
|
+
"with"
|
|
783
|
+
]);
|
|
698
784
|
var nonEmptyStringSchema2 = z3.string().min(1);
|
|
699
785
|
var memoryContentSchema = z3.string().refine((content) => content.trim().length > 0, {
|
|
700
786
|
message: "Memory content is required."
|
|
@@ -800,6 +886,9 @@ function normalizeContent(content) {
|
|
|
800
886
|
function hashEmbeddedContent(content) {
|
|
801
887
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
802
888
|
}
|
|
889
|
+
function idempotencyAliasId(args) {
|
|
890
|
+
return `alias:${createHash("sha256").update(args.scope.scope).update("\0").update(args.scope.scopeKey).update("\0").update(args.idempotencyKey).update("\0").update(args.targetId).digest("hex")}`;
|
|
891
|
+
}
|
|
803
892
|
function boundedLimit(value, fallback) {
|
|
804
893
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
805
894
|
return fallback;
|
|
@@ -865,7 +954,7 @@ function activeVisiblePredicate(args) {
|
|
|
865
954
|
);
|
|
866
955
|
}
|
|
867
956
|
async function findByIdempotencyKey(args) {
|
|
868
|
-
const
|
|
957
|
+
const activeRows = await args.db.select().from(juniorMemoryMemories).where(
|
|
869
958
|
and2(
|
|
870
959
|
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
871
960
|
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
@@ -879,7 +968,49 @@ async function findByIdempotencyKey(args) {
|
|
|
879
968
|
)
|
|
880
969
|
)
|
|
881
970
|
).limit(1);
|
|
882
|
-
|
|
971
|
+
if (activeRows[0]) {
|
|
972
|
+
return parseMemoryRow(activeRows[0]);
|
|
973
|
+
}
|
|
974
|
+
const aliasRows = await args.db.select({ supersededById: juniorMemoryMemories.supersededById }).from(juniorMemoryMemories).where(
|
|
975
|
+
and2(
|
|
976
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
977
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
978
|
+
eq3(juniorMemoryMemories.idempotencyKey, args.idempotencyKey),
|
|
979
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
980
|
+
isNotNull(juniorMemoryMemories.supersededAtMs),
|
|
981
|
+
isNotNull(juniorMemoryMemories.supersededById),
|
|
982
|
+
or2(
|
|
983
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
984
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
985
|
+
)
|
|
986
|
+
)
|
|
987
|
+
).orderBy(
|
|
988
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
989
|
+
asc(juniorMemoryMemories.id)
|
|
990
|
+
);
|
|
991
|
+
for (const alias of aliasRows) {
|
|
992
|
+
if (!alias.supersededById) {
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
const rows = await args.db.select().from(juniorMemoryMemories).where(
|
|
996
|
+
and2(
|
|
997
|
+
eq3(juniorMemoryMemories.id, alias.supersededById),
|
|
998
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
999
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
1000
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
1001
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
1002
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
1003
|
+
or2(
|
|
1004
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
1005
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
).limit(1);
|
|
1009
|
+
if (rows[0]) {
|
|
1010
|
+
return parseMemoryRow(rows[0]);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return void 0;
|
|
883
1014
|
}
|
|
884
1015
|
async function archiveExpiredMemoryBatch(args) {
|
|
885
1016
|
const scopePredicate = visibleScopePredicate(args.scopes);
|
|
@@ -951,7 +1082,7 @@ async function embedOne(embedder, text2) {
|
|
|
951
1082
|
};
|
|
952
1083
|
}
|
|
953
1084
|
async function storeEmbedding(args) {
|
|
954
|
-
if (!args.embedder) {
|
|
1085
|
+
if (!args.embedder && !args.embedding) {
|
|
955
1086
|
return;
|
|
956
1087
|
}
|
|
957
1088
|
try {
|
|
@@ -963,10 +1094,18 @@ async function storeEmbedding(args) {
|
|
|
963
1094
|
return;
|
|
964
1095
|
}
|
|
965
1096
|
let embedding;
|
|
966
|
-
|
|
967
|
-
embedding =
|
|
968
|
-
}
|
|
969
|
-
|
|
1097
|
+
if (args.embedding) {
|
|
1098
|
+
embedding = args.embedding;
|
|
1099
|
+
} else {
|
|
1100
|
+
const embedder = args.embedder;
|
|
1101
|
+
if (!embedder) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
try {
|
|
1105
|
+
embedding = await embedOne(embedder, args.content);
|
|
1106
|
+
} catch {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
970
1109
|
}
|
|
971
1110
|
try {
|
|
972
1111
|
await args.db.insert(juniorMemoryEmbeddings).values({
|
|
@@ -983,6 +1122,192 @@ async function storeEmbedding(args) {
|
|
|
983
1122
|
return;
|
|
984
1123
|
}
|
|
985
1124
|
}
|
|
1125
|
+
function activeScopedSubjectPredicate(args) {
|
|
1126
|
+
const predicate = and2(
|
|
1127
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
1128
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
1129
|
+
eq3(juniorMemoryMemories.kind, args.kind),
|
|
1130
|
+
eq3(juniorMemoryMemories.subjectType, args.subject.subjectType),
|
|
1131
|
+
args.subject.subjectKey === void 0 ? isNull2(juniorMemoryMemories.subjectKey) : eq3(juniorMemoryMemories.subjectKey, args.subject.subjectKey),
|
|
1132
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
1133
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
1134
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
1135
|
+
or2(
|
|
1136
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
1137
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
1138
|
+
)
|
|
1139
|
+
);
|
|
1140
|
+
if (!predicate) {
|
|
1141
|
+
throw new Error("Memory duplicate predicate is empty.");
|
|
1142
|
+
}
|
|
1143
|
+
return predicate;
|
|
1144
|
+
}
|
|
1145
|
+
async function findExactDuplicateMemory(args) {
|
|
1146
|
+
const rows = await args.db.select().from(juniorMemoryMemories).where(
|
|
1147
|
+
and2(
|
|
1148
|
+
activeScopedSubjectPredicate(args),
|
|
1149
|
+
eq3(juniorMemoryMemories.content, args.content)
|
|
1150
|
+
)
|
|
1151
|
+
).orderBy(
|
|
1152
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1153
|
+
asc(juniorMemoryMemories.id)
|
|
1154
|
+
).limit(1);
|
|
1155
|
+
return rows[0] ? parseMemoryRow(rows[0]) : void 0;
|
|
1156
|
+
}
|
|
1157
|
+
async function findVectorDuplicateMemory(args) {
|
|
1158
|
+
const distance = cosineDistance(
|
|
1159
|
+
juniorMemoryEmbeddings.embedding,
|
|
1160
|
+
args.embedding.vector
|
|
1161
|
+
);
|
|
1162
|
+
const rows = await args.db.select({
|
|
1163
|
+
contentHash: juniorMemoryEmbeddings.contentHash,
|
|
1164
|
+
distance,
|
|
1165
|
+
memory: juniorMemoryMemories
|
|
1166
|
+
}).from(juniorMemoryMemories).innerJoin(
|
|
1167
|
+
juniorMemoryEmbeddings,
|
|
1168
|
+
eq3(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
1169
|
+
).where(
|
|
1170
|
+
and2(
|
|
1171
|
+
activeScopedSubjectPredicate(args),
|
|
1172
|
+
eq3(juniorMemoryEmbeddings.provider, args.embedding.provider),
|
|
1173
|
+
eq3(juniorMemoryEmbeddings.model, args.embedding.model),
|
|
1174
|
+
eq3(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
|
|
1175
|
+
eq3(juniorMemoryEmbeddings.metric, EMBEDDING_METRIC),
|
|
1176
|
+
lte(distance, HIGH_CONFIDENCE_DUPLICATE_DISTANCE)
|
|
1177
|
+
)
|
|
1178
|
+
).orderBy(
|
|
1179
|
+
distance,
|
|
1180
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1181
|
+
asc(juniorMemoryMemories.id)
|
|
1182
|
+
).limit(1);
|
|
1183
|
+
const row = rows[0];
|
|
1184
|
+
if (!row || hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
1185
|
+
return void 0;
|
|
1186
|
+
}
|
|
1187
|
+
return parseMemoryRow(row.memory);
|
|
1188
|
+
}
|
|
1189
|
+
async function rememberDuplicateIdempotency(args) {
|
|
1190
|
+
if (args.idempotencyKey === void 0) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
await args.db.insert(juniorMemoryMemories).values({
|
|
1194
|
+
content: args.content,
|
|
1195
|
+
createdAtMs: args.nowMs,
|
|
1196
|
+
expiresAtMs: args.duplicate.expiresAtMs,
|
|
1197
|
+
id: idempotencyAliasId({
|
|
1198
|
+
idempotencyKey: args.idempotencyKey,
|
|
1199
|
+
scope: args.scope,
|
|
1200
|
+
targetId: args.duplicate.id
|
|
1201
|
+
}),
|
|
1202
|
+
idempotencyKey: args.idempotencyKey,
|
|
1203
|
+
observedAtMs: args.nowMs,
|
|
1204
|
+
scope: args.scope.scope,
|
|
1205
|
+
scopeKey: args.scope.scopeKey,
|
|
1206
|
+
sourceKey: sourceKey(args.runtimeContext),
|
|
1207
|
+
sourcePlatform: args.runtimeContext.source.platform,
|
|
1208
|
+
subjectKey: args.subject.subjectKey,
|
|
1209
|
+
subjectType: args.subject.subjectType,
|
|
1210
|
+
supersededAtMs: args.nowMs,
|
|
1211
|
+
supersededById: args.duplicate.id,
|
|
1212
|
+
kind: args.duplicate.kind
|
|
1213
|
+
}).onConflictDoNothing();
|
|
1214
|
+
}
|
|
1215
|
+
async function listSupersessionCandidates(args) {
|
|
1216
|
+
const predicate = activeScopedSubjectPredicate(args);
|
|
1217
|
+
const terms = searchTerms(args.content).filter(
|
|
1218
|
+
(term) => !SUPERSESSION_STOP_TERMS.has(term)
|
|
1219
|
+
);
|
|
1220
|
+
const lexicalRows = terms.length > 0 ? await args.db.select().from(juniorMemoryMemories).where(
|
|
1221
|
+
and2(
|
|
1222
|
+
predicate,
|
|
1223
|
+
or2(
|
|
1224
|
+
...terms.map(
|
|
1225
|
+
(term) => ilike2(juniorMemoryMemories.content, `%${term}%`)
|
|
1226
|
+
)
|
|
1227
|
+
)
|
|
1228
|
+
)
|
|
1229
|
+
) : [];
|
|
1230
|
+
const lexicalCandidates = lexicalRows.map(parseMemoryRow).map((memory) => ({
|
|
1231
|
+
memory,
|
|
1232
|
+
score: searchScore(memory, terms)
|
|
1233
|
+
})).filter((candidate) => candidate.score > 1).sort(
|
|
1234
|
+
(left, right) => right.score - left.score || right.memory.createdAtMs - left.memory.createdAtMs || left.memory.id.localeCompare(right.memory.id)
|
|
1235
|
+
).map((candidate) => candidate.memory);
|
|
1236
|
+
const vectorCandidates = args.embedding ? await listVectorSupersessionCandidates({
|
|
1237
|
+
db: args.db,
|
|
1238
|
+
embedding: args.embedding,
|
|
1239
|
+
kind: args.kind,
|
|
1240
|
+
nowMs: args.nowMs,
|
|
1241
|
+
scope: args.scope,
|
|
1242
|
+
subject: args.subject
|
|
1243
|
+
}) : [];
|
|
1244
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1245
|
+
for (const memory of [...lexicalCandidates, ...vectorCandidates]) {
|
|
1246
|
+
if (byId.size >= SUPERSESSION_CANDIDATE_LIMIT) {
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
byId.set(memory.id, memory);
|
|
1250
|
+
}
|
|
1251
|
+
return [...byId.values()];
|
|
1252
|
+
}
|
|
1253
|
+
async function listVectorSupersessionCandidates(args) {
|
|
1254
|
+
const distance = cosineDistance(
|
|
1255
|
+
juniorMemoryEmbeddings.embedding,
|
|
1256
|
+
args.embedding.vector
|
|
1257
|
+
);
|
|
1258
|
+
const rows = await args.db.select({
|
|
1259
|
+
contentHash: juniorMemoryEmbeddings.contentHash,
|
|
1260
|
+
distance,
|
|
1261
|
+
memory: juniorMemoryMemories
|
|
1262
|
+
}).from(juniorMemoryMemories).innerJoin(
|
|
1263
|
+
juniorMemoryEmbeddings,
|
|
1264
|
+
eq3(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
1265
|
+
).where(
|
|
1266
|
+
and2(
|
|
1267
|
+
activeScopedSubjectPredicate(args),
|
|
1268
|
+
eq3(juniorMemoryEmbeddings.provider, args.embedding.provider),
|
|
1269
|
+
eq3(juniorMemoryEmbeddings.model, args.embedding.model),
|
|
1270
|
+
eq3(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
|
|
1271
|
+
eq3(juniorMemoryEmbeddings.metric, EMBEDDING_METRIC)
|
|
1272
|
+
)
|
|
1273
|
+
).orderBy(
|
|
1274
|
+
distance,
|
|
1275
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1276
|
+
asc(juniorMemoryMemories.id)
|
|
1277
|
+
).limit(SUPERSESSION_CANDIDATE_LIMIT);
|
|
1278
|
+
return rows.flatMap((row) => {
|
|
1279
|
+
if (hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
1280
|
+
return [];
|
|
1281
|
+
}
|
|
1282
|
+
return [parseMemoryRow(row.memory)];
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
async function decideSupersededIds(args) {
|
|
1286
|
+
if (args.kind !== "preference" || !args.decider || args.candidates.length === 0) {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
const existingMemories = args.candidates.map((memory) => ({
|
|
1290
|
+
content: memory.content,
|
|
1291
|
+
id: memory.id
|
|
1292
|
+
}));
|
|
1293
|
+
const candidateIds = new Set(args.candidates.map((memory) => memory.id));
|
|
1294
|
+
try {
|
|
1295
|
+
const decision = await args.decider.adjudicateSupersession({
|
|
1296
|
+
candidate: {
|
|
1297
|
+
content: args.content,
|
|
1298
|
+
kind: "preference"
|
|
1299
|
+
},
|
|
1300
|
+
existingMemories,
|
|
1301
|
+
runtimeContext: args.runtimeContext
|
|
1302
|
+
});
|
|
1303
|
+
if (decision.decision !== "supersedes_old") {
|
|
1304
|
+
return [];
|
|
1305
|
+
}
|
|
1306
|
+
return decision.supersededIds.filter((id) => candidateIds.has(id));
|
|
1307
|
+
} catch {
|
|
1308
|
+
return [];
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
986
1311
|
async function listVisibleMemories(args) {
|
|
987
1312
|
const predicate = activeVisiblePredicate(args);
|
|
988
1313
|
if (!predicate) {
|
|
@@ -1067,7 +1392,20 @@ async function searchVisibleVectorMemories(args) {
|
|
|
1067
1392
|
];
|
|
1068
1393
|
});
|
|
1069
1394
|
}
|
|
1070
|
-
function
|
|
1395
|
+
function observedAgeBoost(memory, nowMs) {
|
|
1396
|
+
const ageMs = Math.max(0, nowMs - memory.observedAtMs);
|
|
1397
|
+
if (ageMs <= 7 * ONE_DAY_MS) {
|
|
1398
|
+
return 0.15;
|
|
1399
|
+
}
|
|
1400
|
+
if (ageMs <= 30 * ONE_DAY_MS) {
|
|
1401
|
+
return 0.1;
|
|
1402
|
+
}
|
|
1403
|
+
if (ageMs <= 90 * ONE_DAY_MS) {
|
|
1404
|
+
return 0.05;
|
|
1405
|
+
}
|
|
1406
|
+
return 0;
|
|
1407
|
+
}
|
|
1408
|
+
function mergeSearchCandidates(candidates, nowMs) {
|
|
1071
1409
|
const byId = /* @__PURE__ */ new Map();
|
|
1072
1410
|
for (const candidate of candidates) {
|
|
1073
1411
|
const existing = byId.get(candidate.memory.id);
|
|
@@ -1080,14 +1418,19 @@ function mergeSearchCandidates(candidates) {
|
|
|
1080
1418
|
score: candidate.score
|
|
1081
1419
|
});
|
|
1082
1420
|
}
|
|
1083
|
-
return [...byId.values()].sort(
|
|
1084
|
-
|
|
1085
|
-
|
|
1421
|
+
return [...byId.values()].sort((left, right) => {
|
|
1422
|
+
const scoreDelta = right.score - left.score;
|
|
1423
|
+
if (Math.abs(scoreDelta) > RELEVANCE_NEAR_TIE_DELTA) {
|
|
1424
|
+
return scoreDelta;
|
|
1425
|
+
}
|
|
1426
|
+
return observedAgeBoost(right.memory, nowMs) - observedAgeBoost(left.memory, nowMs) || right.memory.observedAtMs - left.memory.observedAtMs || left.memory.id.localeCompare(right.memory.id);
|
|
1427
|
+
}).map((candidate) => candidate.memory);
|
|
1086
1428
|
}
|
|
1087
1429
|
function createMemoryStore(db, context, options = {}) {
|
|
1088
1430
|
const runtimeContext = memoryRuntimeContextSchema.parse(context);
|
|
1089
1431
|
const parsedOptions = memoryStoreOptionsSchema.parse({ now: options.now });
|
|
1090
1432
|
const embedder = options.embedder;
|
|
1433
|
+
const supersessionDecider = options.supersessionDecider;
|
|
1091
1434
|
const getNowMs = parsedOptions.now ?? Date.now;
|
|
1092
1435
|
async function archiveExpiredVisibleMemories(input, nowMs) {
|
|
1093
1436
|
input = archiveExpiredMemoriesInputSchema.parse(input ?? {});
|
|
@@ -1119,35 +1462,162 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1119
1462
|
nowMs,
|
|
1120
1463
|
scopes: [scope]
|
|
1121
1464
|
});
|
|
1122
|
-
|
|
1123
|
-
|
|
1465
|
+
if (input.idempotencyKey !== void 0) {
|
|
1466
|
+
const idempotent2 = await findByIdempotencyKey({
|
|
1467
|
+
db,
|
|
1468
|
+
idempotencyKey: input.idempotencyKey,
|
|
1469
|
+
nowMs,
|
|
1470
|
+
scope
|
|
1471
|
+
});
|
|
1472
|
+
if (idempotent2) {
|
|
1473
|
+
await storeEmbedding({
|
|
1474
|
+
content: idempotent2.content,
|
|
1475
|
+
db,
|
|
1476
|
+
embedder,
|
|
1477
|
+
memoryId: idempotent2.id,
|
|
1478
|
+
nowMs
|
|
1479
|
+
});
|
|
1480
|
+
return { created: false, memory: idempotent2 };
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const exactDuplicate = await findExactDuplicateMemory({
|
|
1124
1484
|
content,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1485
|
+
db,
|
|
1486
|
+
kind: input.kind,
|
|
1487
|
+
nowMs,
|
|
1488
|
+
scope,
|
|
1489
|
+
subject
|
|
1490
|
+
});
|
|
1491
|
+
if (exactDuplicate) {
|
|
1492
|
+
await rememberDuplicateIdempotency({
|
|
1493
|
+
content,
|
|
1494
|
+
db,
|
|
1495
|
+
duplicate: exactDuplicate,
|
|
1496
|
+
idempotencyKey: input.idempotencyKey,
|
|
1497
|
+
nowMs,
|
|
1498
|
+
runtimeContext,
|
|
1499
|
+
scope,
|
|
1500
|
+
subject
|
|
1501
|
+
});
|
|
1502
|
+
await storeEmbedding({
|
|
1503
|
+
content: exactDuplicate.content,
|
|
1504
|
+
db,
|
|
1505
|
+
embedder,
|
|
1506
|
+
memoryId: exactDuplicate.id,
|
|
1507
|
+
nowMs
|
|
1508
|
+
});
|
|
1509
|
+
return { created: false, memory: exactDuplicate };
|
|
1510
|
+
}
|
|
1511
|
+
let candidateEmbedding;
|
|
1512
|
+
if (embedder) {
|
|
1513
|
+
try {
|
|
1514
|
+
candidateEmbedding = await embedOne(embedder, content);
|
|
1515
|
+
} catch {
|
|
1516
|
+
candidateEmbedding = void 0;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const vectorDuplicate = candidateEmbedding ? await findVectorDuplicateMemory({
|
|
1520
|
+
db,
|
|
1521
|
+
embedding: candidateEmbedding,
|
|
1522
|
+
kind: input.kind,
|
|
1523
|
+
nowMs,
|
|
1524
|
+
scope,
|
|
1525
|
+
subject
|
|
1526
|
+
}) : void 0;
|
|
1527
|
+
if (vectorDuplicate) {
|
|
1528
|
+
await rememberDuplicateIdempotency({
|
|
1529
|
+
content,
|
|
1530
|
+
db,
|
|
1531
|
+
duplicate: vectorDuplicate,
|
|
1532
|
+
idempotencyKey: input.idempotencyKey,
|
|
1533
|
+
nowMs,
|
|
1534
|
+
runtimeContext,
|
|
1535
|
+
scope,
|
|
1536
|
+
subject
|
|
1537
|
+
});
|
|
1538
|
+
await storeEmbedding({
|
|
1539
|
+
content: vectorDuplicate.content,
|
|
1540
|
+
db,
|
|
1541
|
+
embedder,
|
|
1542
|
+
memoryId: vectorDuplicate.id,
|
|
1543
|
+
nowMs
|
|
1544
|
+
});
|
|
1545
|
+
return { created: false, memory: vectorDuplicate };
|
|
1546
|
+
}
|
|
1547
|
+
let supersededIds = [];
|
|
1548
|
+
if (scopeKind === "personal" && input.kind === "preference" && supersessionDecider && (input.expiresAtMs === void 0 || input.expiresAtMs > nowMs)) {
|
|
1549
|
+
const supersessionCandidates = await listSupersessionCandidates({
|
|
1550
|
+
content,
|
|
1551
|
+
db,
|
|
1552
|
+
...candidateEmbedding ? { embedding: candidateEmbedding } : {},
|
|
1553
|
+
kind: input.kind,
|
|
1554
|
+
nowMs,
|
|
1555
|
+
scope,
|
|
1556
|
+
subject
|
|
1557
|
+
});
|
|
1558
|
+
supersededIds = await decideSupersededIds({
|
|
1559
|
+
candidates: supersessionCandidates,
|
|
1560
|
+
content,
|
|
1561
|
+
decider: supersessionDecider,
|
|
1562
|
+
kind: input.kind,
|
|
1563
|
+
runtimeContext
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
const id = randomUUID();
|
|
1567
|
+
const rows = await db.transaction(async (tx) => {
|
|
1568
|
+
const inserted = await tx.insert(juniorMemoryMemories).values({
|
|
1569
|
+
content,
|
|
1570
|
+
createdAtMs: nowMs,
|
|
1571
|
+
expiresAtMs: input.expiresAtMs,
|
|
1572
|
+
id,
|
|
1573
|
+
idempotencyKey: input.idempotencyKey,
|
|
1574
|
+
observedAtMs: nowMs,
|
|
1575
|
+
scope: scope.scope,
|
|
1576
|
+
scopeKey: scope.scopeKey,
|
|
1577
|
+
sourceKey: sourceKey(runtimeContext),
|
|
1578
|
+
sourcePlatform: runtimeContext.source.platform,
|
|
1579
|
+
subjectKey: subject.subjectKey,
|
|
1580
|
+
subjectType: subject.subjectType,
|
|
1581
|
+
kind: input.kind
|
|
1582
|
+
}).onConflictDoNothing({
|
|
1583
|
+
target: [
|
|
1584
|
+
juniorMemoryMemories.scope,
|
|
1585
|
+
juniorMemoryMemories.scopeKey,
|
|
1586
|
+
juniorMemoryMemories.idempotencyKey
|
|
1587
|
+
],
|
|
1588
|
+
where: sql2`${juniorMemoryMemories.idempotencyKey} IS NOT NULL AND ${juniorMemoryMemories.archivedAtMs} IS NULL AND ${juniorMemoryMemories.supersededAtMs} IS NULL AND ${juniorMemoryMemories.supersededById} IS NULL`
|
|
1589
|
+
}).returning();
|
|
1590
|
+
const insertedMemory = inserted[0];
|
|
1591
|
+
if (!insertedMemory || supersededIds.length === 0) {
|
|
1592
|
+
return inserted;
|
|
1593
|
+
}
|
|
1594
|
+
const superseded = await tx.update(juniorMemoryMemories).set({
|
|
1595
|
+
supersededAtMs: nowMs,
|
|
1596
|
+
supersededById: insertedMemory.id
|
|
1597
|
+
}).where(
|
|
1598
|
+
and2(
|
|
1599
|
+
inArray(juniorMemoryMemories.id, supersededIds),
|
|
1600
|
+
activeScopedSubjectPredicate({
|
|
1601
|
+
kind: input.kind,
|
|
1602
|
+
nowMs,
|
|
1603
|
+
scope,
|
|
1604
|
+
subject
|
|
1605
|
+
})
|
|
1606
|
+
)
|
|
1607
|
+
).returning({ id: juniorMemoryMemories.id });
|
|
1608
|
+
const idsToClean = superseded.map((row) => row.id);
|
|
1609
|
+
if (idsToClean.length > 0) {
|
|
1610
|
+
await tx.delete(juniorMemoryEmbeddings).where(inArray(juniorMemoryEmbeddings.memoryId, idsToClean));
|
|
1611
|
+
}
|
|
1612
|
+
return inserted;
|
|
1613
|
+
});
|
|
1145
1614
|
if (rows[0]) {
|
|
1146
1615
|
const memory = parseMemoryRow(rows[0]);
|
|
1147
1616
|
await storeEmbedding({
|
|
1148
1617
|
content: memory.content,
|
|
1149
1618
|
db,
|
|
1150
1619
|
embedder,
|
|
1620
|
+
embedding: candidateEmbedding,
|
|
1151
1621
|
memoryId: memory.id,
|
|
1152
1622
|
nowMs
|
|
1153
1623
|
});
|
|
@@ -1223,10 +1693,10 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1223
1693
|
});
|
|
1224
1694
|
const terms = searchTerms(input.query);
|
|
1225
1695
|
const lexicalCandidates = candidates.map((memory) => ({ memory, score: searchScore(memory, terms) })).filter((item) => item.score > 0);
|
|
1226
|
-
return mergeSearchCandidates(
|
|
1227
|
-
...vectorCandidates,
|
|
1228
|
-
|
|
1229
|
-
|
|
1696
|
+
return mergeSearchCandidates(
|
|
1697
|
+
[...vectorCandidates, ...lexicalCandidates],
|
|
1698
|
+
nowMs
|
|
1699
|
+
).slice(0, limit);
|
|
1230
1700
|
},
|
|
1231
1701
|
async archiveMemory(input) {
|
|
1232
1702
|
input = archiveMemoryInputSchema.parse(input);
|
|
@@ -1297,9 +1767,10 @@ function memoryRuntimeContext(context) {
|
|
|
1297
1767
|
source: context.source
|
|
1298
1768
|
});
|
|
1299
1769
|
}
|
|
1300
|
-
function memoryStore(context) {
|
|
1770
|
+
function memoryStore(context, options = {}) {
|
|
1301
1771
|
return createMemoryStore(context.db, memoryRuntimeContext(context), {
|
|
1302
|
-
embedder: context.embedder
|
|
1772
|
+
embedder: context.embedder,
|
|
1773
|
+
...options.supersessionDecider ? { supersessionDecider: options.supersessionDecider } : {}
|
|
1303
1774
|
});
|
|
1304
1775
|
}
|
|
1305
1776
|
function boundedLimit2(value, fallback) {
|
|
@@ -1442,6 +1913,16 @@ var searchMemoriesInputSchema2 = Type.Object(
|
|
|
1442
1913
|
},
|
|
1443
1914
|
{ additionalProperties: false }
|
|
1444
1915
|
);
|
|
1916
|
+
var memoryToolProjectionSchema = Type.Object(
|
|
1917
|
+
{
|
|
1918
|
+
id: Type.String({ minLength: 1 }),
|
|
1919
|
+
content: Type.String({ minLength: 1 }),
|
|
1920
|
+
createdAtMs: Type.Number(),
|
|
1921
|
+
observedAtMs: Type.Number(),
|
|
1922
|
+
expiresAtMs: Type.Optional(Type.Number())
|
|
1923
|
+
},
|
|
1924
|
+
{ additionalProperties: false }
|
|
1925
|
+
);
|
|
1445
1926
|
function parseToolInput(schema, input) {
|
|
1446
1927
|
try {
|
|
1447
1928
|
if (!Value.Check(schema, input)) {
|
|
@@ -1476,12 +1957,13 @@ function targetForKind(kind) {
|
|
|
1476
1957
|
return "conversation";
|
|
1477
1958
|
}
|
|
1478
1959
|
function compactMemory(memory) {
|
|
1479
|
-
return {
|
|
1960
|
+
return Value.Parse(memoryToolProjectionSchema, {
|
|
1480
1961
|
id: memory.id,
|
|
1481
1962
|
content: memory.content,
|
|
1482
1963
|
createdAtMs: memory.createdAtMs,
|
|
1964
|
+
observedAtMs: memory.observedAtMs,
|
|
1483
1965
|
...memory.expiresAtMs !== void 0 ? { expiresAtMs: memory.expiresAtMs } : {}
|
|
1484
|
-
};
|
|
1966
|
+
});
|
|
1485
1967
|
}
|
|
1486
1968
|
function createMemoryCreateTool(context) {
|
|
1487
1969
|
return {
|
|
@@ -1496,7 +1978,9 @@ function createMemoryCreateTool(context) {
|
|
|
1496
1978
|
const toolCallId = requireToolCallId(options.toolCallId);
|
|
1497
1979
|
const requestedExpiresAtMs = parseExpiresAt(parsedInput.expires_at);
|
|
1498
1980
|
const runtimeContext = memoryRuntimeContext(context);
|
|
1499
|
-
const store = memoryStore(context
|
|
1981
|
+
const store = memoryStore(context, {
|
|
1982
|
+
supersessionDecider: context.supersessionDecider
|
|
1983
|
+
});
|
|
1500
1984
|
const review = await (async () => {
|
|
1501
1985
|
try {
|
|
1502
1986
|
return parseMemoryReview(
|
|
@@ -1699,8 +2183,10 @@ async function processMemorySession(context) {
|
|
|
1699
2183
|
...run.requester ? { requester: run.requester } : {},
|
|
1700
2184
|
source: run.source
|
|
1701
2185
|
});
|
|
2186
|
+
const agent = createMemoryAgent(context.model);
|
|
1702
2187
|
const store = createMemoryStore(context.db, runtimeContext, {
|
|
1703
|
-
embedder: context.embedder
|
|
2188
|
+
embedder: context.embedder,
|
|
2189
|
+
supersessionDecider: agent
|
|
1704
2190
|
});
|
|
1705
2191
|
await store.archiveExpiredMemories();
|
|
1706
2192
|
const memories = await getTaskMemories(context, async () => {
|
|
@@ -1708,7 +2194,6 @@ async function processMemorySession(context) {
|
|
|
1708
2194
|
limit: 10,
|
|
1709
2195
|
query: evidenceText
|
|
1710
2196
|
});
|
|
1711
|
-
const agent = createMemoryAgent(context.model);
|
|
1712
2197
|
return await agent.extractSessionMemories({
|
|
1713
2198
|
existingMemories: existingMemories.map((memory) => ({
|
|
1714
2199
|
content: memory.content
|
|
@@ -1744,13 +2229,19 @@ function trimContent(content, maxLength) {
|
|
|
1744
2229
|
}
|
|
1745
2230
|
return `${trimmed.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
1746
2231
|
}
|
|
2232
|
+
function formatObservedDate(observedAtMs) {
|
|
2233
|
+
return new Date(observedAtMs).toISOString().slice(0, 10);
|
|
2234
|
+
}
|
|
1747
2235
|
function renderMemoryPrompt(memories) {
|
|
1748
2236
|
const header = "Relevant memories for this request:";
|
|
1749
2237
|
const footer = "Treat these as possibly stale context. Current user instructions and repository evidence take priority.";
|
|
1750
2238
|
const lines = [];
|
|
1751
2239
|
let totalChars = header.length + footer.length + 2;
|
|
1752
2240
|
for (const memory of memories) {
|
|
1753
|
-
const line = `- ${
|
|
2241
|
+
const line = `- Observed ${formatObservedDate(memory.observedAtMs)}: ${trimContent(
|
|
2242
|
+
memory.content,
|
|
2243
|
+
MAX_MEMORY_LINE_CHARS
|
|
2244
|
+
)}`;
|
|
1754
2245
|
if (totalChars + line.length + 1 > MAX_PROMPT_CHARS) {
|
|
1755
2246
|
break;
|
|
1756
2247
|
}
|
|
@@ -1807,6 +2298,12 @@ function memoryToolContext(ctx) {
|
|
|
1807
2298
|
...ctx.userText ? { userText: ctx.userText } : {}
|
|
1808
2299
|
};
|
|
1809
2300
|
}
|
|
2301
|
+
function memoryCreateToolContext(ctx) {
|
|
2302
|
+
return {
|
|
2303
|
+
...memoryToolContext(ctx),
|
|
2304
|
+
supersessionDecider: ctx.supersessionDecider
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
1810
2307
|
function createMemoryPlugin(options = {}) {
|
|
1811
2308
|
const modelId = memoryModelId(options);
|
|
1812
2309
|
return defineJuniorPlugin({
|
|
@@ -1829,14 +2326,23 @@ function createMemoryPlugin(options = {}) {
|
|
|
1829
2326
|
},
|
|
1830
2327
|
hooks: {
|
|
1831
2328
|
tools(ctx) {
|
|
2329
|
+
const agent = createMemoryAgent(ctx.model);
|
|
1832
2330
|
const context = memoryToolContext({
|
|
1833
2331
|
...ctx,
|
|
1834
|
-
agent
|
|
2332
|
+
agent,
|
|
1835
2333
|
db: ctx.db,
|
|
1836
2334
|
embedder: ctx.embedder
|
|
1837
2335
|
});
|
|
1838
2336
|
return {
|
|
1839
|
-
createMemory: createMemoryCreateTool(
|
|
2337
|
+
createMemory: createMemoryCreateTool(
|
|
2338
|
+
memoryCreateToolContext({
|
|
2339
|
+
...ctx,
|
|
2340
|
+
agent,
|
|
2341
|
+
db: ctx.db,
|
|
2342
|
+
embedder: ctx.embedder,
|
|
2343
|
+
supersessionDecider: agent
|
|
2344
|
+
})
|
|
2345
|
+
),
|
|
1840
2346
|
removeMemory: createMemoryRemoveTool(context),
|
|
1841
2347
|
listMemories: createMemoryListTool(context),
|
|
1842
2348
|
searchMemories: createMemorySearchTool(context)
|