@sema-agent/core 5.65.0 → 7.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 (165) hide show
  1. package/CHANGELOG.md +58 -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 +13 -3
  33. package/dist/core/governance-codes.js +15 -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 +62 -2
  65. package/dist/core/permission-rule-consent.js +105 -14
  66. package/dist/core/permission-rule-model.d.ts +118 -16
  67. package/dist/core/permission-rule-model.js +57 -9
  68. package/dist/core/permission-rule-session.d.ts +124 -0
  69. package/dist/core/permission-rule-session.js +121 -0
  70. package/dist/core/permission-rule-store.d.ts +65 -2
  71. package/dist/core/permission-rule-store.js +75 -16
  72. package/dist/core/permission-rule-sync.d.ts +9 -0
  73. package/dist/core/permission-rule-sync.js +52 -19
  74. package/dist/core/retention-policy.d.ts +9 -0
  75. package/dist/core/retention-policy.js +5 -2
  76. package/dist/core/retention.d.ts +13 -2
  77. package/dist/core/runner/assemble-result.d.ts +19 -1
  78. package/dist/core/runner/assemble-result.js +17 -2
  79. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  80. package/dist/core/runner/compaction-call-options.js +3 -0
  81. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  82. package/dist/core/runner/memory-capture-optout.js +53 -0
  83. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  84. package/dist/core/runner/prepare-config-doors.js +16 -0
  85. package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
  86. package/dist/core/runner/prepare-hands-readface.js +99 -7
  87. package/dist/core/runner/prepare-memory.d.ts +88 -0
  88. package/dist/core/runner/prepare-memory.js +305 -24
  89. package/dist/core/runner/prepare-task.d.ts +141 -1
  90. package/dist/core/runner/prepare-task.js +478 -81
  91. package/dist/core/runner/runtask.d.ts +9 -20
  92. package/dist/core/runner/runtask.js +133 -96
  93. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  94. package/dist/core/runner/session-file-state-replay.js +52 -1
  95. package/dist/core/runner/tool-disclosure.js +2 -1
  96. package/dist/core/runner/turn-attachments.d.ts +22 -12
  97. package/dist/core/session-store.d.ts +1 -1
  98. package/dist/core/session-store.js +6 -1
  99. package/dist/core/session.d.ts +34 -1
  100. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  101. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  102. package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
  103. package/dist/core/task-registry-shared.js +11 -1
  104. package/dist/core/tool-errors.js +1 -0
  105. package/dist/core/tool-policy.d.ts +172 -1
  106. package/dist/core/tool-policy.js +32 -1
  107. package/dist/core/tool-result-store.js +2 -1
  108. package/dist/core/trace.d.ts +31 -2
  109. package/dist/core/types.d.ts +795 -89
  110. package/dist/core/types.js +4 -3
  111. package/dist/core/untrusted-text.d.ts +1 -1
  112. package/dist/core/untrusted-text.js +8 -0
  113. package/dist/core/workflow-run-store-contract.js +6 -2
  114. package/dist/core/workflow-run-store.d.ts +4 -1
  115. package/dist/engine/compaction/compaction.d.ts +88 -10
  116. package/dist/engine/compaction/compaction.js +109 -30
  117. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  118. package/dist/engine/execution-env/node-execution-env.js +28 -0
  119. package/dist/engine/harness/agent-harness.d.ts +52 -1
  120. package/dist/engine/harness/agent-harness.js +36 -1
  121. package/dist/engine/harness/types.d.ts +26 -1
  122. package/dist/engine/llm/types.d.ts +50 -4
  123. package/dist/engine/loop/agent-loop.d.ts +5 -1
  124. package/dist/engine/loop/agent-loop.js +25 -0
  125. package/dist/engine/loop/types.d.ts +19 -0
  126. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  127. package/dist/engine/session/session.js +1 -1
  128. package/dist/index.d.ts +20 -9
  129. package/dist/index.js +16 -7
  130. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  131. package/dist/orchestration/run-workflow-tool.js +22 -3
  132. package/dist/orchestration/workflow-governance.d.ts +59 -1
  133. package/dist/orchestration/workflow-governance.js +61 -8
  134. package/dist/orchestration/workflow-meta.d.ts +4 -2
  135. package/dist/orchestration/workflow-primitives.js +56 -13
  136. package/dist/orchestration/workflow-types.d.ts +78 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +163 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +7 -7
  141. package/dist/stores/file/file-history-store.d.ts +368 -0
  142. package/dist/stores/file/file-history-store.js +1248 -0
  143. package/dist/stores/file/index.d.ts +22 -13
  144. package/dist/stores/file/index.js +4 -4
  145. package/dist/stores/file/permission-rule-store.d.ts +11 -0
  146. package/dist/stores/file/permission-rule-store.js +23 -9
  147. package/dist/stores/file/strategy-store.d.ts +3 -3
  148. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  149. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  150. package/dist/tools/fs/fs-bash.js +9 -5
  151. package/dist/tools/fs/fs-shared.d.ts +52 -1
  152. package/dist/tools/fs/fs-shared.js +14 -0
  153. package/dist/tools/fs/fs-write.d.ts +5 -5
  154. package/dist/tools/fs/fs-write.js +71 -14
  155. package/dist/tools/fs/index.d.ts +6 -1
  156. package/dist/tools/fs/index.js +1 -1
  157. package/dist/tools/web.js +2 -1
  158. package/package.json +5 -1
  159. package/test/export-surface.snapshot.json +169 -23
  160. package/dist/core/file-snapshot-store.d.ts +0 -165
  161. package/dist/core/file-snapshot-store.js +0 -259
  162. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  163. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  164. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  165. 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,9 +21,10 @@ 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";
27
+ import { normalizePersistedRule } from "../permission-rule-store.js";
26
28
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
27
29
  import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
28
30
  import { inlineUntrusted } from "../untrusted-text.js";
@@ -49,7 +51,6 @@ import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDisco
49
51
  import { createSharedMemoryTools } from "../shared-memory/tools.js";
50
52
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
51
53
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
52
- import { memoryRecallDisciplineSegment } from "../memory-engine/engine.js";
53
54
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
54
55
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
55
56
  import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
@@ -97,6 +98,7 @@ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDec
97
98
  import { durableParkGapFor } from "../park-selfcheck.js";
98
99
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
99
100
  import { deliverEngineNotice } from "../types.js";
101
+ import { resolveTrackKey } from "../file-history-store.js";
100
102
  let announcedMaterializeEnvBySink = new WeakMap();
101
103
  const announcedMaterializeEnvConsole = new Set();
102
104
  function materializeEnvLedger(onNotice) {
@@ -218,6 +220,7 @@ function announceDeclaredMcpContentClasses(args) {
218
220
  toolCount,
219
221
  ...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
220
222
  sessionId: args.sessionId,
223
+ runId: args.runId,
221
224
  },
222
225
  });
223
226
  }
@@ -324,7 +327,7 @@ class ParkRefusal extends Error {
324
327
  export { resolveCheckpointStore } from "../checkpoint-store.js";
325
328
  export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
326
329
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
327
- function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
330
+ function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
328
331
  const triggerWindow = resolveTriggerWindow(model);
329
332
  if (triggerWindow.clamped) {
330
333
  deliverEngineNotice(deps.onNotice, {
@@ -337,6 +340,7 @@ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
337
340
  declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
338
341
  physicalWindow: triggerWindow.physicalWindow,
339
342
  sessionId,
343
+ runId,
340
344
  },
341
345
  });
342
346
  }
@@ -433,6 +437,187 @@ export async function runGuardChain(args) {
433
437
  const t = applyGuardTrim(working, anchoredWorking);
434
438
  return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
435
439
  }
440
+ function restoredAbsPathsOf(restored, root) {
441
+ const out = [];
442
+ for (const key of [...restored.filesChanged, ...restored.failed.map((f) => f.path)]) {
443
+ const abs = resolveTrackKey(root, key);
444
+ if (abs.ok)
445
+ out.push(abs.abs);
446
+ }
447
+ return out;
448
+ }
449
+ function resolveWiredFileHistoryStore(candidate) {
450
+ if (candidate === undefined)
451
+ return undefined;
452
+ const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
453
+ const verbOf = (v) => {
454
+ try {
455
+ return candidate[v];
456
+ }
457
+ catch {
458
+ return undefined;
459
+ }
460
+ };
461
+ const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
462
+ const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
463
+ if (missing.length > 0) {
464
+ const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
465
+ 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.`);
466
+ e.code = "config.invalid_file_history_store";
467
+ throw e;
468
+ }
469
+ return candidate;
470
+ }
471
+ const LEGACY_PROBE_TIMEOUT_MS = 2_000;
472
+ const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
473
+ async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
474
+ const probe = deps.legacyRewindBoundaryProbe;
475
+ let verdict;
476
+ if (probe !== undefined) {
477
+ try {
478
+ let timer;
479
+ const timeout = new Promise((resolve) => {
480
+ timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
481
+ });
482
+ try {
483
+ verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
484
+ }
485
+ finally {
486
+ clearTimeout(timer);
487
+ }
488
+ if (verdict === LEGACY_PROBE_TIMED_OUT) {
489
+ verdict = undefined;
490
+ try {
491
+ 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 });
492
+ }
493
+ catch {
494
+ }
495
+ }
496
+ }
497
+ catch (err) {
498
+ verdict = undefined;
499
+ try {
500
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
501
+ }
502
+ catch {
503
+ }
504
+ }
505
+ if (verdict !== undefined && typeof verdict !== "boolean") {
506
+ try {
507
+ 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 });
508
+ }
509
+ catch {
510
+ }
511
+ verdict = undefined;
512
+ }
513
+ }
514
+ if (verdict === true) {
515
+ 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.`;
516
+ }
517
+ if (verdict === false) {
518
+ 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.`;
519
+ }
520
+ return form === "keyed"
521
+ ? `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.`
522
+ : `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.`;
523
+ }
524
+ const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
525
+ const FILE_HISTORY_CARRY_MEMO = new WeakMap();
526
+ async function adoptForkedFileHistory(store, session, sessionId, onError) {
527
+ const memo = FILE_HISTORY_CARRY_MEMO.get(store) ?? { settled: new Set(), disclosed: new Set(), inFlight: new Map() };
528
+ FILE_HISTORY_CARRY_MEMO.set(store, memo);
529
+ const discloseOnce = (err) => {
530
+ if (memo.disclosed.has(sessionId))
531
+ return;
532
+ memo.disclosed.add(sessionId);
533
+ onError?.(err, { phase: "rewind", sessionId });
534
+ };
535
+ try {
536
+ if (memo.settled.has(sessionId))
537
+ return;
538
+ const forkedFrom = (await session.getMetadata()).forkedFrom;
539
+ if (forkedFrom === undefined || forkedFrom === sessionId)
540
+ return;
541
+ const shared = memo.inFlight.get(sessionId);
542
+ if (shared !== undefined)
543
+ return await shared;
544
+ if (memo.settled.has(sessionId))
545
+ return;
546
+ const attempt = store.adoptScope(forkedFrom, sessionId).then((carried) => {
547
+ if (carried.ok || carried.error.code === "conflict") {
548
+ memo.settled.add(sessionId);
549
+ return;
550
+ }
551
+ 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)`));
552
+ }, (err) => discloseOnce(err));
553
+ memo.inFlight.set(sessionId, attempt);
554
+ try {
555
+ await attempt;
556
+ }
557
+ finally {
558
+ memo.inFlight.delete(sessionId);
559
+ }
560
+ }
561
+ catch (err) {
562
+ try {
563
+ discloseOnce(err);
564
+ }
565
+ catch {
566
+ }
567
+ }
568
+ }
569
+ function createFileHistoryBoundarySeat(opts) {
570
+ let inFlight;
571
+ let begun = false;
572
+ return {
573
+ begin(entryId) {
574
+ if (begun)
575
+ return;
576
+ begun = true;
577
+ const ac = new AbortController();
578
+ let timer;
579
+ const timeout = new Promise((resolve) => {
580
+ timer = setTimeout(() => {
581
+ ac.abort();
582
+ resolve({ ok: false });
583
+ }, FILE_HISTORY_BOUNDARY_TIMEOUT_MS);
584
+ });
585
+ let work;
586
+ try {
587
+ work = opts.store.snapshot(opts.sessionId, entryId, opts.env, opts.root, ac.signal);
588
+ }
589
+ catch (err) {
590
+ work = Promise.resolve({ ok: false, error: { code: "snapshot_failed", message: `history store threw synchronously: ${err.message}` } });
591
+ }
592
+ work.catch(() => { });
593
+ inFlight = Promise.race([
594
+ work.then((r) => (r.ok ? undefined : `boundary capture failed (${r.error.code}): ${r.error.message}`)),
595
+ timeout.then(() => `boundary capture timed out after ${FILE_HISTORY_BOUNDARY_TIMEOUT_MS / 1000}s (fenced — the attempt can never publish)`),
596
+ ])
597
+ .then((failure) => {
598
+ if (failure !== undefined) {
599
+ try {
600
+ 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 });
601
+ }
602
+ catch {
603
+ }
604
+ }
605
+ })
606
+ .catch((err) => {
607
+ try {
608
+ opts.onError?.(err, { phase: "rewind", sessionId: opts.sessionId });
609
+ }
610
+ catch {
611
+ }
612
+ })
613
+ .finally(() => clearTimeout(timer));
614
+ },
615
+ async settle() {
616
+ if (inFlight !== undefined)
617
+ await inFlight;
618
+ },
619
+ };
620
+ }
436
621
  export function gatedCallIdOf(p) {
437
622
  if (p.suspendRef.token !== undefined)
438
623
  return p.suspendRef.gatedCallId;
@@ -508,7 +693,7 @@ function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
508
693
  }
509
694
  };
510
695
  }
511
- function mcpRevocationWiring(deps) {
696
+ function mcpRevocationWiring(deps, runId) {
512
697
  if (deps.mcpRevocations === undefined)
513
698
  return undefined;
514
699
  const ledger = deps.mcpRevocations;
@@ -517,7 +702,7 @@ function mcpRevocationWiring(deps) {
517
702
  onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
518
703
  code: "mcp.revocation_probe_failed",
519
704
  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) },
705
+ detail: { message: e instanceof Error ? e.message : String(e), runId },
521
706
  }),
522
707
  };
523
708
  }
@@ -674,7 +859,7 @@ async function derivedRouteFallsBack(args) {
674
859
  const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
675
860
  if (verdict === undefined || verdict.ok)
676
861
  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 } : {}) }));
862
+ 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
863
  return true;
679
864
  }
680
865
  catch {
@@ -703,6 +888,43 @@ export function placementValueOrAbsent(value) {
703
888
  function stampPlacementRootSessionId(placementRootResolved) {
704
889
  return placementValueOrAbsent(placementRootResolved);
705
890
  }
891
+ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
892
+ return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
893
+ }
894
+ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
895
+ return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
896
+ }
897
+ async function spliceSessionOverlayRows(overlay, sessionId, persisted, tracer, hostTaskId) {
898
+ if (overlay === undefined)
899
+ return persisted;
900
+ try {
901
+ const served = structuredClone(await overlay.read(sessionId));
902
+ const sessionRows = served.filter((r) => {
903
+ const scope = r?.scope;
904
+ return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
905
+ });
906
+ if (sessionRows.length < served.length) {
907
+ emitTrace(tracer, () => ({
908
+ kind: "permission.rule_store_unreadable",
909
+ version: 1,
910
+ taskId: hostTaskId,
911
+ message: `session-rule overlay served ${served.length - sessionRows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`,
912
+ ts: Date.now(),
913
+ }));
914
+ }
915
+ return sessionRows.length > 0 ? [...sessionRows, ...persisted] : persisted;
916
+ }
917
+ catch (err) {
918
+ emitTrace(tracer, () => ({
919
+ kind: "permission.rule_store_unreadable",
920
+ version: 1,
921
+ taskId: hostTaskId,
922
+ message: `session-rule overlay: ${err instanceof Error ? err.message : String(err)}`,
923
+ ts: Date.now(),
924
+ }));
925
+ return persisted;
926
+ }
927
+ }
706
928
  function refuseRequireExistingWithoutSession(spec) {
707
929
  if (spec.requireExistingSession && !spec.sessionId) {
708
930
  const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
@@ -710,10 +932,13 @@ function refuseRequireExistingWithoutSession(spec) {
710
932
  throw e;
711
933
  }
712
934
  }
713
- export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
935
+ export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
936
+ const runId = uuidv7();
937
+ if (runIdSink !== undefined)
938
+ runIdSink.runId = runId;
714
939
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
715
940
  spec = doors.spec;
716
- const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
941
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
717
942
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
718
943
  const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
719
944
  let shellGatedBash = false;
@@ -727,7 +952,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
727
952
  let effectiveCompModel = compModel;
728
953
  if (spec.compactionModel === undefined &&
729
954
  compModel !== undefined &&
730
- (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId }))) {
955
+ (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId }))) {
731
956
  effectiveCompModel = undefined;
732
957
  }
733
958
  warnCompactionWindowHazard(deps.tracer, spec, model, effectiveCompModel, hostTaskId);
@@ -907,79 +1132,165 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
907
1132
  }
908
1133
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
909
1134
  const rewindNotes = [];
910
- const rewindCaptureRequested = spec.rewindFiles === true;
1135
+ const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
1136
+ if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
1137
+ 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.`);
1138
+ e.code = "config.invalid_on_track_failure";
1139
+ throw e;
1140
+ }
1141
+ const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1142
+ if (fileHistoryStore !== undefined) {
1143
+ await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1144
+ }
1145
+ const legacyRewindFiles = spec.rewindFiles;
1146
+ if (legacyRewindFiles === true) {
1147
+ if (spec.resumeAt !== undefined) {
1148
+ 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.)`);
1149
+ e.code = "rewind.rewind_files_retired";
1150
+ throw e;
1151
+ }
1152
+ try {
1153
+ 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 });
1154
+ }
1155
+ catch {
1156
+ }
1157
+ }
1158
+ if (spec.restoreFiles === true && spec.resumeAt === undefined) {
1159
+ 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.`);
1160
+ e.code = "rewind.invalid_spec";
1161
+ throw e;
1162
+ }
911
1163
  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.`);
1164
+ 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
1165
  e.code = "rewind.conflicting_targets";
914
1166
  throw e;
915
1167
  }
916
- let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
1168
+ let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
917
1169
  const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
918
- if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
1170
+ if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
919
1171
  rewindNotes.push({
920
1172
  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`,
1173
+ 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
1174
  });
923
1175
  }
924
- if (!deps.fileSnapshotStore) {
1176
+ const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1177
+ let historyRoot = taskRootFinal;
1178
+ if (fileHistoryEnabled) {
1179
+ const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
1180
+ if (canonRoot.ok)
1181
+ historyRoot = canonRoot.value;
1182
+ }
1183
+ if (fileHistoryStore === undefined) {
925
1184
  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`);
1185
+ 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
1186
  e.code = "rewind.store_unconfigured";
928
1187
  throw e;
929
1188
  }
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
1189
  }
937
1190
  else if (!handsEnabled) {
938
- if (rewindTarget !== undefined || rewindCaptureRequested) {
1191
+ if (rewindTarget !== undefined) {
939
1192
  rewindNotes.push({
940
1193
  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",
1194
+ message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no file history was recorded or restored",
942
1195
  });
943
1196
  }
944
1197
  }
945
- if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
1198
+ const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
1199
+ let restoredFilePaths = [];
1200
+ if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
1201
+ const historyStore = fileHistoryStore;
946
1202
  if (rewindBefore) {
947
1203
  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;
1204
+ if (await historyStore.canRestore(sessionId, rewindTarget)) {
1205
+ anchor = rewindTarget;
1206
+ }
1207
+ else {
1208
+ const walked = new Set();
1209
+ let cursor = resumeAtBeforeParentId;
1210
+ while (cursor !== null && !walked.has(cursor)) {
1211
+ walked.add(cursor);
1212
+ if (await historyStore.canRestore(sessionId, cursor)) {
1213
+ anchor = cursor;
1214
+ break;
1215
+ }
1216
+ cursor = (await session.getEntry(cursor))?.parentId ?? null;
955
1217
  }
956
- cursor = (await session.getEntry(cursor))?.parentId ?? null;
957
1218
  }
958
1219
  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)`);
1220
+ 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
1221
  e.code = "rewind_snapshot.unresolvable";
961
1222
  throw e;
962
1223
  }
963
1224
  rewindTarget = anchor;
964
1225
  }
965
- const restoreRoot = taskRootFinal;
1226
+ const restoreRoot = historyRoot;
966
1227
  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") {
1228
+ const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
1229
+ 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("; ")})` : ""}`;
1230
+ if (restored.outcome === "failed") {
1231
+ if (restored.error?.code === "not_found") {
970
1232
  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)`);
1233
+ ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
1234
+ : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
973
1235
  e.code = "rewind_snapshot.unresolvable";
974
1236
  throw e;
975
1237
  }
1238
+ const e = new Error(`rewind-files restore failed (${restored.error?.code ?? "restore_failed"}): ${restored.error?.message ?? "unknown"} — ledger: ${ledgerLine(restored)}`);
1239
+ e.code = "rewind.restore_failed";
1240
+ throw e;
1241
+ }
1242
+ if (restored.outcome === "partial") {
1243
+ if (spec.acceptPartialRestore === true) {
1244
+ rewindNotes.push({
1245
+ code: "restore_partial",
1246
+ 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).`,
1247
+ });
1248
+ }
976
1249
  else {
977
- const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
1250
+ 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
1251
  e.code = "rewind.restore_failed";
979
1252
  throw e;
980
1253
  }
981
1254
  }
1255
+ restoredFilePaths = restoredAbsPathsOf(restored, restoreRoot);
982
1256
  }
1257
+ const trackFileEdit = fileHistoryEnabled
1258
+ ? async (req) => {
1259
+ const r = await fileHistoryStore.trackEdit(sessionId, req.key, executionEnv, historyRoot, req.signal);
1260
+ if (r.ok) {
1261
+ if (!r.minted)
1262
+ return { ok: true };
1263
+ return {
1264
+ ok: true,
1265
+ annul: async (proof) => {
1266
+ const a = await fileHistoryStore.annulTrack(sessionId, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1267
+ if (a.ok)
1268
+ return;
1269
+ try {
1270
+ 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 });
1271
+ }
1272
+ catch {
1273
+ }
1274
+ },
1275
+ };
1276
+ }
1277
+ if (onTrackFailure === "proceed-unprotected") {
1278
+ try {
1279
+ 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 });
1280
+ }
1281
+ catch {
1282
+ }
1283
+ return { ok: true };
1284
+ }
1285
+ return {
1286
+ ok: false,
1287
+ 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.`,
1288
+ };
1289
+ }
1290
+ : undefined;
1291
+ const fileHistoryBoundary = fileHistoryEnabled
1292
+ ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1293
+ : undefined;
983
1294
  const harnessRef = {};
984
1295
  const skillScope = new ActiveSkillScope();
985
1296
  const forwardSink = internals?.onForwardEvent;
@@ -1121,7 +1432,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1121
1432
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1122
1433
  ...(durableMandate ? { durableMandate: true } : {}),
1123
1434
  ...(contentMandate ? { contentMandate: true } : {}),
1124
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1435
+ ...(autoModeDecider !== undefined
1436
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1437
+ : {}),
1125
1438
  },
1126
1439
  ]
1127
1440
  : [];
@@ -1135,7 +1448,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1135
1448
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1136
1449
  ...(durableMandate ? { durableMandate: true } : {}),
1137
1450
  ...(contentMandate ? { contentMandate: true } : {}),
1138
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1451
+ ...(autoModeDecider !== undefined
1452
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1453
+ : {}),
1139
1454
  },
1140
1455
  ]
1141
1456
  : []),
@@ -1159,6 +1474,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1159
1474
  spec.handsReadOnly !== true &&
1160
1475
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1161
1476
  let autoModeDecider;
1477
+ let autoModeArming;
1162
1478
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1163
1479
  const enrichSpecToolCtx = (ctx) => ({
1164
1480
  ...ctx,
@@ -1184,6 +1500,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1184
1500
  ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
1185
1501
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1186
1502
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1503
+ get memoryCaptureOptedOut() {
1504
+ return memoryEngineSession?.captureOptOut?.optedOut() === true;
1505
+ },
1506
+ get memoryCaptureIndeterminate() {
1507
+ return memoryEngineSession?.captureOptOut?.indeterminate() === true;
1508
+ },
1509
+ get memoryCaptureControlDir() {
1510
+ return memoryEngineSession?.engine.controlPlaneDir;
1511
+ },
1512
+ get memoryCaptureAncestors() {
1513
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1514
+ return [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }];
1515
+ },
1187
1516
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1188
1517
  parentCwd: taskRootFinal,
1189
1518
  reminderMark,
@@ -1262,6 +1591,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1262
1591
  }
1263
1592
  }
1264
1593
  let runtimeCaps;
1594
+ let runtimeCapsFaulted = false;
1265
1595
  if (deps.runtimeCapsResolver) {
1266
1596
  try {
1267
1597
  runtimeCaps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
@@ -1269,6 +1599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1269
1599
  catch (err) {
1270
1600
  deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1271
1601
  runtimeCaps = { allowWorkflows: false, allowFork: false };
1602
+ runtimeCapsFaulted = true;
1272
1603
  }
1273
1604
  }
1274
1605
  let complianceDenies = new Set();
@@ -1315,7 +1646,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1315
1646
  catch {
1316
1647
  classifierModel = model;
1317
1648
  }
1318
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId })) {
1649
+ if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
1319
1650
  classifierModel = model;
1320
1651
  }
1321
1652
  const classifierSystemPrompt = buildAutoModePrompt(am);
@@ -1340,6 +1671,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1340
1671
  .join("");
1341
1672
  },
1342
1673
  });
1674
+ if (am.persistArming === true) {
1675
+ autoModeArming = autoModeArmingRecipeOf(am, {
1676
+ promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
1677
+ });
1678
+ if (autoModeArming === undefined) {
1679
+ deps.onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
1680
+ "(a rule list carrying a non-string, or a non-finite/out-of-range timeoutMs / failureThreshold / window bound) — " +
1681
+ "NO arming recipe is recorded on this run's parked constraint chain, and a cross-process redemption keeps the " +
1682
+ "conservative behavior (the ancestor classifier answers unavailable and the inherited ask flows to a human)"), { phase: "config", sessionId });
1683
+ }
1684
+ }
1343
1685
  }
1344
1686
  const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
1345
1687
  const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
@@ -1387,6 +1729,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1387
1729
  principal: spec.principal,
1388
1730
  oneShot: spec.oneShot,
1389
1731
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1732
+ parentMemoryCaptureState: () => {
1733
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1734
+ return {
1735
+ optedOut: memoryEngineSession?.captureOptOut?.optedOut() === true,
1736
+ indeterminate: memoryEngineSession?.captureOptOut?.indeterminate() === true,
1737
+ ...(ctl !== undefined ? { controlDir: ctl } : {}),
1738
+ ancestors: [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }],
1739
+ };
1740
+ },
1390
1741
  autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1391
1742
  workflowDepth: internals?.workflowDepth,
1392
1743
  parentCwd: taskRootFinal,
@@ -1465,7 +1816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1465
1816
  tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
1466
1817
  }
1467
1818
  mcp = lockedPreflight.mcp?.length
1468
- ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps))
1819
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
1469
1820
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1470
1821
  for (const w of mcp.warnings)
1471
1822
  deps.onError?.(w, { phase: "mcp", sessionId });
@@ -1621,9 +1972,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1621
1972
  foldProtocolAxes(a2a.toolAxes, "A2A");
1622
1973
  const callIssuedAtRef = {};
1623
1974
  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 };
1975
+ 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
1976
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
1626
- const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames } = handsReadFace;
1977
+ const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
1627
1978
  resolvedReadFace = handsReadFace.resolvedReadFace;
1628
1979
  readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
1629
1980
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
@@ -1868,7 +2219,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1868
2219
  const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
1869
2220
  const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
1870
2221
  const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
1871
- const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
2222
+ const captureLineage = await (async () => {
2223
+ try {
2224
+ const ff = (await session.getMetadata()).forkedFrom;
2225
+ return { ...(ff !== undefined && ff !== sessionId ? { origin: ff } : {}), fault: false };
2226
+ }
2227
+ catch {
2228
+ return { fault: true };
2229
+ }
2230
+ })();
2231
+ const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, recallDisciplineSegment: memoryRecallSegment, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
1872
2232
  spec,
1873
2233
  deps,
1874
2234
  sessionId,
@@ -1879,6 +2239,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1879
2239
  !(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
1880
2240
  !(handsEnabled && isRemoteExecutionEnv(executionEnv) && spec.memoryPersistenceCapable !== true),
1881
2241
  memoryProvenance,
2242
+ memoryCapturePolicy,
2243
+ memoryCapturePolicyDeclared: deps.memoryCapturePolicy !== undefined,
2244
+ captureEntitlement: {
2245
+ value: typeof runtimeCaps?.allowMemoryOptOut === "boolean" ? runtimeCaps.allowMemoryOptOut : undefined,
2246
+ faulted: runtimeCapsFaulted || (runtimeCaps?.allowMemoryOptOut !== undefined && typeof runtimeCaps.allowMemoryOptOut !== "boolean"),
2247
+ },
2248
+ captureCarrierUnsupported: isRemoteExecutionEnv(executionEnv) && deps.memoryCaptureRecordStore === undefined,
2249
+ ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
2250
+ captureFloor: internals?.memoryCaptureFloor === true,
2251
+ ...(internals?.memoryCaptureFloorIndeterminate === true ? { captureFloorIndeterminate: true } : {}),
2252
+ ...(captureLineage.origin !== undefined ? { captureForkOrigin: captureLineage.origin } : {}),
2253
+ ...(captureLineage.fault ? { captureLineageFault: true } : {}),
2254
+ ...(internals?.memoryCaptureQueryDir !== undefined ? { parentCaptureQueryDir: internals.memoryCaptureQueryDir } : {}),
2255
+ ...(internals?.memoryCaptureAncestors !== undefined ? { captureAncestors: internals.memoryCaptureAncestors } : {}),
1882
2256
  memoryPersistenceDeclared: spec.memoryPersistenceCapable,
1883
2257
  rosterCanPersist: spec.memoryPersistenceCapable ??
1884
2258
  tools.some((t) => {
@@ -1937,6 +2311,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1937
2311
  }
1938
2312
  }
1939
2313
  let instructionSources;
2314
+ let projectInstructionContent;
1940
2315
  if (deps.loadProjectMemory) {
1941
2316
  let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
1942
2317
  if (spec.sessionId) {
@@ -1988,6 +2363,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1988
2363
  }
1989
2364
  }
1990
2365
  if (projectMem != null && projectMem.trim()) {
2366
+ projectInstructionContent = projectMem;
1991
2367
  const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
1992
2368
  memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
1993
2369
  }
@@ -2052,7 +2428,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2052
2428
  (memoryPairExcludedName !== undefined
2053
2429
  ? `excludeTools removes "${memoryPairExcludedName}"`
2054
2430
  : `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" });
2431
+ " — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2056
2432
  }
2057
2433
  const skillsListing = skillSpecs.length > 0
2058
2434
  ? {
@@ -2251,7 +2627,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2251
2627
  return deferredSet;
2252
2628
  };
2253
2629
  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)) : [];
2630
+ const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
2255
2631
  const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
2256
2632
  let deferred;
2257
2633
  if (builtinDeferPairNames.length > 0) {
@@ -2261,8 +2637,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2261
2637
  const soleCause = d0.size === 0 && d1.size > 0;
2262
2638
  const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
2263
2639
  if (soleCause && supportNameTaken) {
2640
+ const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
2264
2641
  for (let i = tools.length - 1; i >= 0; i--) {
2265
- if (builtinDeferPairNames.some((n) => n === tools[i].name))
2642
+ if (retractNames.has(tools[i].name))
2266
2643
  tools.splice(i, 1);
2267
2644
  }
2268
2645
  if (sharedMemoryPair.length > 0) {
@@ -2272,8 +2649,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2272
2649
  }
2273
2650
  if (memoryEnginePair.length > 0) {
2274
2651
  memoryEnginePairMounted = false;
2275
- if (memoryBlock !== undefined) {
2276
- const segment = memoryRecallDisciplineSegment(memoryProvenance);
2652
+ if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
2653
+ const segment = memoryRecallSegment;
2277
2654
  if (memoryBlock === segment)
2278
2655
  memoryBlock = undefined;
2279
2656
  else if (memoryBlock.includes(`\n\n${segment}`))
@@ -2282,7 +2659,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2282
2659
  memoryBlock = memoryBlock.slice(segment.length + 2);
2283
2660
  }
2284
2661
  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" });
2662
+ `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
2663
  }
2287
2664
  deferred = d0;
2288
2665
  }
@@ -2307,6 +2684,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2307
2684
  statuses: mcp?.statuses,
2308
2685
  execIsExternalContent,
2309
2686
  sessionId,
2687
+ runId,
2310
2688
  onNotice: deps.onNotice,
2311
2689
  });
2312
2690
  if (memoryEngineSession?.settlement !== undefined) {
@@ -2470,6 +2848,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2470
2848
  reason: waivedReason,
2471
2849
  ...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
2472
2850
  sessionId,
2851
+ runId,
2473
2852
  },
2474
2853
  });
2475
2854
  }
@@ -2930,7 +3309,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2930
3309
  emitTrace(deps.tracer, () => ({
2931
3310
  kind: "loop.recovery",
2932
3311
  version: 1,
2933
- taskId: spec.taskId ?? sessionId,
3312
+ taskId: hostTaskId,
2934
3313
  reason: step.reason,
2935
3314
  ts: Date.now(),
2936
3315
  }));
@@ -3161,7 +3540,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3161
3540
  emitTrace(deps.tracer, () => ({
3162
3541
  kind: "permission.sandbox_admitted",
3163
3542
  version: 1,
3164
- taskId: spec.taskId ?? sessionId,
3543
+ taskId: hostTaskId,
3165
3544
  toolCallId: info.toolCallId,
3166
3545
  toolName: info.toolName,
3167
3546
  askClasses: info.askClasses.map((a) => a.cls),
@@ -3247,16 +3626,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3247
3626
  emitTrace(deps.tracer, () => ({
3248
3627
  kind: "permission.rule_store_unreadable",
3249
3628
  version: 1,
3250
- taskId: spec.taskId ?? sessionId,
3629
+ taskId: hostTaskId,
3251
3630
  message: err instanceof Error ? err.message : String(err),
3252
3631
  ts: Date.now(),
3253
3632
  }));
3254
3633
  return { unreadable: true };
3255
3634
  }
3256
- const admitting = findAdmittingRule(listed.rules, { tool: req.toolName, command, cwd: root });
3635
+ const table = await spliceSessionOverlayRows(deps.sessionPermissionRules, sessionId, listed.rules, deps.tracer, hostTaskId);
3636
+ const admitting = findAdmittingRule(table, { tool: req.toolName, command, cwd: root, sessionId });
3257
3637
  if (admitting !== undefined)
3258
3638
  return persistedRuleHitOf(admitting);
3259
- const coverage = segmentCoverageOf(command, { persisted: listed.rules }, { tool: req.toolName, cwd: root });
3639
+ const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId });
3260
3640
  return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
3261
3641
  },
3262
3642
  };
@@ -3292,26 +3672,36 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3292
3672
  return {};
3293
3673
  if ((spec.principal === undefined || spec.principal === "") && deps.localOwnerRules !== true)
3294
3674
  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
- }
3675
+ const closedDoor = (() => {
3676
+ if (ask?.requiresRealApproval === true)
3677
+ return "mandated";
3678
+ if (ask?.persistedRuleShadowed !== undefined)
3679
+ return "shadowed";
3680
+ if (ask?.decisionReason === "hook")
3681
+ return "mandated";
3682
+ if (ask?.matchedAskRule !== undefined)
3683
+ return "shadowed";
3684
+ if (ask?.inheritedUnresolved === true)
3685
+ return "mandated";
3686
+ if (ask?.ancestorResolved === true)
3687
+ return "mandated";
3688
+ return undefined;
3689
+ })();
3690
+ if (closedDoor !== undefined)
3691
+ return { ruleOffersAbsence: closedDoor };
3303
3692
  if (persistedRuleMandateOf({
3304
3693
  egress: egressTools.has(toolName),
3305
3694
  irreversibility: irreversibilityTier.get(toolName),
3306
3695
  shellGated: shellGatedBash,
3696
+ probeMandated: ask?.probeMandated === true,
3307
3697
  }) !== undefined) {
3308
- return {};
3698
+ return { ruleOffersAbsence: "mandated" };
3309
3699
  }
3310
3700
  const command = args?.command;
3311
3701
  if (typeof command !== "string")
3312
- return {};
3702
+ return { ruleOffersAbsence: "lane_cannot_speak" };
3313
3703
  const offers = suggestRulesForCommand(command, ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : undefined);
3314
- return offers.length > 0 ? { ruleOffers: offers } : {};
3704
+ return offers.length > 0 ? { ruleOffers: offers } : { ruleOffersAbsence: "lane_cannot_speak" };
3315
3705
  };
3316
3706
  const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
3317
3707
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
@@ -3748,7 +4138,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3748
4138
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
3749
4139
  const hookIdentity = mintHookInvocationIdentity({
3750
4140
  sessionId,
3751
- taskId: spec.taskId ?? sessionId,
4141
+ taskId: hostTaskId,
3752
4142
  legKind: wiringManifest.leg.kind,
3753
4143
  isDelegatedChild: delegation.isDelegatedChild,
3754
4144
  ...(internals?.insideFork === true ? { insideFork: true } : {}),
@@ -3757,6 +4147,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3757
4147
  });
3758
4148
  const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
3759
4149
  const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
4150
+ if (fileHistoryBoundary !== undefined) {
4151
+ harness.on("tool_call", async () => {
4152
+ await fileHistoryBoundary.settle();
4153
+ return undefined;
4154
+ });
4155
+ }
3760
4156
  if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
3761
4157
  const adjudicate = effectivePolicy
3762
4158
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, abortController.signal)), abortController.signal, () => ({
@@ -3884,7 +4280,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3884
4280
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
3885
4281
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
3886
4282
  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 } : {}) }))
4283
+ ? inheritedParentConstraints.map((pc) => constraintChainEntryOfLayer(pc))
3888
4284
  : seedInheritedGate?.constraintChain;
3889
4285
  const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
3890
4286
  ? constraintChainDigest(constraintChain)
@@ -4282,7 +4678,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4282
4678
  }
4283
4679
  };
4284
4680
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4285
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
4681
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated) => {
4286
4682
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4287
4683
  if (syncFirstEligible &&
4288
4684
  runtimeCaps?.forceDurableGate !== true &&
@@ -4453,6 +4849,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4453
4849
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
4454
4850
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
4455
4851
  ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
4852
+ ...(probeMandated === true ? { probeMandated: true } : {}),
4456
4853
  ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
4457
4854
  ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
4458
4855
  }),
@@ -4552,7 +4949,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4552
4949
  let humanBareRejection = false;
4553
4950
  try {
4554
4951
  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() })),
4952
+ onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
4556
4953
  event: e,
4557
4954
  identity: hookIdentity,
4558
4955
  reminderMark,
@@ -4581,7 +4978,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4581
4978
  onResolved: (info) => emitTrace(deps.tracer, () => ({
4582
4979
  kind: "permission.persisted_rule_allowed",
4583
4980
  version: 2,
4584
- taskId: spec.taskId ?? sessionId,
4981
+ taskId: hostTaskId,
4585
4982
  toolName: info.toolName,
4586
4983
  toolCallId: info.toolCallId,
4587
4984
  rules: [...info.rules],
@@ -4598,7 +4995,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4598
4995
  onUnavailable: (info) => emitTrace(deps.tracer, () => ({
4599
4996
  kind: "permission.org_snapshot_unavailable",
4600
4997
  version: 1,
4601
- taskId: spec.taskId ?? sessionId,
4998
+ taskId: hostTaskId,
4602
4999
  toolName: info.toolName,
4603
5000
  toolCallId: info.toolCallId,
4604
5001
  message: info.message,
@@ -4720,7 +5117,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4720
5117
  }
4721
5118
  const editAt = editBudget(model);
4722
5119
  const guardAt = guardBudget(model);
4723
- const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
5120
+ const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
4724
5121
  const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
4725
5122
  harness.on("context", async ({ messages, recheck, signal }) => {
4726
5123
  batchHaltRef.current = undefined;
@@ -4735,7 +5132,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4735
5132
  onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
4736
5133
  kind: "tool_result.capped",
4737
5134
  version: 1,
4738
- taskId: spec.taskId ?? sessionId,
5135
+ taskId: hostTaskId,
4739
5136
  ...(info.tool !== undefined ? { tool: info.tool } : {}),
4740
5137
  sizeChars: info.sizeChars,
4741
5138
  storeFallback: info.storeFallback,
@@ -4772,11 +5169,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4772
5169
  : {}),
4773
5170
  });
4774
5171
  if (ccPass !== undefined) {
4775
- recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId, trigger: "frontier" });
5172
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
4776
5173
  }
4777
5174
  const chain = await runGuardChain({
4778
5175
  edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
4779
- sessionId, taskId: spec.taskId ?? sessionId, deps, trimPressureRef, recheck, signal,
5176
+ sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
4780
5177
  pendingProjectionObservations,
4781
5178
  });
4782
5179
  if ("earlyReturn" in chain) {
@@ -5011,7 +5408,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5011
5408
  const effectiveReadFaceObserved = carrierReadFace();
5012
5409
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
5013
5410
  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 } : {}) });
5411
+ 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
5412
  const prepared = buildPrepared();
5016
5413
  preparedHolder.current = prepared;
5017
5414
  return prepared;