Use Multiple Turns
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:
| Advantage | Explanation |
|---|---|
| Step-by-step control | You can review each result before the next step, and correct course early. |
| Focused requests | Each turn has one clear goal, which usually produces better output than one prompt with many goals. |
| Shared context | Later turns can refer to earlier results naturally: "section 2", "the second option", "make it shorter". |
| Interactive refinement | You (or your users) can react to what Claude produced instead of specifying everything in advance. |
| Clarification | Claude 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:
| Reference | Works 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 conversation | Prompt chaining (separate requests) | |
|---|---|---|
| How | One growing history; each step is a new turn | Independent requests; your code passes selected output from one step into the next prompt |
| Context | Every step sees everything before it | Each step sees only what you pass in |
| Cost | Grows with each step, because the history is resent | Each request contains only what it needs |
| Control | Natural and conversational | Precise: you choose exactly what each step receives |
| Best for | Interactive refinement, chats, exploratory work | Automated 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
STEPSdefines 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.pendingandhistoryfollow the safe pattern from Lesson 24: a turn is committed only after it succeeds.- 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.
- 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.
- 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
- Change
STEPSto produce a lesson on Python sets instead of dictionaries. Run the workflow and compare the token growth. - Add a fifth step that asks Claude to list three common beginner mistakes for section 2.
- Rewrite the
Tightenstep as prompt chaining: send a single-turn request that contains only the section 2 text from theExpandstep. Compare the input tokens with the multi-turn version. - Modify
run_workflow()to ask for confirmation (input("Continue? [y/n] ")) after each step, and stop if the answer is noty.
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