@stigmer/runner 3.12.7 → 3.12.9

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.
Files changed (79) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/fetch-interceptor.js +29 -16
  3. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  4. package/dist/activities/execute-cursor/index.d.ts +14 -0
  5. package/dist/activities/execute-cursor/index.js +76 -5
  6. package/dist/activities/execute-cursor/index.js.map +1 -1
  7. package/dist/activities/execute-cursor/message-translator.d.ts +15 -9
  8. package/dist/activities/execute-cursor/message-translator.js +15 -9
  9. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  10. package/dist/activities/execute-deep-agent/index.js +9 -0
  11. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  12. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +2 -0
  13. package/dist/activities/execute-deep-agent/mcp-gate.js +2 -1
  14. package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
  15. package/dist/activities/execute-deep-agent/setup.d.ts +10 -1
  16. package/dist/activities/execute-deep-agent/setup.js +45 -3
  17. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  18. package/dist/payload-codecs.d.ts +1 -1
  19. package/dist/payload-codecs.js +6 -4
  20. package/dist/payload-codecs.js.map +1 -1
  21. package/dist/shared/memory-attachment.d.ts +97 -0
  22. package/dist/shared/memory-attachment.js +136 -0
  23. package/dist/shared/memory-attachment.js.map +1 -0
  24. package/dist/shared/memory-retrieval.d.ts +126 -0
  25. package/dist/shared/memory-retrieval.js +293 -0
  26. package/dist/shared/memory-retrieval.js.map +1 -0
  27. package/dist/shared/runner-credential-keys.js +2 -1
  28. package/dist/shared/runner-credential-keys.js.map +1 -1
  29. package/dist/shared/tool-kind.js +9 -0
  30. package/dist/shared/tool-kind.js.map +1 -1
  31. package/package.json +3 -2
  32. package/src/__tests__/history-encryption-e2e.test.ts +1 -1
  33. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -1
  34. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +49 -15
  35. package/src/activities/execute-cursor/fetch-interceptor.ts +37 -16
  36. package/src/activities/execute-cursor/index.ts +86 -5
  37. package/src/activities/execute-cursor/message-translator.ts +15 -9
  38. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +7 -0
  39. package/src/activities/execute-deep-agent/index.ts +10 -0
  40. package/src/activities/execute-deep-agent/mcp-gate.ts +4 -1
  41. package/src/activities/execute-deep-agent/setup.ts +70 -6
  42. package/src/payload-codecs.ts +12 -9
  43. package/src/shared/__tests__/memory-attachment.test.ts +167 -0
  44. package/src/shared/__tests__/memory-retrieval.test.ts +310 -0
  45. package/src/shared/memory-attachment.ts +167 -0
  46. package/src/shared/memory-retrieval.ts +383 -0
  47. package/src/shared/runner-credential-keys.ts +2 -1
  48. package/src/shared/tool-kind.ts +10 -0
  49. package/dist/claimcheck/compressor.d.ts +0 -2
  50. package/dist/claimcheck/compressor.js +0 -8
  51. package/dist/claimcheck/compressor.js.map +0 -1
  52. package/dist/claimcheck/config.d.ts +0 -7
  53. package/dist/claimcheck/config.js +0 -10
  54. package/dist/claimcheck/config.js.map +0 -1
  55. package/dist/claimcheck/index.d.ts +0 -3
  56. package/dist/claimcheck/index.js +0 -4
  57. package/dist/claimcheck/index.js.map +0 -1
  58. package/dist/claimcheck/payload-codec.d.ts +0 -23
  59. package/dist/claimcheck/payload-codec.js +0 -105
  60. package/dist/claimcheck/payload-codec.js.map +0 -1
  61. package/dist/encryption/config.d.ts +0 -64
  62. package/dist/encryption/config.js +0 -113
  63. package/dist/encryption/config.js.map +0 -1
  64. package/dist/encryption/index.d.ts +0 -3
  65. package/dist/encryption/index.js +0 -3
  66. package/dist/encryption/index.js.map +0 -1
  67. package/dist/encryption/payload-codec.d.ts +0 -41
  68. package/dist/encryption/payload-codec.js +0 -130
  69. package/dist/encryption/payload-codec.js.map +0 -1
  70. package/src/__tests__/claimcheck-codec.test.ts +0 -256
  71. package/src/__tests__/encryption-codec.test.ts +0 -287
  72. package/src/__tests__/fixtures/encrypted-payload-fixture.json +0 -15
  73. package/src/claimcheck/compressor.ts +0 -9
  74. package/src/claimcheck/config.ts +0 -20
  75. package/src/claimcheck/index.ts +0 -3
  76. package/src/claimcheck/payload-codec.ts +0 -139
  77. package/src/encryption/config.ts +0 -161
  78. package/src/encryption/index.ts +0 -3
  79. package/src/encryption/payload-codec.ts +0 -152
@@ -19,15 +19,34 @@ import { AsyncLocalStorage } from "node:async_hooks";
19
19
 
20
20
  import { TimingRecorder, emitTimingLog } from "../../shared/cold-start-timing.js";
21
21
 
22
+ /** One REST path's timing identity: the emitted timeline event and its
23
+ * single segment name. */
24
+ interface TimedRestPath {
25
+ readonly event: string;
26
+ readonly segment: string;
27
+ }
28
+
22
29
  /**
23
- * The one REST path worth timing individually: the Cursor SDK calls
24
- * GET /v1/models inside Agent.create/Agent.resume purely to validate the
25
- * model id, and in proxy mode that is a runner → Stigmer → Cursor double
26
- * hop sitting inside the resolve_agent setup segment (issue #209). The
27
- * emitted `cursor_models_fetch` timeline splits that network cost out of
28
- * the segment total without touching the SDK.
30
+ * REST paths worth timing individually both sit inside Agent.create/
31
+ * Agent.resume on the user-visible resolve_agent setup path, and in proxy
32
+ * mode each is a runner → Stigmer → Cursor double hop. The emitted
33
+ * timelines split that network cost out of the segment total without
34
+ * touching the SDK:
35
+ *
36
+ * - GET /v1/models (`cursor_models_fetch`): the SDK's model-id validation
37
+ * read, ~0.95s of the pre-cache resolve_agent segment (issue #209).
38
+ * - POST /auth/exchange_user_api_key (`cursor_token_exchange`): the SDK's
39
+ * API-key → access-token exchange, the strongest suspect for the
40
+ * remaining unexplained 0.6–1.3s inside Agent.create
41
+ * (stigmer-cloud#484 — this timeline is that issue's Step 1, measure).
29
42
  */
30
- const MODELS_PATH = "/v1/models";
43
+ const TIMED_REST_PATHS: ReadonlyMap<string, TimedRestPath> = new Map([
44
+ ["/v1/models", { event: "cursor_models_fetch", segment: "models_fetch" }],
45
+ [
46
+ "/auth/exchange_user_api_key",
47
+ { event: "cursor_token_exchange", segment: "token_exchange" },
48
+ ],
49
+ ]);
31
50
 
32
51
  const CURSOR_DOMAINS = [
33
52
  "api2.cursor.sh",
@@ -258,13 +277,14 @@ async function fetchWithUrlRewrite(
258
277
  const rewrittenUrl = rewriteUrl(url, config.proxyEndpoint);
259
278
  const rewrittenInit = replaceAuth(init, config);
260
279
  const path = extractPath(url);
261
- const modelsTiming = path === MODELS_PATH ? new TimingRecorder() : undefined;
280
+ const timedPath = TIMED_REST_PATHS.get(path);
281
+ const timing = timedPath ? new TimingRecorder() : undefined;
262
282
 
263
283
  try {
264
284
  const response = await originalFetch(rewrittenUrl, rewrittenInit);
265
285
 
266
- if (modelsTiming) {
267
- emitModelsFetchTiming(modelsTiming, config, response.status);
286
+ if (timedPath && timing) {
287
+ emitRestTiming(timedPath, timing, config, response.status);
268
288
  }
269
289
 
270
290
  if (!response.ok) {
@@ -290,22 +310,23 @@ async function fetchWithUrlRewrite(
290
310
  }
291
311
 
292
312
  /**
293
- * Emit the `cursor_models_fetch` timeline for one proxied GET /v1/models.
313
+ * Emit the timeline for one proxied REST call on a timed path.
294
314
  *
295
315
  * `execution_id` comes from the AsyncLocalStorage execution context — the
296
316
  * whole ExecuteCursor activity runs inside runWithExecutionContext, so the
297
317
  * value is correct even with concurrent activities on one runner process.
298
- * `recordTimingMetric` deliberately ignores this event (no mapped OTel
299
- * instrument): it is a forensic stdout line only, joined to the
318
+ * `recordTimingMetric` deliberately ignores these events (no mapped OTel
319
+ * instruments): they are forensic stdout lines only, joined to the
300
320
  * execution_setup timeline by execution_id in cold-start-baseline analysis.
301
321
  */
302
- function emitModelsFetchTiming(
322
+ function emitRestTiming(
323
+ timedPath: TimedRestPath,
303
324
  timing: TimingRecorder,
304
325
  config: ProxyConfig,
305
326
  httpStatus: number,
306
327
  ): void {
307
- timing.mark("models_fetch");
308
- emitTimingLog("cursor_models_fetch", {
328
+ timing.mark(timedPath.segment);
329
+ emitTimingLog(timedPath.event, {
309
330
  execution_id: executionContext.getStore()?.executionId ?? config.executionId,
310
331
  http_status: httpStatus,
311
332
  }, timing);
@@ -57,7 +57,8 @@ import {
57
57
  } from "../../shared/caller-identity.js";
58
58
  import { readSessionContext } from "../../shared/session-context.js";
59
59
  import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
60
- import { readRecalledMemories } from "../../shared/recalled-memories.js";
60
+ import type { RecalledMemoriesContent } from "../../shared/recalled-memories.js";
61
+ import { selectRecalledFacts } from "../../shared/memory-retrieval.js";
61
62
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
62
63
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
63
64
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
@@ -84,6 +85,7 @@ import {
84
85
  readChannelConversationId,
85
86
  synthesizeConversationAttachment,
86
87
  } from "../../shared/conversation-attachment.js";
88
+ import { synthesizeMemoryAttachment } from "../../shared/memory-attachment.js";
87
89
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
88
90
  import { mergeApprovalPolicies } from "./approval-policy.js";
89
91
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
@@ -717,6 +719,33 @@ async function executeCursorInner(
717
719
  resolvedMcpServers, conversationAttachment, "conversation participation",
718
720
  );
719
721
  }
722
+
723
+ // Phase 4a5: Synthesize the memory capture attachment (DD-005 D1) —
724
+ // the fourth sibling. The recall snapshot's enabled bit IS the
725
+ // attachment decision (server-stamped at execution create; a free
726
+ // local read, like the conversation attachment's session label). The
727
+ // capture context is attribution the server verifies or trusts per
728
+ // edition (Stage 3 provenance decision); the subject is never
729
+ // threaded — it derives from the credential.
730
+ const memoryAttachment = synthesizeMemoryAttachment(
731
+ execution.spec?.recalledMemories,
732
+ {
733
+ org: session.metadata?.org ?? "",
734
+ agentId: blueprint.agent.metadata?.id ?? "",
735
+ sessionId,
736
+ agentExecutionId: executionId,
737
+ },
738
+ {
739
+ bridgeEndpoint: config.mcpBridgeEndpoint,
740
+ credential: attachmentCredential,
741
+ backendEndpoint: config.stigmerBackendEndpoint,
742
+ },
743
+ );
744
+ if (memoryAttachment) {
745
+ resolvedMcpServers = injectSynthesizedAttachment(
746
+ resolvedMcpServers, memoryAttachment, "memory capture",
747
+ );
748
+ }
720
749
  // The one projection point: every mutation above is now visible in the
721
750
  // Cursor SDK config by construction (no per-mutation rebuild to forget).
722
751
  const mcpConfig = toCursorMcpConfig(resolvedMcpServers);
@@ -1154,6 +1183,38 @@ async function executeCursorInner(
1154
1183
  const structuredOutputSchema = spec.executionConfig?.structuredOutputSchema as
1155
1184
  Record<string, unknown> | undefined;
1156
1185
 
1186
+ // Phase 9c: Semantic memory selection (DD-008), memoized to at most one
1187
+ // run per invocation. Deliberately NOT decided by the Phase-10
1188
+ // resolution alone: a resumed-agent primary send carries no memories,
1189
+ // but a mid-send poisoned-handle failure rebuilds on a FRESH agent
1190
+ // whose recovery prompt does — the buildFromPlan drop-hazard class —
1191
+ // so every memory-carrying build site awaits this lazily instead.
1192
+ // Selection runs against the frozen first message's semantics: above
1193
+ // the activation threshold it picks top-k for spec.message, otherwise
1194
+ // (and on any failure) it injects the full candidate set — Phase 2
1195
+ // behavior. The outcome report is stamped on the turn's status ONCE,
1196
+ // picked up by the next persist; a re-invocation replays the report
1197
+ // already persisted on the execution rather than re-selecting (the
1198
+ // written-once rule).
1199
+ let memorySelection: Promise<RecalledMemoriesContent | undefined> | undefined;
1200
+ const selectMemoriesOnce = (): Promise<RecalledMemoriesContent | undefined> => {
1201
+ memorySelection ??= selectRecalledFacts(spec.recalledMemories, spec.message, {
1202
+ proxyEndpoint: config.proxyEndpoint,
1203
+ stigmerToken: config.stigmerToken,
1204
+ executionId,
1205
+ priorReport: execution.status?.recalledMemoriesReport,
1206
+ }).then((selection) => {
1207
+ if (selection.report !== undefined) {
1208
+ status.recalledMemoriesReport = selection.report;
1209
+ }
1210
+ return selection.content;
1211
+ });
1212
+ return memorySelection;
1213
+ };
1214
+ const recalledMemories = promptCarriesStandingContext(resolution.reason)
1215
+ ? await selectMemoriesOnce()
1216
+ : undefined;
1217
+
1157
1218
  // Phase 10: Build the prompt
1158
1219
  const interactionMode = spec.executionConfig?.interactionMode
1159
1220
  ?? InteractionMode.UNSPECIFIED;
@@ -1180,7 +1241,7 @@ async function executeCursorInner(
1180
1241
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1181
1242
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1182
1243
  declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1183
- recalledMemories: readRecalledMemories(spec.recalledMemories),
1244
+ recalledMemories,
1184
1245
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1185
1246
  // The turn's recorded transcript, seeded from the persisted execution
1186
1247
  // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
@@ -1888,7 +1949,10 @@ async function executeCursorInner(
1888
1949
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1889
1950
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1890
1951
  declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1891
- recalledMemories: readRecalledMemories(spec.recalledMemories),
1952
+ // Lazily selected: the primary send may have been a resumed-agent
1953
+ // shape that carried no memories, but this fresh agent's prompt
1954
+ // must (see the Phase 9c memoized selection).
1955
+ recalledMemories: await selectMemoriesOnce(),
1892
1956
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1893
1957
  // Composed fresh (not reused from Phase 10): the failed primary
1894
1958
  // stream may have appended partial work onto status.messages,
@@ -2578,6 +2642,23 @@ export function primarySendCarriesImages(
2578
2642
  return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
2579
2643
  }
2580
2644
 
2645
+ /**
2646
+ * Whether a prompt built for this resolution carries the STANDING context —
2647
+ * instructions, skills, declared preferences, recalled memories, session
2648
+ * context. Exactly one resolution shape does not: a successfully RESUMED
2649
+ * agent, whose native conversation already holds the first message's
2650
+ * context (both its prompt shapes — the raw follow-up and the
2651
+ * decisions-only HITL reinvocation — send no standing sections).
2652
+ *
2653
+ * The named authority for that routing property (the
2654
+ * primarySendCarriesImages idiom): buildPrompt's internal routing and the
2655
+ * activity's standing-context preparation (e.g. the memory selection gate)
2656
+ * both consult THIS predicate, so the two can never drift.
2657
+ */
2658
+ export function promptCarriesStandingContext(reason: AgentResolutionReason): boolean {
2659
+ return reason !== "resumed_successfully";
2660
+ }
2661
+
2581
2662
  /**
2582
2663
  * Append the structured-output contract to a prompt when the execution
2583
2664
  * requests one. A per-turn directive (the buildFromPlan rule): it must ride
@@ -2619,7 +2700,7 @@ export function buildPrompt(input: BuildPromptInput): string {
2619
2700
  // an empty conversation strand the agent with instructions and no story,
2620
2701
  // and the session inherits that amnesia permanently (issue #366).
2621
2702
  if (isHitlReinvocation(approvalDecisions)) {
2622
- if (resolution.reason !== "resumed_successfully") {
2703
+ if (promptCarriesStandingContext(resolution.reason)) {
2623
2704
  return buildHitlRecoveryPrompt(
2624
2705
  {
2625
2706
  instructions,
@@ -2670,7 +2751,7 @@ export function buildPrompt(input: BuildPromptInput): string {
2670
2751
  // cloud DD-006). Catchup last: it is context, and context sits closest to
2671
2752
  // the task (the enhanced prompt's own ordering doctrine); the input files
2672
2753
  // precede it because they are this turn's payload, not background.
2673
- if (resolution.reason === "resumed_successfully") {
2754
+ if (!promptCarriesStandingContext(resolution.reason)) {
2674
2755
  const prefixes = [
2675
2756
  formatInteractionModePrefix(interactionMode),
2676
2757
  formatImplementPlanSection(buildFromPlan, attachments),
@@ -501,15 +501,21 @@ function blockText(b: Record<string, unknown>): string | undefined {
501
501
  *
502
502
  * The Cursor SDK returns sub-agent work as a blob in the task tool's
503
503
  * completed event (not as streaming events with a distinct agent_id).
504
- * Re-verified 2026-07-02 with live recordings on both the pinned SDK (1.0.13)
505
- * and the latest (1.0.22): zero events reach the parent's run.stream() between
506
- * the task tool's "running" and "completed" events, every event carries the
507
- * parent's agent_id, and the child agentId visible in the task args at spawn
508
- * is NOT queryable mid-run through any public read surface (Agent.listRuns /
509
- * Agent.messages.list / Agent.getRun all return not-found for it; the SDK's
510
- * on-disk sub-agent transcript is written only at completion). Live nested
511
- * visibility is therefore an upstream SDK limitation — do not try to fake it
512
- * here; the UI shows an elapsed-time affordance instead (SubAgentSection).
504
+ * Re-verified 2026-08-23 on the pinned SDK (1.0.13) AND the latest (1.0.28),
505
+ * this time including the write side (stigmer/stigmer#839): an injected
506
+ * platform.eventStore/eventNotifier receives parent events only on 1.0.13 and
507
+ * is bypassed entirely on 1.0.28 (persistence moved into the executor daemon's
508
+ * own state dir); zero events reach the parent's run.stream() mid-task; the
509
+ * agentId in the task args is not a platform-store agent id at all, so it can
510
+ * never be addressable via Agent.listRuns / Agent.getRun (this is WHY the
511
+ * 2026-07-02 read-side polls all returned not-found). On 1.0.28 the child
512
+ * transcript lands at a deterministic path knowable at spawn
513
+ * (~/.cursor/projects/<slug>/agent-transcripts/<parentId>/subagents/<argsAgentId>.jsonl)
514
+ * but is flushed only at completion — verified with 200ms sampling across a
515
+ * full sub-agent run. Live nested visibility is therefore still an upstream
516
+ * SDK limitation — do not try to fake it here; the UI shows an elapsed-time
517
+ * affordance instead (SubAgentSection). Probe scripts and recordings:
518
+ * stigmer-cloud _projects/2026-08/20260823.03.cursor-subagent-live-progress.
513
519
  * The result shape is:
514
520
  *
515
521
  * { status: "success", value: { conversationSteps: ConversationStep[] } }
@@ -14,6 +14,7 @@ const nothing = {
14
14
  mcpServerUsageCount: 0,
15
15
  channelMessagingCount: 0,
16
16
  conversationChannelId: undefined,
17
+ memoryCaptureEnabled: false,
17
18
  };
18
19
 
19
20
  describe("shouldConnectMcp", () => {
@@ -34,4 +35,10 @@ describe("shouldConnectMcp", () => {
34
35
  // channel — the escalation tool is the ONLY source.
35
36
  expect(shouldConnectMcp({ ...nothing, conversationChannelId: "agch_1" })).toBe(true);
36
37
  });
38
+
39
+ it("enters on memory capture alone (the memory attachment)", () => {
40
+ // A tool-less agent whose user has memory on: the remember tool is
41
+ // the ONLY source, and skipping the block would silently drop it.
42
+ expect(shouldConnectMcp({ ...nothing, memoryCaptureEnabled: true })).toBe(true);
43
+ });
37
44
  });
@@ -180,6 +180,16 @@ export function createDeepAgentActivities(config: Config) {
180
180
  ? seedStatusFromExecution(setup.execution)
181
181
  : create(AgentExecutionStatusSchema, {});
182
182
 
183
+ // The semantic retriever's injection outcome (DD-008 D5), computed at
184
+ // prompt build in setup Step 8. Stamped here — before the first
185
+ // persist — because setup predates this status object; the server's
186
+ // presence-guarded merge preserves it across report-less writes. On a
187
+ // re-invocation the seeded status already carries the (replayed)
188
+ // report, so this stamp is idempotent by construction.
189
+ if (setup.recalledMemoriesReport !== undefined) {
190
+ initialStatus.recalledMemoriesReport = setup.recalledMemoriesReport;
191
+ }
192
+
183
193
  const statusBuilder = new StatusBuilder(executionId, initialStatus);
184
194
 
185
195
  statusBuilder.setApprovalProvider({
@@ -22,6 +22,8 @@ export interface McpToolSources {
22
22
  /** The serving channel id when this session IS a live channel
23
23
  * conversation (the conversation attachment, DD-008). */
24
24
  readonly conversationChannelId: string | undefined;
25
+ /** The recall snapshot's enabled bit (the memory attachment, DD-005 D1). */
26
+ readonly memoryCaptureEnabled: boolean;
25
27
  }
26
28
 
27
29
  /** True when any tool source demands MCP resolution and connect. */
@@ -29,6 +31,7 @@ export function shouldConnectMcp(sources: McpToolSources): boolean {
29
31
  return (
30
32
  sources.mcpServerUsageCount > 0 ||
31
33
  sources.channelMessagingCount > 0 ||
32
- sources.conversationChannelId !== undefined
34
+ sources.conversationChannelId !== undefined ||
35
+ sources.memoryCaptureEnabled
33
36
  );
34
37
  }
@@ -15,7 +15,7 @@ import { z } from "zod";
15
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
16
  type AgentGraph = any;
17
17
 
18
- import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
18
+ import type { AgentExecution, RecalledMemoriesReport } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
19
19
  import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
20
20
  import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
21
21
  import type { DynamicStructuredTool } from "@langchain/core/tools";
@@ -33,7 +33,7 @@ import {
33
33
  } from "../../shared/caller-identity.js";
34
34
  import { readSessionContext } from "../../shared/session-context.js";
35
35
  import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
36
- import { readRecalledMemories } from "../../shared/recalled-memories.js";
36
+ import { selectRecalledFacts } from "../../shared/memory-retrieval.js";
37
37
  import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
38
38
  import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
39
39
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
@@ -47,6 +47,10 @@ import {
47
47
  readChannelConversationId,
48
48
  synthesizeConversationAttachment,
49
49
  } from "../../shared/conversation-attachment.js";
50
+ import {
51
+ memoryCaptureEnabled,
52
+ synthesizeMemoryAttachment,
53
+ } from "../../shared/memory-attachment.js";
50
54
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
51
55
  import { shouldConnectMcp } from "./mcp-gate.js";
52
56
  import { WorkspaceProvisioner } from "../../shared/workspace/provisioner.js";
@@ -197,6 +201,15 @@ export interface SetupResult {
197
201
  * baseline/candidate/reconcile seam (`capture.ts`) and the CAS capture-class.
198
202
  */
199
203
  readonly gitWorkspace: boolean;
204
+ /**
205
+ * The semantic retriever's injection outcome (DD-008 D5), produced at
206
+ * prompt build (Step 8) — the one place selection runs. The activity
207
+ * stamps it onto the turn's status proto before the first persist; the
208
+ * server's presence-guarded merge preserves it across report-less
209
+ * writes. Undefined when nothing was injected (recall absent, disabled,
210
+ * or empty) — absent report = wholesale, true by construction.
211
+ */
212
+ readonly recalledMemoriesReport: RecalledMemoriesReport | undefined;
200
213
  }
201
214
 
202
215
  export interface SetupDependencies {
@@ -390,11 +403,17 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
390
403
  // free, synchronous read, so no hoisted discovery needed.
391
404
  const conversationChannelId = readChannelConversationId(session.metadata?.labels);
392
405
 
406
+ // The memory-attachment decision (DD-005 D1): the recall snapshot's
407
+ // enabled bit, stamped server-side at execution create — like the
408
+ // conversation attachment, a free, synchronous spec read.
409
+ const captureMemories = memoryCaptureEnabled(execution.spec?.recalledMemories);
410
+
393
411
  let resolvedMcpServers: Awaited<ReturnType<typeof resolveMcpServers>> | null = null;
394
412
  if (shouldConnectMcp({
395
413
  mcpServerUsageCount: mcpServerUsages.length,
396
414
  channelMessagingCount: channelMessaging.length,
397
415
  conversationChannelId,
416
+ memoryCaptureEnabled: captureMemories,
398
417
  })) {
399
418
  await reportSetupProgress(client, executionId, "Connecting tools…");
400
419
  const transportPosture = resolveMcpTransportPosture(config.mode);
@@ -456,6 +475,32 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
456
475
  backfilledServers, conversationAttachment, "conversation participation",
457
476
  );
458
477
  }
478
+
479
+ // The memory capture attachment (DD-005 D1) — same after-backfill
480
+ // rule. The capture context is attribution the server verifies or
481
+ // trusts per edition (Stage 3 provenance decision); the subject is
482
+ // never threaded — it derives from the credential.
483
+ if (captureMemories) {
484
+ const memoryAttachment = synthesizeMemoryAttachment(
485
+ execution.spec?.recalledMemories,
486
+ {
487
+ org: session.metadata?.org ?? "",
488
+ agentId: agent.metadata?.id ?? "",
489
+ sessionId,
490
+ agentExecutionId: executionId,
491
+ },
492
+ {
493
+ bridgeEndpoint: config.mcpBridgeEndpoint,
494
+ credential: attachmentCredential,
495
+ backendEndpoint: config.stigmerBackendEndpoint,
496
+ },
497
+ );
498
+ if (memoryAttachment) {
499
+ backfilledServers = injectSynthesizedAttachment(
500
+ backfilledServers, memoryAttachment, "memory capture",
501
+ );
502
+ }
503
+ }
459
504
  resolvedMcpServers = { resolvedServers: backfilledServers };
460
505
  timing.mark("backfill_mcp");
461
506
 
@@ -554,7 +599,27 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
554
599
  }
555
600
  timing.mark("inject_attachments");
556
601
 
557
- // Step 8: Build enhanced system prompt
602
+ // Step 8: Build enhanced system prompt.
603
+ //
604
+ // Recalled memories go through the semantic retriever (DD-008), not the
605
+ // raw snapshot read: above the activation threshold it selects the
606
+ // top-k facts for THIS turn's message (one batched embeddings call);
607
+ // otherwise — and on any failure — it injects the full candidate set,
608
+ // exactly the Phase 2 behavior. A re-invocation of this execution
609
+ // (approval resume) replays the outcome recorded on the execution's
610
+ // status instead of re-selecting, so the prompt never shifts
611
+ // mid-execution. The report returned here is stamped onto the turn's
612
+ // status by the activity (index.ts) — setup has no status object yet.
613
+ const memorySelection = await selectRecalledFacts(
614
+ execution.spec!.recalledMemories,
615
+ execution.spec!.message,
616
+ {
617
+ proxyEndpoint: config.proxyEndpoint,
618
+ stigmerToken: config.stigmerToken,
619
+ executionId,
620
+ priorReport: execution.status?.recalledMemoriesReport,
621
+ },
622
+ );
558
623
  const systemPrompt = buildEnhancedSystemPrompt({
559
624
  instructions,
560
625
  provisionResults,
@@ -578,9 +643,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
578
643
  declaredPreferences: readDeclaredPreferences(
579
644
  execution.spec!.declaredPreferences,
580
645
  ),
581
- recalledMemories: readRecalledMemories(
582
- execution.spec!.recalledMemories,
583
- ),
646
+ recalledMemories: memorySelection.content,
584
647
  });
585
648
 
586
649
  // Step 9: Construct the LLM model. Resolution to the provider API id
@@ -928,6 +991,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
928
991
  casObserver,
929
992
  captureMode,
930
993
  gitWorkspace,
994
+ recalledMemoriesReport: memorySelection.report,
931
995
  };
932
996
  } catch (err) {
933
997
  if (mcpConnection) {
@@ -12,9 +12,9 @@
12
12
  * decode: claim-check → decrypt (restore the blob, then decrypt)
13
13
  */
14
14
 
15
+ import type { BootstrapKeyMaterial } from "@stigmer/temporal-codecs";
15
16
  import type { PayloadCodec } from "@temporalio/common";
16
17
  import type { Config } from "./config.js";
17
- import type { BootstrapKeyMaterial } from "./encryption/config.js";
18
18
  import { getRunnerSecret } from "./shared/runner-credential-store.js";
19
19
 
20
20
  export async function createPayloadCodecs(
@@ -23,12 +23,18 @@ export async function createPayloadCodecs(
23
23
  ): Promise<PayloadCodec[] | undefined> {
24
24
  const codecs: PayloadCodec[] = [];
25
25
 
26
- const { loadPayloadEncryptionConfig, EncryptionPayloadCodec } = await import(
27
- "./encryption/index.js"
28
- );
26
+ const {
27
+ loadPayloadEncryptionConfig,
28
+ EncryptionPayloadCodec,
29
+ loadClaimcheckConfig,
30
+ ClaimcheckPayloadCodec,
31
+ } = await import("@stigmer/temporal-codecs");
29
32
  // Env-configured keys win outright; server-managed (bootstrap) keys apply
30
- // only when the env is silent — see encryption/config.ts for the rationale.
31
- const encryptionConfig = loadPayloadEncryptionConfig(bootstrapKeys);
33
+ // only when the env is silent — see the lib's encryption/config.ts for the
34
+ // rationale. Key VALUES resolve through getRunnerSecret (the #508 boot
35
+ // capture moves them out of process.env — agent shells must not read
36
+ // them), which is why the loader takes the reader as an argument.
37
+ const encryptionConfig = loadPayloadEncryptionConfig(getRunnerSecret, bootstrapKeys);
32
38
  if (encryptionConfig) {
33
39
  codecs.push(new EncryptionPayloadCodec(encryptionConfig));
34
40
  const source = getRunnerSecret("STIGMER_PAYLOAD_ENCRYPTION_KEY") ? "env" : "bootstrap";
@@ -41,9 +47,6 @@ export async function createPayloadCodecs(
41
47
  );
42
48
  }
43
49
 
44
- const { loadClaimcheckConfig, ClaimcheckPayloadCodec } = await import(
45
- "./claimcheck/index.js"
46
- );
47
50
  const claimcheckConfig = loadClaimcheckConfig();
48
51
  if (claimcheckConfig.enabled) {
49
52
  const { loadArtifactStorageConfig, createArtifactStorage } = await import(