Prompt EngineeringTechniques that hold up

Decomposition

Split one hard prompt into a few checkable ones — and know when not to.

WHY THIS, FOR YOU

You have one prompt that reads the email, works out which plan the customer is on, decides whether they are owed a refund, drafts the reply and returns JSON. It is right about eighty percent of the time and you have no idea which of those four jobs the twenty percent is. So you tweak a sentence, the number moves, and you cannot say why. This lesson is about cutting that prompt into pieces you can actually test — and about the real costs of doing it, so you do not split things that were fine.

Why prompts fail named a category you can only reach after ruling out the other two: the task really is too hard as posed. Splitting is the oldest answer to that, and it is no longer the first one to try. As of October 2026, Anthropic's guidance is that a current model with thinking on does most multi-step work inside one call. So this lesson is half how to cut, and half when to leave the prompt whole.

1.One prompt, five jobs, one number

A prompt doing five things fails at the hardest one and hands you a single output. That output is your only signal, and it is the wrong shape for debugging. With five jobs fused into one call, you cannot:

  • tell which step was wrong when the answer is wrong,
  • change one step without risking the other four,
  • test a step against its own examples,
  • put the easy steps on a cheaper model, or in plain code,
  • log an intermediate that anyone could review.

There is an arithmetic version of the same problem. Five steps at 95% each is about 77% end to end, if the errors are independent. Real errors are correlated, so it is a direction, not a measurement. What is reliable is the shape: accuracy multiplies down, and a fused prompt hides which factor is dragging.

  1. 1 step95%
  2. 2 steps90%
  3. 3 steps86%
  4. 4 steps81%
  5. 5 steps77%
If every step is independently 95% accurate and the errors don't correlate
1 step, 95%. 2 steps, 90%. 3 steps, 86%. 4 steps, 81%. 5 steps, 77%.
End-to-end accuracy as more independently-95%-accurate steps are chained. An illustration to reason from, not a number to quote: real errors are usually correlated. Measure your own chain.

Notice what this argues for. It does not say "more steps is worse". It says that when a result is wrong, a fused prompt gives you one number and no way to find the cause.

2.The chain: extract, decide, write

The shape that works most often is boring: pull out facts, decide something, produce prose. Each step has a narrower job and an output you can check.

ExtractRaw email in, a validated object of facts out. Check: schema, and does the order exist
Decide
  1. refund
  2. decline
  3. needs_human
WriteThe decision, the facts and the customer's own words in, reply text out. Check: length, tone, no new facts
The three-step refund chain. Decide is the only node with more than one exit, and each outcome is a separate path — exactly the branching a fused prompt hides from you.

Look at step 2 once it stands alone, with a typed input and a three-value output. Does it need a model? Refund policy is usually a handful of rules about dates and amounts, and rules are cheaper, faster and exactly repeatable in code. You could not see that while it was buried in the middle of a paragraph. Splitting shows you which parts were never model work.

The sample below is that chain. The parameter spellings are copied from vendor docs as of October 2026, not run here, so try them on your SDK version first.

from datetime import date, timedelta

from anthropic import Anthropic
from pydantic import BaseModel

client = Anthropic()
MODEL = "claude-sonnet-5-5"   # as of October 2026

def text_of(resp) -> str:
    return "".join(b.text for b in resp.content if b.type == "text")

class Facts(BaseModel):
    order_id: str | None
    purchase_date: str | None   # YYYY-MM-DD, or null when the email does not say
    product: str | None
    problem: str | None
    repeat_contact: bool        # added after a reply missed 'third time this month'

def extract(email: str) -> Facts:
    resp = client.messages.parse(
        model=MODEL,
        max_tokens=2000,   # thinking is on by default and counts against this
        system="Extract the facts. Use null for anything not stated. Never guess.",
        messages=[{"role": "user", "content": email}],
        output_format=Facts,
    )
    # The schema holds only for a finished answer: a refusal or a cut-off may not match it.
    if resp.stop_reason != "end_turn":
        raise RuntimeError(f"no usable answer: {resp.stop_reason}")
    return resp.parsed_output

def decide(f: Facts, today: date) -> str:
    # Step 2 began as a model call. It turned out to be three rules.
    if f.order_id is None or f.purchase_date is None or f.repeat_contact:
        return "needs_human"
    if today - date.fromisoformat(f.purchase_date) > timedelta(days=30):
        return "decline"
    return "refund"

def write(decision: str, f: Facts, email: str) -> str:
    resp = client.messages.create(
        model=MODEL,
        max_tokens=2000,
        system="Write a short, kind reply that carries out the decision. State no facts beyond those given.",
        messages=[{"role": "user", "content":
            f"Decision: {decision}\nFacts: {f.model_dump_json()}\n<email>\n{email}\n</email>"}],
    )
    if resp.stop_reason != "end_turn":
        raise RuntimeError(f"no usable answer: {resp.stop_reason}")
    return text_of(resp)

email = "Hi - the lamp I bought in March arrived cracked. Order 8812. Third time this month I've written."
facts = extract(email)
print(facts)   # the whole point of splitting: a typed middle you can look at
reply = write(decide(facts, date.today()), facts, email)

parse with output_format sends Facts as an enforced schema; Output contracts, later in this part, explains how.

That print(facts) is not debugging clutter. It is the thing you bought. You can log it, assert on it, alert when order_id is null more than usual, and hand it to someone who does not read Python. Here purchase_date would come back as None, because "in March" is not a date, and decide sends the case to a person instead of guessing.

Put the seams where you would want to look at the intermediate result.

The failure case is the seam itself. Suppose the first version of Facts had no repeat_contact field. The customer wrote "third time this month", the extractor had nowhere to put it, and the reply was polite and clueless. Every seam is a lossy handoff, and your intermediate shape decides what the rest of the pipeline may know. Two habits help: add the field when a failure shows you what was dropped, and pass the original email to the last step, as write does, so tone survives.

A second shape is the one Anthropic's guide calls the commonest chain: draft, review against written criteria, refine. Each is its own call, so you can log the draft, skip the refine when the review finds nothing, and see whether the reviewer ever changes an outcome. If it never does, delete it.

One more rule of thumb. Steps that depend on each other run in sequence. Steps that do not, such as summarising ten documents, can run at once, which finish in about the time of the slowest one. Only a real dependency forces a queue.

3.What it costs, and when one prompt wins

What you payDetail
LatencyEach dependent step is a round trip, one after another
TokensEvery step re-sends its own instructions and its input
CodeOrchestration, retries, and a distinct failure mode per step
Lost contextStep 3 knows only what step 2 passed on

The vendor's current advice is blunter than the table. With adaptive thinking and subagents, most multi-step work happens inside one call; chain explicitly to inspect intermediate outputs or to enforce a fixed pipeline (as of October 2026). Anthropic's Sonnet 5.5 guide also reports that splitting a structured task into an answer call and then a JSON call kept accuracy high but at very high cost and latency (vendor-run, no numbers given). Treat both as reasons to start with one prompt and earn each split.

Split itKeep it as one prompt
The steps have different failure modes you want to measure apartIt is genuinely one job
You want to store, inspect or audit the middleYou would never look at the intermediate
A step could run on a cheaper model, or on no modelLatency is tight and a human is waiting
A step is deterministic and belongs in codeThe later step needs the original wording and tone
Different steps need different examples or rulesIt already works and you can measure that it works

The last cell is not a joke. Decomposition is a debugging tool, not a virtue. A single prompt that passes its tests does not need splitting because it looks monolithic.

Routing is decomposition's cousin, and often the better first cut. Instead of one prompt with five conditional branches inside it, make one cheap classification call, then dispatch to whichever specialised prompt matches. Every downstream prompt stays short, focused and independently testable. A sixth category means writing a sixth prompt, not editing the one that five other categories depend on.

NaiveOne prompt, five branches inside it

Every category's instructions live in the same call, so a fix to one branch risks the other four, and the prompt keeps growing as categories are added.

What to doClassify, then dispatch

A small classification call names the category. A short prompt built for exactly that category runs alone, and stays focused and independently testable.

The same five categories, handled two ways. Routing keeps every prompt short, and a sixth category becomes a new prompt rather than a new branch inside an old one.

Two things to get right: give the classifier an explicit fallback such as other, going to a generic handler or a human, so it never has to force a bad fit; and log the routing decision, because "which branch did this take" is the first question you will ask when something goes wrong.

A split can also be a security seam. If one call reads untrusted text and a different one acts, the boundary between them is a place to enforce rules in code. A router that picks only from a fixed menu, and never sees tool output again, is what a 2025 design-patterns paper calls an action-selector. Prompt injection explains the threat and Containing prompt injection builds on it.

Next: the technique that makes a single hard step work better, and the many tasks where it is pure waste — Thinking out loud.

WHAT YOU TAKE AWAY

  1. Start with one call, and split only when you need to inspect, store or test the middle.
  2. Give every step a checkable output: a schema, an enum, or a number.
  3. After splitting, ask which steps need a model at all.
  4. Route with one cheap classification call instead of branches inside one prompt.
  5. Expect every seam to lose context, and add the field the next step needs.

RECALL NO SCROLLING BACK

  1. A current model with thinking on can work through several steps in one call. You should still chain when...

  2. The hidden cost of chaining that people miss most often is...

  3. A good place to put a seam is...

  4. Routing means...

  5. Five chained steps that are each right 95 percent of the time...

GO DEEPER