@sentry/junior-memory 0.124.2 → 0.126.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/README.md CHANGED
@@ -15,7 +15,9 @@ exported types, tools, and tests are authoritative.
15
15
  inspection.
16
16
  - The dashboard exposes a searchable, paginated **Memories** user page for
17
17
  personal memories owned by actors linked to the signed-in viewer. Its
18
- **Forget** action archives the selected memory.
18
+ **Forget** action archives the selected memory. The overview charts global
19
+ passive-extraction cost from the durable `memory/memories_captured` events;
20
+ the System plugin report uses the same event-cost feed.
19
21
  - Authenticated REST clients can list and search personal memories through
20
22
  `GET /api/plugins/memory/memories`, read one through
21
23
  `GET /api/plugins/memory/memories/:id`, and archive one through
@@ -55,6 +57,10 @@ exported types, tools, and tests are authoritative.
55
57
  learning.
56
58
  - Passive extraction creates only durable, reusable facts—not transient tasks,
57
59
  conversation summaries, secrets, or speculative interpretation.
60
+ - Every completed passive extraction emits the namespaced
61
+ `memory/memories_captured` conversation event with its best-effort model cost.
62
+ Empty extraction outcomes remain durable for reporting but do not produce a
63
+ transcript row.
58
64
  - Candidate review resolves duplicates and supersession before activation.
59
65
  - Search combines independently ranked vector and PostgreSQL full-text matches
60
66
  with reciprocal rank fusion; provider-specific raw scores are never added
@@ -62,6 +68,10 @@ exported types, tools, and tests are authoritative.
62
68
  - Automatic recall retrieves a broad candidate window, then uses the
63
69
  memory-owned relevance model to admit at most five directly useful memories.
64
70
  An empty result contributes no filler prompt text.
71
+ - Every completed automatic recall attempt emits an invisible, namespaced
72
+ `memory/memories_recalled` conversation event with the admitted memory IDs
73
+ and best-effort embedding and relevance-model cost, including retrievals that
74
+ find no candidates and decisions that admit no memories.
65
75
  - Automatic recall degrades to no prompt contribution when relevance selection
66
76
  fails. Review, extraction, and write failures still fail their owning
67
77
  hook/task without corrupting existing memory state.
package/dist/agent.d.ts CHANGED
@@ -25,9 +25,9 @@ declare const createMemoryRequestSchema: z.ZodObject<{
25
25
  platform: z.ZodLiteral<"slack">;
26
26
  teamId: z.ZodString;
27
27
  channelId: z.ZodString;
28
- type: z.ZodEnum<{
29
- pub: "pub";
30
- priv: "priv";
28
+ visibility: z.ZodEnum<{
29
+ public: "public";
30
+ private: "private";
31
31
  }>;
32
32
  messageTs: z.ZodOptional<z.ZodString>;
33
33
  threadTs: z.ZodOptional<z.ZodString>;
@@ -43,7 +43,7 @@ declare const createMemoryRequestSchema: z.ZodObject<{
43
43
  }, z.core.$strict>>;
44
44
  source: z.ZodObject<{
45
45
  platform: z.ZodLiteral<"local">;
46
- type: z.ZodLiteral<"priv">;
46
+ visibility: z.ZodLiteral<"private">;
47
47
  conversationId: z.ZodString;
48
48
  }, z.core.$strict>;
49
49
  }, z.core.$strict>]>;
@@ -86,9 +86,9 @@ declare const extractSessionRequestSchema: z.ZodObject<{
86
86
  platform: z.ZodLiteral<"slack">;
87
87
  teamId: z.ZodString;
88
88
  channelId: z.ZodString;
89
- type: z.ZodEnum<{
90
- pub: "pub";
91
- priv: "priv";
89
+ visibility: z.ZodEnum<{
90
+ public: "public";
91
+ private: "private";
92
92
  }>;
93
93
  messageTs: z.ZodOptional<z.ZodString>;
94
94
  threadTs: z.ZodOptional<z.ZodString>;
@@ -104,7 +104,7 @@ declare const extractSessionRequestSchema: z.ZodObject<{
104
104
  }, z.core.$strict>>;
105
105
  source: z.ZodObject<{
106
106
  platform: z.ZodLiteral<"local">;
107
- type: z.ZodLiteral<"priv">;
107
+ visibility: z.ZodLiteral<"private">;
108
108
  conversationId: z.ZodString;
109
109
  }, z.core.$strict>;
110
110
  }, z.core.$strict>]>;
@@ -183,12 +183,22 @@ export type MemoryRecallInput = z.output<typeof memoryRecallInputSchema>;
183
183
  export type CreateMemoryRequest = z.output<typeof createMemoryRequestSchema>;
184
184
  export type ExtractSessionRequest = z.output<typeof extractSessionRequestSchema>;
185
185
  export type ExtractedMemory = z.output<typeof extractedMemoryResultSchema>;
186
+ /** Memories proposed by passive extraction and the model cost of that pass. */
187
+ export type MemoryExtractionResult = {
188
+ costUsd?: number;
189
+ memories: ExtractedMemory[];
190
+ };
191
+ /** Memories admitted by automatic recall and the model cost of that decision. */
192
+ export type MemoryRecallResult = {
193
+ costUsd?: number;
194
+ relevantIds: string[];
195
+ };
186
196
  export interface MemoryAgent {
187
197
  /** Select candidate memories that directly help with the current request. */
188
- selectRelevantMemories(request: MemoryRecallInput): Promise<string[]> | string[];
198
+ selectRelevantMemories(request: MemoryRecallInput): Promise<MemoryRecallResult> | MemoryRecallResult;
189
199
  /** Classify a new preference against related active preferences. */
190
200
  adjudicateSupersession(request: MemorySupersessionInput): Promise<MemorySupersessionDecision> | MemorySupersessionDecision;
191
- extractSessionMemories(request: ExtractSessionRequest): Promise<ExtractedMemory[]> | ExtractedMemory[];
201
+ extractSessionMemories(request: ExtractSessionRequest): Promise<MemoryExtractionResult> | MemoryExtractionResult;
192
202
  reviewCreateRequest(request: CreateMemoryRequest): Promise<MemoryReview> | MemoryReview;
193
203
  }
194
204
  /** Create the memory-owned agent that reviews, extracts, and recalls memories. */
package/dist/api.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * workspace scopes.
7
7
  */
8
8
  import { z } from "zod";
9
- import { type PluginRouteApp, type PluginUserPageActor } from "@sentry/junior-plugin-api";
9
+ import { type PluginConversationEventStats, type PluginRouteApp, type PluginUserPageActor } from "@sentry/junior-plugin-api";
10
10
  import type { MemoryDb } from "./store";
11
11
  export declare const memoryApiSchema: z.ZodObject<{
12
12
  content: z.ZodString;
@@ -49,7 +49,17 @@ export declare const memoryDashboardResponseSchema: z.ZodObject<{
49
49
  personal: z.ZodNumber;
50
50
  public: z.ZodNumber;
51
51
  }, z.core.$strict>>;
52
+ extractionDays: z.ZodArray<z.ZodObject<{
53
+ costUsd: z.ZodNumber;
54
+ date: z.ZodISODate;
55
+ events: z.ZodNumber;
56
+ }, z.core.$strict>>;
52
57
  generatedAt: z.ZodISODateTime;
58
+ recallDays: z.ZodArray<z.ZodObject<{
59
+ costUsd: z.ZodNumber;
60
+ date: z.ZodISODate;
61
+ events: z.ZodNumber;
62
+ }, z.core.$strict>>;
53
63
  stats: z.ZodObject<{
54
64
  active: z.ZodNumber;
55
65
  automatic: z.ZodNumber;
@@ -69,6 +79,7 @@ export type MemoryListResponse = z.output<typeof memoryListResponseSchema>;
69
79
  interface MemoryApiOptions {
70
80
  actors(email: string): Promise<PluginUserPageActor[]>;
71
81
  db: MemoryDb;
82
+ eventStats: PluginConversationEventStats;
72
83
  }
73
84
  /** Create the authenticated viewer-memory REST app. */
74
85
  export declare function createMemoryApi(options: MemoryApiOptions): PluginRouteApp;
package/dist/events.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { MemoryRecord } from "./store";
2
- /** Durable transcript event emitted after background memory capture. */
2
+ /** Previous stored memory-capture event shape retained for transcript rendering. */
3
+ export declare const memoriesCapturedEventV1: import("@sentry/junior-plugin-api").DefinedConversationEvent<{
4
+ memories: {
5
+ content: string;
6
+ id: string;
7
+ kind: "preference" | "procedure" | "knowledge";
8
+ observedAtMs: number;
9
+ scope: "personal" | "conversation";
10
+ }[];
11
+ }>;
12
+ /** Durable outcome emitted after every completed passive memory extraction. */
3
13
  export declare const memoriesCapturedEvent: import("@sentry/junior-plugin-api").DefinedConversationEvent<{
4
14
  memories: {
5
15
  content: string;
@@ -8,6 +18,12 @@ export declare const memoriesCapturedEvent: import("@sentry/junior-plugin-api").
8
18
  observedAtMs: number;
9
19
  scope: "personal" | "conversation";
10
20
  }[];
21
+ costUsd?: number | undefined;
22
+ }>;
23
+ /** Durable outcome emitted after one completed automatic recall attempt. */
24
+ export declare const memoriesRecalledEvent: import("@sentry/junior-plugin-api").DefinedConversationEvent<{
25
+ memories: string[];
26
+ costUsd?: number | undefined;
11
27
  }>;
12
28
  /** Select the stable, safe memory fields retained in conversation history. */
13
29
  export declare function capturedMemory(memory: MemoryRecord): {
package/dist/index.js CHANGED
@@ -397,6 +397,7 @@ var memoryRecordSchema = z2.object({
397
397
  }).strict();
398
398
  var embeddingVectorSchema = z2.array(numberSchema).length(MEMORY_EMBEDDING_DIMENSIONS);
399
399
  var embeddingResultSchema = z2.object({
400
+ costUsd: z2.number().finite().nonnegative().optional(),
400
401
  dimensions: z2.literal(MEMORY_EMBEDDING_DIMENSIONS),
401
402
  model: nonEmptyStringSchema2,
402
403
  provider: nonEmptyStringSchema2,
@@ -1680,9 +1681,12 @@ function createMemoryAgent(model) {
1680
1681
  });
1681
1682
  const decision = memoryRecallDecisionSchema.parse(result.object);
1682
1683
  const candidateIds = new Set(request.candidates.map(({ id }) => id));
1683
- return [...new Set(decision.relevantIds)].filter(
1684
- (id) => candidateIds.has(id)
1685
- );
1684
+ return {
1685
+ relevantIds: [...new Set(decision.relevantIds)].filter(
1686
+ (id) => candidateIds.has(id)
1687
+ ),
1688
+ ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : {}
1689
+ };
1686
1690
  },
1687
1691
  async adjudicateSupersession(rawRequest) {
1688
1692
  const request = memorySupersessionInputSchema.parse(rawRequest);
@@ -1702,9 +1706,12 @@ function createMemoryAgent(model) {
1702
1706
  prompt: sessionExtractionPrompt(request),
1703
1707
  maxTokens: 1e3
1704
1708
  });
1705
- return extractedMemoriesFromResponse(
1706
- extractMemoriesResponseSchema.parse(result.object)
1707
- );
1709
+ return {
1710
+ memories: extractedMemoriesFromResponse(
1711
+ extractMemoriesResponseSchema.parse(result.object)
1712
+ ),
1713
+ ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : {}
1714
+ };
1708
1715
  },
1709
1716
  async reviewCreateRequest(rawRequest) {
1710
1717
  const request = parseCreateMemoryRequest(rawRequest);
@@ -2070,7 +2077,7 @@ function runtimeContext(actor) {
2070
2077
  actor,
2071
2078
  source: {
2072
2079
  platform: "slack",
2073
- type: "priv",
2080
+ visibility: "private",
2074
2081
  teamId: actor.teamId,
2075
2082
  channelId: "DDASHBOARD"
2076
2083
  }
@@ -2080,7 +2087,7 @@ function runtimeContext(actor) {
2080
2087
  actor,
2081
2088
  source: {
2082
2089
  platform: "local",
2083
- type: "priv",
2090
+ visibility: "private",
2084
2091
  conversationId: "local:dashboard:memories"
2085
2092
  }
2086
2093
  };
@@ -2170,9 +2177,16 @@ var memoryDashboardDaySchema = z6.object({
2170
2177
  personal: z6.number().int().min(0),
2171
2178
  public: z6.number().int().min(0)
2172
2179
  }).strict();
2180
+ var memoryCostDaySchema = z6.object({
2181
+ costUsd: z6.number().finite().nonnegative(),
2182
+ date: z6.iso.date(),
2183
+ events: z6.number().int().min(0)
2184
+ }).strict();
2173
2185
  var memoryDashboardResponseSchema = z6.object({
2174
2186
  days: z6.array(memoryDashboardDaySchema).length(90),
2187
+ extractionDays: z6.array(memoryCostDaySchema).length(90),
2175
2188
  generatedAt: z6.iso.datetime(),
2189
+ recallDays: z6.array(memoryCostDaySchema).length(90),
2176
2190
  stats: z6.object({
2177
2191
  active: z6.number().int().min(0),
2178
2192
  automatic: z6.number().int().min(0),
@@ -2233,13 +2247,23 @@ function createMemoryApi(options) {
2233
2247
  const memories = createViewerMemories(options.db, actors);
2234
2248
  try {
2235
2249
  if (isDashboard && (request.method === "GET" || request.method === "HEAD")) {
2236
- const [stats, days] = await Promise.all([
2250
+ const [stats, days, extractionDays, recallDays] = await Promise.all([
2237
2251
  memories.stats(),
2238
- memories.timeline({ days: 90 })
2252
+ memories.timeline({ days: 90 }),
2253
+ options.eventStats.costsByDay({
2254
+ days: 90,
2255
+ eventName: "memories_captured"
2256
+ }),
2257
+ options.eventStats.costsByDay({
2258
+ days: 90,
2259
+ eventName: "memories_recalled"
2260
+ })
2239
2261
  ]);
2240
2262
  const body = memoryDashboardResponseSchema.parse({
2241
2263
  days,
2264
+ extractionDays,
2242
2265
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2266
+ recallDays,
2243
2267
  stats
2244
2268
  });
2245
2269
  return request.method === "HEAD" ? new Response(null, {
@@ -2679,6 +2703,7 @@ function memoryToolResult(target, data) {
2679
2703
  }
2680
2704
  function createMemoryCreateTool(context) {
2681
2705
  return definePluginTool({
2706
+ approvalMode: "approve",
2682
2707
  annotations: {
2683
2708
  destructiveHint: false,
2684
2709
  idempotentHint: true,
@@ -2858,23 +2883,47 @@ function eventPreview(content) {
2858
2883
  if (trimmed.length <= MAX_EVENT_PREVIEW_CHARS) return trimmed;
2859
2884
  return `${trimmed.slice(0, MAX_EVENT_PREVIEW_CHARS - 3).trimEnd()}...`;
2860
2885
  }
2861
- var memoriesCapturedEvent = defineConversationEvent({
2886
+ var capturedMemoriesSchema = z8.object({
2887
+ memories: z8.array(capturedMemorySchema).max(100),
2888
+ costUsd: z8.number().finite().nonnegative().optional()
2889
+ }).strict();
2890
+ var recalledMemoriesSchema = z8.object({
2891
+ memories: z8.array(z8.string().min(1)).max(5),
2892
+ costUsd: z8.number().finite().nonnegative().optional()
2893
+ }).strict();
2894
+ function renderCapturedMemories(event) {
2895
+ const count = event.memories.length;
2896
+ if (count === 0) return void 0;
2897
+ return {
2898
+ icon: "brain",
2899
+ title: count === 1 ? "Memory captured" : "Memories captured",
2900
+ preview: count === 1 ? eventPreview(event.memories[0].content) : `${count} memories`,
2901
+ details: event.memories.map((memory) => ({
2902
+ title: memory.content,
2903
+ metadata: [memory.kind, memory.scope]
2904
+ }))
2905
+ };
2906
+ }
2907
+ var memoriesCapturedEventV1 = defineConversationEvent({
2862
2908
  name: "memories_captured",
2863
2909
  version: 1,
2864
2910
  schema: z8.object({
2865
2911
  memories: z8.array(capturedMemorySchema).min(1).max(100)
2866
2912
  }).strict(),
2867
- renderEvent(event) {
2868
- const count = event.memories.length;
2869
- return {
2870
- icon: "brain",
2871
- title: count === 1 ? "Memory captured" : "Memories captured",
2872
- preview: count === 1 ? eventPreview(event.memories[0].content) : `${count} memories`,
2873
- details: event.memories.map((memory) => ({
2874
- title: memory.content,
2875
- metadata: [memory.kind, memory.scope]
2876
- }))
2877
- };
2913
+ renderEvent: renderCapturedMemories
2914
+ });
2915
+ var memoriesCapturedEvent = defineConversationEvent({
2916
+ name: "memories_captured",
2917
+ version: 2,
2918
+ schema: capturedMemoriesSchema,
2919
+ renderEvent: renderCapturedMemories
2920
+ });
2921
+ var memoriesRecalledEvent = defineConversationEvent({
2922
+ name: "memories_recalled",
2923
+ version: 1,
2924
+ schema: recalledMemoriesSchema,
2925
+ renderEvent() {
2926
+ return void 0;
2878
2927
  }
2879
2928
  });
2880
2929
  function capturedMemory(memory) {
@@ -2895,14 +2944,19 @@ var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
2895
2944
  "searchMemories"
2896
2945
  ]);
2897
2946
  var MEMORY_TASK_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2898
- var extractedMemoryCacheSchema = z9.array(
2947
+ var extractedMemorySchema2 = z9.object({
2948
+ content: z9.string().min(1),
2949
+ expiresAtMs: z9.number().finite().nullable(),
2950
+ kind: z9.enum(MEMORY_KINDS),
2951
+ evidenceMessageIndices: z9.array(z9.number().int().nonnegative()).min(1).max(10)
2952
+ }).strict().transform(parseExtractedMemory);
2953
+ var extractedMemoryCacheSchema = z9.union([
2899
2954
  z9.object({
2900
- content: z9.string().min(1),
2901
- expiresAtMs: z9.number().finite().nullable(),
2902
- kind: z9.enum(MEMORY_KINDS),
2903
- evidenceMessageIndices: z9.array(z9.number().int().nonnegative()).min(1).max(10)
2904
- }).strict().transform(parseExtractedMemory)
2905
- );
2955
+ costUsd: z9.number().finite().nonnegative().optional(),
2956
+ memories: z9.array(extractedMemorySchema2).max(5)
2957
+ }).strict(),
2958
+ z9.array(extractedMemorySchema2).max(5).transform((memories) => ({ memories }))
2959
+ ]);
2906
2960
  function recordCapturedMemory(captured, result) {
2907
2961
  const supersededIds = new Set(result.supersededIds ?? []);
2908
2962
  for (let index2 = captured.length - 1; index2 >= 0; index2 -= 1) {
@@ -2969,7 +3023,7 @@ function passiveInput(sessionId, memory, sourceKey2, target) {
2969
3023
  ...memory.expiresAtMs !== null ? { expiresAtMs: memory.expiresAtMs } : {}
2970
3024
  };
2971
3025
  }
2972
- async function getTaskMemories(context, extract) {
3026
+ async function getTaskExtraction(context, extract) {
2973
3027
  const cacheKey = `memory-extraction:${context.id}`;
2974
3028
  const cached = await context.state.get(cacheKey);
2975
3029
  if (cached !== void 0) {
@@ -2979,11 +3033,9 @@ async function getTaskMemories(context, extract) {
2979
3033
  }
2980
3034
  await context.state.delete(cacheKey);
2981
3035
  }
2982
- const memories = await extract();
2983
- if (memories.length > 0) {
2984
- await context.state.set(cacheKey, memories, MEMORY_TASK_STATE_TTL_MS);
2985
- }
2986
- return memories;
3036
+ const extraction = await extract();
3037
+ await context.state.set(cacheKey, extraction, MEMORY_TASK_STATE_TTL_MS);
3038
+ return extraction;
2987
3039
  }
2988
3040
  async function processMemorySession(context) {
2989
3041
  const run = await context.run.load();
@@ -3015,7 +3067,7 @@ async function processMemorySession(context) {
3015
3067
  supersessionDecider: agent
3016
3068
  });
3017
3069
  await store.archiveExpiredMemories();
3018
- const memories = await getTaskMemories(context, async () => {
3070
+ const extraction = await getTaskExtraction(context, async () => {
3019
3071
  const existingMemories = await store.searchMemories({
3020
3072
  limit: 10,
3021
3073
  query: evidenceText
@@ -3029,11 +3081,8 @@ async function processMemorySession(context) {
3029
3081
  runtimeContext: runtimeContext2
3030
3082
  });
3031
3083
  });
3032
- if (memories.length === 0) {
3033
- return;
3034
- }
3035
3084
  const captured = [];
3036
- for (const memory of memories) {
3085
+ for (const memory of extraction.memories) {
3037
3086
  const target = routeExtractedMemory(memory, transcript, run);
3038
3087
  if (target === "drop") {
3039
3088
  continue;
@@ -3047,9 +3096,12 @@ async function processMemorySession(context) {
3047
3096
  const result = await store.createMemory(input);
3048
3097
  recordCapturedMemory(captured, result);
3049
3098
  }
3050
- if (captured.length > 0) {
3051
- await context.events.emit(memoriesCapturedEvent({ memories: captured }));
3052
- }
3099
+ await context.events.emit(
3100
+ memoriesCapturedEvent({
3101
+ memories: captured,
3102
+ ...extraction.costUsd !== void 0 ? { costUsd: extraction.costUsd } : {}
3103
+ })
3104
+ );
3053
3105
  }
3054
3106
 
3055
3107
  // src/recall.ts
@@ -3113,6 +3165,19 @@ function renderMemoryPrompt(memories) {
3113
3165
  "Treat these as possibly stale context. Current user instructions and repository evidence take priority."
3114
3166
  ].join("\n");
3115
3167
  }
3168
+ function addUsd(left, right) {
3169
+ if (left === void 0) return right;
3170
+ if (right === void 0) return left;
3171
+ return Math.round((left + right) * 1e12) / 1e12;
3172
+ }
3173
+ async function emitRecallOutcome(args) {
3174
+ await args.events?.emit(
3175
+ memoriesRecalledEvent({
3176
+ memories: args.memories,
3177
+ ...args.costUsd !== void 0 ? { costUsd: args.costUsd } : {}
3178
+ })
3179
+ );
3180
+ }
3116
3181
  var memoryRecallContext = definePromptContext({
3117
3182
  kind: "recall",
3118
3183
  version: 1,
@@ -3128,19 +3193,33 @@ async function createMemoryPromptContributions(context) {
3128
3193
  ...context.actor ? { actor: context.actor } : {},
3129
3194
  source: context.source
3130
3195
  });
3196
+ let embeddingCostUsd;
3197
+ const sourceEmbedder = context.embedder;
3198
+ const embedder = sourceEmbedder ? {
3199
+ async embedTexts(input) {
3200
+ const result = await sourceEmbedder.embedTexts(input);
3201
+ embeddingCostUsd = addUsd(embeddingCostUsd, result.costUsd);
3202
+ return result;
3203
+ }
3204
+ } : void 0;
3131
3205
  const candidates = await createMemoryStore(context.db, runtimeContext2, {
3132
- ...context.embedder ? { embedder: context.embedder } : {},
3206
+ ...embedder ? { embedder } : {},
3133
3207
  ...context.maxVectorDistance !== void 0 ? { maxVectorDistance: context.maxVectorDistance } : {}
3134
3208
  }).recallMemories({
3135
3209
  query: context.text,
3136
3210
  limit: RECALL_CANDIDATE_LIMIT
3137
3211
  });
3138
3212
  if (candidates.length === 0) {
3213
+ await emitRecallOutcome({
3214
+ ...embeddingCostUsd !== void 0 ? { costUsd: embeddingCostUsd } : {},
3215
+ events: context.events,
3216
+ memories: []
3217
+ });
3139
3218
  return void 0;
3140
3219
  }
3141
- let relevantIds;
3220
+ let recall;
3142
3221
  try {
3143
- relevantIds = await context.agent.selectRelevantMemories({
3222
+ recall = await context.agent.selectRelevantMemories({
3144
3223
  candidates: candidates.map(({ content, id }) => ({ content, id })),
3145
3224
  userRequest: context.text
3146
3225
  });
@@ -3151,8 +3230,14 @@ async function createMemoryPromptContributions(context) {
3151
3230
  const candidatesById = new Map(
3152
3231
  candidates.map((memory) => [memory.id, memory])
3153
3232
  );
3154
- const relevant = relevantIds.map((id) => candidatesById.get(id)).filter((memory) => memory !== void 0).slice(0, DEFAULT_RECALL_LIMIT);
3233
+ const relevant = recall.relevantIds.map((id) => candidatesById.get(id)).filter((memory) => memory !== void 0).slice(0, DEFAULT_RECALL_LIMIT);
3155
3234
  const selected = selectPromptMemories(relevant);
3235
+ const costUsd = addUsd(embeddingCostUsd, recall.costUsd);
3236
+ await emitRecallOutcome({
3237
+ ...costUsd !== void 0 ? { costUsd } : {},
3238
+ events: context.events,
3239
+ memories: selected.map(({ id }) => id)
3240
+ });
3156
3241
  if (selected.length === 0) {
3157
3242
  return void 0;
3158
3243
  }
@@ -3231,6 +3316,15 @@ function formatPercent(value) {
3231
3316
  style: "percent"
3232
3317
  }).format(value);
3233
3318
  }
3319
+ function formatUsd(value) {
3320
+ const maximumFractionDigits = value > 0 && value < 0.01 ? 4 : 2;
3321
+ return new Intl.NumberFormat("en-US", {
3322
+ currency: "USD",
3323
+ maximumFractionDigits,
3324
+ minimumFractionDigits: 2,
3325
+ style: "currency"
3326
+ }).format(value);
3327
+ }
3234
3328
  async function buildMemoryOperationalReport(args) {
3235
3329
  const active = and4(
3236
3330
  isNull3(juniorMemoryMemories.archivedAtMs),
@@ -3265,6 +3359,11 @@ async function buildMemoryOperationalReport(args) {
3265
3359
  const activeCount = counts?.active ?? 0;
3266
3360
  const embeddedCount = counts?.embedded ?? 0;
3267
3361
  const embeddingCoverage = activeCount === 0 ? 0 : embeddedCount / activeCount;
3362
+ const extractionThirtyDays = args.extractionDays.slice(-30);
3363
+ const extractionCostThirtyDays = extractionThirtyDays.reduce(
3364
+ (total, day) => total + day.costUsd,
3365
+ 0
3366
+ );
3268
3367
  return {
3269
3368
  generatedAt: new Date(args.nowMs).toISOString(),
3270
3369
  title: "Memory",
@@ -3274,6 +3373,10 @@ async function buildMemoryOperationalReport(args) {
3274
3373
  tone: activeCount > 0 ? "good" : "neutral",
3275
3374
  value: formatCount(activeCount)
3276
3375
  },
3376
+ {
3377
+ label: "extraction cost \xB7 30d",
3378
+ value: formatUsd(extractionCostThirtyDays)
3379
+ },
3277
3380
  {
3278
3381
  label: "created \xB7 30d",
3279
3382
  value: formatCount(counts?.createdThirtyDays ?? 0)
@@ -3293,6 +3396,19 @@ async function buildMemoryOperationalReport(args) {
3293
3396
  }
3294
3397
  ],
3295
3398
  widgets: [
3399
+ {
3400
+ categories: args.extractionDays.map((day) => ({
3401
+ id: day.date,
3402
+ label: day.date,
3403
+ values: { costUsd: day.costUsd }
3404
+ })),
3405
+ description: "Estimated model cost of passive memory extraction",
3406
+ id: "extraction-cost",
3407
+ series: [{ format: "usd", key: "costUsd", label: "Cost" }],
3408
+ timeRangeDays: [...WINDOWS],
3409
+ title: "Extraction cost",
3410
+ type: "bar_chart"
3411
+ },
3296
3412
  {
3297
3413
  categories: memoryDays.map((day) => ({
3298
3414
  id: day.date,
@@ -3453,7 +3569,11 @@ function memoryPlugin(options = {}) {
3453
3569
  },
3454
3570
  model: modelId ? { structuredModelId: modelId } : { structuredModel: "default" },
3455
3571
  packageName: "@sentry/junior-memory",
3456
- conversationEvents: [memoriesCapturedEvent],
3572
+ conversationEvents: [
3573
+ memoriesCapturedEventV1,
3574
+ memoriesCapturedEvent,
3575
+ memoriesRecalledEvent
3576
+ ],
3457
3577
  cli: {
3458
3578
  commands: [createMemoryCliCommand()]
3459
3579
  },
@@ -3467,15 +3587,21 @@ function memoryPlugin(options = {}) {
3467
3587
  userPages: [createMemoryUserPage()],
3468
3588
  hooks: {
3469
3589
  async operationalReport(ctx) {
3590
+ const extractionDays = await ctx.eventStats.costsByDay({
3591
+ days: 90,
3592
+ eventName: "memories_captured"
3593
+ });
3470
3594
  return await buildMemoryOperationalReport({
3471
3595
  db: ctx.db,
3596
+ extractionDays,
3472
3597
  nowMs: ctx.nowMs
3473
3598
  });
3474
3599
  },
3475
3600
  apiRoutes(ctx) {
3476
3601
  return createMemoryApi({
3477
3602
  actors: ctx.viewer.actors,
3478
- db: ctx.db
3603
+ db: ctx.db,
3604
+ eventStats: ctx.eventStats
3479
3605
  });
3480
3606
  },
3481
3607
  tools(ctx) {
@@ -3508,6 +3634,7 @@ function memoryPlugin(options = {}) {
3508
3634
  ...ctx.actor ? { actor: ctx.actor } : {},
3509
3635
  db: ctx.db,
3510
3636
  embedder: ctx.embedder,
3637
+ events: ctx.events,
3511
3638
  log: ctx.log,
3512
3639
  maxVectorDistance: recallMaxVectorDistance(options),
3513
3640
  source: ctx.source,