structuring complex workflows with Claude
A complex business process rarely fits into a single request to Claude. Multi-step conversation design breaks a large task down into traceable stages with clear state, so every step can be checked, corrected and repeated, instead of hoping for one perfect answer.
Table of Contents
- 1. Why one request is not enough for complex tasks
- 2. State management: what persists between steps
- 3. Message history as the conversation's memory
- 4. Controlling control flow between stages
- 5. Intermediate checks and human-in-the-loop
- 6. Error handling across multiple steps
- 7. Context length and summarization strategies
- 8. Proven conversation patterns for typical workflows
- 9. Comparing designs: linear, branched, iterative
- 10. Summary
- 11. FAQ
1. Why one request is not enough for complex tasks
A single prompt works well for clearly scoped tasks with an unambiguous answer. But once a process requires multiple decisions, intermediate results or external data lookups, a single request quickly reaches its limits. A multi-step conversation design breaks the overall task down into smaller, clearly defined stages that each build on the result of the previous stage.
The advantage of a multi-step conversation design lies not only in better answer quality per step, but above all in traceability: every intermediate step can be logged, validated and, if needed, repeated in isolation without restarting the entire process. With a monolithic prompt meant to handle everything at once, an error in the middle of the process is hard to locate and even harder to fix in a targeted way.
This article shows how state management, message history and control flow are concretely built in a multi-step conversation design, which patterns have proven themselves for typical workflows, and where human-in-the-loop checks make sense.
2. State management: what persists between steps
The Claude API itself is stateless: every request contains the entire history so far, which the application must manage itself. A multi-step conversation design therefore needs a clear separation between the conversation history sent to Claude and the application state maintained outside the conversation, for example which stage of the process the flow is currently in.
A proven pattern is an explicit state object that, alongside the message history, also holds structured intermediate results: the current step, data already collected, and open decisions. This state object is updated at every step and can be persisted and later resumed after a crash or interruption, without the conversation having to start over from scratch.
from dataclasses import dataclass, field
from enum import Enum
class WorkflowStep(Enum):
GATHER_REQUIREMENTS = "gather_requirements"
DRAFT_SOLUTION = "draft_solution"
REVIEW = "review"
FINALIZE = "finalize"
@dataclass
class ConversationState:
"""Explicit application state, separate from the raw message history."""
current_step: WorkflowStep = WorkflowStep.GATHER_REQUIREMENTS
collected_data: dict = field(default_factory=dict)
messages: list = field(default_factory=list)
open_questions: list[str] = field(default_factory=list)
def advance(self, next_step: WorkflowStep) -> None:
"""Move the workflow forward and log the transition."""
print(f"Transitioning: {self.current_step.value} -> {next_step.value}")
self.current_step = next_step
def to_dict(self) -> dict:
"""Serialize state for persistence between sessions."""
return {
"current_step": self.current_step.value,
"collected_data": self.collected_data,
"messages": self.messages,
"open_questions": self.open_questions,
}
3. Message history as the conversation's memory
The message history is the actual memory of a multi-step conversation design: every user message and every Claude response is appended to the list and sent in full with the next request. Without this history, Claude would have no memory whatsoever of previous decisions, gathered information, or already rejected alternatives at each step.
A common mistake is letting the message history grow unchecked without reviewing which parts are actually relevant to the current step. In a multi-step conversation design with many stages, targeted curation pays off: intermediate steps no longer relevant to the current decision can be compressed or replaced with a short summary to keep the context lean and focused without losing important information.
4. Controlling control flow between stages
Control flow determines which step runs next, based on the result of the previous step. A simple but effective pattern uses tool use so that Claude itself signals which next step makes sense, instead of the application having to reconstruct that decision from text patterns. A tool advance_workflow with an enum field for the next step makes this decision explicit and machine readable.
For processes with clear, predictable stages, a linear control flow is suitable, where the application itself dictates the order of steps and Claude only handles the substantive work within each stage. For processes with variable order, for example a diagnosis where different next steps make sense depending on the result, a branched control flow is suitable, where Claude itself determines the next step via tool use.
import anthropic
client = anthropic.Anthropic()
advance_tool = {
"name": "advance_workflow",
"description": "Signal which workflow step should run next",
"input_schema": {
"type": "object",
"properties": {
"next_step": {
"type": "string",
"enum": ["gather_requirements", "draft_solution", "review", "finalize"]
},
"reasoning": {"type": "string", "description": "Why this step is next"}
},
"required": ["next_step", "reasoning"]
}
}
def run_step(state: ConversationState) -> ConversationState:
"""Execute one workflow step and let Claude decide the next transition."""
state.messages.append({"role": "user", "content": build_step_prompt(state)})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
tools=[advance_tool],
tool_choice={"type": "tool", "name": "advance_workflow"},
messages=state.messages,
)
decision = next(b.input for b in response.content if b.type == "tool_use")
state.messages.append({"role": "assistant", "content": response.content})
state.advance(WorkflowStep(decision["next_step"]))
return state
5. Intermediate checks and human-in-the-loop
Not every stage of a multi-step conversation design should run fully automated. For processes with financial, legal or safety related consequences, explicit human approval before the next step makes sense. The design should model such checkpoints as their own stage, where the process pauses and waits for external confirmation, instead of squeezing the check into an existing stage as a side effect.
A proven pattern: at the end of a stage, Claude generates a compact summary of the decisions made so far and the proposed next steps, which is presented to a human for approval. Only after explicit confirmation does the process continue, with the confirmation as additional context for the next Claude call. This intermediate check prevents errors from an early stage from silently propagating into subsequent stages of the multi-step conversation design.
CRITICAL_STEPS = {WorkflowStep.FINALIZE}
def request_human_approval(state: ConversationState, proposed_summary: str) -> bool:
"""Pause the workflow and wait for an explicit external confirmation."""
print(f"--- Approval required before step: {state.current_step.value} ---")
print(proposed_summary)
decision = input("Approve and continue? [y/N]: ").strip().lower()
return decision == "y"
def run_step_with_gate(state: ConversationState) -> ConversationState:
"""Run a step, and if it is critical, gate it on human approval."""
state = run_step(state) # from the control flow example above
if state.current_step in CRITICAL_STEPS:
summary = summarize_state_for_human(state) # application specific
if not request_human_approval(state, summary):
raise RuntimeError("Workflow halted: human approval declined")
return state
6. Error handling across multiple steps
Errors in a multi-step conversation design can occur at any stage: a tool call fails, an external API does not respond, or Claude returns an answer that does not match the expected format. Without explicit error handling, an error from an early stage silently propagates into all subsequent steps and leads to a final result built on a faulty foundation.
Each stage should therefore run its own validation before passing on to the next stage, and offer a defined fallback in case of failure: either a retry of the current stage with enriched context, or a controlled rollback to an earlier, known good stage. A state object persisted after every successful stage makes this rollback technically simple, because the last valid state is always available.
import copy
checkpoint_history: list[ConversationState] = []
def persist_checkpoint(state: ConversationState) -> None:
"""Save a deep copy after every successful step, for rollback on failure."""
checkpoint_history.append(copy.deepcopy(state))
def run_step_with_recovery(state: ConversationState, max_retries: int = 1) -> ConversationState:
"""Retry the current step once, then roll back to the last good checkpoint."""
for attempt in range(max_retries + 1):
try:
new_state = run_step(state)
persist_checkpoint(new_state)
return new_state
except Exception as error:
print(f"Step failed on attempt {attempt}: {error}")
if attempt == max_retries:
if not checkpoint_history:
raise
print("Rolling back to last known good checkpoint.")
return checkpoint_history[-1]
return state
7. Context length and summarization strategies
As the number of stages grows, so does the message history, and eventually a multi-step conversation design approaches the limits of the context window. Instead of dragging the entire history along unchanged, a targeted summarization strategy pays off: completed stages that are no longer decisive for the further course are replaced by a compact summary generated by Claude itself.
This summary should contain the essential decisions and data, but not the full wording of the original interaction. A practical approach lets Claude generate a structured summary in JSON format at the end of each stage, which is then used instead of the detailed messages of that stage in the further course. This keeps the context manageable even in long multi-step conversation designs.
def compress_completed_stage(state: ConversationState, stage_messages: list) -> None:
"""Replace verbose messages of a finished stage with a compact summary."""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=300,
messages=stage_messages + [{
"role": "user",
"content": "Summarize the key decisions and data from this stage as compact JSON."
}]
)
summary_text = response.content[0].text
# Drop the verbose exchange, keep only the compact summary in history
state.messages = [m for m in state.messages if m not in stage_messages]
state.messages.append({"role": "user", "content": f"Prior stage summary: {summary_text}"})
8. Proven conversation patterns for typical workflows
For recurring use cases, a few patterns have established themselves for multi-step conversation designs. The "gather, draft, review" pattern is suitable for creation processes: first requirements are gathered, then a draft is created, then checked against the requirements, with optional feedback loop to the gathering phase if gaps appear. The "plan then execute" pattern strictly separates a planning phase, where Claude outlines the steps up front, from the execution phase, where each planned step is worked through individually.
A third pattern, the "iterative refinement" design, is suitable for creative or analytical tasks where a first draft is improved step by step: each stage receives targeted feedback on the previous version and produces an improved version, until a stopping criterion is reached. All three patterns share the basic idea of a multi-step conversation design: clearly delimited stages with explicit state instead of a single, overloaded request.
9. Comparing designs: linear, branched, iterative
The choice of the right conversation design depends on the structure of the underlying process. The following table compares the three common base designs.
| Design | Control flow | Suitable for | Complexity |
|---|---|---|---|
| Linear | Application dictates fixed order | Predictable processes with fixed stages | Low |
| Branched | Claude determines next step via tool use | Diagnosis, variable decision paths | Medium |
| Iterative | Repeated refinement until stopping criterion | Creative and analytical improvement tasks | Medium to high |
In practice, many real applications combine elements of all three designs: a rough linear framework with individual branched decision points and an iterative refinement loop within a single stage. What matters is choosing the design deliberately, instead of letting it emerge implicitly from a growing number of special cases in the prompt.
Mironsoft
Claude workflow design and agentic automation
Automate complex processes reliably with Claude?
We design multi-step conversation designs with clear state management, control flow and human-in-the-loop checkpoints that automate your complex business processes in a traceable way.
Workflow analysis
Breaking down existing processes into clearly delimited stages
State management
Persistent state objects for robust, resumable processes
Human-in-the-loop
Checkpoints for critical decisions in the automation process
10. Summary
Multi-step conversation design breaks complex tasks down into traceable stages with explicit state, instead of a single overloaded request. A separate state object holds process progress, collected data and open questions independently of the raw message history. Control flow can be dictated linearly by the application or determined by Claude itself via tool use, depending on how predictable the process is.
Human-in-the-loop checkpoints before critical decisions, robust error handling with defined rollback points, and summarization strategies against growing context length are the three most important building blocks for production ready multi-step conversation designs. Anyone who plans for these building blocks from the start builds workflows that stay traceable and maintainable even for longer, more complex processes.
Multi-Step Conversation Design: Key Takeaways
Separate state
Explicitly separate application state from the raw message history and persist it.
Choose control flow deliberately
Linear for predictable processes, branched via tool use for variable decision paths.
Involve humans
Critical stages with explicit approval instead of a fully automated run.
Curate context
Summarize completed stages instead of letting the full history grow without bound.