← All posts

Agentic Design Patterns: A Practical Catalog With Tradeoffs

Quick answer: Agentic design patterns are reusable ways to structure how an AI agent thinks and acts. The core ones are tool use, ReAct, plan-and-execute, reflection, routing, and orchestrator-worker. Each trades off speed, cost, and reliability differently, and most real agents combine two or three rather than picking one.

Once you have built a couple of agents, you notice the same shapes recurring. Those shapes have names, and knowing them saves you from reinventing (or misusing) each one. Here is a working catalog: what each pattern is, when to reach for it, the tradeoff you accept, and short pseudocode.

The catalog at a glance

Pattern What it does Reach for it when Main cost
Tool use Model calls functions to act The task needs live data or actions Tool reliability
ReAct Interleave reasoning and acting in a loop Open-ended tasks with unknown steps Can wander
Plan-and-execute Plan all steps first, then run them Tasks with clear, known structure Rigid if reality shifts
Reflection Agent critiques and revises its own output Quality matters more than speed Extra model calls
Routing Classify input, dispatch to the right handler Varied inputs need different treatment Misroutes
Orchestrator-worker Lead delegates subtasks to workers Work splits into parallel chunks Coordination overhead

Tool use

The foundational pattern. The model is given a set of functions and decides when to call them and with what arguments. Everything agentic builds on this, because tools are how an agent does anything beyond generating text. For the underlying mechanics, see what is tool calling in LLMs.

tools = {"web_search": web_search, "web_fetch": web_fetch}

decision = model.decide(prompt, tools)
if decision.tool:
    result = tools[decision.tool](**decision.args)

Reach for it whenever the task needs something outside the model's frozen knowledge. The tradeoff is that your agent is only as good as its tools, so tool reliability becomes the ceiling on the whole system.

ReAct (reason + act)

ReAct interleaves thinking and doing in a loop: the model reasons about what to do, acts with a tool, observes the result, then reasons again. It does not plan the whole task up front; it figures out the next step from what it just learned.

context = [goal]
for _ in range(MAX_STEPS):
    thought = model.think(context)          # reason
    if thought.done:
        return thought.answer
    obs = tools[thought.action](thought.args)  # act
    context += [thought, obs]                  # observe, feed back

Reach for ReAct on open-ended tasks where you cannot know the steps in advance, like research or troubleshooting. The tradeoff is that without a plan to anchor it, the agent can wander, repeat itself, or lose the thread on long tasks. A step limit and good memory are non-negotiable.

Plan-and-execute

The mirror image of ReAct. The agent writes a full plan first, then executes each step, only re-planning if something breaks. Separating planning from execution keeps the agent on track and makes its behavior easier to audit.

plan = planner.make_plan(goal)     # list of steps, up front
results = []
for step in plan:
    results.append(execute(step, tools))
    if failed(results[-1]):
        plan = planner.replan(goal, results)  # only when needed

Reach for this when the task has a known structure, like a data pipeline or a checklist workflow. The tradeoff is rigidity: if reality diverges from the plan, a pure plan-and-execute agent can march confidently in the wrong direction until it re-plans. In practice, people blend it with ReAct, planning coarsely and reasoning within steps.

Reflection

The agent produces an answer, then critiques its own work and revises. A second pass (sometimes a separate critic prompt) catches errors, gaps, and sloppy reasoning the first pass missed.

draft = model.generate(task)
critique = model.critique(task, draft)   # what's wrong with this?
if critique.needs_revision:
    draft = model.revise(task, draft, critique)
return draft

Reach for reflection when quality matters more than latency: code generation, analysis, anything where a wrong answer is expensive. The tradeoff is obvious in the code, it is at least two model calls instead of one, and sometimes several. Do not bolt reflection onto tasks where the first answer is usually fine.

Routing

Routing classifies the incoming request and dispatches it to the handler best suited for it: a specific tool, a specialized sub-agent, or even a different model. It keeps each path simple instead of forcing one giant prompt to handle everything.

category = router.classify(request)   # "billing" | "research" | "code"
handler = handlers[category]
return handler.run(request)

Reach for routing when your inputs vary widely and each type deserves different treatment. A support system might route refund questions to a tool-using agent and policy questions to a simple lookup. The tradeoff is misclassification: a wrong route sends the request down the wrong path, so the router itself needs to be reliable and should have a sensible default.

Orchestrator-worker

A lead agent breaks a goal into subtasks and delegates each to a worker agent, then combines the results. When subtasks are independent, the workers run in parallel, which is where this pattern earns its keep.

subtasks = orchestrator.plan(goal)
results = run_parallel([worker.run(t) for t in subtasks])
return orchestrator.synthesize(goal, results)

Reach for it when work splits into independent chunks, most obviously multi-source research. The tradeoff is coordination overhead: routing, merging, and conflict resolution are new failure points, and more agents means more cost. This is a whole topic on its own, covered in what are multi-agent systems.

How web tools slot into every pattern

Notice that almost every pattern above had a tool call in it, and the tool was usually web search or web fetch. That is not a coincidence. The patterns organize how an agent thinks; the web tools supply what it thinks about. A ReAct loop with no live data just reasons in circles over stale training knowledge.

The practical problem is that the live web is hostile to naive fetching: rendering, bot blocks, rate limits, messy HTML. So the tool sitting inside these patterns needs to be robust. That is the role link.sc plays: one call returns any URL as clean markdown, and one call searches the web and returns structured results with real target URLs, each ready to fetch in full.

curl https://api.link.sc/v1/search \
  -H "x-api-key: lsc_..." \
  -H "Content-Type: application/json" \
  -d '{"q": "best agentic frameworks 2026", "engine": "google"}'

Wire that behind the web_search and web_fetch tools in any pattern above and the pattern actually has something to work with. For the practical wiring in a popular framework, see give LangChain agents a web fetch tool.

How to choose

You rarely pick one pattern. You compose them. A common, sturdy combination looks like this: route the request first, then run a ReAct loop for open-ended work or plan-and-execute for structured work, add reflection on high-stakes outputs, and only reach for orchestrator-worker when parallelism buys real speed.

Start with the simplest thing that works: plain tool use, then ReAct. Add reflection when quality demands it, routing when inputs diverge, and multiple agents only when a single agent genuinely cannot cope. Every pattern you add buys capability and costs you latency, money, and a new way to fail. Spend that budget deliberately.


Building agents with any of these patterns? link.sc is the reliable web tool that slots into all of them: clean fetch and full-content search from one API. Start free with 500 credits a month.