@sema-agent/core 5.45.0 → 5.47.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/agents/subagent.js +130 -3
  3. package/dist/core/checkpoint-store.d.ts +12 -0
  4. package/dist/core/governance-codes.d.ts +13 -0
  5. package/dist/core/governance-codes.js +33 -0
  6. package/dist/core/hooks.d.ts +9 -2
  7. package/dist/core/hooks.js +6 -5
  8. package/dist/core/memory-engine/content-origin.d.ts +3 -1
  9. package/dist/core/memory-engine/delegation-settlement.d.ts +318 -0
  10. package/dist/core/memory-engine/delegation-settlement.js +661 -0
  11. package/dist/core/memory-engine/engine.d.ts +209 -4
  12. package/dist/core/memory-engine/engine.js +885 -39
  13. package/dist/core/memory-engine/export-bundle.d.ts +10 -1
  14. package/dist/core/memory-engine/export-bundle.js +21 -0
  15. package/dist/core/memory-engine/file-backend.d.ts +34 -4
  16. package/dist/core/memory-engine/file-backend.js +168 -40
  17. package/dist/core/memory-engine/frontmatter.d.ts +69 -1
  18. package/dist/core/memory-engine/frontmatter.js +156 -2
  19. package/dist/core/memory-engine/header-hints.d.ts +17 -0
  20. package/dist/core/memory-engine/header-hints.js +6 -0
  21. package/dist/core/memory-engine/index.d.ts +7 -5
  22. package/dist/core/memory-engine/index.js +5 -3
  23. package/dist/core/memory-engine/layout.d.ts +39 -2
  24. package/dist/core/memory-engine/layout.js +27 -14
  25. package/dist/core/memory-engine/memory-backend-contract.js +108 -0
  26. package/dist/core/memory-engine/origin-clearance.d.ts +66 -0
  27. package/dist/core/memory-engine/origin-clearance.js +84 -0
  28. package/dist/core/memory-engine/provenance-wording.d.ts +50 -0
  29. package/dist/core/memory-engine/provenance-wording.js +15 -0
  30. package/dist/core/memory-engine/sync-client.d.ts +1 -1
  31. package/dist/core/memory-engine/sync-client.js +33 -1
  32. package/dist/core/memory-engine/tools.d.ts +64 -3
  33. package/dist/core/memory-engine/tools.js +37 -9
  34. package/dist/core/memory-engine/types.d.ts +145 -3
  35. package/dist/core/memory-engine/types.js +1 -1
  36. package/dist/core/reminder-mint.d.ts +70 -0
  37. package/dist/core/reminder-mint.js +25 -0
  38. package/dist/core/runner/git-status-frame.d.ts +3 -14
  39. package/dist/core/runner/git-status-frame.js +39 -14
  40. package/dist/core/runner/prepare-config-doors.d.ts +4 -0
  41. package/dist/core/runner/prepare-config-doors.js +15 -0
  42. package/dist/core/runner/prepare-hands-readface.d.ts +5 -11
  43. package/dist/core/runner/prepare-hands-readface.js +26 -0
  44. package/dist/core/runner/prepare-memory.d.ts +11 -0
  45. package/dist/core/runner/prepare-memory.js +61 -24
  46. package/dist/core/runner/prepare-task.d.ts +46 -1
  47. package/dist/core/runner/prepare-task.js +128 -23
  48. package/dist/core/runner/runtask.js +62 -55
  49. package/dist/core/session-reconcile.js +3 -2
  50. package/dist/core/side-query.d.ts +11 -1
  51. package/dist/core/side-query.js +3 -0
  52. package/dist/core/types.d.ts +85 -10
  53. package/dist/core/types.js +3 -0
  54. package/dist/engine/harness/types.d.ts +46 -1
  55. package/dist/engine/harness/types.js +11 -0
  56. package/dist/engine/session/import-validate.js +6 -1
  57. package/dist/engine/session/session.d.ts +20 -0
  58. package/dist/engine/session/session.js +26 -1
  59. package/dist/index.d.ts +3 -1
  60. package/dist/index.js +3 -1
  61. package/dist/orchestration/run-workflow-tool.d.ts +16 -0
  62. package/dist/orchestration/run-workflow-tool.js +23 -3
  63. package/dist/orchestration/workflow-governance.d.ts +8 -1
  64. package/dist/prompt-assembly/epoch.js +2 -0
  65. package/dist/prompt-assembly/types.d.ts +6 -0
  66. package/dist/prompts/default.d.ts +13 -1
  67. package/dist/prompts/default.js +5 -1
  68. package/dist/tools/fs/fs-bash.d.ts +4 -0
  69. package/dist/tools/fs/fs-bash.js +1 -1
  70. package/dist/tools/fs/fs-read.d.ts +1 -1
  71. package/dist/tools/fs/fs-read.js +8 -7
  72. package/dist/tools/fs/fs-shared.d.ts +10 -4
  73. package/dist/tools/fs/fs-shared.js +6 -3
  74. package/dist/tools/fs/gh-rate-limit.d.ts +4 -1
  75. package/dist/tools/fs/gh-rate-limit.js +3 -2
  76. package/dist/tools/fs/index.d.ts +10 -2
  77. package/dist/tools/fs/index.js +2 -1
  78. package/dist/tools/task-list.d.ts +5 -1
  79. package/package.json +1 -1
  80. package/test/export-surface.snapshot.json +21 -2
@@ -98,6 +98,11 @@ export interface PrepareHandsReadFaceInput {
98
98
  /** borrowed-mutable — the classify doctrine's probe installs (`.set`, only where the doctrine
99
99
  * owns the seat). Same-object contract as above. */
100
100
  reversibilityProbes: PrepareSafetyScanResult["reversibilityProbes"];
101
+ /** borrowed-readonly — design/319 (A ticket): the run's reminder provenance mark (minted/adopted
102
+ * by the driver's adoption ladder); threaded into the hands band so the fs-side engine mints
103
+ * (the Read cyber/dedup/offset/empty reminders, the gh rate-limit hint) stamp the same mark the
104
+ * system-prompt declaration names. */
105
+ reminderMark: string;
101
106
  }
102
107
  /** The phase's outputs (相 API 规则件 four-class form) — ALL settled before the return; the driver
103
108
  * binds them as fresh consts (R-5) except the inverted-closure trio and the two shellGated bits,
@@ -173,17 +178,6 @@ export interface PrepareHandsReadFaceResult {
173
178
  /** write-back — the Monitor twin of the bit above. */
174
179
  shellGatedMonitor: boolean;
175
180
  }
176
- /** The hands-LESS resolver arm (#242) — SYNCHRONOUS by contract. The original P10 else-arm ran
177
- * with ZERO awaits, so on a hands-less leg the stretch from the driver's A2A warning callback
178
- * (deployment `onError`, which can queue a microtask) through the P11 SendMessage/delegation
179
- * mounts was uninterruptible. An `async` phase call would open exactly that microtask window (the
180
- * T1 hazard, here on the hands-less leg — a queued `spec.tools` mutation could suppress the
181
- * SendMessage mount or plant a trusted-revive delegation entry). So this leg is its OWN plain sync
182
- * seam the driver calls WITHOUT `await` (dispatching on the `handsEnabled` boolean it already
183
- * owns — NOT on a `Promise` identity check, which a host that replaces `globalThis.Promise` could
184
- * defeat; r3): the leg is byte-for-byte as synchronous as before the extraction.
185
- * (Adversarial review 2026-08-18, verified against the a945be28 baseline: awaits 3981–4243 are all
186
- * inside the hands branch; the else arm reached 4440/4463 with none.) */
187
181
  export declare function resolveHandsLessReadFace(input: PrepareHandsReadFaceInput): PrepareHandsReadFaceResult;
188
182
  /** The hands-MOUNT branch — the async half of the phase (see the module header for the contract).
189
183
  * The driver awaits this ONLY on the hands leg (`handsEnabled === true`); the hands-less leg goes
@@ -12,8 +12,29 @@ let readFaceClampConsoleAnnounced = false;
12
12
  export function __resetReadFaceClampAnnouncement() {
13
13
  readFaceClampConsoleAnnounced = false;
14
14
  }
15
+ function assertStringArraySeat(value, seat, entryLaw) {
16
+ if (value === undefined)
17
+ return;
18
+ if (!Array.isArray(value)) {
19
+ const e = new Error(`${seat} must be an array or absent (got ${value === null ? "null" : typeof value}) — a non-array here would be iterated character by character, so it refuses loudly instead.`);
20
+ e.code = "config.read_face_seat";
21
+ throw e;
22
+ }
23
+ if (entryLaw !== "strings")
24
+ return;
25
+ const entries = value;
26
+ for (const entry of entries) {
27
+ if (typeof entry !== "string") {
28
+ const e = new Error(`${seat} entries must be strings (got ${entry === null ? "null" : typeof entry}).`);
29
+ e.code = "config.read_face_seat";
30
+ throw e;
31
+ }
32
+ }
33
+ }
15
34
  export function resolveHandsLessReadFace(input) {
16
35
  const { resume, fullShellReachable, spec, deps } = input;
36
+ assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
37
+ assertStringArraySeat(spec.readDenyPatterns, "TaskSpec.readDenyPatterns", "compiler-owned");
17
38
  let handsLessResolvedFace;
18
39
  let readDenyAdditionsNormalized = [];
19
40
  {
@@ -56,6 +77,10 @@ export function resolveHandsLessReadFace(input) {
56
77
  }
57
78
  export async function prepareHandsMount(input) {
58
79
  const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
80
+ assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
81
+ assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
82
+ assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
83
+ assertStringArraySeat(spec.readDenyPatterns, "TaskSpec.readDenyPatterns", "compiler-owned");
59
84
  let readFileStateForCheckpoint;
60
85
  let seedContextFiles;
61
86
  let handsCwdRef;
@@ -193,6 +218,7 @@ export async function prepareHandsMount(input) {
193
218
  ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
194
219
  ...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
195
220
  readFace: liveReadFace,
221
+ reminderMark: input.reminderMark,
196
222
  includeShell: handsIncludeShell,
197
223
  readOnly: handsReadOnly,
198
224
  ...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
@@ -21,6 +21,10 @@ export interface PrepareMemoryInput {
21
21
  * its roster (or cannot reach the store); the RB-276 index seed follows the same gate.
22
22
  */
23
23
  writeToolsMounted: boolean;
24
+ /** design/319 (A ticket) — the run's reminder provenance mark: the memory engine's announcement
25
+ * block is an engine-authored `<system-reminder>`, so its open tag must carry the same mark the
26
+ * system-prompt declaration names. Threaded into every `engine.inject` call this phase builds. */
27
+ reminderMark: string;
24
28
  /**
25
29
  * The session-wide persistence verdict: `TaskSpec.memoryPersistenceCapable` (the deployment's own
26
30
  * statement — the only honest channel for a custom memory writer persisting through its closure,
@@ -46,6 +50,13 @@ export interface PrepareMemoryInput {
46
50
  * the memory store" stays a true statement instead of a disclosure the store then contradicts.
47
51
  */
48
52
  memoryPersistenceDeclared?: boolean;
53
+ /**
54
+ * design/336 §13-3 — the memory-provenance master switch, screened at the config doors
55
+ * (`config.memory_provenance`, exact spellings, absent folded to `"carry"`). Every engine this
56
+ * phase constructs runs under it, and the pollution notices/tool wording state the mode-true
57
+ * behavior off it. See {@link RunnerDeps.memoryProvenance}.
58
+ */
59
+ memoryProvenance: "off" | "carry";
49
60
  /**
50
61
  * design/178 ②-1 — whether the `memory_search`/`memory_get` pair PASSED its early mount conjuncts
51
62
  * (exclusion + name occupancy, decided in prepare-task BEFORE this phase). True ⇒ this phase builds
@@ -1,10 +1,10 @@
1
- import { sep } from "node:path";
1
+ import { isAbsolute, sep } from "node:path";
2
2
  import { deliverEngineNotice } from "../types.js";
3
3
  import { admitMemoryScopes } from "../memory-admission.js";
4
4
  import { adoptLegacyRepoDirs, canonicalize, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, isContainedIn, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
5
5
  import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
6
6
  import { normalizeMemorySpec } from "../memory.js";
7
- import { MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_RECALL_DISCIPLINE, MemoryEngine, memoryHarvestQuarantinedNotice, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
7
+ import { MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
8
8
  import { createMemoryEngineTools } from "../memory-engine/tools.js";
9
9
  import { assertScopeContractPlacement, parseScopeKey, resolveProjectId } from "../memory-engine/scope-contract.js";
10
10
  import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
@@ -135,7 +135,7 @@ export async function prepareMemory(input) {
135
135
  const choosePersonalBackend = () => typeof pinned === "string" && pinned ? new FileMemoryEngineBackend(personalMemoryDir, { controlDir: personalControlDir }) : backend;
136
136
  const createPersonalEngine = (personalBackend) => {
137
137
  return {
138
- engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident }),
138
+ engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident, provenance: input.memoryProvenance }),
139
139
  backend: personalBackend,
140
140
  };
141
141
  };
@@ -155,7 +155,7 @@ export async function prepareMemory(input) {
155
155
  : planeScopes(memorySpec.scopes, memorySpec.writeScope)).map((scope) => ({ scope, origin: originOf(scope) }));
156
156
  const pollutedOpts = (engine) => {
157
157
  const rec = engine.sessionPollution(sessionId);
158
- return rec !== undefined ? { polluted: { reason: rec.reason } } : {};
158
+ return rec !== undefined ? { polluted: { reason: rec.reason, ...(rec.cause !== undefined ? { cause: rec.cause } : {}) } } : {};
159
159
  };
160
160
  let writeEngine;
161
161
  let writeHandle;
@@ -194,20 +194,21 @@ export async function prepareMemory(input) {
194
194
  memoryDir,
195
195
  controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
196
196
  onIncident: onEngineIncident,
197
+ provenance: input.memoryProvenance,
197
198
  });
198
199
  const personal = createPersonalEngine(personalBackendChosen);
199
200
  const personalEngine = personal.engine;
200
201
  const p = planes;
201
- const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted });
202
+ const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted, sessionId });
202
203
  materializedResidue.push(...planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null));
203
- const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted });
204
+ const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted, sessionId });
204
205
  materializedResidue.push(...planeScopes(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null));
205
206
  const writeIsPersonal = p.writePlane === "personal";
206
207
  writeEngine = writeIsPersonal ? personalEngine : projectEngine;
207
208
  writeHandle = writeIsPersonal ? personalHandle : projectHandle;
208
209
  readOnlyEngine = writeIsPersonal ? projectEngine : personalEngine;
209
210
  readOnlyHandle = writeIsPersonal ? projectHandle : personalHandle;
210
- injectFn = () => mergeInjections(projectEngine.inject(projectHandle, { writeToolMounted: input.writeToolsMounted }), personalEngine.inject(personalHandle, { writeToolMounted: input.writeToolsMounted }));
211
+ injectFn = () => mergeInjections(projectEngine.inject(projectHandle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark }), personalEngine.inject(personalHandle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark }));
211
212
  toolPlanes = [
212
213
  {
213
214
  backend: retrievalBackend(backend, adoptionRestricted || p.writePlane !== "project"),
@@ -243,11 +244,11 @@ export async function prepareMemory(input) {
243
244
  else if (personalOnly) {
244
245
  const personal = createPersonalEngine(choosePersonalBackend());
245
246
  const personalEngine = personal.engine;
246
- const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted });
247
+ const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted, sessionId });
247
248
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
248
249
  writeEngine = personalEngine;
249
250
  writeHandle = handle;
250
- injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted });
251
+ injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
251
252
  harvestBoth = () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...admitNothingOpts });
252
253
  toolPlanes = [
253
254
  {
@@ -264,12 +265,13 @@ export async function prepareMemory(input) {
264
265
  memoryDir,
265
266
  controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
266
267
  onIncident: onEngineIncident,
268
+ provenance: input.memoryProvenance,
267
269
  });
268
- const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted });
270
+ const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted, sessionId });
269
271
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
270
272
  writeEngine = engine;
271
273
  writeHandle = handle;
272
- injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted });
274
+ injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
273
275
  harvestBoth = () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...admitNothingOpts });
274
276
  toolPlanes = [
275
277
  {
@@ -302,15 +304,18 @@ export async function prepareMemory(input) {
302
304
  let pollutionAnnounced = false;
303
305
  const announceHarvestContainment = (report) => {
304
306
  const { count, moved, escalated } = pollutionContainmentCounts(report);
305
- if (count === 0)
306
- return;
307
- let reason;
308
- try {
309
- reason = writeEngine.sessionPollution(sessionId)?.reason;
310
- }
311
- catch {
307
+ const indexOnly = input.memoryProvenance !== "off" && count === 0 && report.containment?.indexRolledBack === true;
308
+ if (count > 0 || indexOnly) {
309
+ let reason;
310
+ try {
311
+ reason = writeEngine.sessionPollution(sessionId)?.reason;
312
+ }
313
+ catch {
314
+ }
315
+ deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(reason !== undefined ? { reason } : {}), sessionId, provenance: input.memoryProvenance, ...(indexOnly ? { indexRolledBack: true } : {}) }));
312
316
  }
313
- deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(reason !== undefined ? { reason } : {}), sessionId }));
317
+ for (const notice of memoryHoldNotices(report, sessionId))
318
+ deliverEngineNotice(deps.onNotice, notice);
314
319
  };
315
320
  const harvestSafe = async (phase = "terminal") => {
316
321
  try {
@@ -342,18 +347,40 @@ export async function prepareMemory(input) {
342
347
  harvest: harvestSafe,
343
348
  pollution: {
344
349
  polluted: () => writeEngine.sessionPollution(sessionId),
345
- markPolluted: (reason) => {
346
- const outcome = writeEngine.markSessionPolluted(sessionId, reason);
350
+ markPolluted: (reason, cause) => {
351
+ const outcome = writeEngine.markSessionPolluted(sessionId, reason, cause);
347
352
  if (outcome === "existed" || (outcome === "unpersisted" && pollutionAnnounced))
348
353
  return;
349
354
  pollutionAnnounced = true;
350
- deliverEngineNotice(deps.onNotice, memorySessionPollutedNotice({ reason, sessionId }));
355
+ deliverEngineNotice(deps.onNotice, memorySessionPollutedNotice({ reason, sessionId, provenance: input.memoryProvenance }));
351
356
  },
352
357
  },
353
358
  contentSafety: {
354
359
  trustedTools: new Set(memorySpec.trustedTools ?? []),
355
360
  execIsExternalContent: memorySpec.execIsExternalContent === true,
356
361
  },
362
+ ...(input.memoryProvenance !== "off"
363
+ ? { settlement: { controlDir: writeEngine.controlPlaneDir, sessionId, provenance: "carry" } }
364
+ : {}),
365
+ ...(input.memoryProvenance !== "off"
366
+ ? {
367
+ recallTaint: {
368
+ judgeDeliveredPath: (absPath) => {
369
+ try {
370
+ if (!isAbsolute(absPath))
371
+ return false;
372
+ const roots = [writeHandle.memoryDir, ...(readOnlyHandle !== undefined ? [readOnlyHandle.memoryDir] : [])];
373
+ if (!roots.some((r) => isContainedIn(r, absPath)))
374
+ return false;
375
+ return entryFileHeadCarriesOrigin(absPath);
376
+ }
377
+ catch {
378
+ return false;
379
+ }
380
+ },
381
+ },
382
+ }
383
+ : {}),
357
384
  };
358
385
  effectiveMemoryScopes = {
359
386
  state: "mounted",
@@ -361,8 +388,17 @@ export async function prepareMemory(input) {
361
388
  scopes: mountedScopeRows,
362
389
  writeScope: memorySpec.writeScope,
363
390
  };
391
+ if (input.memoryProvenance !== "off") {
392
+ const session = memoryEngineSession;
393
+ const markDerived = (ids) => {
394
+ const shown = ids.slice(0, 3).join(", ") + (ids.length > 3 ? ", …" : "");
395
+ session.pollution.markPolluted(`this session read the content of external-origin memory entr${ids.length === 1 ? "y" : "ies"} ${shown}`, "derived");
396
+ };
397
+ for (const plane of toolPlanes)
398
+ plane.onTaintedDelivery = markDerived;
399
+ }
364
400
  if (input.memorySearchToolsPlanned)
365
- memoryTools = createMemoryEngineTools({ planes: toolPlanes, sessionPollution: () => writeEngine.sessionPollution(sessionId) });
401
+ memoryTools = createMemoryEngineTools({ planes: toolPlanes, sessionPollution: () => writeEngine.sessionPollution(sessionId), provenance: input.memoryProvenance });
366
402
  }
367
403
  catch (err) {
368
404
  if (typeof err.code === "string" &&
@@ -411,7 +447,8 @@ export async function prepareMemory(input) {
411
447
  if (blockBody.trim())
412
448
  memoryBlock = blockBody;
413
449
  if (memoryTools !== undefined) {
414
- memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${MEMORY_RECALL_DISCIPLINE}` : MEMORY_RECALL_DISCIPLINE;
450
+ const segment = memoryRecallDisciplineSegment(input.memoryProvenance);
451
+ memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${segment}` : segment;
415
452
  }
416
453
  if (memoryEngineSession.handle.writeScope !== null && input.writeToolsMounted && input.rosterCanPersist) {
417
454
  memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${MEMORY_PREFERENCE_DISCIPLINE}` : MEMORY_PREFERENCE_DISCIPLINE;
@@ -142,6 +142,11 @@ export interface Prepared {
142
142
  * the `Session` contract instead. */
143
143
  session: StoredSession;
144
144
  sessionId: string;
145
+ /** design/319 (A ticket) — the session's reminder provenance mark this run mints under (adopted
146
+ * through the prepare adoption ladder: checkpoint seed → trusted fork channel → session entry →
147
+ * fresh mint). Every engine-authored `<system-reminder>` open tag in the run carries it, and the
148
+ * system prompt's Harness declaration names it. Always present on a completed prepare. */
149
+ reminderMark: string;
145
150
  /** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
146
151
  * `deps.rootPath ?? executionEnv.cwd`) — the same value the hands/LSP/policy/restore use. The Runner's
147
152
  * rewind/snapshot path MUST key off THIS, not `deps.rootPath`, or a worktree-isolated turn snapshots the base
@@ -414,8 +419,13 @@ export interface Prepared {
414
419
  polluted: () => {
415
420
  at: number;
416
421
  reason: string;
422
+ cause?: import("../memory-engine/types.js").MemoryOriginCause;
417
423
  } | undefined;
418
- markPolluted: (reason: string) => void;
424
+ /** design/336 §2.2 `cause` is the structured mechanical fact the mark records (and the
425
+ * origin marker minted off it carries): `"observed"` for a witnessed external event,
426
+ * `"static"` for the capability over-approximation, `"derived"` for recall-taint. Absent
427
+ * folds to `"observed"` at mint time (the honest floor). */
428
+ markPolluted: (reason: string, cause?: import("../memory-engine/types.js").MemoryOriginCause) => void;
419
429
  };
420
430
  /** design/178 §3 — the task's content-safety config (normalized memory spec): the allowlist for
421
431
  * UNDECLARED tools and the strict execution-class upgrade. Consumed by the tool wrap only. */
@@ -423,6 +433,30 @@ export interface Prepared {
423
433
  trustedTools: ReadonlySet<string>;
424
434
  execIsExternalContent: boolean;
425
435
  };
436
+ /**
437
+ * design/336 §3.3 — the delegation-settlement handle: the control-plane coordinates a
438
+ * settlement writer needs, as PURE DATA. Consumers (the subagent background leg's write-ahead
439
+ * + terminal observation, the tool wrap's sync unattestable row) can outlive this prepared
440
+ * leg, so they rebuild their write handle from these fields alone — never from the live
441
+ * engine/session objects above. Present only under `memoryProvenance: "carry"` (the default):
442
+ * an `"off"` deployment keeps the pre-336 accepted-cost posture byte-level.
443
+ */
444
+ settlement?: {
445
+ /** The WRITE plane's control-plane dir (the pollution-marker/lineage sidecar home). */
446
+ controlDir: string;
447
+ sessionId: string;
448
+ provenance: "carry";
449
+ };
450
+ /**
451
+ * design/336 §5.5 (file-face half) — the Read-tool recall-taint judgment, present only under
452
+ * `memoryProvenance: "carry"`. True ⇔ the delivered ABSOLUTE path sits inside a mounted memory
453
+ * plane and its head bytes carry a committed external-origin marker; the tool wrap then marks
454
+ * the session derived (same seat and cause as the memory_get propagation). Never throws;
455
+ * relative paths and unreadable files answer false (named residuals beside the Bash channel).
456
+ */
457
+ recallTaint?: {
458
+ judgeDeliveredPath: (absPath: string) => boolean;
459
+ };
426
460
  };
427
461
  /** A per-task env minted by `RunnerDeps.executionEnvFactory` (design/48 remote seam) that THIS task owns
428
462
  * and the Runner must tear down on task end. Undefined when the env came from a (caller-owned) static
@@ -1167,6 +1201,17 @@ export interface RunInternals {
1167
1201
  * `TaskSpec` field — the untrusted-caller surface), mirrors `inheritedManifestScope`.
1168
1202
  */
1169
1203
  insideFork?: boolean;
1204
+ /**
1205
+ * design/319 (A ticket) — the PARENT's reminder provenance mark, set ONLY by the Agent tool's
1206
+ * fork route on the child it spawns ("one declaration, one mark": a fork runs under the parent's
1207
+ * byte-identical system-prompt declaration, so its own engine mints must carry the PARENT's mark;
1208
+ * a spawn/clone context mints its own). Verified through the mint home's verify port at adoption
1209
+ * (`isValidReminderMark` — an unrecognized value re-mints, fail-safe); the forked SESSION's own
1210
+ * `reminder_mark` entry is the primary carrier (a store fork copies committed history), this
1211
+ * channel is the in-process belt over it. TRUSTED run-scoped channel (NOT a {@link TaskSpec}
1212
+ * field), mirroring `insideFork`.
1213
+ */
1214
+ reminderMark?: string;
1170
1215
  /**
1171
1216
  * RB-204 P1 (codex review) — set UNCONDITIONALLY by every core spawn path (`createSubagentTool`'s
1172
1217
  * `childInternals` — shared by all four spawn legs sync/steer/background/fork, its persisted