Skip to content

fix(agents): continue paused server-tool turns instead of returning them unfinished - #533

Open
0xABCD01 wants to merge 1 commit into
anthropics:mainfrom
0xABCD01:fix/527-pause-turn-continuation
Open

0xABCD01 wants to merge 1 commit into
anthropics:mainfrom
0xABCD01:fix/527-pause-turn-continuation

Conversation

@0xABCD01

@0xABCD01 0xABCD01 commented Nov 5, 2026

Copy link
Copy Markdown

Description

Fixes #527.

In the agents example, Agent._agent_loop() treated any response without a client tool_use block as complete. A server-tool response (web search / code execution) with stop_reason="pause_turn" contains only server_tool_use blocks, so run() returned the unfinished turn instead of continuing it.

The loop now continues a paused turn: the assistant message is already in history, so the next iteration issues the follow-up request. Consecutive continuations are bounded by MAX_PAUSE_TURN_CONTINUATIONS = 10; past the cap, run() returns the last response. The counter resets whenever a response carries client tool calls.

One judgment call to flag: the issue asks for a bound on repeated continuations but does not name a value or an exhaustion behavior. I picked 10 (an order of magnitude above a realistic chained server-tool turn, while capping a pathological always-pause loop at 10 extra API calls) and returning the last response rather than raising, since run() callers do not handle exceptions from it and the returned response's stop_reason shows what happened. Happy to adjust either.

Continuing the turn also required a fix in agents/utils/history_util.py. With prompt caching enabled (the default), MessageHistory.format_for_api() adds cache_control by spreading the last message's content blocks as dicts — but assistant responses are stored as raw SDK block models, and a continued pause_turn is the first path where an assistant message is the last one at request time, so the continuation raised TypeError: 'ServerToolUseBlock' object is not a mapping. The caching branch now dumps non-dict blocks to dicts first (model_dump(exclude_unset=True), which matches the SDK's own wire form for the same blocks); dict blocks pass through unchanged, and no other formatting path is touched.

Quickstart

  • Computer Use Demo
  • Customer Support Agent
  • Financial Data Analyst
  • N/A

(The quickstart checklist predates the agents example and has no entry for it; this change is in agents/, named here and in the description, so N/A is the closest available option.)

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Code refactoring
  • Other (please describe):

Testing

  • Added/updated unit tests
  • Tested manually
  • Verified in development environment

Added agents/test_pause_turn.py (offline; mocked client, real SDK Message objects, no API calls):

  • pause_turn response followed by end_turn: run() returns the second response, messages.create called twice. (Prompt caching disabled here to isolate stop-reason handling.)
  • always-pause client: the loop stops after 1 + 10 calls and returns the last paused response.
  • the shipped default configuration (prompt caching enabled): pause_turn followed by end_turn returns the final response with exactly 2 create calls and no exception; the test also asserts the follow-up request's last message is the paused assistant message, as dict blocks with cache_control applied.

The tests fail without this change, and the default-configuration test also fails with the TypeError above when only the loop change is applied without the history_util fix; the issue's repro script flips from AssertionError: run() returned an unfinished server-tool turn to a continued, completed turn. pytest agents/: 3 passed. pyright reports 0 errors on agents/agent.py and agents/utils/history_util.py (same as HEAD); ruff check on the touched files reports only a pre-existing BLE001 in history_util.py, unchanged from HEAD.

Screenshots

N/A — no UI change.

Additional Notes

Scope is the agents example only. I checked the other examples for the same shape: computer-use-demo and browser-use-demo loop the same way but configure client tools only, so pause_turn cannot occur there today; computer-use-best-practices does see server_tool_use blocks (advisor) and breaks on end_turn/no-tool-use, so it may deserve the same look separately.

@0xABCD01
0xABCD01 force-pushed the fix/527-pause-turn-continuation branch from 6d64c61 to 86bbd97 Compare November 7, 2026 09:39
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.

agents: run() returns an unfinished server-tool turn on pause_turn

1 participant