Study guides / CCAR-P / Domain 2

Solution Design & Architecture · Lesson 5 of 6

2.5 - Decomposition Techniques for Complex Problems

Choose among prompt chaining, sectioning, routing and plan-then-execute, place step boundaries where they can be verified, and add checks between steps so errors do not compound.

Decomposition turns one problem that is too large, too varied or too risky for a single reliable model call into steps you can inspect, test and, where possible, run in parallel. It is not free. Every boundary adds latency and tokens and risks losing information, so the architect's questions are: which technique fits this problem's shape, where should the boundaries go, and what verifies each step. Lesson 2.3 covers choosing between workflow and agent, and lesson 2.4 covers multi-agent orchestration; this lesson is the toolbox inside those choices.

Four techniques and when each fits

TechniqueStructureUse whenMain risk
Prompt chainingFixed sequence; each call processes the previous output, optionally with programmatic gates between stepsThe task cleanly splits into fixed subtasks; you accept latency for accuracy and inspectabilityErrors compound; the path cannot adapt to surprises
SectioningIndependent subtasks run concurrently and their outputs are aggregatedSubtasks do not depend on each other, or a concern (such as a safety check) should run separately from the main answerAggregation logic; inconsistent standards across sections
RoutingClassify the input, then send it to a specialised prompt, toolset or modelDistinct categories are better handled separately; you want to send easy inputs to a cheaper modelThe classifier's accuracy is a ceiling; needs a fallback route
Plan-then-executeA planner drafts steps from what it finds, an executor performs them, the plan is revised as facts emergeThe scope is unknown up front: unfamiliar codebase, incident investigationVariable cost and time; harder to debug; plan quality decides everything

These map onto Anthropic's workflow patterns: chaining, routing, and parallelization in its sectioning form. Parallelization also has a voting form (run the same task several times for diverse attempts or a consensus). Plan-then-execute is the dynamic end of the spectrum, close to Anthropic's orchestrator-workers pattern, where subtasks are not predefined but determined by the orchestrator (lesson 2.4). An evaluator-optimizer loop is a related tool for the verification step below.

Match the technique to the problem's characteristics, not to what sounds most advanced. Known steps and structured inputs point to chaining or sectioning; distinct input categories point to routing; an open-ended investigation points to plan-then-execute. Offering a fixed pipeline for an open-ended investigation, or dynamic planning for structured document processing, is the mismatch to watch for.

Choosing the boundaries

A boundary earns its cost when at least one of these holds:

  1. You can verify what crosses it. A step whose output cannot be checked is a black box, whatever its size.
  2. The step needs different context, tools or instructions. This is the separation-of-concerns benefit Anthropic cites for routing and chaining.
  3. The work is independent and can run in parallel.
  4. One pass is overloaded. When a single call processes many items, depth becomes inconsistent: thorough on early items, thin on later ones, and the same pattern judged differently in two places. Anthropic's context-engineering guidance explains the underlying pressure: as the number of tokens in the context grows, the model's ability to recall information from it decreases, so context should be treated as a scarce resource. The structural fix is per-item passes plus a separate cross-item integration pass. A larger window or a longer, more insistent prompt does not change the structure, so confirm the diagnosis and the fix with an eval rather than assuming.

Equally important, do not split more than the evidence supports. Anthropic's guidance on multi-agent design favours context-centric decomposition over problem-centric: dividing purely by type of work creates constant coordination overhead, and work should be split when its context can genuinely be isolated. Design the interface between steps as deliberately as the steps: pass structured data that keeps content and metadata (source, confidence, item ID) so later steps can cite and cross-check, rather than passing prose summaries that lose them.

Common exam distractor

For inconsistent depth across many items in one request, the distractors are a bigger context window, a more detailed prompt, a more capable model, or batching items into groups with no integration pass. The structural answer is per-item analysis passes followed by a cross-item integration pass. The second family of distractors is technique mismatch: a rigid pipeline for open-ended investigation, or a free-roaming planner for a fixed, structured extraction.

Verification between steps

In a chain, per-step reliabilities multiply: an unnoticed error at step 2 becomes the input, and the confident premise, of every later step. So put a check at each boundary, cheapest first:

A failing gate needs a route: repair with the specific error, retry with a changed input, or escalate. It must never simply continue. Evaluate each step on its own dataset and the pipeline end to end, so that when the final output is wrong you can locate the step that introduced the defect. Finally, note Anthropic's caution that with adaptive thinking and native subagent orchestration, Claude handles much multistep reasoning internally. Chain for control, inspection, parallelism or a required pipeline structure, not merely to make the model think in stages.

Key concept: a boundary must earn its cost

Every extra step costs latency, tokens and information. Justify each boundary by what it buys: a verifiable output, a different context, parallelism, or relief for an overloaded pass. If you cannot name the benefit, merge the steps. If you cannot name the check, you have a black box in the middle of the pipeline.

Worked example: contract portfolio review

Thirty vendor contracts need a comparative risk summary. A single call with all thirty produces uneven depth and inconsistent judgements. A decomposed design: a router tags each contract by type so a specialised prompt is used; a sectioned per-contract pass extracts indemnity, termination and liability terms into a schema, running in parallel; a gate validates the schema and checks that every quoted clause exists in the source text; an integration pass then compares the structured findings across contracts for cross-contract issues; a human reviews only items the gates flagged. Note that the integration pass reads the compact structured findings, not thirty raw documents, so it avoids recreating the overloaded pass it was meant to fix.

Exam traps

Practice question

A legal-operations team asks Claude to review 30 vendor contracts in one request and produce a risk summary. The output is detailed for the first ten contracts and superficial for the last ten, and the same indemnity clause is flagged in one contract but passed in another. Which change best addresses the problem?

  • A Review each contract in its own parallel pass into a validated schema, then run an integration pass over the structured findings to check cross-contract consistency Correct

    Per-item passes give each contract dedicated attention and the schema gate makes outputs checkable; the integration pass targets exactly the cross-contract inconsistency without re-reading all raw text.

  • B Move to a model with a larger context window so all thirty contracts fit comfortably and the model can attend to every clause at once

    The contracts already fit, and the problem is uneven attention across items in a single pass. A larger window does not change that structure and can worsen recall as the context grows.

  • C Add prompt instructions requiring equal depth for every contract and consistent treatment of identical clauses, and ask the model to re-read its answer before finishing

    This is still one overloaded pass. Instructions steer behaviour but do not remove the structural cause, and there is no check that they were followed.

  • D Replace the review with an autonomous agent that decides which contracts to read, in what order, and when it has seen enough to write the summary

    The steps here are known and structured (review each, then compare), so an open-ended agent adds variance and cost without adding capability.

Build exercise: Decompose a portfolio review and prove it beats one big pass

Intermediate · 90 minutes

You'll practice:

  1. Create 8 short synthetic contracts (a page each). Plant three known issues: an uncapped indemnity in two contracts, an auto-renewal in one, and one contract that is clean. Write the expected findings per contract in a file.

    Planted issues give you ground truth, so you can measure whether decomposition improves recall and consistency rather than judging by impression.

    You should see: A contracts folder and an expected.json mapping each contract ID to its list of expected findings.

    Hints
    1. Which finding do you most want to see treated the same way in two different documents?
    2. Use identical or near-identical indemnity wording in two contracts so consistency is testable.
    3. expected = {'c01': ['uncapped_indemnity'], 'c02': ['auto_renewal'], 'c03': [], 'c04': ['uncapped_indemnity']}  # extend to 8 contracts
  2. Implement the baseline: one call that receives all 8 contracts and returns findings per contract. Score it against expected.json.

    You need the single-pass number to know whether decomposition helped. The exercise is only meaningful if the comparison is measured.

    You should see: A baseline score such as findings recalled and false positives, plus a note on whether the two identical clauses were treated consistently.

    Hints
    1. How will you compare free-text findings with the expected labels automatically?
    2. Ask for findings as labels from a closed list so scoring is a set comparison.
    3. labels = 'uncapped_indemnity, auto_renewal, unlimited_liability, none'
      prompt = 'For each contract return JSON {id: [labels]} using only these labels: ' + labels + '\n\n' + all_contracts_text
  3. Implement the decomposed version, step one: a per-contract pass run concurrently with a bounded number of parallel calls, each returning schema-constrained findings.

    This is the sectioning step. Bounding concurrency respects rate limits and keeps behaviour predictable.

    You should see: Eight structured results, produced in roughly the time of the slowest single call rather than the sum.

    Hints
    1. What could go wrong if you launch hundreds of calls at once?
    2. Use AsyncAnthropic with asyncio.gather and an asyncio.Semaphore to cap concurrency.
    3. import asyncio, json, anthropic
      client = anthropic.AsyncAnthropic()
      MODEL = 'claude-sonnet-5'  # check the models overview page for current IDs
      SCHEMA = {'type': 'object', 'properties': {'findings': {'type': 'array', 'items': {'type': 'object', 'properties': {'label': {'type': 'string'}, 'quote': {'type': 'string'}}, 'required': ['label', 'quote'], 'additionalProperties': False}}}, 'required': ['findings'], 'additionalProperties': False}
      sem = asyncio.Semaphore(4)
      async def review(doc):
          async with sem:
              r = await client.messages.create(model=MODEL, max_tokens=1024, messages=[{'role': 'user', 'content': 'Review this contract for indemnity, renewal and liability risks. Quote the clause for each finding.\n\n' + doc['text']}], output_config={'format': {'type': 'json_schema', 'schema': SCHEMA}})
          return {'id': doc['id'], 'text': doc['text'], **json.loads(next(b.text for b in r.content if b.type == 'text'))}
      async def run(docs):
          return await asyncio.gather(*(review(d) for d in docs))
  4. Add a gate after the per-contract pass: reject any finding whose quoted clause does not appear in the source contract text, and route rejected items to a retry once, then to an escalation list.

    This is verification between steps in its cheapest form: deterministic code that catches fabricated or mismatched quotes before they reach the integration pass.

    You should see: A list of accepted findings and a separate escalation list. If you plant a fabricated quote, the gate catches it.

    Hints
    1. What can you verify about a quote without asking a model?
    2. Check that the quoted text is a substring of the contract after normalising whitespace.
    3. def gate(result):
          norm = lambda s: ' '.join(s.split()).lower()
          text = norm(result['text'])
          ok = [f for f in result['findings'] if norm(f['quote']) in text]
          bad = [f for f in result['findings'] if norm(f['quote']) not in text]
          return ok, bad
  5. Add the integration pass: give Claude only the accepted structured findings (not the raw contracts) and ask for cross-contract inconsistencies and a ranked risk summary.

    The integration pass handles what per-item passes cannot see. Feeding it compact structured findings avoids recreating the overloaded single pass.

    You should see: A summary that flags the two identical indemnity clauses together and recommends consistent treatment.

    Hints
    1. What does the integration step need that the per-item step did not have?
    2. Pass a JSON list of {id, label, quote} and ask it to group identical or conflicting treatments and to rank by risk.
    3. prompt = 'Findings from separate contract reviews (JSON):\n' + json.dumps(accepted) + '\n\nGroup findings that describe the same clause, note any contract where a comparable clause was not flagged, and rank issues by risk.'
  6. Score the decomposed pipeline against expected.json and compare with the baseline. Then write a short rationale for each boundary (router, per-item pass, gate, integration) naming the benefit it delivered, and remove any that delivered none.

    A boundary must earn its cost. Removing a step that did not help is part of good decomposition.

    You should see: A comparison of recall, false positives, consistency across the two identical clauses, calls and total tokens, and a rationale table with a keep or remove verdict per boundary.

    Hints
    1. Which boundary, if removed, would change your score?
    2. Ablate one step at a time and rerun; keep a boundary only if it improved a measured metric or made a failure detectable.
    3. Example: per-item pass improved recall on late contracts; gate caught one fabricated quote; integration pass produced the only consistency finding; router added a call and changed nothing, so it is removed.

Sources