Skip to content

mcp: a host can pause between the calls of a run_batch - #1271

Merged
wass08 merged 1 commit into
mainfrom
mcp/batch-pause
Nov 11, 2026
Merged

wass08 merged 1 commit into
mainfrom
mcp/batch-pause

Conversation

@wass08

@wass08 wass08 commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

run_batch is one synchronous operation: its calls run back to back and nothing else on the server runs until the last one returns. A batch of several expensive calls (whole-scene checks on a large building) holds the event loop for its whole length, so a host's timers and I/O wait for it. A host that keeps a lease alive with a timer loses it during such a batch.

This adds an opt-in pause after each call:

  • core: the batch is a sequence that stops after each call (createBatchSteps, exported as RUN_BATCH_STEPS). AGENT_OPERATIONS.run_batch runs it through in one go, as before, so the chat and every existing caller are unchanged.
  • mcp: createPascalMcpServer({ betweenBatchCalls }). When a host gives it, the batch tool runs one call at a time, each in the scene's scope, and awaits the host after each.

Why opt-in. Today a batch plans on the scene and applies its changes in one uninterrupted stretch, so no other call can reach the scene in between. A server that serves concurrent calls on one bridge (the standalone server) relies on that. With a pause, the batch is planned on the scene as its first call found it and applied after its last, so only a host that builds a scene of its own for each request should give the hook. Without it nothing changes.

The batch's own behaviour is the same: calls in order, each on the scene the calls before it left; stop applies nothing when a call is refused, skip reports it and applies the rest; same result shape; one history step.

How to test

  1. cd packages/mcp && bun test src/tools/run-batch-pause.test.ts: with a pausing host, the pause runs after each of four calls and sees the scene as the batch found it; the batch then lands whole and one undo removes it. Four batches (plain, stopped by a refusal, past a refusal with skip, an unknown tool) answer the same with and without the pause.
  2. bun test src/tools in packages/mcp (1,290 pass) and bun test src/agent-operations in packages/core (1,558 pass).

Screenshots / screen recording

N/A — non-visual change

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches shared batch semantics and MCP run_batch execution path; opt-in only, but hosts using betweenBatchCalls rely on planning on the initial scene snapshot until final apply—wrong usage on a shared bridge could race concurrent tool calls.

Overview
Adds an opt-in hook so MCP hosts can yield the event loop between successive run_batch tool calls instead of blocking for the whole batch.

Core splits batch execution into a generator (createBatchSteps / exported RUN_BATCH_STEPS) that yields after each call while still planning on the same working scene and merging to one history step at the end. createBatchOperation and AGENT_OPERATIONS.run_batch unchanged—they drain the generator synchronously, so the chat and other existing callers behave as before.

MCP accepts createPascalMcpServer({ betweenBatchCalls }). When set, run_batch runs through RUN_BATCH_STEPS inside the scene scope and awaits the host callback after every call; order, refusal policy (stop/skip), result shape, and single undo step stay the same. Hosts serving one shared bridge on concurrent requests should omit the hook.

Docs in agent-surfaces.md describe when to use it. New tests in run-batch-pause.test.ts assert pauses see the pre-application scene and that paused vs uninterrupted batches agree.

Reviewed by Cursor Bugbot for commit 4c60a75. Bugbot is set up for automated code reviews on this repo. Configure here.

A batch is one synchronous operation, so a long one holds the event loop
for its whole length and the host's timers and I/O wait for it.

core's batch is now a sequence that stops after each call; the synchronous
operation runs it through as before. A host that gives betweenBatchCalls
is awaited after each call. Only a host with a scene of its own per
request should: the batch is planned on the scene as its first call found
it and applied after its last. Without the hook a batch is uninterrupted.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@wass08
wass08 merged commit 563d1aa into main Nov 11, 2026
14 of 16 checks passed
@wass08
wass08 deleted the mcp/batch-pause branch November 11, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant