You've made API calls, but a single call only returns one response. If you want to automate a workflow, Claude needs to act, look at the result, decide what's next, and keep going. That pattern is what people mean when they talk about agentic workflows.
An agent is an autonomous version of Claude, running both sides of the messaging loop without a human in the middle. An agent receives a task, picks a tool, and executes code in a loop until Claude decides the task is done.
The easiest way to implement an agent loop looks like this:
end_turn.Think of it as a conversation where the turns alternate: the user kicks things off, the agent calls a tool, the tool returns a result, and the agent keeps going until it has an answer.
To see this loop run end to end without dragging in a database or a UI, we'll wire up a fake tool called get_weather and ask Claude what to wear in Austin today. Claude has no way to know the weather on its own, so it has to call the tool, read the result, and then give you an answer.
Here's the whole script:
import anthropic
client = anthropic.Anthropic()
# The tools array tells Claude what's available:
# a name, a description, and a JSON schema for the inputs.
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city to get weather for",
}
},
"required": ["city"],
},
}
]
# run_tool is just a hardcoded lookup.
# In a real app, this would hit your database, an API, whatever.
def run_tool(name, tool_input):
if name == "get_weather":
return f"Weather in {tool_input['city']}: 95F, sunny"
raise ValueError(f"Unknown tool: {name}")
messages = [
{"role": "user", "content": "What should I wear in Austin today?"}
]
# The agent loop. Each iteration sends messages to Claude
# and switches on the response's stop reason.
while True:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
# Claude is done. Print the final text and break.
for block in response.content:
if block.type == "text":
print(block.text)
break
if response.stop_reason == "tool_use":
# Find the tool use blocks in the response and run each one.
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
}
)
# Push the assistant's response and our tool results
# back into messages, then loop again so Claude can answer.
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
تسجيل الدخول للانضمام إلى النقاش
Three pieces to notice:
run_tool is just a hardcoded lookup. In a real app, this would hit your database, an API, whatever.end_turn, Claude is done — print the final text and break. On tool_use, find the tool use blocks, run each one, push the assistant's response and your tool results back into messages, and loop again so Claude can answer.When you run the script, you'll see two turns:
tool_use. Claude requests get_weather for Austin, and your code returns the temperature and conditions.end_turn, and Claude tells you to wear something light and breathable.Two API calls, one tool execution, one final answer. That's the entire loop. Everything you build with the Claude API is going to be similar to this.
In a real environment, this same loop powers something like an auto-review endpoint: a compliance agent that reads a structural report, looks up the relevant building codes via a tool, and writes risk findings back to the database one by one as it works.
The shape of the loop is identical to what you just ran. The differences are:
end_turn.