Skip to content
MINH VO A working notebook
by an engineer in Vietnam
Foundation8 min read

Use an agent only when the task needs dynamic decisions

Compare a fixed invoice workflow with a repository investigation to decide where dynamic agent behavior earns its execution cost and complexity.

Open notebook with connected abstract shapes and olive thread linking paper swatches

If you are designing an LLM application, use an agent when the next useful action depends on evidence the system has not seen yet. Use a fixed workflow when the required steps and their transitions are already known. The distinction is about how the application chooses actions, not whether it contains a language model.

For example, a model can extract fields from an invoice inside a fixed sequence: parse the file, request structured fields, validate totals, and send uncertain cases to a review queue. A repository investigation is less predictable. A failing test may lead to a parser, a configuration file, or a dependency boundary, and each finding changes what should be inspected next.

Write the fixed workflow first

Consider a hypothetical expense application that accepts an invoice image. The product requirement is to produce a draft containing supplier, currency, line items, and total. It must never submit payment. A finite workflow can handle this with explicit states:

received -> extracted -> validated -> draft_created
                 |           |
                 v           v
           needs_review  needs_review

The extraction step may use an LLM, but the model does not decide to open a browser, contact the supplier, or pay the invoice. The application selects the next state using validation results. This narrow design is easier to test because every allowed transition is visible.

Suppose the extracted line items total 90 units of currency while the invoice total says 100. The workflow can send the case to review with a discrepancy label. An autonomous investigation might search for a missing tax line, but that action is useful only if the input contains more evidence and the product permits using it. Extra agency cannot recover a fact that does not exist.

Fixed invoice processing is compared with a repository investigation whose next action depends on new evidenceView full-size image ↗

The figure compares a predetermined path with an evidence-dependent loop. Both designs need stopping conditions. A fixed workflow can also be badly designed if it retries a failed extraction forever or silently creates an incomplete draft.

Identify the decision that requires an agent

Now consider a repository task: a test says an API accepts an invalid date. The investigator first reads the test and the endpoint. If validation happens in a shared schema, the next action is to inspect that schema and its callers. If the endpoint bypasses validation, the next action changes. The useful sequence cannot be completely specified without examining the code.

The ReAct paper studies systems that interleave reasoning and actions while interacting with external environments. Its results belong to the tasks and models studied in that paper. For an engineering application, the practical idea is to let observations influence subsequent actions while retaining an explicit execution boundary.

Write down the decisions you want to delegate. “Investigate why the date is accepted” is clearer than “be an autonomous engineer.” Allowed actions might include reading files, running a named test command, and editing the relevant validator. Publishing a release is a separate capability with a different impact. The agent’s task description and tool permissions should agree about that scope.

Compare the whole cost of completion

An agent can make several model requests and tool calls before returning an answer. Count the entire trace when comparing it with a fixed workflow: generation cost, tool latency, failed attempts, and the review needed to accept the result. A short final answer can hide a long investigation.

For a pilot, prepare ten representative tasks with acceptance criteria decided in advance. Include straightforward cases that a fixed path handles well and cases where the next action genuinely depends on findings. Run both designs under a stated time or action budget. Record whether the result met the requirement and whether the system stopped honestly when it could not finish.

A useful table might have one row per task and columns for accepted result, invalid side effect, tool calls, elapsed time, and reviewer notes. The figures would be measurements from your experiment, not estimates inferred from the architecture. The evaluation best-practices guide, retrieved on September 20, 2026, recommends evaluating the actual task rather than relying only on general model benchmarks.

Read a paired comparison without inventing a benchmark

Anthropic’s Building effective agents, published December 19, 2024, distinguishes predetermined workflow paths from model-directed tool use and describes the latency and cost tradeoff. Its current page flags that the tooling landscape has changed since publication. Here we use the architectural distinction, not its historical product descriptions or benchmark claims.

Before running a real pilot, test the report you plan to use. The following four rows are fabricated outcomes for an accounting exercise. They are not measurements of any model, framework or repository. Each row represents one task attempted by both designs under the same hypothetical permission policy. A review outcome can be appropriate even though it is not an accepted completed draft or diagnosis.

FixtureFixed acceptedAgent acceptedFixed callsAgent calls
Complete invoiceYesYes25
Missing tax evidenceNoNo27
Date validator bypassNoYes36
Missing reproduction inputNoNo12

This executable Python report keeps the task pairs together. Its input contract requires actual Boolean acceptance labels, nonnegative integer call counts, and a nonempty list or tuple of rows. Real records would additionally carry task IDs, attempts, elapsed time, review decisions and forbidden-effect checks.

def compare(rows):
    if type(rows) not in (list, tuple) or not rows:
        raise ValueError("need paired tasks")
    fixed_ok = agent_ok = fixed_calls = agent_calls = 0
    wins = losses = 0
    for fixed, agent, fc, ac in rows:
        if type(fixed) is not bool or type(agent) is not bool:
            raise ValueError("acceptance must be Boolean")
        if any(type(n) is not int or n < 0 for n in (fc, ac)):
            raise ValueError("calls must be nonnegative integers")
        fixed_ok += fixed
        agent_ok += agent
        fixed_calls += fc
        agent_calls += ac
        wins += agent and not fixed
        losses += fixed and not agent
    return {"accepted": (fixed_ok, agent_ok),
            "calls": (fixed_calls, agent_calls),
            "agent_only": wins, "fixed_only": losses}

rows = [(True, True, 2, 5), (False, False, 2, 7),
        (False, True, 3, 6), (False, False, 1, 2)]
print(compare(rows))

The output contains accepted counts (1, 2), calls (8, 20), one agent-only success and no fixed-only success. The missing-tax task consumes five extra calls without obtaining enough evidence. The date task contributes the one additional accepted result. A single aggregate would hide which decision used the additional work usefully.

Four fabricated paired tasks show where extra agent calls accompany an additional accepted result and where they do notView full-size image ↗

Change the date task’s agent label to false and rerun: accepted counts become equal while the call difference remains twelve. Then change the fixed label to true instead: the same extra work produces no agent-only win. These mutations check the comparison logic. They cannot establish a general return on investment, and calls are not interchangeable units of money or latency.

For a real pilot, compare systems on the same task instances, separate invoice tasks from repository tasks, and preserve individual attempts when generation is stochastic. A four-task mix does not support a reliable population estimate. Review accepted answers without assuming the more elaborate architecture is better, record honest review or incomplete outcomes separately, and inspect any forbidden side effect independently of answer quality. The final choice depends on which task stratum needs dynamic decisions and whether its observed benefit justifies the complete execution and review cost.

Keep the exit states explicit

An agent should be able to finish with a verified result, request a necessary clarification, or return an evidence-backed partial result after exhausting its budget. Those states need different user messages. A budget exhaustion should not look like success because the model has reached the point where it must produce final text.

For the date-validation investigation, a useful incomplete result might identify the failing test, the validator that appears responsible, and the next check. It should not claim that a patch passed when the test process never completed. Preserve the command output or an artifact reference so a later run can continue from evidence.

State also determines whether repeated actions are safe. Reading the same file twice wastes time; creating the same invoice draft twice can create a duplicate record. Durable operation identifiers and execution checks belong in the application even when the agent is good at remembering its previous steps.

You can combine the designs. A fixed outer workflow can authorize a bounded investigation, receive its findings, and pass them to a deterministic validation stage. That arrangement lets the agent choose useful inspection steps while keeping completion and side effects under explicit application rules. Begin with the smallest dynamic decision your task requires, then expand only when the evaluation shows a concrete benefit.

Sources & further reading

  1. ReAct: Synergizing Reasoning and Acting in Language Models
  2. OpenAI evaluation best practices
  3. Anthropic: Building effective agents, 19 December 2024
← Back to the journal
All notes

Illustration

100%