Production Agent Architecture: Task Decomposition Strategies (Part 6)


05 Aug 2026  Istvan Dobrentei  9 mins read.

Lessons Learned While Preparing for the Claude Architect Certification

Two shapes, one question

Every part so far has used one shape of loop: give the model a goal and some tools, let it call whatever it wants, keep going until it stops. That shape has a name — dynamic decomposition — but until now the series never had to name it, because there was nothing to contrast it with.

Task 1.6 introduces the contrast. The objective’s own examples are about code review and test-writing, not customer support, so this part switches examples too, the way Part 3 did. The fake project this time is three short files: auth.py, which checks who’s calling before doing anything, and orders.py and payments.py, which don’t. No single file looks broken on its own — the problem only shows up once you compare them.

The question Task 1.6 asks is: given a workflow like that, do you hand the model a goal and let it figure out the steps, or do you write the steps yourself and have the model fill in each one? Both are legitimate. Picking the wrong one for a given job is the actual mistake this task is testing for.

A fixed sequence: prompt chaining

The first pattern doesn’t use the agentic loop at all. It’s a short Python function that calls the model once per file, then once more at the end. Notice that review_file takes one file, not all three — that’s deliberate. Pasting every file into a single prompt and asking for one combined review works fine for three short files, but it stops working as the project grows: the more content competes for the model’s attention in one pass, the more likely it is to give some files a thorough read and others a shallow skim. This is what the objective calls attention dilution, and per-file passes sidestep it entirely — no file ever has to share attention with another during its own review.

def review_file(file_name: str, content: str) -> str:
    response = client.messages.create(
        model=MODEL,
        max_tokens=400,
        system=REVIEW_SYSTEM_PROMPT,
        messages=[{"role": "user", "content": f"File: {file_name}\n\n{content}"}],
    )
    return extract_text(response)


def run_chained_review(files: dict[str, str]) -> tuple[dict[str, str], str]:
    per_file = {name: review_file(name, content) for name, content in files.items()}
    return per_file, integrate_findings(per_file)

run_chained_review always makes exactly len(files) + 1 calls to the model: one per file, plus one integration pass at the end. That count is decided by the Python code before the first API call ever goes out — the model has no say in how many files get reviewed or when the integration pass happens. That’s the whole definition of prompt chaining: a fixed sequence, written in code, where each step’s output becomes the next step’s input.

integrate_findings is the second half of the chain. It gets every file’s findings at once and looks specifically for patterns that no single-file review could catch:

def integrate_findings(per_file_findings: dict[str, str]) -> str:
    bundle = "\n\n".join(f"--- {name} ---\n{findings}" for name, findings in per_file_findings.items())
    response = client.messages.create(
        model=MODEL, max_tokens=400, system=INTEGRATION_SYSTEM_PROMPT, messages=[{"role": "user", "content": bundle}]
    )
    return extract_text(response)

Both functions end the same way: pull the text block out of the response and return it. That’s common enough between them — and with the loop in Pattern 2 below — that it’s its own small function, extract_text, instead of being repeated three times.

Running this against the three-file project, the per-file pass flagged payments.py for missing an amount check and orders.py and auth.py for their own local issues — each file reviewed with no idea the others exist. The integration pass then caught something none of them could: auth.py’s review flagged a missing try/except around its database call, and the integration pass pointed out that orders.py and payments.py make the same kind of database call with no such flag anywhere in their own reviews — an inconsistency that’s only visible once you can see all three findings side by side.

No fixed sequence: dynamic decomposition

The second pattern is the agentic loop from Part 1, unchanged, pointed at a more open-ended goal: figure out what to test first in a codebase you haven’t seen yet.

DYNAMIC_SYSTEM_PROMPT = (
    "You are planning test coverage for a legacy codebase. Use list_files and "
    "read_file to explore it. Decide yourself which files are worth reading "
    "and in what order — nothing here fixes that in advance. When you're "
    "done, give a prioritized list of what to test first and why."
)


def run_dynamic_investigation(goal: str) -> str:
    return run_agentic_loop(
        user_message=goal,
        system=DYNAMIC_SYSTEM_PROMPT,
        tools=DYNAMIC_TOOLS,
        tool_impls=DYNAMIC_TOOL_IMPLS,
    )

Nothing in this code decides how many times read_file gets called, or in what order. On an actual run, the model listed the files, read all three, and ranked payments.py first — not because a fixed rule said “financial code first,” but because reading it surfaced two separate problems at once (no caller check, no amount check), which made it the most convincing candidate for “test this before anything else” once the model could see both issues together. A fixed pipeline could easily have been written to always review files in the same order, but it couldn’t have produced that specific reasoning without first reading the file and discovering what was actually wrong with it. That’s what “adapts as dependencies are discovered” means in practice: the plan is a consequence of what got found, not a template filled in afterward.

You can find the full file on GitHub. A PHP port is available too, built the same way as the earlier parts.

Picking one over the other

The three files in this example are small enough that either pattern could technically handle both jobs. The point isn’t that one pattern is better — it’s that they fit different shapes of problem, and the shape of the problem is usually obvious before you write a line of code.

A code review over a known, fixed set of files is predictable: you already know how many files there are, and every one of them deserves the same kind of local pass before anything looks at all of them together. That predictability is exactly what a fixed pipeline is good at — it guarantees every file gets reviewed, once, in isolation, with no risk of the model deciding one file doesn’t need a look.

“What should we test first in a codebase we don’t know yet” isn’t predictable the same way. There’s no fixed number of files worth reading, and which ones matter depends entirely on what’s in them — something you can’t know until you’ve looked. Forcing that into a fixed sequence means either reading every file whether it’s relevant or not, or guessing at relevance before you have any evidence for the guess. Letting the model decide, one read_file call at a time, means it only goes deeper where the code actually gives it a reason to.

Critical design decisions

  • The step count is the tell: if you can write down the exact number and order of steps before running anything, that’s a sign the job wants prompt chaining. If the right number of steps depends on what the first few steps find, that’s dynamic decomposition.
  • Isolation is a feature, not a limitation: review_file’s system prompt explicitly tells it not to comment on other files. A per-file pass that tries to reason about the whole project defeats the purpose of splitting the work up in the first place.
  • The integration pass needs everything, not a summary: integrate_findings receives every file’s findings verbatim. A cross-file inconsistency is easy to miss if one side of it got paraphrased away before the comparison happened.
  • Dynamic decomposition is just the ordinary agentic loop: nothing about run_dynamic_investigation is new. Task 1.6 isn’t asking you to build a different mechanism — it’s asking you to recognize when the mechanism you already have is the right choice, and when it isn’t.

Consequences

  • Forcing an open-ended investigation into a fixed pipeline -> either every file gets the same depth of attention whether it needs it or not, or someone has to guess in advance which files matter, before any evidence exists to base that guess on.
  • Letting a predictable review run as an open-ended investigation -> the model might decide one file doesn’t need a look, or spend disproportionate effort on one file while skimming another, since nothing guarantees uniform coverage.
  • A per-file pass that isn’t told to stay isolated -> it starts reasoning about files it hasn’t actually read yet, producing cross-file claims that are really just guesses dressed up as findings.
  • An integration pass fed summaries instead of full findings -> the one thing it exists to catch — inconsistency between files — is exactly what a summary is most likely to smooth over.

The two patterns aren’t competitors. A production review pipeline can run prompt chaining for the parts of the job that are genuinely predictable, and drop into dynamic decomposition the moment a step’s next move actually depends on what it just found.