# Agent Executors

The runtime that drives the agent loop — dispatching tool calls, managing state, enforcing limits and handling errors.

**Rests on:** Agent Loop

**Leads to:** Agentic Test Authoring, Escalation Workflows

*AI Agents · AI Agents*


## Why learn this

Understanding the executor unlocks the rest of the agent stack. Before this page, you know what an agent is — a model that plans, calls tools, observes results, and iterates. What you don't yet know is what actually runs that loop, what enforces the rules that stop it from running forever, and where the reliability of real agent systems is decided. This concept sits directly beneath **Build an AI Agent (From Scratch)** — the executor is the runtime that framework's message loop plugs into. It also sets you up for **Agent Memory**, because the executor manages the state that memory reads and writes. And it earns you a seat at the table for **Agent Communication Protocols** and **Agent Cards**, since every agent, however it talks to others, needs an executor to stay alive. Read this and you'll never again look at an agent framework as magic — you'll see it as a disciplined loop with a safety harness.


## The intuition

An agent is a loop: decide, act, observe, repeat. A large language model makes the decisions — it reads the situation and proposes an action, like 'search the web for flight prices' or 'send this email'. But something has to actually carry that action out. Something has to call the search engine, take the result it returns, and hand it back to the model so it can decide what to do next. That something is the executor. Think of the executor as the stage manager of a play. The actor (the model) says 'I will now call the search tool'. The stage manager (the executor) actually goes offstage, makes the call, waits, and brings the result back to the actor. The stage manager also keeps the whole production from collapsing: if the phone line is down, it tries again. If the actor calls 'search' a hundred times in a row, the stage manager says 'that's enough, we're closing the show'. If something unexpected happens — the search tool returns nonsense — the stage manager catches the error and tells the actor 'try something else'. The model is the imagination; the executor is the hands. Without the executor, the model would just talk about doing things, forever, like a playwright who never leaves the study. The executor is what makes a plan real.


## An analogy

Imagine you're a manager at a busy office. One of your employees, let's call her Priya, is brilliant at figuring out what needs to be done but she's terrible at details. She'll say 'We need to call three suppliers and order the parts.' She doesn't care about the phone number's country code, or what to do if the line is busy, or whether it's okay to call after 5pm. Your job is to actually do it. You have a checklist: call supplier A, note what they said. If the line is busy, wait two minutes and call again. If it's still busy after three tries, mark it as failed and move on. If the supplier says 'we don't carry that part', you write that down and tell Priya what happened, so she can adjust her plan. You also have a rule: no more than ten phone calls total, because the day has to end sometime. This is exactly what an executor does. Priya is the model, the phone is the tool, your checklist is the executor's logic. The analogy starts to break down when you notice that the executor is not a person with judgment. It does not understand the meaning of the supplier's reply. It only knows 'the tool returned a string of text' and 'I must pass this string to the model'. Where a human manager might read between the lines — 'that supplier sounds annoyed, maybe try someone else' — the executor is gloriously literal. It follows rules exactly, every time, with no boredom and no improvisation. That's a feature: predictability is what makes the executor testable. The model can be creative; the executor must be mechanical.


## Definition

An agent executor is the runtime layer that drives the agent loop — it takes the model's decision, runs the chosen tool call, catches and handles errors, tracks how many iterations have happened, enforces time limits and retry rules, and passes the result back so the loop can continue until a termination condition is met.


## How it connects

You have already mastered the large language model itself — how it generates text, how attention works, how it produces one token at a time. The executor builds on that in a crucial way: the model's output is text, but the agent needs to turn that text into an action. The bridge is tool calling, and the executor is what performs that bridge. The learner's library notes on the **Agent Loop** point to the same core — 'the cycle at the heart of every agent: receive context, decide an action, execute it, observe the result, repeat until done' — and the executor is precisely the 'execute' and 'observe' parts of that loop. It also connects to **Agent Memory**, because the executor holds the running state of the conversation and decides what to keep. And it connects to **Build an AI Agent (From Scratch)**: that course walks you through building a message loop and tool calls from first principles, and this page gives you the underlying executor that makes that loop safe and reliable.


## The mathematics

This concept does not rest on a particular branch of mathematics like calculus or linear algebra. It rests on the mathematics of counting and comparison — arithmetic. The executor's job is to keep track of numbers: how many times the loop has run, how much time has passed, how many times a tool call has been retried. It makes decisions by comparing those numbers to limits: if the iteration count is greater than the maximum, stop. If the elapsed time exceeds the timeout, stop. These are simple operations, but they are the entire substance of the executor. There is no derivative, no dot product, no probability distribution. The mathematics here is the mathematics of a stopwatch and a tally counter. That is not a simplification — it is the point. The executor is deliberately simple so that it can be relied upon. The model handles complexity; the executor handles counting. So the operation you need to know is how to compare two numbers and decide which is bigger. That's it. You do it every day when you check if you have enough money to buy something, or if you're late for a meeting. The executor does the same thing, but it does it millions of times without getting tired.

The executor's core logic is:

if iteration_count >= max_iterations: stop
if elapsed_time >= timeout: stop
if retry_count < max_retries and error_occurred: retry
else: propagate error

Each of these is a simple comparison: `a >= b` means 'a is greater than or equal to b'.


## A worked example

```
Suppose your agent has a maximum of 5 tool calls allowed. It has already made 3 calls. The timeout for a single tool call is 30 seconds, and the current call has been running for 25 seconds.

Step 1: 3 (calls so far) < 5 (max calls) — continue   # The executor checks whether the agent can make another call. Since 3 is less than 5, it proceeds.
Step 2: 25 seconds (elapsed) < 30 seconds (timeout) — still waiting   # The executor checks the time. 25 is less than 30, so the call is allowed to keep running.
Step 3: After 30 seconds, suppose the tool hasn't returned. The executor stops the call.   # The timeout has been reached. The executor cancels the call, records a timeout error, and decides what to do next (retry or fail).

Result: The executor's decisions are all comparisons of this kind. In this example, the agent gets to make its 4th and 5th calls, but the 5th call times out, so the loop ends with an error state.
```


## From scratch, in Python

```python
# Plain Python executor, no frameworks.
# We'll build a tiny agent that can call two 'tools': one that returns a message,
# and one that sometimes fails.

import time

# --- Building block: the while loop ---
# A while loop repeats a block of code as long as a condition is true.
# Example: count = 0
# while count < 3:
#     print('count is', count)
#     count = count + 1
# This prints 0, 1, 2.

# --- Building block: the list ---
# A list holds items in order. You can add to it and change it.
# Example: fruits = ['apple', 'banana']
# fruits.append('mango')  # now fruits is ['apple', 'banana', 'mango']
# fruits[0] is 'apple' — the index starts at 0.

# --- Building block: the dictionary ---
# A dictionary looks things up by a name (a key) rather than by position.
# Example: person = {'name': 'Priya', 'age': 30}
# person['name'] is 'Priya'

# Our two tools. Each is a function that takes a string and returns a string.
def search_web(query):
    # Pretend this does a real web search.
    return f"Here are results for '{query}': ..."

def send_email(address, message):
    # Sometimes this fails, to test retries.
    if len(address) < 5:
        raise RuntimeError('Invalid email address')
    return f"Sent email to {address}"

# The executor function.
def run_agent(initial_message, max_rounds=5, timeout_per_call=2, max_retries=2):
    """Drives the agent loop: decide, act, observe, repeat."""
    messages = [initial_message]  # a list of conversation messages
    round_count = 0

    while round_count < max_rounds:
        round_count += 1
        print(f"--- Round {round_count} ---")

        # (In a real agent, this is where the model picks an action.
        #  Here we hardcode a simple decision rule.)
        last_message = messages[-1]
        if "search" in last_message:
            action = "search"
            argument = last_message.replace("search for ", "")
        elif "email" in last_message:
            action = "email"
            argument = last_message.replace("email ", "")
        else:
            print("No action — ending.")
            break

        print(f"Action: {action}, argument: {argument}")

        # --- Enforce timeout: use time.time() to measure elapsed seconds ---
        start = time.time()
        result = None
        error = None

        while True:
            try:
                if action == "search":
                    result = search_web(argument)
                elif action == "email":
                    # Split on the first comma to get address and message
                    parts = argument.split(",")
                    address = parts[0].strip()
                    message = parts[1].strip()
                    result = send_email(address, message)
                break  # success, exit the retry loop
            except Exception as e:
                # Retry logic: if we haven't exceeded max_retries, try again.
                if start + timeout_per_call < time.time():
                    error = "timeout"
                    print("Timeout reached.")
                    break
                if error is None:  # first error
                    error = str(e)
                    print(f"Error: {error}. Retrying...")
                    time.sleep(0.1)
                else:
                    print("Max retries exceeded.")
                    break

        if error:
            print(f"Call failed after attempts: {error}")
            break

        # Observation: append the result to messages, so the next round sees it.
        messages.append(result)
        print(f"Observation: {result}\n")

    print(f"\nFinished after {round_count} rounds.")
    return messages

# Run it
run_agent("search for best chai in Delhi")
```


## The same thing, with a framework

In practice, you'd use a framework like LangChain or LlamaIndex. Both wrap the same loop. For example, LangChain's `AgentExecutor` class does exactly this. Under the hood, it calls the model, parses the tool call, executes it, catches errors, updates the conversation history, and checks against `max_iterations` and `timeout`. You write:

```python
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI()
tools = [search_web, send_email]  # your actual tools
agent = create_tool_calling_agent(llm, tools)
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=5, verbose=True)
executor.invoke({"input": "search for best chai in Delhi"})
```

This is the same logic you wrote by hand: it loops, checks `max_iterations`, catches errors, and manages state. The framework is just a polished version of your plain-Python `run_agent`.


## What the code does

The code above is a minimal executor. Let's go through it line by line, focusing on the key parts.

First, two functions, `search_web` and `send_email`, stand in for tools. They take a string and return a string. `send_email` deliberately raises an error if the address is too short, so we can see retries happen.

Then comes `run_agent`. It takes an initial message, and three limits: `max_rounds` (5 by default), `timeout_per_call` (2 seconds), and `max_retries` (2). It keeps a list called `messages` — this is the conversation state. `round_count` starts at 0.

The `while round_count < max_rounds:` loop is the agent loop. Each iteration is one round. The executor increments the counter, then looks at the last message to decide an action. In a real agent, the model would do this — but here it's a simple rule, so you can see the executor's role clearly.

Inside the loop, the executor does the tool call. It records the start time with `time.time()`. Then it enters an inner `while True:` loop that is the retry logic. It tries the tool call inside a `try` block. If it succeeds, `break` exits the retry loop. If it fails, the `except` block checks if the total time has exceeded the timeout. If so, it records a 'timeout' error and breaks. Otherwise, it records the error message and retries (by going back to the top of the inner loop). Notice that we never actually count retries here — the example makes the point that a real executor would. If the error is not a timeout and it's the first error, we sleep briefly and try again. If a second error occurs, `error` is already set, so it prints 'Max retries exceeded' and breaks.

After the tool call, if `error` is set, the whole loop stops. If not, the result is appended to `messages` — that's the observation step. The loop continues until `round_count` hits `max_rounds`.

This tiny executor demonstrates the four pillars: dispatch (calling the right tool), state (the `messages` list), limits (round count, timeout, retries), and error handling (the try/except).


## Common misconceptions

**The model is the agent** — The large language model is the agent, so the executor is just an optional add-on.

The model only produces text. It has no ability to call a search engine, read a file, or send an email. Without an executor, the plan never becomes an action. In fact: The agent is the whole system: model plus executor plus tools. The model decides, the executor acts.

**The executor understands the results** — When the tool returns, the executor reads it and knows what it means.

The executor treats the result as an opaque string. It doesn't interpret it; it just passes it back to the model. Interpretation is the model's job. In fact: The executor shuttles data. It is deliberately dumb about semantics.

**More retries always help** — If a tool call fails, I should just retry it as many times as possible.

Retrying wastes time and money, and can compound errors. If a tool is down, retrying a hundred times won't fix it. In fact: Retries are for transient failures (like a network blip). They have a sensible limit, often 2-3, and the executor enforces that limit.

**The executor is unique to each framework** — LangChain's executor, LlamaIndex's executor, and my own code are all completely different things.

They all implement the same core loop: decide, act, observe, repeat, with limits and error handling. The differences are in polish, not essence. In fact: Beneath every framework lie the same primitives. Once you understand the executor, you understand them all.

**Errors only happen if I write bad code** — If my agent code is correct, nothing will go wrong at runtime.

Tools are external: networks drop, APIs change, servers time out. Errors are inevitable. The executor's error handling is what keeps the agent from crashing. In fact: Reliable agents are built on the assumption that things will fail. The executor is the safety net.


## In short

The agent executor is the runtime that turns a model's decisions into real actions. It drives the loop, dispatching tool calls, managing conversation state, enforcing iteration caps, timeouts, and retries, and handling errors gracefully. It is the difference between a model that talks and an agent that does. Remember: the model is the brain, the executor is the hands and the stopwatch.


---

Read this on the web: https://basavapurushottam.com/learn-ai/agent-executors
