@sentry/junior-memory 0.90.0 → 0.92.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 CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginModel } from "@sentry/junior-plugin-api";
1
+ import { type PluginModel } from "@sentry/junior-plugin-api";
2
2
  import { z } from "zod";
3
3
  import type { MemorySupersessionDecision, MemorySupersessionInput } from "./store";
4
4
  declare const createMemoryRequestSchema: z.ZodObject<{
@@ -6,7 +6,7 @@ declare const createMemoryRequestSchema: z.ZodObject<{
6
6
  expiresAtMs: z.ZodOptional<z.ZodNumber>;
7
7
  runtimeContext: z.ZodUnion<readonly [z.ZodObject<{
8
8
  conversationId: z.ZodOptional<z.ZodString>;
9
- requester: z.ZodOptional<z.ZodObject<{
9
+ actor: z.ZodOptional<z.ZodObject<{
10
10
  platform: z.ZodLiteral<"slack">;
11
11
  teamId: z.ZodString;
12
12
  email: z.ZodOptional<z.ZodString>;
@@ -27,7 +27,7 @@ declare const createMemoryRequestSchema: z.ZodObject<{
27
27
  }, z.core.$strict>;
28
28
  }, z.core.$strict>, z.ZodObject<{
29
29
  conversationId: z.ZodOptional<z.ZodString>;
30
- requester: z.ZodOptional<z.ZodObject<{
30
+ actor: z.ZodOptional<z.ZodObject<{
31
31
  platform: z.ZodLiteral<"local">;
32
32
  email: z.ZodOptional<z.ZodString>;
33
33
  fullName: z.ZodOptional<z.ZodString>;
@@ -48,9 +48,26 @@ declare const extractSessionRequestSchema: z.ZodObject<{
48
48
  existingMemories: z.ZodDefault<z.ZodArray<z.ZodObject<{
49
49
  content: z.ZodString;
50
50
  }, z.core.$strict>>>;
51
+ actors: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
52
+ platform: z.ZodLiteral<"slack">;
53
+ teamId: z.ZodString;
54
+ email: z.ZodOptional<z.ZodString>;
55
+ fullName: z.ZodOptional<z.ZodString>;
56
+ userId: z.ZodString;
57
+ userName: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strict>, z.ZodObject<{
59
+ platform: z.ZodLiteral<"local">;
60
+ email: z.ZodOptional<z.ZodString>;
61
+ fullName: z.ZodOptional<z.ZodString>;
62
+ userId: z.ZodString;
63
+ userName: z.ZodOptional<z.ZodString>;
64
+ }, z.core.$strict>, z.ZodObject<{
65
+ platform: z.ZodLiteral<"system">;
66
+ name: z.ZodString;
67
+ }, z.core.$strict>], "platform">>;
51
68
  runtimeContext: z.ZodUnion<readonly [z.ZodObject<{
52
69
  conversationId: z.ZodOptional<z.ZodString>;
53
- requester: z.ZodOptional<z.ZodObject<{
70
+ actor: z.ZodOptional<z.ZodObject<{
54
71
  platform: z.ZodLiteral<"slack">;
55
72
  teamId: z.ZodString;
56
73
  email: z.ZodOptional<z.ZodString>;
@@ -71,7 +88,7 @@ declare const extractSessionRequestSchema: z.ZodObject<{
71
88
  }, z.core.$strict>;
72
89
  }, z.core.$strict>, z.ZodObject<{
73
90
  conversationId: z.ZodOptional<z.ZodString>;
74
- requester: z.ZodOptional<z.ZodObject<{
91
+ actor: z.ZodOptional<z.ZodObject<{
75
92
  platform: z.ZodLiteral<"local">;
76
93
  email: z.ZodOptional<z.ZodString>;
77
94
  fullName: z.ZodOptional<z.ZodString>;
@@ -91,6 +108,30 @@ declare const extractSessionRequestSchema: z.ZodObject<{
91
108
  assistant: "assistant";
92
109
  }>;
93
110
  text: z.ZodString;
111
+ provenance: z.ZodOptional<z.ZodObject<{
112
+ authority: z.ZodEnum<{
113
+ instruction: "instruction";
114
+ context: "context";
115
+ }>;
116
+ actor: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
117
+ platform: z.ZodLiteral<"slack">;
118
+ teamId: z.ZodString;
119
+ email: z.ZodOptional<z.ZodString>;
120
+ fullName: z.ZodOptional<z.ZodString>;
121
+ userId: z.ZodString;
122
+ userName: z.ZodOptional<z.ZodString>;
123
+ }, z.core.$strict>, z.ZodObject<{
124
+ platform: z.ZodLiteral<"local">;
125
+ email: z.ZodOptional<z.ZodString>;
126
+ fullName: z.ZodOptional<z.ZodString>;
127
+ userId: z.ZodString;
128
+ userName: z.ZodOptional<z.ZodString>;
129
+ }, z.core.$strict>, z.ZodObject<{
130
+ platform: z.ZodLiteral<"system">;
131
+ name: z.ZodString;
132
+ }, z.core.$strict>], "platform">>;
133
+ }, z.core.$strict>>;
134
+ isRunActor: z.ZodOptional<z.ZodBoolean>;
94
135
  }, z.core.$strict>, z.ZodObject<{
95
136
  type: z.ZodLiteral<"toolResult">;
96
137
  toolName: z.ZodString;
@@ -128,6 +169,7 @@ declare const extractedMemoryResultSchema: z.ZodObject<{
128
169
  procedure: "procedure";
129
170
  knowledge: "knowledge";
130
171
  }>;
172
+ evidenceMessageIndices: z.ZodArray<z.ZodNumber>;
131
173
  }, z.core.$strict>;
132
174
  export type MemoryReview = z.output<typeof memoryReviewDecisionSchema>;
133
175
  export type CreateMemoryRequest = z.output<typeof createMemoryRequestSchema>;
package/dist/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
  import { defineJuniorPlugin } from "@sentry/junior-plugin-api";
3
3
 
4
4
  // src/agent.ts
5
+ import { actorSchema } from "@sentry/junior-plugin-api";
5
6
  import { z as z2 } from "zod";
6
7
 
7
8
  // src/types.ts
8
9
  import {
9
- localRequesterSchema,
10
+ localActorSchema,
10
11
  localSourceSchema,
11
- slackRequesterSchema,
12
+ slackActorSchema,
12
13
  slackSourceSchema
13
14
  } from "@sentry/junior-plugin-api";
14
15
  import { z } from "zod";
@@ -28,12 +29,12 @@ var MEMORY_EMBEDDING_DIMENSIONS = 1536;
28
29
  var nonEmptyStringSchema = z.string().min(1);
29
30
  var slackMemoryRuntimeContextSchema = z.object({
30
31
  conversationId: nonEmptyStringSchema.optional(),
31
- requester: slackRequesterSchema.optional(),
32
+ actor: slackActorSchema.optional(),
32
33
  source: slackSourceSchema
33
34
  }).strict();
34
35
  var localMemoryRuntimeContextSchema = z.object({
35
36
  conversationId: nonEmptyStringSchema.optional(),
36
- requester: localRequesterSchema.optional(),
37
+ actor: localActorSchema.optional(),
37
38
  source: localSourceSchema
38
39
  }).strict();
39
40
  var memoryRuntimeContextSchema = z.union([
@@ -61,19 +62,27 @@ var createMemoryRequestSchema = z2.object({
61
62
  currentUserText: z2.string().min(1).optional()
62
63
  }).strict().optional()
63
64
  }).strict();
65
+ var transcriptProvenanceSchema = z2.object({
66
+ authority: z2.enum(["instruction", "context"]),
67
+ actor: actorSchema.optional()
68
+ }).strict();
69
+ var evidenceMessageIndicesSchema = z2.array(z2.number().int().nonnegative()).min(1).max(10).describe("Indices from <run-transcript> that directly support this memory.");
64
70
  var extractSessionRequestSchema = z2.object({
65
71
  existingMemories: z2.array(
66
72
  z2.object({
67
73
  content: z2.string().min(1)
68
74
  }).strict()
69
75
  ).max(10).default([]),
76
+ actors: z2.array(actorSchema),
70
77
  runtimeContext: memoryRuntimeContextSchema,
71
78
  transcript: z2.array(
72
79
  z2.discriminatedUnion("type", [
73
80
  z2.object({
74
81
  type: z2.literal("message"),
75
82
  role: z2.enum(["user", "assistant"]),
76
- text: z2.string().min(1)
83
+ text: z2.string().min(1),
84
+ provenance: transcriptProvenanceSchema.optional(),
85
+ isRunActor: z2.boolean().optional()
77
86
  }).strict(),
78
87
  z2.object({
79
88
  type: z2.literal("toolResult"),
@@ -116,10 +125,10 @@ var memoryReviewResponseSchema = z2.discriminatedUnion("decision", [
116
125
  z2.object({
117
126
  decision: z2.literal("store"),
118
127
  kind: memoryKindSchema.describe(
119
- "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."
128
+ "Use preference only for actor-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."
120
129
  ),
121
130
  canonicalFact: z2.string().min(1).describe(
122
- "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."
131
+ "Stored memory text. It must be self-contained and must not include actor names, actor/user labels, source labels, or first- or second-person wording."
123
132
  ),
124
133
  expiresAtMs: expiresAtMsSchema
125
134
  }).strict(),
@@ -130,17 +139,19 @@ var memoryReviewResponseSchema = z2.discriminatedUnion("decision", [
130
139
  ]);
131
140
  var extractedMemorySchema = z2.object({
132
141
  kind: memoryKindSchema.describe(
133
- "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."
142
+ "Use preference only for actor-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."
134
143
  ),
135
144
  canonicalFact: z2.string().min(1).describe(
136
- "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."
145
+ "Stored memory text as one self-contained fact. It must not include actor names, actor/user labels, source labels, or first- or second-person wording."
137
146
  ),
138
- expiresAtMs: expiresAtMsSchema
147
+ expiresAtMs: expiresAtMsSchema,
148
+ evidenceMessageIndices: evidenceMessageIndicesSchema
139
149
  }).strict();
140
150
  var extractedMemoryResultSchema = z2.object({
141
151
  content: z2.string().min(1),
142
152
  expiresAtMs: expiresAtMsSchema,
143
- kind: memoryKindSchema
153
+ kind: memoryKindSchema,
154
+ evidenceMessageIndices: evidenceMessageIndicesSchema
144
155
  }).strict();
145
156
  var extractMemoriesResponseSchema = z2.object({
146
157
  memories: z2.array(extractedMemorySchema).max(5).describe(
@@ -172,7 +183,7 @@ var MEMORY_EXTRACTION_SYSTEM = [
172
183
  ].join("\n");
173
184
  var MEMORY_SUPERSESSION_SYSTEM = [
174
185
  "You are Junior's memory supersession agent.",
175
- "Decide whether a new requester preference clearly replaces existing active requester preferences.",
186
+ "Decide whether a new actor preference clearly replaces existing active actor preferences.",
176
187
  "Return supersedes_old only for obvious changed preferences about the same mutable slot.",
177
188
  "If the facts are additive, different topics, duplicate, broader/narrower without direct replacement, or uncertain, do not supersede."
178
189
  ].join("\n");
@@ -181,19 +192,19 @@ var CANONICAL_CONTENT_RULES = [
181
192
  "- Store the minimum useful assertion supported by source evidence; do not add adjacent steps, caveats, or generalized advice.",
182
193
  "- Do not return both concise and expanded variants of the same source assertion; keep the shortest self-contained canonical memory.",
183
194
  "- Put ownership in structured fields, not prose.",
184
- "- For requester memories, omit the subject and write a stable fact such as 'Prefers X', 'Uses Y', or 'Thinks Z'.",
195
+ "- For actor memories, omit the subject and write a stable fact such as 'Prefers X', 'Uses Y', or 'Thinks Z'.",
185
196
  "- Drop perspective/provenance markers while preserving useful context.",
186
- "- Remove requester names, display names, requester/user labels, first- or second-person wording, thread labels, channel labels, and source labels."
197
+ "- Remove actor names, display names, actor/user labels, first- or second-person wording, thread labels, channel labels, and source labels."
187
198
  ];
188
199
  function escapeXml(value) {
189
200
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
190
201
  }
191
202
  function runtimeDescription(request) {
192
203
  const runtime = request.runtimeContext;
193
- const requester = runtime.requester?.platform === "slack" ? `slack:${runtime.requester.teamId}:${runtime.requester.userId}` : runtime.requester?.platform === "local" ? `local:${runtime.requester.userId}` : "none";
204
+ const actor = runtime.actor?.platform === "slack" ? `slack:${runtime.actor.teamId}:${runtime.actor.userId}` : runtime.actor?.platform === "local" ? `local:${runtime.actor.userId}` : "none";
194
205
  const source = runtime.source.platform === "slack" ? `slack:${runtime.source.teamId}:${runtime.source.channelId}` : `local:${runtime.source.conversationId}`;
195
206
  const lines = [
196
- `- requester: ${escapeXml(requester)}`,
207
+ `- actor: ${escapeXml(actor)}`,
197
208
  `- source: ${escapeXml(source)}`,
198
209
  `- has_conversation: ${runtime.conversationId ? "true" : "false"}`,
199
210
  `- expires_at: ${request.expiresAtMs === void 0 ? "never" : escapeXml(new Date(request.expiresAtMs).toISOString())}`
@@ -225,14 +236,22 @@ function existingMemoriesContext(request) {
225
236
  "</existing-memories>"
226
237
  ].join("\n");
227
238
  }
228
- function memoryKindsContext() {
229
- return [
230
- "<memory-kinds>",
231
- "- preference: a durable first-person personal preference, opinion, habit, or workflow owned by the current requester. Stored as requester memory.",
239
+ function allowedExtractionKinds(actorCount) {
240
+ return actorCount === 1 ? new Set(MEMORY_KINDS) : /* @__PURE__ */ new Set(["procedure", "knowledge"]);
241
+ }
242
+ function memoryKindsContext(allowedKinds) {
243
+ const lines = ["<memory-kinds>"];
244
+ if (allowedKinds.has("preference")) {
245
+ lines.push(
246
+ "- preference: a durable first-person personal preference, opinion, habit, or workflow owned by the current actor. Stored as actor memory."
247
+ );
248
+ }
249
+ lines.push(
232
250
  "- 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.",
233
- "- 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.",
251
+ "- knowledge: stable shared project, channel, operational, or runbook fact that is not a personal actor preference. Direct answers to user inquiries qualify only when they are durable beyond this run. Stored as conversation memory.",
234
252
  "</memory-kinds>"
235
- ].join("\n");
253
+ );
254
+ return lines.join("\n");
236
255
  }
237
256
  function reviewPrompt(request) {
238
257
  const sections = [
@@ -250,17 +269,17 @@ function reviewPrompt(request) {
250
269
  "<rules>",
251
270
  "- Return store only when the candidate is public/shareable, durable, and self-contained.",
252
271
  "- First classify the memory kind: preference, procedure, or knowledge.",
253
- "- Use kind=preference only for first-person facts authored by the current requester about their own preference, opinion, habit, identity, or workflow.",
254
- "- 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.",
272
+ "- Use kind=preference only for first-person facts authored by the current actor about their own preference, opinion, habit, identity, or workflow.",
273
+ "- 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 actor.",
255
274
  "- Use kind=procedure for reusable task/process/runbook instructions.",
256
275
  "- Use kind=knowledge for shared project, channel, operational, or runbook facts.",
257
276
  "- 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.",
258
- "- 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.",
277
+ "- A candidate may be badly phrased by an outer assistant or extraction pass. When current-user-message contains the actor's own first-person memory fact, treat that as actor-authored source evidence and canonicalize the fact instead of rejecting for third-person wording.",
259
278
  "- 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.",
260
279
  "- 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.",
261
280
  "- Store content as person-less, source-less canonical knowledge. Ownership and source live in structured metadata, not prose.",
262
- "- For requester memories, omit the subject and write the content as a stable fact such as 'Prefers X', 'Uses Y', or 'Thinks Z'.",
263
- "- Remove requester names, display names, requester/user labels, first- or second-person wording, thread labels, channel labels, and source labels from stored content.",
281
+ "- For actor memories, omit the subject and write the content as a stable fact such as 'Prefers X', 'Uses Y', or 'Thinks Z'.",
282
+ "- Remove actor names, display names, actor/user labels, first- or second-person wording, thread labels, channel labels, and source labels from stored content.",
264
283
  "- Reject third-party personal profile facts, even if they mention a name.",
265
284
  "- Reject vague content such as 'remember this' unless the candidate or current-user-message contains the concrete fact.",
266
285
  "- Preserve the requested expiration when one exists; otherwise set expiresAtMs to null.",
@@ -270,6 +289,15 @@ function reviewPrompt(request) {
270
289
  ].filter((section) => section !== void 0);
271
290
  return sections.join("\n");
272
291
  }
292
+ function transcriptActorLabel(actor) {
293
+ if (!actor) {
294
+ return "none";
295
+ }
296
+ if (actor.platform === "system") {
297
+ return `system:${actor.name}`;
298
+ }
299
+ return actor.platform === "slack" ? `slack:${actor.teamId}:${actor.userId}` : `local:${actor.userId}`;
300
+ }
273
301
  function runTranscriptContext(request) {
274
302
  return [
275
303
  "<run-transcript>",
@@ -281,8 +309,11 @@ function runTranscriptContext(request) {
281
309
  "</tool-result>"
282
310
  ].join("\n");
283
311
  }
312
+ const authority = entry.provenance?.authority ?? "context";
313
+ const isRunActor = entry.isRunActor === true;
314
+ const actor = transcriptActorLabel(entry.provenance?.actor);
284
315
  return [
285
- `<message index="${index2}" role="${entry.role}">`,
316
+ `<message index="${index2}" role="${entry.role}" authority="${authority}" is_run_actor="${isRunActor ? "true" : "false"}" actor="${escapeXml(actor)}">`,
286
317
  escapeXml(entry.text),
287
318
  "</message>"
288
319
  ].join("\n");
@@ -291,6 +322,8 @@ function runTranscriptContext(request) {
291
322
  ].join("\n");
292
323
  }
293
324
  function sessionExtractionPrompt(request) {
325
+ const allowedKinds = allowedExtractionKinds(request.actors.length);
326
+ const allowsPreference = allowedKinds.has("preference");
294
327
  return [
295
328
  "<memory-extraction-input>",
296
329
  "Extract durable memories from this completed agent run using the runtime-owned context below.",
@@ -301,12 +334,19 @@ function sessionExtractionPrompt(request) {
301
334
  "",
302
335
  existingMemoriesContext(request),
303
336
  "",
304
- memoryKindsContext(),
337
+ memoryKindsContext(allowedKinds),
305
338
  "",
306
339
  runTranscriptContext(request),
307
340
  "",
308
341
  "<rules>",
309
342
  "- Return at most five memories.",
343
+ "- Every returned memory must cite one or more evidenceMessageIndices from <run-transcript>.",
344
+ "- Cite only indices that directly support the stored fact; do not cite assistant messages as independent evidence.",
345
+ "- Each transcript message exposes authority (instruction or context), is_run_actor, and an actor id. Use these to classify evidence.",
346
+ ...allowsPreference ? [
347
+ "- For a preference, cite only messages with authority=instruction and is_run_actor=true; a preference must be the run actor's own first-person fact."
348
+ ] : [],
349
+ "- For a procedure or knowledge memory, cite run-actor instruction messages, public context messages, or successful tool results.",
310
350
  "- Use user messages and successful tool results as source evidence for storable facts.",
311
351
  "- Use failed tool results only when the failure reveals durable process knowledge, not transient errors.",
312
352
  "- Use assistant messages only as context; do not store the assistant's claims unless supported by user messages or tool results.",
@@ -318,9 +358,18 @@ function sessionExtractionPrompt(request) {
318
358
  "- 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.",
319
359
  "- Set kind=procedure for reusable task/process/runbook instructions.",
320
360
  "- Set kind=knowledge for shared team, project, channel, runbook, or operational facts.",
321
- "- Set kind=preference only for clear durable first-person facts authored by the current requester about their own preference, opinion, habit, identity, or workflow.",
322
- "- 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.",
323
- "- User-authored task instructions are procedures, not preferences, unless they explicitly describe the requester's personal preference or habit.",
361
+ ...allowsPreference ? [
362
+ "- Set kind=preference only for clear durable first-person facts authored by the current actor about their own preference, opinion, habit, identity, or workflow.",
363
+ "- A single task request or ask-for-help is never a durable preference, even when phrased as an ongoing action for this run (for example 'help me capture takeaways as we go'). Do not convert a one-off ask into a 'Prefers ...' memory.",
364
+ "- A durable preference requires explicitly stated, generalizable first-person phrasing such as 'I prefer ...', 'I always ...', or 'I never ...' that describes how the actor wants things done in general, not just for the current task."
365
+ ] : [
366
+ "- This completed run has multiple run actors. Return only conversation-scoped procedure or knowledge memories.",
367
+ "- Do not return personal preferences, opinions, habits, identity facts, or workflow preferences from any actor in this run.",
368
+ "- Do not convert a personal first-person statement into shared knowledge or procedure. Statements like 'I prefer ...', 'I use ...', 'I always ...', or 'I never ...' are not memory evidence in this run.",
369
+ "- Shared team, channel, repository, or operational norms are eligible only when the source states them as collective practice or durable operational fact, not as one individual's preference."
370
+ ],
371
+ "- 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 actor.",
372
+ "- User-authored task instructions are procedures, not preferences, unless they explicitly describe the actor's personal preference or habit.",
324
373
  "- Procedural statements such as 'for X, do Y', 'when X, do Y', and 'to accomplish X, do Y' belong in procedures.",
325
374
  ...CANONICAL_CONTENT_RULES,
326
375
  "- Skip a candidate when existing-memories already cover the same durable fact.",
@@ -417,7 +466,8 @@ function extractedMemoriesFromResponse(response) {
417
466
  const toMemory = (memory) => parseExtractedMemory({
418
467
  content: memory.canonicalFact,
419
468
  expiresAtMs: memory.expiresAtMs,
420
- kind: memory.kind
469
+ kind: memory.kind,
470
+ evidenceMessageIndices: memory.evidenceMessageIndices
421
471
  });
422
472
  return response.memories.map(toMemory);
423
473
  }
@@ -709,21 +759,21 @@ function sourceConversationKey(ctx) {
709
759
  }
710
760
  return `slack:${ctx.source.teamId}:${ctx.source.channelId}:${threadKey}`;
711
761
  }
712
- function requesterScopeKey(ctx) {
713
- const requester = ctx.requester;
714
- if (!requester?.userId) {
762
+ function actorScopeKey(ctx) {
763
+ const actor = ctx.actor;
764
+ if (!actor?.userId) {
715
765
  return void 0;
716
766
  }
717
- if (requester.platform === "slack") {
718
- return `slack:${requester.teamId}:${requester.userId}`;
767
+ if (actor.platform === "slack") {
768
+ return `slack:${actor.teamId}:${actor.userId}`;
719
769
  }
720
- return `local:${requester.userId}`;
770
+ return `local:${actor.userId}`;
721
771
  }
722
772
  function deriveMemoryScope(ctx, scope) {
723
773
  if (scope === "personal") {
724
- const scopeKey2 = requesterScopeKey(ctx);
774
+ const scopeKey2 = actorScopeKey(ctx);
725
775
  if (!scopeKey2) {
726
- throw new Error("Personal memory requires requester context.");
776
+ throw new Error("Personal memory requires actor context.");
727
777
  }
728
778
  return { scope, scopeKey: scopeKey2 };
729
779
  }
@@ -735,9 +785,9 @@ function deriveMemoryScope(ctx, scope) {
735
785
  }
736
786
  function deriveMemorySubject(ctx, scope) {
737
787
  if (scope.scope === "personal") {
738
- const subjectKey2 = requesterScopeKey(ctx);
788
+ const subjectKey2 = actorScopeKey(ctx);
739
789
  if (!subjectKey2) {
740
- throw new Error("User-subject memory requires requester context.");
790
+ throw new Error("User-subject memory requires actor context.");
741
791
  }
742
792
  return { subjectType: "user", subjectKey: subjectKey2 };
743
793
  }
@@ -1780,8 +1830,8 @@ var KNOWN_TOOL_INPUT_ERROR_MESSAGES = /* @__PURE__ */ new Set([
1780
1830
  "Memory id is required.",
1781
1831
  "Memory was not found in the current context.",
1782
1832
  "Memory id prefix is ambiguous.",
1783
- "Personal memory requires requester context.",
1784
- "User-subject memory requires requester context."
1833
+ "Personal memory requires actor context.",
1834
+ "User-subject memory requires actor context."
1785
1835
  ]);
1786
1836
  function throwToolInputError(message) {
1787
1837
  throw new PluginToolInputError(message);
@@ -1798,7 +1848,7 @@ function asToolInputError(error) {
1798
1848
  function memoryRuntimeContext(context) {
1799
1849
  return memoryRuntimeContextSchema.parse({
1800
1850
  ...context.conversationId ? { conversationId: context.conversationId } : {},
1801
- ...context.requester ? { requester: context.requester } : {},
1851
+ ...context.actor ? { actor: context.actor } : {},
1802
1852
  source: context.source
1803
1853
  });
1804
1854
  }
@@ -1988,7 +2038,7 @@ function createInput(context, input, toolCallId) {
1988
2038
  }
1989
2039
  function targetForKind(kind) {
1990
2040
  if (kind === "preference") {
1991
- return "requester";
2041
+ return "actor";
1992
2042
  }
1993
2043
  return "conversation";
1994
2044
  }
@@ -2125,7 +2175,7 @@ function createMemoryListTool(context) {
2125
2175
  }
2126
2176
  function createMemorySearchTool(context) {
2127
2177
  return definePluginTool({
2128
- description: "Search active memories visible in the current context. Use when the model needs targeted memory recall. The tool searches only the current requester and active conversation scopes.",
2178
+ description: "Search active memories visible in the current context. Use when the model needs targeted memory recall. The tool searches only the current actor and active conversation scopes.",
2129
2179
  annotations: { readOnlyHint: true, destructiveHint: false },
2130
2180
  inputSchema: searchMemoriesInputSchema2,
2131
2181
  outputSchema: memoryManyOutputSchema,
@@ -2163,22 +2213,61 @@ var extractedMemoryCacheSchema = z5.array(
2163
2213
  z5.object({
2164
2214
  content: z5.string().min(1),
2165
2215
  expiresAtMs: z5.number().finite().nullable(),
2166
- kind: z5.enum(MEMORY_KINDS)
2216
+ kind: z5.enum(MEMORY_KINDS),
2217
+ evidenceMessageIndices: z5.array(z5.number().int().nonnegative()).min(1).max(10)
2167
2218
  }).strict().transform(parseExtractedMemory)
2168
2219
  );
2169
- function targetForKind2(kind) {
2170
- if (kind === "preference") {
2171
- return "requester";
2220
+ function isRunActorInstruction(entry) {
2221
+ return entry.type === "message" && entry.role === "user" && entry.provenance?.authority === "instruction" && entry.isRunActor === true;
2222
+ }
2223
+ function isConversationEvidence(entry) {
2224
+ if (entry.type === "toolResult") {
2225
+ return entry.isError === false && Boolean(entry.text?.trim());
2172
2226
  }
2173
- return "conversation";
2227
+ if (entry.type === "message" && entry.role === "user" && entry.provenance?.authority === "instruction" && entry.isRunActor === false) {
2228
+ return Boolean(entry.provenance.actor);
2229
+ }
2230
+ return entry.type === "message" && entry.role === "user" && entry.provenance?.authority === "context";
2174
2231
  }
2175
- function memoryIdempotencySuffix(memory) {
2176
- 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);
2232
+ function citedEntries(indices, transcript) {
2233
+ const seen = /* @__PURE__ */ new Set();
2234
+ const entries = [];
2235
+ for (const index2 of indices) {
2236
+ if (seen.has(index2)) {
2237
+ continue;
2238
+ }
2239
+ seen.add(index2);
2240
+ const entry = transcript[index2];
2241
+ if (!entry) {
2242
+ return { valid: false, entries: [] };
2243
+ }
2244
+ entries.push(entry);
2245
+ }
2246
+ return { valid: entries.length > 0, entries };
2177
2247
  }
2178
- function passiveInput(sessionId, memory, sourceKey2) {
2248
+ function routeExtractedMemory(memory, transcript, run) {
2249
+ const cited = citedEntries(memory.evidenceMessageIndices, transcript);
2250
+ if (!cited.valid) {
2251
+ return "drop";
2252
+ }
2253
+ if (memory.kind === "preference") {
2254
+ const exactlyOneRunActor = Boolean(run.actor) && run.actors.length === 1;
2255
+ if (!exactlyOneRunActor) {
2256
+ return "drop";
2257
+ }
2258
+ return cited.entries.every(isRunActorInstruction) ? "personal" : "drop";
2259
+ }
2260
+ return cited.entries.every(
2261
+ (entry) => isRunActorInstruction(entry) || isConversationEvidence(entry)
2262
+ ) ? "conversation" : "drop";
2263
+ }
2264
+ function memoryIdempotencySuffix(memory, target) {
2265
+ return createHash2("sha256").update(target).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);
2266
+ }
2267
+ function passiveInput(sessionId, memory, sourceKey2, target) {
2179
2268
  return {
2180
2269
  content: memory.content,
2181
- idempotencyKey: `session:${sourceKey2}:${sessionId}:${memoryIdempotencySuffix(memory)}`,
2270
+ idempotencyKey: `session:${sourceKey2}:${sessionId}:${memoryIdempotencySuffix(memory, target)}`,
2182
2271
  kind: memory.kind,
2183
2272
  ...memory.expiresAtMs !== null ? { expiresAtMs: memory.expiresAtMs } : {}
2184
2273
  };
@@ -2187,7 +2276,11 @@ async function getTaskMemories(context, extract) {
2187
2276
  const cacheKey = `memory-extraction:${context.id}`;
2188
2277
  const cached = await context.state.get(cacheKey);
2189
2278
  if (cached !== void 0) {
2190
- return extractedMemoryCacheSchema.parse(cached);
2279
+ const parsed = extractedMemoryCacheSchema.safeParse(cached);
2280
+ if (parsed.success) {
2281
+ return parsed.data;
2282
+ }
2283
+ await context.state.delete(cacheKey);
2191
2284
  }
2192
2285
  const memories = await extract();
2193
2286
  if (memories.length > 0) {
@@ -2216,7 +2309,7 @@ async function processMemorySession(context) {
2216
2309
  }
2217
2310
  const runtimeContext = memoryRuntimeContextSchema.parse({
2218
2311
  conversationId: run.conversationId,
2219
- ...run.requester ? { requester: run.requester } : {},
2312
+ ...run.actor ? { actor: run.actor } : {},
2220
2313
  source: run.source
2221
2314
  });
2222
2315
  const agent = createMemoryAgent(context.model);
@@ -2234,6 +2327,7 @@ async function processMemorySession(context) {
2234
2327
  existingMemories: existingMemories.map((memory) => ({
2235
2328
  content: memory.content
2236
2329
  })),
2330
+ actors: run.actors,
2237
2331
  transcript,
2238
2332
  runtimeContext
2239
2333
  });
@@ -2242,12 +2336,13 @@ async function processMemorySession(context) {
2242
2336
  return;
2243
2337
  }
2244
2338
  for (const memory of memories) {
2245
- const input = passiveInput(run.runId, memory, sourceKey2);
2246
- if (targetForKind2(memory.kind) === "conversation") {
2247
- await store.createConversationMemory(input);
2339
+ const target = routeExtractedMemory(memory, transcript, run);
2340
+ if (target === "drop") {
2248
2341
  continue;
2249
2342
  }
2250
- if (!run.requester) {
2343
+ const input = passiveInput(run.runId, memory, sourceKey2, target);
2344
+ if (target === "conversation") {
2345
+ await store.createConversationMemory(input);
2251
2346
  continue;
2252
2347
  }
2253
2348
  await store.createMemory(input);
@@ -2298,17 +2393,13 @@ async function createMemoryPromptMessages(context) {
2298
2393
  }
2299
2394
  const runtimeContext = memoryRuntimeContextSchema.parse({
2300
2395
  ...context.conversationId ? { conversationId: context.conversationId } : {},
2301
- ...context.requester ? { requester: context.requester } : {},
2396
+ ...context.actor ? { actor: context.actor } : {},
2302
2397
  source: context.source
2303
2398
  });
2304
- const memories = await createMemoryStore(
2305
- context.db,
2306
- runtimeContext,
2307
- {
2308
- ...context.embedder ? { embedder: context.embedder } : {},
2309
- ...context.maxVectorDistance !== void 0 ? { maxVectorDistance: context.maxVectorDistance } : {}
2310
- }
2311
- ).searchMemories({
2399
+ const memories = await createMemoryStore(context.db, runtimeContext, {
2400
+ ...context.embedder ? { embedder: context.embedder } : {},
2401
+ ...context.maxVectorDistance !== void 0 ? { maxVectorDistance: context.maxVectorDistance } : {}
2402
+ }).searchMemories({
2312
2403
  query: context.text,
2313
2404
  limit: DEFAULT_RECALL_LIMIT
2314
2405
  });
@@ -2345,7 +2436,7 @@ function memoryToolContext(ctx) {
2345
2436
  return {
2346
2437
  agent: ctx.agent,
2347
2438
  ...ctx.conversationId ? { conversationId: ctx.conversationId } : {},
2348
- ...ctx.requester ? { requester: ctx.requester } : {},
2439
+ ...ctx.actor ? { actor: ctx.actor } : {},
2349
2440
  db: ctx.db,
2350
2441
  ...ctx.embedder ? { embedder: ctx.embedder } : {},
2351
2442
  source: ctx.source,
@@ -2405,7 +2496,7 @@ function createMemoryPlugin(options = {}) {
2405
2496
  async userPrompt(ctx) {
2406
2497
  return await createMemoryPromptMessages({
2407
2498
  ...ctx.conversationId ? { conversationId: ctx.conversationId } : {},
2408
- ...ctx.requester ? { requester: ctx.requester } : {},
2499
+ ...ctx.actor ? { actor: ctx.actor } : {},
2409
2500
  db: ctx.db,
2410
2501
  embedder: ctx.embedder,
2411
2502
  maxVectorDistance: recallMaxVectorDistance(options),