LISTEN TO THIS ARTICLE

Reactive agents respond to the current situation. Deliberative agents plan or reason about possible actions. Hybrid systems combine those approaches. Autonomy is a separate question: how much freedom does the system have to choose and execute its next step?

For an engineer choosing an architecture, those distinctions are more useful than a model leaderboard. Start with the work you need done, the evidence that would count as success and the consequences of a wrong action.

Which type of AI agent should you build?

A starting point for architecture selection, not a performance benchmark
PatternGood starting useMain limitationFirst check
ReactiveA known signal needs a predefined responseRules may miss an unfamiliar situationDoes the rule recognise invalid or missing input?
DeliberativeAn investigation needs several observations and revised decisionsMore opportunities for unnecessary steps and tool errorsCan it stop, verify the result and stay within budget?
HybridRoutine requests and ambiguous cases share one serviceThe routing boundary can send work down the wrong pathHow often does routing cause an avoidable failure?
Fixed workflowThe sequence is already known, such as validate, retrieve, formatUnexpected cases require an explicit branch or escalationDoes adding model-directed control improve task success?

These labels overlap. A fixed workflow can contain an LLM call; a reactive controller can sit inside a larger planning system. There is no universal latency or price attached to a category. Measure the actual model, tools, infrastructure and retries.

Reactive agents: a response without an explicit plan

A simple thermostat illustrates a reactive policy: compare a reading with a threshold and select an action. In software, a rule that routes a known incident code to its runbook has a similar shape. Its usefulness comes from a clear mapping between input and response.

Reactive does not always mean stateless. A controller might retain recent observations without searching over future plans. Nor does fast output prove that a system is reactive. A chatbot interface alone tells you little about the planning, retrieval or memory behind it.

Use this pattern when the response is well specified and testable. Include malformed inputs, missing readings and changed conditions in the tests. The escape route for an unknown case is part of the architecture, rather than something to add after deployment.

Deliberative agents: choose the next step from evidence

A deliberative system represents a goal and considers how to reach it. For an incident investigation, that might mean inspecting a failed request, checking a dependency and changing the working hypothesis when the evidence disagrees.

ReAct studies interleaving reasoning and actions in language models. Actions can obtain external information, which then informs subsequent decisions. Its reported results concern the tasks and baselines evaluated in the paper; they do not establish that every tool loop is reliable.

A reasoning model is one component, not the whole agent. Tool permissions, observations, stopping rules and result checks determine what the surrounding system can do. Extra inference or extra calls may help on a difficult task, but an architecture label cannot tell you whether the improvement justifies its cost.

Before increasing an agent’s reasoning budget, use the test-time compute evaluation guide to compare the answers returned with the extra work required.

Hybrid agents: make the boundary explicit

A practical hybrid might return a cached service status through a fixed path and send an unexplained outage to a bounded investigator. Both paths can share an output format, while only the investigator needs to choose successive observations.

Routing is itself a decision worth evaluating. A false escalation wastes time; a complex incident incorrectly treated as routine can produce a misleading answer. Record the selected route and its eventual outcome so that you can distinguish a weak router from a weak downstream model.

Anthropic distinguishes predefined workflows from systems where the model directs its own process, and recommends starting with the simpler solution. That distinction is useful when assessing whether a workflow actually needs more autonomy. Read the engineering guidance.

Choose the architecture by the work it must complete and the failures you can tolerate.

Autonomy is a permission boundary

A planner that proposes a database change for review has less execution authority than a rule that applies that change automatically. Neither the number of agents nor the length of a run determines that authority.

Write down which actions require approval, which resources are accessible and what ends the run. Keep read-only investigation separate from writes. A tool allowlist does not replace authentication or resource-level permissions, and a prompt asking the model to be careful does not enforce either.

Start by granting enough authority to complete a bounded task. Broader access needs evidence that the task requires it, plus a way to inspect and reverse the resulting changes where possible.

A runnable example: route first, then limit the loop

This Python example makes three decisions visible: routine status checks bypass planning, write requests go to review, and investigations stop after a fixed number of steps. It uses only the standard library and makes no model calls or external changes.

"""Teaching example: bounded routing, no models, network calls or side effects.
Run: python3 agent-routing-example.py [--test]
The step budget is not a wall-clock deadline or a production security boundary.
"""
import argparse
from dataclasses import dataclass
import unittest


@dataclass(frozen=True)
class Task:
    kind: str
    requires_write: bool = False


def run(task, planner, max_steps=3):
    if type(max_steps) is not int or max_steps < 1:
        raise ValueError("max_steps must be a positive integer")
    trace = []
    if task.requires_write:
        return "human_review", ["write request blocked before routing"]
    if task.kind == "service_status":
        return "fixed_workflow", ["return cached status; no planner call"]
    if task.kind != "investigation":
        return "human_review", ["unsupported task kind"]
    for step in range(1, max_steps + 1):
        action = planner(step)
        trace.append(action)
        if action == "done":
            return "completed", trace
        if action != "inspect_read_only":
            return "human_review", trace
    return "budget_exhausted", trace


class RoutingTests(unittest.TestCase):
    def test_fixed_path_never_calls_planner(self):
        def forbidden(_):
            self.fail("planner must not run")
        self.assertEqual(run(Task("service_status"), forbidden)[0], "fixed_workflow")

    def test_write_gate_precedes_fixed_path(self):
        def forbidden(_):
            self.fail("planner must not run")
        self.assertEqual(run(Task("service_status", True), forbidden)[0], "human_review")

    def test_investigation_can_finish(self):
        self.assertEqual(run(Task("investigation"), lambda n: "done" if n == 2 else "inspect_read_only"),
                         ("completed", ["inspect_read_only", "done"]))

    def test_nonterminating_planner_is_bounded(self):
        self.assertEqual(run(Task("investigation"), lambda _: "inspect_read_only", 2),
                         ("budget_exhausted", ["inspect_read_only"] * 2))

    def test_unknown_action_stops(self):
        self.assertEqual(run(Task("investigation"), lambda _: "delete_database")[0], "human_review")

    def test_unknown_task_stops(self):
        self.assertEqual(run(Task("other"), lambda _: "done")[0], "human_review")

    def test_invalid_budgets(self):
        for budget in (0, -1, True, 1.5):
            with self.subTest(budget=budget), self.assertRaises(ValueError):
                run(Task("investigation"), lambda _: "done", budget)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--test", action="store_true", help="run routing boundary tests")
    args = parser.parse_args()
    if args.test:
        unittest.main(argv=["agent-routing-example.py"])
    else:
        for task in (Task("service_status"), Task("investigation"), Task("investigation", True)):
            print(task.kind, task.requires_write, run(task, lambda n: "done" if n == 2 else "inspect_read_only"))

Save the code as agent-routing-example.py and run python3 agent-routing-example.py. Run python3 agent-routing-example.py --test for the seven boundary tests.

This is a teaching example, not a benchmark or production agent. The task labels are supplied by the caller, the planner returns scripted actions, and no inspection is executed. A real system needs trusted permission checks, timeouts on each call, spend limits, validated tool arguments and independent checks of completion. The step limit cannot interrupt a hung planner call.

What Symphony-Coord actually investigates

Symphony-Coord investigates adaptive task routing in multi-agent LLM systems. It screens candidates and uses a LinUCB selector informed by task and agent context, with feedback after execution. It does not demonstrate robots coordinating through musical rhythm.

The May 2026 revision explicitly separates assumptions for its immediate-feedback selector from deferred-update effects. Treat the reported simulation and LLM benchmark results as evidence about that studied setup. The example above is a deterministic router, not an implementation of Symphony-Coord.

Evaluate the architecture before expanding it

Build a small, representative task set before comparing designs. Include ordinary requests, incomplete inputs, tool failures and cases that should stop for review. Define success with observable outputs, such as a correct diagnosis supported by the retrieved evidence.

  1. Establish a baseline. Try a fixed workflow or single model call first.
  2. Compare completed tasks. Track correctness, unacceptable actions, escalation rate, elapsed time and total cost, including failed attempts.
  3. Inspect the boundary. Review cases where the router selected the wrong path or the planner continued after useful work ended.
  4. Retest after changes. Keep a held-out set so that a prompt improvement is not merely adaptation to familiar examples.
More autonomy is useful only when it improves outcomes within explicit permissions and budgets.

For deeper context, continue with what agentic AI means and why connected agents can still struggle to communicate.

Sources

Updated 6 September 2026. Corrected the earlier description of Symphony-Coord, separated autonomy from architecture, and removed unsupported universal cost, latency and model-capability claims. The decision framework and example are Swarm Signal teaching material; no new benchmark results are claimed.