Skip to content

    Forward Deployed Engineer

    Workflow or Agent: Choosing Control Flow in Code

    Decide between a single model call, a bounded workflow and an agent loop for a product catalog task, then write the workflow in plain Python first.

    By Published 8 min read

    Share

    What you'll learn: how to decide with four yes-or-no questions whether a task needs a single call, a bounded workflow or an agent loop, why the catalog problem gets a workflow, and how to write that workflow in plain Python with a test that proves what it does.

    At a glance

    Figure 1. Four questions pick the control flow

    flowchart TD accTitle: Choosing a control flow for a task accDescr: Four questions in order. If one model call with the right context is enough, use a single call. If the steps are known before the request arrives, use a workflow written in code. If a wrong step cannot be undone or held for approval, use a workflow with a human decision. If success cannot be measured and capped, build the evaluation first. Only when all four checks pass is an agent loop chosen. Start(["A task that needs a model"]) Q1{"Is one call with the right context enough?"} Q2{"Are the steps known before the request arrives?"} Q3{"Can a wrong step be undone, or held for approval?"} Q4{"Can you measure success and cap steps and time?"} A1["Single call"] A2["Workflow in code"] A3["Workflow with a human decision"] A4["Build the evaluation first"] A5["Agent loop with limits"] Start --> Q1 Q1 -->|"yes"| A1 Q1 -->|"no"| Q2 Q2 -->|"yes"| A2 Q2 -->|"no"| Q3 Q3 -->|"no"| A3 Q3 -->|"yes"| Q4 Q4 -->|"no"| A4 Q4 -->|"yes"| A5

    How to read this: start at the top, follow yes or no, and stop at the first rectangle you reach.

    • A workflow is code that fixes the order of the steps. An agent loop lets the model choose the next step.
    • The catalog task reaches "Workflow in code" at the second question.
    • Write the plain-code version first; a framework is a later decision.
    • The model still does the language work inside a workflow.

    Scenario

    Composite scenario, not a real company. Lumen Industrial Supply's sales engineers receive requests such as "monitor vibration on 40 pumps in a humid plant, budget around 30k, must work with our existing OPC UA setup." A manager asks for "an agent for product discovery." The tools exist: search the catalog, read one product record, check whether a product speaks a protocol. Every tool only reads. All numbers are illustrative.

    What you will be able to do

    • Answer the four questions for a task and name the control flow they select.
    • Write a fixed workflow for the catalog task in plain Python and test it against the capstone's planner.
    • Count model calls and estimate relative cost for a workflow and for a model-planned loop.
    • Explain in three sentences why you would not start with a framework.

    Prerequisites

    Core ideas

    Three shapes of control

    Shape Who chooses the next step How you test it How it fails
    Single call Nobody: one request, one answer One input and one expected output Wrong or missing facts
    Workflow Your code Ordinary unit tests on each step A step is wrong or missing
    Agent loop The model, every turn Many runs, graded by a set of cases Loops, wrong tool, wrong arguments

    Anthropic's Building effective agents uses the same split: workflows are "systems where LLMs and tools are orchestrated through predefined code paths", agents are systems where LLMs "dynamically direct their own processes and tool usage". Its advice is "finding the simplest solution possible, and only increasing complexity when needed."

    Predict first. For the catalog task, a planner model is asked "what is the next step?" on every turn. If you ran it ten times on the same request, how many different tool sequences would you expect to see in production?

    Answer

    Probably more than one, and that is the cost: a real model can reorder, repeat or skip steps. The capstone's MockProvider is a rule-based script, so it always produces one sequence, which makes it a workflow with extra steps.

    The catalog task is a workflow

    The sequence never changes: search, read the top candidates, check each required protocol, read a gateway if the protocol is missing. Only the number of candidates varies, and a loop handles that.

    Figure 2. The catalog workflow as states

    stateDiagram-v2 accTitle: States of the catalog workflow accDescr: The workflow starts by searching the catalog. An empty result ends it. Otherwise it reads the specs of the top candidates, checks each required protocol, reads a bridging gateway when a protocol is unsupported, and finishes. A step or time limit moves any running state to stopped. [*] --> Searching Searching --> Done: no products found Searching --> ReadingSpecs: candidates found ReadingSpecs --> CheckingProtocols CheckingProtocols --> ReadingGateway: protocol unsupported, gateway exists CheckingProtocols --> Done: all checked ReadingGateway --> Done Searching --> Stopped: limit reached ReadingSpecs --> Stopped: limit reached CheckingProtocols --> Stopped: limit reached Done --> [*] Stopped --> [*]

    How to read this: every arrow is a line of code you can unit test; the model chooses none of them.

    Decision Made by Why
    Turn the request into requirements Model Free text in, structured fields out
    Which tool comes next Code The order is known
    How many candidates to read Code A constant, easy to change
    Write the recommendation text Model Language out, checked by code afterwards

    Plain code before a framework

    The same article says frameworks "often create extra layers of abstraction that can obscure the underlying prompts" and suggests developers "start by using LLM APIs directly". A reader who has written the loop once can read any framework's loop.

    At the customer: a manager asks for "an agent". Ask what decision the model would make that code cannot. If the answer is "none", propose a workflow and revisit when a task needs a variable plan.

    Worked example

    Count the model calls for one request in the capstone with the mock provider. Token counts come from the capstone's rough estimator, and the prices are placeholder prices from app/config.py (1 and 5 euros per million input and output tokens). Fill in your provider's published prices before quoting a number.

    Assumptions: one request, the catalog request above, public caller.

    Call type Calls Input tokens Output tokens
    Extract requirements 1 77 32
    Plan the next step 7 2,162 130
    Compose the answer 1 553 134
    Total 9 2,792 296
    Version Arithmetic (millionths of a euro) Cost at placeholder prices
    Model-planned loop 2,792 x 1 + 296 x 5 = 4,272 0.004272 euro
    Order fixed in code 630 x 1 + 166 x 5 = 1,460 0.00146 euro

    With the order fixed in code, the seven planner calls disappear. The model-planned version costs 4,272 / 1,460, about 2.9 times more, for the same six tool calls. Planner calls also grow, from 132 to 462 input tokens, because each resends the earlier observations.

    Build it

    The decision tree from Figure 1 is a small function; answer its four traits from the task, not from taste.

    Verified: capstone/fde-product-discovery-copilot/lessons/M4-L1/m4_l1_workflow.py

    def choose_control_flow(traits: TaskTraits) -> ControlFlow:
        """The decision tree of Figure 1, top to bottom. The first question that fires decides."""
        if traits.one_call_is_enough:
            return "single_call"
        if traits.steps_known_in_advance:
            return "workflow"
        if not traits.wrong_step_can_be_undone_or_gated:
            return "workflow_with_human"
        if not traits.success_measurable_and_capped:
            return "build_evaluation_first"
        return "agent_loop"

    The workflow is a short function over the registry from the next lesson. registry.call never raises, so a failed step is recorded and the workflow carries on.

    Verified: capstone/fde-product-discovery-copilot/lessons/M4-L1/m4_l1_workflow.py

    def run_fixed_workflow(req: Requirements, registry: ToolRegistry, ctx: ToolContext) -> list[Step]:
        """Search, read the top candidates, check each protocol, read the first bridging gateway."""
        steps: list[Step] = []
    
        def call(tool: str, args: dict[str, Any]) -> dict[str, Any] | None:
            outcome = registry.call(tool, args, ctx)
            steps.append(Step(tool, args, outcome.ok))
            return outcome.data if outcome.ok else None
    
        found = call("search_catalog", search_args(req))
        candidates = [p["sku"] for p in found["products"][:3]] if found else []
        for sku in candidates:
            call("get_product_spec", {"sku": sku})
        bridges: list[str] = []
        for sku in candidates:
            for protocol in req.protocols:
                check = call("check_compatibility", {"sku": sku, "protocol": protocol})
                if check and not check["supported"]:
                    bridges += [g for g in check["via"][:1] if g not in bridges]
        for sku in bridges[:2]:
            call("get_product_spec", {"sku": sku})
        return steps

    The test compares this workflow with the capstone's model-planned runner. The mock follows a fixed script, so the sequences are equal; a real model might not match.

    Verified: capstone/fde-product-discovery-copilot/lessons/M4-L1/test_m4_l1_workflow.py

    def test_plain_code_makes_the_same_calls_as_the_scripted_planner(ctx: ToolContext) -> None:
        req = extract_requirements(REQUEST)
        fixed = run_fixed_workflow(req, default_registry(), ctx)
        planned = WorkflowRunner(MockProvider(), default_registry(), ctx).run(req)
        # The mock "model" follows a fixed script, so the two sequences match. A real model may differ.
        assert [(s.tool, s.args) for s in fixed] == [(o["tool"], o["args"]) for o in planned.observations]

    Running the workflow on the catalog request printed this (real output):

    1 search_catalog {'query': 'vibration pump humid OPC UA', 'limit': 5, 'category': 'sensors', 'max_unit_price_eur': 750.0, 'min_ip': 65} ok
    2 get_product_spec {'sku': 'LUM-VS-100'} ok
    3 get_product_spec {'sku': 'LUM-VS-200'} ok
    4 check_compatibility {'sku': 'LUM-VS-100', 'protocol': 'OPC UA'} ok
    5 check_compatibility {'sku': 'LUM-VS-200', 'protocol': 'OPC UA'} ok
    6 get_product_spec {'sku': 'LUM-GW-200'} ok

    The price cap of 750 is the 30,000 budget divided by 40 pumps, which assumes one sensor per pump. Tell the customer that assumption.

    Exercise

    Decision exercise. A second Lumen request arrives: "A customer emailed about a delayed order. Find the order, read the carrier status, and draft a reply. If the customer asks for a refund, a person decides." Options: (A) one model call with the email and the order pasted in, (B) a workflow, (C) an agent loop that can also issue refunds. Choose one and justify it with the four questions.

    Model answer

    Choose B. One call is not enough because the order and carrier data live in other systems. The steps are known: find the order, read the status, draft the reply. A refund is a decision for a person. Option C fails the third question: the loop cannot undo a wrong refund.

    Explain it back. Explain to a customer's engineering manager, in three sentences, why the product discovery assistant is a workflow and not an agent. A good explanation contains:

    • the property of the task that decides it (a fixed order of steps);
    • what the model still does (reads the request, writes the answer);
    • what would make you revisit the choice (steps that cannot be listed in advance).

    Task: add a TaskTraits for the delayed-order task to a copy of the test file, assert the result with choose_control_flow, and write one sentence naming the trait you were least sure about and what evidence would change your answer. Done when a colleague who has not read this lesson can read your test and see why the answer is a workflow.

    Hint 1: which trait is hardest?

    Look at steps_known_in_advance: can you list the steps before the email arrives, including when the order is not found?

    Hint 2: what does "gated" mean here?

    wrong_step_can_be_undone_or_gated is true when a person or a rule stands between a mistake and its effect. Reading an order is undoable; issuing a refund is not, unless approval stands in front of it.

    Common mistakes

    • Choosing "agent" because the word is in the project title, before answering the four questions.
    • Answering "steps known in advance" with "mostly". If the order can change, write down what changes it.
    • Letting the model decide the order and testing only the final text, so a wrong tool sequence goes unseen.
    • Reading the mock planner's matching output as proof that a real model will plan the same way.

    Knowledge check

    1. A task needs facts from three internal systems in a fixed order. Which control flow do the four questions select?

    A workflow in code. The facts are outside the model, so one call is not enough, and the steps are known in advance, so the second question ends the tree.

    2. Why did the model-planned version cost about 2.9 times more in the worked example?

    It made seven extra planner calls, each resending earlier observations, so inputs grew from 132 to 462 estimated tokens. The tool calls are identical.

    3. The test shows that plain code and the mock planner make the same six calls. What does that not prove?

    That a real model would plan the same way. The mock is a script, so equal sequences show only that the script is a fixed workflow. A real model can reorder, repeat or skip steps.

    4. When would you move this task from a workflow to an agent loop?

    When real tasks appear whose steps cannot be listed in advance (different tools depending on what earlier tools returned) and you can measure success and cap steps and time. Until then the extra cost buys nothing.

    5. What does the price cap of 750 euros in the first search call assume?

    One sensor per pump: the 30,000 budget divided by 40 pumps. If the customer needs several per pump, or gateways in the budget, the cap is wrong, so state the assumption.

    You can now

    • Answer four questions about a task and say which control flow they select.
    • Write a fixed tool workflow in plain Python and compare it with a planner.
    • Count planner calls and estimate relative cost with placeholder prices.
    • Explain why a framework is a later decision.

    Further reading

    Last verified: 2026-11-02

    Next

    Tool definitions and validated arguments

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments