@sentry/junior-memory 0.78.0 → 0.80.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 +16 -13
- package/dist/db/schema.d.ts +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +140 -52
- package/dist/index.js.map +1 -1
- package/dist/store.d.ts +16 -7
- package/dist/types.d.ts +2 -2
- package/migrations/0004_gigantic_celestials.sql +12 -0
- package/migrations/meta/0004_snapshot.json +348 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +2 -2
- package/src/agent.ts +30 -32
- package/src/cli/format.ts +1 -1
- package/src/db/schema.ts +6 -11
- package/src/index.ts +2 -1
- package/src/process-session.ts +26 -6
- package/src/store.ts +132 -5
- package/src/tools.ts +17 -4
- package/src/types.ts +2 -11
package/dist/agent.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { PluginModel } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
declare const memoryTargetSchema: z.ZodEnum<{
|
|
4
|
-
requester: "requester";
|
|
5
|
-
conversation: "conversation";
|
|
6
|
-
}>;
|
|
7
3
|
declare const createMemoryRequestSchema: z.ZodObject<{
|
|
8
4
|
content: z.ZodString;
|
|
9
5
|
expiresAtMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -103,9 +99,10 @@ declare const extractSessionRequestSchema: z.ZodObject<{
|
|
|
103
99
|
}, z.core.$strict>;
|
|
104
100
|
declare const memoryReviewDecisionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
105
101
|
decision: z.ZodLiteral<"store">;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
kind: z.ZodEnum<{
|
|
103
|
+
preference: "preference";
|
|
104
|
+
procedure: "procedure";
|
|
105
|
+
knowledge: "knowledge";
|
|
109
106
|
}>;
|
|
110
107
|
content: z.ZodString;
|
|
111
108
|
expiresAtMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -122,21 +119,27 @@ declare const memoryReviewDecisionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
122
119
|
unsupported_scope: "unsupported_scope";
|
|
123
120
|
}>;
|
|
124
121
|
}, z.core.$strict>], "decision">;
|
|
125
|
-
|
|
122
|
+
declare const extractedMemoryResultSchema: z.ZodObject<{
|
|
123
|
+
content: z.ZodString;
|
|
124
|
+
expiresAtMs: z.ZodNullable<z.ZodNumber>;
|
|
125
|
+
kind: z.ZodEnum<{
|
|
126
|
+
preference: "preference";
|
|
127
|
+
procedure: "procedure";
|
|
128
|
+
knowledge: "knowledge";
|
|
129
|
+
}>;
|
|
130
|
+
}, z.core.$strict>;
|
|
126
131
|
export type MemoryReview = z.output<typeof memoryReviewDecisionSchema>;
|
|
127
132
|
export type CreateMemoryRequest = z.output<typeof createMemoryRequestSchema>;
|
|
128
133
|
export type ExtractSessionRequest = z.output<typeof extractSessionRequestSchema>;
|
|
129
|
-
export
|
|
130
|
-
content: string;
|
|
131
|
-
expiresAtMs: number | null;
|
|
132
|
-
target: MemoryTarget;
|
|
133
|
-
}
|
|
134
|
+
export type ExtractedMemory = z.output<typeof extractedMemoryResultSchema>;
|
|
134
135
|
export interface MemoryAgent {
|
|
135
136
|
extractSessionMemories(request: ExtractSessionRequest): Promise<ExtractedMemory[]> | ExtractedMemory[];
|
|
136
137
|
reviewCreateRequest(request: CreateMemoryRequest): Promise<MemoryReview> | MemoryReview;
|
|
137
138
|
}
|
|
138
139
|
/** Create the memory-owned agent that reviews and extracts memory candidates. */
|
|
139
140
|
export declare function createMemoryAgent(model: PluginModel): MemoryAgent;
|
|
141
|
+
/** Parse the canonical extracted-memory shape stored across task retries. */
|
|
142
|
+
export declare function parseExtractedMemory(memory: unknown): ExtractedMemory;
|
|
140
143
|
/** Parse the structured decision returned by the memory agent. */
|
|
141
144
|
export declare function parseMemoryReview(result: unknown): MemoryReview;
|
|
142
145
|
/** Parse the structured input sent to the memory agent. */
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -53,19 +53,19 @@ export declare const juniorMemoryMemories: import("drizzle-orm/pg-core").PgTable
|
|
|
53
53
|
identity: undefined;
|
|
54
54
|
generated: undefined;
|
|
55
55
|
}, {}, {}>;
|
|
56
|
-
|
|
56
|
+
kind: import("drizzle-orm/pg-core").PgColumn<{
|
|
57
57
|
name: "type";
|
|
58
58
|
tableName: "junior_memory_memories";
|
|
59
59
|
dataType: "string";
|
|
60
60
|
columnType: "PgText";
|
|
61
|
-
data: "preference" | "
|
|
61
|
+
data: "preference" | "procedure" | "knowledge";
|
|
62
62
|
driverParam: string;
|
|
63
63
|
notNull: true;
|
|
64
64
|
hasDefault: false;
|
|
65
65
|
isPrimaryKey: false;
|
|
66
66
|
isAutoincrement: false;
|
|
67
67
|
hasRuntimeDefault: false;
|
|
68
|
-
enumValues: ["preference", "
|
|
68
|
+
enumValues: ["preference", "procedure", "knowledge"];
|
|
69
69
|
baseColumn: never;
|
|
70
70
|
identity: undefined;
|
|
71
71
|
generated: undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { createMemoryPlugin, memoryPlugin } from "./plugin";
|
|
|
2
2
|
export type { MemoryPluginOptions } from "./plugin";
|
|
3
3
|
export { createMemoryStore } from "./store";
|
|
4
4
|
export type { ArchiveMemoryInput, CreateMemoryInput, CreateMemoryResult, ListMemoriesInput, MemoryDb, MemoryEmbeddingProvider, MemoryRecord, MemoryStore, MemoryStoreOptions, SearchMemoriesInput, } from "./store";
|
|
5
|
-
export
|
|
5
|
+
export { MEMORY_KINDS } from "./types";
|
|
6
|
+
export type { MemoryKind, MemoryRuntimeContext } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -12,16 +12,7 @@ import {
|
|
|
12
12
|
slackSourceSchema
|
|
13
13
|
} from "@sentry/junior-plugin-api";
|
|
14
14
|
import { z } from "zod";
|
|
15
|
-
var
|
|
16
|
-
"preference",
|
|
17
|
-
"identity",
|
|
18
|
-
"relationship",
|
|
19
|
-
"knowledge",
|
|
20
|
-
"context",
|
|
21
|
-
"event",
|
|
22
|
-
"task",
|
|
23
|
-
"observation"
|
|
24
|
-
];
|
|
15
|
+
var MEMORY_KINDS = ["preference", "procedure", "knowledge"];
|
|
25
16
|
var MEMORY_SCOPES = ["personal", "conversation"];
|
|
26
17
|
var MEMORY_SUBJECT_TYPES = [
|
|
27
18
|
"user",
|
|
@@ -51,8 +42,7 @@ var memoryRuntimeContextSchema = z.union([
|
|
|
51
42
|
]);
|
|
52
43
|
|
|
53
44
|
// src/agent.ts
|
|
54
|
-
var
|
|
55
|
-
var memoryKindSchema = z2.enum(["preference", "procedure", "fact"]);
|
|
45
|
+
var memoryKindSchema = z2.enum(MEMORY_KINDS);
|
|
56
46
|
var memoryRejectReasonSchema = z2.enum([
|
|
57
47
|
"not_public_shareable",
|
|
58
48
|
"secret_or_credential",
|
|
@@ -100,7 +90,7 @@ var expiresAtMsSchema = z2.number().finite().nullable().describe(
|
|
|
100
90
|
var memoryReviewDecisionSchema = z2.discriminatedUnion("decision", [
|
|
101
91
|
z2.object({
|
|
102
92
|
decision: z2.literal("store"),
|
|
103
|
-
|
|
93
|
+
kind: memoryKindSchema,
|
|
104
94
|
content: z2.string().min(1),
|
|
105
95
|
expiresAtMs: z2.number().finite().optional()
|
|
106
96
|
}).strict(),
|
|
@@ -113,7 +103,7 @@ var memoryReviewResponseSchema = z2.discriminatedUnion("decision", [
|
|
|
113
103
|
z2.object({
|
|
114
104
|
decision: z2.literal("store"),
|
|
115
105
|
kind: memoryKindSchema.describe(
|
|
116
|
-
"Use preference only for requester-owned personal preferences, opinions, habits, or workflows. Use procedure for reusable task or process instructions. Use
|
|
106
|
+
"Use preference only for requester-owned personal preferences, opinions, habits, or workflows. Use procedure for reusable task or process instructions. Use knowledge for shared project, channel, operational, or runbook facts."
|
|
117
107
|
),
|
|
118
108
|
canonicalFact: z2.string().min(1).describe(
|
|
119
109
|
"Stored memory text. It must be self-contained and must not include requester names, requester/user labels, source labels, or first- or second-person wording."
|
|
@@ -127,13 +117,18 @@ var memoryReviewResponseSchema = z2.discriminatedUnion("decision", [
|
|
|
127
117
|
]);
|
|
128
118
|
var extractedMemorySchema = z2.object({
|
|
129
119
|
kind: memoryKindSchema.describe(
|
|
130
|
-
"Use preference only for requester-owned personal preferences, opinions, habits, or workflows. Use procedure for reusable task or process instructions. Use
|
|
120
|
+
"Use preference only for requester-owned personal preferences, opinions, habits, or workflows. Use procedure for reusable task or process instructions. Use knowledge for shared project, channel, operational, or runbook facts."
|
|
131
121
|
),
|
|
132
122
|
canonicalFact: z2.string().min(1).describe(
|
|
133
123
|
"Stored memory text as one self-contained fact. It must not include requester names, requester/user labels, source labels, or first- or second-person wording."
|
|
134
124
|
),
|
|
135
125
|
expiresAtMs: expiresAtMsSchema
|
|
136
126
|
}).strict();
|
|
127
|
+
var extractedMemoryResultSchema = z2.object({
|
|
128
|
+
content: z2.string().min(1),
|
|
129
|
+
expiresAtMs: expiresAtMsSchema,
|
|
130
|
+
kind: memoryKindSchema
|
|
131
|
+
}).strict();
|
|
137
132
|
var extractMemoriesResponseSchema = z2.object({
|
|
138
133
|
memories: z2.array(extractedMemorySchema).max(5).describe(
|
|
139
134
|
"Accepted public/shareable durable memories from the completed run. Return one object per distinct source assertion and classify it with kind."
|
|
@@ -162,12 +157,6 @@ var CANONICAL_CONTENT_RULES = [
|
|
|
162
157
|
"- Drop perspective/provenance markers while preserving useful context.",
|
|
163
158
|
"- Remove requester names, display names, requester/user labels, first- or second-person wording, thread labels, channel labels, and source labels."
|
|
164
159
|
];
|
|
165
|
-
function targetForKind(kind) {
|
|
166
|
-
if (kind === "preference") {
|
|
167
|
-
return "requester";
|
|
168
|
-
}
|
|
169
|
-
return "conversation";
|
|
170
|
-
}
|
|
171
160
|
function escapeXml(value) {
|
|
172
161
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
173
162
|
}
|
|
@@ -213,7 +202,7 @@ function memoryKindsContext() {
|
|
|
213
202
|
"<memory-kinds>",
|
|
214
203
|
"- preference: a durable first-person personal preference, opinion, habit, or workflow owned by the current requester. Stored as requester memory.",
|
|
215
204
|
"- procedure: reusable instructions for how a task, lookup, investigation, process, triage flow, or runbook should be done. Store the method, source-of-truth, prerequisite, or decision path when it took effort to discover. Stored as conversation memory.",
|
|
216
|
-
"-
|
|
205
|
+
"- knowledge: stable shared project, channel, operational, or runbook fact that is not a personal requester preference. Direct answers to user inquiries qualify only when they are durable beyond this run. Stored as conversation memory.",
|
|
217
206
|
"</memory-kinds>"
|
|
218
207
|
].join("\n");
|
|
219
208
|
}
|
|
@@ -232,14 +221,14 @@ function reviewPrompt(request) {
|
|
|
232
221
|
"",
|
|
233
222
|
"<rules>",
|
|
234
223
|
"- Return store only when the candidate is public/shareable, durable, and self-contained.",
|
|
235
|
-
"- First classify the memory kind: preference, procedure, or
|
|
224
|
+
"- First classify the memory kind: preference, procedure, or knowledge.",
|
|
236
225
|
"- Use kind=preference only for first-person facts authored by the current requester about their own preference, opinion, habit, identity, or workflow.",
|
|
237
226
|
"- Reject named third-person personal facts such as another person's preference, opinion, habit, identity, relationship, or workflow. Do not assume a named person is the current requester.",
|
|
238
227
|
"- Use kind=procedure for reusable task/process/runbook instructions.",
|
|
239
|
-
"- Use kind=
|
|
228
|
+
"- Use kind=knowledge for shared project, channel, operational, or runbook facts.",
|
|
240
229
|
"- When current-user-message contains an explicit memory request with a concrete fact or procedure, extract from current-user-message even if the candidate is vague, incomplete, or phrased as an instruction.",
|
|
241
230
|
"- A candidate may be badly phrased by an outer assistant or extraction pass. When current-user-message contains the requester's own first-person memory fact, treat that as requester-authored source evidence and canonicalize the fact instead of rejecting for third-person wording.",
|
|
242
|
-
"- When candidate wording personalizes a shared task, process, runbook, project, channel, or operational fact, use current-user-message to recover the shared fact and classify it as procedure or
|
|
231
|
+
"- When candidate wording personalizes a shared task, process, runbook, project, channel, or operational fact, use current-user-message to recover the shared fact and classify it as procedure or knowledge.",
|
|
243
232
|
"- Explicit procedure requests are valid when the source text contains both task context and action. Canonicalize them as shared procedure facts instead of rejecting them as vague.",
|
|
244
233
|
"- Store content as person-less, source-less canonical knowledge. Ownership and source live in structured metadata, not prose.",
|
|
245
234
|
"- For requester memories, omit the subject and write the content as a stable fact such as 'Prefers X', 'Uses Y', or 'Thinks Z'.",
|
|
@@ -300,7 +289,7 @@ function sessionExtractionPrompt(request) {
|
|
|
300
289
|
"- Do not store the fact that the user asked for advice, search, recall, planning, listing, inspection, or removal. Store only stable knowledge discovered in response, such as a reusable method or source-of-truth.",
|
|
301
290
|
"- A user question asking how, what, where, or whether to do something is not source evidence for the answer. Store the answer only when supported by a user-authored factual statement or a tool result.",
|
|
302
291
|
"- Set kind=procedure for reusable task/process/runbook instructions.",
|
|
303
|
-
"- Set kind=
|
|
292
|
+
"- Set kind=knowledge for shared team, project, channel, runbook, or operational facts.",
|
|
304
293
|
"- Set kind=preference only for clear durable first-person facts authored by the current requester about their own preference, opinion, habit, identity, or workflow.",
|
|
305
294
|
"- Reject named third-person personal facts such as another person's preference, opinion, habit, identity, relationship, or workflow. Do not assume a named person is the current requester.",
|
|
306
295
|
"- User-authored task instructions are procedures, not preferences, unless they explicitly describe the requester's personal preference or habit.",
|
|
@@ -344,7 +333,7 @@ function memoryReviewFromResponse(response) {
|
|
|
344
333
|
if (response.decision === "store") {
|
|
345
334
|
return parseMemoryReview({
|
|
346
335
|
decision: "store",
|
|
347
|
-
|
|
336
|
+
kind: response.kind,
|
|
348
337
|
content: response.canonicalFact,
|
|
349
338
|
...response.expiresAtMs !== null ? { expiresAtMs: response.expiresAtMs } : {}
|
|
350
339
|
});
|
|
@@ -355,13 +344,16 @@ function memoryReviewFromResponse(response) {
|
|
|
355
344
|
});
|
|
356
345
|
}
|
|
357
346
|
function extractedMemoriesFromResponse(response) {
|
|
358
|
-
const toMemory = (memory) => ({
|
|
347
|
+
const toMemory = (memory) => parseExtractedMemory({
|
|
359
348
|
content: memory.canonicalFact,
|
|
360
349
|
expiresAtMs: memory.expiresAtMs,
|
|
361
|
-
|
|
350
|
+
kind: memory.kind
|
|
362
351
|
});
|
|
363
352
|
return response.memories.map(toMemory);
|
|
364
353
|
}
|
|
354
|
+
function parseExtractedMemory(memory) {
|
|
355
|
+
return extractedMemoryResultSchema.parse(memory);
|
|
356
|
+
}
|
|
365
357
|
function parseMemoryReview(result) {
|
|
366
358
|
return memoryReviewDecisionSchema.parse(result);
|
|
367
359
|
}
|
|
@@ -391,7 +383,7 @@ var juniorMemoryMemories = pgTable(
|
|
|
391
383
|
id: text("id").primaryKey(),
|
|
392
384
|
scope: text("scope", { enum: MEMORY_SCOPES }).notNull(),
|
|
393
385
|
scopeKey: text("scope_key").notNull(),
|
|
394
|
-
|
|
386
|
+
kind: text("type", { enum: MEMORY_KINDS }).notNull(),
|
|
395
387
|
subjectType: text("subject_type", { enum: MEMORY_SUBJECT_TYPES }).notNull(),
|
|
396
388
|
subjectKey: text("subject_key"),
|
|
397
389
|
content: text("content").notNull(),
|
|
@@ -423,16 +415,11 @@ var juniorMemoryMemories = pgTable(
|
|
|
423
415
|
sql`${table.scope} IN ('personal', 'conversation')`
|
|
424
416
|
),
|
|
425
417
|
check(
|
|
426
|
-
"
|
|
427
|
-
sql`${table.
|
|
418
|
+
"junior_memory_memories_kind_check",
|
|
419
|
+
sql`${table.kind} IN (
|
|
428
420
|
'preference',
|
|
429
|
-
'
|
|
430
|
-
'
|
|
431
|
-
'knowledge',
|
|
432
|
-
'context',
|
|
433
|
-
'event',
|
|
434
|
-
'task',
|
|
435
|
-
'observation'
|
|
421
|
+
'procedure',
|
|
422
|
+
'knowledge'
|
|
436
423
|
)`
|
|
437
424
|
),
|
|
438
425
|
check(
|
|
@@ -492,7 +479,7 @@ function formatMemory(row, args) {
|
|
|
492
479
|
`scope_key=${row.scopeKey}`,
|
|
493
480
|
`subject_type=${row.subjectType}`,
|
|
494
481
|
...row.subjectKey ? [`subject_key=${row.subjectKey}`] : [],
|
|
495
|
-
`
|
|
482
|
+
`kind=${row.kind}`,
|
|
496
483
|
`created_at=${formatDate(row.createdAtMs)}`,
|
|
497
484
|
`observed_at=${formatDate(row.observedAtMs)}`,
|
|
498
485
|
`expires_at=${formatDate(row.expiresAtMs)}`,
|
|
@@ -623,8 +610,10 @@ import {
|
|
|
623
610
|
eq as eq3,
|
|
624
611
|
gt as gt2,
|
|
625
612
|
ilike as ilike2,
|
|
613
|
+
inArray,
|
|
626
614
|
isNull as isNull2,
|
|
627
615
|
like,
|
|
616
|
+
lte,
|
|
628
617
|
or as or2,
|
|
629
618
|
sql as sql2
|
|
630
619
|
} from "drizzle-orm";
|
|
@@ -702,6 +691,7 @@ function deriveVisibleMemoryScopes(ctx) {
|
|
|
702
691
|
// src/store.ts
|
|
703
692
|
var DEFAULT_LIST_LIMIT = 50;
|
|
704
693
|
var DEFAULT_SEARCH_LIMIT = 10;
|
|
694
|
+
var DEFAULT_EXPIRED_ARCHIVE_LIMIT = 100;
|
|
705
695
|
var VECTOR_SEARCH_OVERFETCH = 4;
|
|
706
696
|
var MAX_MEMORY_CONTENT_CHARS = 4e3;
|
|
707
697
|
var EMBEDDING_METRIC = "cosine";
|
|
@@ -713,7 +703,8 @@ var numberSchema = z3.number().finite();
|
|
|
713
703
|
var createMemoryInputSchema = z3.object({
|
|
714
704
|
content: memoryContentSchema,
|
|
715
705
|
expiresAtMs: numberSchema.optional(),
|
|
716
|
-
idempotencyKey: nonEmptyStringSchema2
|
|
706
|
+
idempotencyKey: nonEmptyStringSchema2,
|
|
707
|
+
kind: z3.enum(MEMORY_KINDS)
|
|
717
708
|
}).strict();
|
|
718
709
|
var listMemoriesInputSchema = z3.object({
|
|
719
710
|
limit: numberSchema.optional()
|
|
@@ -726,6 +717,9 @@ var archiveMemoryInputSchema = z3.object({
|
|
|
726
717
|
id: nonEmptyStringSchema2,
|
|
727
718
|
reason: nonEmptyStringSchema2.optional()
|
|
728
719
|
}).strict();
|
|
720
|
+
var archiveExpiredMemoriesInputSchema = z3.object({
|
|
721
|
+
limit: numberSchema.optional()
|
|
722
|
+
}).strict();
|
|
729
723
|
var clockSchema = z3.function({ input: [], output: numberSchema }).optional();
|
|
730
724
|
var memoryStoreOptionsSchema = z3.object({
|
|
731
725
|
now: clockSchema
|
|
@@ -759,7 +753,7 @@ var memoryRowSchema = z3.object({
|
|
|
759
753
|
subjectType: z3.enum(MEMORY_SUBJECT_TYPES),
|
|
760
754
|
supersededAtMs: optionalNumberSchema,
|
|
761
755
|
supersededById: optionalStringSchema,
|
|
762
|
-
|
|
756
|
+
kind: z3.enum(MEMORY_KINDS)
|
|
763
757
|
}).strict().superRefine((row, ctx) => {
|
|
764
758
|
if (row.subjectType === "general") {
|
|
765
759
|
if (row.subjectKey !== void 0) {
|
|
@@ -791,7 +785,7 @@ var memoryRecordSchema = z3.object({
|
|
|
791
785
|
subjectType: z3.enum(MEMORY_SUBJECT_TYPES),
|
|
792
786
|
supersededAtMs: numberSchema.optional(),
|
|
793
787
|
supersededById: nonEmptyStringSchema2.optional(),
|
|
794
|
-
|
|
788
|
+
kind: z3.enum(MEMORY_KINDS)
|
|
795
789
|
}).strict();
|
|
796
790
|
var embeddingVectorSchema = z3.array(numberSchema).length(MEMORY_EMBEDDING_DIMENSIONS);
|
|
797
791
|
var embeddingResultSchema = z3.object({
|
|
@@ -829,7 +823,7 @@ function parseMemoryRow(row) {
|
|
|
829
823
|
return memoryRecordSchema.parse({
|
|
830
824
|
id: parsed.id,
|
|
831
825
|
scope: parsed.scope,
|
|
832
|
-
|
|
826
|
+
kind: parsed.kind,
|
|
833
827
|
subjectType: parsed.subjectType,
|
|
834
828
|
content: parsed.content,
|
|
835
829
|
observedAtMs: parsed.observedAtMs,
|
|
@@ -878,11 +872,53 @@ async function findByIdempotencyKey(args) {
|
|
|
878
872
|
eq3(juniorMemoryMemories.idempotencyKey, args.idempotencyKey),
|
|
879
873
|
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
880
874
|
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
881
|
-
isNull2(juniorMemoryMemories.supersededById)
|
|
875
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
876
|
+
or2(
|
|
877
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
878
|
+
gt2(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
879
|
+
)
|
|
882
880
|
)
|
|
883
881
|
).limit(1);
|
|
884
882
|
return rows[0] ? parseMemoryRow(rows[0]) : void 0;
|
|
885
883
|
}
|
|
884
|
+
async function archiveExpiredMemoryBatch(args) {
|
|
885
|
+
const scopePredicate = visibleScopePredicate(args.scopes);
|
|
886
|
+
if (!scopePredicate) {
|
|
887
|
+
return { archivedCount: 0 };
|
|
888
|
+
}
|
|
889
|
+
const predicates = [
|
|
890
|
+
scopePredicate,
|
|
891
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
892
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
893
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
894
|
+
lte(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
895
|
+
];
|
|
896
|
+
if (args.idempotencyKey !== void 0) {
|
|
897
|
+
predicates.push(
|
|
898
|
+
eq3(juniorMemoryMemories.idempotencyKey, args.idempotencyKey)
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
const archivedIds = await args.db.transaction(async (tx) => {
|
|
902
|
+
const expired = await tx.select({ id: juniorMemoryMemories.id }).from(juniorMemoryMemories).where(and2(...predicates)).orderBy(
|
|
903
|
+
asc(juniorMemoryMemories.expiresAtMs),
|
|
904
|
+
asc(juniorMemoryMemories.id)
|
|
905
|
+
).limit(boundedLimit(args.limit, DEFAULT_EXPIRED_ARCHIVE_LIMIT));
|
|
906
|
+
const ids = expired.map((row) => row.id);
|
|
907
|
+
if (ids.length === 0) {
|
|
908
|
+
return [];
|
|
909
|
+
}
|
|
910
|
+
const archived = await tx.update(juniorMemoryMemories).set({
|
|
911
|
+
archivedAtMs: args.nowMs,
|
|
912
|
+
archiveReason: "expired"
|
|
913
|
+
}).where(and2(inArray(juniorMemoryMemories.id, ids), ...predicates)).returning({ id: juniorMemoryMemories.id });
|
|
914
|
+
const idsToClean = archived.map((row) => row.id);
|
|
915
|
+
if (idsToClean.length > 0) {
|
|
916
|
+
await tx.delete(juniorMemoryEmbeddings).where(inArray(juniorMemoryEmbeddings.memoryId, idsToClean));
|
|
917
|
+
}
|
|
918
|
+
return idsToClean;
|
|
919
|
+
});
|
|
920
|
+
return { archivedCount: archivedIds.length };
|
|
921
|
+
}
|
|
886
922
|
function searchScore(memory, terms) {
|
|
887
923
|
const haystack = memory.content.toLowerCase();
|
|
888
924
|
return terms.reduce(
|
|
@@ -1053,6 +1089,15 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1053
1089
|
const parsedOptions = memoryStoreOptionsSchema.parse({ now: options.now });
|
|
1054
1090
|
const embedder = options.embedder;
|
|
1055
1091
|
const getNowMs = parsedOptions.now ?? Date.now;
|
|
1092
|
+
async function archiveExpiredVisibleMemories(input, nowMs) {
|
|
1093
|
+
input = archiveExpiredMemoriesInputSchema.parse(input ?? {});
|
|
1094
|
+
return await archiveExpiredMemoryBatch({
|
|
1095
|
+
db,
|
|
1096
|
+
limit: input.limit,
|
|
1097
|
+
nowMs,
|
|
1098
|
+
scopes: deriveVisibleMemoryScopes(runtimeContext)
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1056
1101
|
async function createScopedMemory(rawInput, scopeKind) {
|
|
1057
1102
|
const input = createMemoryInputSchema.parse(rawInput);
|
|
1058
1103
|
const nowMs = getNowMs();
|
|
@@ -1062,6 +1107,18 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1062
1107
|
if (content.length > MAX_MEMORY_CONTENT_CHARS) {
|
|
1063
1108
|
throw new Error("Memory content exceeds the maximum length.");
|
|
1064
1109
|
}
|
|
1110
|
+
await archiveExpiredMemoryBatch({
|
|
1111
|
+
db,
|
|
1112
|
+
nowMs,
|
|
1113
|
+
scopes: [scope]
|
|
1114
|
+
});
|
|
1115
|
+
await archiveExpiredMemoryBatch({
|
|
1116
|
+
db,
|
|
1117
|
+
idempotencyKey: input.idempotencyKey,
|
|
1118
|
+
limit: 1,
|
|
1119
|
+
nowMs,
|
|
1120
|
+
scopes: [scope]
|
|
1121
|
+
});
|
|
1065
1122
|
const id = randomUUID();
|
|
1066
1123
|
const rows = await db.insert(juniorMemoryMemories).values({
|
|
1067
1124
|
content,
|
|
@@ -1076,7 +1133,7 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1076
1133
|
sourcePlatform: runtimeContext.source.platform,
|
|
1077
1134
|
subjectKey: subject.subjectKey,
|
|
1078
1135
|
subjectType: subject.subjectType,
|
|
1079
|
-
|
|
1136
|
+
kind: input.kind
|
|
1080
1137
|
}).onConflictDoNothing({
|
|
1081
1138
|
target: [
|
|
1082
1139
|
juniorMemoryMemories.scope,
|
|
@@ -1099,6 +1156,7 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1099
1156
|
const idempotent = await findByIdempotencyKey({
|
|
1100
1157
|
db,
|
|
1101
1158
|
idempotencyKey: input.idempotencyKey,
|
|
1159
|
+
nowMs,
|
|
1102
1160
|
scope
|
|
1103
1161
|
});
|
|
1104
1162
|
if (!idempotent) {
|
|
@@ -1114,6 +1172,9 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1114
1172
|
return { created: false, memory: idempotent };
|
|
1115
1173
|
}
|
|
1116
1174
|
return {
|
|
1175
|
+
async archiveExpiredMemories(input) {
|
|
1176
|
+
return await archiveExpiredVisibleMemories(input, getNowMs());
|
|
1177
|
+
},
|
|
1117
1178
|
async createMemory(input) {
|
|
1118
1179
|
return await createScopedMemory(input, "personal");
|
|
1119
1180
|
},
|
|
@@ -1124,6 +1185,11 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1124
1185
|
input = listMemoriesInputSchema.parse(input);
|
|
1125
1186
|
const nowMs = getNowMs();
|
|
1126
1187
|
const scopes = deriveVisibleMemoryScopes(runtimeContext);
|
|
1188
|
+
await archiveExpiredMemoryBatch({
|
|
1189
|
+
db,
|
|
1190
|
+
nowMs,
|
|
1191
|
+
scopes
|
|
1192
|
+
});
|
|
1127
1193
|
return await listVisibleMemories({
|
|
1128
1194
|
db,
|
|
1129
1195
|
limit: input.limit,
|
|
@@ -1135,6 +1201,11 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1135
1201
|
input = searchMemoriesInputSchema.parse(input);
|
|
1136
1202
|
const nowMs = getNowMs();
|
|
1137
1203
|
const scopes = deriveVisibleMemoryScopes(runtimeContext);
|
|
1204
|
+
await archiveExpiredMemoryBatch({
|
|
1205
|
+
db,
|
|
1206
|
+
nowMs,
|
|
1207
|
+
scopes
|
|
1208
|
+
});
|
|
1138
1209
|
const limit = boundedLimit(input.limit, DEFAULT_SEARCH_LIMIT);
|
|
1139
1210
|
const vectorCandidates = await searchVisibleVectorMemories({
|
|
1140
1211
|
db,
|
|
@@ -1394,9 +1465,16 @@ function createInput(context, input, toolCallId) {
|
|
|
1394
1465
|
return {
|
|
1395
1466
|
content: requireMemoryContent(input.content),
|
|
1396
1467
|
idempotencyKey: `tool:${sourceIdempotencyKey(context)}:${toolCallId}`,
|
|
1468
|
+
kind: input.kind,
|
|
1397
1469
|
...input.expiresAtMs !== void 0 ? { expiresAtMs: input.expiresAtMs } : {}
|
|
1398
1470
|
};
|
|
1399
1471
|
}
|
|
1472
|
+
function targetForKind(kind) {
|
|
1473
|
+
if (kind === "preference") {
|
|
1474
|
+
return "requester";
|
|
1475
|
+
}
|
|
1476
|
+
return "conversation";
|
|
1477
|
+
}
|
|
1400
1478
|
function compactMemory(memory) {
|
|
1401
1479
|
return {
|
|
1402
1480
|
id: memory.id,
|
|
@@ -1407,7 +1485,7 @@ function compactMemory(memory) {
|
|
|
1407
1485
|
}
|
|
1408
1486
|
function createMemoryCreateTool(context) {
|
|
1409
1487
|
return {
|
|
1410
|
-
description: "Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a public/shareable fact. Do not use for ordinary statements like 'I prefer X', 'I use Y', or 'X goes before Y' unless the user also asks you to remember/store/save it; passive memory learning handles those after the visible reply. Pass one self-contained natural-language candidate preserving the user's explicit memory intent. Do not ask the user to rephrase ordinary first-person facts, and do not rewrite them into display-name or third-person wording. Do not include secrets, private personal details, medical/legal/financial/sensitive facts, or another person's personal preference, opinion, habit, identity, relationship, workflow, or private life. Runtime context derives actor, scope, source, and subject ids; the memory agent decides
|
|
1488
|
+
description: "Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a public/shareable fact. Do not use for ordinary statements like 'I prefer X', 'I use Y', or 'X goes before Y' unless the user also asks you to remember/store/save it; passive memory learning handles those after the visible reply. Pass one self-contained natural-language candidate preserving the user's explicit memory intent. Do not ask the user to rephrase ordinary first-person facts, and do not rewrite them into display-name or third-person wording. Do not include secrets, private personal details, medical/legal/financial/sensitive facts, or another person's personal preference, opinion, habit, identity, relationship, workflow, or private life. Runtime context derives actor, scope, source, and subject ids; the memory agent decides canonical stored content and memory kind, then the plugin derives storage target from kind.",
|
|
1411
1489
|
executionMode: "sequential",
|
|
1412
1490
|
inputSchema: createMemoryInputSchema2,
|
|
1413
1491
|
execute: async (input, options) => {
|
|
@@ -1455,13 +1533,14 @@ function createMemoryCreateTool(context) {
|
|
|
1455
1533
|
context,
|
|
1456
1534
|
{
|
|
1457
1535
|
content: review.content,
|
|
1536
|
+
kind: review.kind,
|
|
1458
1537
|
...review.expiresAtMs !== void 0 ? { expiresAtMs: review.expiresAtMs } : requestedExpiresAtMs !== void 0 ? { expiresAtMs: requestedExpiresAtMs } : {}
|
|
1459
1538
|
},
|
|
1460
1539
|
toolCallId
|
|
1461
1540
|
);
|
|
1462
1541
|
const result = await (async () => {
|
|
1463
1542
|
try {
|
|
1464
|
-
if (review.
|
|
1543
|
+
if (targetForKind(review.kind) === "conversation") {
|
|
1465
1544
|
return await store.createConversationMemory(memoryInput);
|
|
1466
1545
|
}
|
|
1467
1546
|
return await store.createMemory(memoryInput);
|
|
@@ -1564,16 +1643,23 @@ var extractedMemoryCacheSchema = z4.array(
|
|
|
1564
1643
|
z4.object({
|
|
1565
1644
|
content: z4.string().min(1),
|
|
1566
1645
|
expiresAtMs: z4.number().finite().nullable(),
|
|
1567
|
-
|
|
1568
|
-
}).strict()
|
|
1646
|
+
kind: z4.enum(MEMORY_KINDS)
|
|
1647
|
+
}).strict().transform(parseExtractedMemory)
|
|
1569
1648
|
);
|
|
1649
|
+
function targetForKind2(kind) {
|
|
1650
|
+
if (kind === "preference") {
|
|
1651
|
+
return "requester";
|
|
1652
|
+
}
|
|
1653
|
+
return "conversation";
|
|
1654
|
+
}
|
|
1570
1655
|
function memoryIdempotencySuffix(memory) {
|
|
1571
|
-
return createHash2("sha256").update(memory.
|
|
1656
|
+
return createHash2("sha256").update(targetForKind2(memory.kind)).update("\0").update(memory.kind).update("\0").update(memory.content).update("\0").update(memory.expiresAtMs === null ? "never" : String(memory.expiresAtMs)).digest("hex").slice(0, 32);
|
|
1572
1657
|
}
|
|
1573
1658
|
function passiveInput(sessionId, memory, sourceKey2) {
|
|
1574
1659
|
return {
|
|
1575
1660
|
content: memory.content,
|
|
1576
1661
|
idempotencyKey: `session:${sourceKey2}:${sessionId}:${memoryIdempotencySuffix(memory)}`,
|
|
1662
|
+
kind: memory.kind,
|
|
1577
1663
|
...memory.expiresAtMs !== null ? { expiresAtMs: memory.expiresAtMs } : {}
|
|
1578
1664
|
};
|
|
1579
1665
|
}
|
|
@@ -1616,6 +1702,7 @@ async function processMemorySession(context) {
|
|
|
1616
1702
|
const store = createMemoryStore(context.db, runtimeContext, {
|
|
1617
1703
|
embedder: context.embedder
|
|
1618
1704
|
});
|
|
1705
|
+
await store.archiveExpiredMemories();
|
|
1619
1706
|
const memories = await getTaskMemories(context, async () => {
|
|
1620
1707
|
const existingMemories = await store.searchMemories({
|
|
1621
1708
|
limit: 10,
|
|
@@ -1635,7 +1722,7 @@ async function processMemorySession(context) {
|
|
|
1635
1722
|
}
|
|
1636
1723
|
for (const memory of memories) {
|
|
1637
1724
|
const input = passiveInput(run.runId, memory, sourceKey2);
|
|
1638
|
-
if (memory.
|
|
1725
|
+
if (targetForKind2(memory.kind) === "conversation") {
|
|
1639
1726
|
await store.createConversationMemory(input);
|
|
1640
1727
|
continue;
|
|
1641
1728
|
}
|
|
@@ -1770,6 +1857,7 @@ function createMemoryPlugin(options = {}) {
|
|
|
1770
1857
|
}
|
|
1771
1858
|
var memoryPlugin = createMemoryPlugin();
|
|
1772
1859
|
export {
|
|
1860
|
+
MEMORY_KINDS,
|
|
1773
1861
|
createMemoryPlugin,
|
|
1774
1862
|
createMemoryStore,
|
|
1775
1863
|
memoryPlugin
|