@sema-agent/core 7.52.0 → 7.53.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 (191) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +3 -1
  3. package/dist/agents/cross-session-envelope.d.ts +8 -0
  4. package/dist/agents/cross-session-envelope.js +6 -0
  5. package/dist/agents/cross-session-judge.d.ts +21 -1
  6. package/dist/agents/cross-session-judge.js +8 -0
  7. package/dist/agents/cross-session-ref.d.ts +5 -0
  8. package/dist/agents/cross-session-ref.js +1 -0
  9. package/dist/agents/org-delivery-gate.d.ts +122 -0
  10. package/dist/agents/org-delivery-gate.js +69 -0
  11. package/dist/agents/peer-held-queue.d.ts +9 -4
  12. package/dist/agents/peer-held-queue.js +6 -5
  13. package/dist/agents/peer-session-drain.d.ts +35 -5
  14. package/dist/agents/peer-session-drain.js +271 -180
  15. package/dist/agents/read-inbox-tool.d.ts +7 -0
  16. package/dist/agents/read-inbox-tool.js +5 -1
  17. package/dist/agents/subagent.d.ts +3 -0
  18. package/dist/agents/subagent.js +99 -72
  19. package/dist/agents/tool-filter.d.ts +18 -0
  20. package/dist/agents/tool-filter.js +6 -0
  21. package/dist/brain/anthropic.d.ts +6 -1
  22. package/dist/brain/anthropic.js +4 -4
  23. package/dist/brain/model-presets.js +2 -1
  24. package/dist/brain/route-adjudicator.d.ts +6 -0
  25. package/dist/brain/routing.d.ts +24 -1
  26. package/dist/brain/routing.js +54 -19
  27. package/dist/brain/timeout.js +5 -4
  28. package/dist/core/a2a.d.ts +5 -2
  29. package/dist/core/a2a.js +30 -13
  30. package/dist/core/allow-entry.d.ts +2 -1
  31. package/dist/core/allow-entry.js +2 -2
  32. package/dist/core/auto-compaction.d.ts +57 -0
  33. package/dist/core/auto-compaction.js +28 -2
  34. package/dist/core/checkpoint-store.d.ts +29 -20
  35. package/dist/core/checkpoint-store.js +3 -3
  36. package/dist/core/compliance.js +3 -1
  37. package/dist/core/effective-path-target.d.ts +25 -6
  38. package/dist/core/effective-path-target.js +24 -2
  39. package/dist/core/engine-notice.d.ts +13 -2
  40. package/dist/core/gate-fold.js +3 -2
  41. package/dist/core/gate-lanes.js +11 -13
  42. package/dist/core/gate-outcome.d.ts +11 -6
  43. package/dist/core/governance-codes.d.ts +3 -1
  44. package/dist/core/governance-codes.js +3 -0
  45. package/dist/core/hands-band-screen.d.ts +7 -0
  46. package/dist/core/hands-band-screen.js +32 -0
  47. package/dist/core/hooks.d.ts +7 -5
  48. package/dist/core/hooks.js +5 -3
  49. package/dist/core/locked-config.d.ts +84 -32
  50. package/dist/core/locked-config.js +128 -42
  51. package/dist/core/mcp-probe.js +1 -1
  52. package/dist/core/mcp-server-spec.d.ts +88 -10
  53. package/dist/core/mcp-server-spec.js +183 -33
  54. package/dist/core/mcp.d.ts +21 -14
  55. package/dist/core/mcp.js +55 -51
  56. package/dist/core/memory-engine/engine.d.ts +14 -19
  57. package/dist/core/memory-engine/engine.js +7 -5
  58. package/dist/core/mode-policy.d.ts +3 -18
  59. package/dist/core/mode-policy.js +2 -16
  60. package/dist/core/mods/kernel/dispatch.js +13 -15
  61. package/dist/core/mods/kernel/events.d.ts +9 -6
  62. package/dist/core/mods/kernel/events.js +2 -2
  63. package/dist/core/mods/kernel/receive.d.ts +39 -10
  64. package/dist/core/mods/kernel/receive.js +119 -54
  65. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  66. package/dist/core/mods/kernel/site-rules.js +232 -40
  67. package/dist/core/mods/stations.d.ts +0 -6
  68. package/dist/core/mods/stations.js +0 -7
  69. package/dist/core/mounted-mcp.d.ts +57 -0
  70. package/dist/core/mounted-mcp.js +71 -2
  71. package/dist/core/own-property-receipt.d.ts +13 -0
  72. package/dist/core/own-property-receipt.js +3 -1
  73. package/dist/core/parent-spec-seat.d.ts +2 -2
  74. package/dist/core/permission-rule-model.d.ts +58 -9
  75. package/dist/core/permission-rule-model.js +81 -7
  76. package/dist/core/permission-rule-org.d.ts +163 -33
  77. package/dist/core/permission-rule-org.js +205 -71
  78. package/dist/core/permission-rules.d.ts +7 -1
  79. package/dist/core/permission-rules.js +37 -8
  80. package/dist/core/retention.js +7 -2
  81. package/dist/core/retired-keys.d.ts +8 -2
  82. package/dist/core/retired-keys.js +12 -1
  83. package/dist/core/roles.js +60 -10
  84. package/dist/core/rule-lane-reads.js +1 -1
  85. package/dist/core/runner/contracts.d.ts +1 -0
  86. package/dist/core/runner/gate-exit.js +4 -3
  87. package/dist/core/runner/mcp-redial.d.ts +52 -10
  88. package/dist/core/runner/mcp-redial.js +38 -9
  89. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  90. package/dist/core/runner/permission-rule-lanes.js +22 -10
  91. package/dist/core/runner/person-path-rows.d.ts +3 -1
  92. package/dist/core/runner/person-path-rows.js +2 -2
  93. package/dist/core/runner/prepare-config-doors.d.ts +48 -2
  94. package/dist/core/runner/prepare-config-doors.js +161 -145
  95. package/dist/core/runner/prepare-defer-classify.js +4 -7
  96. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  97. package/dist/core/runner/prepare-delegation-surface.js +7 -5
  98. package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
  99. package/dist/core/runner/prepare-hands-readface.js +2 -1
  100. package/dist/core/runner/prepare-inherited-gate.js +2 -2
  101. package/dist/core/runner/prepare-listings.d.ts +1 -1
  102. package/dist/core/runner/prepare-listings.js +4 -2
  103. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  104. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  105. package/dist/core/runner/prepare-memory.d.ts +3 -2
  106. package/dist/core/runner/prepare-memory.js +6 -2
  107. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  108. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  109. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  110. package/dist/core/runner/prepare-policy-chain.js +12 -12
  111. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  112. package/dist/core/runner/prepare-project-context.js +6 -4
  113. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  114. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  115. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  116. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  117. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  118. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  119. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  120. package/dist/core/runner/prepare-question-face.js +4 -3
  121. package/dist/core/runner/prepare-task.js +10 -10
  122. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
  123. package/dist/core/runner/prepare-wiring-manifest.js +8 -5
  124. package/dist/core/runner/resume-admission.d.ts +4 -1
  125. package/dist/core/runner/resume-admission.js +7 -5
  126. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  127. package/dist/core/runner/resume-internals-and-config.js +3 -8
  128. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  129. package/dist/core/runner/retired-runner-deps.js +1 -4
  130. package/dist/core/runner/run-leg.js +42 -12
  131. package/dist/core/runner/run-recovery-lanes.js +5 -3
  132. package/dist/core/runner/run-terminal-adoption.js +6 -5
  133. package/dist/core/runner/run-turn-boundary.js +9 -3
  134. package/dist/core/runner/runtask.js +1 -1
  135. package/dist/core/runner/skills-seat.d.ts +15 -0
  136. package/dist/core/runner/skills-seat.js +57 -0
  137. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  138. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  139. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  140. package/dist/core/runner/synthetic-tools.js +2 -12
  141. package/dist/core/runner-deps.d.ts +29 -14
  142. package/dist/core/session-policy-store.d.ts +19 -0
  143. package/dist/core/session-policy-store.js +22 -2
  144. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  145. package/dist/core/task-event.d.ts +38 -2
  146. package/dist/core/task-notification.js +2 -2
  147. package/dist/core/task-spec.d.ts +1 -1
  148. package/dist/core/tool-policy.d.ts +3 -0
  149. package/dist/core/tool-policy.js +10 -4
  150. package/dist/core/types.d.ts +2 -2
  151. package/dist/core/types.js +1 -1
  152. package/dist/core/untrusted-text.d.ts +16 -0
  153. package/dist/core/untrusted-text.js +7 -2
  154. package/dist/core/user-attachment.d.ts +13 -2
  155. package/dist/core/user-attachment.js +47 -41
  156. package/dist/core/wiring-manifest.d.ts +4 -2
  157. package/dist/engine/harness/agent-harness.js +2 -1
  158. package/dist/engine/harness/types.d.ts +3 -0
  159. package/dist/engine/loop/agent-loop.d.ts +12 -1
  160. package/dist/engine/loop/agent-loop.js +17 -5
  161. package/dist/engine/loop/types.d.ts +7 -1
  162. package/dist/index.d.ts +15 -9
  163. package/dist/index.js +12 -6
  164. package/dist/internal/harness.d.ts +1 -0
  165. package/dist/internal/harness.js +1 -0
  166. package/dist/orchestration/goal.d.ts +24 -1
  167. package/dist/orchestration/goal.js +3 -2
  168. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  169. package/dist/prompt-assembly/artifact-store.js +38 -12
  170. package/dist/prompt-assembly/artifact.d.ts +12 -11
  171. package/dist/prompt-assembly/artifact.js +52 -21
  172. package/dist/prompt-assembly/assemble.d.ts +2 -0
  173. package/dist/prompt-assembly/assemble.js +2 -1
  174. package/dist/prompt-assembly/catalog/index.js +2 -1
  175. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  176. package/dist/prompt-assembly/catalog/memory.js +6 -0
  177. package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
  178. package/dist/prompt-assembly/composer.d.ts +2 -0
  179. package/dist/prompt-assembly/composer.js +1 -1
  180. package/dist/prompt-assembly/overlay.d.ts +21 -11
  181. package/dist/prompt-assembly/overlay.js +55 -17
  182. package/dist/prompt-assembly/select.d.ts +21 -6
  183. package/dist/prompt-assembly/select.js +39 -2
  184. package/dist/prompts/default.d.ts +0 -11
  185. package/dist/prompts/default.js +0 -3
  186. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  187. package/dist/stores/file/permission-rule-store.js +2 -1
  188. package/dist/tools/artifact/artifact-tool.js +4 -3
  189. package/package.json +1 -1
  190. package/test/export-surface.snapshot.json +65 -5
  191. package/wire-changes/core@7.53.0.json +1946 -0
@@ -1,5 +1,5 @@
1
1
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
2
- import { mountedMcpPoolRows } from "../mounted-mcp.js";
2
+ import { mountedMcpPoolRows, toolNamesOfServer } from "../mounted-mcp.js";
3
3
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
4
4
  import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
5
5
  import { protocolOf } from "../protocol-table.js";
@@ -7,9 +7,10 @@ import { deliverEngineNotice } from "../types.js";
7
7
  import { inlineUntrusted } from "../untrusted-text.js";
8
8
  import { fileArgPath } from "../tool-registry.js";
9
9
  import { RosterBuilder } from "../tool-roster.js";
10
+ import { isNotStartedDelegationResult } from "../../agents/subagent.js";
10
11
  import { thrownText } from "../thrown-value.js";
11
12
  export function prepareMemoryEngineSession(input) {
12
- const { memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, mountedMcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef } = input;
13
+ const { memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mountedMcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef } = input;
13
14
  const tools = roster.tools;
14
15
  const delegationProvenanceChannel = internals?.delegationProvenance;
15
16
  if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
@@ -22,7 +23,7 @@ export function prepareMemoryEngineSession(input) {
22
23
  provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
23
24
  announceDeclaredMcpContentClasses({
24
25
  entries: lockedPreflight.mcp,
25
- statuses: mcp.statuses,
26
+ mountedNames: roster.tools.map((t) => t.name),
26
27
  execIsExternalContent,
27
28
  sessionId,
28
29
  runId,
@@ -133,6 +134,9 @@ export function prepareMemoryEngineSession(input) {
133
134
  isPolluting: poolToolPollutes,
134
135
  });
135
136
  const result = await inner(toolCallId, params, signal, onUpdate);
137
+ const delivery = delegationDeliveryOf(result);
138
+ if (delivery === "none")
139
+ return result;
136
140
  const att = readCardAttestation(result?.details);
137
141
  if (att === "external") {
138
142
  mark(`tool "${t.name}" returned the output of a delegated agent that invoked an external-content-class tool at runtime` +
@@ -141,8 +145,11 @@ export function prepareMemoryEngineSession(input) {
141
145
  }
142
146
  else if (att !== "clean") {
143
147
  if (external) {
144
- const staticFaceReason = `tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
145
- (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)");
148
+ const staticFaceReason = (delivery === "launched"
149
+ ? `tool "${t.name}" launched a background delegated agent whose tool face can reach external content`
150
+ : `tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content`) +
151
+ (typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)") +
152
+ (delivery === "launched" ? " — its output enters this session when it finishes" : "");
146
153
  if (!delegationEvidenceAttestedOnly) {
147
154
  mark(staticFaceReason, "static");
148
155
  }
@@ -207,13 +214,19 @@ export function prepareMemoryEngineSession(input) {
207
214
  }
208
215
  return { armed: memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined };
209
216
  }
217
+ function delegationDeliveryOf(result) {
218
+ if (isNotStartedDelegationResult(result))
219
+ return "none";
220
+ const details = typeof result === "object" && result !== null ? result.details : undefined;
221
+ return typeof details === "object" && details !== null && details.status === "async_launched" ? "launched" : "report";
222
+ }
210
223
  function announceDeclaredMcpContentClasses(args) {
211
224
  for (const entry of args.entries ?? []) {
212
225
  const declaredClass = entry.contentOrigin;
213
226
  if (declaredClass === undefined)
214
227
  continue;
215
228
  const server = inlineUntrusted(String(entry.name), 160);
216
- const toolCount = args.statuses?.find((s) => s.name === entry.name)?.toolNames?.length ?? 0;
229
+ const toolCount = toolNamesOfServer(args.mountedNames, entry.name).length;
217
230
  const resourceFaceCaveat = " (Not covered: the cross-server resource faces — ListMcpResourcesTool/ReadMcpResourceTool/ReadMcpResourceDirTool — stay externally classified and still mark.)";
218
231
  const posture = declaredClass === "local"
219
232
  ? `this server's mounted tools' invocations will not mark this session's memory as externally exposed.${resourceFaceCaveat}`
@@ -208,11 +208,12 @@ export interface PrepareMemoryResult {
208
208
  * BEFORE it). It performs the one destructive read this phase has — `inject()` drains the announcement queue —
209
209
  * so a hands-phase refusal cannot consume announcements a later prepare should deliver, and it takes the
210
210
  * roster-wide persistence verdict the driver can only read off the MOUNTED roster (`rosterCanPersist`: the
211
- * `TaskSpec.memoryPersistenceCapable` declaration when set, else the known-store-path inference over the
212
- * assembled tools). Call exactly once per prepare.
211
+ * `memoryPersistenceCapable` declaration when set, else the store-path inference over the assembled tools) and
212
+ * `isSubagent` (a non-fork delegated child — no user conversation, no not-mounted notice). Call once per prepare.
213
213
  */
214
214
  composeInjection: (facts: {
215
215
  rosterCanPersist: boolean;
216
+ isSubagent: boolean;
216
217
  }) => MemoryInjectionComposition;
217
218
  /**
218
219
  * The FROZEN org-admission verdict of this prepare: every org scope the task actually mounts
@@ -6,7 +6,7 @@ import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir,
6
6
  import { memoryProjectScopeAbsentNotice, normalizeMemorySpec } from "../memory.js";
7
7
  import { deriveSupersededSet } from "../memory-engine/consolidation.js";
8
8
  import { shadowNamesOf } from "../memory-engine/shadowing.js";
9
- import { MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA, MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_CAPTURE_INDETERMINATE_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memoryRestoreWithheldNotice, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
9
+ import { MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA, MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_CAPTURE_OPTOUT_NOTICE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memoryRestoreWithheldNotice, memorySessionPollutedNotice, memoryUnavailableNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
10
10
  import { adjudicateCaptureOptOut, captureOptOutDeniedError, captureOptOutUnpersistedError } from "./memory-capture-optout.js";
11
11
  import { readSessionCaptureOptOut } from "../memory-engine/layout.js";
12
12
  import { sessionIncarnationResolver } from "../memory-engine/session-incarnation.js";
@@ -14,6 +14,7 @@ import { createMemoryEngineTools } from "../memory-engine/tools.js";
14
14
  import { assertScopeContractPlacement, parseScopeKey, resolveProjectId } from "../memory-engine/scope-contract.js";
15
15
  import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
16
16
  import { writableRootOf } from "../memory-engine/types.js";
17
+ import { catalogText } from "../../prompt-assembly/catalog/index.js";
17
18
  import { asThrownError, thrownText } from "../thrown-value.js";
18
19
  let undeclaredCapturePostureAnnounced = false;
19
20
  export async function prepareMemory(input) {
@@ -781,7 +782,7 @@ export async function prepareMemory(input) {
781
782
  blockBody = [MEMORY_CAPTURE_OPTOUT_NOTICE, injection.index, injection.announceBlock].filter((s) => Boolean(s && s.trim())).join("\n\n");
782
783
  }
783
784
  else if (captureIndeterminate && facts.rosterCanPersist) {
784
- blockBody = [MEMORY_CAPTURE_INDETERMINATE_NOTICE, injection.index, injection.announceBlock].filter((s) => Boolean(s && s.trim())).join("\n\n");
785
+ blockBody = [memoryUnavailableNotice({ what: "capture-record-unreadable", notesReadable: true }), injection.index, injection.announceBlock].filter((s) => Boolean(s && s.trim())).join("\n\n");
785
786
  }
786
787
  else if (!facts.rosterCanPersist && injection.instruction === "" && injection.readOnlyNotice === undefined) {
787
788
  blockBody = blockBody.trim() ? `${MEMORY_READONLY_NOTICE}\n\n${blockBody}` : MEMORY_READONLY_NOTICE;
@@ -817,6 +818,9 @@ export async function prepareMemory(input) {
817
818
  if (memoryBlock !== undefined && injection.indexSeed !== undefined && facts.rosterCanPersist && !captureOptedOut && !captureIndeterminate)
818
819
  seedFiles = [injection.indexSeed];
819
820
  }
821
+ else if (!facts.rosterCanPersist && !facts.isSubagent) {
822
+ memoryBlock = effectiveMemoryScopes?.reason === "mount-failed" ? memoryUnavailableNotice({ what: "mount-failed", notesReadable: false }) : catalogText("core/memory.no-persistent-store");
823
+ }
820
824
  return {
821
825
  memoryBlock,
822
826
  ...(recallDisciplineSegment !== undefined ? { recallDisciplineSegment } : {}),
@@ -20,10 +20,10 @@ import type { ToolSelector } from "../tool-face.js";
20
20
  export interface PrepareOffloadWrappersInput {
21
21
  /** borrowed-readonly — the REBOUND spec (the config-doors result); read for the per-task coarse
22
22
  * offload knob only. */
23
- spec: Pick<TaskSpec, "toolResultThresholdChars">;
24
- /** borrowed-readonly — the leg's compiled selector: an offloaded result is read back through the reader tool, so a leg whose selector
25
- * does not admit the reader (a sub-agent whose lists leave it out, a deployment that excludes it) offloads nothing — a preview that
26
- * sends the model to a tool it does not have is a dead end. */
23
+ spec: Pick<TaskSpec, "toolResultThresholdChars" | "excludeAllTools">;
24
+ /** borrowed-readonly — the leg's compiled selector, read through the valve (`valveLeaves`): an offloaded result is read back through the
25
+ * reader tool, so a leg whose valve does not leave the reader (a sub-agent whose lists leave it out, a deployment that excludes it, a
26
+ * run whose whole face is switched off) offloads nothing — a preview that sends the model to a tool it does not have is a dead end. */
27
27
  selector: Pick<ToolSelector, "admits">;
28
28
  /** borrowed-readonly — the deployment's coarse offload knob and its injected store (used AS-IS when
29
29
  * present; a Runner-shared instance is viewed per task, see the body). */
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, InMemoryToolResultStore, OFFLOAD_TOOL_NAME, RunnerSharedToolResultStore, ScopedToolResultStore, withToolResultOffload, } from "../tool-result-store.js";
2
2
  import { faceOf } from "../tool-roster.js";
3
+ import { valveLeaves } from "../../agents/tool-filter.js";
3
4
  const FIRST_PARTY_OFFLOAD_THRESHOLD_CHARS = 50_000;
4
5
  export function prepareOffloadWrappers(input) {
5
6
  const { spec, deps, resume, offloadScope, sessionId, selector } = input;
@@ -21,7 +22,7 @@ export function prepareOffloadWrappers(input) {
21
22
  };
22
23
  const explicitGlobalThreshold = spec.toolResultThresholdChars ?? deps.toolResultThresholdChars;
23
24
  const offloadThreshold = explicitGlobalThreshold ?? DEFAULT_TOOL_RESULT_THRESHOLD_CHARS;
24
- const offloadEnabled = Number.isFinite(offloadThreshold) && offloadThreshold > 0 && selector.admits({ name: OFFLOAD_TOOL_NAME });
25
+ const offloadEnabled = Number.isFinite(offloadThreshold) && offloadThreshold > 0 && valveLeaves(spec, selector)({ name: OFFLOAD_TOOL_NAME });
25
26
  const rawOffloadStore = offloadEnabled ? (deps.toolResultStore ?? new InMemoryToolResultStore()) : undefined;
26
27
  const offloadStore = rawOffloadStore instanceof RunnerSharedToolResultStore
27
28
  ? new ScopedToolResultStore(rawOffloadStore, offloadScope)
@@ -46,7 +46,7 @@ export interface PreparePolicyChainInput {
46
46
  /** borrowed-readonly — the REBOUND spec. Read: `sessionId` / `principal` (the session-rule read and the lanes' identity),
47
47
  * `durableApproval` / `checkpointStore` (the mandate-park facility test and the sandbox arming's park prerequisites), `taskId` (the late-ask observer's detail),
48
48
  * `tools` (the names the caller mounted, for the name audit). Never mutated. */
49
- spec: Pick<TaskSpec, "sessionId" | "principal" | "durableApproval" | "taskId" | "checkpointStore" | "tools">;
49
+ spec: Pick<TaskSpec, "sessionId" | "principal" | "durableApproval" | "taskId" | "checkpointStore" | "tools" | "excludeAllTools">;
50
50
  /** borrowed-readonly — the deployment seats: `sessionPolicyStore` (the rule reads),
51
51
  * `permissionRuleStore` / `localOwnerRules` (the lanes and the offer factory), `onError` / `onNotice` (the advisories,
52
52
  * the refusal disclosures, the late-ask observer), `tracer` (the admission and rule-store frames), `checkpointStore` (the park facility). Receiver preserved;
@@ -1,4 +1,4 @@
1
- import { withArgsReading } from "../effective-path-target.js";
1
+ import { argsAsTheToolReads, withArgsReading } from "../effective-path-target.js";
2
2
  import { policyAskClassOf } from "../ask-class.js";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
4
  import { classifierUnavailableSeat } from "./classifier-ladder.js";
@@ -6,7 +6,8 @@ import { resolveCheckpointStore } from "../checkpoint-store.js";
6
6
  import { chainJudges, inheritedToolChecks, screeningInstallationsOf, standsInFor } from "../mods/stations.js";
7
7
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
8
8
  import { isIsolated } from "../remote-env.js";
9
- import { earlierGrammarAdvisory } from "../session-policy-store.js";
9
+ import { deliverEngineNotice } from "../engine-notice.js";
10
+ import { earlierGrammarNotice } from "../session-policy-store.js";
10
11
  import { attest, attestationOf, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, mergeAttestation, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, withAttestation, wrapToolPolicy } from "../tool-policy.js";
11
12
  import { judgeRuleToolName, mountedToolNames } from "../tool-name-aliases.js";
12
13
  import { approvalCardMemberOf } from "../tool-roster.js";
@@ -15,6 +16,7 @@ import { emitTrace } from "../trace.js";
15
16
  import { skillGrantBindingMembers } from "../skill-grant.js";
16
17
  import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
17
18
  import { delegationPoolReading } from "./sub-agent-deny-lists.js";
19
+ import { valveLeaves } from "../../agents/tool-filter.js";
18
20
  import { autoStrippedRule, cardDisclosureOf, nearMissSpellingOf, rootFacingToolNames } from "../allow-entry.js";
19
21
  import { arbitrateByMode, askClassMembersOf, askFaceOf, askMandateMember } from "../ask-origin.js";
20
22
  import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
@@ -46,7 +48,7 @@ export async function preparePolicyChain(input) {
46
48
  const known = mountedToolNames(tools);
47
49
  for (const t of harnessTools)
48
50
  known.add(t.name);
49
- const pool = delegationPoolReading(spec.tools ?? [], selector);
51
+ const pool = delegationPoolReading(spec.tools ?? [], valveLeaves(spec, selector));
50
52
  const callerNames = mountedToolNames(spec.tools ?? []);
51
53
  const rootFacing = rootFacingToolNames([...known, ...callerNames, ...pool.names]);
52
54
  const unmatched = new Set();
@@ -119,7 +121,7 @@ export async function preparePolicyChain(input) {
119
121
  catch {
120
122
  }
121
123
  };
122
- const announceEarlierGrammar = (rulesSessionId) => (entries, field, grammar) => deps.onError?.(Object.assign(new Error(earlierGrammarAdvisory(rulesSessionId, entries, field, grammar)), { code: "config.session_rules_earlier_grammar" }), { phase: "config", sessionId });
124
+ const announceEarlierGrammar = (rulesSessionId) => (entries, field, grammar) => deliverEngineNotice(deps.onNotice, earlierGrammarNotice({ rulesSessionId, sessionId, entries, field, grammar }));
123
125
  let sessionRulePolicy;
124
126
  if (deps.sessionPolicyStore && spec.sessionId) {
125
127
  const store = deps.sessionPolicyStore;
@@ -343,7 +345,7 @@ export async function preparePolicyChain(input) {
343
345
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, approved, csignal, ancestorAutoMode) => {
344
346
  const ancestorDecider = ancestorAutoMode?.decider;
345
347
  const ancestorTracker = ancestorAutoMode?.denialTracking;
346
- let editArgs = edit;
348
+ let editArgs = argsAsTheToolReads(creq, edit);
347
349
  let settledBy = approved;
348
350
  const onApproval = (d) => withAttestation(d, mergeAttestation(creq, attestationOf(settledBy, creq), attestationOf(d, creq), "answer"));
349
351
  const vetoed = onApproval;
@@ -356,8 +358,7 @@ export async function preparePolicyChain(input) {
356
358
  }
357
359
  let re;
358
360
  try {
359
- const editReq = withArgsReading(creq, editArgs, csignal ?? abortController.signal);
360
- re = refuseOutOfContractDecision(await pol.check(editReq !== undefined ? await editReq : { ...creq, args: editArgs }, csignal ?? abortController.signal));
361
+ re = refuseOutOfContractDecision(await pol.check(await withArgsReading(creq, editArgs, csignal ?? abortController.signal), csignal ?? abortController.signal));
361
362
  }
362
363
  catch (err) {
363
364
  reportPolicyThrow(notifyOwnHookCrash, "an inherited parent policy's re-check of the approved edit", creq, err, csignal ?? abortController.signal);
@@ -373,7 +374,7 @@ export async function preparePolicyChain(input) {
373
374
  return onApproval(withAttestation({ action: "allow", updatedInput: re.updatedInput !== undefined ? re.updatedInput : editArgs, ...(stated !== undefined ? { decisionReason: stated } : {}) }, re.attestation));
374
375
  }
375
376
  if (re.updatedInput !== undefined)
376
- editArgs = re.updatedInput;
377
+ editArgs = argsAsTheToolReads(creq, re.updatedInput);
377
378
  const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: ancestorAutoMode, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit", allowRewrite: editArgs, facts: inheritedStationFacts(creq), unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(ancestorDecider?.timeoutMs !== undefined ? { budgetMs: ancestorDecider.timeoutMs } : {}), route: frozenApproverRoute(onAskOf), onNotice: deps.onNotice, sessionId, runId }) });
378
379
  if (judged.kind === "allow") {
379
380
  return onApproval(judged.result);
@@ -408,7 +409,7 @@ export async function preparePolicyChain(input) {
408
409
  settledBy = rr;
409
410
  if (rr.updatedInput === undefined)
410
411
  return onApproval({ action: "allow", updatedInput: editArgs, decisionReason: "mode" });
411
- editArgs = rr.updatedInput;
412
+ editArgs = argsAsTheToolReads(creq, rr.updatedInput);
412
413
  }
413
414
  };
414
415
  const settleInstallationAsk = async (pc, decision, creq, csignal) => {
@@ -427,7 +428,7 @@ export async function preparePolicyChain(input) {
427
428
  return settledAs(decision);
428
429
  return settledAs(mandateUnreconstructibleDeny(creq));
429
430
  }
430
- const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
431
+ const presentedArgs = decision.updatedInput !== undefined ? argsAsTheToolReads(creq, decision.updatedInput) : creq.args;
431
432
  const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call", allowRewrite: decision.updatedInput, facts: inheritedStationFacts(creq), unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(pc.autoMode?.decider.timeoutMs !== undefined ? { budgetMs: pc.autoMode.decider.timeoutMs } : {}), route: frozenApproverRoute(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) });
432
433
  if (judged.kind === "allow")
433
434
  return settledAs(judged.result);
@@ -635,8 +636,7 @@ export async function preparePolicyChain(input) {
635
636
  }
636
637
  }
637
638
  const recheckLayers = [...recheckPrefix, ...postEmitterSameInstanceAdapters];
638
- const finalRead = withArgsReading(req, decision.updatedInput, signal);
639
- const finalReq = finalRead !== undefined ? await finalRead : { ...req, args: decision.updatedInput };
639
+ const finalReq = await withArgsReading(req, decision.updatedInput, signal);
640
640
  let asked;
641
641
  let revalidated;
642
642
  let restated;
@@ -14,7 +14,7 @@ export interface PrepareProjectContextInput {
14
14
  /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `sessionId` (phase derivation + the
15
15
  * continuation projections), `skills` (the roster), `userInvokedSkills` (the person's invoked skills — their
16
16
  * rows are written here), `principal` (the shared-memory tools' trusted identity). Never mutated. */
17
- spec: Pick<TaskSpec, "sessionId" | "skills" | "userInvokedSkills" | "principal">;
17
+ spec: Pick<TaskSpec, "sessionId" | "skills" | "userInvokedSkills" | "principal" | "excludeAllTools">;
18
18
  /** borrowed-readonly — trusted internals: `agentName` (seam ctx), `inheritedSkillGrants` (the parent's captured
19
19
  * skill grant rows, seeded as `inherited`), `inheritedAllowedTools` (the parent's request entries, seeded as this leg's
20
20
  * own). `undefined` on a standalone prepare. */
@@ -13,10 +13,12 @@ import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSki
13
13
  import { skillLoadReading } from "./skill-base-dir.js";
14
14
  import { RosterBuilder } from "../tool-roster.js";
15
15
  import { mountedToolNames } from "../tool-name-aliases.js";
16
+ import { valveLeaves } from "../../agents/tool-filter.js";
16
17
  import { asThrownError } from "../thrown-value.js";
17
18
  export async function prepareProjectContext(input) {
18
19
  const { deps, spec, internals, session, sessionId, runId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, allowedTools, allowedToolTables, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input;
19
20
  const tools = roster.tools;
21
+ const leaves = valveLeaves(spec, toolFaceSnapshot.selector);
20
22
  let memoryBlock = memoryBlockFromEngine;
21
23
  let instructionSources;
22
24
  let projectInstructionContent;
@@ -115,11 +117,11 @@ export async function prepareProjectContext(input) {
115
117
  for (const alias of t.aliases ?? [])
116
118
  mountedNameDomain.add(alias);
117
119
  }
118
- const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => !toolFaceSnapshot.selector.admits({ name: n }));
120
+ const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => !leaves({ name: n }));
119
121
  const occupiedName = SHARED_MEMORY_TOOL_NAMES.find((n) => mountedNameDomain.has(n));
120
122
  if (excludedName !== undefined || occupiedName !== undefined) {
121
123
  announceMemoryNotMounted(onceLedger, sessionId, "shared", SHARED_MEMORY_TOOL_NAMES, excludedName !== undefined
122
- ? `the run's tool selector (allowTools / excludeTools) does not admit "${excludedName}"`
124
+ ? `the run's tool face (allowTools / excludeTools / excludeAllTools) does not admit "${excludedName}"`
123
125
  : `this task already declares a tool named "${occupiedName}" (the built-in yields to it)`);
124
126
  }
125
127
  else {
@@ -137,10 +139,10 @@ export async function prepareProjectContext(input) {
137
139
  }
138
140
  else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) {
139
141
  announceMemoryNotMounted(onceLedger, sessionId, "engine", MEMORY_ENGINE_TOOL_NAMES, memoryPairExcludedName !== undefined
140
- ? `the run's tool selector (allowTools / excludeTools) does not admit "${memoryPairExcludedName}"`
142
+ ? `the run's tool face (allowTools / excludeTools / excludeAllTools) does not admit "${memoryPairExcludedName}"`
141
143
  : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`);
142
144
  }
143
- const skillsListing = skillSpecs.length > 0
145
+ const skillsListing = skillSpecs.length > 0 && leaves({ name: SKILL_TOOL_NAME })
144
146
  ? {
145
147
  entries: skillSpecs.map((s) => ({
146
148
  name: s.name,
@@ -31,7 +31,7 @@ export interface PreparePromptAssemblyInput {
31
31
  readonly role?: HostRole;
32
32
  readonly form?: HostForm;
33
33
  };
34
- /** borrowed-readonly — the center binding (`TaskSpec.promptBinding`, validated), or undefined. */
34
+ /** borrowed-readonly — the run's binding (`TaskSpec.promptBinding`, validated), or undefined: a selector value. */
35
35
  binding: string | undefined;
36
36
  /** borrowed-readonly — the leg's mods bus: `prompt.compose` collects the deploy and task seats' overlays. */
37
37
  modBus: LegModBus;
@@ -1,12 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
3
3
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
4
- import { adoptCenter, centerOverlaysOf } from "../../prompt-assembly/artifact.js";
4
+ import { adoptCenter, centerOverlaysOf, verifyPromptArtifactBody } from "../../prompt-assembly/artifact.js";
5
+ import { refusalClause, storedArtifactLine } from "../../prompt-assembly/artifact-store.js";
5
6
  import { catalogDigestOf, digestOf, prepareEpochRecord } from "../../prompt-assembly/epoch.js";
6
7
  import { rendererVector } from "../../prompt-assembly/renderers.js";
7
8
  import { SEMA_DEFAULT_CATALOG } from "../../prompt-assembly/catalog/index.js";
8
9
  import { describeCatalog } from "../../prompt-assembly/explain.js";
9
- import { selectorsOf } from "../../prompt-assembly/select.js";
10
+ import { selectorRecordOf, selectorsOf } from "../../prompt-assembly/select.js";
10
11
  import { auditToolCollisions, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
11
12
  import { buildEnvironmentContext } from "../../prompts/default.js";
12
13
  import { raisePromptCompose } from "../mods/stations.js";
@@ -31,13 +32,21 @@ export async function preparePromptAssembly(input) {
31
32
  const stored = await session.getPromptEpoch();
32
33
  let centerAdoption;
33
34
  {
34
- const failUnavailable = (what) => {
35
- throw Object.assign(new Error(`prompt snapshot unavailable: ${what} (artifact source miss / not verifiable here${deps.promptSource === undefined ? "; RunnerDeps.promptSource is not configured" : ""}) — refusing to recompose a hybrid prompt`), { code: "prompt.snapshot_unavailable" });
35
+ const failUnavailable = (what, refusal) => {
36
+ const why = refusal !== undefined ? `the source's copy is ${refusalClause(refusal)}` : `artifact source miss / not verifiable here${deps.promptSource === undefined ? "; RunnerDeps.promptSource is not configured" : ""}`;
37
+ throw Object.assign(new Error(`prompt snapshot unavailable: ${what} (${why}) — refusing to recompose a hybrid prompt`), { code: "prompt.snapshot_unavailable" });
36
38
  };
37
39
  const resolvePinned = async (digest, sourceRevision, via) => {
38
- const art = await deps.promptSource?.get(digest);
40
+ const told = {};
41
+ const art = await deps.promptSource?.get(digest, (r) => void (told.current = r));
39
42
  const adopted = art !== undefined ? adoptCenter(art, sourceRevision) : undefined;
40
- return adopted ?? failUnavailable(`${via} center prompt artifact ${digest.slice(0, 23)}… has no verified resolvable copy`);
43
+ if (adopted !== undefined)
44
+ return adopted;
45
+ const checked = art !== undefined ? verifyPromptArtifactBody(art) : undefined;
46
+ const refusal = told.current ?? (checked !== undefined && !checked.ok ? { code: checked.code, reason: checked.reason } : undefined);
47
+ if (refusal !== undefined)
48
+ deps.onError?.(new Error(storedArtifactLine(digest, refusal, `the session fails prompt.snapshot_unavailable (a hybrid prompt is never recomposed)`)), { phase: "prompt-constitution", sessionId });
49
+ return failUnavailable(`${via} center prompt artifact ${digest.slice(0, 23)}… has no verified resolvable copy`, refusal);
41
50
  };
42
51
  const pinned = stored?.kind === "pin" ? stored.descriptor : undefined;
43
52
  const retiredCenterPin = stored?.kind === "legacy" && stored.centerPinned;
@@ -76,14 +85,8 @@ export async function preparePromptAssembly(input) {
76
85
  promptProfile,
77
86
  fableMitigations,
78
87
  };
79
- const selectors = selectorsOf(facts, host);
80
- const selectorRecord = {
81
- ...(binding !== undefined ? { binding } : {}),
82
- traits: [...selectors.traits].sort(),
83
- host: { role: selectors.host.role, form: selectors.host.form },
84
- facts: { ...selectors.facts },
85
- };
86
- const centerOverlays = centerOverlaysOf(centerAdoption, binding);
88
+ const selectorRecord = selectorRecordOf(selectorsOf(facts, host, binding));
89
+ const centerOverlays = centerOverlaysOf(centerAdoption);
87
90
  const lowerAdds = new Map();
88
91
  for (const { overlay } of [...sugar, ...centerOverlays])
89
92
  if (overlay.op === "add")
@@ -91,16 +94,17 @@ export async function preparePromptAssembly(input) {
91
94
  const composed = await raisePromptCompose(modBus, {
92
95
  model: model.id,
93
96
  catalog: describeCatalog().sections,
94
- selectors: { traits: [...selectors.traits], host: selectors.host, facts: selectors.facts },
97
+ selectors: selectorRecord,
95
98
  }, { lowerAdds, ...(input.signal !== undefined ? { signal: input.signal } : {}) });
96
99
  for (const w of composed.warnings)
97
100
  deps.onError?.(new Error(w), { phase: "prompt-constitution", sessionId });
98
101
  const taskOverlays = [...sugar, ...composed.task];
99
102
  const buildAssembleInputs = (center) => ({
100
103
  taskOverlays,
101
- centerOverlays: centerOverlaysOf(center, binding),
104
+ centerOverlays: centerOverlaysOf(center),
102
105
  deployOverlays: composed.deploy,
103
106
  host,
107
+ ...(binding !== undefined ? { binding } : {}),
104
108
  tools,
105
109
  facts,
106
110
  ...(envFacts.cwd !== undefined ? { workspaceFolder: envFacts.cwd } : {}),
@@ -27,6 +27,7 @@ import type { SourcedOverlay } from "../../prompt-assembly/overlay.js";
27
27
  import type { ContainmentFace } from "../../tools/fs/index.js";
28
28
  import type { LockedPreflight } from "../locked-config.js";
29
29
  import type { MaterializedMcp } from "../mcp.js";
30
+ import type { RosterBuilder } from "../tool-roster.js";
30
31
  import type { ResolvedRole } from "../roles.js";
31
32
  import type { RunnerDeps, TaskSpec } from "../types.js";
32
33
  import type { SessionTreeEntry } from "../../internal/harness-types.js";
@@ -37,8 +38,8 @@ import { type ContextWindowOverlay } from "../context-settings.js";
37
38
  export interface PreparePromptInputsInput {
38
39
  /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `systemPrompt` / `appendSystemPrompt` (handed whole to the
39
40
  * request-body sugar), `clientContext` (zone, email), `envFacts` (the deployment's sandbox + resume facts),
40
- * `compaction` (the within-task flag). Never mutated. */
41
- spec: Pick<TaskSpec, "systemPrompt" | "appendSystemPrompt" | "clientContext" | "envFacts" | "compaction">;
41
+ * `compaction` (the within-task flag), `excludeAllTools` (the valve's whole-face switch). Never mutated. */
42
+ spec: Pick<TaskSpec, "systemPrompt" | "appendSystemPrompt" | "clientContext" | "envFacts" | "compaction" | "excludeAllTools">;
42
43
  /** borrowed-readonly — deployment deps: `onError` (the operator lane: fail-closed advisory, cwd misalignment,
43
44
  * git-lane degrade), `modelKnowledgeCutoffs`. Receiver preserved (`deps.onError?.(…)`). */
44
45
  deps: Pick<RunnerDeps, "onError" | "modelKnowledgeCutoffs">;
@@ -61,8 +62,9 @@ export interface PreparePromptInputsInput {
61
62
  resolvedReadFace: ContainmentFace | undefined;
62
63
  /** borrowed-readonly — the resolved write face (the open-writes flag + the structured env fact). */
63
64
  resolvedWriteFace: ContainmentFace | undefined;
64
- /** borrowed-readonly — the frozen tool face; only `defer` is read (the Workflow short-pointer flag). */
65
- toolFaceSnapshot: Pick<ToolFaceSnapshot, "defer">;
65
+ /** borrowed-readonly — the frozen tool face; `defer` (the Workflow short-pointer flag) and `selector` (with the spec's whole-face
66
+ * switch, the valve's reading that decides which servers' instructions are taught). */
67
+ toolFaceSnapshot: Pick<ToolFaceSnapshot, "defer" | "selector">;
66
68
  /** borrowed-readonly — the resolved prompt profile (rides the flags to the legacy ctx). */
67
69
  promptProfile: "simple" | "classic";
68
70
  /** borrowed-readonly — the fable-mitigations profile axis (rides the flags with the profile). */
@@ -81,8 +83,12 @@ export interface PreparePromptInputsInput {
81
83
  };
82
84
  /** borrowed-readonly — the resolved role; only its `systemPrompt` preset is read (second source). */
83
85
  resolvedRole: Pick<ResolvedRole, "systemPrompt">;
84
- /** borrowed-readonly — the materialized MCP roster; only `serverInstructions` (the disposed entries) is read. */
85
- mcp: Pick<MaterializedMcp, "serverInstructions">;
86
+ /** borrowed-mutable — the materialized MCP roster; `serverInstructions` (the disposed entries) and `tools` (each server's listing: did it list
87
+ * any tool) are read, and the instructions lane's ledger is stated: the block is what the model is told at the run's start. */
88
+ mcp: Pick<MaterializedMcp, "serverInstructions" | "tools" | "instructionsDelta">;
89
+ /** borrowed-readonly — the run's roster as the protocol-tools phase mounted it (the deployment's overlay applied, the valve not yet):
90
+ * the tools the teaching answer reads, with the valve's predicate. */
91
+ roster: Pick<RosterBuilder, "tools">;
86
92
  /** borrowed-readonly — the resolved model (id for the env line + the cutoff lookup). */
87
93
  model: Model;
88
94
  /** borrowed-readonly — the run's external-content-target fold (the single-source disclosure fact). */
@@ -1,5 +1,7 @@
1
1
  import { formatLocalDate, isValidTimeZone } from "../../prompts/default.js";
2
2
  import { sugarOverlays } from "../../prompt-assembly/sugar.js";
3
+ import { valveLeaves } from "../../agents/tool-filter.js";
4
+ import { instructionsTaught, toldAtStart } from "../mounted-mcp.js";
3
5
  import { isIsolated } from "../remote-env.js";
4
6
  import { readDateAnchor } from "./announce-once-ledger.js";
5
7
  import { probeGitStatusLane } from "./git-status-frame.js";
@@ -31,9 +33,10 @@ export async function preparePromptInputs(input) {
31
33
  isSubagent: delegation.isNonForkChild,
32
34
  };
33
35
  const sugar = sugarOverlays({ spec, resolvedRole, ...(internals !== undefined ? { internals } : {}) });
34
- const mcpInstructionsBlock = mcp.serverInstructions.length
35
- ? `# MCP Server Instructions\n${mcp.serverInstructions.map(renderMcpInstructionsEntry).join("\n\n")}`
36
- : undefined;
36
+ const valveLeft = valveLeaves(spec, toolFaceSnapshot.selector);
37
+ const taught = mcp.serverInstructions.filter((entry) => instructionsTaught(valveLeft, entry.server, input.roster.tools, mcp.tools));
38
+ toldAtStart(mcp.instructionsDelta, new Set(taught.map((entry) => entry.server)));
39
+ const mcpInstructionsBlock = taught.length ? `# MCP Server Instructions\n${taught.map(renderMcpInstructionsEntry).join("\n\n")}` : undefined;
37
40
  const userTz = spec.clientContext?.timeZone;
38
41
  const tzValid = userTz !== undefined && isValidTimeZone(userTz);
39
42
  const dateAnchor = await readDateAnchor(session);
@@ -32,12 +32,14 @@ import type { AnnounceOnceSink, IrreversibilityTier, Prepared, RollbackStack, To
32
32
  import { RosterBuilder, type ToolRosterDeltaSeat } from "../tool-roster.js";
33
33
  import type { LegModBus } from "../mods/stations.js";
34
34
  export interface PrepareProtocolToolsInput {
35
- /** borrowed-readonly — the frozen preflight; only `mcp` (the declared server entries) is read: the
36
- * materialize input, the RefreshMcpTools mount predicate, and whether a real client was acquired. */
37
- lockedPreflight: Pick<LockedPreflight, "mcp">;
35
+ /** borrowed-readonly — the frozen preflight; `mcp` (the declared server entries: the materialize input, the RefreshMcpTools
36
+ * mount predicate, and whether a real client was acquired) and `a2a` (the declared peers the door judged: the materialize
37
+ * input and whether a handle was acquired) are read. */
38
+ lockedPreflight: Pick<LockedPreflight, "mcp" | "a2a">;
38
39
  /** borrowed-readonly — the REBOUND spec. Read: `principal` (the MCP header / the A2A identity), `tools`
39
- * (the caller names both reserved-name guards check against), `a2a` (the peer entries). */
40
- spec: Pick<TaskSpec, "principal" | "tools" | "a2a">;
40
+ * (the caller names both reserved-name guards check against), `excludeAllTools` (the valve's whole-face switch: the live roster lane's
41
+ * reading, and the first-dial gate). */
42
+ spec: Pick<TaskSpec, "principal" | "tools" | "excludeAllTools">;
41
43
  /** borrowed-readonly — the deployment seats the materializers and their operator faces read: `onElicit`
42
44
  * and `mcpImageResizer` ride into the MCP intake; `onError` takes the A2A warnings (no ledger — a peer is
43
45
  * re-read every prepare); `mcpRevocations` + `onNotice` make the revocation wiring. */
@@ -1,6 +1,8 @@
1
1
  import { Type } from "typebox";
2
2
  import { materializeMcpTools } from "../mcp.js";
3
- import { applyMcpRosterDelta, commitMcpRosterChanges, redialMcpServer } from "./mcp-redial.js";
3
+ import { applyMcpRosterDelta, commitMcpRosterChanges, redialMcpServer, rosterHolding, settledOnRoster } from "./mcp-redial.js";
4
+ import { valveLeaves, valveLeavesPeer } from "../../agents/tool-filter.js";
5
+ import { resourceFacesLeft } from "../mounted-mcp.js";
4
6
  import { materializeA2aTools } from "../a2a.js";
5
7
  import { deliverEngineNotice } from "../types.js";
6
8
  import { RosterBuilder } from "../tool-roster.js";
@@ -24,8 +26,11 @@ export async function prepareProtocolTools(input) {
24
26
  if (overPeer !== undefined) {
25
27
  throw Object.assign(new Error(`MCP server name ${JSON.stringify(overPeer.name.slice(0, 40) + "…")} is ${overPeer.name.length} characters; a peer rides every roster row of its tools and the roster's bound on a name is ${TOOL_WIRE_NAME_MAX_CHARS}.`), { code: "config.tool_name_too_long" });
26
28
  }
29
+ const serverReached = valveLeavesPeer(spec, toolFaceSnapshot.selector);
30
+ const leaves = valveLeaves(spec, toolFaceSnapshot.selector);
31
+ const resourcesReached = resourceFacesLeft(leaves);
27
32
  const mcp = lockedPreflight.mcp?.length
28
- ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId), mcpPool === undefined ? undefined : { pool: mcpPool.pool, ownsPool: mcpPool.owned }, sectionDisposerOf(input.modBus, abortController.signal))
33
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId), mcpPool === undefined ? undefined : { pool: mcpPool.pool, ownsPool: mcpPool.owned }, sectionDisposerOf(input.modBus, abortController.signal), (entry) => resourcesReached || serverReached(mintNamespacePrefix(MCP_NAMESPACE, entry.name)))
29
34
  :
30
35
  { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], reconnect: async (server) => ({ server, prefix: mintNamespacePrefix(MCP_NAMESPACE, server), outcome: "not_declared", reason: "this task declared no MCP servers", toolCount: 0, added: [], removed: [] }), dispose: async () => { } };
31
36
  if (lockedPreflight.mcp?.length)
@@ -69,7 +74,7 @@ export async function prepareProtocolTools(input) {
69
74
  const rebuildHarnessToolsRef = {};
70
75
  const contentOriginWrapRef = {};
71
76
  const toolCallGateArmedRef = { armed: false };
72
- const rosterApply = { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes: (axes, label) => foldProtocolAxes(axes, label), toolEffects, axisExplicitNegatives, roster };
77
+ const rosterApply = { spec, toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes: (axes, label) => foldProtocolAxes(axes, label), toolEffects, axisExplicitNegatives, roster, declared: lockedPreflight.mcp ?? [], instructionsDelta: mcp.instructionsDelta };
73
78
  const rosterCommit = { rebuildHarnessToolsRef, rosterSeat, toolCallGateArmedRef, irreversibleTools, egressTools };
74
79
  const redialInput = { ...rosterApply, ...rosterCommit, mcp, deps, sessionId, runId, irreversibilityTier };
75
80
  const mcpRedial = lockedPreflight.mcp?.length ? (server) => redialMcpServer(redialInput, server) : undefined;
@@ -88,14 +93,17 @@ export async function prepareProtocolTools(input) {
88
93
  const lines = [];
89
94
  let changed = false;
90
95
  let anyActiveFailure = false;
96
+ const reported = [];
91
97
  for (const r of results) {
92
98
  if (r.tools === undefined) {
99
+ reported.push(settledOnRoster(r, rosterHolding(rosterApply, r.server), roster));
93
100
  lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
94
101
  if (r.status === "failed")
95
102
  anyActiveFailure = true;
96
103
  continue;
97
104
  }
98
- const applied = applyMcpRosterDelta(rosterApply, { server: r.server, prefix: r.prefix, tools: r.tools, axes: r.axes ?? [], dropped: r.dropped ?? [] });
105
+ const applied = applyMcpRosterDelta(rosterApply, { server: r.server, prefix: r.prefix, tools: r.tools, axes: r.axes ?? [], dropped: r.dropped ?? [], ...(r.instructions !== undefined ? { instructions: r.instructions } : {}) });
106
+ reported.push(settledOnRoster(r, applied, roster));
99
107
  if (!applied.applied) {
100
108
  lines.push(`${r.server}: failed (${applied.foldError})`);
101
109
  anyActiveFailure = true;
@@ -103,16 +111,15 @@ export async function prepareProtocolTools(input) {
103
111
  }
104
112
  changed = true;
105
113
  const detail = [];
106
- const shownAdded = r.added.filter((n) => !applied.excluded.includes(n));
107
- if (shownAdded.length > 0)
108
- detail.push(`added: ${shownAdded.join(", ")}`);
109
- if (r.removed.length > 0)
110
- detail.push(`removed: ${r.removed.join(", ")}`);
114
+ if (applied.added.length > 0)
115
+ detail.push(`added: ${applied.added.join(", ")}`);
116
+ if (applied.removed.length > 0)
117
+ detail.push(`removed: ${applied.removed.join(", ")}`);
111
118
  if (applied.excluded.length > 0)
112
119
  detail.push(`excluded by deployment config (not mounted): ${applied.excluded.join(", ")}`);
113
120
  if ((r.dropped?.length ?? 0) > 0)
114
121
  detail.push(`dropped: ${r.dropped.map((d) => `${d.tool} (${d.reason.length > 90 ? `${d.reason.slice(0, 90)}…` : d.reason})`).join("; ")}`);
115
- lines.push(`${r.server}: refreshed — ${applied.pushed} tool${applied.pushed === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
122
+ lines.push(`${r.server}: refreshed — ${applied.mounted.length} tool${applied.mounted.length === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
116
123
  }
117
124
  if (changed) {
118
125
  const ungated = await commitMcpRosterChanges(rosterCommit);
@@ -122,7 +129,7 @@ export async function prepareProtocolTools(input) {
122
129
  const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
123
130
  return {
124
131
  content: [{ type: "text", text }],
125
- details: { results },
132
+ details: { results: reported },
126
133
  terminate: false,
127
134
  ...(anyActiveFailure && !changed ? { isError: true } : {}),
128
135
  };
@@ -157,7 +164,7 @@ export async function prepareProtocolTools(input) {
157
164
  }
158
165
  };
159
166
  foldProtocolAxes(mcp.toolAxes, "MCP");
160
- const a2aDeclared = spec.a2a?.length ? spec.a2a : undefined;
167
+ const a2aDeclared = lockedPreflight.a2a;
161
168
  const a2a = a2aDeclared
162
169
  ? await materializeA2aTools(a2aDeclared, spec.principal, abortController.signal)
163
170
  : { tools: [], toolAxes: [], warnings: [], statuses: [], refresh: async () => [], dispose: async () => { } };