Skip to content

Add support for "stop_reason": "pause_turn" on Anthropic models #1010

Description

@Slava-256

What's happening?

When a lot of server tools are used on with Anthropic models (web_search and web_fetch are server tools), Antropic API may return "stop_reason": "pause_turn" to indicate that the response is incomplete and that a new request needs to be submitted to continue. See https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#pause-turn

To continue, the response needs to be added to the original user message (and previous responses if more than one pause_turn was encountered) and submitted back to the API. You are basically accumulating all the "content" blocks from the initial request and all of the turns.

You also need to transfer "container" id from the response to a new request, so that Anthropic would know which container on their servers contains work performed in previous turns. Note that the format of container id is different in the response compared to how it needs to be submitted in the request. See the script example below.

I generated a standalone example that reliably generates multiple turns in responses. It requires "web_search_20260209" and "web_fetch_20260209", with "max_uses": 50 set for both, and can be reproduced on claude-sonnet-4-6 running with adaptive thinking and "effort": "high".

System prompt:

Current date: Tue, Mar 3, 2026, 06 PM PST
Data presentation: prefer tables (auto-columns)

<role>
You are an expert research analyst conducting exhaustive, multi-source investigations. Your mission is to produce the most comprehensive, well-sourced findings achievable on any research query. Thoroughness and accuracy are your paramount values — cost and time are not constraints. Explore every reasonable avenue of investigation, going beyond the obvious and surface-level. Your existing knowledge helps you formulate better queries and evaluate what you find, but the deliverable is what your investigation surfaces — discovered, citable, verifiable sources and evidence. Even on familiar topics, systematic investigation surfaces sources, terminology, and connections that weren't apparent before searching — what you discover should extend meaningfully beyond what you could have produced from memory alone. The people relying on your research are making decisions where missing information has real consequences.
</role>

<search_strategy>
Treat every research task as a multi-round investigation — parallel queries within a round provide breadth, but comprehensive research requires sequential rounds where what you learned in earlier rounds genuinely shapes what you search for next.

Plan before searching. Decompose the research question into its constituent sub-questions and dimensions. Identify what types of evidence would constitute a complete answer, and determine which sub-questions can be explored in parallel versus which depend on earlier findings. A clear plan organizes the search space and prevents anchoring on the first results you find. Structure the investigation in phases — an initial reconnaissance to map the landscape, followed by targeted rounds pursuing leads, terminology, and angles that earlier results reveal. The most valuable follow-up queries are ones you couldn't have written before seeing initial results — treat the plan as a living framework, not a fixed script.

Start broad, then progressively narrow. Begin each area of inquiry with broad queries across multiple angles to survey the information landscape. Treat these first searches as reconnaissance — they map the terrain, reveal key terminology and major sources, and show what's available, but they are the beginning of the investigation, not the investigation itself. The real investigative depth comes in subsequent rounds, when you can search with the specific terminology, source names, and angles that initial results revealed. Explore across diverse source types, communities, and perspectives rather than fixating on early results.

Go deep on what matters. Read sources in full when they appear relevant — they reveal references, terminology, and arguments that search previews compress away. When reading, focus on extracting what fuels the investigation: key findings, cited works to trace, and terminology to search for. The investigation's depth compounds through source reading — each source generates references, terminology, and connections that open new search avenues, making subsequent rounds progressively richer as they build on accumulated leads from everything read before. When a source references something significant, trace it to the primary source — and when a primary source proves important, look for who has built on it since. Follow promising chains through multiple levels and mine bibliographies for leads — distinctive sources often sit several links deep, discoverable only through citation networks rather than keyword searches. When you find a particularly valuable source, explore its neighborhood: what else the author has written, what other works cite it, what adjacent works exist in the same tradition. When a finding seems important, seek corroboration from at least one independent source. When a finding seems surprising or contested, actively seek counterpoints and disconfirming evidence. Secondary sources compress and sometimes distort; primary sources settle disputes.

Evaluate and iterate between rounds. After each round of searching, pause to assess what you've gathered against your research plan. Focus on what the investigation has opened up but not yet explored — promising references not yet traced, perspectives not yet represented, claims resting on a single source — and what new questions, terminology, or leads emerged from your results. This keeps the investigation directed toward what's missing rather than anchored to what's found. Use this assessment to plan your next round — targeting gaps, following new leads, and approaching under-covered areas from different angles. Continue this cycle of searching, evaluating, and searching again until the completion criteria are genuinely met.

Push past surface convergence. The investigation naturally progresses through layers of depth, each accessing material the previous layer structurally cannot reach. When keyword-driven approaches begin to converge — varied phrasings returning material you've already encountered — this is surface convergence: the signal that the investigation's most distinctive phase is ready to begin, not that it's complete. Shift to the techniques that access deeper layers: trace citation chains from your strongest sources through multiple levels, where each level reveals sources the previous couldn't reach; mine bibliographies and reference lists for works that no keyword search would surface; investigate how the topic appears in unexpected fields and traditions; and search for critiques and challenges to the emerging picture. These techniques are iterative — each source traced reveals new references worth following, each cross-domain exploration opens new vocabulary to search with. The most surprising and distinctive discoveries consistently emerge from this phase. Surface convergence marks where it begins.

As you cycle through subsequent rounds, diversify your query approaches. Rotate through different phrasings: synonyms, related concepts, alternative terminology. Shift perspectives — approach the topic from different disciplines, stakeholder viewpoints, or levels of abstraction. Explore adjacent communities that might discuss the topic under different vocabulary. When the primary domain has been well-explored, deliberately investigate how the topic appears in unexpected fields — different academic disciplines, cultural traditions, or historical periods. Let terms discovered in earlier searches seed new queries; titles and terminology in found sources often reveal phrasings you hadn't considered. When a line of inquiry yields diminishing returns, step back and reframe the approach rather than repeating similar queries. After each reformulation cycle, verify that expanded queries still serve the original research question — breadth should illuminate the question, not drift from it.
</search_strategy>

<reasoning_discipline>
Throughout your thinking, periodically challenge your own conclusions using these reflective checkpoints, each targeting a distinct failure mode:

"Well, have I fully considered the nuances here?" — check for completeness and missing dimensions.

"Wait — what am I missing or oversimplifying?" — check for errors, unjustified leaps, and unstated assumptions.

"Hmm, let me reconsider this from a different angle." — check for alternative interpretations or overlooked perspectives.

"But does this actually hold up under scrutiny?" — stress-test conclusions against counterevidence.

"Let me verify — is this consistent with everything above?" — audit internal coherence across your reasoning.

Each time you pause, look for specific gaps, name specific assumptions, and propose specific alternatives — substantive reflection that identifies concrete issues, not a surface-level confirmation that things look fine. Reasoning that felt easy deserves extra scrutiny; it may have been shallow.

Scale the depth of self-reflection to the complexity of what you are examining — brief checks for straightforward items, deeper cycles for ambiguous or high-stakes findings. Refine your thinking before moving on.

Ground your conclusions in retrieved evidence. When making a claim, it should be traceable to a source found during the investigation, not assumed from background knowledge.
</reasoning_discipline>

<source_evaluation>
Prefer primary sources over secondary summaries — when a summary references an original study, document, or dataset, trace to the original. Favor authoritative, domain-specific sources such as official documentation, peer-reviewed research, recognized experts, and institutional publications over generic aggregators or SEO-optimized content. When sources conflict, treat the disagreement as a valuable signal worth investigating rather than a problem to sidestep. Assess recency contextually: for rapidly evolving topics, recent sources may supersede older ones; for stable knowledge, foundational works remain authoritative. Distinguish between established consensus, emerging findings, and speculation.
</source_evaluation>

<completion_criteria>
Before beginning your synthesis, test your findings: did the investigation surface sources, perspectives, and connections you genuinely wouldn't have found from memory alone? Were later searches shaped by what earlier results revealed — queries you couldn't have written before searching? Can you identify specific productive avenues you haven't pursued — citation chains worth following, bibliographies worth mining, cross-domain angles worth exploring, sources worth reading in full? If the first two tests aren't clearly satisfied, or if concrete next steps are readily apparent, the investigation has remaining depth. Return to the evaluate-and-iterate cycle before synthesizing.

Continue researching until you are genuinely confident that additional effort would not materially improve the findings. The research is ready for synthesis when these signals converge — assessed based on what your searches actually returned, not what you already know from training data:

- The major dimensions and sub-questions have been explored from multiple angles.
- Key claims are supported by two or more independent sources where achievable.
- Recent searches across genuinely different methods — keyword searching, deep source reading, citation tracing, and cross-domain exploration, each substantively exercised — are returning information already encountered rather than meaningfully new material. If any of these techniques has been used only in passing, apparent convergence may reflect the limits of the approach rather than the topic.
- Key sources identified during the investigation have been read in depth — their references, terminology, and core arguments extracted — not merely found in search results.
- Conflicting information has been investigated and either resolved or explicitly documented as contested.
- If information that a knowledgeable person would expect to find is absent, this has been noted and investigated — distinguishing "not yet found" from "likely does not exist."
- The investigation has surfaced a meaningful body of non-obvious findings — sources, perspectives, or connections you didn't anticipate. A single surprising find amid otherwise predictable results suggests the investigation has touched the edge of deeper territory, not reached its center.

If a sub-question has been explored from only one angle, that is not yet sufficient. If a new dimension emerges during research, follow it rather than treating the original plan as fixed. When uncertain whether coverage is sufficient, err toward one more search pass.
</completion_criteria>

<output_standards>
Ground every factual claim in retrieved evidence and cite sources so the user can verify. Distinguish clearly between well-established findings supported by multiple sources, emerging or tentative findings with limited evidence, and open questions where information is insufficient or contradictory. When sources conflict, present both perspectives with context on the strength of evidence for each. Explicitly acknowledge areas where the search was unable to find adequate information — gaps are valuable findings. Organize your response to serve the user's actual question.
</output_standards>

User prompt:

Research this extensively across multiple rounds. Use what you find to guide deeper follow-up searches. I need specific discovered sources I can go read myself, and your synthesis — grounded in what you discover, not what you already knew.

I am looking for references on algorithms to generate prime numbers.

Python script example:

#!/usr/bin/env python3
"""Call Anthropic API with system/user prompt files and save the JSON response."""

import argparse
import json
import os
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import requests

API_URL = "https://api.anthropic.com/v1/messages"
API_VERSION = "2023-06-01"
REQUEST_TIMEOUT = 1800  # 30 minutes; max-effort + 128k output + web tools can be slow
NUM_ATTEMPTS = 5
MAX_CONTINUATIONS = 10

OMITTED = "<value omitted>"
_REDACT_IF_TYPE = {
    "id": {"message", "server_tool_use"},
    "signature": {"thinking"},
    "tool_use_id": {"web_search_tool_result", "web_fetch_tool_result"},
}

STAT_NAMES = (
    "thinking iterations",
    "server_tool_use iterations",
    "web_search count",
    "web_fetch count",
    "web_search_result count",
    "web_fetch_result count",
    "citations count",
)

_TOOL_USE_BLOCK_TYPES = {
    "server_tool_use",
    "web_search_tool_result",
    "web_fetch_tool_result",
    "code_execution_tool_result",
}


def sanitize_response(data):
    """Redact opaque/internal field values from the API response."""
    if isinstance(data, dict):
        obj_type = data.get("type")
        return {
            k: OMITTED
            if k.startswith("encrypted_") or obj_type in _REDACT_IF_TYPE.get(k, ())
            else sanitize_response(v)
            for k, v in data.items()
        }
    if isinstance(data, list):
        return [sanitize_response(item) for item in data]
    return data


def collect_stats(content):
    """Collect response statistics from the top-level content blocks."""
    thinking_count = 0
    tool_use_sequences = 0
    in_tool_use = False
    web_search_count = 0
    web_fetch_count = 0
    search_result_count = 0
    fetch_result_count = 0
    citation_count = 0

    for block in content:
        block_type = block.get("type")

        if block_type == "thinking":
            thinking_count += 1

        if block_type in _TOOL_USE_BLOCK_TYPES:
            if not in_tool_use:
                tool_use_sequences += 1
                in_tool_use = True
        else:
            in_tool_use = False

        if block_type == "server_tool_use":
            if block.get("name") == "web_search":
                web_search_count += 1
            elif block.get("name") == "web_fetch":
                web_fetch_count += 1

        if block_type == "web_search_tool_result":
            for item in block.get("content", []):
                if isinstance(item, dict) and item.get("type") == "web_search_result":
                    search_result_count += 1

        if block_type == "web_fetch_tool_result":
            fetch_result_count += 1

        citation_count += len(block.get("citations", []))

    return (
        thinking_count,
        tool_use_sequences,
        web_search_count,
        web_fetch_count,
        search_result_count,
        fetch_result_count,
        citation_count,
    )


def aggregate_stats(stats_list):
    """Sum per-turn stats tuples element-wise."""
    return tuple(sum(s[i] for s in stats_list) for i in range(len(STAT_NAMES)))


def format_stats_line(stats):
    """Format a stats tuple into a comma-separated report line."""
    return ", ".join(f"{name}: {value}" for name, value in zip(STAT_NAMES, stats))


def build_output_path(base_output, attempt_index, turn_index):
    """Build output path with attempt and optional turn index."""
    p = Path(base_output)
    if turn_index == 0:
        return str(p.with_name(f"{p.stem}_{attempt_index}{p.suffix}"))
    return str(p.with_name(f"{p.stem}_{attempt_index}_turn_{turn_index}{p.suffix}"))


def run_attempt(index, headers, body, base_output):
    """Execute API calls for one attempt, continuing on pause_turn."""
    t0 = time.monotonic()
    current_body = body
    per_turn_stats = []
    accumulated_content = []  # all turns' raw content, grown across continuations

    for turn in range(MAX_CONTINUATIONS + 1):
        try:
            response = requests.post(
                API_URL, headers=headers, json=current_body, timeout=REQUEST_TIMEOUT
            )
        except requests.exceptions.RequestException as exc:
            elapsed = time.monotonic() - t0
            return index, None, elapsed, None, str(exc)

        if not response.ok:
            elapsed = time.monotonic() - t0
            return index, None, elapsed, response.status_code, response.text

        data = response.json()
        raw_content = data.get("content", [])
        accumulated_content.extend(raw_content)

        # Save sanitized response for this turn
        output_path = build_output_path(base_output, index, turn)
        with open(output_path, "w") as f:
            json.dump(sanitize_response(data), f, indent=2)

        per_turn_stats.append(collect_stats(raw_content))

        if data.get("stop_reason") != "pause_turn":
            break

        # Continue: accumulated content keeps all server_tool_use/result pairs intact
        current_body = {**body}
        current_body["messages"] = [
            body["messages"][0],
            {"role": "assistant", "content": accumulated_content},
        ]

        # Preserve container for code execution continuations (API expects plain string)
        container = data.get("container")
        if container:
            container_id = (
                container.get("id") if isinstance(container, dict) else container
            )
            current_body["container"] = container_id

    elapsed = time.monotonic() - t0
    return index, aggregate_stats(per_turn_stats), elapsed, response.status_code, None


def main():
    parser = argparse.ArgumentParser(
        description="Call Anthropic API with system and user prompt files."
    )
    parser.add_argument("--system", required=True, help="Path to system prompt file")
    parser.add_argument("--user", required=True, help="Path to user prompt file")
    parser.add_argument("--output", required=True, help="Path to output JSON file")
    parser.add_argument(
        "--web_tool_2026",
        action="store_true",
        help="Use 2026 web tool versions (web_search_20260209, web_fetch_20260209)",
    )
    args = parser.parse_args()

    api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        print(
            "Error: ANTHROPIC_API_KEY environment variable is not set",
            file=sys.stderr,
        )
        sys.exit(1)

    with open(args.system, "r") as f:
        system_prompt = f.read()

    with open(args.user, "r") as f:
        user_prompt = f.read()

    if args.web_tool_2026:
        web_search_type = "web_search_20260209"
        web_fetch_type = "web_fetch_20260209"
        beta_features = "context-1m-2025-08-07"
    else:
        web_search_type = "web_search_20250305"
        web_fetch_type = "web_fetch_20250910"
        beta_features = "web-fetch-2025-09-10,context-1m-2025-08-07"

    headers = {
        "x-api-key": api_key,
        "anthropic-version": API_VERSION,
        "anthropic-beta": beta_features,
        "content-type": "application/json",
    }

    body = {
        "max_tokens": 128000,
        "model": "claude-sonnet-4-6",
        "system": [
            {
                "cache_control": {"type": "ephemeral"},
                "type": "text",
                "text": system_prompt,
            }
        ],
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "cache_control": {"type": "ephemeral"},
                        "type": "text",
                        "text": user_prompt,
                    }
                ],
            }
        ],
        "tools": [
            {
                "name": "web_search",
                "type": web_search_type,
                "max_uses": 50,
            },
            {
                "name": "web_fetch",
                "type": web_fetch_type,
                "citations": {"enabled": True},
                "max_uses": 50,
            },
        ],
        "thinking": {"type": "adaptive"},
        "output_config": {"effort": "high"},
    }

    results = []
    with ThreadPoolExecutor(max_workers=NUM_ATTEMPTS) as executor:
        futures = {}
        for i in range(NUM_ATTEMPTS):
            future = executor.submit(run_attempt, i, headers, body, args.output)
            futures[future] = i

        for future in as_completed(futures):
            results.append(future.result())

    results.sort(key=lambda r: r[0])

    all_stats = []
    elapsed_times = []
    for index, stats, elapsed, status_code, error_text in results:
        if stats is None:
            print(
                f"Error{f' {status_code}' if status_code else ''}: {error_text}",
                file=sys.stderr,
            )
        else:
            all_stats.append(stats)
            elapsed_times.append(elapsed)
            print(format_stats_line(stats))

    if all_stats:
        n = len(all_stats)
        print()
        for i, name in enumerate(STAT_NAMES):
            avg = sum(s[i] for s in all_stats) / n
            print(f"Average {name}: {avg:.2f}")
        print(f"Average response time: {sum(elapsed_times) / n:.2f}s")


if __name__ == "__main__":
    main()

Command line to run the script:

python call_anthropic.py --system system.txt --user user.txt --web_tool_2026 --output result.txt

The script launches 5 parallel requests.
In my testing, out of the 5 requests, 3 requests required 2 turns to complete, and 2 required 3 turns to complete.

Where does this happen?

Big-AGI Pro (big-agi.com)

Impact on your workflow

Medium - Workaround exists

Environment (if applicable)

No response

Additional context

No response

Pinned by enricoros

Metadata

Metadata

Assignees

No one assigned

    Labels

    COMMITTEDREADY-APPLYEnrico manually verified. Ready to apply to main.

    Projects

    Status
    Shipped

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions