Skip to main content

Your First Agent with query()

You're about to write your first autonomous agent in 10 lines of code.

Unlike traditional APIs where you send a request and get a response, agents work differently: They receive a task, reason about what to do, execute tools autonomously, evaluate results, and iterate until the task is complete. Your job is to start the agent and process the stream of messages it produces.

The query() function is your entry point to this autonomous loop. By the end of this lesson, you'll understand how to configure it, iterate over its message stream, and extract results.

Installation and Authentication

Install the SDK

# Python
pip install claude-agent-sdk

# TypeScript
npm install @anthropic-ai/claude-agent-sdk

Set Up API Access

The SDK authenticates using environment variables. Three options exist:

Option 1: Anthropic API (Recommended for development)

export ANTHROPIC_API_KEY=your-api-key-here

Get your API key at console.anthropic.com.

Option 2: Amazon Bedrock (Production-grade)

export CLAUDE_CODE_USE_BEDROCK=1
export AWS_PROFILE=your-profile

Option 3: Google Vertex AI

export CLAUDE_CODE_USE_VERTEX=1
export GOOGLE_PROJECT_ID=your-project
export GOOGLE_REGION=us-central1

For this lesson, use Option 1 (Anthropic API). Authentication will complete in seconds when you first call query().

Understanding query()

The query() function is an async generator—it streams messages as the agent executes.

Basic Pattern: Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
async for message in query(
prompt="Find all Python files in the current directory and count their lines",
options=ClaudeAgentOptions(
allowed_tools=["Bash", "Glob"],
permission_mode="default"
)
):
print(f"Message type: {message.type}")

asyncio.run(main())

Output:

Message type: assistant
Message type: tool_use
Message type: assistant
Message type: result
Done: Found 47 Python files totaling 18,294 lines of code

The stream ends when message.type == "result" appears, signaling agent completion.

The agent:

  1. assistant: Claude receives the prompt and generates a plan
  2. tool_use: Claude decides to use Bash/Glob to find files
  3. assistant: Claude processes the results and generates output
  4. result: The final answer is delivered

Basic Pattern: TypeScript

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

async function main() {
for await (const message of query({
prompt: "Find all Python files in the current directory and count their lines",
options: {
allowedTools: ["Bash", "Glob"],
permissionMode: "default"
}
})) {
console.log(`Message type: ${message.type}`);
}
}

main();

Output:

Message type: assistant
Message type: tool_use
Message type: assistant
Message type: result
Done: Found 47 Python files totaling 18,294 lines of code

Same message flow as Python. The async iterator pattern (for await...of) is the TypeScript equivalent of Python's async for.

ClaudeAgentOptions: Controlling Agent Behavior

ClaudeAgentOptions is a configuration object that controls what tools the agent can use and how it executes.

Parameter 1: allowed_tools

Specifies which tools the agent can invoke. The Agent SDK provides these built-in tools:

ToolWhat It Does
ReadRead any file (text, images, PDFs, Jupyter notebooks)
WriteCreate new files
EditMake precise edits to existing files (character-by-character)
BashRun terminal commands and scripts
GlobFind files matching patterns (**/*.ts, src/**/test*.py)
GrepSearch file contents with regex
WebSearchSearch the web
WebFetchFetch and parse web pages
TaskSpawn subagents for parallel work

Example: Code review agent that can read and search, but not modify

options = ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob"],
# ... other config
)

Why restrict tools? An agent with access to only Read and Grep cannot accidentally delete files. This is a security principle: Start with deny-all, then allowlist only what's necessary.

Parameter 2: permission_mode

Controls how the agent prompts for permission before executing certain operations.

ModeBehaviorWhen to Use
defaultPrompts user before file edits, Bash commands, deletionsInteractive sessions where you want approval for each operation
acceptEditsAuto-approves file edits, still prompts for dangerous operationsTrusted workflows where edits are safe but Bash needs approval
bypassPermissionsNo prompts; agent executes without askingAutomated pipelines, production systems (use with caution)

Example: Auto-approve file edits for code formatting

options = ClaudeAgentOptions(
allowed_tools=["Read", "Edit"],
permission_mode="acceptEdits" # Edits approved; Bash still requires permission
)

Parameter 3: max_turns

Limits the number of agent iterations. The agent counts each cycle of reasoning → tool use → evaluation as one turn.

Why limit turns? Prevents runaway agents that loop indefinitely:

options = ClaudeAgentOptions(
allowed_tools=["Bash", "Read"],
max_turns=5 # Stop after 5 reasoning cycles maximum
)

Without max_turns, an agent could theoretically run forever. Set a limit appropriate to your task:

  • Simple tasks (search, format): max_turns=3
  • Standard tasks (refactor, debug): max_turns=5-10
  • Complex tasks (architecture design): max_turns=15

Parameter 4: cwd (Current Working Directory)

Specifies where the agent executes Bash commands and file operations.

options = ClaudeAgentOptions(
allowed_tools=["Read", "Bash"],
cwd="/path/to/project" # All file operations relative to this directory
)

This prevents the agent from accidentally modifying system files outside your project.

Processing Message Types

When you iterate over query() messages, three types appear:

Message Type 1: assistant

The agent's reasoning—its plan before tool execution.

async for message in query(prompt="Task", options=opts):
if message.type == "assistant":
print(f"Agent thinks: {message.content}")

Output:

Agent thinks: I should first find Python files in the directory, then count
their lines. Let me start by using Glob to find all .py files.

The content field contains the agent's natural language reasoning. Use this to understand what the agent is planning to do.

Message Type 2: tool_use

The agent has decided to use a tool. This message contains what tool and what parameters.

async for message in query(prompt="Task", options=opts):
if hasattr(message, "tool_use") and message.tool_use:
for tool_call in message.tool_use:
print(f"Using {tool_call.name} with {tool_call.input}")

Output:

Using Glob with {'pattern': '**/*.py'}
Using Bash with {'command': 'wc -l *.py | tail -1'}

You won't typically act on this message—the SDK handles tool execution automatically. But you can use it for logging or monitoring what the agent is doing.

Message Type 3: result

The final result. This is what you extract for application use.

async for message in query(prompt="Task", options=opts):
if message.type == "result":
print(f"Final answer: {message.result}")
# Output: "I found 45 Python files totaling 12,450 lines of code"

The result field contains the agent's final output—the answer to your prompt.

Complete Example: Reading Code Quality

This example shows a complete agent that analyzes code quality:

Python (with message processing)

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def analyze_code_quality():
"""Analyze code quality in a project."""

options = ClaudeAgentOptions(
allowed_tools=["Read", "Bash", "Glob"],
permission_mode="default",
max_turns=5,
cwd="/path/to/your/project"
)

prompt = """
Analyze code quality in this project:
1. Find all Python files
2. Check for obvious style issues (long functions, missing docstrings)
3. Report a summary with recommendations
"""

message_count = 0
async for message in query(prompt=prompt, options=options):
message_count += 1

if message.type == "assistant":
# Agent is thinking; we can log this for debugging
print(f"[Turn {message_count}] Agent reasoning...")

elif hasattr(message, "tool_use") and message.tool_use:
# Agent is using tools; useful for monitoring
print(f"[Turn {message_count}] Using tools...")

elif message.type == "result":
# Extract final result
print(f"\n=== Analysis Complete ===")
print(message.result)
return message.result

asyncio.run(analyze_code_quality())

Output:

[Turn 1] Agent reasoning...
[Turn 2] Using tools...
[Turn 3] Agent reasoning...
[Turn 4] Using tools...
[Turn 5] Agent reasoning...

=== Analysis Complete ===
Found 23 Python files. Issues detected:
- 4 functions exceeding 50 lines (recommendation: break into smaller functions)
- 8 files missing module docstrings
- Average line length: 68 chars (good)

Priority actions:
1. Add docstrings to auth.py and database.py
2. Refactor calculate_total() in utils.py (78 lines)

TypeScript equivalent:

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

async function analyzeCodeQuality() {
const options = {
allowedTools: ["Read", "Bash", "Glob"],
permissionMode: "default",
maxTurns: 5,
cwd: "/path/to/your/project"
};

const prompt = `
Analyze code quality in this project:
1. Find all Python files
2. Check for obvious style issues
3. Report a summary with recommendations
`;

let messageCount = 0;
for await (const message of query({ prompt, options })) {
messageCount++;

if (message.type === "assistant") {
console.log(`[Turn ${messageCount}] Agent reasoning...`);
} else if ("toolUse" in message && message.toolUse) {
console.log(`[Turn ${messageCount}] Using tools...`);
} else if (message.type === "result") {
console.log(`\n=== Analysis Complete ===`);
console.log(message.result);
return message.result;
}
}
}

analyzeCodeQuality();

Output:

[Turn 1] Agent reasoning...
[Turn 2] Using tools...
[Turn 3] Agent reasoning...
[Turn 4] Using tools...
[Turn 5] Agent reasoning...

=== Analysis Complete ===
Found 23 Python files. Issues detected:
- 4 functions exceeding 50 lines
- 8 files missing module docstrings
- Average line length: 68 chars (good)

Key Differences from the Client SDK

If you've used the Anthropic Client SDK (anthropic package), the Agent SDK works fundamentally differently:

FeatureClient SDKAgent SDK
Tool LoopYou implement tool executionSDK handles it automatically
Async PatternSingle-turn client.messages.create()Streaming async for message in query()
Tools AvailableLimited (vision, code execution)Rich (file operations, bash, search, web)
Session ContinuityManual with token trackingBuilt-in with resume parameter
PermissionsNo built-in permission systemThree modes: default, acceptEdits, bypassPermissions
SubagentsManual task spawningNative Task tool for parallel agents

The Agent SDK is designed for autonomous agents with long execution cycles. The Client SDK is designed for chatbots with human turns. Pick the right tool for your use case.

Try With AI

Use your AI companion (Claude, ChatGPT, Gemini, or similar) to explore query() in action.

Prompt 1: Set Up Your First Agent

I want to write my first Claude Agent SDK agent. Help me:
1. Create a simple Python script that uses query() to read a file from my project
and summarize its contents
2. Walk me through the ClaudeAgentOptions I need (what tools, what permission mode)
3. Show me how to iterate through messages and extract the final result

Here's my project structure: [paste your directory structure or describe it]

What you're learning: How to structure a basic query() call with appropriate configuration for your workflow, and how to handle the message stream it produces.

Prompt 2: Debug Message Processing

I ran a query() and got these message types in my output:
[paste the message types you saw: assistant, tool_use, result, etc.]

For each message type, explain:
- What does it tell me about what the agent was doing?
- Which message type should I extract data from?
- How would I access the actual results/answers?

Show me the Python code pattern for checking each message type.

What you're learning: How to identify different message types flowing from the agent and correctly extract results without processing noise.

Prompt 3: Design Permissions for Your Use Case

I need to build an agent for this workflow: [describe your task]

The agent should be able to:
- Read files: [what types, what directories?]
- Modify files: [if needed, what kinds of changes?]
- Run commands: [what bash operations are necessary?]

Help me:
1. Choose the right set of allowed_tools
2. Pick the right permission_mode (default, acceptEdits, bypassPermissions)
3. Set max_turns to something reasonable for this task
4. Design a prompt that communicates clear intent without over-specifying

Show me the complete ClaudeAgentOptions configuration.

What you're learning: How to think about agent permissions as a security principle, and how to balance automation (acceptEdits, bypassPermissions) with safety (default, restricted tools).

Safety Note

When you give agents access to Write/Edit/Bash and permission_mode=bypassPermissions, the agent can modify or delete files without asking. Start conservatively: Use allowed_tools=["Read", "Glob", "Grep"] first to observe how the agent behaves. Gradually expand permissions as you verify the agent works reliably.


Reflect on Your Skill

You built a claude-agent skill in Lesson 0. Test and improve it based on what you learned.

Test Your Skill

Using my claude-agent skill, create a basic query() call with ClaudeAgentOptions.
Does my skill show how to configure allowed_tools and permission_mode?

Identify Gaps

Ask yourself:

  • Did my skill include query() patterns and async iteration?
  • Did it cover ClaudeAgentOptions configuration (allowed_tools, permission_mode, max_turns)?

Improve Your Skill

If you found gaps:

My claude-agent skill is missing query() patterns and options configuration.
Update it to include:
- Basic query() with async for loop
- ClaudeAgentOptions parameters
- Message type processing (assistant, tool_use, result)