Skip to main content
Module 1: Agent Foundations

Your first agent: a tool-using loop

Assemble the pieces into a minimal working agent — and see why it needs guardrails.

Now assemble the atom, the loop, and tools into a working agent. The pseudocode below is deliberately framework-free so you see the whole machine:

def run_agent(goal, tools, max_steps=8):
    messages = [{"role": "user", "content": goal}]
    for step in range(max_steps):                      # a stop condition!
        response = model.call(messages, tools=tools)   # reason
        if response.tool_call:
            result = execute(response.tool_call)       # act
            messages.append(response)                  # record what it did
            messages.append(tool_result(result))       # observe
        else:
            return response.text                        # done
    return "Stopped: hit step limit."

That's a real agent. The model reasons with tools available; if it requests one, you execute it and feed the result back; if it answers without a tool call, you're done. Everything else in this course refines this skeleton.

Look at what's already here, because two lines are quietly doing reliability work. The max_steps cap is a stop condition — without it, a confused model can loop forever. And feeding the tool result back as an observation is the ground truth that keeps the agent connected to reality. Even this toy is designed for failure, and that's the right instinct.

In practice you'll likely use a framework — OpenAI Agents SDK for a clean built-in loop, LangGraph for stateful orchestration with persistence and human-in-the-loop, CrewAI for fast multi-agent prototypes. But heed Anthropic's advice: start with the raw model API, understand what's under the hood, and reduce abstraction as you go to production. Frameworks hide the prompts and the loop — convenient until you're debugging, when that hidden layer is exactly what you need to see. Build the loop by hand once; then a framework is a labor-saver, not a black box.

Run your skeleton on a simple multi-step task and watch it work — the model calling tools, results flowing back, an answer emerging. Then watch it fail: give it a task it can't complete and see it burn all your steps, or a vague tool and see it misfire. That failure is the honest starting point for Modules 2 and 3, where we make this reliable enough to trust.

Try it

Implement the loop above with one or two real tools (even a calculator and a web-search stub). Run it on a two-step task. Then remove the `max_steps` guard, give it an impossible goal, and observe why that guard exists.

Stay in the loop

Enjoying the free lessons? Get an email when we publish new courses and updates — no spam, unsubscribe anytime.

Discussion (0)

Ask a question or share what worked for you. Comments are reviewed before they appear.

Log in to join the discussion and ask questions about this lesson.

No comments yet. Be the first to start the discussion!