ai newsDi., 20. Okt.
EilmeldungTrump administration orders ban on DeepSeek in federal agencies

What Is an AI Agent? How to Build One from Scratch in Python

Originally published at GeekCoding101 . "Agent" Is Everywhere. What Does It Actually Mean? Every product launch in 2026 seems to include the word agent : coding agents, browser agents, research agents, "agentic workflows". In my Agentic Frameworks guide I compared the big frameworks for building th

dev.toDienstag 01:29 · 20.10.9 Min. Lesezeit
Bild: dev.to

Posted on Oct 19 • Originally published at geekcoding101.com

Every product launch in 2026 seems to include the word agent: coding agents, browser agents, research agents, "agentic workflows". In my Agentic Frameworks guide I compared the big frameworks for building them. But before you pick a framework, it's worth understanding what an agent really is, because the core idea fits in about 50 lines of Python. In this post we'll define it, look at the loop that powers every agent, and build one from scratch. 🚀

An AI agent is a system where a large language model decides its own next steps and takes actions through tools in a loop, until a goal is reached.

A good rule of thumb: if you can draw the flowchart in advance, build a workflow; it's cheaper, faster and more predictable. Reach for an agent when the path depends on what the model discovers along the way, like debugging, research, or cleaning up messy data.

Memory, planning and multi-agent setups are refinements on top of these four. Everything else is detail.

The model never runs anything itself. It asks for a tool call (in the Claude API, a tool_use block with stop_reason: "tool_use" ), your code runs it, and you send back a tool_result. The whole conversation, including every tool call and result, is resent on each turn, which is how the agent "remembers" what it already did.

Let's build a small file agent: it can list, read and write text files inside a workspace/ folder, and we'll ask it to clean up a messy to-do list. I'm using the official Anthropic Python SDK, but the same loop works with any provider that supports tool calling.

uv init file-agent cd file-agent uv add anthropic export ANTHROPIC_API_KEY = sk-ant-... # from console.anthropic.com Enter fullscreen mode Exit fullscreen mode The Complete Agent Save this as agent.py:

""" A minimal AI agent: Claude plus three file tools, in a loop. """ from pathlib import Path import anthropic WORKDIR = Path ( " workspace " ). resolve () WORKDIR. mkdir ( exist_ok = True ) client = anthropic. Anthropic () # reads ANTHROPIC_API_KEY from the environment TOOLS = [ { " name ": " list_files ", " description ": " List the files in the workspace folder.

", " input_schema ": { " type ": " object ", " properties ": {}, " additionalProperties ": False }, }, { " name ": " read_file ", " description ": " Read a text file from the workspace folder. ", " input_schema ": { " type ": " object ", " properties ": { " path ": { " type ": " string ", " description ": " File name inside the workspace " }}, " required ": [ " path " ], " additionalProperties ": False, }, }, { " name ": " write_file ", " description ": " Create or overwrite a text file in the workspace folder. ", " input_schema ": { " type ": " object ", " properties ": { " path ": { " type ": " string ", " description ": " File name inside the workspace " }, " content ": { " type ": " string ", " description ": " Full new content of the file " }, }, " required ": [ " path ", " content " ], " additionalProperties ": False, }, }, ] def safe_path ( name: str ) -> Path: """ Resolve a file name inside WORKDIR and refuse anything that escapes it.

""" path = ( WORKDIR / name ). resolve () if not path. is_relative_to ( WORKDIR ): raise ValueError ( f " { name } is outside the workspace " ) return path def run_tool ( name: str, args: dict ) -> str: if name == " list_files ": return " \n ". join ( p. name for p in sorted ( WORKDIR. iterdir ())) or " (empty) " if name == " read_file ": return safe_path ( args [ " path " ]). read_text ( encoding = " utf-8 " ) if name == " write_file ": safe_path ( args [ " path " ]). write_text ( args [ " content " ], encoding = " utf-8 " ) return f " Wrote { len ( args [ ' content ' ]) } characters to { args [ ' path ' ] } " raise ValueError ( f " Unknown tool { name } " ) def run_agent ( task: str, max_steps: int = 20 ) -> str: messages = [{ " role ": " user ", " content ": task }] for step in range ( max_steps ): response = client. messages. create ( model = " claude-opus-5-5 ", max_tokens = 16000, system = " You are a careful assistant that works on files in a workspace folder. " " Look at what exists before changing anything. ", tools = TOOLS, messages = messages, ) # Keep the whole assistant turn (text, thinking and tool calls) in the history. messages. append ({ " role ": " assistant ", " content ": response. content }) if response. stop_reason!= " tool_use ": # end_turn (done), max_tokens, refusal...: stop and return what we have. return "". join ( b. text for b in response. content if b. type == " text " ) results = [] for block in response. content: if block. type!= " tool_use ": continue print ( f " [step { step } ] { block. name } ( { block. input } ) " ) try: output, is_error = run_tool ( block. name, block. input ), False except Exception as e: # report failures to the model instead of crashing output, is_error = f " Error: { e } ", True results. append ({ " type ": " tool_result ", " tool_use_id ": block. id, " content ": output, " is_error ": is_error }) # All results of one turn go back together in a single user message. messages. append ({ " role ": " user ", " content ": results }) return " Stopped: too many steps.

" if __name__ == " __main__ ": ( WORKDIR / " todo.txt " ). write_text ( " buy milk \n write blog post about agents \n buy milk \n ", encoding = " utf-8 " ) print ( run_agent ( " Clean up todo.txt: remove duplicates, sort it, and save the result as todo-clean.txt. " )) Enter fullscreen mode Exit fullscreen mode uv run agent.py Enter fullscreen mode Exit fullscreen mode On a run you'll see something like this (the exact steps vary, since the model decides them):

[step 0] list_files({}) [step 1] read_file({'path': 'todo.txt'}) [step 2] write_file({'path': 'todo-clean.txt', 'content': 'buy milk\nwrite blog post about agents\n'}) Done! I removed the duplicate "buy milk" entry, sorted the list, and saved it to todo-clean.txt. Enter fullscreen mode Exit fullscreen mode Nobody told it to call list_files first or to read before writing. It decided that, partly because the system prompt says to look before changing anything.

Tool definitions are prompts. The model only knows a tool through its name, description and input_schema. Clear descriptions ("Full new content of the file") prevent most wrong calls. additionalProperties: False keeps the model from inventing parameters.

Append the whole assistant turn. messages.append({"role": "assistant", "content": response.content}) keeps the text, any thinking blocks and the tool_use blocks exactly as returned. Extracting only the text would break the next request, because every tool_result must match a tool_use id from the previous turn.

Return all results together. One assistant turn can contain several tool calls (the model reads two files in parallel, for example). Send all their results back in one user message.

Errors are results, not crashes. When a tool fails, we send "is_error": True with the message. The model reads it and usually recovers, for example by listing files after guessing a wrong name.

Stop on anything but tool_use. end_turn means done, but max_tokens or refusal also end the loop here instead of spinning forever.

safe_path is the guardrail. The model chooses file names, so never trust them: resolving the path and checking it stays inside WORKDIR blocks../../.ssh/id_rsa -style escapes. Together with max_steps, this is the minimum safety every agent needs.

Once you understand the loop, you don't have to write it every time. The Anthropic SDK's tool runner (beta) builds the schemas from type hints and docstrings and runs the loop for you:

import anthropic from anthropic import beta_tool from agent import WORKDIR, safe_path client = anthropic. Anthropic () @beta_tool def list_files () -> str: """ List the files in the workspace folder. """ return " \n ". join ( p. name for p in sorted ( WORKDIR. iterdir ())) or " (empty) " @beta_tool def read_file ( path: str ) -> str: """ Read a text file from the workspace folder.

Args: path: File name inside the workspace. """ return safe_path ( path ). read_text ( encoding = " utf-8 " ) @beta_tool def write_file ( path: str, content: str ) -> str: """ Create or overwrite a text file in the workspace folder. Args: path: File name inside the workspace. content: Full new content of the file.

""" safe_path ( path ). write_text ( content, encoding = " utf-8 " ) return f " Wrote { len ( content ) } characters to { path } " runner = client. beta. messages. tool_runner ( model = " claude-opus-5-5 ", max_tokens = 16000, tools = [ list_files, read_file, write_file ], messages = [{ " role ": " user ", " content ": " Clean up todo.txt: remove duplicates, sort it, save as todo-clean.txt. " }], ) final = runner. until_done () print ( final. content [ - 1 ]. text ) Enter fullscreen mode Exit fullscreen mode Frameworks like LangGraph, CrewAI, the OpenAI Agents SDK or Google ADK add more on top (state graphs, multiple agents, tracing, deployment), but underneath they all run this same loop.

A chatbot answers one message at a time. An agent pursues a goal across many steps, choosing and calling tools, reading the results and deciding what to do next on its own.

No. As shown above, a working agent is a loop around a model API plus a few tool functions. Frameworks help once you need persistence, multiple agents, tracing or deployment.

"Agentic AI" describes systems with this agent behavior: models that plan, use tools and act with some autonomy, as opposed to single-shot text generation.

MCP standardizes how agents get tools. Instead of writing read_file yourself, your agent can connect to an existing MCP server for files, GitHub, databases and more. See What Is an MCP Server?

More in the Agent Stack Insider series 👇

🚀 Agentic Frameworks Compared: LangGraph, CrewAI & More

🚀 What Is an MCP Server? How to Build One in Python

Quelle: dev.to (dev.to). Original öffnen