Motivation
Today the async-confirmation gate (peerd-runtime/tools/gates.js) is synchronous from the agent's point of view: a side-effecting tool call stops the loop until the user answers. That's the classic failure mode Cloudflare OS's README calls out — the user walks away, comes back to an agent stuck on the first approval, and eventually flips confirmation off entirely (the --dangerously-skip-permissions spiral). The safety mechanism trains users to disable it.
The pattern (from Cloudflare OS Gatekeepers)
When an action requires approval, the mediating layer:
- Queues the action with a human-readable description, and tells the agent it succeeded.
- Simulates its outcome, so subsequent reads reflect the pending write and the agent can keep working — even queuing dependent actions.
- The user approves or rejects in bulk later, at their convenience. Rejection may cascade (dependent actions) and may require restarting the workload if simulation state can't be rolled back.
Supporting contract details worth copying from packages/workshop-shared/src/gatekeeper.ts (github.com/cloudflare/cloudflare-os):
implementsRevert per action — approved-then-regretted actions get automated revert where possible; the UI only offers revert when the author says it's implementable.
awaitDecision hint — actions that can't be simulated declare it, and the harness suspends the turn as today. The mechanism is additive; blocking remains the fallback, not the default.
autoApprovable needs two signals — the author's per-action verdict AND a user-enabled rule for that action kind. Neither alone suffices. (Their drain loop also never applies past the first non-eligible action — ordering is preserved through a human gate.)
Mapping into peerd
Where it lives: the dispatcher's confirmation gate + pre/post tool-use hooks (gates.js); the checkpoint/review substrate already gives diff-and-revert for file-shaped mutations. Candidate surfaces, in rough order of tractability:
- Sandbox file/app/notebook writes — trivially simulable: apply to a checkpoint/shadow layer immediately (the agent reads its own pending writes back), commit on approve, discard on reject. Checkpoints already exist; this is mostly wiring + UI.
- dweb share / install — currently force-confirmed every time (
tools/defs/dweb-share.js, dweb-install.js). Could become queued cards (share stays consent-heavy — publishing is irreversible — but the queue lets goal mode line up several and the user approve as a batch).
- Outbound messages (a2a
ask/send) — queue with the rendered payload; inherently non-simulable responses ⇒ awaitDecision semantics when the agent needs the reply.
- Web-actor page mutations — hardest; true simulation of a third-party page isn't feasible. Likely stays blocking (
awaitDecision) or limited to a described-intent queue for low-risk actions.
Shape of an action record: tool + args digest, human description, revertability, author auto-approvable verdict, lineage (already attached to every tool result). Batch approval UI in the side panel; every queue/approve/reject/revert event goes to the append-only audit log.
Cautions
- Injection fencing: a queued action's description is rendered to the user and can embed page-derived text — must be
wrapUntrusted-fenced like actor replies.
- Plan/Act interplay: Plan mode already refuses mutations outright; the queue is an Act-mode feature. It must not become a Plan bypass (queuing in Plan = still refused).
- Honest simulation: never simulate what can't be made true later (e.g. don't fabricate a peer's reply). When in doubt,
awaitDecision.
- Goal mode is the biggest beneficiary (long autonomous runs) and the biggest risk surface (large queues) — consider a queue-depth cap per session.
Related: #238 (reversible execution traces — same revert substrate, complementary mechanism).
Motivation
Today the async-confirmation gate (
peerd-runtime/tools/gates.js) is synchronous from the agent's point of view: a side-effecting tool call stops the loop until the user answers. That's the classic failure mode Cloudflare OS's README calls out — the user walks away, comes back to an agent stuck on the first approval, and eventually flips confirmation off entirely (the--dangerously-skip-permissionsspiral). The safety mechanism trains users to disable it.The pattern (from Cloudflare OS Gatekeepers)
When an action requires approval, the mediating layer:
Supporting contract details worth copying from
packages/workshop-shared/src/gatekeeper.ts(github.com/cloudflare/cloudflare-os):implementsRevertper action — approved-then-regretted actions get automated revert where possible; the UI only offers revert when the author says it's implementable.awaitDecisionhint — actions that can't be simulated declare it, and the harness suspends the turn as today. The mechanism is additive; blocking remains the fallback, not the default.autoApprovableneeds two signals — the author's per-action verdict AND a user-enabled rule for that action kind. Neither alone suffices. (Their drain loop also never applies past the first non-eligible action — ordering is preserved through a human gate.)Mapping into peerd
Where it lives: the dispatcher's confirmation gate + pre/post tool-use hooks (
gates.js); the checkpoint/review substrate already gives diff-and-revert for file-shaped mutations. Candidate surfaces, in rough order of tractability:tools/defs/dweb-share.js,dweb-install.js). Could become queued cards (share stays consent-heavy — publishing is irreversible — but the queue lets goal mode line up several and the user approve as a batch).ask/send) — queue with the rendered payload; inherently non-simulable responses ⇒awaitDecisionsemantics when the agent needs the reply.awaitDecision) or limited to a described-intent queue for low-risk actions.Shape of an action record: tool + args digest, human description, revertability, author auto-approvable verdict, lineage (already attached to every tool result). Batch approval UI in the side panel; every queue/approve/reject/revert event goes to the append-only audit log.
Cautions
wrapUntrusted-fenced like actor replies.awaitDecision.Related: #238 (reversible execution traces — same revert substrate, complementary mechanism).