@sema-agent/core 7.6.3 → 7.8.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 (164) hide show
  1. package/CHANGELOG.md +70 -10
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -0,0 +1,28 @@
1
+ import type { MaybeCompactOptions } from "../auto-compaction.js";
2
+ import type { Prepared } from "./contracts.js";
3
+ /** env-tail migration — the git frame's authority shell: the single-point F3 wrap the first-frame
4
+ * listings use (one `<system-reminder>` per body, body neutralized — idempotent over the already-
5
+ * sanitized snapshot; the S2 breakout posture). */
6
+ export declare function wrapGitFrame(body: string, mark: string | undefined): string;
7
+ /**
8
+ * env-tail migration — the git lane's leg-boundary decision: read ladder (BRANCH AUTHORITY) then
9
+ * the `(kind, hash)` compare. Ladder rungs:
10
+ * ① nearest mirror on the active session branch — a PENDING carrier (or an announced carrier
11
+ * whose frame entry is off the branch) is a MANDATORY re-announce gate, overriding everything;
12
+ * ② the checkpoint mirror, only when the branch carries no mirror at all — its own `entryId`
13
+ * must sit on the active branch to count as announced (a rewound frame under a descendant
14
+ * checkpoint reads as pending);
15
+ * ③ full absence ⇒ conservative re-announce (duplicate-tolerant by design).
16
+ * Seeds `ref.announced` with the resolved prior (pendingness preserved — a suspend before the
17
+ * receipt then mirrors the honest state) and returns the frame BODY to deliver, or undefined when
18
+ * the announced view already matches (or the lane has nothing to say this leg). Tombstone kinds
19
+ * fire on the availability FLIP EDGE only: with no prior view at all there is nothing to disown —
20
+ * no frame (the probe's own onError already told the deployment).
21
+ */
22
+ export declare function resolveGitLegDelivery(prepared: Prepared, cpMirror: unknown, report: (err: Error) => void): Promise<string | undefined>;
23
+ /** env-tail migration — the compaction interaction option for one `maybeCompact` invocation (ALL
24
+ * THREE routes — turn-boundary auto, prompt-too-long recovery, end-of-task finish — thread this
25
+ * same builder, the single-source requirement): the current tuple restated as PENDING on the
26
+ * compaction entry (same CAS as the new baseline) + the run's re-assert closure as the landing
27
+ * hook. Empty when the lane never announced anything (nothing to preserve across the summary). */
28
+ export declare function gitRestateOption(prepared: Prepared): Pick<MaybeCompactOptions, "gitRestate">;
@@ -0,0 +1,94 @@
1
+ import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
2
+ import { mintSystemReminder } from "../reminder-mint.js";
3
+ import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
4
+ import { branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame } from "./git-status-frame.js";
5
+ export function wrapGitFrame(body, mark) {
6
+ return mintSystemReminder(sanitizeUntrustedText(body, SHELLED_BODY_ENVELOPE_TAGS), mark);
7
+ }
8
+ export async function resolveGitLegDelivery(prepared, cpMirror, report) {
9
+ const ref = prepared.gitStatusRef;
10
+ const frame = ref.frame;
11
+ if (frame === undefined)
12
+ return undefined;
13
+ let prior;
14
+ let pending = false;
15
+ try {
16
+ const branchReadout = await prepared.session.getGitAnnouncement?.();
17
+ if (branchReadout !== undefined) {
18
+ if (branchReadout.status === "pending") {
19
+ pending = true;
20
+ prior = { kind: branchReadout.kind, hash: branchReadout.hash };
21
+ }
22
+ else {
23
+ prior = { kind: branchReadout.kind, hash: branchReadout.hash, ...(branchReadout.entryId !== undefined ? { entryId: branchReadout.entryId } : {}) };
24
+ }
25
+ }
26
+ else {
27
+ const shaped = normalizeGitAnnouncement(cpMirror);
28
+ if (shaped !== undefined) {
29
+ if (shaped.pending === true || shaped.entryId === undefined) {
30
+ pending = true;
31
+ prior = { kind: shaped.kind, hash: shaped.hash };
32
+ }
33
+ else if (gitFrameContextVisible(await prepared.session.getBranch(), shaped.entryId)) {
34
+ prior = { kind: shaped.kind, hash: shaped.hash, entryId: shaped.entryId };
35
+ }
36
+ else {
37
+ pending = true;
38
+ prior = { kind: shaped.kind, hash: shaped.hash };
39
+ }
40
+ }
41
+ }
42
+ }
43
+ catch (err) {
44
+ pending = true;
45
+ report(err instanceof Error ? err : new Error(String(err)));
46
+ }
47
+ if (prior !== undefined && !pending && prior.entryId !== undefined) {
48
+ try {
49
+ const newest = newestEngineGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
50
+ if (newest === undefined || newest.entryId !== prior.entryId)
51
+ pending = true;
52
+ }
53
+ catch (err) {
54
+ pending = true;
55
+ report(err instanceof Error ? err : new Error(String(err)));
56
+ }
57
+ }
58
+ if (prior !== undefined)
59
+ ref.announced = pending ? { ...prior, pending: true } : { ...prior };
60
+ const negative = frame.kind === "unavailable" || frame.kind === "non-repo";
61
+ if (negative) {
62
+ if (prior === undefined && !pending) {
63
+ let mustDisown;
64
+ try {
65
+ mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
66
+ }
67
+ catch (err) {
68
+ mustDisown = true;
69
+ report(err instanceof Error ? err : new Error(String(err)));
70
+ }
71
+ return mustDisown ? frame.body : undefined;
72
+ }
73
+ if (prior !== undefined && !pending && prior.kind === frame.kind && prior.hash === frame.hash)
74
+ return undefined;
75
+ return frame.body;
76
+ }
77
+ if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
78
+ ref.protectedText = wrapGitFrame(frame.body, prepared.reminderMark);
79
+ if (frame.kind === "full" && frame.shrunk !== undefined) {
80
+ ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
81
+ }
82
+ return undefined;
83
+ }
84
+ return frame.body;
85
+ }
86
+ export function gitRestateOption(prepared) {
87
+ const ref = prepared.gitStatusRef;
88
+ if (ref.frame === undefined || ref.announced === undefined || ref.reassert === undefined)
89
+ return {};
90
+ const use = ref.overBudgetShrunk && ref.frame.shrunk !== undefined
91
+ ? { kind: "degraded", hash: ref.frame.shrunk.hash }
92
+ : { kind: ref.frame.kind, hash: ref.frame.hash };
93
+ return { gitRestate: { pending: use, land: ref.reassert } };
94
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The zero-value constructor of {@link RunState}, the run loop's ONE shared mutable record (seven groups,
3
+ * sixty fields; its writers are the run loop's three top-level machines, see the interface's own doc in
4
+ * contracts.ts). The driver mints one per run leg and hands it to every machine as a `borrowed-mutable`
5
+ * seat; nothing here reads or writes host state, so the skeleton is machinery, not a phase.
6
+ *
7
+ * Named without the `run-` family prefix on purpose: that prefix marks a DRIVEN run lane (one the driver
8
+ * calls with an Input and reads a Result from — the phase-api gate judges every such file), and the
9
+ * layering registry's run-lanes stratum is a glob over the same prefix. A machinery module the lanes stand
10
+ * on sits one layer below them under a name of its own, exactly as the prepare-path machinery does.
11
+ */
12
+ import type { RunState } from "./contracts.js";
13
+ /** Zero-value skeleton — every REAL value is assigned at the field's original declaration site. */
14
+ export declare function createRunState(): RunState;
@@ -0,0 +1,11 @@
1
+ export function createRunState() {
2
+ return {
3
+ telemetry: { cacheFamily: "input-excludes-cached", pricing: { inputPer1M: 0, outputPer1M: 0 }, pricingConfigured: true, unpricedSpend: false, tracer: undefined, taskId: "", runId: "", taskStart: 0, taskStartMonotonic: 0, cacheBreakReported: false },
4
+ degrade: { degradeToModel: undefined, degraded: undefined, outputErrorStreak: 0, outputInvalid: false, recordDegraded: () => { } },
5
+ limits: { turnsExceeded: false, budgetHit: undefined, budgetAxis: undefined, platformTerminal: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
6
+ budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, remainingTokens: undefined, maxTokensWindow: undefined, overBudget: () => undefined, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => undefined },
7
+ turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
8
+ counters: { approachNoticesSent: 0, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
9
+ attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, sizeGuidelineState: undefined, attachmentsInjected: 0 },
10
+ };
11
+ }
@@ -46,7 +46,7 @@ export interface PrepareAskLaneInput {
46
46
  handsCwdRef: CwdRef | undefined;
47
47
  /** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
48
48
  * lookup over whatever the roster holds when the ask is minted). Never mutated here. */
49
- tools: AgentTool[];
49
+ tools: readonly AgentTool[];
50
50
  /** borrowed-mutable — the inherited-unavailable marker set. Writer here: `resolveAskBound` consumes (`delete`) a
51
51
  * marked call at its fail-closed intercept; the policy-chain phase records, the gate station sweeps. */
52
52
  inheritedUnavailableAsks: Set<string>;
@@ -34,6 +34,7 @@ import type { AutoModeArmReason } from "../wiring-manifest.js";
34
34
  import type { ReadFace } from "../../tools/fs/index.js";
35
35
  import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
36
36
  import { type BlockedRef } from "./synthetic-tools.js";
37
+ import { RosterBuilder } from "../tool-roster.js";
37
38
  export interface PrepareCapsAndWorkflowInput {
38
39
  /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
39
40
  * `enablePlanMode`, `interactiveTools`, `checkpointStore`; the caps/compliance stations read `principal`, `selfOrchestration`,
@@ -124,10 +125,10 @@ export interface PrepareCapsAndWorkflowInput {
124
125
  }>;
125
126
  }
126
127
  export interface PrepareCapsAndWorkflowResult {
127
- /** owned — THE run's roster, minted here from the caller's tools; every later mount pushes into this same array
128
- * (`Prepared.tools`, whose JSDoc carries the writer table). Writers here: the map that mints it, then the ReportBlocked,
129
- * ReportFindings, ExitPlanMode, EnterPlanMode and Workflow pushes. */
130
- tools: AgentTool[];
128
+ /** owned — THE run's roster BUILDER (design/388: the mount array is private to it; `Prepared.tools` is its read face,
129
+ * whose JSDoc carries the writer table). Minted here from the caller's tools; every later mount goes through its verbs.
130
+ * Writers here: the caller mounts, then the ReportBlocked, ReportFindings, ExitPlanMode, EnterPlanMode and Workflow mounts. */
131
+ roster: RosterBuilder;
131
132
  /** borrowed-mutable — the ReportBlocked tool's write seat (`Prepared.blockedRef`). Writer: the tool at run time; read at
132
133
  * result assembly. */
133
134
  blockedRef: BlockedRef;
@@ -2,10 +2,12 @@ import { isSyntheticApiErrorMessage } from "../../internal/harness.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { forkGovernanceDenial } from "../../agents/subagent.js";
4
4
  import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelope.js";
5
- import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestration/run-workflow-tool.js";
5
+ import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
6
6
  import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
7
7
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
8
+ import { thinkingOffExpressible } from "../../brain/reasoning.js";
8
9
  import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
10
+ import { AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "../auto-mode-defaults.js";
9
11
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
10
12
  import { createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
11
13
  import { resolveCheckpointStore } from "../checkpoint-store.js";
@@ -14,9 +16,11 @@ import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-
14
16
  import { resolveTaskModel } from "../roles.js";
15
17
  import { brainToRuntime } from "../runtime.js";
16
18
  import { defaultTaskRegistry } from "../task-registry.js";
19
+ import { emitTrace } from "../trace.js";
17
20
  import { defineTool, isDefineToolProduct } from "../tools.js";
18
21
  import { derivedRouteFallsBack } from "./derived-route-fallback.js";
19
22
  import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
23
+ import { RosterBuilder, callerMountSource } from "../tool-roster.js";
20
24
  function assembleParentCaptureState(o, i, ctl, ancestors) {
21
25
  const build = (optedOut, indeterminate) => ({
22
26
  optedOut: optedOut === true,
@@ -62,7 +66,12 @@ function autoModeArmReasonOf(intent, facePresent, capsAutoMode, capsFaulted) {
62
66
  }
63
67
  export async function prepareCapsAndWorkflow(input) {
64
68
  const { spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
65
- const tools = (spec.tools ?? []).map((t) => {
69
+ const roster = new RosterBuilder(toolEffects);
70
+ for (const t of spec.tools ?? []) {
71
+ const mounted = mountCallerTool(t);
72
+ roster.mount(mounted, { source: callerMountSource(mounted), mountedBy: "scenario" });
73
+ }
74
+ function mountCallerTool(t) {
66
75
  if (isDefineToolProduct(t)) {
67
76
  return maybeOffload(t, t);
68
77
  }
@@ -70,20 +79,20 @@ export async function prepareCapsAndWorkflow(input) {
70
79
  ...t,
71
80
  execute: (args, ctx) => t.execute(args, enrichSpecToolCtx(ctx)),
72
81
  }), t);
73
- });
82
+ }
74
83
  const blockedRef = {};
75
84
  if (spec.enableBlockedReport !== false) {
76
- tools.push(createReportBlockedTool(blockedRef));
85
+ roster.mount(createReportBlockedTool(blockedRef), { source: "builtin", mountedBy: "always" });
77
86
  }
78
87
  if (!(spec.tools ?? []).some((t) => t.name === REPORT_FINDINGS_TOOL_NAME || t.aliases?.includes(REPORT_FINDINGS_TOOL_NAME))) {
79
- tools.push(createReportFindingsTool());
88
+ roster.mount(createReportFindingsTool(), { source: "builtin", mountedBy: "always" });
80
89
  }
81
90
  if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
82
91
  const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
83
92
  if (spec.interactiveTools === true || planReviewFace) {
84
- tools.push(defineTool(createPresentPlanTool(requestReview)));
93
+ roster.mount(defineTool(createPresentPlanTool(requestReview)), { source: "builtin", mountedBy: "optional" });
85
94
  if (planReviewFace) {
86
- tools.push(defineTool(createEnterPlanModeTool(enterPlanMode)));
95
+ roster.mount(defineTool(createEnterPlanModeTool(enterPlanMode)), { source: "builtin", mountedBy: "optional" });
87
96
  }
88
97
  }
89
98
  }
@@ -156,11 +165,23 @@ export async function prepareCapsAndWorkflow(input) {
156
165
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
157
166
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
158
167
  const classifierRuntime = brainToRuntime(deps.brain);
168
+ const classifierCap = thinkingOffExpressible(classifierModel) ? { maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS } : {};
159
169
  autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
160
170
  autoModeDecider = createAutoModeDecider({
161
171
  ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
162
172
  ...(am.failureThreshold !== undefined ? { failureThreshold: am.failureThreshold } : {}),
163
173
  ...(am.onBreakerOpen !== undefined ? { onBreakerOpen: am.onBreakerOpen } : {}),
174
+ onClassified: (info) => emitTrace(deps.tracer, () => ({
175
+ kind: "auto_mode.classified",
176
+ version: 1,
177
+ taskId: hostTaskId,
178
+ toolCallId: info.toolCallId,
179
+ model: classifierModel.id,
180
+ ms: info.ms,
181
+ verdict: info.verdict,
182
+ ...(info.cause !== undefined ? { cause: info.cause } : {}),
183
+ ts: Date.now(),
184
+ })),
164
185
  classify: async (input, signal) => {
165
186
  const ctx = await session.buildContext();
166
187
  const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) &&
@@ -169,6 +190,8 @@ export async function prepareCapsAndWorkflow(input) {
169
190
  const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
170
191
  const response = await classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
171
192
  signal,
193
+ reasoning: "off",
194
+ ...classifierCap,
172
195
  ...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
173
196
  ...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
174
197
  });
@@ -193,8 +216,7 @@ export async function prepareCapsAndWorkflow(input) {
193
216
  workflowToolsActive = true;
194
217
  const currentSizeGuideline = () => resolveWorkflowSizeGuideline(deps.workflowLimits?.sizeGuideline).size;
195
218
  workflowSizeGuideline = { legGuideline: currentSizeGuideline(), current: currentSizeGuideline };
196
- toolEffects.set(RUN_WORKFLOW_TOOL_NAME, "write");
197
- tools.push(await createRunWorkflowTool({
219
+ roster.mount(await createRunWorkflowTool({
198
220
  runner: runnerSelf,
199
221
  scriptRunner: deps.workflowScriptRunner,
200
222
  governanceBaseline: deps.workflowGovernanceBaseline,
@@ -249,7 +271,7 @@ export async function prepareCapsAndWorkflow(input) {
249
271
  parentCenterSourceRevision: () => input.centerAdoption()?.sourceRevision,
250
272
  ...(forwardEvent ? { forwardEvent } : {}),
251
273
  inheritedGateForChildren,
252
- }));
274
+ }), { source: "builtin", mountedBy: "workflow" });
253
275
  }
254
- return { tools, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline };
276
+ return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline };
255
277
  }
@@ -1,43 +1,22 @@
1
- /**
2
- * design/390 M11 — prepareTask's TOOL-FACE FOLD + DEFERRED CLASSIFICATION phase: the exclusion valve's
3
- * true unmount (in place, on the shared mount array), the full-shell anti-drift invariant, the classic-
4
- * profile description swap, `classifyDeferredOverFace` (the one classifier both counterfactual passes
5
- * run), the built-in memory pairs' support-name pre-check (sole-cause counterfactual, whole-group
6
- * retraction, the engine pair's recall paragraph stripped back out of the memory block) and the final
7
- * `deferred` set. The body is the driver's stretch moved verbatim (one exception, named below); the
8
- * interface is the dependency list it had implicitly (design/238 R-1).
9
- *
10
- * SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between
11
- * the git-status probe before it and the content-origin wrap after it (design/390 §1.5 S1: a stretch
12
- * with no await is extracted as a sync function — an `async` wrapper would open a microtask window).
13
- *
14
- * The one non-move: the stretch used to write `sharedMemoryPairMounted = false` /
15
- * `memoryEnginePairMounted = false` on the retraction arm. Nothing reads either flag after this stretch
16
- * (the driver's next reader was this stretch's own pair derivation, which runs first), so the two dead
17
- * writes are not carried into the Result — the flags arrive as borrowed-readonly inputs.
18
- *
19
- * Throws pass through unchanged: `internal.full_shell_reachable_mismatch` (an engine invariant, never a
20
- * configuration error). The reserved-name refusal for a standing ToolSearch collision is NOT here — it
21
- * belongs to the disclosure mount downstream, which reads the `deferred` set this phase returns.
22
- */
23
- import type { AgentTool } from "../../internal/harness.js";
24
1
  import type { Model } from "../../internal/llm.js";
25
2
  import type { MaterializedA2a } from "../a2a.js";
26
3
  import type { MaterializedMcp } from "../mcp.js";
27
4
  import type { RunnerDeps, TaskSpec } from "../types.js";
28
5
  import type { AnnounceOnceSink, ToolFaceSnapshot } from "./contracts.js";
6
+ import { RosterBuilder } from "../tool-roster.js";
29
7
  export interface PrepareDeferClassifyInput {
30
8
  /** borrowed-readonly — the REBOUND spec (`spec′`); only `tools` is read (the caller roster the
31
9
  * classifier judges: names for the full-schema filter, specs for the `defer` declarations). */
32
- spec: Pick<TaskSpec, "tools">;
10
+ spec: Pick<TaskSpec, "tools" | "excludeAllTools">;
33
11
  /** borrowed-readonly — deployment deps; only `deferMode` is read (the classifier's mode knob). */
34
12
  deps: Pick<RunnerDeps, "deferMode">;
35
13
  /** borrowed-mutable — the shared mount array (later refs alias it; in-place contract, never
36
14
  * re-created). Mutated here, in order: `splice` of every excluded name; element REPLACEMENT with a
37
15
  * shallow copy for the classic-profile description swap (the caller's ToolSpec object is never
38
16
  * mutated); `splice` of a retracted memory group. After this phase: the content-origin wrap
39
- * (element replacement with the wrapped object), the disclosure mount, the MCP refresh re-splice. */
40
- tools: AgentTool[];
17
+ * (element replacement with the wrapped object), the disclosure mount, the MCP refresh re-splice. Since
18
+ * design/388 every one of those writes is a builder verb (`unmountWhere` / `replaceAt`). */
19
+ roster: RosterBuilder;
41
20
  /** borrowed-readonly — the frozen task-start tool face: `exclude` (the valve), `defer` (operator
42
21
  * defers, exact wire names), `alwaysLoad` (the inline pins). */
43
22
  toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude" | "defer" | "alwaysLoad">;
@@ -4,6 +4,7 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
4
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
5
5
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
6
6
  import { TOOL_SEARCH_NAME, classifyDeferred } from "./tool-disclosure.js";
7
+ import { RosterBuilder } from "../tool-roster.js";
7
8
  function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
8
9
  return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
9
10
  }
@@ -11,13 +12,15 @@ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
11
12
  return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
12
13
  }
13
14
  export function prepareDeferClassify(input) {
14
- const { spec, deps, tools, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryRecallSegment, onceLedger, sessionId } = input;
15
+ const { spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryRecallSegment, onceLedger, sessionId } = input;
16
+ const tools = roster.tools;
15
17
  let memoryBlock = input.memoryBlock;
16
- if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
18
+ if (spec.excludeAllTools === true) {
19
+ roster.unmountWhere(() => true);
20
+ }
21
+ else if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
17
22
  const excluded = new Set(toolFaceSnapshot.exclude);
18
- for (let i = tools.length - 1; i >= 0; i--)
19
- if (excluded.has(tools[i].name))
20
- tools.splice(i, 1);
23
+ roster.unmountWhere((t) => excluded.has(t.name));
21
24
  }
22
25
  if (handsEnabled) {
23
26
  const fullShellOnRoster = tools.some((t) => t.name === "Bash" && getToolContract(t, "core").contractId === FULL_SHELL_CONTRACT_ID);
@@ -33,7 +36,7 @@ export function prepareDeferClassify(input) {
33
36
  for (let i = 0; i < tools.length; i++) {
34
37
  const t = tools[i];
35
38
  if (t.descriptionClassic !== undefined)
36
- tools[i] = { ...t, description: t.descriptionClassic };
39
+ roster.replaceAt(i, { ...t, description: t.descriptionClassic });
37
40
  }
38
41
  }
39
42
  const userToolNames = (spec.tools ?? []).map((t) => t.name);
@@ -73,10 +76,7 @@ export function prepareDeferClassify(input) {
73
76
  const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
74
77
  if (soleCause && supportNameTaken) {
75
78
  const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
76
- for (let i = tools.length - 1; i >= 0; i--) {
77
- if (retractNames.has(tools[i].name))
78
- tools.splice(i, 1);
79
- }
79
+ roster.unmountWhere((t) => retractNames.has(t.name));
80
80
  if (sharedMemoryPair.length > 0) {
81
81
  onceLedger.onError(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
82
82
  `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
@@ -6,6 +6,7 @@ import type { CwdRef } from "../../tools/fs/index.js";
6
6
  import { type ToolResultStore } from "../tool-result-store.js";
7
7
  import type { RunnerDeps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
8
8
  import type { PrepareResume, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
9
+ import { RosterBuilder } from "../tool-roster.js";
9
10
  export interface PrepareDelegationSurfaceInput {
10
11
  /** borrowed-readonly — the REBOUND spec. Read: `tools` (the shadow checks and the revival spawner's delegation-tool
11
12
  * lookup), `oneShot` (the read/kill faces' completion promises), `retainBackgroundProcesses` (the Monitor receipt),
@@ -44,10 +45,10 @@ export interface PrepareDelegationSurfaceInput {
44
45
  /** borrowed-mutable — the lane's late-bound facts; the SendMessage mount hands the SAME object to its peer seats (identity is
45
46
  * the contract: the wiring station fills `askEffective` later). Not written here. */
46
47
  peerLaneRefs: PeerLaneRefs;
47
- /** borrowed-mutable — the run's shared roster (`Prepared.tools`, whose JSDoc carries the writer table). Writers here: the
48
- * eight mounts (TaskOutput, TaskStop, SendMessage, AgentTranscript, ListAgents, Monitor, the worktree pair, the offload
49
- * reader), every one a push. */
50
- tools: AgentTool[];
48
+ /** borrowed-mutable — the run's roster builder (`Prepared.tools` is its read face; the writer table is on that field).
49
+ * Writers here: the eight mounts (TaskOutput, TaskStop, SendMessage, AgentTranscript, ListAgents, Monitor, the worktree
50
+ * pair, the offload reader), every one a `mount`. */
51
+ roster: RosterBuilder;
51
52
  /** borrowed-mutable — the effect collection (`Prepared.toolEffects`). Writers here: TaskOutput → read, TaskStop / SendMessage
52
53
  * (built-in mount only) / Monitor / EnterWorktree / ExitWorktree → write. */
53
54
  toolEffects: Map<string, ToolEffect>;
@@ -8,6 +8,7 @@ import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from ".
8
8
  import { createReadToolResultTool } from "../tool-result-store.js";
9
9
  import { emitTrace } from "../trace.js";
10
10
  import { deliverEngineNotice } from "../types.js";
11
+ import { RosterBuilder } from "../tool-roster.js";
11
12
  function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
12
13
  if (delegationForRevive === undefined)
13
14
  return undefined;
@@ -29,21 +30,21 @@ function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
29
30
  };
30
31
  }
31
32
  export function prepareDelegationSurface(input) {
32
- const { spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, tools, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle } = input;
33
+ const { spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle } = input;
33
34
  let worktreeSessionRef;
34
35
  const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
35
36
  if (delegationSurfaceActive || internals?.parentNotify !== undefined || peerLaneActive) {
36
37
  if (delegationSurfaceActive) {
37
- toolEffects.set("TaskOutput", "read");
38
- toolEffects.set("TaskStop", "write");
39
- tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
38
+ roster.mountAll([
39
+ firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })),
40
+ firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })),
41
+ ], { source: "builtin", mountedBy: "delegation" });
40
42
  }
41
43
  if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
42
- toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
43
44
  axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
44
45
  const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
45
46
  const reviveSpawn = deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined ? buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) : undefined;
46
- tools.push(firstPartyOffload(createSendMessageTool({
47
+ roster.mount(firstPartyOffload(createSendMessageTool({
47
48
  runner: runnerSelf,
48
49
  registry: defaultTaskRegistry,
49
50
  ...(subagentRetain ? { retain: subagentRetain } : {}),
@@ -76,9 +77,9 @@ export function prepareDelegationSurface(input) {
76
77
  message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
77
78
  detail: { handle, ...(scope !== undefined ? { scope } : {}) },
78
79
  }),
79
- })));
80
+ })), { source: "builtin", mountedBy: delegationSurfaceActive ? "delegation" : internals?.parentNotify !== undefined ? "hostInternals" : "peerLane" });
80
81
  if (delegationSurfaceActive && !(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
81
- tools.push(firstPartyOffload(createAgentTranscriptTool({
82
+ roster.mount(firstPartyOffload(createAgentTranscriptTool({
82
83
  runner: runnerSelf,
83
84
  registry: defaultTaskRegistry,
84
85
  agentStore: deps.backgroundAgentStore,
@@ -91,17 +92,16 @@ export function prepareDelegationSurface(input) {
91
92
  message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
92
93
  detail: { handle, ...(scope !== undefined ? { scope } : {}) },
93
94
  }),
94
- })));
95
+ })), { source: "builtin", mountedBy: "delegation" });
95
96
  }
96
97
  }
97
98
  }
98
99
  const listAgents = peerLaneActive && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
99
100
  if (listAgents !== undefined)
100
- tools.push(firstPartyOffload(listAgents));
101
+ roster.mount(firstPartyOffload(listAgents), { source: "builtin", mountedBy: "peerLane" });
101
102
  if (backgroundTaskToolsActive) {
102
- toolEffects.set("Monitor", "write");
103
103
  envHandToolNames.add("Monitor");
104
- tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
104
+ roster.mount(firstPartyOffload(createMonitorTool(executionEnv, {
105
105
  registry: defaultTaskRegistry,
106
106
  owner: hostTaskId,
107
107
  scope: taskScope,
@@ -110,11 +110,9 @@ export function prepareDelegationSurface(input) {
110
110
  ...(handsCwdRef !== undefined ? { cwdRef: handsCwdRef } : {}),
111
111
  ...(offloadStore !== undefined ? { toolResultStore: offloadStore } : {}),
112
112
  ...(spec.retainBackgroundProcesses === true ? { retainBackgroundProcesses: true } : {}),
113
- })));
113
+ })), { source: "builtin", mountedBy: "backgroundTasks" });
114
114
  }
115
115
  if (handsCwdRef !== undefined) {
116
- toolEffects.set("EnterWorktree", "write");
117
- toolEffects.set("ExitWorktree", "write");
118
116
  worktreeSessionRef = {
119
117
  ...(resume?.seed.activeWorktree
120
118
  ? {
@@ -136,9 +134,9 @@ export function prepareDelegationSurface(input) {
136
134
  };
137
135
  if (workspaceStateSettle !== undefined)
138
136
  workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
139
- tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
137
+ roster.mountAll(createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))), { source: "builtin", mountedBy: "hands" });
140
138
  }
141
139
  if (offloadStore)
142
- tools.push(createReadToolResultTool(offloadStore));
140
+ roster.mount(createReadToolResultTool(offloadStore), { source: "builtin", mountedBy: "optional" });
143
141
  return { delegationSurfaceActive, worktreeSessionRef };
144
142
  }
@@ -33,6 +33,7 @@ import type { CwdRef } from "../../tools/fs/fs-shared.js";
33
33
  import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes } from "./contracts.js";
34
34
  import type { StopForDenialLimit } from "./denial-limit-arms.js";
35
35
  import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
36
+ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
36
37
  export interface PrepareGateStationsInput {
37
38
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then no `tool_call`
38
39
  * station is built). Read: `adjudicate`, `resolveAskBound`, `notifyPermissionDenied`, `notifyHookError`. */
@@ -41,7 +42,9 @@ export interface PrepareGateStationsInput {
41
42
  parkAsk: ParkAsk | undefined;
42
43
  /** borrowed-readonly — the FINAL mount roster: iterated once for the effect sync and the write-protection arming. Never
43
44
  * mutated here. */
44
- tools: AgentTool[];
45
+ tools: readonly AgentTool[];
46
+ /** borrowed-readonly — the leg's delta seat (design/388): its current roster is the face source for every gated call. */
47
+ toolRosterDeltas: ToolRosterDeltaSeat;
45
48
  /** borrowed-mutable — the effect collection. Writer here: the final-roster sync fills MISSING entries from each
46
49
  * first-party tool's declared effect (never overrides a scanned one); the plan-mode deny reads it per call. */
47
50
  toolEffects: Map<string, ToolEffect>;
@@ -7,6 +7,7 @@ import { directDeny } from "./gate-exit.js";
7
7
  import { emitTrace } from "../trace.js";
8
8
  import { createWriteProtectionCheck } from "../write-protect.js";
9
9
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
10
+ import { toolCallFaceOf } from "../tool-roster.js";
10
11
  function screenGateResult(result) {
11
12
  if (result.suspend !== undefined)
12
13
  return { defects: [] };
@@ -39,7 +40,7 @@ function maybeHumanRejectionHalt(input) {
39
40
  };
40
41
  }
41
42
  export function prepareGateStations(input) {
42
- const { askLane, parkAsk, tools, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedBash, shellGatedMonitor, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash } = input;
43
+ const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedBash, shellGatedMonitor, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash } = input;
43
44
  if (askLane !== undefined && parkAsk !== undefined) {
44
45
  const { adjudicate, resolveAskBound, notifyPermissionDenied, notifyHookError } = askLane;
45
46
  const { resolveContentAsk, suspendAsk } = parkAsk;
@@ -103,6 +104,7 @@ export function prepareGateStations(input) {
103
104
  hookTimeoutMs,
104
105
  ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
105
106
  ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
107
+ toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name),
106
108
  adjudicate,
107
109
  resolveAsk: resolveAskBound,
108
110
  suspendAsk,
@@ -5,6 +5,7 @@ import type { Model } from "../../internal/llm.js";
5
5
  import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
6
6
  import type { StoredSession } from "../session.js";
7
7
  import type { IrreversibilityTier, PrepareResume, ReversibilityProbes, RunInternals, ToolFaceSnapshot } from "./contracts.js";
8
+ import { RosterBuilder } from "../tool-roster.js";
8
9
  /** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code.
9
10
  * Deliberately asymmetric — it resets only the console latch: the WeakSet arm needs no seam
10
11
  * because a test resets it by minting a fresh sink function (identity IS the ledger key), while
@@ -174,8 +175,8 @@ export interface PrepareHandsReadFaceInput {
174
175
  * contract, never map/copy). This phase pushes the hands band IN PLACE (after caller tools /
175
176
  * built-ins / MCP / A2A, before the background-task mounts — T22 order). Mutation owners after
176
177
  * this phase: P11 background/Monitor/worktree pushes, the driver's excludeTools splice, the
177
- * refresh seam's runtime splice/push. */
178
- tools: AgentTool[];
178
+ * refresh seam's runtime splice/push. Since design/388 the array is builder-private: this phase `mountAll`s the band. */
179
+ roster: RosterBuilder;
179
180
  /** borrowed-readonly HERE (census divergence #5 vs the design row): this phase only reads
180
181
  * `.has("Bash")`/`.has("Monitor")` for the provenance bits — its mutation owners are B-2/P8/P9
181
182
  * (+refresh seam)/P11/P22, per the B-2 Result's mutation-owner table. */
@@ -8,6 +8,7 @@ import { normalizeFileText } from "../../tools/fs/encoding.js";
8
8
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
9
9
  import { deliverEngineNotice } from "../types.js";
10
10
  import { rebaseWorkspacePath } from "./workspace-path.js";
11
+ import { RosterBuilder } from "../tool-roster.js";
11
12
  const readFaceClampAnnouncedSinks = new WeakSet();
12
13
  let readFaceClampConsoleAnnounced = false;
13
14
  export function __resetReadFaceClampAnnouncement() {
@@ -123,7 +124,7 @@ export function resolveHandsLessReadFace(input) {
123
124
  }
124
125
  export async function prepareHandsMount(input) {
125
126
  const envHandToolNames = new Set();
126
- const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
+ const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
128
  assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
128
129
  assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
129
130
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
@@ -351,7 +352,7 @@ export async function prepareHandsMount(input) {
351
352
  deps.onError?.(new Error(`spec.tools ${shadowed.length > 1 ? "entries" : "entry"} ${names} collide${shadowed.length > 1 ? "" : "s"} with the built-in hands tool band — the built-in wins (registered later; the harness tool Map is last-write-wins) and the caller tool never dispatches this task. Rename the caller tool, or don't inject executionEnv if you intend to replace the band.`), { phase: "config", sessionId });
352
353
  }
353
354
  }
354
- tools.push(...band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
355
+ roster.mountAll(band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))), { source: "builtin", mountedBy: "hands" });
355
356
  const shellGate = effectiveShellGate;
356
357
  if (shellGate === "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
357
358
  deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — no shell safety-axis ` +