# How to Implement the Agent Loop from Scratch: Lessons from the AI Engineering from Scratch Curriculum

> Learn how to implement the agent loop from scratch using Python standard library components. Discover the Observe Think Act cycle in agent engineering with the ai-engineering-from-scratch curriculum.

- Repository: [Rohit Ghumare/ai-engineering-from-scratch](https://github.com/rohitg00/ai-engineering-from-scratch)
- Tags: how-to-guide
- Published: 2026-06-14

---

**The agent engineering lessons in the *AI Engineering from Scratch* repository implement a minimalist ReAct-style agent loop using only Python standard library components, demonstrating how a message buffer, tool registry, and turn budget work together to create the Observe → Think → Act cycle found in all modern agent frameworks.**

The *AI Engineering from Scratch* curriculum by Rohit Grewal provides a ground-up implementation of agent architectures in Phase 14, Lesson 01. This lesson teaches you how to implement the agent loop from scratch without relying on external frameworks like LangGraph or AutoGen. By building the core control flow in pure Python, you gain the mental model necessary to understand, extend, or replace any higher-level agent orchestration tool.

## Core Architecture of the Agent Loop

The implementation in [[`phases/14-agent-engineering/01-the-agent-loop/code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/code/main.py)](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/code/main.py) breaks the agent into five discrete components that form an invariant cycle: **Observe → Think → Act → Observe → Stop**.

### Message Buffer

The **Message Buffer** stores the full conversation transcript so the LLM always sees the complete history. Implemented as a simple Python list of dictionaries, each entry contains a `role` key (`user`, `assistant`, `tool`, or `observation`) and a `content` key. This buffer is passed to the LLM on every iteration, allowing the agent to maintain context across multiple turns.

### Tool Registry

The **Tool Registry** maps string names to callable functions and validates input arguments. The `ToolRegistry` class maintains a dictionary mapping `name → function`, where each tool declares a JSON schema for its parameters. When the agent calls a tool, the registry validates the arguments and returns either the result or a JSON-serializable error message if the call is malformed.

### Stop Conditions and Turn Budget

The **Stop Condition** terminates the loop when the model signals completion via a `finish` action, when an assistant turn contains no tool calls, or when a safety guard fires. To prevent infinite loops, the implementation uses a **Turn Budget** defined by the constant `MAX_TURNS` (default approximately 200). This counter decrements each iteration, raising a `RuntimeError` if exceeded.

### Observation Formatter

After each tool execution, the **Observation Formatter** converts raw results into strings the LLM can ingest. Whether the tool returns a value or raises an exception, the formatter stringifies the output and appends it to the message buffer as an observation entry, ensuring the next LLM call has access to the tool's impact.

## Step-by-Step Implementation in [`main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/main.py)

The lesson implements the loop as a tight while-loop that orchestrates the five components. According to the source code in [`main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/main.py), the `AgentLoop` follows this exact sequence:

1. Initialize the buffer with the user's prompt
2. Query the `ToyLLM` to generate a response containing Thought, Action, and Observation tags
3. Parse the action using `parse_action()` to extract the tool name and arguments
4. Execute the tool via `tool_registry.call()` or break if the action is `finish`
5. Format the result as an observation and append to the buffer
6. Increment the turn counter and repeat until termination

The code explicitly demonstrates that this cycle requires fewer than 200 lines of Python, with no external dependencies beyond the standard library.

## Practical Code Examples

The following snippets demonstrate how to run the reference implementation and extend it.

### Running the Reference Agent

Execute the agent loop directly from the repository root:

```bash
python3 phases/14-agent-engineering/01-the-agent-loop/code/main.py

```

This runs the full ReAct trace, showing how the agent reasons through thoughts, selects actions, and observes results until reaching a final answer.

### Adding a Custom Tool

Extend the `ToolRegistry` to add new capabilities without modifying the core loop:

```python
def reverse(text: str) -> str:
    return text[::-1]

tool_registry.register("reverse", reverse)

```

After registration, the agent can invoke `reverse` during reasoning. Test it by asking the agent *"What is the reverse of 'hello'?"* and observe the new tool being called in the trace.

### Integrating a Real LLM Provider

Replace the `ToyLLM` class with a production client while keeping the surrounding loop identical:

```python

# Example integration with OpenAI

def generate(buffer):
    response = openai.chat.completions.create(
        model="gpt-4",
        messages=buffer
    )
    return response.choices[0].message.content

# The loop remains unchanged

while turn < MAX_TURNS:
    response = generate(buffer)
    # ... parse and execute as before

```

The buffer format remains compatible with the ChatML-style messages used by OpenAI and Anthropic APIs.

## Why This Foundation Matters

Modern frameworks like **LangGraph**, **AutoGen v0.4**, **CrewAI**, and the **OpenAI Agents SDK** all implement variations of this same ReAct cycle. The differences lie in surrounding infrastructure: state checkpointing (LangGraph), actor-model messaging (AutoGen), role templating (CrewAI), or tracing spans (OpenAI Agents SDK). By mastering the bare-metal implementation in `ai-engineering-from-scratch`, you understand that these frameworks are thin layers atop the fundamental **Observe → Think → Act** pattern.

The lesson documentation in [[`phases/14-agent-engineering/01-the-agent-loop/docs/en.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/docs/en.md)](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/docs/en.md) further explains how 2025-2026 "native reasoning" capabilities will separate thought tokens into dedicated channels, but the underlying control flow remains identical to this scratch implementation.

## Summary

- The **agent loop** is implemented as a while-loop orchestrating five components: Message Buffer, Tool Registry, Stop Conditions, Turn Budget, and Observation Formatter.
- The implementation in [`phases/14-agent-engineering/01-the-agent-loop/code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/code/main.py) uses fewer than 200 lines of pure Python with zero external dependencies.
- **ReAct-style reasoning** is achieved through a `ToyLLM` that emits parseable Thought/Action/Observation tags, which the loop converts into tool calls and buffer updates.
- The **Turn Budget** (`MAX_TURNS`) prevents infinite loops by hard-capping iterations at approximately 200 cycles.
- This foundational pattern directly translates to understanding modern frameworks like LangGraph and AutoGen, which wrap the same cycle with additional infrastructure.

## Frequently Asked Questions

### What is the agent loop and why is it important?

The agent loop is the fundamental control flow that powers autonomous AI agents, consisting of an iterative cycle where the LLM observes the current state, reasons about the next step, and executes an action. This pattern, implemented in the *AI Engineering from Scratch* curriculum as the Observe → Think → Act cycle, is important because it forms the architectural foundation of every modern agent framework from LangGraph to AutoGen. Understanding this loop from scratch allows engineers to debug, extend, or replace higher-level abstractions with confidence.

### How does the lesson prevent infinite loops?

The lesson implements a **Turn Budget** using the constant `MAX_TURNS`, which defaults to approximately 200 iterations. Each pass through the while-loop decrements this counter, and exceeding the limit raises a `RuntimeError` that terminates execution. This safety guard ensures that even if the LLM enters a repetitive reasoning pattern or fails to emit a `finish` action, the program will halt predictably rather than consuming resources indefinitely.

### Can I replace the ToyLLM with a real LLM provider like OpenAI or Anthropic?

Yes, the `ToyLLM` class is designed as a drop-in replacement for production APIs. You can substitute it with an OpenAI or Anthropic client by ensuring the replacement accepts the message buffer (formatted as a list of dictionaries with `role` and `content` keys) and returns a string containing parseable action tags. The surrounding `AgentLoop` logic, including the message buffer management and tool registry calls, remains completely unchanged when swapping the LLM backend.

### How does this scratch implementation compare to frameworks like LangGraph or AutoGen?

The *AI Engineering from Scratch* implementation demonstrates the core ReAct pattern that powers all these frameworks, but without the surrounding infrastructure. LangGraph adds state checkpointing and graph-based routing, AutoGen introduces actor-model messaging between agents, and CrewAI layers role-based templating on top of the same cycle. By studying this minimal implementation, you learn that these frameworks are essentially orchestration layers around the invariant **Observe → Think → Act** loop found in [`phases/14-agent-engineering/01-the-agent-loop/code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/14-agent-engineering/01-the-agent-loop/code/main.py).