## Boundary Desk\:&#32;one call at a time

Boundary Desk begins with a deceptively small question\:&#32;what did the word Approve authorize\?

For the generic tool gate in the supplied wrapper\,&#32;the answer is the current call\.&#32;There is no built\-in choice here named “allow for session” or “always allow\,” and selecting Approve does not write a per\-tool policy\.

### Inspect the actual binary choice

The wrapper calls\:

~~~ts
choice = await uiContext.select(safetyPrompt, ["Approve", "Deny"]);
const approved = choice === "Approve";
~~~

Only the exact returned label&#32;`Approve`&#32;is positive\.&#32;`Deny`\,&#32;`undefined`\,&#32;and other nonmatching values are not approval\.&#32;A dialog exception also stops this path and is reported as a false resolution before being rethrown\.

These are the generic tool\-approval choices\.&#32;They are not Review Desk’s local review outcomes\,&#32;an ACP plan\-review menu\,&#32;or a domain delegation dialog\.

### Predict the second call

Inspect&#32;`boundary-approval-does-not-persist`\:

~~~json
{
  "approval": "exec",
  "args": { "action": "publish" },
  "mode": "always-ask",
  "choices": ["Approve", "Deny"],
  "repeat": 2
}
~~~

**Prediction\:**&#32;does the first answer eliminate the second question\?

**Recorded answer\:**&#32;no\.&#32;The same wrapped tool\,&#32;with unchanged settings\,&#32;was invoked twice\.&#32;There were two prompts and two&#32;`tool_call`&#32;events\,&#32;but only one successful call and one inert executor entry\.&#32;The second invocation threw&#32;`Tool call denied by user: seed_note`\.

The first inert result remains present in the report even though the final outcome is a throw\.&#32;That is not evidence that the second invocation ran\.&#32;Read&#32;`successfulCalls`\,&#32;the execution ledger\,&#32;and the per\-call lifecycle together\.

The simpler cases establish the alternatives independently\:

| Case | Recorded response | Prompts | Inert executions | Outcome |
| --- | ---: | --- | --- | --- |
| `boundary-approve-one-call` | Approve | 1 | 1 | return |
| `boundary-deny-one-call` | Deny | 1 | 0 | throw |
| `boundary-dismiss-one-call` | `undefined`\,&#32;represented by fixture null | 1 | 0 | throw |

A declined call is not a persisted deny policy\.&#32;Conversely\,&#32;a user\-policy deny does not require a dialog in which a person declines this particular call\.

### Follow the order\,&#32;not just the final banner

For the directly exercised wrapper path\,&#32;the useful sequence is\:

1. Consume any existing loop\-emission marker and sample approval inputs from the execute\-time context\.
2. Resolve the original input\;&#32;an effective deny stops this wrapper path before its own&#32;`tool_call`&#32;handler emission\.
3. If not already emitted by the loop\,&#32;run applicable&#32;`tool_call`&#32;handlers\,&#32;which can block or return replacement input\.
4. Resolve approval against the effective input and report an effective xdev tier when that callback exists\.
5. If approval is required\,&#32;wait for the applicable scheduled\-call preview when such a waiter is installed\.
6. Emit&#32;`tool_approval_requested`&#32;when approval lifecycle handlers are present\.
7. Obtain the selection\,&#32;or resolve false because no UI is available\.
8. Emit&#32;`tool_approval_resolved`\;&#32;execute the underlying tool only after a positive required decision\.

`ui.select`&#32;and the executor are steps in this explanation\,&#32;not extra OMP event names\.&#32;The existing&#32;`extensions-runner.test.ts`&#32;records requested → UI selection → resolved order and tests preview waiting for canonical and wire\-aliased tool names\.&#32;The private fixture report records the lifecycle pairs\,&#32;prompt data\,&#32;and executor counts through its structural adapter\;&#32;it does not exercise that transcript preview waiter\.

An approval requested event can therefore exist even when no dialog was displayed\.&#32;It records a required decision path\,&#32;not proof of a human interaction\.&#32;The empty&#32;`sessionId`&#32;in several private scenario events comes from their deliberately partial execute\-time context\,&#32;which has no session manager\.&#32;It is not a real reader’s conversation identity\.

### Reclassification has a supported scope

The wrapper’s supported replacement path applies a returned&#32;`input`&#32;before the full approval gate\.&#32;The existing runner tests verify that a revised input can become denied\,&#32;that a prompt describes revised rather than original input\,&#32;and that an xdev replacement forfeits the bypass\.

For normal model\-loop preparation\,&#32;`AgentLoopConfig.beforeToolCall`&#32;documents replacement argument revalidation and propagation into scheduling and tool\-call history\.&#32;The retained&#32;[Tools\,&#32;interception and native delegation](<https://present-sketch-tp94.here.now/chapters/extensions-tools-interception-and-native-delegation>)&#32;chapter explains the session wiring and its distinction from direct execution\.&#32;A direct&#32;`tool.execute()`&#32;call is not proof that the loop’s schema revalidation occurred\.

The runner’s handlers are not a field\-by\-field replacement pipeline\:&#32;they receive the normalized event\,&#32;and the last returned result object is retained unless a block short\-circuits\.&#32;Computer\-provider calls expose a synthetic actions\/safety\-check view\,&#32;so the wrapper does not apply ordinary returned input replacements to those execution parameters\.

Do not extend the supported replacement proof to arbitrary in\-process mutation\,&#32;to code that changes data after approval\,&#32;or to every nested executor\.&#32;The wrapper samples mode and user policies before awaiting handlers and selection\;&#32;it is not a continuous policy\-revalidation transaction while a dialog remains open\.

### Cancellation is not one universal mechanism

The recorded dismissal case and synthetic RPC cancellation both produce&#32;`undefined`\,&#32;which the wrapper refuses\.&#32;Existing runner tests also cover a throwing approval selector and cancellation while an extension\-owned&#32;`tool_call`&#32;confirmation is pending\.

Those are different seams\.&#32;The runner supplies signals and active\-work timeout accounting to extension\-handler dialogs\.&#32;The generic approval selector call shown above does not itself pass dialog options containing the execution signal\.&#32;Do not claim from these records that every external abort automatically cancels every native approval selection or defeats every possible late response\.&#32;A cancelled presentation\,&#32;a blocked tool\,&#32;and rollback of an earlier effect remain separate claims\.

**Paper checkpoint\:**&#32;after the Approve\-then\-Deny repeat case\,&#32;what is the execution count\?&#32;**Worked answer\:**&#32;one\.&#32;The second prompt did not persist or reuse the first answer\,&#32;and the denial did not undo the first inert record\.

**Failure boundary\:**&#32;the wrapper’s result hooks can transform returned content\,&#32;details\,&#32;and error presentation after execution\.&#32;They cannot undo an external effect\.&#32;An approval event\,&#32;a nonthrowing result\,&#32;or a visible success label is not a substitute for checking the operation’s own postcondition\.

**Source anchors\:**&#32;`packages/coding-agent/src/extensibility/extensions/wrapper.ts`&#32;—&#32;`ExtensionToolWrapper.execute`\;&#32;`packages/coding-agent/src/extensibility/extensions/runner.ts`&#32;—&#32;`emitToolCall`\,&#32;`markToolCallEmitted`\,&#32;`consumeToolCallEmitted`\,&#32;`waitForToolApprovalPreview`\;&#32;`packages/agent/src/types.ts`&#32;—&#32;`AgentLoopConfig.beforeToolCall`\;&#32;`packages/coding-agent/test/extensions-runner.test.ts`&#32;— tool approval lifecycle\,&#32;input replacement\,&#32;timeout\,&#32;and cancellation tests\.
