Back to modules
Module 3

Agent SDK Basics

Install and use the Agent SDK to build your own autonomous agents. Learn message types, the event loop, and session management.

📖 35 min
SDK InstallationFirst AgentMessage TypesSessions

Installation

Python

bash
pip install claude-agent-sdk

Set your API key:

bash
export ANTHROPICAPIKEY=sk-...

Or in code:

python
import os

os.environ["ANTHROPICAPIKEY"] = "sk-..."

TypeScript

bash
npm install @anthropic-ai/claude-agent-sdk

Set your API key:

bash
export ANTHROPICAPIKEY=sk-...

---

Your First Agent

Python Example

python
import asyncio

from claudeagentsdk import query, ClaudeAgentOptions

async def main():

# Simple agent: read a file

async for message in query(

prompt="Read package.json and tell me the main entry point",

options=ClaudeAgentOptions(

allowedtools=["Read"]

)

):

print(f"Message: {message}")

asyncio.run(main())

Run it:

bash
python agent.py

Output:

plaintext
Message: SystemMessage(data={'sessionid': '...'})

Message: AssistantMessage(...)

Message: UserMessage(...)

[... tool execution ...]

Message: ResultMessage(...)

TypeScript Example

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {

for await (const message of query({

prompt: "Read package.json and tell me the main entry point",

options: {

allowedTools: ["Read"]

}

})) {

console.log(Message:, message);

}

}

main();

---

Message Types

The SDK sends you messages as the agent loop runs. Each message tells you what's happening.

1. SystemMessage (type: "init")

Fired when the agent session starts.

python
SystemMessage(

subtype="init",

data={

"sessionid": "sessabc123...",

"timestamp": "2024-...",

"model": "claude-3-5-sonnet"

}

)

Use case: Log session start, capture session ID for resuming

python
for message in query(prompt="..."):

if isinstance(message, SystemMessage) and message.subtype == "init":

sessionid = message.data["sessionid"]

print(f"Session started: {sessionid}")

2. AssistantMessage

Claude has made a decision to use tools.

python
AssistantMessage(

content=[

TextBlock(text="I'll read your file"),

ToolUseBlock(

id="toolxyz",

name="Read",

input={"filepath": "/path/to/file"}

)

]

)

Use case: See what Claude decided to do before tools execute

3. UserMessage

Tool results being sent back to Claude.

python
UserMessage(

content=[

ToolResultBlock(

tooluseid="toolxyz",

content="<file contents here>"

)

]

)

Use case: Monitor what tools returned

4. ResultMessage

Final completion message.

python
ResultMessage(

subtype="success",

result="The main entry point is 'dist/index.js'",

usage={"inputtokens": 234, "outputtokens": 45},

cost={"input": 0.00001, "output": 0.00002}

)

Subtypes:

  • "success" — Agent completed successfully
  • "errormaxturns" — Hit max iteration limit
  • "errormaxbudget" — Exceeded cost budget
  • "errortoolerror" — A tool threw an exception
  • "cancelled" — Agent was cancelled
  • Use case: Check if task succeeded, log costs

    python
    async for message in query(prompt="..."):

    if isinstance(message, ResultMessage):

    if message.subtype == "success":

    print(f"Success! Result: {message.result}")

    else:

    print(f"Failed: {message.subtype}")

    print(f"Cost: ${message.cost['input'] + message.cost['output']}")

    ---

    Processing Messages

    Pattern: Collect Final Result

    python
    finalresult = None

    async for message in query(prompt="..."):

    if isinstance(message, ResultMessage):

    if message.subtype == "success":

    finalresult = message.result

    print(finalresult)

    break # Done!

    Pattern: Monitor Progress

    python
    async for message in query(prompt="..."):

    if isinstance(message, AssistantMessage):

    for block in message.content:

    if isinstance(block, ToolUseBlock):

    print(f"Using tool: {block.name}")

    elif isinstance(message, ResultMessage):

    print(f"Agent finished: {message.result}")

    Pattern: Handle Errors

    python
    async for message in query(prompt="..."):

    if isinstance(message, ResultMessage):

    if message.subtype == "errormaxturns":

    print("Agent ran out of iterations. Partial result:")

    print(message.result)

    elif message.subtype == "errortoolerror":

    print("A tool failed:", message.result)

    else:

    print("Success:", message.result)

    break

    ---

    Session Management

    What Is a Session?

    A session is a stateful conversation with an agent. The agent remembers previous context.

    python

    Session 1: Ask agent to read auth.ts

    async for message in query(prompt="Read auth.ts"):

    if isinstance(message, ResultMessage):

    sessionid = ... # Save this

    result = message.result

    Later: Session 2: Resume with new question

    async for message in query(

    prompt="Now find where this is used",

    options={"resume": sessionid} # Resume same session

    ):

    # Agent remembers auth.ts content!

    pass

    Resuming Sessions

    The agent remembers everything from the previous session:

    python

    Session 1

    async for msg in query(prompt="Read api.ts"):

    if isinstance(msg, ResultMessage):

    sessionid = msg.data.get("sessionid")

    Session 2 (minutes later)

    async for msg in query(

    prompt="What patterns does it use?",

    options={"resume": sessionid}

    ):

    # Agent still has api.ts context!

    Use case: Long-running tasks split across sessions

    Session Context Window

    Each session has its own context window. As you add messages, context fills up.

    plaintext
    Session starts: 200K tokens available (depends on model)

    Turn 1: You ask question (1K tokens used, 199K left)

    Turn 2: Agent reads file (10K tokens used, 189K left)

    Turn 3: You ask followup (2K tokens used, 187K left)

    ...continues until context is full or task completes

    ---

    Agent Options

    Control agent behavior with ClaudeAgentOptions:

    python
    options = ClaudeAgentOptions(

    model="claude-3-5-sonnet",

    allowedtools=["Read", "Bash", "Grep"],

    maxturns=20,

    effort="high"

    )

    Common Options

    OptionDefaultUse Case
    ---------------------------
    allowedtoolsNone (ask)Pre-approve tools, no permission prompts
    maxturnsUnlimitedPrevent infinite loops
    maxbudgetusdNoneStop if cost exceeds budget
    effortHighControl reasoning depth (low/medium/high/xhigh/max)
    modelclaude-3-5-sonnetChoose model
    permissionmode"default"How to handle tools not in allowedtools

    ---

    Key Gotchas

    Gotcha #1: Sessions have token limits

  • Don't run 1000 turns in one session
  • Context will fill up
  • Create new sessions for very long tasks
  • Gotcha #2: Effort vs extended thinking

  • effort controls reasoning depth per turn
  • Extended thinking is separate (must opt-in explicitly)
  • Not the same feature!
  • Gotcha #3: Async/await required

  • SDK is async-first
  • You must use async for or asyncio.run()
  • Synchronous code won't work
  • Gotcha #4: Results are summaries

  • ResultMessage.result is a summary
  • Full tool outputs aren't included
  • If you need raw outputs, intercept AssistantMessage/UserMessage
  • ---

    Next Steps

    You now know how to run agents with the SDK. Next module: Tools & Tool Use — what tools are available and how they work!