How to Implement the Agent Loop from Scratch: Lessons from the AI Engineering from Scratch Curriculum
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) 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
The lesson implements the loop as a tight while-loop that orchestrates the five components. According to the source code in main.py, the AgentLoop follows this exact sequence:
- Initialize the buffer with the user's prompt
- Query the
ToyLLMto generate a response containing Thought, Action, and Observation tags - Parse the action using
parse_action()to extract the tool name and arguments - Execute the tool via
tool_registry.call()or break if the action isfinish - Format the result as an observation and append to the buffer
- 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:
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:
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:
# 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) 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.pyuses fewer than 200 lines of pure Python with zero external dependencies. - ReAct-style reasoning is achieved through a
ToyLLMthat 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →