Build a Working AI Agent Loop in Just 50 Lines of Python

This tutorial walks through a minimal 50‑line Python implementation of an AI Agent Loop, covering the core four‑step cycle, dual termination strategies, deterministic vs. autonomous designs, tool registration, and a complete runnable example.

Qborfy AI
Qborfy AI
Qborfy AI
Build a Working AI Agent Loop in Just 50 Lines of Python

Minimal Agent Loop Skeleton

The loop consists of four simple steps: (1) append the user’s input to a messages list, (2) send the list to an LLM, (3) inspect the LLM’s response for tool_calls, and (4) either execute the requested tool(s) and feed the result back into messages or return the final reply when no tool is needed. The outer for i in range(max_iterations) provides a hard iteration bound.

import json
from openai import OpenAI

client = OpenAI()

tools = {}

def register_tool(name, func, description, schema):
    """Register a tool with its function and JSON schema."""
    tools[name] = {"func": func, "schema": {"type": "function", "function": {"name": name, "description": description, "parameters": schema}}}

def run_agent(user_input, system_prompt="You are a helpful assistant.", max_iterations=10):
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_input},
    ]
    for i in range(max_iterations):
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=[t["schema"] for t in tools.values()] if tools else None,
        )
        msg = response.choices[0].message
        messages.append(msg)
        if not msg.tool_calls:
            return msg.content  # No tool needed – loop ends
        for tool_call in msg.tool_calls:
            name = tool_call.function.name
            args = json.loads(tool_call.function.arguments)
            if name not in tools:
                result = f"Error: unknown tool {name}"
            else:
                try:
                    result = tools[name]["func"](**args)
                except Exception as e:
                    result = f"Tool execution failed: {e}"
            messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(result)})
    return "Maximum iterations reached, loop terminated."

Termination Strategies

The max_iterations parameter acts as a safety net to prevent infinite loops when an LLM repeatedly calls the same tool or never signals completion. The second strategy relies on the LLM itself: if the response contains no tool_calls, the agent assumes the task is finished and returns the content. Combining both ensures graceful exits in normal cases and hard stops in pathological ones.

Source: Loop workflow – Google ADK [1]

Deterministic vs. Autonomous Loops

Google ADK’s LoopAgent follows a deterministic design: sub‑agents are pre‑ordered (e.g., CriticAgent → RefinerAgent) and the loop runs them in that fixed sequence until an exit_loop signal is emitted. This is ideal for repeatable pipelines such as "review → modify → review → modify…".

In contrast, an autonomous loop lets the LLM decide the next action based on the current context—e.g., reading a file, fixing a bug, or running a test—making the workflow dynamic and adaptable.

Adding Tools to the Loop

To make the agent useful, two example tools are registered: search_files (searches filenames by pattern) and read_file (reads file content with a 2000‑character limit). Each registration includes a clear description and a JSON schema that specifies required parameters, enabling the LLM to choose the correct tool.

import os

def search_files(pattern, directory="."):
    """Search for filenames containing the given pattern."""
    results = []
    for root, dirs, files in os.walk(directory):
        for f in files:
            if pattern.lower() in f.lower():
                results.append(os.path.join(root, f))
    return "
".join(results) if results else "No matching files"

def read_file(path):
    """Read file content, truncating to 2000 characters."""
    if not os.path.exists(path):
        return f"File not found: {path}"
    with open(path, "r") as f:
        return f.read()[:2000]

register_tool(
    "search_files",
    search_files,
    "Search for files whose names contain a keyword",
    {"type": "object", "properties": {"pattern": {"type": "string", "description": "Filename keyword"}, "directory": {"type": "string", "description": "Search directory, default current"}}, "required": ["pattern"]}
)

register_tool(
    "read_file",
    read_file,
    "Read the content of a specified file",
    {"type": "object", "properties": {"path": {"type": "string", "description": "File path"}}, "required": ["path"]}
)

Running the Loop

Example invocation:

result = run_agent(
    "Help me find a test file in the project and show its content",
    system_prompt="You are a code assistant with file‑search and read capabilities.",
    max_iterations=10,
)
print(result)

The agent will (1) call search_files with the keyword "test", (2) pick one of the returned files, (3) call read_file on it, and (4) summarize the result for the user.

Limitations and Next Steps

No parallel tool execution – tasks are processed sequentially.

No context window management – long conversations may exceed token limits.

Lacks observability – internal reasoning is only visible via prints.

No hook system – cannot inject custom logic before/after tool calls.

Result handling is simplistic; production SDKs expose richer ResultMessage statuses (success, error_max_turns, error_max_budget_usd, error_during_execution, error_max_structured_output_retries).

The next article will dive into the inner workings of a single message inside an Agent, revealing why agents sometimes call multiple tools or pause unexpectedly.

Source: Loop workflow – Google ADK [2]
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonLLMTool IntegrationAI AgentAgent LoopDeterministic vs AutonomousTermination Strategy
Qborfy AI
Written by

Qborfy AI

A knowledge base that logs daily experiences and learning journeys, sharing them with you to grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.