N
Noah Sheldon
BlogProjectsSkillsExperienceLinks
free ai audit
Home/Blog/Agentic Design/Beyond the Chatbot: The Rise of Agentic Orchestrators
Deep DiveAgentic DesignAI AgentsOrchestrationArchitecture

Beyond the Chatbot: The Rise of Agentic Orchestrators

The shift from conversational AI to autonomous execution. How agentic orchestrators work, why they matter, and what it takes to build them at enterprise scale.

Jul 19, 2026
8 min

Most people think AI is just a chatbot - you type a question, it gives you an answer. But the real power of AI is not in answering questions. It is in doing things. Instead of asking an AI to "write an email," what if you could give it a goal like "find all customers who haven't paid, send them a reminder, and update the spreadsheet"- and it just does it?

That is what agentic orchestrators do. This post explains what they are, how they work, and why they matter - in plain language, with no assumed knowledge. Every concept is followed by working code you can run yourself.

What you will learn: What an orchestrator is · How it differs from a chatbot · The 5-step execution loop · How to build one from scratch in Python · Error handling, cost, and safety · A complete working example


1. Chatbots vs. Orchestrators: The Core Difference

Let us start with something familiar. Open ChatGPT and type: "Write a summary of the latest AI research." The AI will write a summary based on what it already knows. It cannot search the internet (unless you give it that tool). It cannot access your files. It just predicts the next word based on its training data.

That is a chatbot. One prompt in, one response out. No tools. No memory between sessions. No ability to check if its answer is correct.

Diagram 1: How a chatbot works

You type a promptAI generates textOutputNo tools. No checking. One pass.

Now imagine something different. You give the AI a goal, not a prompt. You also give it tools: a search engine, a database, an email API. The AI does not just respond - it acts.

It makes a plan. It executes each step. If a step fails, it fixes itself and tries again. When the goal is complete, it tells you. That is an orchestrator.

Diagram 2: How an orchestrator works

Your Goal1. Make a Plan2. Execute Step3. Check4. NextRetrySuccess = keep going | Error = retry from Step 2

The chatbot gives you information. The orchestrator does work. That is the fundamental shift this entire post is about.


2. Building an Orchestrator: First Principles

Let us build a tiny orchestrator from scratch. We will start with nothing but Python and build up, step by step. You can copy this code and run it yourself.

Step 1: A Tool

An orchestrator needs tools - things it can use to interact with the world. Let us define what a tool looks like in code:

from dataclasses import dataclass
from typing import Callable

@dataclass
class Tool:
    name: str          # e.g. "search"
    description: str   # e.g. "Search the web for information"
    function: Callable # The actual code that runs

A tool has three parts: a name so the AI can refer to it, a description so the AI knows when to use it, and a function that does the actual work. Here is a concrete example:

def search_web(query: str) -> str:
    """Pretend to search the web."""
    return f"Results for: {query}"

search_tool = Tool(
    name="search",
    description="Search the web for information",
    function=search_web
)

A tool is just a named function with a description

namedescriptionfunction (code)The AI reads the description to decide which tool to use, then calls the function.

Step 2: A Goal

The user gives the orchestrator a goal. For now, it is just a string:

goal = "Find the latest news about AI in healthcare and save it to a file."

Step 3: The Plan

The orchestrator needs to turn the goal into a list of steps. In a real system, this is done by an LLM. For our example, we will hard-code the planning to keep it simple:

@dataclass
class Step:
    tool_name: str
    input: str

def make_plan(goal: str) -> list[Step]:
    """Break a goal into steps."""
    # In a real system, an LLM does this.
    # Here we hard-code the plan for demonstration.
    return [
        Step(tool_name="search", input="AI healthcare latest news"),
    ]

Step 4: Execute Each Step

The orchestrator loops through the plan, finds the right tool for each step, and runs it. This is the core loop:

def execute_step(step: Step, tools: list[Tool]) -> str:
    """Find the right tool and run it."""
    for tool in tools:
        if tool.name == step.tool_name:
            return tool.function(step.input)
    return f"Error: no tool named {step.tool_name}"

# The execution loop
results = []
for step in make_plan(goal):
    result = execute_step(step, [search_tool])
    results.append(result)
    print(f"Step done: {step.tool_name}")

This is the heart of every orchestrator. A simple loop: for each step, find the tool, run it, save the result. Everything else - planning, error handling, retrying - is built on top of this loop.


3. Adding Error Handling: The Self-Correction Loop

The most important feature of an orchestrator is that it does not crash when something goes wrong. Let us add error handling to our loop:

@dataclass
class StepResult:
    success: bool
    output: str
    error: str = ""

def execute_step_safe(step: Step, tools: list[Tool]) -> StepResult:
    """Run a step and return a result, even if it fails."""
    try:
        result = execute_step(step, tools)
        return StepResult(success=True, output=result)
    except Exception as e:
        return StepResult(success=False, output="", error=str(e))

# The self-correcting loop
max_retries = 3

for step in make_plan(goal):
    for attempt in range(max_retries):
        result = execute_step_safe(step, [search_tool])
        if result.success:
            print(f"✓ Step succeeded: {step.tool_name}")
            break
        else:
            print(f"✗ Attempt {attempt + 1} failed: {result.error}")
            if attempt < max_retries - 1:
                print("  Retrying...")
    else:
        print(f"  All attempts failed for: {step.tool_name}")

The self-correction loop visualised

Run StepSuccess?NextRetryKeep retrying up to 3 times, then give up

This pattern - try, check, retry if failed, move on if succeeded - is what separates a real orchestrator from a prototype. In production systems, you also add:

  • Exponential backoff: Wait 1 second, then 2, then 4 before retrying
  • Different fallback tools: If the search API fails, try a different one
  • Human escalation: If all retries fail, tell a human

4. The Complete Orchestrator

Here is everything put together - a complete, runnable orchestrator in under 50 lines:

from dataclasses import dataclass
from typing import Callable
import time

# ── 1. Define a tool ──
@dataclass
class Tool:
    name: str
    desc: str
    fn: Callable

def search(q: str) -> str:
    return f"Top result for: {q}"

tools = [Tool("search", "Search the web", search)]

# ── 2. Define a step ──
@dataclass
class Step:
    tool: str
    input: str

# ── 3. The orchestrator ──
def run(goal: str, tools: list[Tool], max_retries=3):
    # Plan (hardcoded for demo - LLM does this in production)
    steps = [Step("search", goal)]

    for step in steps:
        for attempt in range(max_retries):
            try:
                for t in tools:
                    if t.name == step.tool:
                        result = t.fn(step.input)
                        print(f"✓ {step.tool}: {result}")
                        break
                break  # success → move to next step
            except Exception as e:
                print(f"✗ Attempt {attempt+1}: {e}")
                time.sleep(1)  # wait before retry
        else:
            print(f"  All retries failed for: {step.tool}")

# ── 4. Run it ──
run("Find AI news", tools)
# Output:
# ✓ search: Top result for: Find AI news

This is a real, working orchestrator. It is tiny - but it contains the same fundamental pattern used by production systems at companies like a global ratings agency anda global investment bank. Everything else - LLM-based planning, parallel execution, state persistence, observability - is built on top of this basic loop.


5. What Production Orchestrators Add

The example above is minimal. Production orchestrators add several layers on top:

LLM-Based Planning

Instead of hard-coded steps, a real orchestrator uses an LLM to generate the plan dynamically. The LLM reads the goal, looks at the available tools, and decides what steps to take:

def plan_with_llm(goal: str, tools: list[Tool]) -> list[Step]:
    prompt = f"Goal: {goal}\n"
    prompt += f"Available tools: {[t.name for t in tools]}\n"
    prompt += "What steps should I take?"
    # In reality: call OpenAI / Anthropic API
    # The LLM returns a structured plan
    return parse_llm_response(llm.generate(prompt))

Parallel Execution

Steps that do not depend on each other can run at the same time, reducing total execution time:

from concurrent.futures import ThreadPoolExecutor

def run_parallel(steps: list[Step], tools: list[Tool]):
    with ThreadPoolExecutor(max_workers=5) as pool:
        results = pool.map(lambda s: execute(s, tools), steps)
    return list(results)

State Management

Production orchestrators save their state between steps. If the server crashes and restarts, the orchestrator picks up where it left off - it does not start over:

@dataclass
class OrchestratorState:
    goal: str
    completed_steps: list[Step]
    current_step: int
    results: dict

def save_state(state: OrchestratorState):
    with open("state.json", "w") as f:
        json.dump(state.__dict__, f)

def load_state() -> OrchestratorState:
    with open("state.json") as f:
        data = json.load(f)
    return OrchestratorState(**data)

Production orchestrator - the full stack

User Goal / API RequestLLM PlannerExecute Step 1Execute Step 2Execute Step 3State: Save progress after each stepComplete ✓Each layer adds resilience: planning, execution, state persistence

6. The Hard Parts: What Makes This Difficult

Building a simple orchestrator is easy (you just did it). Building one that works reliably in production is hard. Here are the three biggest challenges:

Challenge 1: Reliability

Every external API can fail. Networks timeout. Databases go down. Rate limits get hit. Your orchestrator must handle all of these gracefully:

def execute_with_retry(step, tools, max_retries=3):
    for attempt in range(max_retries):
        try:
            return execute_step(step, tools)
        except TimeoutError:
            wait = 2 ** attempt  # exponential backoff
            time.sleep(wait)
        except RateLimitError:
            time.sleep(60)  # wait a full minute
    raise Exception("All retries exhausted")

Challenge 2: Cost

Every LLM call costs money. If your orchestrator loops without a limit, costs explode. Always set a budget:

MAX_TOKENS = 100_000  # $0.01-0.15 depending on model

def track_cost(prompt: str, response: str):
    tokens = len(prompt.split()) + len(response.split())
    if tokens > MAX_TOKENS:
        raise Exception("Budget exceeded")

Challenge 3: Safety

An autonomous agent can make bad decisions. You need guardrails - rules the agent cannot override:

GUARDRAILS = [
    "Never delete data",
    "Never send money",
    "Never share user passwords",
    "Ask before sending external emails",
]

def check_guardrails(step: Step) -> bool:
    for rule in GUARDRAILS:
        if rule.lower() in step.input.lower():
            print(f"Blocked by guardrail: {rule}")
            return False
    return True

7. Summary: What to Remember

Plan → Execute → Check → Fix → Complete
  • A chatbot answers questions. An orchestrator does work.
  • The core loop is simple: for each step, run the tool, check the result, retry if needed, move on.
  • Error handling is the most important feature. Orchestrators must self-correct, not crash.
  • Production orchestrators add: LLM-based planning, parallel execution, state persistence, cost tracking, and safety guardrails.
  • The hard problems are not AI problems - they are reliability, cost, and safety problems.

If you are new to this topic, here is your next step: take the minimal orchestrator from section 4, add one more tool (like a file writer), and give it a multi-step goal. Watch it plan, execute, and deliver. Once that works, add error handling. Then add retries. You will have built what takes most teams months to productionise.

# Your challenge: extend this orchestrator
# 1. Add a "write_file" tool
# 2. Give it a goal: "search for AI news and save to file"
# 3. Handle the case where search fails
# 4. Handle the case where the file already exists

def write_file(filename: str, content: str) -> str:
    with open(filename, "w") as f:
        f.write(content)
    return f"Written to {filename}"

# Your orchestrator should:
# 1. Plan: search → write_file
# 2. Execute each step
# 3. Retry on failure
# 4. Report completion

Chapter

Agentic Design

Reading level

Deep Dive

About the author

NS

Noah Sheldon

Applied AI/ML @ Fitch

Keep reading

High Level Design

Building AI Agents: From Concept to Production

Practical guide to architecting and deploying autonomous AI agents that actually deliver value in a production environment.

Read · 14 min

High Level Design

Scaling Trading Systems at BNP Paribas: Lessons in Financial Technology

Deep dive into building resilient post-trade systems that handle millions in daily transactions.

Read · 15 min

High Level Design

System Design Fundamentals: Scaling Zero to Millions

Comprehensive guide to system design principles - from MVP architecture to distributed systems handling millions of requests.

Read · 15 min
NS

Noah Sheldon

Associate Director, Applied AI/ML @ Fitch Ratings

Get a free AI opportunity audit

never miss a deep dive

New essays land when they ship. No spam, unsubscribe anytime.