Build Your Own AI Agent in 100 Lines of Python
Build a real AI agent from scratch — no framework. Just the Anthropic API, a tool-use loop, and two tools the model can call to explore your files.
“Agent” sounds like it needs a heavyweight framework. It doesn’t. Strip away the abstractions and an AI agent is one small idea: a language model in a loop, calling tools you give it, until the task is done. In this guide we’ll build a working agent in about a hundred lines of Python — no LangChain, no orchestration library — using Anthropic’s API and its SDK. Our agent will be able to explore a directory and answer questions about your code.
The whole idea, in one diagram
The loop is the agent:
- Send the conversation (plus a list of tools) to the model.
- The model either answers, or asks to call a tool.
- If it called a tool, you run it and feed the result back.
- Repeat until the model stops asking for tools.
That’s it. Everything else — memory, planning, multi-step reasoning — emerges from running this loop with good tools.
Setup
Install the SDK and set your API key:
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
import os
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
MODEL = "claude-opus-4-8"
Defining tools
A tool is a name, a description, and a JSON Schema for its inputs. The description matters more than you’d think — it’s how the model decides when to reach for the tool. We’ll give our agent two: one to list a directory, one to read a file.
TOOLS = [
{
"name": "list_files",
"description": "List files and folders in a directory. Use this to "
"explore the project before reading individual files.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Directory to list. Use '.' for the current folder."}
},
"required": ["path"],
},
},
{
"name": "read_file",
"description": "Read and return the contents of a text file.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file to read."}
},
"required": ["path"],
},
},
]
Implementing the tools
The model never runs code — you do. Each tool is an ordinary Python function. Keep them confined to a safe directory; the model’s inputs are untrusted, so never feed a raw path straight into the filesystem in production.
def list_files(path="."):
return "\n".join(sorted(os.listdir(path)))
def read_file(path):
with open(path, "r", encoding="utf-8") as f:
return f.read()
TOOL_FUNCTIONS = {"list_files": list_files, "read_file": read_file}
def run_tool(name, tool_input):
try:
return TOOL_FUNCTIONS[name](**tool_input)
except Exception as exc: # surface errors back to the model
return f"Error: {exc}"
Notice we return errors as strings instead of crashing. The model reads the error and adapts — tries a different path, asks a clarifying question — which is half of what makes an agent feel capable.
The loop
Here’s the engine. We call the API; if the model’s stop_reason is tool_use, we execute every tool it requested, append the results, and go around again. Crucially, we append the model’s entire response (response.content) back into the history — that preserves the tool_use blocks the API needs to match against our results.
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model=MODEL,
max_tokens=1024,
system="You are a coding assistant. Use the tools to explore the "
"project, then answer the user's question concisely.",
tools=TOOLS,
messages=messages,
)
for block in response.content: # show the model's narration
if block.type == "text":
print(block.text)
if response.stop_reason != "tool_use":
return # end_turn — the agent is done
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
print(f" → {block.name}({block.input})")
output = run_tool(block.name, block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id, # must match the request
"content": output,
})
messages.append({"role": "user", "content": results})
if __name__ == "__main__":
run_agent("What does this project do? Look around and summarize it.")
Watch it think
Run it inside any project directory and you’ll see the agent work the loop on its own:
→ list_files({'path': '.'})
→ read_file({'path': 'package.json'})
→ read_file({'path': 'src/index.ts'})
This is a small Astro blog. It builds static pages from Markdown in
src/content/blog, deploys to Cloudflare Pages, and includes an RSS feed...
Nobody told it to read package.json first — it decided that listing the directory, then reading the manifest, then the entry point was the way to understand an unfamiliar project. That planning is the model’s; the loop just gave it room to act.
Where to go next
This 100-line core is genuinely the same shape the big frameworks wrap. To grow it:
- More tools. Add
write_file,run_command, or a web search. Capability scales with the tools you expose. - Standardize them with MCP. The Model Context Protocol lets your agent plug into tools other people built, instead of hand-writing each one. The layer above — how agents discover which tools exist — is being standardized too, under ARD.
- Run it cheaper or locally. Swap in a smaller model, or run a model locally with Ollama for offline experiments. For production, prompt caching cuts the cost of resending the conversation each turn.
- Let it think. Enabling extended thinking gives the model room to reason between tool calls on harder tasks.
The takeaway
An agent isn’t a framework — it’s a loop. Once you’ve written while True: call the model, run its tools, feed back the results, every agent product you’ve read about becomes legible. Start here, add one tool at a time, and you’ll understand exactly where the intelligence lives and where your code does.
Tagged
Keep reading
Chisato · · 4 min read What Is Prompt Chaining? Multi-Step LLM Pipelines
Prompt chaining splits a task into a sequence of smaller LLM calls, each one feeding the next, instead of asking one giant prompt to do everything.
Chisato · · 6 min read Grok Voice Think Fast 2.0: Pricing, Specs, Default Date
xAI's Grok Voice Think Fast 2.0 becomes the default grok-voice-latest on Aug 5, with an 82.9% speech-quality score and $0.08/min pricing. What changed.
Chisato · · 5 min read AI Agent Memory: Short-Term vs Long-Term Context
How AI agents remember: short-term memory bound by the context window versus long-term memory persisted in external storage like a vector database.