cd /news/artificial-intelligence/a-framework-free-walkthrough-of-the-… · home topics artificial-intelligence article
[ARTICLE · art-87364] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

A Framework-Free Walkthrough of the Control Loop Behind Every Tool-Calling AI Agent

A developer's guide demonstrates the core control loop behind every tool-calling AI agent, using a Gemini model and free-tier API key. The walkthrough explains the four-step cycle of sending context, evaluating responses, executing tool calls, and terminating, with code examples for tool definitions and dispatch. It emphasizes that LLMs do not execute code directly but output structured decisions for the application to run.

read3 min views1 publishedAug 5, 2026

Most "build an AI agent" tutorials skip straight to higher-level frameworks like LangGraph, CrewAI, or AutoGen. As a result, developers call .invoke()

without seeing the mechanics underneath. Beneath every agent framework lies a single, surprisingly straightforward control loop.

To make this walkthrough accessible, the examples in this guide use a Gemini model. Because anyone with a Google Account can obtain a free-tier API key, you can run the code and follow along directly without running into paid platform barriers or complex setups.

Every AI agent, regardless of framework, executes the same four-step cycle:

Send Context: Pass the conversation history and tool definitions to the LLM.

Evaluate Response: The LLM returns either a final text response or a tool execution request.

Execute and Append: If the model requests a tool call, run the corresponding local function, append the output to the conversation history, and return to Step 1.

Terminate: Repeat the cycle until the LLM produces a final answer or reaches a designated safety ceiling.

LLMs do not execute code, query databases, or call external APIs directly. Instead, the model outputs structured decision data specifying which tool to execute and which parameters to pass. Your application receives this instruction, runs the corresponding local function, and returns the output to the model.

An LLM evaluates available functions solely through tool definitions—API specifications provided in the request payload. High-quality descriptions act as operational instructions for the model, directly determining how accurately it selects tools:

tool_definition = {
    "name": "check_service_health",
    "description": (
        "Checks current operational status and latency for a given service. "
        "Use this first when investigating incident alerts."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "service_name": {
                "type": "string",
                "description": "The target service identifier (e.g., 'auth-api')"
            }
        },
        "required": ["service_name"]
    }
}

When the model returns a tool call request, your application must dispatch that request to the target function. For simple agents with few tools, a standard match/case

block or lookup table handles dispatch cleanly:

def dispatch_tool_call(tool_name: str, arguments: dict):
    match tool_name:
        case "check_service_health":
            return check_service_health(**arguments)
        case "check_recent_deployments":
            return check_recent_deployments(**arguments)
        case "search_runbook":
            return search_runbook(**arguments)
        case _:
            raise ValueError(f"Unknown tool requested: {tool_name}")

Because LLMs are stateless, your runtime loop must manage conversation state across every turn. Each tool-calling cycle requires appending two distinct messages to the chat history:

messages.append(response.message)

messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": str(tool_result)
})

Assemble the step-by-step cycle inside a single loop function, checking for final text responses on each iteration. Always define an explicit iteration ceiling (max_iterations = 10

) to protect against infinite loops if a model fails to converge:

def run_agent_loop(user_prompt: str, max_iterations: int = 10) -> str:
    messages = [{"role": "user", "content": user_prompt}]

    for _ in range(max_iterations):
        response = llm.generate(messages=messages, tools=tools)

        if not response.tool_calls:
            return response.text

        for tool_call in response.tool_calls:
            result = dispatch_tool_call(tool_call.name, tool_call.args)
            messages.append(response.message)
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": str(result)
            })

    raise RuntimeError("Agent exceeded maximum iteration limit.")

Understanding this core loop removes the ambiguity surrounding agent frameworks. Higher-level orchestration layers build on this foundation to manage edge cases, state persistence, and complex routing, but the core mechanism remains simple: structured model decisions guiding local code execution.

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @gemini 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-framework-free-wal…] indexed:0 read:3min 2026-08-05 ·