Build your first AI agent in Python with Claude
Build a Python AI agent on the Claude API in 22 minutes, with memory across turns, a tool it calls on its own and long chats it sums up.

What you need
Step 1: Set up the project
5 minYour API key lets the code call Claude, and a
.envfile keeps the key out of your code.- Go to platform.claude.com/settings/keys, create an API key and copy it.
- Run the commands below in a terminal. On Windows, activate the environment with
.venv\Scripts\activateinstead. - In the new folder, create a file named
.envwith one line:ANTHROPIC_API_KEY=followed by your key. - Add
.envto your.gitignorefile, so the key never ends up in Git.
Run thismkdir my-agent && cd my-agent python3 -m venv .venv && source .venv/bin/activate pip install anthropic python-dotenv tzdata
You should see the 3 packages installed with no errors, and a
.envfile in yourmy-agentfolder.pip says the anthropic package needs a newer Python?
The SDK needs Python 3.10 or later. Install a newer Python, delete the
.venvfolder and create it again with the new version.Keep the key out of chats, screenshots and shared files. If it leaks, delete it in the Console and create a new one.
Step 2: Write a tool Claude can call
4 minA tool is a plain Python function plus a short description. Claude reads the description and decides when to use it.
- Create
agent.pyin the project folder and paste in the code below. - Read the tool's
description. It tells Claude when the tool helps, so write your own just as plainly. - To add a tool later, write the function, add its entry to
TOOLSand add its name toFUNCTIONS.
Your agent.py, part 1from datetime import datetime from zoneinfo import ZoneInfo import anthropic from dotenv import load_dotenv load_dotenv() def get_time(timezone): try: return datetime.now(ZoneInfo(timezone)).strftime("%A %H:%M") except Exception: return f"Unknown time zone: {timezone}. Use a name like Europe/Paris." TOOLS = [ { "name": "get_time", "description": "Get the current day and time in a time zone. Use an IANA name such as Europe/Paris or America/New_York.", "input_schema": { "type": "object", "properties": {"timezone": {"type": "string", "description": "IANA time zone name"}}, "required": ["timezone"], }, } ] FUNCTIONS = {"get_time": get_time}You should see a saved
agent.pywith theget_timefunction and its matching entry inTOOLS.The
tzdatapackage you installed gives Windows the time zone names thatZoneInfoneeds.- Create
Step 3: Give the agent memory and a tool loop
6 minThe API doesn't remember past turns, so the agent sends the whole chat each time and loops until Claude stops calling tools.
- Paste the class below under the code from step 2.
self.memorykeeps every turn: your messages, Claude's replies and the tool results.- When
stop_reasonistool_use, the loop runs each tool and sends the result back with the sametool_use_id.
Your agent.py, part 2class Agent: def __init__(self, model="claude-opus-5-5"): self.client = anthropic.Anthropic() self.model = model self.memory = [] def chat(self, text): self.memory.append({"role": "user", "content": text}) while True: response = self.client.messages.create( model=self.model, max_tokens=16000, system="You are a helpful assistant. Use your tools when they help.", tools=TOOLS, messages=self.memory, ) self.memory.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": return "".join(b.text for b in response.content if b.type == "text") results = [] for block in response.content: if block.type == "tool_use": output = FUNCTIONS[block.name](**block.input) results.append({"type": "tool_result", "tool_use_id": block.id, "content": output}) self.memory.append({"role": "user", "content": results})You should see an
Agentclass with achat()method below your tool code.The API returns a 400 error about a tool_result block?
Each
tool_useblock needs atool_resultwith the same ID in the very next message. Append Claude's fullresponse.content, then send all the results together.Anthropic suggests Claude Opus 5.5 as the model to start with. For cheap, simple chats, create the agent with
Agent(model="claude-haiku-5-5").Step 4: Chat with your agent
3 minA short loop at the end of the file turns the class into a chat you can run in the terminal.
- Paste the code below at the end of
agent.py. - Run
python agent.pyand ask: What time is it in Tokyo right now? - Then ask a follow-up, such as "And in Paris?", to check that it remembers what you asked.
Your agent.py, part 3if __name__ == "__main__": agent = Agent() print("Chat with your agent. Type quit to stop.") while True: text = input("You: ").strip() if text.lower() in ("quit", "exit"): break if text: print("Agent:", agent.chat(text))You should see the current time in Tokyo, then in Paris, without you repeating the question.
You get an authentication error?
Check that
.envis in the folder you run the script from, with no quotes or spaces around the key. Then run the script from inside that folder.- Paste the code below at the end of
Step 5: Keep long chats inside the context window
4 minEvery turn resends the whole chat, so long sessions get slower and cost more unless older turns are summed up.
- In
chat(), replace the wholeself.client.messages.create(...)call with the one below. - Leave the rest of the loop as it is. It already saves Claude's full reply, which is where the summary arrives.
- Keep the trigger at 50,000 tokens or more. That's the lowest value the API accepts.
Replace the create callresponse = self.client.beta.messages.create( betas=["compact-2026-01-12"], context_management={"edits": [{ "type": "compact_20260112", "trigger": {"type": "input_tokens", "value": 50000}, }]}, model=self.model, max_tokens=16000, system="You are a helpful assistant. Use your tools when they help.", tools=TOOLS, messages=self.memory, )You should see replies as before. Once a chat passes the trigger, one reply starts with a
compactionblock that holds the summary.You trimmed old messages yourself and now get a 400 error about a thinking block?
On accounts created on or after August 31, 2026, Claude's thinking blocks only stay valid if earlier messages never change. Undo the trimming and let compaction shorten the chat.
Compaction is in beta, so the call goes through
client.beta.messageswith thecompact-2026-01-12beta flag.- In
You get
A Python agent that remembers the conversation, calls your own tool and sums up long chats on its own
Try next
Did this guide work for you?
Thanks. It helps us fix the steps.
Sources
0 of 5Next: Set up the project


