Use Multiple Turns

Su Suriya Ravichandran Updated 10 Nov 2026
10 min read ·Lesson 28 of 200

What "Multiple Turns" Means

A turn is one message in a conversation: one user message, or one assistant reply. A multi-turn interaction is a conversation in which several user and assistant turns build on each other:

Turn 1  user:       "Outline a tutorial on Python decorators."
Turn 2  assistant:  "1. What decorators are  2. Writing one  3. Decorators with arguments ..."
Turn 3  user:       "Expand section 2 with a code example."
Turn 4  assistant:  "## Writing a Decorator ..."
Turn 5  user:       "Now add an exercise for that section."

Lessons 24–27 covered the mechanics: sending history, continuing, chat loops, and resets. This lesson focuses on how to use multiple turns deliberately to get better results than a single request can provide, and when a single request is the better choice.


Why Multiple Turns Help

A single large prompt asks Claude to do everything at once. Splitting work across turns has concrete advantages:

AdvantageExplanation
Step-by-step controlYou can review each result before the next step, and correct course early.
Focused requestsEach turn has one clear goal, which usually produces better output than one prompt with many goals.
Shared contextLater turns can refer to earlier results naturally: "section 2", "the second option", "make it shorter".
Interactive refinementYou (or your users) can react to what Claude produced instead of specifying everything in advance.
ClarificationClaude can ask a question and continue once it has the answer.

The cost is that every turn resends the history, so input tokens grow with each step (Lesson 24).


Common Multi-Turn Patterns

1. Progressive Building

Create something large in steps: an outline first, then each part.

turns = [
    "Outline a beginner tutorial on Python list comprehensions, as 4 numbered sections.",
    "Write section 1 in full, with one code example.",
    "Write section 2 in the same style.",
]

Why it works: the outline defines the structure, and each later turn produces one section with the outline already in context. The sections stay consistent with each other because Claude sees what it has already written.

2. Iterative Refinement

Produce a draft, then improve it with targeted feedback:

turns = [
    "Write a 3-sentence product description for a reusable water bottle.",
    "Make it more energetic and aimed at students.",
    "Shorten it to 2 sentences and end with a call to action.",
]

Each instruction modifies the previous result. "It" refers to the latest version because that version is in the history.

3. Draft, Critique, Revise

Ask Claude to review its own output against criteria, then improve it:

turns = [
    "Write a Python function that validates an email address.",
    "Review the function above for edge cases and readability. List the problems only.",
    "Rewrite the function to fix every problem you listed.",
]

Why it works: separating critique from writing makes the review explicit. The revision turn has a concrete list of problems to fix, which tends to produce a more careful result than asking for a perfect answer in one go.

4. Clarifying Questions

Allow Claude to ask before answering when a request is ambiguous:

system = (
    "If a request is missing information you need, ask one short clarifying question "
    "instead of guessing. Otherwise, answer directly."
)

Turn 1, the user writes "Write a SQL query for the top customers". Turn 2, Claude asks "Top by what: total spend, number of orders, or something else?" Turn 3, the user answers, and Claude writes the query. The answer depends on the clarification, which is possible only in a multi-turn conversation.

5. Few-Shot Examples as Turns

Earlier turns can be synthetic examples written by you, showing the exact format you want (Lesson 23). This is multi-turn structure used for teaching rather than for conversation.


Referring to Earlier Turns

Because the full history is visible, users and your code can refer to earlier content naturally:

ReferenceWorks because
"Expand section 2."The outline with numbered sections is in the history
"Use the second option."Claude's list of options is in the history
"Make it shorter.""It" is the most recent draft
"Go back to the first version."All versions are still in the history

These references stop working if the referenced turn has been trimmed or reset away (Lessons 24 and 27). When a workflow depends on a specific earlier result, keep that result in the history or repeat it explicitly in the new message.


Multi-Turn Conversation vs. Prompt Chaining

There are two ways to split work into steps:

Multi-turn conversationPrompt chaining (separate requests)
HowOne growing history; each step is a new turnIndependent requests; your code passes selected output from one step into the next prompt
ContextEvery step sees everything before itEach step sees only what you pass in
CostGrows with each step, because the history is resentEach request contains only what it needs
ControlNatural and conversationalPrecise: you choose exactly what each step receives
Best forInteractive refinement, chats, exploratory workAutomated pipelines with well-defined stages

Example of chaining:

outline = ask("Outline a tutorial on Python sets, 4 sections, one line each.")
section_1 = ask(f"Write the first section of this tutorial outline:\n<outline>\n{outline}\n</outline>")

Here, ask() stands for a helper that sends a single-turn request and returns its text. The second request contains only the outline, not the whole conversation that produced it.

Rule of thumb: use multi-turn conversations when humans interact step by step. Use chaining for automated pipelines where each step needs only specific inputs. Many applications combine both.


When a Single Turn Is Better

Multiple turns are not always an improvement:

  • The task is simple and well-defined, such as "Translate this sentence to French." One request is faster and cheaper.
  • You already know all the requirements. Put them in one clear prompt, instead of revealing them one turn at a time.
  • Steps do not depend on each other. Independent tasks are better as separate single requests, possibly run in parallel (Lesson 75).
  • Cost matters most. Every extra turn resends the growing history.

Practical Example: A Multi-Turn Tutorial Writer

This script runs a fixed multi-turn workflow to draft part of a tutorial lesson. It produces an outline, expands one section, adds an exercise, then shortens the section. Each step builds on the previous ones. It also shows how input tokens grow from turn to turn.

"""tutorial_writer.py: draft tutorial content through a multi-turn workflow."""

import anthropic
from anthropic import Anthropic
from anthropic.types import Message

MODEL = "claude-opus-5-5"
SYSTEM = (
    "You are an expert technical educator writing W3Schools-style Python tutorials. "
    "Use clear Markdown. Do not add introductions or conclusions unless asked."
)

STEPS = [
    ("Outline", "Create a 4-section outline for a beginner lesson on Python dictionaries. "
                "Number the sections and give each a one-line description."),
    ("Expand",  "Write section 2 in full: an explanation, one code example, and its output."),
    ("Exercise", "Add one short exercise for section 2, with a hidden solution in a <details> block."),
    ("Tighten", "Rewrite section 2 (not the exercise) to be about 30 percent shorter, keeping the example."),
]


def get_text(message: Message) -> str:
    return "".join(block.text for block in message.content if block.type == "text").strip()


def run_workflow(client: Anthropic) -> list[tuple[str, str]]:
    history: list[dict] = []
    results = []

    for name, instruction in STEPS:
        pending = history + [{"role": "user", "content": instruction}]
        try:
            reply = client.messages.create(
                model=MODEL, max_tokens=4096, system=SYSTEM, messages=pending
            )
        except anthropic.APIError as error:
            print(f"[{name}] failed: {error}. Stopping the workflow.")
            break

        if reply.stop_reason != "end_turn":
            print(f"[{name}] stopped with {reply.stop_reason}. Stopping the workflow.")
            break

        history = pending + [{"role": "assistant", "content": reply.content}]
        results.append((name, get_text(reply)))
        print(f"[{name}] done (input tokens: {reply.usage.input_tokens}, "
              f"output tokens: {reply.usage.output_tokens})")

    return results


if __name__ == "__main__":
    for name, text in run_workflow(Anthropic()):
        print(f"\n===== {name} =====\n{text}")

Example progress output (numbers vary):

[Outline] done (input tokens: 78, output tokens: 240)
[Expand] done (input tokens: 330, output tokens: 610)
[Exercise] done (input tokens: 955, output tokens: 380)
[Tighten] done (input tokens: 1350, output tokens: 470)

How It Works

  1. STEPS defines the workflow as named instructions. Each instruction is short and focused, and it refers to earlier results ("section 2", "the exercise") instead of repeating them.
  2. pending and history follow the safe pattern from Lesson 24: a turn is committed only after it succeeds.
  3. The workflow stops on problems. If a step fails or does not end normally, for example by being truncated, later steps would build on incomplete content, so the loop stops instead of continuing blindly.
  4. The progress lines show input tokens rising with every step, from 78 to 1,350 in this example, because each request includes all earlier turns. That growth is the cost of shared context.
  5. The system prompt keeps style rules, such as the tutorial format and the absence of introductions and conclusions, constant across all steps, so the instructions do not need to repeat them.

Possible improvements: let a human approve each step before continuing, or switch the last step to prompt chaining, passing only the section text, to save tokens.


Common Mistakes

Putting Everything into One Huge Turn

What happens: a prompt with many goals produces uneven results, with some parts detailed and others skipped.

Fix: split complex work into focused steps when the steps depend on each other or need review.

Splitting Simple Tasks into Many Turns

What happens: extra latency and cost for no quality benefit.

Fix: use a single clear request for simple, fully specified tasks.

Referring to Content That Is No Longer in the History

What happens: "Expand section 2" after a trim or reset produces confused answers, because the outline is gone.

Fix: keep needed results in the history, or include them explicitly in the new message.

Continuing After a Failed or Truncated Step

What happens: later steps build on incomplete content, and errors compound.

Fix: check stop_reason at every step, and stop, retry, or fix before continuing.

Ignoring Token Growth

What happens: long workflows become surprisingly expensive.

Fix: monitor usage.input_tokens per step, and use chaining or summaries for long pipelines.


Best Practices

  • Give each turn one clear goal.
  • Use the system prompt for rules that apply to every turn.
  • Refer to earlier results explicitly, such as "section 2" or "the list above".
  • Check each step's stop reason before building on it.
  • Choose deliberately between multi-turn and chaining: conversation for interaction, chaining for automated pipelines.
  • Keep a human in the loop for important multi-step outputs.
  • Watch input tokens as conversations grow.

Exercise

  1. Change STEPS to produce a lesson on Python sets instead of dictionaries. Run the workflow and compare the token growth.
  2. Add a fifth step that asks Claude to list three common beginner mistakes for section 2.
  3. Rewrite the Tighten step as prompt chaining: send a single-turn request that contains only the section 2 text from the Expand step. Compare the input tokens with the multi-turn version.
  4. Modify run_workflow() to ask for confirmation (input("Continue? [y/n] ")) after each step, and stop if the answer is not y.
Show answers for steps 3 and 4

Step 3:

expand_text = dict(results)["Expand"]

reply = client.messages.create(
    model=MODEL,
    max_tokens=4096,
    system=SYSTEM,
    messages=[{
        "role": "user",
        "content": f"Rewrite this section to be about 30 percent shorter, keeping the example:\n\n"
                   f"<section>\n{expand_text}\n</section>",
    }],
)
print(reply.usage.input_tokens)

The input tokens are lower than in the multi-turn Tighten step, because the outline and exercise turns are not sent. The trade-off is that Claude no longer sees the rest of the lesson.

Step 4: inside the loop, after printing the progress line:

answer = input(f"Continue after {name}? [y/n] ").strip().lower()
if answer != "y":
    print("Workflow stopped by user.")
    break

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Use Multiple Turns and get answers drawn from it.

Signed-in readers only.