The first agent I built in Python ran for 40 minutes and burned $3.10 doing nothing. It read a CSV, decided it needed more context, re-read the same CSV, then summarised a file it had already summarised twice. The code was fine. The design wasn’t.
That gap between “code that runs” and “agent that helps” is where most tutorials stop. What follows is about the second half: how to build AI agents with Python that finish a job, fail loudly when they can’t, and don’t quietly eat your afternoon.
What an agent really is, stripped of the marketing
Three things separate an agent from a prompt: tools, a loop, and a decision about when to stop. A prompt chain runs the steps you wrote down. An agent picks the steps, calls functions you handed it, reads the results, and chooses what to do next.
That’s the whole trick. No orchestration graph required, no vector database on day one. If you can write a while loop and make an HTTP request, you can have one running before dinner.
The loop that does the work
Here’s the shape of it in plain terms. You keep a list of messages. You send that list to the model along with a description of the tools it’s allowed to use. If the model comes back asking to call a tool, you run the Python function, append the result as a new message, and go around again. If it comes back with ordinary text instead, you’re done and you return that text.
Everything else in agent development is refinement of that loop. The interesting decisions are which tools exist, how much context the model sees, and what happens when it gets stuck.
Setting up a Python project that won’t fight you
Keep the skeleton boring so you can spend your attention on behaviour.
- A virtual environment and a pinned requirements file. Agent SDKs ship breaking changes on a monthly cadence, so pin versions and upgrade deliberately.
- An .env file for keys, loaded with python-dotenv. Never hardcode an API key, even in a scratch file you’ll delete later.
- A model with reliable tool calling. Small cheap models are great for classification, but they fumble multi-step tool use in ways that cost you more debugging time than the tokens saved.
- Structured logging from the first commit. Print the model’s requested tool calls, the arguments, and the result length. You will read these logs constantly.
A working agent in about 60 lines
Start with one tool. Two at most. A genuinely useful first agent might read files from a directory and search their contents, or fetch a URL and pull out the article body. Write the function, write a short description of when to use it, and wire it into the loop.
Cap the loop hard. I use eight iterations as a default, which covers most real tasks and keeps a runaway agent from spending an hour deciding. When the cap is hit, don’t return a half-finished answer as if it were complete. Return a clear failure: “Gave up after 8 steps, last action was searching for X.” That message is worth more than a confident-sounding guess.
The system prompt matters less than people expect and more than they hope. Two or three sentences on the goal, the tools available, and the output format beats a 900-word personality brief. Vague instructions like “be thorough” produce agents that loop.
Tools are where the value actually lives
A language model with no tools can only talk. Give it the ability to query your database, check a shipping API, or read a specific folder and it becomes useful. Tool design is the highest-leverage skill in this whole process.
- One job per tool. A function called handle_customer that does six things will confuse the model about when to call it. Split it into lookup_order, issue_refund, and send_reply.
- Return strings, not objects. JSON-serialise results and truncate them. A 40,000-character API response is 10,000 tokens you didn’t need.
- Validate arguments before executing. Bad arguments should come back as a helpful error the model can correct, not a Python traceback that kills the run.
- Write error messages for the model. “Date must be ISO format, e.g. 2025-03-14” lets it retry successfully. “KeyError” does not.
The distinction between an agent that flails and one that lands the task is usually how forgiving its tools are. There’s a good breakdown of that persistence problem in how to create an AI agent that actually finishes the job, and most of it comes down to feedback loops rather than model choice.
Memory and state: the part everyone underestimates
Context windows are bigger than they were, which tempts people into passing every message forever. That works until turn 30, when the agent is paying to re-read its own rambling and losing the thread anyway.
Two habits fix most of it. First, summarise: once the conversation passes a threshold, replace the oldest turns with a compact summary that keeps decisions and open questions, and drop the rest. Second, persist to SQLite or a JSON file between runs. If your agent’s job is monitoring something daily, it should not relearn the context every morning.
Truncate tool outputs aggressively. Cap file reads at a couple of thousand characters and paginate if the model asks for more. You’ll cut costs by a large margin and, oddly, get better answers.
Guardrails that save you money and embarrassment
Assume the agent will do something unexpected. The question is whether that costs you a dollar or a customer.
Put a hard ceiling on spend per run. Allowlist which functions can be called at all, so a confused model can’t reach for a delete endpoint. Run anything that writes data in a dry-run mode first and log what it would have done. Add timeouts on every network call inside a tool, because one hanging request stalls the entire loop.
For anything with real consequences, require human approval before execution. A confirmation step that takes a person four seconds is cheaper than an apology email.
When a framework earns its place
LangGraph, CrewAI, Pydantic AI, smolagents, MetaGPT. They’re all reasonable, and none of them will save a bad design. My rule: build the raw loop first, badly and quickly, so you understand what’s happening. Then reach for a framework when you need durable state across restarts, parallel sub-agents, or a graph you can visualise for a teammate.
Multi-agent setups are a good example of a place where a framework genuinely helps, since the coordination overhead in hand-rolled code gets ugly fast. If that’s the direction you’re heading, this walkthrough of building a small Python tool with a team of AI agents using MetaGPT shows what the division of labour looks like in practice.
If you’re choosing between hosted platforms rather than libraries, this comparison of what separates the AI agent platforms is more useful than any feature checklist.
Testing an agent when the output is never identical
You can’t assert on exact strings. You can assert on outcomes.
Build a set of 20 real tasks with a checkable ending: the right row updated, the correct file produced, the ticket tagged properly. Run the whole set after every change and track the pass rate as a number. An agent that succeeds on 17 of 20 is a known quantity. One that “seems to work” is a liability.
Keep the failures in the set even after you fix them. Regression suites are how agents stay working once the novelty wears off and you stop watching every run.
Pick one boring task and ship it
The best first project is something you do every week, takes about fifteen minutes, and has an obvious success condition. Triaging a shared inbox. Turning meeting notes into a formatted update. Pulling numbers from three dashboards into one sheet.
Boring is the point. You’ll learn more from a small agent that runs unattended for two weeks than from a clever one you demo once. Schedule it with cron or a GitHub Actions workflow, watch the logs for the first few runs, then let it work quietly in the background.
And if the coding side turns out to be the blocker rather than the design, there’s a whole separate route worth knowing about: building AI agents without writing code covers where the no-code tools hold up and precisely where they fall apart.

