The Messages API
The agent loop runs on one HTTP call, repeated: POST /v1/messages. You send the whole conversation each turn; the model sends back one assistant message. Before you build the loop, you need to read these two shapes fluently — what the model says, and what you say back.
The request: a list of messages
A request carries messages[]. Each message has a role (user or assistant) and a content array of typed blocks. You also pass tools — the definitions the model is allowed to call. There's no hidden server-side memory: the conversation is the array you send, so every turn you append and resend.
The assistant message carries blocks, not just text
When the model replies, its content is an array that can mix:
text— prose for the user.thinking— the model's reasoning, when extended thinking is on.tool_use— a request to run a tool:{ type, id, name, input }. Theidis the thread that ties the call to your answer.
You reply with a user message whose content is one tool_result block per call, each keyed by tool_use_id matching the tool_use.id you're answering. That keying is how the model knows which result belongs to which call when it asked for several at once.
stop_reason is the loop's pulse
Every assistant message ends with a stop_reason. Your loop reads exactly this field to decide whether to act again or halt:
stop_reason meaning loop should
─────────────────────────────────────────────────────────────────────────
tool_use model wants a tool run; tool_use blocks run tools, send
are present in content tool_result, continue
end_turn model is done; it answered in text stop
max_tokens response hit the token cap mid-stream handle / continue
stop_sequence a configured stop string was emitted stop
refusal model declined to continue stop
For the basic loop there are two cases: tool_use means keep going — run the tools and feed back tool_result blocks; anything else means stop. The richer handling of max_tokens, stop_sequence, and refusal comes later; the loop's pulse is just "is this tool_use or not?"
One turn, on the wire
// → POST /v1/messages (you send the conversation + the tools)
{
"model": "claude-...",
"max_tokens": 1024,
"tools": [
{
"name": "bash",
"description": "Run a shell command in the working tree.",
"input_schema": {
"type": "object",
"properties": { "command": { "type": "string" } },
"required": ["command"]
}
}
],
"messages": [
{ "role": "user", "content": "What's in the current directory?" }
]
}
// ← assistant response (the model asks to run a tool, then waits)
{
"role": "assistant",
"content": [
{ "type": "text", "text": "Let me look." },
{
"type": "tool_use",
"id": "toolu_01A...", // <-- the thread id
"name": "bash",
"input": { "command": "ls -la" }
}
],
"stop_reason": "tool_use"
}
// → your next turn: append the assistant message above, then send a
// user message answering the call, keyed by tool_use_id
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A...", // <-- must match the tool_use.id
"content": "total 8\ndrwxr-xr-x ... README.md src/"
}
]
}
When the model fires several tool_use blocks in one message, you run them and return several tool_result blocks — and order is not the contract, tool_use_id is. Match each result to its call by id, and remember to append both the assistant message and your tool_result user message, so the next request shows the model its own request alongside your answer.
This step is the working subset of the official tool-use flow. The full request/response shape — including parallel tool calls and the tool_choice controls — is documented in How tool use works.