agentfootprint 9.112.2 → 9.114.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 (282) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/CLAUDE.md +2 -1
  3. package/dist/adapters/observability/audit.js +8 -0
  4. package/dist/adapters/observability/audit.js.map +1 -1
  5. package/dist/adapters/observability/otel.js +44 -2
  6. package/dist/adapters/observability/otel.js.map +1 -1
  7. package/dist/adapters/observability/xray.js +19 -0
  8. package/dist/adapters/observability/xray.js.map +1 -1
  9. package/dist/adapters/types.js.map +1 -1
  10. package/dist/core/Agent.js +11 -1
  11. package/dist/core/Agent.js.map +1 -1
  12. package/dist/core/agent/composeRequest.js +19 -11
  13. package/dist/core/agent/composeRequest.js.map +1 -1
  14. package/dist/core/agent/coverage/absent.js +163 -7
  15. package/dist/core/agent/coverage/absent.js.map +1 -1
  16. package/dist/core/agent/coverage/answer.js +3 -1
  17. package/dist/core/agent/coverage/answer.js.map +1 -1
  18. package/dist/core/agent/coverage/read.js +18 -12
  19. package/dist/core/agent/coverage/read.js.map +1 -1
  20. package/dist/core/agent/findings/ledger.js +27 -5
  21. package/dist/core/agent/findings/ledger.js.map +1 -1
  22. package/dist/core/agent/findings/offer.js +29 -9
  23. package/dist/core/agent/findings/offer.js.map +1 -1
  24. package/dist/core/agent/findings/serve.js +76 -7
  25. package/dist/core/agent/findings/serve.js.map +1 -1
  26. package/dist/core/agent/findings/types.js.map +1 -1
  27. package/dist/core/agent/findings/unsettled.js +283 -0
  28. package/dist/core/agent/findings/unsettled.js.map +1 -0
  29. package/dist/core/agent/stages/callLLM.js +30 -8
  30. package/dist/core/agent/stages/callLLM.js.map +1 -1
  31. package/dist/core/agent/stages/route.js +10 -2
  32. package/dist/core/agent/stages/route.js.map +1 -1
  33. package/dist/core/agent/stages/toolCalls.js +365 -68
  34. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  35. package/dist/core/agent/stages/window.js +5 -2
  36. package/dist/core/agent/stages/window.js.map +1 -1
  37. package/dist/core/agent/toolDispatch.js +4 -1
  38. package/dist/core/agent/toolDispatch.js.map +1 -1
  39. package/dist/core/agent/toolResultCap.js +0 -0
  40. package/dist/core/agent/toolResultCap.js.map +1 -1
  41. package/dist/core/agent/window/ledgerFactPins.js +13 -2
  42. package/dist/core/agent/window/ledgerFactPins.js.map +1 -1
  43. package/dist/core/agent/window/toolNames.js +17 -2
  44. package/dist/core/agent/window/toolNames.js.map +1 -1
  45. package/dist/core/checkin.js +5 -1
  46. package/dist/core/checkin.js.map +1 -1
  47. package/dist/core/inputRequest.js +32 -2
  48. package/dist/core/inputRequest.js.map +1 -1
  49. package/dist/core/pause.js +13 -1
  50. package/dist/core/pause.js.map +1 -1
  51. package/dist/core/runCheckpoint.js +26 -2
  52. package/dist/core/runCheckpoint.js.map +1 -1
  53. package/dist/esm/adapters/observability/audit.d.ts +8 -0
  54. package/dist/esm/adapters/observability/audit.js +8 -0
  55. package/dist/esm/adapters/observability/audit.js.map +1 -1
  56. package/dist/esm/adapters/observability/otel.d.ts +17 -2
  57. package/dist/esm/adapters/observability/otel.js +44 -2
  58. package/dist/esm/adapters/observability/otel.js.map +1 -1
  59. package/dist/esm/adapters/observability/xray.d.ts +8 -0
  60. package/dist/esm/adapters/observability/xray.js +19 -0
  61. package/dist/esm/adapters/observability/xray.js.map +1 -1
  62. package/dist/esm/adapters/types.d.ts +49 -0
  63. package/dist/esm/adapters/types.js.map +1 -1
  64. package/dist/esm/core/Agent.js +11 -1
  65. package/dist/esm/core/Agent.js.map +1 -1
  66. package/dist/esm/core/agent/composeRequest.d.ts +10 -7
  67. package/dist/esm/core/agent/composeRequest.js +19 -11
  68. package/dist/esm/core/agent/composeRequest.js.map +1 -1
  69. package/dist/esm/core/agent/coverage/absent.d.ts +31 -4
  70. package/dist/esm/core/agent/coverage/absent.js +160 -6
  71. package/dist/esm/core/agent/coverage/absent.js.map +1 -1
  72. package/dist/esm/core/agent/coverage/answer.js +3 -1
  73. package/dist/esm/core/agent/coverage/answer.js.map +1 -1
  74. package/dist/esm/core/agent/coverage/index.d.ts +1 -1
  75. package/dist/esm/core/agent/coverage/read.d.ts +12 -2
  76. package/dist/esm/core/agent/coverage/read.js +19 -13
  77. package/dist/esm/core/agent/coverage/read.js.map +1 -1
  78. package/dist/esm/core/agent/coverage/types.d.ts +42 -0
  79. package/dist/esm/core/agent/findings/ledger.d.ts +23 -6
  80. package/dist/esm/core/agent/findings/ledger.js +27 -5
  81. package/dist/esm/core/agent/findings/ledger.js.map +1 -1
  82. package/dist/esm/core/agent/findings/offer.d.ts +23 -6
  83. package/dist/esm/core/agent/findings/offer.js +27 -8
  84. package/dist/esm/core/agent/findings/offer.js.map +1 -1
  85. package/dist/esm/core/agent/findings/serve.d.ts +19 -6
  86. package/dist/esm/core/agent/findings/serve.js +77 -8
  87. package/dist/esm/core/agent/findings/serve.js.map +1 -1
  88. package/dist/esm/core/agent/findings/types.d.ts +81 -1
  89. package/dist/esm/core/agent/findings/types.js.map +1 -1
  90. package/dist/esm/core/agent/findings/unsettled.d.ts +128 -0
  91. package/dist/esm/core/agent/findings/unsettled.js +278 -0
  92. package/dist/esm/core/agent/findings/unsettled.js.map +1 -0
  93. package/dist/esm/core/agent/stages/callLLM.js +30 -8
  94. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  95. package/dist/esm/core/agent/stages/route.js +10 -2
  96. package/dist/esm/core/agent/stages/route.js.map +1 -1
  97. package/dist/esm/core/agent/stages/toolCalls.d.ts +90 -2
  98. package/dist/esm/core/agent/stages/toolCalls.js +362 -68
  99. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  100. package/dist/esm/core/agent/stages/window.js +5 -2
  101. package/dist/esm/core/agent/stages/window.js.map +1 -1
  102. package/dist/esm/core/agent/toolDispatch.d.ts +4 -1
  103. package/dist/esm/core/agent/toolDispatch.js +4 -1
  104. package/dist/esm/core/agent/toolDispatch.js.map +1 -1
  105. package/dist/esm/core/agent/toolResultCap.d.ts +4 -1
  106. package/dist/esm/core/agent/toolResultCap.js +0 -0
  107. package/dist/esm/core/agent/toolResultCap.js.map +1 -1
  108. package/dist/esm/core/agent/types.d.ts +5 -0
  109. package/dist/esm/core/agent/window/ledgerFactPins.d.ts +10 -1
  110. package/dist/esm/core/agent/window/ledgerFactPins.js +13 -2
  111. package/dist/esm/core/agent/window/ledgerFactPins.js.map +1 -1
  112. package/dist/esm/core/agent/window/strategy.d.ts +2 -1
  113. package/dist/esm/core/agent/window/toolNames.d.ts +16 -1
  114. package/dist/esm/core/agent/window/toolNames.js +17 -2
  115. package/dist/esm/core/agent/window/toolNames.js.map +1 -1
  116. package/dist/esm/core/checkin.js +5 -1
  117. package/dist/esm/core/checkin.js.map +1 -1
  118. package/dist/esm/core/inputRequest.d.ts +20 -1
  119. package/dist/esm/core/inputRequest.js +32 -2
  120. package/dist/esm/core/inputRequest.js.map +1 -1
  121. package/dist/esm/core/pause.d.ts +13 -1
  122. package/dist/esm/core/pause.js +13 -1
  123. package/dist/esm/core/pause.js.map +1 -1
  124. package/dist/esm/core/runCheckpoint.js +26 -2
  125. package/dist/esm/core/runCheckpoint.js.map +1 -1
  126. package/dist/esm/events/payloads.d.ts +76 -1
  127. package/dist/esm/index.d.ts +2 -2
  128. package/dist/esm/index.js.map +1 -1
  129. package/dist/esm/lib/bug-report/transcript.d.ts +5 -0
  130. package/dist/esm/lib/bug-report/transcript.js +1 -0
  131. package/dist/esm/lib/bug-report/transcript.js.map +1 -1
  132. package/dist/esm/lib/semantics/projection.js +1 -0
  133. package/dist/esm/lib/semantics/projection.js.map +1 -1
  134. package/dist/esm/lib/time-travel/receipt.d.ts +4 -3
  135. package/dist/esm/lib/time-travel/receipt.js +4 -3
  136. package/dist/esm/lib/time-travel/receipt.js.map +1 -1
  137. package/dist/esm/lib/time-travel/servedView.js +13 -9
  138. package/dist/esm/lib/time-travel/servedView.js.map +1 -1
  139. package/dist/esm/lib/trace-toolpack/traceToolpack.js +130 -17
  140. package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
  141. package/dist/esm/memory/causal/evidenceRecorder.d.ts +2 -0
  142. package/dist/esm/memory/causal/evidenceRecorder.js +13 -0
  143. package/dist/esm/memory/causal/evidenceRecorder.js.map +1 -1
  144. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.d.ts +2 -0
  145. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js +11 -0
  146. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
  147. package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +10 -1
  148. package/dist/esm/recorders/observability/BoundaryRecorder.js +9 -1
  149. package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
  150. package/dist/esm/recorders/observability/FlowchartRecorder.d.ts +4 -1
  151. package/dist/esm/recorders/observability/FlowchartRecorder.js +13 -1
  152. package/dist/esm/recorders/observability/FlowchartRecorder.js.map +1 -1
  153. package/dist/esm/recorders/observability/LiveStateRecorder.d.ts +2 -0
  154. package/dist/esm/recorders/observability/LiveStateRecorder.js +9 -0
  155. package/dist/esm/recorders/observability/LiveStateRecorder.js.map +1 -1
  156. package/dist/esm/recorders/observability/RouteRecorder.js +5 -0
  157. package/dist/esm/recorders/observability/RouteRecorder.js.map +1 -1
  158. package/dist/esm/recorders/observability/StatusRecorder.js +7 -0
  159. package/dist/esm/recorders/observability/StatusRecorder.js.map +1 -1
  160. package/dist/esm/recorders/observability/ToolChoiceRecorder.js +5 -0
  161. package/dist/esm/recorders/observability/ToolChoiceRecorder.js.map +1 -1
  162. package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +18 -2
  163. package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
  164. package/dist/esm/recorders/observability/status/statusTemplates.js +5 -0
  165. package/dist/esm/recorders/observability/status/statusTemplates.js.map +1 -1
  166. package/dist/esm/security/extractSequence.d.ts +36 -5
  167. package/dist/esm/security/extractSequence.js +99 -26
  168. package/dist/esm/security/extractSequence.js.map +1 -1
  169. package/dist/index.js.map +1 -1
  170. package/dist/lib/bug-report/transcript.js +1 -0
  171. package/dist/lib/bug-report/transcript.js.map +1 -1
  172. package/dist/lib/semantics/projection.js +1 -0
  173. package/dist/lib/semantics/projection.js.map +1 -1
  174. package/dist/lib/time-travel/receipt.js +4 -3
  175. package/dist/lib/time-travel/receipt.js.map +1 -1
  176. package/dist/lib/time-travel/servedView.js +13 -9
  177. package/dist/lib/time-travel/servedView.js.map +1 -1
  178. package/dist/lib/trace-toolpack/traceToolpack.js +130 -17
  179. package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
  180. package/dist/memory/causal/evidenceRecorder.js +13 -0
  181. package/dist/memory/causal/evidenceRecorder.js.map +1 -1
  182. package/dist/recorders/observability/AgentThinkingTraceRecorder.js +11 -0
  183. package/dist/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
  184. package/dist/recorders/observability/BoundaryRecorder.js +9 -1
  185. package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
  186. package/dist/recorders/observability/FlowchartRecorder.js +13 -1
  187. package/dist/recorders/observability/FlowchartRecorder.js.map +1 -1
  188. package/dist/recorders/observability/LiveStateRecorder.js +9 -0
  189. package/dist/recorders/observability/LiveStateRecorder.js.map +1 -1
  190. package/dist/recorders/observability/RouteRecorder.js +5 -0
  191. package/dist/recorders/observability/RouteRecorder.js.map +1 -1
  192. package/dist/recorders/observability/StatusRecorder.js +7 -0
  193. package/dist/recorders/observability/StatusRecorder.js.map +1 -1
  194. package/dist/recorders/observability/ToolChoiceRecorder.js +5 -0
  195. package/dist/recorders/observability/ToolChoiceRecorder.js.map +1 -1
  196. package/dist/recorders/observability/commentary/commentaryTemplates.js +18 -2
  197. package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
  198. package/dist/recorders/observability/status/statusTemplates.js +5 -0
  199. package/dist/recorders/observability/status/statusTemplates.js.map +1 -1
  200. package/dist/security/extractSequence.js +99 -26
  201. package/dist/security/extractSequence.js.map +1 -1
  202. package/dist/types/adapters/observability/audit.d.ts +8 -0
  203. package/dist/types/adapters/observability/audit.d.ts.map +1 -1
  204. package/dist/types/adapters/observability/otel.d.ts +17 -2
  205. package/dist/types/adapters/observability/otel.d.ts.map +1 -1
  206. package/dist/types/adapters/observability/xray.d.ts +8 -0
  207. package/dist/types/adapters/observability/xray.d.ts.map +1 -1
  208. package/dist/types/adapters/types.d.ts +49 -0
  209. package/dist/types/adapters/types.d.ts.map +1 -1
  210. package/dist/types/core/Agent.d.ts.map +1 -1
  211. package/dist/types/core/agent/composeRequest.d.ts +10 -7
  212. package/dist/types/core/agent/composeRequest.d.ts.map +1 -1
  213. package/dist/types/core/agent/coverage/absent.d.ts +31 -4
  214. package/dist/types/core/agent/coverage/absent.d.ts.map +1 -1
  215. package/dist/types/core/agent/coverage/answer.d.ts.map +1 -1
  216. package/dist/types/core/agent/coverage/index.d.ts +1 -1
  217. package/dist/types/core/agent/coverage/index.d.ts.map +1 -1
  218. package/dist/types/core/agent/coverage/read.d.ts +12 -2
  219. package/dist/types/core/agent/coverage/read.d.ts.map +1 -1
  220. package/dist/types/core/agent/coverage/types.d.ts +42 -0
  221. package/dist/types/core/agent/coverage/types.d.ts.map +1 -1
  222. package/dist/types/core/agent/findings/ledger.d.ts +23 -6
  223. package/dist/types/core/agent/findings/ledger.d.ts.map +1 -1
  224. package/dist/types/core/agent/findings/offer.d.ts +23 -6
  225. package/dist/types/core/agent/findings/offer.d.ts.map +1 -1
  226. package/dist/types/core/agent/findings/serve.d.ts +19 -6
  227. package/dist/types/core/agent/findings/serve.d.ts.map +1 -1
  228. package/dist/types/core/agent/findings/types.d.ts +81 -1
  229. package/dist/types/core/agent/findings/types.d.ts.map +1 -1
  230. package/dist/types/core/agent/findings/unsettled.d.ts +129 -0
  231. package/dist/types/core/agent/findings/unsettled.d.ts.map +1 -0
  232. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  233. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  234. package/dist/types/core/agent/stages/toolCalls.d.ts +90 -2
  235. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  236. package/dist/types/core/agent/stages/window.d.ts.map +1 -1
  237. package/dist/types/core/agent/toolDispatch.d.ts +4 -1
  238. package/dist/types/core/agent/toolDispatch.d.ts.map +1 -1
  239. package/dist/types/core/agent/toolResultCap.d.ts +4 -1
  240. package/dist/types/core/agent/toolResultCap.d.ts.map +1 -1
  241. package/dist/types/core/agent/types.d.ts +5 -0
  242. package/dist/types/core/agent/types.d.ts.map +1 -1
  243. package/dist/types/core/agent/window/ledgerFactPins.d.ts +10 -1
  244. package/dist/types/core/agent/window/ledgerFactPins.d.ts.map +1 -1
  245. package/dist/types/core/agent/window/strategy.d.ts +2 -1
  246. package/dist/types/core/agent/window/strategy.d.ts.map +1 -1
  247. package/dist/types/core/agent/window/toolNames.d.ts +16 -1
  248. package/dist/types/core/agent/window/toolNames.d.ts.map +1 -1
  249. package/dist/types/core/checkin.d.ts.map +1 -1
  250. package/dist/types/core/inputRequest.d.ts +20 -1
  251. package/dist/types/core/inputRequest.d.ts.map +1 -1
  252. package/dist/types/core/pause.d.ts +13 -1
  253. package/dist/types/core/pause.d.ts.map +1 -1
  254. package/dist/types/core/runCheckpoint.d.ts.map +1 -1
  255. package/dist/types/events/payloads.d.ts +76 -1
  256. package/dist/types/events/payloads.d.ts.map +1 -1
  257. package/dist/types/index.d.ts +2 -2
  258. package/dist/types/index.d.ts.map +1 -1
  259. package/dist/types/lib/bug-report/transcript.d.ts +5 -0
  260. package/dist/types/lib/bug-report/transcript.d.ts.map +1 -1
  261. package/dist/types/lib/semantics/projection.d.ts.map +1 -1
  262. package/dist/types/lib/time-travel/receipt.d.ts +4 -3
  263. package/dist/types/lib/time-travel/receipt.d.ts.map +1 -1
  264. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -1
  265. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
  266. package/dist/types/memory/causal/evidenceRecorder.d.ts +2 -0
  267. package/dist/types/memory/causal/evidenceRecorder.d.ts.map +1 -1
  268. package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts +2 -0
  269. package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts.map +1 -1
  270. package/dist/types/recorders/observability/BoundaryRecorder.d.ts +10 -1
  271. package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
  272. package/dist/types/recorders/observability/FlowchartRecorder.d.ts +4 -1
  273. package/dist/types/recorders/observability/FlowchartRecorder.d.ts.map +1 -1
  274. package/dist/types/recorders/observability/LiveStateRecorder.d.ts +2 -0
  275. package/dist/types/recorders/observability/LiveStateRecorder.d.ts.map +1 -1
  276. package/dist/types/recorders/observability/RouteRecorder.d.ts.map +1 -1
  277. package/dist/types/recorders/observability/ToolChoiceRecorder.d.ts.map +1 -1
  278. package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
  279. package/dist/types/recorders/observability/status/statusTemplates.d.ts.map +1 -1
  280. package/dist/types/security/extractSequence.d.ts +36 -5
  281. package/dist/types/security/extractSequence.d.ts.map +1 -1
  282. package/package.json +1 -1
@@ -9,7 +9,10 @@
9
9
  * payload so footprintjs captures a checkpoint.
10
10
  * • `resume` runs after the consumer supplies the human's answer.
11
11
  * Treats that answer as the paused tool's result, appends to
12
- * history, then continues the ReAct iteration loop.
12
+ * history, then continues the ReAct iteration loop. Calls the model
13
+ * batched AFTER the paused one were never dispatched; every resume
14
+ * path settles them with one fixed sentence each, a bracket and a
15
+ * count, and runs none of them ("── The batch settlement (9.113.0)").
13
16
  *
14
17
  * Dispatch resolution order (9.92.0 — DISPATCH FOLLOWS THE OFFER):
15
18
  * 0. A name on THIS epoch's wire resolves to the party whose contract the
@@ -72,9 +75,10 @@ import { agentToolDispatch } from '../toolDispatch.js';
72
75
  import { runToolChain, runToolAfterChain } from '../middleware/runChain.js';
73
76
  import { recordDecisions } from '../middleware/ledger.js';
74
77
  import { ownsReservedArgument, splitFindings } from '../findings/reserved.js';
75
- import { knownResults } from '../findings/offer.js';
78
+ import { isResultMessage, knownResults } from '../findings/offer.js';
76
79
  import { basisRowFrom, foldLedger, recordFindings, standingRowsFrom, } from '../findings/ledger.js';
77
80
  import { contingentRowsOf, groundedArgumentValues, hasSetAsideStanding, } from '../findings/contingent.js';
81
+ import { withUnsettledRows } from '../findings/unsettled.js';
78
82
  import { evidenceFromHistory, exemptFromRun, } from '../evidence/evidenceIndex.js';
79
83
  import { noteRepeatedCall, repeatedCallLedgers } from '../repeatedCall.js';
80
84
  import { formatToolArgIssues, validateToolArgs, } from '../toolArgsValidation.js';
@@ -448,6 +452,127 @@ function argsForPausedCall(scope, toolCallId, peel = false) {
448
452
  function appendBatchResult(scope, entry) {
449
453
  scope.toolResults = [...(scope.toolResults ?? []), entry];
450
454
  }
455
+ /**
456
+ * THE PAUSED BATCH (9.113.0) — the turn that proposed the paused call, and
457
+ * the calls on it that a pause left un-dispatched.
458
+ *
459
+ * The batch loop in `execute` dispatches a turn's calls one at a time, in the
460
+ * order the model proposed them, and every pause RETURNS from inside it: the
461
+ * checkpoint is taken on call k, and calls k+1..N were never reached — no
462
+ * gate judged them, no tool ran, no bracket opened. The resume paths used to
463
+ * answer call k and stop, so the next request carried N `tool_use` blocks and
464
+ * fewer `tool_result`s (a provider builds results only from `role: 'tool'`
465
+ * messages), and `iteration_end` counted one call.
466
+ *
467
+ * READ FROM `history`, NEVER CARRIED. Every pause site commits
468
+ * `scope.history = newHistory` before it returns, so the assistant turn that
469
+ * proposed the batch is on the checkpoint — and it is the very message a
470
+ * provider turns into the `tool_use` blocks the settlement must answer. It is
471
+ * therefore the one owner of "which calls does the next request carry", and
472
+ * the checkpoint gains no key: a checkpoint written before this release
473
+ * settles the same way. The window never removes the turn holding the paused
474
+ * call (`../window/turns.ts` · `refusalFor`, its `paused-tool` arm).
475
+ *
476
+ * The LATEST turn proposing the id is the paused one — the pause site commits
477
+ * that turn last, and a provider may reuse an id across turns. No such turn:
478
+ * `size: 0` and nothing to settle, the only answer a missing turn supports.
479
+ *
480
+ * A call after the paused one that history ALREADY answers — a `role: 'tool'`
481
+ * message for its id after that turn — is not un-dispatched, whoever wrote
482
+ * the answer. No library door writes one before the resume; a checkpoint is
483
+ * data, though, and an app may have patched results into a stored one.
484
+ * Settling such a call again would put a second result for its id on the
485
+ * next request, a shape a provider rejects.
486
+ */
487
+ export function pausedBatchOf(history, pausedToolCallId) {
488
+ for (let i = history.length - 1; i >= 0; i--) {
489
+ const message = history[i];
490
+ if (message?.role !== 'assistant')
491
+ continue;
492
+ const proposed = message.toolCalls ?? [];
493
+ const at = proposed.findIndex((call) => call.id === pausedToolCallId);
494
+ if (at < 0)
495
+ continue;
496
+ const answered = new Set(history.slice(i + 1).flatMap((m) => (m.role === 'tool' ? [m.toolCallId] : [])));
497
+ return {
498
+ size: proposed.length,
499
+ undispatched: proposed.slice(at + 1).filter((call) => !answered.has(call.id)),
500
+ };
501
+ }
502
+ return { size: 0, undispatched: [] };
503
+ }
504
+ // LENS · tool-result · persistent-history
505
+ // reads: toolName ← the un-dispatched call on the paused turn (`pausedBatchOf`); paused ← scope.pausedToolName + scope.pausedToolCallId, the checkpoint's own carriers
506
+ // law: may omit, never deny; every clause anchored to the call it was composed on; names no destination.
507
+ /**
508
+ * What a call the paused batch never dispatched gets back (9.113.0) — ONE
509
+ * fixed sentence, on all four resume paths.
510
+ *
511
+ * Every clause is a past fact about one finished event, anchored by name: the
512
+ * call this result answers ("that call"), the call the run paused on (by id
513
+ * and by tool), and the resume. It names no destination. The design page's draft
514
+ * (`docs/design/2026-09-honest-answer-ledger.md`, § 5.5, "The batch
515
+ * settlement") ended "propose it again if still needed" — an instruction about a LATER call,
516
+ * re-read on every call after this one, the tool-less wrap-up included.
517
+ * Whether the call is still needed is the model's to judge from the facts;
518
+ * the surfaces recomposed per request (the tool list, the system prompt) own
519
+ * the present tense. Precedents: the 9.86.1 resumed-call refusal in this file
520
+ * ("was not executed on that call, and the resumed dispatch had no second
521
+ * checkpoint…") and `../selfCallNotice.ts` · `selfCallNotice`.
522
+ *
523
+ * "The run paused ON call …", never "call … paused the run": on two of the
524
+ * four doors the pause was a gate's, not the call's — a middleware's `ask`, a
525
+ * credential's consent need — and on all four the run paused AT that call.
526
+ *
527
+ * The sentence is for the MODEL. The same fact as data rides the settled
528
+ * message as `LLMMessage.notDispatched` and both of its brackets as
529
+ * `notDispatched` (`notDispatchedMarker`), and a reader asking "did this call
530
+ * run?" reads that — never this text.
531
+ *
532
+ * Exported for the producer registry (`test/modelFacingSurfaces.test.ts`).
533
+ */
534
+ export function notDispatchedResult(toolName, paused) {
535
+ return (`Tool '${toolName}' was not executed on that call: the run paused on call ` +
536
+ `'${paused.toolCallId}' to '${paused.toolName}', earlier in the same batch, and resumed ` +
537
+ `without executing the calls that followed it in that batch.`);
538
+ }
539
+ /**
540
+ * The same fact as DATA (9.113.0) — the ONE composer of the settlement's
541
+ * marker, `{ pausedCall: { toolCallId, toolName } }`. The settled call's
542
+ * history message (`LLMMessage.notDispatched`) and both of its brackets
543
+ * (`ToolStartPayload.notDispatched`, `ToolEndPayload.notDispatched`) each take
544
+ * a fresh copy from here, so the three carriers can never disagree and no
545
+ * reader holds an object another reader holds.
546
+ */
547
+ function notDispatchedMarker(paused) {
548
+ return { pausedCall: { toolCallId: paused.toolCallId, toolName: paused.toolName } };
549
+ }
550
+ /**
551
+ * The empty-lookup check's PRODUCER CORPUS (9.77.0) — every `role: 'tool'`
552
+ * RESULT in `history` from a tool the judged one names in `argumentsFrom`,
553
+ * as the run served it. The caller hands the history the judged call was
554
+ * served (the call itself is pushed after it), so a producer called earlier in
555
+ * the same batch is already in it — which is right: the model can only have
556
+ * taken a value from a result it had already been served.
557
+ *
558
+ * A message the batch settlement wrote (9.113.0) is no result: its ground
559
+ * never ran, so it produced nothing to take a value from. Read off the marker
560
+ * (`../findings/offer.ts` · `isResultMessage`), never the sentence.
561
+ *
562
+ * Exported for its unit test (`test/integrity/emptyLookup.test.ts`).
563
+ */
564
+ export function producerCorpusOf(history, argumentsFrom) {
565
+ const produced = [];
566
+ for (const message of history) {
567
+ if (!isResultMessage(message))
568
+ continue;
569
+ const name = message.toolName;
570
+ if (typeof name !== 'string' || !argumentsFrom.includes(name))
571
+ continue;
572
+ produced.push({ toolName: name, text: message.content });
573
+ }
574
+ return produced;
575
+ }
451
576
  /**
452
577
  * The judge's turn on ONE landed result (9.104.0) — called at the five
453
578
  * places a result joins the batch (the execute loop and the four resume
@@ -710,6 +835,13 @@ export function buildToolCallsHandler(deps) {
710
835
  checked,
711
836
  ...(notChecked.length > 0 && { notChecked }),
712
837
  ...(cannotCover.length > 0 && { cannotCover }),
838
+ // The suggestion, as declared (9.113.0): the sentence, and the
839
+ // typed tool as a copy (`tryInsteadToolOfAbsence`). The EVENT only:
840
+ // it is advice about a call not yet made, not ground the answer
841
+ // stands on, so the tracked row below and the block composed from
842
+ // it never carry it.
843
+ ...(facts.tryInstead !== undefined && { tryInstead: facts.tryInstead }),
844
+ ...(facts.tryInsteadTool !== undefined && { tryInsteadTool: facts.tryInsteadTool }),
713
845
  });
714
846
  }
715
847
  else {
@@ -777,20 +909,9 @@ export function buildToolCallsHandler(deps) {
777
909
  ledger?.note('empty-lookup', 'write', 'not-applicable');
778
910
  return;
779
911
  }
780
- // The producer corpus: every earlier `role: 'tool'` message from a tool
781
- // THIS tool declares as one of its grounds. `history` here excludes the
782
- // call being judged (it is pushed after), and a producer called earlier
783
- // in the same batch is already in it — which is right: the model can only
784
- // have taken a value from a result it had already been served.
785
- const produced = [];
786
- for (const message of history) {
787
- if (message.role !== 'tool')
788
- continue;
789
- const name = message.toolName;
790
- if (typeof name !== 'string' || !argumentsFrom.includes(name))
791
- continue;
792
- produced.push({ toolName: name, text: message.content });
793
- }
912
+ // The producer corpus: the earlier RESULTS from a tool THIS tool declares
913
+ // as one of its grounds — never a settled message (`producerCorpusOf`).
914
+ const produced = producerCorpusOf(history, argumentsFrom);
794
915
  const { findings, disposition } = emptyLookupOf({
795
916
  toolName: call.toolName,
796
917
  toolCallId: call.toolCallId,
@@ -805,6 +926,45 @@ export function buildToolCallsHandler(deps) {
805
926
  ledger?.note('empty-lookup', 'write', disposition, disposition === 'checked-fail' ? Date.now() : undefined);
806
927
  fileIntegrityFindings(scope, findings, call.iteration);
807
928
  };
929
+ /**
930
+ * A lookup that pauses still declares what it looked at (9.114.0) — the
931
+ * RAISE-SITE reader of `InputRequestDeclaration.absence`.
932
+ *
933
+ * A tool that found nothing and raised `requestInput` about the miss used
934
+ * to leave no record of it: `declareCoverage` reads a RETURNED value, and
935
+ * the pause branch returns the checkpoint before any of its call sites.
936
+ * The app could not file it either — the declaration's `context` rides to
937
+ * the model on resume, never to the record. So the raise site files it,
938
+ * through the same two readers a returned absence meets: `declareCoverage`
939
+ * (the one recognizer, `tools.absent`, the `coverageDeclared` row — the
940
+ * bytes a returned `absent(…)` files) and the empty-lookup write seam, with
941
+ * the envelope as the tool's own answer — it ran, nothing errored, nothing
942
+ * denied it, and no ceiling judged a result the model was never served.
943
+ *
944
+ * The delivered status `declareCoverage` returns is dropped on purpose: a
945
+ * paused call is not settled, so there is no `tool_end` for it to ride.
946
+ * Nothing on resume reads the field (the served result there is the
947
+ * person's `InputResponseResult`), so the rows are filed once, here.
948
+ *
949
+ * CONTAINED the way the returned path contains it. The recognizer reads an
950
+ * envelope minted elsewhere without repairing it, so its lists can be
951
+ * something `declareCoverage` cannot copy (`checked: [null]`,
952
+ * `not_checked: 5`). Returned, that throw lands in the dispatch `try` and
953
+ * errors the call; here it would land in the `catch` and fail the run. So
954
+ * the copy is caught here instead, and the error text comes back: the
955
+ * caller errors the call with it and does not pause. That is what the same
956
+ * value returned would do. `undefined` means filed.
957
+ */
958
+ const declareRaisedAbsence = (scope, call, args, absence, history) => {
959
+ try {
960
+ declareCoverage(scope, call, absence);
961
+ }
962
+ catch (err) {
963
+ return err instanceof Error ? err.message : String(err);
964
+ }
965
+ noticeEmptyLookup(scope, call, args, absence, history, true);
966
+ return undefined;
967
+ };
808
968
  /**
809
969
  * The semantic envelope (9.53.0) — recognized at the SAME execute
810
970
  * boundaries as coverage and the ceiling (one implementation, every door),
@@ -1999,6 +2159,68 @@ export function buildToolCallsHandler(deps) {
1999
2159
  recordDecisions(scope, verdict.decisions);
2000
2160
  return verdict.kind === 'deny' ? verdict.reason : verdict.result;
2001
2161
  };
2162
+ const settleBatch = (answered, paused) => {
2163
+ const batch = pausedBatchOf(answered, paused.toolCallId);
2164
+ if (batch.undispatched.length === 0)
2165
+ return { history: answered, settled: [], size: batch.size, paused };
2166
+ const settled = batch.undispatched.map((call) => ({
2167
+ call,
2168
+ result: notDispatchedResult(call.name, paused),
2169
+ }));
2170
+ return {
2171
+ history: [
2172
+ ...answered,
2173
+ ...settled.map(({ call, result }) => ({
2174
+ role: 'tool',
2175
+ content: result,
2176
+ toolCallId: call.id,
2177
+ toolName: call.name,
2178
+ notDispatched: notDispatchedMarker(paused),
2179
+ })),
2180
+ ],
2181
+ settled,
2182
+ size: batch.size,
2183
+ paused,
2184
+ };
2185
+ };
2186
+ /**
2187
+ * Each settled sibling's bracket, after the paused call's own `tool_end` —
2188
+ * the ONE site that stamps `notDispatched` on the stream: `tool_start` with
2189
+ * the args the call would have run with (the batch loop's peel, so
2190
+ * `_findings` never shows on a bracket), then `tool_end` carrying the
2191
+ * sentence and `durationMs: 0`. Both carry the marker the history message
2192
+ * carries (`notDispatchedMarker`), and no other bracket in the library ever
2193
+ * does.
2194
+ *
2195
+ * No `error`. It says a call FAILED — the tool threw, or the call itself
2196
+ * could not run as written (an args rejection, a `wants` block, a consent
2197
+ * the tool needed and did not get, an unknown name). A settled call did not
2198
+ * fail: the library decided not to dispatch it, as it does for a call a
2199
+ * permission policy denies or halts, and those brackets carry no `error`
2200
+ * either. Stamped here, it would register a tool failure in every
2201
+ * error-rate reader (a span status, a dashboard, an alert) on every
2202
+ * mid-batch pause, and say a second, false thing beside the marker, which
2203
+ * is the one owner of why there is no result (`src/core/README.md` · "One
2204
+ * owner of 'never dispatched' — in history and on the stream", the reader
2205
+ * table). No `status` either: that field is the TOOL's own word.
2206
+ */
2207
+ const bracketSettled = (scope, settlement) => {
2208
+ for (const { call, result } of settlement.settled) {
2209
+ typedEmit(scope, 'agentfootprint.stream.tool_start', {
2210
+ toolName: call.name,
2211
+ toolCallId: call.id,
2212
+ args: peelCall(call).args,
2213
+ parallelCount: settlement.size,
2214
+ notDispatched: notDispatchedMarker(settlement.paused),
2215
+ });
2216
+ typedEmit(scope, 'agentfootprint.stream.tool_end', {
2217
+ toolCallId: call.id,
2218
+ result,
2219
+ durationMs: 0,
2220
+ notDispatched: notDispatchedMarker(settlement.paused),
2221
+ });
2222
+ }
2223
+ };
2002
2224
  // Resolve a tool's declared credential (declare-and-push) and execute it,
2003
2225
  // emitting the same credential.* events as the main loop. Used by the
2004
2226
  // check-in RESUME path when a human APPROVES — the tool never ran at pause
@@ -2324,15 +2546,24 @@ export function buildToolCallsHandler(deps) {
2324
2546
  // came from (9.102.0; before, only the last batch resolved and an
2325
2547
  // offered older id filed as `unknownId`). Read through the same
2326
2548
  // per-call peel the loop below applies (a tool that owns the name
2327
- // declares nothing). Gated: unarmed, not one line runs.
2549
+ // declares nothing). A ruled-out standing whose result THIS door
2550
+ // recorded as an absence (`declareCoverage`'s rows on
2551
+ // `coverageDeclared`, never a second reading of a return) AND the
2552
+ // model was served as one (the served string — the door records the
2553
+ // return before the after-tool chain, the ceiling and placement act,
2554
+ // so a denied, refused or ticketed result files nothing) gets its
2555
+ // `unsettled-by-absence` row immediately after it, in the same write,
2556
+ // worded from what was served (9.113.0, `findings/unsettled.ts`; the
2557
+ // key is read only for such a standing); the standing rows pass
2558
+ // through untouched. Gated: unarmed, not one line runs.
2328
2559
  if (deps.findings === true) {
2329
2560
  const known = knownResults([...(scope.history ?? [])], [...(scope.toolResults ?? [])]);
2330
- recordFindings(scope, toolCalls.flatMap((tc) => {
2561
+ recordFindings(scope, withUnsettledRows(toolCalls.flatMap((tc) => {
2331
2562
  const declared = peelCall(tc).findings;
2332
2563
  return declared?.previous !== undefined
2333
2564
  ? standingRowsFrom(known, declared, { toolCallId: tc.id }, iteration)
2334
2565
  : [];
2335
- }));
2566
+ }), known, () => [...(scope.coverageDeclared ?? [])]));
2336
2567
  }
2337
2568
  scope.toolResults = [];
2338
2569
  // ── THE TOWERS AT DISPATCH (9.110.0) — one corpus per batch ────────
@@ -3065,6 +3296,9 @@ export function buildToolCallsHandler(deps) {
3065
3296
  await endCall(tc.id);
3066
3297
  }
3067
3298
  catch (err) {
3299
+ // Set when a raise declared a miss the door could not file:
3300
+ // the call then errors with this text and does not pause.
3301
+ let raisedMissError;
3068
3302
  if (isPauseRequest(err)) {
3069
3303
  // The typed half of the question (9.24.0): a tool that raised
3070
3304
  // `askHuman({ question, component })` nominated a screen
@@ -3082,51 +3316,90 @@ export function buildToolCallsHandler(deps) {
3082
3316
  await assertComponentDeliverable(scope, tc.name, rawComponent, `askHuman in tool '${tc.name}'`);
3083
3317
  }
3084
3318
  }
3085
- // A pause has NOT settled this call — it is waiting for a
3086
- // person, and the resources it opened are what the resume
3087
- // needs. No `'call'` teardown here, deliberately.
3088
- //
3089
- // Commit partial state so resume() can find history intact.
3090
- scope.history = newHistory;
3091
- scope.pausedToolCallId = tc.id;
3092
- scope.pausedToolName = tc.name;
3093
- scope.pausedToolStartMs = startMs;
3094
- // The args the tool WAS RUNNING WITH (post-transform), for the
3095
- // after-tool moment on the far side of the pause (8.13.0) —
3096
- // the same reason the three sibling pauses carry theirs.
3097
- scope.pausedToolArgs = callArgs;
3098
3319
  const declaration = typeof err.data === 'object' && err.data !== null
3099
3320
  ? err.data.inputRequest
3100
3321
  : undefined;
3101
- const awaitingInput = declaration === undefined
3102
- ? undefined
3103
- : stampInputRequest(validateInputDeclaration(declaration), `${deps.currentRun?.().runId ?? scope.turnStartMs}:${tc.id}`, {
3104
- originalRequest: scope.userMessage,
3105
- toolCallId: tc.id,
3106
- ...(scope.currentSkillId !== undefined && {
3107
- skillId: scope.currentSkillId,
3108
- }),
3109
- ...(scope.turnRoute?.offered !== undefined && {
3110
- offeredSkillIds: [...scope.turnRoute.offered],
3111
- }),
3112
- });
3113
- // Returning a defined value triggers footprintjs pause —
3114
- // the returned object becomes the checkpoint's pauseData.
3115
- return {
3116
- toolCallId: tc.id,
3117
- toolName: tc.name,
3118
- ...(awaitingInput !== undefined
3119
- ? { question: awaitingInput.question, awaitingInput }
3120
- : typeof err.data === 'object' && err.data !== null
3121
- ? err.data
3122
- : { data: err.data }),
3322
+ // A lookup that pauses still declares what it looked at
3323
+ // (9.114.0): the miss it is asking about is filed HERE, the
3324
+ // raise site, FIRST — the one moment the declaration's
3325
+ // `absence` is read. So a declaration that CARRIES one is
3326
+ // judged before anything is written for the pause, and a miss
3327
+ // the door cannot file errors the call below, as the same value
3328
+ // returned would, with nothing written for a pause. A MALFORMED
3329
+ // declaration still leaves the committed record it always left,
3330
+ // whether or not it carried `absence`: its error leaves the
3331
+ // stage only after the pause writes are made, so a hand-raised
3332
+ // request (a `pauseHere`/`askHuman` that never went through
3333
+ // `requestInput`) fails with the in-flight batch on the record.
3334
+ // `null` is the field omitted (`inputRequest.ts` ·
3335
+ // `validateInputDeclaration`).
3336
+ //
3337
+ // The pause writes: partial state, so resume() can find the
3338
+ // history intact.
3339
+ const writePause = () => {
3340
+ scope.history = newHistory;
3341
+ scope.pausedToolCallId = tc.id;
3342
+ scope.pausedToolName = tc.name;
3343
+ scope.pausedToolStartMs = startMs;
3344
+ // The args the tool WAS RUNNING WITH (post-transform), for
3345
+ // the after-tool moment on the far side of the pause
3346
+ // (8.13.0) — the same reason the three sibling pauses carry
3347
+ // theirs.
3348
+ scope.pausedToolArgs = callArgs;
3123
3349
  };
3350
+ const carriesAbsence = typeof declaration === 'object' &&
3351
+ declaration !== null &&
3352
+ declaration.absence != null;
3353
+ let declared;
3354
+ if (carriesAbsence) {
3355
+ try {
3356
+ declared = validateInputDeclaration(declaration);
3357
+ }
3358
+ catch (invalid) {
3359
+ writePause();
3360
+ throw invalid;
3361
+ }
3362
+ }
3363
+ raisedMissError =
3364
+ declared?.absence === undefined
3365
+ ? undefined
3366
+ : declareRaisedAbsence(scope, { toolName: tc.name, toolCallId: tc.id, iteration }, callArgs, declared.absence, newHistory);
3367
+ if (raisedMissError === undefined) {
3368
+ // A pause has NOT settled this call — it is waiting for a
3369
+ // person, and the resources it opened are what the resume
3370
+ // needs. No `'call'` teardown here, deliberately.
3371
+ writePause();
3372
+ const awaitingInput = declaration === undefined
3373
+ ? undefined
3374
+ : stampInputRequest(declared ?? validateInputDeclaration(declaration), `${deps.currentRun?.().runId ?? scope.turnStartMs}:${tc.id}`, {
3375
+ originalRequest: scope.userMessage,
3376
+ toolCallId: tc.id,
3377
+ ...(scope.currentSkillId !== undefined && {
3378
+ skillId: scope.currentSkillId,
3379
+ }),
3380
+ ...(scope.turnRoute?.offered !== undefined && {
3381
+ offeredSkillIds: [...scope.turnRoute.offered],
3382
+ }),
3383
+ });
3384
+ // Returning a defined value triggers footprintjs pause —
3385
+ // the returned object becomes the checkpoint's pauseData.
3386
+ return {
3387
+ toolCallId: tc.id,
3388
+ toolName: tc.name,
3389
+ ...(awaitingInput !== undefined
3390
+ ? { question: awaitingInput.question, awaitingInput }
3391
+ : typeof err.data === 'object' && err.data !== null
3392
+ ? err.data
3393
+ : { data: err.data }),
3394
+ };
3395
+ }
3124
3396
  }
3125
3397
  // A tool that threw still RAN, and may have opened the thing it
3126
- // was about to close.
3398
+ // was about to close. A raise whose miss could not be filed is
3399
+ // settled here too — the returned path's error, not a pause.
3127
3400
  await endCall(tc.id);
3128
3401
  error = true;
3129
- result = err instanceof Error ? err.message : String(err);
3402
+ result = raisedMissError ?? (err instanceof Error ? err.message : String(err));
3130
3403
  }
3131
3404
  }
3132
3405
  }
@@ -3904,10 +4177,13 @@ export function buildToolCallsHandler(deps) {
3904
4177
  askResultStr += applyToolEffects(scope, { toolName, toolCallId, iteration }, resumeEnvelope, askTransition);
3905
4178
  emitTransitionConflict(scope, iteration, askTransition);
3906
4179
  }
3907
- const askHistory = [
4180
+ // The batch settlement (9.113.0): the answered call's result, then the
4181
+ // siblings the pause left un-dispatched, each settled — never run.
4182
+ const askSettlement = settleBatch([
3908
4183
  ...scope.history,
3909
4184
  { role: 'tool', content: askResultStr, toolCallId, toolName },
3910
- ];
4185
+ ], { toolName, toolCallId });
4186
+ const askHistory = askSettlement.history;
3911
4187
  scope.history = askHistory;
3912
4188
  scope.lastToolResult = {
3913
4189
  toolName,
@@ -3928,10 +4204,14 @@ export function buildToolCallsHandler(deps) {
3928
4204
  ...(error === true && { error: true }),
3929
4205
  ...(resumeEnvelope?.status !== undefined && { status: resumeEnvelope.status }),
3930
4206
  });
4207
+ bracketSettled(scope, askSettlement);
3931
4208
  typedEmit(scope, 'agentfootprint.agent.iteration_end', {
3932
4209
  turnIndex: 0,
3933
4210
  iterIndex: iteration,
3934
- toolCallCount: 1,
4211
+ // The brackets THIS leg closed: the answered call's, plus one per
4212
+ // settled sibling. The calls before the paused one closed theirs on
4213
+ // the leg that paused, which ended in a checkpoint, not here.
4214
+ toolCallCount: 1 + askSettlement.settled.length,
3935
4215
  history: askHistory,
3936
4216
  });
3937
4217
  scope.iteration = iteration + 1;
@@ -4048,10 +4328,12 @@ export function buildToolCallsHandler(deps) {
4048
4328
  decisionResultStr += applyToolEffects(scope, { toolName, toolCallId, iteration }, resumeEnvelope, decisionTransition);
4049
4329
  emitTransitionConflict(scope, iteration, decisionTransition);
4050
4330
  }
4051
- const decisionHistory = [
4331
+ // The batch settlement (9.113.0) — the ask path's twin.
4332
+ const decisionSettlement = settleBatch([
4052
4333
  ...scope.history,
4053
4334
  { role: 'tool', content: decisionResultStr, toolCallId, toolName },
4054
- ];
4335
+ ], { toolName, toolCallId });
4336
+ const decisionHistory = decisionSettlement.history;
4055
4337
  scope.history = decisionHistory;
4056
4338
  // Drives `on-tool-return` triggers, same as the execute path.
4057
4339
  scope.lastToolResult = {
@@ -4073,10 +4355,12 @@ export function buildToolCallsHandler(deps) {
4073
4355
  ...(error === true && { error: true }),
4074
4356
  ...(resumeEnvelope?.status !== undefined && { status: resumeEnvelope.status }),
4075
4357
  });
4358
+ bracketSettled(scope, decisionSettlement);
4076
4359
  typedEmit(scope, 'agentfootprint.agent.iteration_end', {
4077
4360
  turnIndex: 0,
4078
4361
  iterIndex: iteration,
4079
- toolCallCount: 1,
4362
+ // The brackets this leg closed — see the ask path's twin.
4363
+ toolCallCount: 1 + decisionSettlement.settled.length,
4080
4364
  history: decisionHistory,
4081
4365
  });
4082
4366
  scope.iteration = iteration + 1;
@@ -4158,10 +4442,12 @@ export function buildToolCallsHandler(deps) {
4158
4442
  consentResultStr += applyToolEffects(scope, { toolName, toolCallId, iteration }, consentEnvelope, consentTransition);
4159
4443
  emitTransitionConflict(scope, iteration, consentTransition);
4160
4444
  }
4161
- const consentHistory = [
4445
+ // The batch settlement (9.113.0) — the ask path's twin.
4446
+ const consentSettlement = settleBatch([
4162
4447
  ...scope.history,
4163
4448
  { role: 'tool', content: consentResultStr, toolCallId, toolName },
4164
- ];
4449
+ ], { toolName, toolCallId });
4450
+ const consentHistory = consentSettlement.history;
4165
4451
  scope.history = consentHistory;
4166
4452
  scope.lastToolResult = {
4167
4453
  toolName,
@@ -4182,10 +4468,12 @@ export function buildToolCallsHandler(deps) {
4182
4468
  ...(error === true && { error: true }),
4183
4469
  ...(consentEnvelope?.status !== undefined && { status: consentEnvelope.status }),
4184
4470
  });
4471
+ bracketSettled(scope, consentSettlement);
4185
4472
  typedEmit(scope, 'agentfootprint.agent.iteration_end', {
4186
4473
  turnIndex: 0,
4187
4474
  iterIndex: iteration,
4188
- toolCallCount: 1,
4475
+ // The brackets this leg closed — see the ask path's twin.
4476
+ toolCallCount: 1 + consentSettlement.settled.length,
4189
4477
  history: consentHistory,
4190
4478
  });
4191
4479
  scope.iteration = iteration + 1;
@@ -4263,7 +4551,10 @@ export function buildToolCallsHandler(deps) {
4263
4551
  if (deps.stepPlanFor) {
4264
4552
  resultStr += applyStepReturn(scope, { toolName, toolCallId, iteration });
4265
4553
  }
4266
- const newHistory = [
4554
+ // The batch settlement (9.113.0) — the door a `requestInput` answer
4555
+ // comes back through, and the one most likely to meet a batch: a tool
4556
+ // that asks a person mid-lookup, batched with the lookups after it.
4557
+ const pauseSettlement = settleBatch([
4267
4558
  ...scope.history,
4268
4559
  {
4269
4560
  role: 'tool',
@@ -4271,7 +4562,8 @@ export function buildToolCallsHandler(deps) {
4271
4562
  toolCallId,
4272
4563
  toolName,
4273
4564
  },
4274
- ];
4565
+ ], { toolName, toolCallId });
4566
+ const newHistory = pauseSettlement.history;
4275
4567
  scope.history = newHistory;
4276
4568
  // Drives `on-tool-return` triggers, same as every other dispatch path.
4277
4569
  scope.lastToolResult = { toolName, result: resultStr };
@@ -4287,10 +4579,12 @@ export function buildToolCallsHandler(deps) {
4287
4579
  result: pauseCapped.result,
4288
4580
  durationMs: Date.now() - startMs,
4289
4581
  });
4582
+ bracketSettled(scope, pauseSettlement);
4290
4583
  typedEmit(scope, 'agentfootprint.agent.iteration_end', {
4291
4584
  turnIndex: 0,
4292
4585
  iterIndex: iteration,
4293
- toolCallCount: 1,
4586
+ // The brackets this leg closed — see the ask path's twin.
4587
+ toolCallCount: 1 + pauseSettlement.settled.length,
4294
4588
  // Plain local array — see the matching note on the execute path.
4295
4589
  history: newHistory,
4296
4590
  });