@sentry/junior-memory 0.80.0 → 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/store.d.ts
CHANGED
|
@@ -78,9 +78,37 @@ export interface MemoryEmbeddingProvider {
|
|
|
78
78
|
vectors: number[][];
|
|
79
79
|
}>;
|
|
80
80
|
}
|
|
81
|
+
export interface MemorySupersessionInput {
|
|
82
|
+
candidate: {
|
|
83
|
+
content: string;
|
|
84
|
+
kind: "preference";
|
|
85
|
+
};
|
|
86
|
+
existingMemories: [
|
|
87
|
+
{
|
|
88
|
+
content: string;
|
|
89
|
+
id: string;
|
|
90
|
+
},
|
|
91
|
+
...Array<{
|
|
92
|
+
content: string;
|
|
93
|
+
id: string;
|
|
94
|
+
}>
|
|
95
|
+
];
|
|
96
|
+
runtimeContext: MemoryRuntimeContext;
|
|
97
|
+
}
|
|
98
|
+
export type MemorySupersessionDecision = {
|
|
99
|
+
decision: "supersedes_old";
|
|
100
|
+
supersededIds: [string, ...string[]];
|
|
101
|
+
} | {
|
|
102
|
+
decision: "distinct" | "uncertain";
|
|
103
|
+
};
|
|
104
|
+
export interface MemorySupersessionDecider {
|
|
105
|
+
/** Decide whether a new preference clearly replaces active old preferences. */
|
|
106
|
+
adjudicateSupersession(input: MemorySupersessionInput): Promise<MemorySupersessionDecision> | MemorySupersessionDecision;
|
|
107
|
+
}
|
|
81
108
|
export interface MemoryStoreOptions {
|
|
82
109
|
embedder?: MemoryEmbeddingProvider;
|
|
83
110
|
now?: () => number;
|
|
111
|
+
supersessionDecider?: MemorySupersessionDecider;
|
|
84
112
|
}
|
|
85
113
|
/** Context-bound storage operations for visible long-term memories. */
|
|
86
114
|
export interface MemoryStore {
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Source, type Requester } from "@sentry/junior-plugin-api";
|
|
2
|
-
import { type MemoryEmbeddingProvider, type MemoryDb } from "./store";
|
|
2
|
+
import { type MemoryEmbeddingProvider, type MemoryDb, type MemorySupersessionDecider } from "./store";
|
|
3
3
|
import { type MemoryAgent } from "./agent";
|
|
4
4
|
export type MemoryReviewer = Pick<MemoryAgent, "reviewCreateRequest">;
|
|
5
5
|
/** Runtime-owned context used to bind memory tools to visible scopes. */
|
|
@@ -12,12 +12,15 @@ export interface MemoryToolContext {
|
|
|
12
12
|
source: Source;
|
|
13
13
|
userText?: string;
|
|
14
14
|
}
|
|
15
|
+
export interface MemoryCreateToolContext extends MemoryToolContext {
|
|
16
|
+
supersessionDecider?: MemorySupersessionDecider;
|
|
17
|
+
}
|
|
15
18
|
type MemoryWriteToolInput = {
|
|
16
19
|
content: string;
|
|
17
20
|
expires_at?: string;
|
|
18
21
|
};
|
|
19
22
|
/** Create a tool that submits an explicit memory candidate for storage. */
|
|
20
|
-
export declare function createMemoryCreateTool(context:
|
|
23
|
+
export declare function createMemoryCreateTool(context: MemoryCreateToolContext): {
|
|
21
24
|
description: string;
|
|
22
25
|
executionMode: string;
|
|
23
26
|
inputSchema: import("@sinclair/typebox").TObject<{
|
|
@@ -31,6 +34,7 @@ export declare function createMemoryCreateTool(context: MemoryToolContext): {
|
|
|
31
34
|
expiresAtMs?: number | undefined;
|
|
32
35
|
id: string;
|
|
33
36
|
content: string;
|
|
37
|
+
observedAtMs: number;
|
|
34
38
|
createdAtMs: number;
|
|
35
39
|
};
|
|
36
40
|
}>;
|
|
@@ -50,6 +54,7 @@ export declare function createMemoryRemoveTool(context: MemoryToolContext): {
|
|
|
50
54
|
expiresAtMs?: number | undefined;
|
|
51
55
|
id: string;
|
|
52
56
|
content: string;
|
|
57
|
+
observedAtMs: number;
|
|
53
58
|
createdAtMs: number;
|
|
54
59
|
};
|
|
55
60
|
}>;
|
|
@@ -72,6 +77,7 @@ export declare function createMemoryListTool(context: MemoryToolContext): {
|
|
|
72
77
|
expiresAtMs?: number | undefined;
|
|
73
78
|
id: string;
|
|
74
79
|
content: string;
|
|
80
|
+
observedAtMs: number;
|
|
75
81
|
createdAtMs: number;
|
|
76
82
|
}[];
|
|
77
83
|
}>;
|
|
@@ -96,6 +102,7 @@ export declare function createMemorySearchTool(context: MemoryToolContext): {
|
|
|
96
102
|
expiresAtMs?: number | undefined;
|
|
97
103
|
id: string;
|
|
98
104
|
content: string;
|
|
105
|
+
observedAtMs: number;
|
|
99
106
|
createdAtMs: number;
|
|
100
107
|
}[];
|
|
101
108
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"commander": "^14.0.3",
|
|
28
28
|
"drizzle-orm": "^0.45.2",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@sentry/junior-plugin-api": "0.
|
|
30
|
+
"@sentry/junior-plugin-api": "0.81.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.1",
|
package/src/agent.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { PluginModel } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import type {
|
|
4
|
+
MemorySupersessionDecision,
|
|
5
|
+
MemorySupersessionInput,
|
|
6
|
+
} from "./store";
|
|
3
7
|
import { MEMORY_KINDS, memoryRuntimeContextSchema } from "./types";
|
|
4
8
|
|
|
5
9
|
const memoryKindSchema = z.enum(MEMORY_KINDS);
|
|
@@ -62,6 +66,28 @@ const extractSessionRequestSchema = z
|
|
|
62
66
|
.min(1),
|
|
63
67
|
})
|
|
64
68
|
.strict();
|
|
69
|
+
const supersessionRequestSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
candidate: z
|
|
72
|
+
.object({
|
|
73
|
+
content: z.string().min(1),
|
|
74
|
+
kind: z.literal("preference"),
|
|
75
|
+
})
|
|
76
|
+
.strict(),
|
|
77
|
+
existingMemories: z
|
|
78
|
+
.array(
|
|
79
|
+
z
|
|
80
|
+
.object({
|
|
81
|
+
content: z.string().min(1),
|
|
82
|
+
id: z.string().min(1),
|
|
83
|
+
})
|
|
84
|
+
.strict(),
|
|
85
|
+
)
|
|
86
|
+
.min(1)
|
|
87
|
+
.max(10),
|
|
88
|
+
runtimeContext: memoryRuntimeContextSchema,
|
|
89
|
+
})
|
|
90
|
+
.strict();
|
|
65
91
|
|
|
66
92
|
const expiresAtMsSchema = z
|
|
67
93
|
.number()
|
|
@@ -140,6 +166,19 @@ const extractMemoriesResponseSchema = z
|
|
|
140
166
|
),
|
|
141
167
|
})
|
|
142
168
|
.strict();
|
|
169
|
+
const supersessionDecisionResponseSchema = z.discriminatedUnion("decision", [
|
|
170
|
+
z
|
|
171
|
+
.object({
|
|
172
|
+
decision: z.literal("supersedes_old"),
|
|
173
|
+
supersededIds: z.array(z.string().min(1)).min(1).max(10),
|
|
174
|
+
})
|
|
175
|
+
.strict(),
|
|
176
|
+
z
|
|
177
|
+
.object({
|
|
178
|
+
decision: z.enum(["distinct", "uncertain"]),
|
|
179
|
+
})
|
|
180
|
+
.strict(),
|
|
181
|
+
]);
|
|
143
182
|
|
|
144
183
|
type MemoryReviewResponse = z.output<typeof memoryReviewResponseSchema>;
|
|
145
184
|
type ExtractMemoriesResponse = z.output<typeof extractMemoriesResponseSchema>;
|
|
@@ -153,6 +192,10 @@ export type ExtractSessionRequest = z.output<
|
|
|
153
192
|
export type ExtractedMemory = z.output<typeof extractedMemoryResultSchema>;
|
|
154
193
|
|
|
155
194
|
export interface MemoryAgent {
|
|
195
|
+
/** Decide whether a new preference safely replaces active old preferences. */
|
|
196
|
+
adjudicateSupersession(
|
|
197
|
+
request: MemorySupersessionInput,
|
|
198
|
+
): Promise<MemorySupersessionDecision> | MemorySupersessionDecision;
|
|
156
199
|
extractSessionMemories(
|
|
157
200
|
request: ExtractSessionRequest,
|
|
158
201
|
): Promise<ExtractedMemory[]> | ExtractedMemory[];
|
|
@@ -175,6 +218,12 @@ const MEMORY_EXTRACTION_SYSTEM = [
|
|
|
175
218
|
"Reject secrets, credentials, private or sensitive personal details, gossip, speculative claims about other people, assistant/system implementation details, vague references, and low-durability chatter.",
|
|
176
219
|
"If no public, durable, self-contained memory remains after rewriting, return an empty memories array.",
|
|
177
220
|
].join("\n");
|
|
221
|
+
const MEMORY_SUPERSESSION_SYSTEM = [
|
|
222
|
+
"You are Junior's memory supersession agent.",
|
|
223
|
+
"Decide whether a new requester preference clearly replaces existing active requester preferences.",
|
|
224
|
+
"Return supersedes_old only for obvious changed preferences about the same mutable slot.",
|
|
225
|
+
"If the facts are additive, different topics, duplicate, broader/narrower without direct replacement, or uncertain, do not supersede.",
|
|
226
|
+
].join("\n");
|
|
178
227
|
const CANONICAL_CONTENT_RULES = [
|
|
179
228
|
"- Stored memory text must be a rewritten fact, not copied user wording or a sentence about who said it.",
|
|
180
229
|
"- Store the minimum useful assertion supported by source evidence; do not add adjacent steps, caveats, or generalized advice.",
|
|
@@ -355,9 +404,52 @@ function sessionExtractionPrompt(request: ExtractSessionRequest): string {
|
|
|
355
404
|
].join("\n");
|
|
356
405
|
}
|
|
357
406
|
|
|
407
|
+
function supersessionPrompt(request: MemorySupersessionInput): string {
|
|
408
|
+
return [
|
|
409
|
+
"<memory-supersession-input>",
|
|
410
|
+
"Decide whether the candidate preference clearly replaces one or more existing active preferences.",
|
|
411
|
+
"",
|
|
412
|
+
runtimeDescription({
|
|
413
|
+
runtimeContext: request.runtimeContext,
|
|
414
|
+
}),
|
|
415
|
+
"",
|
|
416
|
+
"<candidate>",
|
|
417
|
+
escapeXml(JSON.stringify(request.candidate)),
|
|
418
|
+
"</candidate>",
|
|
419
|
+
"",
|
|
420
|
+
"<existing-memories>",
|
|
421
|
+
escapeXml(JSON.stringify(request.existingMemories)),
|
|
422
|
+
"</existing-memories>",
|
|
423
|
+
"",
|
|
424
|
+
"<rules>",
|
|
425
|
+
"- Return supersedes_old only when the candidate and old memory describe the same mutable preference slot and the candidate is the newer value.",
|
|
426
|
+
"- Examples of same mutable slot: preferred programming language, preferred review style, preferred notification cadence, preferred tool for a task.",
|
|
427
|
+
"- Do not supersede when the candidate is just more specific, an additional preference, a different task/context, or the same value phrased differently.",
|
|
428
|
+
"- Do not supersede memories from different topics even if they are both preferences.",
|
|
429
|
+
"- Only return ids that appear in existing-memories.",
|
|
430
|
+
"- If unsure, return uncertain.",
|
|
431
|
+
"</rules>",
|
|
432
|
+
"</memory-supersession-input>",
|
|
433
|
+
].join("\n");
|
|
434
|
+
}
|
|
435
|
+
|
|
358
436
|
/** Create the memory-owned agent that reviews and extracts memory candidates. */
|
|
359
437
|
export function createMemoryAgent(model: PluginModel): MemoryAgent {
|
|
360
438
|
return {
|
|
439
|
+
async adjudicateSupersession(rawRequest) {
|
|
440
|
+
const request = supersessionRequestSchema.parse(
|
|
441
|
+
rawRequest,
|
|
442
|
+
) as MemorySupersessionInput;
|
|
443
|
+
const result = await model.completeObject({
|
|
444
|
+
schema: supersessionDecisionResponseSchema,
|
|
445
|
+
system: MEMORY_SUPERSESSION_SYSTEM,
|
|
446
|
+
prompt: supersessionPrompt(request),
|
|
447
|
+
maxTokens: 400,
|
|
448
|
+
});
|
|
449
|
+
return supersessionDecisionResponseSchema.parse(
|
|
450
|
+
result.object,
|
|
451
|
+
) as MemorySupersessionDecision;
|
|
452
|
+
},
|
|
361
453
|
async extractSessionMemories(rawRequest) {
|
|
362
454
|
const request = extractSessionRequestSchema.parse(rawRequest);
|
|
363
455
|
const result = await model.completeObject({
|
package/src/plugin.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createMemoryListTool,
|
|
7
7
|
createMemoryRemoveTool,
|
|
8
8
|
createMemorySearchTool,
|
|
9
|
+
type MemoryCreateToolContext,
|
|
9
10
|
type MemoryReviewer,
|
|
10
11
|
type MemoryToolContext,
|
|
11
12
|
} from "./tools";
|
|
@@ -48,6 +49,22 @@ function memoryToolContext(ctx: {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function memoryCreateToolContext(ctx: {
|
|
53
|
+
agent: MemoryReviewer;
|
|
54
|
+
conversationId?: string;
|
|
55
|
+
db: MemoryCreateToolContext["db"];
|
|
56
|
+
embedder?: MemoryCreateToolContext["embedder"];
|
|
57
|
+
requester?: MemoryCreateToolContext["requester"];
|
|
58
|
+
source: MemoryCreateToolContext["source"];
|
|
59
|
+
supersessionDecider: MemoryCreateToolContext["supersessionDecider"];
|
|
60
|
+
userText?: string;
|
|
61
|
+
}): MemoryCreateToolContext {
|
|
62
|
+
return {
|
|
63
|
+
...memoryToolContext(ctx),
|
|
64
|
+
supersessionDecider: ctx.supersessionDecider,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/** Create Junior's long-term memory plugin registration. */
|
|
52
69
|
export function createMemoryPlugin(options: MemoryPluginOptions = {}) {
|
|
53
70
|
const modelId = memoryModelId(options);
|
|
@@ -73,14 +90,23 @@ export function createMemoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
73
90
|
},
|
|
74
91
|
hooks: {
|
|
75
92
|
tools(ctx) {
|
|
93
|
+
const agent = createMemoryAgent(ctx.model);
|
|
76
94
|
const context = memoryToolContext({
|
|
77
95
|
...ctx,
|
|
78
|
-
agent
|
|
96
|
+
agent,
|
|
79
97
|
db: ctx.db as MemoryDb,
|
|
80
98
|
embedder: ctx.embedder,
|
|
81
99
|
});
|
|
82
100
|
return {
|
|
83
|
-
createMemory: createMemoryCreateTool(
|
|
101
|
+
createMemory: createMemoryCreateTool(
|
|
102
|
+
memoryCreateToolContext({
|
|
103
|
+
...ctx,
|
|
104
|
+
agent,
|
|
105
|
+
db: ctx.db as MemoryDb,
|
|
106
|
+
embedder: ctx.embedder,
|
|
107
|
+
supersessionDecider: agent,
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
84
110
|
removeMemory: createMemoryRemoveTool(context),
|
|
85
111
|
listMemories: createMemoryListTool(context),
|
|
86
112
|
searchMemories: createMemorySearchTool(context),
|
package/src/process-session.ts
CHANGED
|
@@ -135,8 +135,10 @@ export async function processMemorySession(
|
|
|
135
135
|
...(run.requester ? { requester: run.requester } : {}),
|
|
136
136
|
source: run.source,
|
|
137
137
|
});
|
|
138
|
+
const agent = createMemoryAgent(context.model);
|
|
138
139
|
const store = createMemoryStore(context.db as MemoryDb, runtimeContext, {
|
|
139
140
|
embedder: context.embedder,
|
|
141
|
+
supersessionDecider: agent,
|
|
140
142
|
});
|
|
141
143
|
await store.archiveExpiredMemories();
|
|
142
144
|
const memories = await getTaskMemories(context, async () => {
|
|
@@ -144,7 +146,6 @@ export async function processMemorySession(
|
|
|
144
146
|
limit: 10,
|
|
145
147
|
query: evidenceText,
|
|
146
148
|
});
|
|
147
|
-
const agent = createMemoryAgent(context.model);
|
|
148
149
|
return await agent.extractSessionMemories({
|
|
149
150
|
existingMemories: existingMemories.map((memory) => ({
|
|
150
151
|
content: memory.content,
|
package/src/recall.ts
CHANGED
|
@@ -32,6 +32,10 @@ function trimContent(content: string, maxLength: number): string {
|
|
|
32
32
|
return `${trimmed.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function formatObservedDate(observedAtMs: number): string {
|
|
36
|
+
return new Date(observedAtMs).toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
function renderMemoryPrompt(memories: MemoryRecord[]): string | undefined {
|
|
36
40
|
const header = "Relevant memories for this request:";
|
|
37
41
|
const footer =
|
|
@@ -40,7 +44,10 @@ function renderMemoryPrompt(memories: MemoryRecord[]): string | undefined {
|
|
|
40
44
|
let totalChars = header.length + footer.length + 2;
|
|
41
45
|
|
|
42
46
|
for (const memory of memories) {
|
|
43
|
-
const line = `- ${
|
|
47
|
+
const line = `- Observed ${formatObservedDate(memory.observedAtMs)}: ${trimContent(
|
|
48
|
+
memory.content,
|
|
49
|
+
MAX_MEMORY_LINE_CHARS,
|
|
50
|
+
)}`;
|
|
44
51
|
if (totalChars + line.length + 1 > MAX_PROMPT_CHARS) {
|
|
45
52
|
break;
|
|
46
53
|
}
|