@sentry/junior-memory 0.80.1 → 0.82.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 +611 -54
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +2 -0
- package/dist/recall.d.ts +2 -0
- package/dist/store.d.ts +30 -0
- package/dist/tools.d.ts +9 -2
- package/package.json +2 -2
- package/src/agent.ts +92 -0
- package/src/plugin.ts +47 -2
- package/src/process-session.ts +2 -1
- package/src/recall.ts +18 -4
- package/src/store.ts +686 -51
- 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,25 @@ 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 SOURCE_CHANNEL_BOOST = 0.05;
|
|
774
|
+
var SUPERSESSION_STOP_TERMS = /* @__PURE__ */ new Set([
|
|
775
|
+
"and",
|
|
776
|
+
"for",
|
|
777
|
+
"prefer",
|
|
778
|
+
"prefers",
|
|
779
|
+
"preference",
|
|
780
|
+
"the",
|
|
781
|
+
"use",
|
|
782
|
+
"uses",
|
|
783
|
+
"with"
|
|
784
|
+
]);
|
|
698
785
|
var nonEmptyStringSchema2 = z3.string().min(1);
|
|
699
786
|
var memoryContentSchema = z3.string().refine((content) => content.trim().length > 0, {
|
|
700
787
|
message: "Memory content is required."
|
|
@@ -800,6 +887,9 @@ function normalizeContent(content) {
|
|
|
800
887
|
function hashEmbeddedContent(content) {
|
|
801
888
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
802
889
|
}
|
|
890
|
+
function idempotencyAliasId(args) {
|
|
891
|
+
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")}`;
|
|
892
|
+
}
|
|
803
893
|
function boundedLimit(value, fallback) {
|
|
804
894
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
805
895
|
return fallback;
|
|
@@ -818,6 +908,12 @@ function sourceKey(ctx) {
|
|
|
818
908
|
}
|
|
819
909
|
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:${threadKey}`;
|
|
820
910
|
}
|
|
911
|
+
function sourceChannelPrefix(ctx) {
|
|
912
|
+
if (ctx.source.platform !== "slack") {
|
|
913
|
+
return void 0;
|
|
914
|
+
}
|
|
915
|
+
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:`;
|
|
916
|
+
}
|
|
821
917
|
function parseMemoryRow(row) {
|
|
822
918
|
const parsed = memoryRowSchema.parse(row);
|
|
823
919
|
return memoryRecordSchema.parse({
|
|
@@ -865,7 +961,7 @@ function activeVisiblePredicate(args) {
|
|
|
865
961
|
);
|
|
866
962
|
}
|
|
867
963
|
async function findByIdempotencyKey(args) {
|
|
868
|
-
const
|
|
964
|
+
const activeRows = await args.db.select().from(juniorMemoryMemories).where(
|
|
869
965
|
and2(
|
|
870
966
|
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
871
967
|
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
@@ -879,7 +975,49 @@ async function findByIdempotencyKey(args) {
|
|
|
879
975
|
)
|
|
880
976
|
)
|
|
881
977
|
).limit(1);
|
|
882
|
-
|
|
978
|
+
if (activeRows[0]) {
|
|
979
|
+
return parseMemoryRow(activeRows[0]);
|
|
980
|
+
}
|
|
981
|
+
const aliasRows = await args.db.select({ supersededById: juniorMemoryMemories.supersededById }).from(juniorMemoryMemories).where(
|
|
982
|
+
and2(
|
|
983
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
984
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
985
|
+
eq3(juniorMemoryMemories.idempotencyKey, args.idempotencyKey),
|
|
986
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
987
|
+
isNotNull(juniorMemoryMemories.supersededAtMs),
|
|
988
|
+
isNotNull(juniorMemoryMemories.supersededById),
|
|
989
|
+
or2(
|
|
990
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
991
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
992
|
+
)
|
|
993
|
+
)
|
|
994
|
+
).orderBy(
|
|
995
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
996
|
+
asc(juniorMemoryMemories.id)
|
|
997
|
+
);
|
|
998
|
+
for (const alias of aliasRows) {
|
|
999
|
+
if (!alias.supersededById) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const rows = await args.db.select().from(juniorMemoryMemories).where(
|
|
1003
|
+
and2(
|
|
1004
|
+
eq3(juniorMemoryMemories.id, alias.supersededById),
|
|
1005
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
1006
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
1007
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
1008
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
1009
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
1010
|
+
or2(
|
|
1011
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
1012
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
1013
|
+
)
|
|
1014
|
+
)
|
|
1015
|
+
).limit(1);
|
|
1016
|
+
if (rows[0]) {
|
|
1017
|
+
return parseMemoryRow(rows[0]);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return void 0;
|
|
883
1021
|
}
|
|
884
1022
|
async function archiveExpiredMemoryBatch(args) {
|
|
885
1023
|
const scopePredicate = visibleScopePredicate(args.scopes);
|
|
@@ -951,7 +1089,7 @@ async function embedOne(embedder, text2) {
|
|
|
951
1089
|
};
|
|
952
1090
|
}
|
|
953
1091
|
async function storeEmbedding(args) {
|
|
954
|
-
if (!args.embedder) {
|
|
1092
|
+
if (!args.embedder && !args.embedding) {
|
|
955
1093
|
return;
|
|
956
1094
|
}
|
|
957
1095
|
try {
|
|
@@ -963,10 +1101,18 @@ async function storeEmbedding(args) {
|
|
|
963
1101
|
return;
|
|
964
1102
|
}
|
|
965
1103
|
let embedding;
|
|
966
|
-
|
|
967
|
-
embedding =
|
|
968
|
-
}
|
|
969
|
-
|
|
1104
|
+
if (args.embedding) {
|
|
1105
|
+
embedding = args.embedding;
|
|
1106
|
+
} else {
|
|
1107
|
+
const embedder = args.embedder;
|
|
1108
|
+
if (!embedder) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
try {
|
|
1112
|
+
embedding = await embedOne(embedder, args.content);
|
|
1113
|
+
} catch {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
970
1116
|
}
|
|
971
1117
|
try {
|
|
972
1118
|
await args.db.insert(juniorMemoryEmbeddings).values({
|
|
@@ -983,6 +1129,192 @@ async function storeEmbedding(args) {
|
|
|
983
1129
|
return;
|
|
984
1130
|
}
|
|
985
1131
|
}
|
|
1132
|
+
function activeScopedSubjectPredicate(args) {
|
|
1133
|
+
const predicate = and2(
|
|
1134
|
+
eq3(juniorMemoryMemories.scope, args.scope.scope),
|
|
1135
|
+
eq3(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
1136
|
+
eq3(juniorMemoryMemories.kind, args.kind),
|
|
1137
|
+
eq3(juniorMemoryMemories.subjectType, args.subject.subjectType),
|
|
1138
|
+
args.subject.subjectKey === void 0 ? isNull2(juniorMemoryMemories.subjectKey) : eq3(juniorMemoryMemories.subjectKey, args.subject.subjectKey),
|
|
1139
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
1140
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
1141
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
1142
|
+
or2(
|
|
1143
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
1144
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
1145
|
+
)
|
|
1146
|
+
);
|
|
1147
|
+
if (!predicate) {
|
|
1148
|
+
throw new Error("Memory duplicate predicate is empty.");
|
|
1149
|
+
}
|
|
1150
|
+
return predicate;
|
|
1151
|
+
}
|
|
1152
|
+
async function findExactDuplicateMemory(args) {
|
|
1153
|
+
const rows = await args.db.select().from(juniorMemoryMemories).where(
|
|
1154
|
+
and2(
|
|
1155
|
+
activeScopedSubjectPredicate(args),
|
|
1156
|
+
eq3(juniorMemoryMemories.content, args.content)
|
|
1157
|
+
)
|
|
1158
|
+
).orderBy(
|
|
1159
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1160
|
+
asc(juniorMemoryMemories.id)
|
|
1161
|
+
).limit(1);
|
|
1162
|
+
return rows[0] ? parseMemoryRow(rows[0]) : void 0;
|
|
1163
|
+
}
|
|
1164
|
+
async function findVectorDuplicateMemory(args) {
|
|
1165
|
+
const distance = cosineDistance(
|
|
1166
|
+
juniorMemoryEmbeddings.embedding,
|
|
1167
|
+
args.embedding.vector
|
|
1168
|
+
);
|
|
1169
|
+
const rows = await args.db.select({
|
|
1170
|
+
contentHash: juniorMemoryEmbeddings.contentHash,
|
|
1171
|
+
distance,
|
|
1172
|
+
memory: juniorMemoryMemories
|
|
1173
|
+
}).from(juniorMemoryMemories).innerJoin(
|
|
1174
|
+
juniorMemoryEmbeddings,
|
|
1175
|
+
eq3(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
1176
|
+
).where(
|
|
1177
|
+
and2(
|
|
1178
|
+
activeScopedSubjectPredicate(args),
|
|
1179
|
+
eq3(juniorMemoryEmbeddings.provider, args.embedding.provider),
|
|
1180
|
+
eq3(juniorMemoryEmbeddings.model, args.embedding.model),
|
|
1181
|
+
eq3(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
|
|
1182
|
+
eq3(juniorMemoryEmbeddings.metric, EMBEDDING_METRIC),
|
|
1183
|
+
lte(distance, HIGH_CONFIDENCE_DUPLICATE_DISTANCE)
|
|
1184
|
+
)
|
|
1185
|
+
).orderBy(
|
|
1186
|
+
distance,
|
|
1187
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1188
|
+
asc(juniorMemoryMemories.id)
|
|
1189
|
+
).limit(1);
|
|
1190
|
+
const row = rows[0];
|
|
1191
|
+
if (!row || hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
1192
|
+
return void 0;
|
|
1193
|
+
}
|
|
1194
|
+
return parseMemoryRow(row.memory);
|
|
1195
|
+
}
|
|
1196
|
+
async function rememberDuplicateIdempotency(args) {
|
|
1197
|
+
if (args.idempotencyKey === void 0) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
await args.db.insert(juniorMemoryMemories).values({
|
|
1201
|
+
content: args.content,
|
|
1202
|
+
createdAtMs: args.nowMs,
|
|
1203
|
+
expiresAtMs: args.duplicate.expiresAtMs,
|
|
1204
|
+
id: idempotencyAliasId({
|
|
1205
|
+
idempotencyKey: args.idempotencyKey,
|
|
1206
|
+
scope: args.scope,
|
|
1207
|
+
targetId: args.duplicate.id
|
|
1208
|
+
}),
|
|
1209
|
+
idempotencyKey: args.idempotencyKey,
|
|
1210
|
+
observedAtMs: args.nowMs,
|
|
1211
|
+
scope: args.scope.scope,
|
|
1212
|
+
scopeKey: args.scope.scopeKey,
|
|
1213
|
+
sourceKey: sourceKey(args.runtimeContext),
|
|
1214
|
+
sourcePlatform: args.runtimeContext.source.platform,
|
|
1215
|
+
subjectKey: args.subject.subjectKey,
|
|
1216
|
+
subjectType: args.subject.subjectType,
|
|
1217
|
+
supersededAtMs: args.nowMs,
|
|
1218
|
+
supersededById: args.duplicate.id,
|
|
1219
|
+
kind: args.duplicate.kind
|
|
1220
|
+
}).onConflictDoNothing();
|
|
1221
|
+
}
|
|
1222
|
+
async function listSupersessionCandidates(args) {
|
|
1223
|
+
const predicate = activeScopedSubjectPredicate(args);
|
|
1224
|
+
const terms = searchTerms(args.content).filter(
|
|
1225
|
+
(term) => !SUPERSESSION_STOP_TERMS.has(term)
|
|
1226
|
+
);
|
|
1227
|
+
const lexicalRows = terms.length > 0 ? await args.db.select().from(juniorMemoryMemories).where(
|
|
1228
|
+
and2(
|
|
1229
|
+
predicate,
|
|
1230
|
+
or2(
|
|
1231
|
+
...terms.map(
|
|
1232
|
+
(term) => ilike2(juniorMemoryMemories.content, `%${term}%`)
|
|
1233
|
+
)
|
|
1234
|
+
)
|
|
1235
|
+
)
|
|
1236
|
+
) : [];
|
|
1237
|
+
const lexicalCandidates = lexicalRows.map(parseMemoryRow).map((memory) => ({
|
|
1238
|
+
memory,
|
|
1239
|
+
score: searchScore(memory, terms)
|
|
1240
|
+
})).filter((candidate) => candidate.score > 1).sort(
|
|
1241
|
+
(left, right) => right.score - left.score || right.memory.createdAtMs - left.memory.createdAtMs || left.memory.id.localeCompare(right.memory.id)
|
|
1242
|
+
).map((candidate) => candidate.memory);
|
|
1243
|
+
const vectorCandidates = args.embedding ? await listVectorSupersessionCandidates({
|
|
1244
|
+
db: args.db,
|
|
1245
|
+
embedding: args.embedding,
|
|
1246
|
+
kind: args.kind,
|
|
1247
|
+
nowMs: args.nowMs,
|
|
1248
|
+
scope: args.scope,
|
|
1249
|
+
subject: args.subject
|
|
1250
|
+
}) : [];
|
|
1251
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1252
|
+
for (const memory of [...lexicalCandidates, ...vectorCandidates]) {
|
|
1253
|
+
if (byId.size >= SUPERSESSION_CANDIDATE_LIMIT) {
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
byId.set(memory.id, memory);
|
|
1257
|
+
}
|
|
1258
|
+
return [...byId.values()];
|
|
1259
|
+
}
|
|
1260
|
+
async function listVectorSupersessionCandidates(args) {
|
|
1261
|
+
const distance = cosineDistance(
|
|
1262
|
+
juniorMemoryEmbeddings.embedding,
|
|
1263
|
+
args.embedding.vector
|
|
1264
|
+
);
|
|
1265
|
+
const rows = await args.db.select({
|
|
1266
|
+
contentHash: juniorMemoryEmbeddings.contentHash,
|
|
1267
|
+
distance,
|
|
1268
|
+
memory: juniorMemoryMemories
|
|
1269
|
+
}).from(juniorMemoryMemories).innerJoin(
|
|
1270
|
+
juniorMemoryEmbeddings,
|
|
1271
|
+
eq3(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
1272
|
+
).where(
|
|
1273
|
+
and2(
|
|
1274
|
+
activeScopedSubjectPredicate(args),
|
|
1275
|
+
eq3(juniorMemoryEmbeddings.provider, args.embedding.provider),
|
|
1276
|
+
eq3(juniorMemoryEmbeddings.model, args.embedding.model),
|
|
1277
|
+
eq3(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
|
|
1278
|
+
eq3(juniorMemoryEmbeddings.metric, EMBEDDING_METRIC)
|
|
1279
|
+
)
|
|
1280
|
+
).orderBy(
|
|
1281
|
+
distance,
|
|
1282
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1283
|
+
asc(juniorMemoryMemories.id)
|
|
1284
|
+
).limit(SUPERSESSION_CANDIDATE_LIMIT);
|
|
1285
|
+
return rows.flatMap((row) => {
|
|
1286
|
+
if (hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
return [parseMemoryRow(row.memory)];
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
async function decideSupersededIds(args) {
|
|
1293
|
+
if (args.kind !== "preference" || !args.decider || args.candidates.length === 0) {
|
|
1294
|
+
return [];
|
|
1295
|
+
}
|
|
1296
|
+
const existingMemories = args.candidates.map((memory) => ({
|
|
1297
|
+
content: memory.content,
|
|
1298
|
+
id: memory.id
|
|
1299
|
+
}));
|
|
1300
|
+
const candidateIds = new Set(args.candidates.map((memory) => memory.id));
|
|
1301
|
+
try {
|
|
1302
|
+
const decision = await args.decider.adjudicateSupersession({
|
|
1303
|
+
candidate: {
|
|
1304
|
+
content: args.content,
|
|
1305
|
+
kind: "preference"
|
|
1306
|
+
},
|
|
1307
|
+
existingMemories,
|
|
1308
|
+
runtimeContext: args.runtimeContext
|
|
1309
|
+
});
|
|
1310
|
+
if (decision.decision !== "supersedes_old") {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
return decision.supersededIds.filter((id) => candidateIds.has(id));
|
|
1314
|
+
} catch {
|
|
1315
|
+
return [];
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
986
1318
|
async function listVisibleMemories(args) {
|
|
987
1319
|
const predicate = activeVisiblePredicate(args);
|
|
988
1320
|
if (!predicate) {
|
|
@@ -1014,7 +1346,11 @@ async function searchVisibleMemories(args) {
|
|
|
1014
1346
|
)
|
|
1015
1347
|
)
|
|
1016
1348
|
);
|
|
1017
|
-
return rows.map(
|
|
1349
|
+
return rows.map((row) => ({
|
|
1350
|
+
memory: parseMemoryRow(row),
|
|
1351
|
+
score: 0,
|
|
1352
|
+
sourceKey: row.sourceKey
|
|
1353
|
+
}));
|
|
1018
1354
|
}
|
|
1019
1355
|
async function searchVisibleVectorMemories(args) {
|
|
1020
1356
|
if (!args.embedder) {
|
|
@@ -1059,15 +1395,38 @@ async function searchVisibleVectorMemories(args) {
|
|
|
1059
1395
|
if (row.distance === null || !Number.isFinite(distanceValue) || hashEmbeddedContent(row.memory.content) !== row.contentHash) {
|
|
1060
1396
|
return [];
|
|
1061
1397
|
}
|
|
1398
|
+
if (args.maxDistance !== void 0 && distanceValue > args.maxDistance) {
|
|
1399
|
+
return [];
|
|
1400
|
+
}
|
|
1062
1401
|
return [
|
|
1063
1402
|
{
|
|
1064
1403
|
memory: parseMemoryRow(row.memory),
|
|
1065
|
-
score: 1 / (1 + Math.max(0, distanceValue))
|
|
1404
|
+
score: 1 / (1 + Math.max(0, distanceValue)),
|
|
1405
|
+
sourceKey: row.memory.sourceKey
|
|
1066
1406
|
}
|
|
1067
1407
|
];
|
|
1068
1408
|
});
|
|
1069
1409
|
}
|
|
1070
|
-
function
|
|
1410
|
+
function sourceBoost(candidate, currentChannelPrefix) {
|
|
1411
|
+
if (!currentChannelPrefix) {
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
return candidate.sourceKey.startsWith(currentChannelPrefix) ? SOURCE_CHANNEL_BOOST : 0;
|
|
1415
|
+
}
|
|
1416
|
+
function observedAgeBoost(memory, nowMs) {
|
|
1417
|
+
const ageMs = Math.max(0, nowMs - memory.observedAtMs);
|
|
1418
|
+
if (ageMs <= 7 * ONE_DAY_MS) {
|
|
1419
|
+
return 0.15;
|
|
1420
|
+
}
|
|
1421
|
+
if (ageMs <= 30 * ONE_DAY_MS) {
|
|
1422
|
+
return 0.1;
|
|
1423
|
+
}
|
|
1424
|
+
if (ageMs <= 90 * ONE_DAY_MS) {
|
|
1425
|
+
return 0.05;
|
|
1426
|
+
}
|
|
1427
|
+
return 0;
|
|
1428
|
+
}
|
|
1429
|
+
function mergeSearchCandidates(candidates, nowMs, currentChannelKey) {
|
|
1071
1430
|
const byId = /* @__PURE__ */ new Map();
|
|
1072
1431
|
for (const candidate of candidates) {
|
|
1073
1432
|
const existing = byId.get(candidate.memory.id);
|
|
@@ -1077,17 +1436,28 @@ function mergeSearchCandidates(candidates) {
|
|
|
1077
1436
|
}
|
|
1078
1437
|
byId.set(candidate.memory.id, {
|
|
1079
1438
|
memory: candidate.memory,
|
|
1080
|
-
score: candidate.score
|
|
1439
|
+
score: candidate.score,
|
|
1440
|
+
sourceKey: candidate.sourceKey
|
|
1081
1441
|
});
|
|
1082
1442
|
}
|
|
1083
|
-
return [...byId.values()].sort(
|
|
1084
|
-
|
|
1085
|
-
|
|
1443
|
+
return [...byId.values()].sort((left, right) => {
|
|
1444
|
+
const scoreDelta = right.score - left.score;
|
|
1445
|
+
if (Math.abs(scoreDelta) > RELEVANCE_NEAR_TIE_DELTA) {
|
|
1446
|
+
return scoreDelta;
|
|
1447
|
+
}
|
|
1448
|
+
const sourceDelta = sourceBoost(right, currentChannelKey) - sourceBoost(left, currentChannelKey);
|
|
1449
|
+
if (sourceDelta !== 0) {
|
|
1450
|
+
return sourceDelta;
|
|
1451
|
+
}
|
|
1452
|
+
return observedAgeBoost(right.memory, nowMs) - observedAgeBoost(left.memory, nowMs) || right.memory.observedAtMs - left.memory.observedAtMs || left.memory.id.localeCompare(right.memory.id);
|
|
1453
|
+
}).map((candidate) => candidate.memory);
|
|
1086
1454
|
}
|
|
1087
1455
|
function createMemoryStore(db, context, options = {}) {
|
|
1088
1456
|
const runtimeContext = memoryRuntimeContextSchema.parse(context);
|
|
1089
1457
|
const parsedOptions = memoryStoreOptionsSchema.parse({ now: options.now });
|
|
1090
1458
|
const embedder = options.embedder;
|
|
1459
|
+
const maxVectorDistance = options.maxVectorDistance;
|
|
1460
|
+
const supersessionDecider = options.supersessionDecider;
|
|
1091
1461
|
const getNowMs = parsedOptions.now ?? Date.now;
|
|
1092
1462
|
async function archiveExpiredVisibleMemories(input, nowMs) {
|
|
1093
1463
|
input = archiveExpiredMemoriesInputSchema.parse(input ?? {});
|
|
@@ -1119,35 +1489,162 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1119
1489
|
nowMs,
|
|
1120
1490
|
scopes: [scope]
|
|
1121
1491
|
});
|
|
1122
|
-
|
|
1123
|
-
|
|
1492
|
+
if (input.idempotencyKey !== void 0) {
|
|
1493
|
+
const idempotent2 = await findByIdempotencyKey({
|
|
1494
|
+
db,
|
|
1495
|
+
idempotencyKey: input.idempotencyKey,
|
|
1496
|
+
nowMs,
|
|
1497
|
+
scope
|
|
1498
|
+
});
|
|
1499
|
+
if (idempotent2) {
|
|
1500
|
+
await storeEmbedding({
|
|
1501
|
+
content: idempotent2.content,
|
|
1502
|
+
db,
|
|
1503
|
+
embedder,
|
|
1504
|
+
memoryId: idempotent2.id,
|
|
1505
|
+
nowMs
|
|
1506
|
+
});
|
|
1507
|
+
return { created: false, memory: idempotent2 };
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
const exactDuplicate = await findExactDuplicateMemory({
|
|
1124
1511
|
content,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1512
|
+
db,
|
|
1513
|
+
kind: input.kind,
|
|
1514
|
+
nowMs,
|
|
1515
|
+
scope,
|
|
1516
|
+
subject
|
|
1517
|
+
});
|
|
1518
|
+
if (exactDuplicate) {
|
|
1519
|
+
await rememberDuplicateIdempotency({
|
|
1520
|
+
content,
|
|
1521
|
+
db,
|
|
1522
|
+
duplicate: exactDuplicate,
|
|
1523
|
+
idempotencyKey: input.idempotencyKey,
|
|
1524
|
+
nowMs,
|
|
1525
|
+
runtimeContext,
|
|
1526
|
+
scope,
|
|
1527
|
+
subject
|
|
1528
|
+
});
|
|
1529
|
+
await storeEmbedding({
|
|
1530
|
+
content: exactDuplicate.content,
|
|
1531
|
+
db,
|
|
1532
|
+
embedder,
|
|
1533
|
+
memoryId: exactDuplicate.id,
|
|
1534
|
+
nowMs
|
|
1535
|
+
});
|
|
1536
|
+
return { created: false, memory: exactDuplicate };
|
|
1537
|
+
}
|
|
1538
|
+
let candidateEmbedding;
|
|
1539
|
+
if (embedder) {
|
|
1540
|
+
try {
|
|
1541
|
+
candidateEmbedding = await embedOne(embedder, content);
|
|
1542
|
+
} catch {
|
|
1543
|
+
candidateEmbedding = void 0;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const vectorDuplicate = candidateEmbedding ? await findVectorDuplicateMemory({
|
|
1547
|
+
db,
|
|
1548
|
+
embedding: candidateEmbedding,
|
|
1549
|
+
kind: input.kind,
|
|
1550
|
+
nowMs,
|
|
1551
|
+
scope,
|
|
1552
|
+
subject
|
|
1553
|
+
}) : void 0;
|
|
1554
|
+
if (vectorDuplicate) {
|
|
1555
|
+
await rememberDuplicateIdempotency({
|
|
1556
|
+
content,
|
|
1557
|
+
db,
|
|
1558
|
+
duplicate: vectorDuplicate,
|
|
1559
|
+
idempotencyKey: input.idempotencyKey,
|
|
1560
|
+
nowMs,
|
|
1561
|
+
runtimeContext,
|
|
1562
|
+
scope,
|
|
1563
|
+
subject
|
|
1564
|
+
});
|
|
1565
|
+
await storeEmbedding({
|
|
1566
|
+
content: vectorDuplicate.content,
|
|
1567
|
+
db,
|
|
1568
|
+
embedder,
|
|
1569
|
+
memoryId: vectorDuplicate.id,
|
|
1570
|
+
nowMs
|
|
1571
|
+
});
|
|
1572
|
+
return { created: false, memory: vectorDuplicate };
|
|
1573
|
+
}
|
|
1574
|
+
let supersededIds = [];
|
|
1575
|
+
if (scopeKind === "personal" && input.kind === "preference" && supersessionDecider && (input.expiresAtMs === void 0 || input.expiresAtMs > nowMs)) {
|
|
1576
|
+
const supersessionCandidates = await listSupersessionCandidates({
|
|
1577
|
+
content,
|
|
1578
|
+
db,
|
|
1579
|
+
...candidateEmbedding ? { embedding: candidateEmbedding } : {},
|
|
1580
|
+
kind: input.kind,
|
|
1581
|
+
nowMs,
|
|
1582
|
+
scope,
|
|
1583
|
+
subject
|
|
1584
|
+
});
|
|
1585
|
+
supersededIds = await decideSupersededIds({
|
|
1586
|
+
candidates: supersessionCandidates,
|
|
1587
|
+
content,
|
|
1588
|
+
decider: supersessionDecider,
|
|
1589
|
+
kind: input.kind,
|
|
1590
|
+
runtimeContext
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
const id = randomUUID();
|
|
1594
|
+
const rows = await db.transaction(async (tx) => {
|
|
1595
|
+
const inserted = await tx.insert(juniorMemoryMemories).values({
|
|
1596
|
+
content,
|
|
1597
|
+
createdAtMs: nowMs,
|
|
1598
|
+
expiresAtMs: input.expiresAtMs,
|
|
1599
|
+
id,
|
|
1600
|
+
idempotencyKey: input.idempotencyKey,
|
|
1601
|
+
observedAtMs: nowMs,
|
|
1602
|
+
scope: scope.scope,
|
|
1603
|
+
scopeKey: scope.scopeKey,
|
|
1604
|
+
sourceKey: sourceKey(runtimeContext),
|
|
1605
|
+
sourcePlatform: runtimeContext.source.platform,
|
|
1606
|
+
subjectKey: subject.subjectKey,
|
|
1607
|
+
subjectType: subject.subjectType,
|
|
1608
|
+
kind: input.kind
|
|
1609
|
+
}).onConflictDoNothing({
|
|
1610
|
+
target: [
|
|
1611
|
+
juniorMemoryMemories.scope,
|
|
1612
|
+
juniorMemoryMemories.scopeKey,
|
|
1613
|
+
juniorMemoryMemories.idempotencyKey
|
|
1614
|
+
],
|
|
1615
|
+
where: sql2`${juniorMemoryMemories.idempotencyKey} IS NOT NULL AND ${juniorMemoryMemories.archivedAtMs} IS NULL AND ${juniorMemoryMemories.supersededAtMs} IS NULL AND ${juniorMemoryMemories.supersededById} IS NULL`
|
|
1616
|
+
}).returning();
|
|
1617
|
+
const insertedMemory = inserted[0];
|
|
1618
|
+
if (!insertedMemory || supersededIds.length === 0) {
|
|
1619
|
+
return inserted;
|
|
1620
|
+
}
|
|
1621
|
+
const superseded = await tx.update(juniorMemoryMemories).set({
|
|
1622
|
+
supersededAtMs: nowMs,
|
|
1623
|
+
supersededById: insertedMemory.id
|
|
1624
|
+
}).where(
|
|
1625
|
+
and2(
|
|
1626
|
+
inArray(juniorMemoryMemories.id, supersededIds),
|
|
1627
|
+
activeScopedSubjectPredicate({
|
|
1628
|
+
kind: input.kind,
|
|
1629
|
+
nowMs,
|
|
1630
|
+
scope,
|
|
1631
|
+
subject
|
|
1632
|
+
})
|
|
1633
|
+
)
|
|
1634
|
+
).returning({ id: juniorMemoryMemories.id });
|
|
1635
|
+
const idsToClean = superseded.map((row) => row.id);
|
|
1636
|
+
if (idsToClean.length > 0) {
|
|
1637
|
+
await tx.delete(juniorMemoryEmbeddings).where(inArray(juniorMemoryEmbeddings.memoryId, idsToClean));
|
|
1638
|
+
}
|
|
1639
|
+
return inserted;
|
|
1640
|
+
});
|
|
1145
1641
|
if (rows[0]) {
|
|
1146
1642
|
const memory = parseMemoryRow(rows[0]);
|
|
1147
1643
|
await storeEmbedding({
|
|
1148
1644
|
content: memory.content,
|
|
1149
1645
|
db,
|
|
1150
1646
|
embedder,
|
|
1647
|
+
embedding: candidateEmbedding,
|
|
1151
1648
|
memoryId: memory.id,
|
|
1152
1649
|
nowMs
|
|
1153
1650
|
});
|
|
@@ -1211,6 +1708,7 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1211
1708
|
db,
|
|
1212
1709
|
embedder,
|
|
1213
1710
|
limit: limit * VECTOR_SEARCH_OVERFETCH,
|
|
1711
|
+
...maxVectorDistance !== void 0 ? { maxDistance: maxVectorDistance } : {},
|
|
1214
1712
|
nowMs,
|
|
1215
1713
|
query: input.query,
|
|
1216
1714
|
scopes
|
|
@@ -1222,11 +1720,15 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1222
1720
|
scopes
|
|
1223
1721
|
});
|
|
1224
1722
|
const terms = searchTerms(input.query);
|
|
1225
|
-
const lexicalCandidates = candidates.map((
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1723
|
+
const lexicalCandidates = candidates.map((candidate) => ({
|
|
1724
|
+
...candidate,
|
|
1725
|
+
score: searchScore(candidate.memory, terms)
|
|
1726
|
+
})).filter((item) => item.score > 0);
|
|
1727
|
+
return mergeSearchCandidates(
|
|
1728
|
+
[...vectorCandidates, ...lexicalCandidates],
|
|
1729
|
+
nowMs,
|
|
1730
|
+
sourceChannelPrefix(runtimeContext)
|
|
1731
|
+
).slice(0, limit);
|
|
1230
1732
|
},
|
|
1231
1733
|
async archiveMemory(input) {
|
|
1232
1734
|
input = archiveMemoryInputSchema.parse(input);
|
|
@@ -1297,9 +1799,10 @@ function memoryRuntimeContext(context) {
|
|
|
1297
1799
|
source: context.source
|
|
1298
1800
|
});
|
|
1299
1801
|
}
|
|
1300
|
-
function memoryStore(context) {
|
|
1802
|
+
function memoryStore(context, options = {}) {
|
|
1301
1803
|
return createMemoryStore(context.db, memoryRuntimeContext(context), {
|
|
1302
|
-
embedder: context.embedder
|
|
1804
|
+
embedder: context.embedder,
|
|
1805
|
+
...options.supersessionDecider ? { supersessionDecider: options.supersessionDecider } : {}
|
|
1303
1806
|
});
|
|
1304
1807
|
}
|
|
1305
1808
|
function boundedLimit2(value, fallback) {
|
|
@@ -1442,6 +1945,16 @@ var searchMemoriesInputSchema2 = Type.Object(
|
|
|
1442
1945
|
},
|
|
1443
1946
|
{ additionalProperties: false }
|
|
1444
1947
|
);
|
|
1948
|
+
var memoryToolProjectionSchema = Type.Object(
|
|
1949
|
+
{
|
|
1950
|
+
id: Type.String({ minLength: 1 }),
|
|
1951
|
+
content: Type.String({ minLength: 1 }),
|
|
1952
|
+
createdAtMs: Type.Number(),
|
|
1953
|
+
observedAtMs: Type.Number(),
|
|
1954
|
+
expiresAtMs: Type.Optional(Type.Number())
|
|
1955
|
+
},
|
|
1956
|
+
{ additionalProperties: false }
|
|
1957
|
+
);
|
|
1445
1958
|
function parseToolInput(schema, input) {
|
|
1446
1959
|
try {
|
|
1447
1960
|
if (!Value.Check(schema, input)) {
|
|
@@ -1476,12 +1989,13 @@ function targetForKind(kind) {
|
|
|
1476
1989
|
return "conversation";
|
|
1477
1990
|
}
|
|
1478
1991
|
function compactMemory(memory) {
|
|
1479
|
-
return {
|
|
1992
|
+
return Value.Parse(memoryToolProjectionSchema, {
|
|
1480
1993
|
id: memory.id,
|
|
1481
1994
|
content: memory.content,
|
|
1482
1995
|
createdAtMs: memory.createdAtMs,
|
|
1996
|
+
observedAtMs: memory.observedAtMs,
|
|
1483
1997
|
...memory.expiresAtMs !== void 0 ? { expiresAtMs: memory.expiresAtMs } : {}
|
|
1484
|
-
};
|
|
1998
|
+
});
|
|
1485
1999
|
}
|
|
1486
2000
|
function createMemoryCreateTool(context) {
|
|
1487
2001
|
return {
|
|
@@ -1496,7 +2010,9 @@ function createMemoryCreateTool(context) {
|
|
|
1496
2010
|
const toolCallId = requireToolCallId(options.toolCallId);
|
|
1497
2011
|
const requestedExpiresAtMs = parseExpiresAt(parsedInput.expires_at);
|
|
1498
2012
|
const runtimeContext = memoryRuntimeContext(context);
|
|
1499
|
-
const store = memoryStore(context
|
|
2013
|
+
const store = memoryStore(context, {
|
|
2014
|
+
supersessionDecider: context.supersessionDecider
|
|
2015
|
+
});
|
|
1500
2016
|
const review = await (async () => {
|
|
1501
2017
|
try {
|
|
1502
2018
|
return parseMemoryReview(
|
|
@@ -1699,8 +2215,10 @@ async function processMemorySession(context) {
|
|
|
1699
2215
|
...run.requester ? { requester: run.requester } : {},
|
|
1700
2216
|
source: run.source
|
|
1701
2217
|
});
|
|
2218
|
+
const agent = createMemoryAgent(context.model);
|
|
1702
2219
|
const store = createMemoryStore(context.db, runtimeContext, {
|
|
1703
|
-
embedder: context.embedder
|
|
2220
|
+
embedder: context.embedder,
|
|
2221
|
+
supersessionDecider: agent
|
|
1704
2222
|
});
|
|
1705
2223
|
await store.archiveExpiredMemories();
|
|
1706
2224
|
const memories = await getTaskMemories(context, async () => {
|
|
@@ -1708,7 +2226,6 @@ async function processMemorySession(context) {
|
|
|
1708
2226
|
limit: 10,
|
|
1709
2227
|
query: evidenceText
|
|
1710
2228
|
});
|
|
1711
|
-
const agent = createMemoryAgent(context.model);
|
|
1712
2229
|
return await agent.extractSessionMemories({
|
|
1713
2230
|
existingMemories: existingMemories.map((memory) => ({
|
|
1714
2231
|
content: memory.content
|
|
@@ -1735,8 +2252,8 @@ async function processMemorySession(context) {
|
|
|
1735
2252
|
|
|
1736
2253
|
// src/recall.ts
|
|
1737
2254
|
var DEFAULT_RECALL_LIMIT = 5;
|
|
1738
|
-
var MAX_PROMPT_CHARS =
|
|
1739
|
-
var MAX_MEMORY_LINE_CHARS =
|
|
2255
|
+
var MAX_PROMPT_CHARS = 4e3;
|
|
2256
|
+
var MAX_MEMORY_LINE_CHARS = 600;
|
|
1740
2257
|
function trimContent(content, maxLength) {
|
|
1741
2258
|
const trimmed = content.trim();
|
|
1742
2259
|
if (trimmed.length <= maxLength) {
|
|
@@ -1744,13 +2261,19 @@ function trimContent(content, maxLength) {
|
|
|
1744
2261
|
}
|
|
1745
2262
|
return `${trimmed.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
1746
2263
|
}
|
|
2264
|
+
function formatObservedDate(observedAtMs) {
|
|
2265
|
+
return new Date(observedAtMs).toISOString().slice(0, 10);
|
|
2266
|
+
}
|
|
1747
2267
|
function renderMemoryPrompt(memories) {
|
|
1748
2268
|
const header = "Relevant memories for this request:";
|
|
1749
2269
|
const footer = "Treat these as possibly stale context. Current user instructions and repository evidence take priority.";
|
|
1750
2270
|
const lines = [];
|
|
1751
2271
|
let totalChars = header.length + footer.length + 2;
|
|
1752
2272
|
for (const memory of memories) {
|
|
1753
|
-
const line = `- ${
|
|
2273
|
+
const line = `- Observed ${formatObservedDate(memory.observedAtMs)}: ${trimContent(
|
|
2274
|
+
memory.content,
|
|
2275
|
+
MAX_MEMORY_LINE_CHARS
|
|
2276
|
+
)}`;
|
|
1754
2277
|
if (totalChars + line.length + 1 > MAX_PROMPT_CHARS) {
|
|
1755
2278
|
break;
|
|
1756
2279
|
}
|
|
@@ -1777,7 +2300,10 @@ async function createMemoryPromptMessages(context) {
|
|
|
1777
2300
|
const memories = await createMemoryStore(
|
|
1778
2301
|
context.db,
|
|
1779
2302
|
runtimeContext,
|
|
1780
|
-
|
|
2303
|
+
{
|
|
2304
|
+
...context.embedder ? { embedder: context.embedder } : {},
|
|
2305
|
+
...context.maxVectorDistance !== void 0 ? { maxVectorDistance: context.maxVectorDistance } : {}
|
|
2306
|
+
}
|
|
1781
2307
|
).searchMemories({
|
|
1782
2308
|
query: context.text,
|
|
1783
2309
|
limit: DEFAULT_RECALL_LIMIT
|
|
@@ -1788,6 +2314,8 @@ async function createMemoryPromptMessages(context) {
|
|
|
1788
2314
|
|
|
1789
2315
|
// src/plugin.ts
|
|
1790
2316
|
var MEMORY_MODEL_ENV = "AI_MEMORY_MODEL";
|
|
2317
|
+
var MEMORY_RECALL_MAX_VECTOR_DISTANCE_ENV = "MEMORY_RECALL_MAX_VECTOR_DISTANCE";
|
|
2318
|
+
var DEFAULT_RECALL_MAX_VECTOR_DISTANCE = 0.45;
|
|
1791
2319
|
function memoryModelId(options) {
|
|
1792
2320
|
const explicitModelId = options.modelId?.trim();
|
|
1793
2321
|
if (explicitModelId) {
|
|
@@ -1796,6 +2324,19 @@ function memoryModelId(options) {
|
|
|
1796
2324
|
const envModelId = process.env[MEMORY_MODEL_ENV]?.trim();
|
|
1797
2325
|
return envModelId || void 0;
|
|
1798
2326
|
}
|
|
2327
|
+
function recallMaxVectorDistance(options) {
|
|
2328
|
+
if (options.recallMaxVectorDistance !== void 0) {
|
|
2329
|
+
return options.recallMaxVectorDistance;
|
|
2330
|
+
}
|
|
2331
|
+
const raw = process.env[MEMORY_RECALL_MAX_VECTOR_DISTANCE_ENV]?.trim();
|
|
2332
|
+
if (raw) {
|
|
2333
|
+
const parsed = Number(raw);
|
|
2334
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
2335
|
+
return Math.min(parsed, 1);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return DEFAULT_RECALL_MAX_VECTOR_DISTANCE;
|
|
2339
|
+
}
|
|
1799
2340
|
function memoryToolContext(ctx) {
|
|
1800
2341
|
return {
|
|
1801
2342
|
agent: ctx.agent,
|
|
@@ -1807,6 +2348,12 @@ function memoryToolContext(ctx) {
|
|
|
1807
2348
|
...ctx.userText ? { userText: ctx.userText } : {}
|
|
1808
2349
|
};
|
|
1809
2350
|
}
|
|
2351
|
+
function memoryCreateToolContext(ctx) {
|
|
2352
|
+
return {
|
|
2353
|
+
...memoryToolContext(ctx),
|
|
2354
|
+
supersessionDecider: ctx.supersessionDecider
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
1810
2357
|
function createMemoryPlugin(options = {}) {
|
|
1811
2358
|
const modelId = memoryModelId(options);
|
|
1812
2359
|
return defineJuniorPlugin({
|
|
@@ -1829,14 +2376,23 @@ function createMemoryPlugin(options = {}) {
|
|
|
1829
2376
|
},
|
|
1830
2377
|
hooks: {
|
|
1831
2378
|
tools(ctx) {
|
|
2379
|
+
const agent = createMemoryAgent(ctx.model);
|
|
1832
2380
|
const context = memoryToolContext({
|
|
1833
2381
|
...ctx,
|
|
1834
|
-
agent
|
|
2382
|
+
agent,
|
|
1835
2383
|
db: ctx.db,
|
|
1836
2384
|
embedder: ctx.embedder
|
|
1837
2385
|
});
|
|
1838
2386
|
return {
|
|
1839
|
-
createMemory: createMemoryCreateTool(
|
|
2387
|
+
createMemory: createMemoryCreateTool(
|
|
2388
|
+
memoryCreateToolContext({
|
|
2389
|
+
...ctx,
|
|
2390
|
+
agent,
|
|
2391
|
+
db: ctx.db,
|
|
2392
|
+
embedder: ctx.embedder,
|
|
2393
|
+
supersessionDecider: agent
|
|
2394
|
+
})
|
|
2395
|
+
),
|
|
1840
2396
|
removeMemory: createMemoryRemoveTool(context),
|
|
1841
2397
|
listMemories: createMemoryListTool(context),
|
|
1842
2398
|
searchMemories: createMemorySearchTool(context)
|
|
@@ -1848,6 +2404,7 @@ function createMemoryPlugin(options = {}) {
|
|
|
1848
2404
|
...ctx.requester ? { requester: ctx.requester } : {},
|
|
1849
2405
|
db: ctx.db,
|
|
1850
2406
|
embedder: ctx.embedder,
|
|
2407
|
+
maxVectorDistance: recallMaxVectorDistance(options),
|
|
1851
2408
|
source: ctx.source,
|
|
1852
2409
|
text: ctx.text
|
|
1853
2410
|
});
|