Claude Agent SDKclaude-agent-sdk 0.2.157 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

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

  • SystemMessage carries session lifecycle events, and its subtype field says which. init opens a run with the session metadata. compact_boundary fires after the loop has compacted the conversation. informational is a plain-text status banner, and worker_shutting_down means the host is exiting. Its payload is a dictionary in data, not named fields.
  • AssistantMessage is 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.
  • UserMessage arrives 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.
  • StreamEvent appears 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.
  • ResultMessage marks the end of the loop. It carries the final text, the turn count, the token usage, the cost and the session id, and its subtype says 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:

  • TextBlock holds text Claude wrote, in a text field.
  • ToolUseBlock is a request for a tool: a name, the arguments as a dictionary, and an id of its own.
  • ToolResultBlock is the answer, carrying the same id in tool_use_id so the two can be paired, the content, and an is_error flag.
  • ThinkingBlock holds 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 buildingThe message to handleWhat you read from it
A background job or a CI stepThe result messageThe final text, the cost, and the subtype that says whether it worked
A progress displayAssistant messagesEach content block as it completes: the text, or the name of the tool being called
A live, typing-as-it-goes interfaceStream eventsText deltas, accumulated by your code
An audit trail of tool useAssistant and user messagesThe tool name and arguments, then the result paired by its id
A conversation you resume laterThe result messageThe 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.
Watch out. Breaking out of the loop as soon as the result message arrives is the mistake this stream invites, and it costs you the trailing events. In Python there is a second trap in the same message: the cost and usage fields are typed as optional, so a program that reads them without checking for None works until the first run that ends badly.
Try it yourself
  • 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.