@sema-agent/core 5.65.0 → 6.0.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 (159) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/agents/subagent.d.ts +2 -2
  3. package/dist/agents/subagent.js +11 -0
  4. package/dist/agents/verify.d.ts +1 -1
  5. package/dist/brain/anthropic.js +1 -1
  6. package/dist/brain/errors.d.ts +29 -0
  7. package/dist/brain/errors.js +20 -0
  8. package/dist/brain/open-responses.js +2 -2
  9. package/dist/brain/route-adjudicator.d.ts +8 -1
  10. package/dist/brain/route-adjudicator.js +1 -0
  11. package/dist/brain/status-sink.js +12 -1
  12. package/dist/brain/stream-engine.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +26 -0
  14. package/dist/core/auto-compaction.js +7 -2
  15. package/dist/core/auto-mode-arming.d.ts +138 -0
  16. package/dist/core/auto-mode-arming.js +181 -0
  17. package/dist/core/auto-mode-defaults.d.ts +13 -0
  18. package/dist/core/auto-mode-defaults.js +5 -0
  19. package/dist/core/auto-mode-prompt.d.ts +14 -3
  20. package/dist/core/auto-mode-prompt.js +10 -7
  21. package/dist/core/auto-mode-rebuild.d.ts +75 -0
  22. package/dist/core/auto-mode-rebuild.js +41 -0
  23. package/dist/core/auto-mode.d.ts +15 -0
  24. package/dist/core/auto-mode.js +4 -2
  25. package/dist/core/checkpoint-store.d.ts +18 -0
  26. package/dist/core/context-edit.d.ts +47 -5
  27. package/dist/core/context-guard.d.ts +1 -1
  28. package/dist/core/file-history-retention.d.ts +106 -0
  29. package/dist/core/file-history-retention.js +36 -0
  30. package/dist/core/file-history-store.d.ts +768 -0
  31. package/dist/core/file-history-store.js +880 -0
  32. package/dist/core/governance-codes.d.ts +2 -1
  33. package/dist/core/governance-codes.js +14 -0
  34. package/dist/core/hooks.d.ts +39 -7
  35. package/dist/core/hooks.js +38 -21
  36. package/dist/core/lsp.d.ts +2 -2
  37. package/dist/core/mcp.d.ts +29 -7
  38. package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
  39. package/dist/core/memory-engine/consolidation-driver.js +71 -4
  40. package/dist/core/memory-engine/consolidation.d.ts +25 -2
  41. package/dist/core/memory-engine/consolidation.js +4 -1
  42. package/dist/core/memory-engine/distiller.d.ts +84 -1
  43. package/dist/core/memory-engine/distiller.js +68 -0
  44. package/dist/core/memory-engine/dual-root.js +3 -0
  45. package/dist/core/memory-engine/engine.d.ts +328 -15
  46. package/dist/core/memory-engine/engine.js +355 -29
  47. package/dist/core/memory-engine/file-backend.d.ts +30 -0
  48. package/dist/core/memory-engine/file-backend.js +14 -13
  49. package/dist/core/memory-engine/frontmatter.d.ts +22 -1
  50. package/dist/core/memory-engine/frontmatter.js +3 -0
  51. package/dist/core/memory-engine/header-hints.d.ts +5 -0
  52. package/dist/core/memory-engine/index.d.ts +5 -4
  53. package/dist/core/memory-engine/index.js +5 -4
  54. package/dist/core/memory-engine/layout.d.ts +88 -2
  55. package/dist/core/memory-engine/layout.js +112 -3
  56. package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
  57. package/dist/core/memory-engine/provenance-wording.js +3 -0
  58. package/dist/core/memory-engine/tools.d.ts +89 -8
  59. package/dist/core/memory-engine/tools.js +263 -22
  60. package/dist/core/memory-engine/types.d.ts +64 -1
  61. package/dist/core/memory-recall.d.ts +6 -0
  62. package/dist/core/memory.d.ts +27 -1
  63. package/dist/core/memory.js +16 -2
  64. package/dist/core/permission-rule-consent.d.ts +20 -0
  65. package/dist/core/permission-rule-consent.js +12 -3
  66. package/dist/core/permission-rule-model.d.ts +67 -7
  67. package/dist/core/permission-rule-model.js +53 -7
  68. package/dist/core/permission-rule-store.js +15 -10
  69. package/dist/core/permission-rule-sync.js +15 -11
  70. package/dist/core/retention-policy.d.ts +9 -0
  71. package/dist/core/retention-policy.js +5 -2
  72. package/dist/core/retention.d.ts +13 -2
  73. package/dist/core/runner/assemble-result.d.ts +19 -1
  74. package/dist/core/runner/assemble-result.js +17 -2
  75. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  76. package/dist/core/runner/compaction-call-options.js +3 -0
  77. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  78. package/dist/core/runner/memory-capture-optout.js +53 -0
  79. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  80. package/dist/core/runner/prepare-config-doors.js +16 -0
  81. package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
  82. package/dist/core/runner/prepare-hands-readface.js +99 -7
  83. package/dist/core/runner/prepare-memory.d.ts +88 -0
  84. package/dist/core/runner/prepare-memory.js +305 -24
  85. package/dist/core/runner/prepare-task.d.ts +141 -1
  86. package/dist/core/runner/prepare-task.js +443 -79
  87. package/dist/core/runner/runtask.d.ts +9 -20
  88. package/dist/core/runner/runtask.js +133 -96
  89. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  90. package/dist/core/runner/session-file-state-replay.js +52 -1
  91. package/dist/core/runner/tool-disclosure.js +2 -1
  92. package/dist/core/runner/turn-attachments.d.ts +22 -12
  93. package/dist/core/session-store.d.ts +1 -1
  94. package/dist/core/session-store.js +6 -1
  95. package/dist/core/session.d.ts +34 -1
  96. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  97. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  98. package/dist/core/task-registry-shared.js +11 -1
  99. package/dist/core/tool-errors.js +1 -0
  100. package/dist/core/tool-policy.d.ts +172 -1
  101. package/dist/core/tool-policy.js +32 -1
  102. package/dist/core/tool-result-store.js +2 -1
  103. package/dist/core/trace.d.ts +24 -0
  104. package/dist/core/types.d.ts +784 -89
  105. package/dist/core/types.js +4 -3
  106. package/dist/core/untrusted-text.d.ts +1 -1
  107. package/dist/core/untrusted-text.js +8 -0
  108. package/dist/core/workflow-run-store-contract.js +6 -2
  109. package/dist/core/workflow-run-store.d.ts +4 -1
  110. package/dist/engine/compaction/compaction.d.ts +88 -10
  111. package/dist/engine/compaction/compaction.js +109 -30
  112. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  113. package/dist/engine/execution-env/node-execution-env.js +28 -0
  114. package/dist/engine/harness/agent-harness.d.ts +52 -1
  115. package/dist/engine/harness/agent-harness.js +36 -1
  116. package/dist/engine/harness/types.d.ts +26 -1
  117. package/dist/engine/llm/types.d.ts +50 -4
  118. package/dist/engine/loop/agent-loop.d.ts +5 -1
  119. package/dist/engine/loop/agent-loop.js +25 -0
  120. package/dist/engine/loop/types.d.ts +19 -0
  121. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  122. package/dist/engine/session/session.js +1 -1
  123. package/dist/index.d.ts +18 -8
  124. package/dist/index.js +14 -6
  125. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  126. package/dist/orchestration/run-workflow-tool.js +22 -3
  127. package/dist/orchestration/workflow-governance.d.ts +59 -1
  128. package/dist/orchestration/workflow-governance.js +61 -8
  129. package/dist/orchestration/workflow-meta.d.ts +4 -2
  130. package/dist/orchestration/workflow-primitives.js +56 -13
  131. package/dist/orchestration/workflow-types.d.ts +78 -2
  132. package/dist/orchestration/workflow.d.ts +20 -0
  133. package/dist/orchestration/workflow.js +163 -14
  134. package/dist/prompt-assembly/event-registry.js +1 -1
  135. package/dist/prompts/default.d.ts +7 -7
  136. package/dist/stores/file/file-history-store.d.ts +368 -0
  137. package/dist/stores/file/file-history-store.js +1248 -0
  138. package/dist/stores/file/index.d.ts +22 -13
  139. package/dist/stores/file/index.js +4 -4
  140. package/dist/stores/file/permission-rule-store.js +1 -0
  141. package/dist/stores/file/strategy-store.d.ts +3 -3
  142. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  143. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  144. package/dist/tools/fs/fs-bash.js +9 -5
  145. package/dist/tools/fs/fs-shared.d.ts +52 -1
  146. package/dist/tools/fs/fs-shared.js +14 -0
  147. package/dist/tools/fs/fs-write.d.ts +5 -5
  148. package/dist/tools/fs/fs-write.js +71 -14
  149. package/dist/tools/fs/index.d.ts +6 -1
  150. package/dist/tools/fs/index.js +1 -1
  151. package/dist/tools/web.js +2 -1
  152. package/package.json +5 -1
  153. package/test/export-surface.snapshot.json +155 -23
  154. package/dist/core/file-snapshot-store.d.ts +0 -165
  155. package/dist/core/file-snapshot-store.js +0 -259
  156. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  157. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  158. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  159. package/dist/stores/file/file-snapshot-store.js +0 -353
@@ -6,6 +6,7 @@ const PROMPT_HASH_SALT = randomBytes(16);
6
6
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
8
8
  import { createAutoModeDecider } from "../auto-mode.js";
9
+ import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
9
10
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
10
11
  import { resolveTaskModel } from "../roles.js";
11
12
  import { primaryActivityArg } from "../arg-summary.js";
@@ -20,7 +21,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEn
20
21
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
22
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
22
23
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
23
- import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
+ import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
25
  const PERSISTED_RULE_TOOL = "Bash";
25
26
  import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
26
27
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
@@ -49,7 +50,6 @@ import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDisco
49
50
  import { createSharedMemoryTools } from "../shared-memory/tools.js";
50
51
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
51
52
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
52
- import { memoryRecallDisciplineSegment } from "../memory-engine/engine.js";
53
53
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
54
54
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
55
55
  import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
@@ -97,6 +97,7 @@ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDec
97
97
  import { durableParkGapFor } from "../park-selfcheck.js";
98
98
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
99
99
  import { deliverEngineNotice } from "../types.js";
100
+ import { resolveTrackKey } from "../file-history-store.js";
100
101
  let announcedMaterializeEnvBySink = new WeakMap();
101
102
  const announcedMaterializeEnvConsole = new Set();
102
103
  function materializeEnvLedger(onNotice) {
@@ -218,6 +219,7 @@ function announceDeclaredMcpContentClasses(args) {
218
219
  toolCount,
219
220
  ...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
220
221
  sessionId: args.sessionId,
222
+ runId: args.runId,
221
223
  },
222
224
  });
223
225
  }
@@ -324,7 +326,7 @@ class ParkRefusal extends Error {
324
326
  export { resolveCheckpointStore } from "../checkpoint-store.js";
325
327
  export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
326
328
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
327
- function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
329
+ function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
328
330
  const triggerWindow = resolveTriggerWindow(model);
329
331
  if (triggerWindow.clamped) {
330
332
  deliverEngineNotice(deps.onNotice, {
@@ -337,6 +339,7 @@ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
337
339
  declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
338
340
  physicalWindow: triggerWindow.physicalWindow,
339
341
  sessionId,
342
+ runId,
340
343
  },
341
344
  });
342
345
  }
@@ -433,6 +436,187 @@ export async function runGuardChain(args) {
433
436
  const t = applyGuardTrim(working, anchoredWorking);
434
437
  return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
435
438
  }
439
+ function restoredAbsPathsOf(restored, root) {
440
+ const out = [];
441
+ for (const key of [...restored.filesChanged, ...restored.failed.map((f) => f.path)]) {
442
+ const abs = resolveTrackKey(root, key);
443
+ if (abs.ok)
444
+ out.push(abs.abs);
445
+ }
446
+ return out;
447
+ }
448
+ function resolveWiredFileHistoryStore(candidate) {
449
+ if (candidate === undefined)
450
+ return undefined;
451
+ const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
452
+ const verbOf = (v) => {
453
+ try {
454
+ return candidate[v];
455
+ }
456
+ catch {
457
+ return undefined;
458
+ }
459
+ };
460
+ const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
461
+ const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
462
+ if (missing.length > 0) {
463
+ const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
464
+ const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Omit the key to run without file history.`);
465
+ e.code = "config.invalid_file_history_store";
466
+ throw e;
467
+ }
468
+ return candidate;
469
+ }
470
+ const LEGACY_PROBE_TIMEOUT_MS = 2_000;
471
+ const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
472
+ async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
473
+ const probe = deps.legacyRewindBoundaryProbe;
474
+ let verdict;
475
+ if (probe !== undefined) {
476
+ try {
477
+ let timer;
478
+ const timeout = new Promise((resolve) => {
479
+ timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
480
+ });
481
+ try {
482
+ verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
483
+ }
484
+ finally {
485
+ clearTimeout(timer);
486
+ }
487
+ if (verdict === LEGACY_PROBE_TIMED_OUT) {
488
+ verdict = undefined;
489
+ try {
490
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
491
+ }
492
+ catch {
493
+ }
494
+ }
495
+ }
496
+ catch (err) {
497
+ verdict = undefined;
498
+ try {
499
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
500
+ }
501
+ catch {
502
+ }
503
+ }
504
+ if (verdict !== undefined && typeof verdict !== "boolean") {
505
+ try {
506
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
507
+ }
508
+ catch {
509
+ }
510
+ verdict = undefined;
511
+ }
512
+ }
513
+ if (verdict === true) {
514
+ return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
515
+ }
516
+ if (verdict === false) {
517
+ return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
518
+ }
519
+ return form === "keyed"
520
+ ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
521
+ : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
522
+ }
523
+ const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
524
+ const FILE_HISTORY_CARRY_MEMO = new WeakMap();
525
+ async function adoptForkedFileHistory(store, session, sessionId, onError) {
526
+ const memo = FILE_HISTORY_CARRY_MEMO.get(store) ?? { settled: new Set(), disclosed: new Set(), inFlight: new Map() };
527
+ FILE_HISTORY_CARRY_MEMO.set(store, memo);
528
+ const discloseOnce = (err) => {
529
+ if (memo.disclosed.has(sessionId))
530
+ return;
531
+ memo.disclosed.add(sessionId);
532
+ onError?.(err, { phase: "rewind", sessionId });
533
+ };
534
+ try {
535
+ if (memo.settled.has(sessionId))
536
+ return;
537
+ const forkedFrom = (await session.getMetadata()).forkedFrom;
538
+ if (forkedFrom === undefined || forkedFrom === sessionId)
539
+ return;
540
+ const shared = memo.inFlight.get(sessionId);
541
+ if (shared !== undefined)
542
+ return await shared;
543
+ if (memo.settled.has(sessionId))
544
+ return;
545
+ const attempt = store.adoptScope(forkedFrom, sessionId).then((carried) => {
546
+ if (carried.ok || carried.error.code === "conflict") {
547
+ memo.settled.add(sessionId);
548
+ return;
549
+ }
550
+ discloseOnce(new Error(`file-history fork carry from session "${forkedFrom}" failed (${carried.error.code}): ${carried.error.message} — this forked session starts with an EMPTY rewind history, so boundaries recorded before the fork are not restorable from it (the run itself proceeds; the carry is retried on this session's next turn while its scope is still empty)`));
551
+ }, (err) => discloseOnce(err));
552
+ memo.inFlight.set(sessionId, attempt);
553
+ try {
554
+ await attempt;
555
+ }
556
+ finally {
557
+ memo.inFlight.delete(sessionId);
558
+ }
559
+ }
560
+ catch (err) {
561
+ try {
562
+ discloseOnce(err);
563
+ }
564
+ catch {
565
+ }
566
+ }
567
+ }
568
+ function createFileHistoryBoundarySeat(opts) {
569
+ let inFlight;
570
+ let begun = false;
571
+ return {
572
+ begin(entryId) {
573
+ if (begun)
574
+ return;
575
+ begun = true;
576
+ const ac = new AbortController();
577
+ let timer;
578
+ const timeout = new Promise((resolve) => {
579
+ timer = setTimeout(() => {
580
+ ac.abort();
581
+ resolve({ ok: false });
582
+ }, FILE_HISTORY_BOUNDARY_TIMEOUT_MS);
583
+ });
584
+ let work;
585
+ try {
586
+ work = opts.store.snapshot(opts.sessionId, entryId, opts.env, opts.root, ac.signal);
587
+ }
588
+ catch (err) {
589
+ work = Promise.resolve({ ok: false, error: { code: "snapshot_failed", message: `history store threw synchronously: ${err.message}` } });
590
+ }
591
+ work.catch(() => { });
592
+ inFlight = Promise.race([
593
+ work.then((r) => (r.ok ? undefined : `boundary capture failed (${r.error.code}): ${r.error.message}`)),
594
+ timeout.then(() => `boundary capture timed out after ${FILE_HISTORY_BOUNDARY_TIMEOUT_MS / 1000}s (fenced — the attempt can never publish)`),
595
+ ])
596
+ .then((failure) => {
597
+ if (failure !== undefined) {
598
+ try {
599
+ opts.onError?.(new Error(`file-history ${failure} — this turn has no boundary and cannot be rewound to (the turn itself proceeds)`), { phase: "rewind", sessionId: opts.sessionId });
600
+ }
601
+ catch {
602
+ }
603
+ }
604
+ })
605
+ .catch((err) => {
606
+ try {
607
+ opts.onError?.(err, { phase: "rewind", sessionId: opts.sessionId });
608
+ }
609
+ catch {
610
+ }
611
+ })
612
+ .finally(() => clearTimeout(timer));
613
+ },
614
+ async settle() {
615
+ if (inFlight !== undefined)
616
+ await inFlight;
617
+ },
618
+ };
619
+ }
436
620
  export function gatedCallIdOf(p) {
437
621
  if (p.suspendRef.token !== undefined)
438
622
  return p.suspendRef.gatedCallId;
@@ -508,7 +692,7 @@ function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
508
692
  }
509
693
  };
510
694
  }
511
- function mcpRevocationWiring(deps) {
695
+ function mcpRevocationWiring(deps, runId) {
512
696
  if (deps.mcpRevocations === undefined)
513
697
  return undefined;
514
698
  const ledger = deps.mcpRevocations;
@@ -517,7 +701,7 @@ function mcpRevocationWiring(deps) {
517
701
  onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
518
702
  code: "mcp.revocation_probe_failed",
519
703
  message: `the mcpRevocations.isRevoked probe threw — MCP dispatch fails OPEN (no server treated as revoked) until the probe recovers: ${e instanceof Error ? e.message : String(e)}`,
520
- detail: { message: e instanceof Error ? e.message : String(e) },
704
+ detail: { message: e instanceof Error ? e.message : String(e), runId },
521
705
  }),
522
706
  };
523
707
  }
@@ -674,7 +858,7 @@ async function derivedRouteFallsBack(args) {
674
858
  const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
675
859
  if (verdict === undefined || verdict.ok)
676
860
  return false;
677
- deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}) }));
861
+ deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}), ...(args.runId !== undefined ? { runId: args.runId } : {}) }));
678
862
  return true;
679
863
  }
680
864
  catch {
@@ -703,6 +887,12 @@ export function placementValueOrAbsent(value) {
703
887
  function stampPlacementRootSessionId(placementRootResolved) {
704
888
  return placementValueOrAbsent(placementRootResolved);
705
889
  }
890
+ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
891
+ return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
892
+ }
893
+ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
894
+ return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
895
+ }
706
896
  function refuseRequireExistingWithoutSession(spec) {
707
897
  if (spec.requireExistingSession && !spec.sessionId) {
708
898
  const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
@@ -710,10 +900,13 @@ function refuseRequireExistingWithoutSession(spec) {
710
900
  throw e;
711
901
  }
712
902
  }
713
- export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
903
+ export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
904
+ const runId = uuidv7();
905
+ if (runIdSink !== undefined)
906
+ runIdSink.runId = runId;
714
907
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
715
908
  spec = doors.spec;
716
- const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
909
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
717
910
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
718
911
  const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
719
912
  let shellGatedBash = false;
@@ -727,7 +920,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
727
920
  let effectiveCompModel = compModel;
728
921
  if (spec.compactionModel === undefined &&
729
922
  compModel !== undefined &&
730
- (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId }))) {
923
+ (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId }))) {
731
924
  effectiveCompModel = undefined;
732
925
  }
733
926
  warnCompactionWindowHazard(deps.tracer, spec, model, effectiveCompModel, hostTaskId);
@@ -907,79 +1100,165 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
907
1100
  }
908
1101
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
909
1102
  const rewindNotes = [];
910
- const rewindCaptureRequested = spec.rewindFiles === true;
1103
+ const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
1104
+ if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
1105
+ const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
1106
+ e.code = "config.invalid_on_track_failure";
1107
+ throw e;
1108
+ }
1109
+ const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1110
+ if (fileHistoryStore !== undefined) {
1111
+ await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1112
+ }
1113
+ const legacyRewindFiles = spec.rewindFiles;
1114
+ if (legacyRewindFiles === true) {
1115
+ if (spec.resumeAt !== undefined) {
1116
+ const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is always on when a RunnerDeps.fileHistoryStore is wired.)`);
1117
+ e.code = "rewind.rewind_files_retired";
1118
+ throw e;
1119
+ }
1120
+ try {
1121
+ deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is always on when RunnerDeps.fileHistoryStore is wired, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
1122
+ }
1123
+ catch {
1124
+ }
1125
+ }
1126
+ if (spec.restoreFiles === true && spec.resumeAt === undefined) {
1127
+ const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
1128
+ e.code = "rewind.invalid_spec";
1129
+ throw e;
1130
+ }
911
1131
  if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
912
- const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when rewindFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
1132
+ const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
913
1133
  e.code = "rewind.conflicting_targets";
914
1134
  throw e;
915
1135
  }
916
- let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
1136
+ let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
917
1137
  const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
918
- if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
1138
+ if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
919
1139
  rewindNotes.push({
920
1140
  code: "conversation_only",
921
- message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without rewindFiles, so files remain at their current state`,
1141
+ message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without restoreFiles, so files remain at their current state`,
922
1142
  });
923
1143
  }
924
- if (!deps.fileSnapshotStore) {
1144
+ const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1145
+ let historyRoot = taskRootFinal;
1146
+ if (fileHistoryEnabled) {
1147
+ const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
1148
+ if (canonRoot.ok)
1149
+ historyRoot = canonRoot.value;
1150
+ }
1151
+ if (fileHistoryStore === undefined) {
925
1152
  if (rewindTarget !== undefined) {
926
- const e = new Error(`rewind-files: restoring the working tree to entry "${rewindTarget}" requires a snapshot backend, but this deployment wired no RunnerDeps.fileSnapshotStore — no snapshot was ever captured, so the files were NOT rewound`);
1153
+ const e = new Error(`rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
927
1154
  e.code = "rewind.store_unconfigured";
928
1155
  throw e;
929
1156
  }
930
- if (rewindCaptureRequested) {
931
- rewindNotes.push({
932
- code: "snapshot_store_unconfigured",
933
- message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
934
- });
935
- }
936
1157
  }
937
1158
  else if (!handsEnabled) {
938
- if (rewindTarget !== undefined || rewindCaptureRequested) {
1159
+ if (rewindTarget !== undefined) {
939
1160
  rewindNotes.push({
940
1161
  code: "files_env_unsupported",
941
- message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no working-tree snapshot was captured or restored",
1162
+ message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no file history was recorded or restored",
942
1163
  });
943
1164
  }
944
1165
  }
945
- if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
1166
+ const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
1167
+ let restoredFilePaths = [];
1168
+ if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
1169
+ const historyStore = fileHistoryStore;
946
1170
  if (rewindBefore) {
947
1171
  let anchor;
948
- const walked = new Set();
949
- let cursor = resumeAtBeforeParentId;
950
- while (cursor !== null && !walked.has(cursor)) {
951
- walked.add(cursor);
952
- if (await deps.fileSnapshotStore.has(sessionId, cursor)) {
953
- anchor = cursor;
954
- break;
1172
+ if (await historyStore.canRestore(sessionId, rewindTarget)) {
1173
+ anchor = rewindTarget;
1174
+ }
1175
+ else {
1176
+ const walked = new Set();
1177
+ let cursor = resumeAtBeforeParentId;
1178
+ while (cursor !== null && !walked.has(cursor)) {
1179
+ walked.add(cursor);
1180
+ if (await historyStore.canRestore(sessionId, cursor)) {
1181
+ anchor = cursor;
1182
+ break;
1183
+ }
1184
+ cursor = (await session.getEntry(cursor))?.parentId ?? null;
955
1185
  }
956
- cursor = (await session.getEntry(cursor))?.parentId ?? null;
957
1186
  }
958
1187
  if (anchor === undefined) {
959
- const e = new Error(`rewind-files: no file snapshot exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context (earlier turns ran without rewindFiles, or their snapshots were reaped)`);
1188
+ const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
960
1189
  e.code = "rewind_snapshot.unresolvable";
961
1190
  throw e;
962
1191
  }
963
1192
  rewindTarget = anchor;
964
1193
  }
965
- const restoreRoot = taskRootFinal;
1194
+ const restoreRoot = historyRoot;
966
1195
  const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
967
- const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
968
- if (!restored.ok) {
969
- if (restored.error.code === "not_found") {
1196
+ const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
1197
+ const ledgerLine = (r) => `applied ${r.filesChanged.length}, identical ${r.identicalSkipped.length}, refused ${r.refused.length}${r.refused.length > 0 ? ` (${r.refused.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}, failed ${r.failed.length}${r.failed.length > 0 ? ` (${r.failed.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}`;
1198
+ if (restored.outcome === "failed") {
1199
+ if (restored.error?.code === "not_found") {
970
1200
  const e = new Error(rewindBefore
971
- ? `rewind-files: the resolved "before" snapshot anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
972
- : `rewind-files: no file snapshot exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound (only a COMPLETED turn that ran with rewindFiles is snapshotted, and snapshots are keyed by that turn's END leaf; earlier turns that ran without rewindFiles, mid-turn entries, and reaped snapshots have none)`);
1201
+ ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
1202
+ : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
973
1203
  e.code = "rewind_snapshot.unresolvable";
974
1204
  throw e;
975
1205
  }
1206
+ const e = new Error(`rewind-files restore failed (${restored.error?.code ?? "restore_failed"}): ${restored.error?.message ?? "unknown"} — ledger: ${ledgerLine(restored)}`);
1207
+ e.code = "rewind.restore_failed";
1208
+ throw e;
1209
+ }
1210
+ if (restored.outcome === "partial") {
1211
+ if (spec.acceptPartialRestore === true) {
1212
+ rewindNotes.push({
1213
+ code: "restore_partial",
1214
+ message: `file restore to entry "${rewindTarget}" was PARTIAL and was tolerated by acceptPartialRestore — ledger: ${ledgerLine(restored)}. Re-running the same restore converges (per-file idempotent).`,
1215
+ });
1216
+ }
976
1217
  else {
977
- const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
1218
+ const e = new Error(`rewind-files: restore to entry "${rewindTarget}" was PARTIAL — ledger: ${ledgerLine(restored)}. The task is refused rather than run on a mixed-state tree (set acceptPartialRestore: true to tolerate and disclose instead); re-running the same restore converges (per-file idempotent).`);
978
1219
  e.code = "rewind.restore_failed";
979
1220
  throw e;
980
1221
  }
981
1222
  }
1223
+ restoredFilePaths = restoredAbsPathsOf(restored, restoreRoot);
982
1224
  }
1225
+ const trackFileEdit = fileHistoryEnabled
1226
+ ? async (req) => {
1227
+ const r = await fileHistoryStore.trackEdit(sessionId, req.key, executionEnv, historyRoot, req.signal);
1228
+ if (r.ok) {
1229
+ if (!r.minted)
1230
+ return { ok: true };
1231
+ return {
1232
+ ok: true,
1233
+ annul: async (proof) => {
1234
+ const a = await fileHistoryStore.annulTrack(sessionId, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1235
+ if (a.ok)
1236
+ return;
1237
+ try {
1238
+ deps.onError?.(new Error(`file-history could not retract the first-touch record for "${req.key}" after its edit failed to land (${a.error.code}: ${a.error.message}) — the path stays tracked, so a rewind to a boundary below it may converge bytes this engine never wrote`), { phase: "rewind", sessionId });
1239
+ }
1240
+ catch {
1241
+ }
1242
+ },
1243
+ };
1244
+ }
1245
+ if (onTrackFailure === "proceed-unprotected") {
1246
+ try {
1247
+ deps.onError?.(new Error(`file-history first-touch backup for "${req.key}" failed (${r.error.code}: ${r.error.message}) — proceeding UNPROTECTED per RunnerDeps.onTrackFailure: the pristine promise for this path is void, and only in this process's memory (no durable trace exists, so after a process restart the next edit can still mint already-modified bytes as pristine v1)`), { phase: "rewind", sessionId });
1248
+ }
1249
+ catch {
1250
+ }
1251
+ return { ok: true };
1252
+ }
1253
+ return {
1254
+ ok: false,
1255
+ refusal: `Error (${req.tool}): the edit was refused because its first-touch file-history backup could not be persisted (${r.error.code}: ${r.error.message}). Proceeding would leave "${req.path}" with no durable pristine record — after the history store recovers, a retried edit would mint already-modified bytes as the pristine baseline. Retry when the file-history store is healthy.`,
1256
+ };
1257
+ }
1258
+ : undefined;
1259
+ const fileHistoryBoundary = fileHistoryEnabled
1260
+ ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1261
+ : undefined;
983
1262
  const harnessRef = {};
984
1263
  const skillScope = new ActiveSkillScope();
985
1264
  const forwardSink = internals?.onForwardEvent;
@@ -1121,7 +1400,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1121
1400
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1122
1401
  ...(durableMandate ? { durableMandate: true } : {}),
1123
1402
  ...(contentMandate ? { contentMandate: true } : {}),
1124
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1403
+ ...(autoModeDecider !== undefined
1404
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1405
+ : {}),
1125
1406
  },
1126
1407
  ]
1127
1408
  : [];
@@ -1135,7 +1416,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1135
1416
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1136
1417
  ...(durableMandate ? { durableMandate: true } : {}),
1137
1418
  ...(contentMandate ? { contentMandate: true } : {}),
1138
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1419
+ ...(autoModeDecider !== undefined
1420
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1421
+ : {}),
1139
1422
  },
1140
1423
  ]
1141
1424
  : []),
@@ -1159,6 +1442,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1159
1442
  spec.handsReadOnly !== true &&
1160
1443
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1161
1444
  let autoModeDecider;
1445
+ let autoModeArming;
1162
1446
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1163
1447
  const enrichSpecToolCtx = (ctx) => ({
1164
1448
  ...ctx,
@@ -1184,6 +1468,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1184
1468
  ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
1185
1469
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1186
1470
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1471
+ get memoryCaptureOptedOut() {
1472
+ return memoryEngineSession?.captureOptOut?.optedOut() === true;
1473
+ },
1474
+ get memoryCaptureIndeterminate() {
1475
+ return memoryEngineSession?.captureOptOut?.indeterminate() === true;
1476
+ },
1477
+ get memoryCaptureControlDir() {
1478
+ return memoryEngineSession?.engine.controlPlaneDir;
1479
+ },
1480
+ get memoryCaptureAncestors() {
1481
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1482
+ return [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }];
1483
+ },
1187
1484
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1188
1485
  parentCwd: taskRootFinal,
1189
1486
  reminderMark,
@@ -1262,6 +1559,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1262
1559
  }
1263
1560
  }
1264
1561
  let runtimeCaps;
1562
+ let runtimeCapsFaulted = false;
1265
1563
  if (deps.runtimeCapsResolver) {
1266
1564
  try {
1267
1565
  runtimeCaps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
@@ -1269,6 +1567,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1269
1567
  catch (err) {
1270
1568
  deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1271
1569
  runtimeCaps = { allowWorkflows: false, allowFork: false };
1570
+ runtimeCapsFaulted = true;
1272
1571
  }
1273
1572
  }
1274
1573
  let complianceDenies = new Set();
@@ -1315,7 +1614,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1315
1614
  catch {
1316
1615
  classifierModel = model;
1317
1616
  }
1318
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId })) {
1617
+ if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
1319
1618
  classifierModel = model;
1320
1619
  }
1321
1620
  const classifierSystemPrompt = buildAutoModePrompt(am);
@@ -1340,6 +1639,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1340
1639
  .join("");
1341
1640
  },
1342
1641
  });
1642
+ if (am.persistArming === true) {
1643
+ autoModeArming = autoModeArmingRecipeOf(am, {
1644
+ promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
1645
+ });
1646
+ if (autoModeArming === undefined) {
1647
+ deps.onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
1648
+ "(a rule list carrying a non-string, or a non-finite/out-of-range timeoutMs / failureThreshold / window bound) — " +
1649
+ "NO arming recipe is recorded on this run's parked constraint chain, and a cross-process redemption keeps the " +
1650
+ "conservative behavior (the ancestor classifier answers unavailable and the inherited ask flows to a human)"), { phase: "config", sessionId });
1651
+ }
1652
+ }
1343
1653
  }
1344
1654
  const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
1345
1655
  const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
@@ -1387,6 +1697,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1387
1697
  principal: spec.principal,
1388
1698
  oneShot: spec.oneShot,
1389
1699
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1700
+ parentMemoryCaptureState: () => {
1701
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1702
+ return {
1703
+ optedOut: memoryEngineSession?.captureOptOut?.optedOut() === true,
1704
+ indeterminate: memoryEngineSession?.captureOptOut?.indeterminate() === true,
1705
+ ...(ctl !== undefined ? { controlDir: ctl } : {}),
1706
+ ancestors: [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }],
1707
+ };
1708
+ },
1390
1709
  autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1391
1710
  workflowDepth: internals?.workflowDepth,
1392
1711
  parentCwd: taskRootFinal,
@@ -1465,7 +1784,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1465
1784
  tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
1466
1785
  }
1467
1786
  mcp = lockedPreflight.mcp?.length
1468
- ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps))
1787
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
1469
1788
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1470
1789
  for (const w of mcp.warnings)
1471
1790
  deps.onError?.(w, { phase: "mcp", sessionId });
@@ -1621,9 +1940,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1621
1940
  foldProtocolAxes(a2a.toolAxes, "A2A");
1622
1941
  const callIssuedAtRef = {};
1623
1942
  const memoryWriteGateRef = {};
1624
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts };
1943
+ const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
1625
1944
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
1626
- const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames } = handsReadFace;
1945
+ const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
1627
1946
  resolvedReadFace = handsReadFace.resolvedReadFace;
1628
1947
  readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
1629
1948
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
@@ -1868,7 +2187,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1868
2187
  const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
1869
2188
  const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
1870
2189
  const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
1871
- const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
2190
+ const captureLineage = await (async () => {
2191
+ try {
2192
+ const ff = (await session.getMetadata()).forkedFrom;
2193
+ return { ...(ff !== undefined && ff !== sessionId ? { origin: ff } : {}), fault: false };
2194
+ }
2195
+ catch {
2196
+ return { fault: true };
2197
+ }
2198
+ })();
2199
+ const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, recallDisciplineSegment: memoryRecallSegment, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
1872
2200
  spec,
1873
2201
  deps,
1874
2202
  sessionId,
@@ -1879,6 +2207,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1879
2207
  !(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
1880
2208
  !(handsEnabled && isRemoteExecutionEnv(executionEnv) && spec.memoryPersistenceCapable !== true),
1881
2209
  memoryProvenance,
2210
+ memoryCapturePolicy,
2211
+ memoryCapturePolicyDeclared: deps.memoryCapturePolicy !== undefined,
2212
+ captureEntitlement: {
2213
+ value: typeof runtimeCaps?.allowMemoryOptOut === "boolean" ? runtimeCaps.allowMemoryOptOut : undefined,
2214
+ faulted: runtimeCapsFaulted || (runtimeCaps?.allowMemoryOptOut !== undefined && typeof runtimeCaps.allowMemoryOptOut !== "boolean"),
2215
+ },
2216
+ captureCarrierUnsupported: isRemoteExecutionEnv(executionEnv) && deps.memoryCaptureRecordStore === undefined,
2217
+ ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
2218
+ captureFloor: internals?.memoryCaptureFloor === true,
2219
+ ...(internals?.memoryCaptureFloorIndeterminate === true ? { captureFloorIndeterminate: true } : {}),
2220
+ ...(captureLineage.origin !== undefined ? { captureForkOrigin: captureLineage.origin } : {}),
2221
+ ...(captureLineage.fault ? { captureLineageFault: true } : {}),
2222
+ ...(internals?.memoryCaptureQueryDir !== undefined ? { parentCaptureQueryDir: internals.memoryCaptureQueryDir } : {}),
2223
+ ...(internals?.memoryCaptureAncestors !== undefined ? { captureAncestors: internals.memoryCaptureAncestors } : {}),
1882
2224
  memoryPersistenceDeclared: spec.memoryPersistenceCapable,
1883
2225
  rosterCanPersist: spec.memoryPersistenceCapable ??
1884
2226
  tools.some((t) => {
@@ -1937,6 +2279,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1937
2279
  }
1938
2280
  }
1939
2281
  let instructionSources;
2282
+ let projectInstructionContent;
1940
2283
  if (deps.loadProjectMemory) {
1941
2284
  let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
1942
2285
  if (spec.sessionId) {
@@ -1988,6 +2331,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1988
2331
  }
1989
2332
  }
1990
2333
  if (projectMem != null && projectMem.trim()) {
2334
+ projectInstructionContent = projectMem;
1991
2335
  const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
1992
2336
  memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
1993
2337
  }
@@ -2052,7 +2396,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2052
2396
  (memoryPairExcludedName !== undefined
2053
2397
  ? `excludeTools removes "${memoryPairExcludedName}"`
2054
2398
  : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
2055
- " — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2399
+ " — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2056
2400
  }
2057
2401
  const skillsListing = skillSpecs.length > 0
2058
2402
  ? {
@@ -2251,7 +2595,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2251
2595
  return deferredSet;
2252
2596
  };
2253
2597
  const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
2254
- const memoryEnginePair = memoryEnginePairMounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
2598
+ const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
2255
2599
  const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
2256
2600
  let deferred;
2257
2601
  if (builtinDeferPairNames.length > 0) {
@@ -2261,8 +2605,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2261
2605
  const soleCause = d0.size === 0 && d1.size > 0;
2262
2606
  const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
2263
2607
  if (soleCause && supportNameTaken) {
2608
+ const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
2264
2609
  for (let i = tools.length - 1; i >= 0; i--) {
2265
- if (builtinDeferPairNames.some((n) => n === tools[i].name))
2610
+ if (retractNames.has(tools[i].name))
2266
2611
  tools.splice(i, 1);
2267
2612
  }
2268
2613
  if (sharedMemoryPair.length > 0) {
@@ -2272,8 +2617,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2272
2617
  }
2273
2618
  if (memoryEnginePair.length > 0) {
2274
2619
  memoryEnginePairMounted = false;
2275
- if (memoryBlock !== undefined) {
2276
- const segment = memoryRecallDisciplineSegment(memoryProvenance);
2620
+ if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
2621
+ const segment = memoryRecallSegment;
2277
2622
  if (memoryBlock === segment)
2278
2623
  memoryBlock = undefined;
2279
2624
  else if (memoryBlock.includes(`\n\n${segment}`))
@@ -2282,7 +2627,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2282
2627
  memoryBlock = memoryBlock.slice(segment.length + 2);
2283
2628
  }
2284
2629
  deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
2285
- `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2630
+ `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — these tools mount together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2286
2631
  }
2287
2632
  deferred = d0;
2288
2633
  }
@@ -2307,6 +2652,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2307
2652
  statuses: mcp?.statuses,
2308
2653
  execIsExternalContent,
2309
2654
  sessionId,
2655
+ runId,
2310
2656
  onNotice: deps.onNotice,
2311
2657
  });
2312
2658
  if (memoryEngineSession?.settlement !== undefined) {
@@ -2470,6 +2816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2470
2816
  reason: waivedReason,
2471
2817
  ...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
2472
2818
  sessionId,
2819
+ runId,
2473
2820
  },
2474
2821
  });
2475
2822
  }
@@ -2930,7 +3277,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2930
3277
  emitTrace(deps.tracer, () => ({
2931
3278
  kind: "loop.recovery",
2932
3279
  version: 1,
2933
- taskId: spec.taskId ?? sessionId,
3280
+ taskId: hostTaskId,
2934
3281
  reason: step.reason,
2935
3282
  ts: Date.now(),
2936
3283
  }));
@@ -3161,7 +3508,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3161
3508
  emitTrace(deps.tracer, () => ({
3162
3509
  kind: "permission.sandbox_admitted",
3163
3510
  version: 1,
3164
- taskId: spec.taskId ?? sessionId,
3511
+ taskId: hostTaskId,
3165
3512
  toolCallId: info.toolCallId,
3166
3513
  toolName: info.toolName,
3167
3514
  askClasses: info.askClasses.map((a) => a.cls),
@@ -3247,7 +3594,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3247
3594
  emitTrace(deps.tracer, () => ({
3248
3595
  kind: "permission.rule_store_unreadable",
3249
3596
  version: 1,
3250
- taskId: spec.taskId ?? sessionId,
3597
+ taskId: hostTaskId,
3251
3598
  message: err instanceof Error ? err.message : String(err),
3252
3599
  ts: Date.now(),
3253
3600
  }));
@@ -3292,26 +3639,36 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3292
3639
  return {};
3293
3640
  if ((spec.principal === undefined || spec.principal === "") && deps.localOwnerRules !== true)
3294
3641
  return {};
3295
- if (ask?.requiresRealApproval === true ||
3296
- ask?.persistedRuleShadowed !== undefined ||
3297
- ask?.decisionReason === "hook" ||
3298
- ask?.matchedAskRule !== undefined ||
3299
- ask?.inheritedUnresolved === true ||
3300
- ask?.ancestorResolved === true) {
3301
- return {};
3302
- }
3642
+ const closedDoor = (() => {
3643
+ if (ask?.requiresRealApproval === true)
3644
+ return "mandated";
3645
+ if (ask?.persistedRuleShadowed !== undefined)
3646
+ return "shadowed";
3647
+ if (ask?.decisionReason === "hook")
3648
+ return "mandated";
3649
+ if (ask?.matchedAskRule !== undefined)
3650
+ return "shadowed";
3651
+ if (ask?.inheritedUnresolved === true)
3652
+ return "mandated";
3653
+ if (ask?.ancestorResolved === true)
3654
+ return "mandated";
3655
+ return undefined;
3656
+ })();
3657
+ if (closedDoor !== undefined)
3658
+ return { ruleOffersAbsence: closedDoor };
3303
3659
  if (persistedRuleMandateOf({
3304
3660
  egress: egressTools.has(toolName),
3305
3661
  irreversibility: irreversibilityTier.get(toolName),
3306
3662
  shellGated: shellGatedBash,
3663
+ probeMandated: ask?.probeMandated === true,
3307
3664
  }) !== undefined) {
3308
- return {};
3665
+ return { ruleOffersAbsence: "mandated" };
3309
3666
  }
3310
3667
  const command = args?.command;
3311
3668
  if (typeof command !== "string")
3312
- return {};
3669
+ return { ruleOffersAbsence: "lane_cannot_speak" };
3313
3670
  const offers = suggestRulesForCommand(command, ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : undefined);
3314
- return offers.length > 0 ? { ruleOffers: offers } : {};
3671
+ return offers.length > 0 ? { ruleOffers: offers } : { ruleOffersAbsence: "lane_cannot_speak" };
3315
3672
  };
3316
3673
  const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
3317
3674
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
@@ -3748,7 +4105,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3748
4105
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
3749
4106
  const hookIdentity = mintHookInvocationIdentity({
3750
4107
  sessionId,
3751
- taskId: spec.taskId ?? sessionId,
4108
+ taskId: hostTaskId,
3752
4109
  legKind: wiringManifest.leg.kind,
3753
4110
  isDelegatedChild: delegation.isDelegatedChild,
3754
4111
  ...(internals?.insideFork === true ? { insideFork: true } : {}),
@@ -3757,6 +4114,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3757
4114
  });
3758
4115
  const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
3759
4116
  const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
4117
+ if (fileHistoryBoundary !== undefined) {
4118
+ harness.on("tool_call", async () => {
4119
+ await fileHistoryBoundary.settle();
4120
+ return undefined;
4121
+ });
4122
+ }
3760
4123
  if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
3761
4124
  const adjudicate = effectivePolicy
3762
4125
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, abortController.signal)), abortController.signal, () => ({
@@ -3884,7 +4247,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3884
4247
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
3885
4248
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
3886
4249
  const constraintChain = (inheritedParentConstraints?.length ?? 0) > 0
3887
- ? inheritedParentConstraints.map((pc) => constraintChainEntryOf(pc.policy, { ...(pc.autoMode !== undefined ? { autoModeArmed: true } : {}), ...(pc.durableMandate === true ? { durableMandate: true } : {}), ...(pc.contentMandate === true ? { contentMandate: true } : {}) }))
4250
+ ? inheritedParentConstraints.map((pc) => constraintChainEntryOfLayer(pc))
3888
4251
  : seedInheritedGate?.constraintChain;
3889
4252
  const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
3890
4253
  ? constraintChainDigest(constraintChain)
@@ -4282,7 +4645,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4282
4645
  }
4283
4646
  };
4284
4647
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4285
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
4648
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated) => {
4286
4649
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4287
4650
  if (syncFirstEligible &&
4288
4651
  runtimeCaps?.forceDurableGate !== true &&
@@ -4453,6 +4816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4453
4816
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
4454
4817
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
4455
4818
  ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
4819
+ ...(probeMandated === true ? { probeMandated: true } : {}),
4456
4820
  ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
4457
4821
  ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
4458
4822
  }),
@@ -4552,7 +4916,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4552
4916
  let humanBareRejection = false;
4553
4917
  try {
4554
4918
  result = await runToolGate({
4555
- onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: spec.taskId ?? sessionId, site: f.site, message: f.error.message, ts: Date.now() })),
4919
+ onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
4556
4920
  event: e,
4557
4921
  identity: hookIdentity,
4558
4922
  reminderMark,
@@ -4581,7 +4945,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4581
4945
  onResolved: (info) => emitTrace(deps.tracer, () => ({
4582
4946
  kind: "permission.persisted_rule_allowed",
4583
4947
  version: 2,
4584
- taskId: spec.taskId ?? sessionId,
4948
+ taskId: hostTaskId,
4585
4949
  toolName: info.toolName,
4586
4950
  toolCallId: info.toolCallId,
4587
4951
  rules: [...info.rules],
@@ -4598,7 +4962,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4598
4962
  onUnavailable: (info) => emitTrace(deps.tracer, () => ({
4599
4963
  kind: "permission.org_snapshot_unavailable",
4600
4964
  version: 1,
4601
- taskId: spec.taskId ?? sessionId,
4965
+ taskId: hostTaskId,
4602
4966
  toolName: info.toolName,
4603
4967
  toolCallId: info.toolCallId,
4604
4968
  message: info.message,
@@ -4720,7 +5084,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4720
5084
  }
4721
5085
  const editAt = editBudget(model);
4722
5086
  const guardAt = guardBudget(model);
4723
- const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
5087
+ const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
4724
5088
  const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
4725
5089
  harness.on("context", async ({ messages, recheck, signal }) => {
4726
5090
  batchHaltRef.current = undefined;
@@ -4735,7 +5099,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4735
5099
  onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
4736
5100
  kind: "tool_result.capped",
4737
5101
  version: 1,
4738
- taskId: spec.taskId ?? sessionId,
5102
+ taskId: hostTaskId,
4739
5103
  ...(info.tool !== undefined ? { tool: info.tool } : {}),
4740
5104
  sizeChars: info.sizeChars,
4741
5105
  storeFallback: info.storeFallback,
@@ -4772,11 +5136,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4772
5136
  : {}),
4773
5137
  });
4774
5138
  if (ccPass !== undefined) {
4775
- recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId, trigger: "frontier" });
5139
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
4776
5140
  }
4777
5141
  const chain = await runGuardChain({
4778
5142
  edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
4779
- sessionId, taskId: spec.taskId ?? sessionId, deps, trimPressureRef, recheck, signal,
5143
+ sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
4780
5144
  pendingProjectionObservations,
4781
5145
  });
4782
5146
  if ("earlyReturn" in chain) {
@@ -5011,7 +5375,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5011
5375
  const effectiveReadFaceObserved = carrierReadFace();
5012
5376
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
5013
5377
  const preparedHolder = {};
5014
- const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
5378
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
5015
5379
  const prepared = buildPrepared();
5016
5380
  preparedHolder.current = prepared;
5017
5381
  return prepared;