The messages you get back
A message is one object the SDK yields for a step of the loop, and five of them drive a session: a system message that opens it, an assistant message per content block, a user message carrying each tool result, a stream event while text is being written, and a result message that closes it.
Last updated: 20 Oct, 2026 · Claude Agent SDK
The How the agent loop works lesson described the loop as a cycle. From your code the loop is not a cycle at all; it is a stream you read. Every stage of it arrives as an object with a class of its own, and knowing which five matter is the whole skill of reading a run.
Reading the five messages the loop yields
SystemMessagecarries session lifecycle events, and itssubtypefield says which.initopens a run with the session metadata.compact_boundaryfires after the loop has compacted the conversation.informationalis a plain-text status banner, andworker_shutting_downmeans the host is exiting. Its payload is a dictionary indata, not named fields.AssistantMessageis Claude's output, one message per content block. A reply with text and a tool call therefore arrives as two messages, and the two share a message id. It carries the model name as well as the content.UserMessagearrives after each tool runs, holding the result that was sent back to Claude. Tool results are on the user's side of the conversation Claude reads, which is why a result your own tool produced comes back as a user message. It is also the message type for anything you stream into a running session.StreamEventappears only when partial messages are turned on, and holds a raw API streaming event: a text delta, a chunk of a tool's input. Streaming output is built on it.ResultMessagemarks the end of the loop. It carries the final text, the turn count, the token usage, the cost and the session id, and itssubtypesays whether the run succeeded or hit a limit. The result message goes through its fields one at a time.
Both SDKs also yield events that are useful without being needed to drive the loop: rate-limit status, task notifications. A program that handles the five above and ignores the rest is complete.
Looking inside an assistant message
An assistant message holds a list of content blocks, and the block type is what tells you whether Claude wrote prose or asked for a tool:
TextBlockholds text Claude wrote, in atextfield.ToolUseBlockis a request for a tool: a name, the arguments as a dictionary, and anidof its own.ToolResultBlockis the answer, carrying the same id intool_use_idso the two can be paired, the content, and anis_errorflag.ThinkingBlockholds reasoning text, when the run is configured to send it.- Two more block types exist for tools that run on Anthropic's side rather than yours, and the reference lists them without expanding them.
Following the order they arrive in
The order is fixed, which makes a run easy to follow once you have seen one. A session opens with the system message. Then, per turn, Claude's blocks arrive as assistant messages, each tool result follows as a user message, and the cycle repeats. The last assistant message is the text-only answer, and the result message closes the run.
One detail is worth building in from the start: the agent loop page notes that a small number of trailing system events can arrive after the result message, so a loop should iterate the stream to completion rather than breaking out of it when the result appears.
Choosing which messages to handle
| What you are building | The message to handle | What you read from it |
|---|---|---|
| A background job or a CI step | The result message | The final text, the cost, and the subtype that says whether it worked |
| A progress display | Assistant messages | Each content block as it completes: the text, or the name of the tool being called |
| A live, typing-as-it-goes interface | Stream events | Text deltas, accumulated by your code |
| An audit trail of tool use | Assistant and user messages | The tool name and arguments, then the result paired by its id |
| A conversation you resume later | The result message | The session id |
In Python you tell them apart with isinstance against the classes imported from claude_agent_sdk. The TypeScript SDK checks a type string instead, and nests the content one level deeper, which is the main thing to watch when reading a TypeScript example as a Python developer.
Where the message list changes what you write
- Showing a user what the agent is doing, where handling assistant messages gives you the tool names to display and the result message alone would give you silence followed by an answer.
- Logging a run for later, where the pairing of a tool-use id with its result is what makes the log readable.
- Deciding a run failed, where the result message's subtype is the answer and the absence of text is not.
None works until the first run that ends badly.Related
- Previous: SDK, CLI and Client SDK compared
- Next: query() and ClaudeAgentOptions
- See also: The result message
- Reference: Python SDK message types
- Write down the five message types and, next to each, what your own program would do with it.
- For a run that calls two tools in one turn, count how many assistant and user messages arrive before the result.
- Decide which of the five you would log and which you would throw away, and say what you lose by throwing each one away.
This is what real progress feels like.