Skip to content

Conversation

@rodrigo-olivares
Copy link
Contributor

@rodrigo-olivares rodrigo-olivares commented Nov 11, 2025

summary

update claude agent sdk cookbooks from v0.0.20 to v0.1.6

what changed

version updates

  • bump sdk from v0.0.20 to v0.1.6
  • update README with version requirements and feature lists
  • update pyproject.toml dependency

code modernization

  • replace string-based type checking with proper SDK type abstractions
    • "Assistant" in msg.__class__.__name__isinstance(msg, AssistantMessage)
    • hasattr(msg, "result")isinstance(msg, ResultMessage)
  • modernize all agent.py files (research, chief_of_staff, observability)
  • modernize utils/agent_visualizer.py with proper type handling

new features added to notebooks

notebook 01 (31 → 44 cells):

  • programmatic hooks with HookMatcher (vs file-based only)
  • programmatic subagents with AgentDefinition (vs file-based only)
  • system prompt patterns (append to presets, custom replacements)
  • session management (resume/fork_session)
  • tool filtering with disallowed_tools
  • enhanced what/why/how explanations throughout
  • improved narrative flow and feature organization

notebook 02 (22 → 25 cells):

  • custom tools with SDK MCP servers (@tool decorator and create_sdk_mcp_server())
  • improved explanations for SDK MCP vs external MCP

note: notebook 00 was already updated separately by Pedram and remains unchanged in this PR

supporting changes

  • add utils/track_report.py for shared audit logging
  • update agent.py files to support session management parameters
  • add setting_sources configuration for filesystem settings

testing

  • all notebooks run successfully
  • no breaking changes to user experience
  • all agent.py files updated consistently

🤖 Generated with Claude Code

@rodrigo-olivares rodrigo-olivares force-pushed the cookbook_update branch 3 times, most recently from b36ef65 to 3fde6f6 Compare November 11, 2025 05:31
@github-actions
Copy link

github-actions bot commented Nov 11, 2025

Summary

Status Count
🔍 Total 21
✅ Successful 6
⏳ Timeouts 0
🔀 Redirected 0
👻 Excluded 1
❓ Unknown 0
🚫 Errors 14
⛔ Unsupported 0

Errors per input

Errors in claude_agent_sdk/README.md

Errors in temp_md/01_The_chief_of_staff_agent.md

  • [ERROR] file:///home/runner/work/claude-cookbooks/claude-cookbooks/temp_md/chief_of_staff_agent/flow_diagram.md | Cannot find file: File not found. Check if file exists and path is correct
  • [200] https://www.anthropic.com/engineering/claude-code-best-practices | Rejected status code (this depends on your "accept" configuration): OK

Errors in temp_md/02_The_observability_agent.md

@github-actions
Copy link

Notebook Review Summary

I've reviewed all 7 changed files (2 notebooks and 5 Python modules). Overall, the code quality is excellent with comprehensive educational content and well-structured examples.


✅ What Looks Good

Notebooks (01 & 02)

  • Excellent pedagogical structure: Progressive feature introduction with clear What/Why/How sections
  • Comprehensive examples: Each feature demonstrated with runnable code cells
  • Strong documentation: Extensive markdown explanations and inline comments
  • Real-world scenarios: Chief of Staff and Observability agents are practical, relatable use cases
  • Advanced features well explained: Plan mode, hooks, subagents, MCP servers all clearly demonstrated
  • Good visual aids: Uses agent_visualizer to show conversation flows

Python Modules

  • Clean, consistent code structure: All three agent modules (research, chief_of_staff, observability) follow the same pattern
  • Good separation of concerns: Activity handling separated from core agent logic
  • Async/await properly implemented: Correct use of asyncio patterns throughout
  • Type hints: Proper type annotations in function signatures
  • Error handling: Try/except blocks with meaningful error messages
  • Reusable utilities: agent_visualizer.py and track_report.py are well-designed helper modules

⚠️ Suggestions for Improvement

01_The_chief_of_staff_agent.ipynb

  1. Cell outputs missing (cells 6, 12, 13, 16, 21):

    • Several code cells don't show their execution output
    • This makes it harder for readers to understand expected behavior
    • Recommendation: Run notebook and save outputs before committing
  2. Feature numbering inconsistency:

    • Jump from Feature 6 to "Feature 6.5" then Feature 7 to "Feature 7.5"
    • Consider: "Feature 6a/6b" or renumber as distinct features
    • Recommendation: Use consistent numbering (6, 7, 8, 9...) or clear subsections (6.1, 6.2)
  3. Hooks configuration clarity:

    • Mentions .claude/settings.local.json but doesn't show the full config
    • Recommendation: Include a code cell showing the complete hooks configuration
  4. Plan mode limitation (cell 14):

    • Note about `ExitPlanMode()) tool and SDK adaptation is important but buried
    • Recommendation: Move this to a prominent callout box or warning section
  5. Session management example (cells 34-36):

    • Good demonstration but output formatting could be clearer
    • Recommendation: Add visual separators between resume/fork examples

02_The_observability_agent.ipynb

  1. Docker requirement buried (cell heading):

    • Docker dependency mentioned mid-explanation
    • Recommendation: Add prominent prerequisite section at notebook start
  2. GitHub token security (cell heading):

    • Token setup instructions are good but could emphasize security more
    • Recommendation: Add warning about never committing .env files
  3. Cell outputs missing (cells after git MCP, github MCP examples):

    • Similar to notebook 01, missing execution outputs
    • Recommendation: Run and save outputs
  4. MCP server comparison table:

    • External vs SDK MCP servers well explained but could use a comparison table
    • Recommendation: Add a markdown table summarizing when to use each
  5. Error handling for missing Docker:

    • No guidance if Docker isn't running
    • Recommendation: Add troubleshooting section for common MCP server issues

Python Modules

chief_of_staff_agent/agent.py

  1. Line 74-82: Multi-line string formatting could be cleaner

    • Use textwrap.dedent or triple-quote alignment
  2. Line 41-49: Many parameters in send_query()

    • Consider using a config dataclass for cleaner API
  3. Docstring: Excellent but very long

    • Consider splitting features into separate documentation file

observability_agent/agent.py

  1. Line 42-55: Hardcoded GitHub MCP config

    • Good for defaults but limits flexibility
    • Recommendation: Allow override via environment variables
  2. Line 80: Token check os.environ.get("GITHUB_TOKEN")

    • Silent failure if token missing
    • Recommendation: Add warning log if token not found

research_agent/agent.py

  1. Line 46-60: Docstring explanation about sync/async handlers
    • Good documentation but seems unnecessarily complex
    • Recommendation: Pick one pattern (async) for consistency

utils/agent_visualizer.py

  1. Line 65-67: Text truncation at 500 chars

    • Hardcoded magic number
    • Recommendation: Make configurable or use constant
  2. Line 88-100: Tool result handling could be more robust

    • Assumes specific dict structure
    • Recommendation: Add type checking for safety

utils/track_report.py

  1. Line 17: Debug print to stderr

    • Good for development but should be configurable
    • Recommendation: Use logging module with levels
  2. Line 33-39: Path construction fragile

    • Hardcoded relative paths "../chief_of_staff_agent/audit/"
    • Recommendation: Use environment variable or config file
  3. Line 70: History limit at 50 entries

    • Magic number
    • Recommendation: Make configurable

❌ Critical Issues

None Found!

The code has no blocking issues. All suggestions above are improvements for maintainability and user experience, not correctness issues.


Additional Recommendations

  1. Testing: Consider adding pytest tests for the agent modules
  2. Requirements: Ensure pyproject.toml includes all MCP server dependencies
  3. README: Update with Docker prerequisite and token setup instructions
  4. Examples: Add a quickstart example that doesn't require MCP servers
  5. Versioning: Document which SDK version these notebooks are compatible with

Summary

Overall Assessment: Strong work! These notebooks provide excellent educational material for the Claude Agent SDK. The code is well-structured, examples are practical, and the progression from basic to advanced features is logical.

Primary Action Items:

  • Run notebooks and save cell outputs
  • Clarify Docker/token prerequisites upfront
  • Consider refactoring hardcoded paths in utilities

Estimated Impact: These improvements would move the notebooks from "good" to "excellent" for production documentation.

Great job on demonstrating the full capabilities of the Claude Agent SDK! 🚀

@rodrigo-olivares rodrigo-olivares force-pushed the cookbook_update branch 3 times, most recently from da87177 to 3653cc5 Compare November 11, 2025 05:50
@github-actions
Copy link

Notebook Review: Claude Agent SDK Cookbooks

✅ What Looks Good

Structure & Organization

  • Excellent progressive learning path: Three notebooks build logically (00 → 01 → 02), from basic research agents to complex multi-agent systems
  • Clear feature breakdown: Notebook 01 introduces features incrementally with What/Why/How sections
  • Reusable patterns: All agents properly modularized into agent.py files

Code Quality

  • Consistent helper functions: print_activity(), get_activity_text(), send_query() implemented consistently
  • Proper async/await patterns: All async operations handled correctly
  • Good error handling: Try-except blocks with informative error messages

Documentation

  • Rich explanations: Each feature well-documented with practical examples
  • Visual aids: visualize_conversation() provides excellent debugging support
  • Real-world context: Chief of Staff scenario with TechStart Inc makes examples relatable

Technical Implementation

  • SDK feature coverage: Comprehensive demonstration of ClaudeAgentOptions, sessions, hooks, subagents, MCP
  • Type hints: Good use of type annotations
  • Environment management: Proper .env and load_dotenv() usage

⚠️ Suggestions for Improvement

1. Docker Dependency Handling

Location: 02_The_observability_agent.ipynb
Issue: Requires Docker but doesn't handle gracefully when unavailable
Suggestion: Add error handling with clear Docker setup instructions

2. Cost Tracking Inconsistency

Location: 00_The_one_liner_research_agent.ipynb
Issue: Hardcoded pricing may become outdated
Suggestion: Reference docs or use SDK built-in cost calculation with date note

3. Session Management Clarity

Location: 01_The_chief_of_staff_agent.ipynb Feature 8
Issue: Fork vs resume distinction unclear
Suggestion: Add visual comments showing linear continuation vs branching

4. Incomplete Execution

Location: 01_The_chief_of_staff_agent.ipynb cells 4, 15, 40-42
Issue: Missing or truncated outputs
Suggestion: Re-run all cells for complete outputs or explain intentional omissions

5. Logging to stderr

Location: utils/track_report.py:17-26
Issue: stderr logging may confuse in production
Suggestion: Use proper Python logging module

6. Missing Environment Validation

Location: All notebooks
Issue: No validation of required environment variables
Suggestion: Add validation at start to fail fast with clear errors

7. Plan Mode Incomplete

Location: 01_The_chief_of_staff_agent.ipynb Feature 4
Issue: Incomplete pattern for executing plans
Suggestion: Show complete two-step plan generation then execution

8. MCP Configuration Portability

Location: observability_agent/agent.py:42-55
Issue: Hardcoded configuration
Suggestion: Extract to configurable function

❌ Critical Issues That Must Be Fixed

1. Missing Type Annotations

Location: utils/agent_visualizer.py
Issue: print_activity() and visualize_conversation() lack type hints for msg parameter
Fix Required: Add Union type hints for message types

2. Unsafe Path Operations

Location: utils/track_report.py:36-39
Issue: Relative paths may fail depending on execution location
Fix Required: Use absolute path resolution from project root

3. Incomplete Error Context

Location: All agent modules (research_agent/agent.py:84, chief_of_staff_agent/agent.py:130, observability_agent/agent.py:108)
Issue: Exception handling loses important context
Fix Required: Add diagnostic context before re-raising (prompt snippet, config)

4. Missing Null Safety

Location: utils/agent_visualizer.py:73-76
Issue: Accessing block.input without existence check
Fix Required: Add hasattr check before accessing input attribute


Summary

Excellent SDK introduction with well-structured examples. Main improvements needed:

  1. Robustness: Better error handling and environment validation
  2. Documentation: Complete outputs and clearer feature explanations
  3. Portability: Fix path operations and configuration management
  4. Type Safety: Add missing type hints for IDE support

Critical issues are straightforward defensive programming fixes.

Overall Assessment: 8.5/10 - Strong content with minor technical debt to address.

@rodrigo-olivares rodrigo-olivares force-pushed the cookbook_update branch 2 times, most recently from cb3ea50 to e8bffeb Compare November 11, 2025 14:43
@github-actions
Copy link

Notebook Review: Claude Agent SDK Cookbooks

I've completed a comprehensive review of the three notebooks and associated Python modules.

✅ What Looks Good

Excellent Educational Structure

  • Progressive complexity: The three notebooks build naturally from simple to advanced
  • Clear learning objectives with What/Why/How sections
  • Practical real-world scenarios

Strong Code Quality

  • Consistent patterns across all agent.py modules
  • Good abstraction and type hints
  • Proper error handling with try-except blocks

Documentation Excellence

  • Well-commented code cells
  • Systematic feature explanations
  • Examples before complexity

⚠️ Suggestions for Improvement

Notebook 00 (Research Agent)

  • Result extraction pattern not explained
  • Missing directory structure requirements note
  • No error handling examples for WebSearch failures

Notebook 01 (Chief of Staff)

  • ExitPlanMode() note buried in text
  • Programmatic hooks import may confuse learners
  • Final example needs architecture diagram reference earlier

Notebook 02 (Observability)

  • Docker requirement needs more prominence
  • Git MCP server path may not be intended repository
  • Mock data in custom tools needs production integration comment
  • MCP response format not explained

Python Modules

  • chief_of_staff_agent/agent.py:74-82 has inconsistent indentation in system prompt
  • observability_agent/agent.py hardcoded config could use explanation comment
  • utils/track_report.py debug prints should be wrapped in DEBUG flag

❌ Critical Issues (Must Fix)

Issue 1: Missing MCP Server Error Handling

Location: Notebook 02, multiple cells
Problem: Docker/token failures will produce cryptic errors
Fix: Add prerequisite validation cells

Issue 2: Hardcoded Paths

Location: chief_of_staff_agent/agent.py:99, utils/track_report.py:36-39
Problem: Relative paths break when imported from different locations
Fix: Use project-relative paths or make configurable

Issue 3: Session Management Race Condition

Location: Notebook 01, cells 34-36
Problem: Multiple SystemMessage instances could capture wrong session ID
Fix: Only capture FIRST init message with validation

Issue 4: Missing Subagent Error Handling

Location: Notebook 01, cell 28
Problem: No demonstration of subagent failure handling
Fix: Add try-except example

📊 Summary

  • Files reviewed: 8 (3 notebooks + 5 Python modules)
  • Critical issues: 4
  • Suggestions: 15+
  • Overall: Excellent educational material with robust core implementation

🎯 Priority Recommendations

Priority 1 (Must Fix)

  1. Add prerequisite validation
  2. Fix hardcoded paths
  3. Add session ID validation

Priority 2 (Should Fix)
4. Add error handling examples
5. Make debug prints configurable
6. Add prerequisites section to notebook 02

🏆 Overall Assessment

High-quality educational material with excellent progressive complexity and comprehensive feature coverage. Critical issues are about robustness/error handling - core concepts are sound.

Recommendation: Address Priority 1 issues before final release. Material already suitable for developers with good debugging skills.


🤖 Generated with Claude Code

- bump sdk from v0.0.20 to v0.1.6
- modernize code with sdk type abstractions
- add session management and tool filtering
- expand notebook 01 with more features
- improve narrative flow and explanations
- add utils for report tracking
@github-actions
Copy link

Notebook Changes

This PR modifies the following notebooks:

📓 claude_agent_sdk/01_The_chief_of_staff_agent.ipynb

View diff
nbdiff claude_agent_sdk/01_The_chief_of_staff_agent.ipynb (b29ec6f109c0379fa2eb620611a3d504e28fba09) claude_agent_sdk/01_The_chief_of_staff_agent.ipynb (f93243bff527cddeafa7d668575f74714ee9a3cc)
--- claude_agent_sdk/01_The_chief_of_staff_agent.ipynb (b29ec6f109c0379fa2eb620611a3d504e28fba09)  (no timestamp)
+++ claude_agent_sdk/01_The_chief_of_staff_agent.ipynb (f93243bff527cddeafa7d668575f74714ee9a3cc)  (no timestamp)
## modified /cells/0/source:
@@ -1,6 +1,6 @@
 from dotenv import load_dotenv
 from utils.agent_visualizer import print_activity, visualize_conversation
 
-from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
+from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, ResultMessage
 
 load_dotenv()

## modified /cells/1/source:
@@ -2,7 +2,7 @@
 
 #### Introduction
 
-In notebook 00, we built a simple research agent. In this notebook, we'll incrementally introduce key Claude Code SDK features for building comprehensive agents. For each introduced feature, we'll explain:
+In notebook 00, we built a simple research agent. In this notebook, we'll incrementally introduce key Claude Agent SDK features for building comprehensive agents. For each introduced feature, we'll explain:
 - **What**: what the feature is
 - **Why**: what the feature can do and why you would want to use it
 - **How**: a minimal implementation showing how to use it

## modified /cells/3/source:
@@ -6,4 +6,5 @@
 
 **How**: 
 - Have a `CLAUDE.md` file in the working directory - in our example: `chief_of_staff_agent/CLAUDE.md`
-- Set the `cwd` argument of your ClaudeSDKClient to point to directory of your CLAUDE.md file
+- Set the `cwd` argument of your ClaudeSDKClient to point to directory of your CLAUDE.md file
+- Explicitly configure setting_sources, include 'project' to load project-level CLAUDE.md

## inserted before /cells/4/outputs/0:
+  output:
+    output_type: stream
+    name: stdout
+    text:
+      Based on the company context, **TechStart Inc's current runway is 20 months**, which extends until **September 2025**.
+      
+      This is calculated from:
+      - **Cash in Bank**: $10M
+      - **Monthly Burn Rate**: $500,000
+      
+      The company closed a $10M Series A round in January 2024, and at the current burn rate of $500K/month, the runway extends through September 2025.
+      
+      If you'd like a more detailed financial analysis including projections based on different scenarios (hiring plans, revenue growth, etc.), I can use the `/budget-impact` slash command to provide deeper insights.

## modified /cells/4/source:
@@ -2,10 +2,11 @@ async with ClaudeSDKClient(
     options=ClaudeAgentOptions(
         model="claude-sonnet-4-5",
         cwd="chief_of_staff_agent",  # Points to subdirectory with our CLAUDE.md
+        setting_sources=["project"],  # Required to load CLAUDE.md and .claude/ settings
     )
 ) as agent:
     await agent.query("What's our current runway?")
     async for msg in agent.receive_response():
-        if hasattr(msg, "result"):
+        if isinstance(msg, ResultMessage):
             print(msg.result)
 # The agent should know from the CLAUDE.md file: $500K burn, 20 months runway

## inserted before /cells/5:
+  markdown cell:
+    source:
+      ### Feature 1: System Prompt Patterns
+      
+      **What**: Control how your agent behaves by customizing the system prompt. Three approaches: append to Claude Code's preset, provide a complete custom replacement, or use an empty prompt.
+      
+      **Why**: File-based context (CLAUDE.md, Feature 0) provide project-specific context and instructions that are automatically read by the Agent SDK when it runs in a directory, they serve as persistent “memory” for your project. On the other hand, system prompts can be better suited to shape behavior and personality.
+      
+      **How**: Use `system_prompt` to append, pass a string to replace entirely, or pass `None` for empty. Choose based on your needs: append for additions, custom for specialized agents, empty for minimal tasks.
+  code cell:
+    source:
+      # Append to Claude Code preset (preserves tool instructions)
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              setting_sources=["project"], 
+              system_prompt={
+                  "type":"preset",
+                  "preset":"claude_code", #Let's re-use Claude Code's prompt and append the simple line below
+                  "append":"You are also a financial expert that always draws analogies between financial analysis and coding"
+              },
+          )
+      ) as agent:
+          await agent.query("Off the top of your mind and without running any calculations, how healthy is our business today?")
+          async for msg in agent.receive_response():
+              if isinstance(msg, ResultMessage):
+                  print(msg.result)
+  markdown cell:
+    source:
+      If you think Claude Code's base system prompt is not really useful for your use case, you can instead pass along your system prompt as a string in the system_prompt parameter.

## modified /cells/5/source:
@@ -1,4 +1,4 @@
-### Feature 1: The Bash tool for Python Script Execution
+### Feature 2: The Bash tool for Python Script Execution
 
 **What**: The Bash tool allows your agent to (among other things) run Python scripts directly, enabling access to procedural knowledge, complex computations, data analysis and other integrations that go beyond the agent's native capabilities.
 
@@ -8,5 +8,5 @@
 1. `hiring_impact.py`: Calculates how new engineering hires affect burn rate, runway, and cash position. Essential for the `financial-analyst` subagent to model hiring scenarios against the $500K monthly burn and 20-month runway.
 2. `talent_scorer.py`: Scores candidates on technical skills, experience, culture fit, and salary expectations using weighted criteria. Core tool for the `recruiter` subagent to rank engineering candidates against TechStart's $180-220K senior engineer benchmarks.
 3. `simple_calculation.py`: Performs quick financial calculations for runway, burn rate, and quarterly metrics. Utility script for chief of staff to get instant metrics without complex modeling.
-4. `financial_forecast.py`: Models ARR growth scenarios (base/optimistic/pessimistic) given the current $2.4M ARR growing at 15% MoM.Critical for `financial-analyst` to project Series B readiness and validate the $30M fundraising target.
+4. `financial_forecast.py`: Models ARR growth scenarios (base/optimistic/pessimistic) given the current 2.4M ARR growing at 15 percent MoM. Critical for `financial-analyst` to project Series B readiness and validate the $30M fundraising target.
 5. `decision_matrix.py`: Creates weighted decision matrices for strategic choices like the SmartDev acquisition or office expansion. Helps chief of staff systematically evaluate complex decisions with multiple stakeholders and criteria.

## modified /cells/6/source:
@@ -3,6 +3,7 @@ async with ClaudeSDKClient(
         model="claude-sonnet-4-5",
         allowed_tools=["Bash", "Read"],
         cwd="chief_of_staff_agent",  # Points to subdirectory where our agent is defined
+        setting_sources=["project"],
     )
 ) as agent:
     await agent.query(
@@ -10,6 +11,6 @@ async with ClaudeSDKClient(
     )
     async for msg in agent.receive_response():
         print_activity(msg)
-        if hasattr(msg, "result"):
+        if isinstance(msg, ResultMessage):
             print("\n")
             print(msg.result)

## modified /cells/7/source:
@@ -1,10 +1,10 @@
-### Feature 2: Output Styles
+### Feature 3: Output Styles
 
 **What**: Output styles allow you to use different output styles for different audiences. Each style is defined in a markdown file.
 
 **Why**: Your agent might be used by people of different levels of expertise or they might have different priorities. Your output style can help differentiate between these segments without having to create a separate agent.
 
 **How**:
-- Configure a markdown file per style in `chief_of_staff_agent/.claude/output-styles/`. For example, check out the Executive Ouput style in `.claude/output-styles/executive.md`. Output styles are defined with a simple frontmatter including two fields: name and description. Note: Make sure the name in the frontmatter matches exactly the file's name (case sensitive)
+- Configure a markdown file per style in `chief_of_staff_agent/.claude/output-styles/`. For example, check out the Executive Output style in `.claude/output-styles/executive.md`. Output styles are defined with a simple frontmatter including two fields: name and description. Note: Make sure the name in the frontmatter matches exactly the file's name (case sensitive)
 
 > **IMPORTANT**: Output styles modify the system prompt that Claude Code has underneath, leaving out the parts focused on software engineering and giving you more control for your specific use case beyond software engineering work.

## modified /cells/8/source:
@@ -4,6 +4,7 @@ async with ClaudeSDKClient(
         model="claude-sonnet-4-5",
         cwd="chief_of_staff_agent",
         settings='{"outputStyle": "executive"}',
+        setting_sources=["project"],
     )
 ) as agent:
     await agent.query("Tell me in two sentences about your writing output style.")
@@ -17,6 +18,7 @@ async with ClaudeSDKClient(
         model="claude-sonnet-4-5",
         cwd="chief_of_staff_agent",
         settings='{"outputStyle": "technical"}',
+        setting_sources=["project"],
     )
 ) as agent:
     await agent.query("Tell me in two sentences about your writing output style.")

## modified /cells/11/source:
@@ -1,4 +1,4 @@
-### Feature 3: Plan Mode - Strategic Planning Without Execution
+### Feature 4: Plan Mode - Strategic Planning Without Execution
 
 **What**: Plan mode instructs the agent to create a detailed execution plan without performing any actions. The agent analyzes requirements, proposes solutions, and outlines steps, but doesn't modify files, execute commands, or make changes.
 
@@ -6,4 +6,4 @@
 
 **How**: Just set `permission_mode="plan"`
 
-> Note: this feature shines in Claude Code but still needs to be fully adapted for headless applications with the SDK. Namely, the agent will try calling its `ExitPlanMode()` tool, which is only relevant in the interactive mode. In this case, you can send up a follow-up query with `continue_conversation=True` for the agent to execute its plan in context.
+> Note: this feature shines in Claude Code but still needs to be fully adapted for headless applications with the SDK. Namely, the agent will try calling its `ExitPlanMode()` tool once it is done planning, which is relevant in the interactive mode. In this case, you can send up a follow-up query with `continue_conversation=True` for the agent to execute its plan in context and changing the settings for `permission_mode`. Or, if you are building your own UX on top of the SDK, you can leverage the `ExitPlanMode()` toolcall itself, as it the agent will write its plan in the only parameter for this tool: `plan`. You can take this plan, render it for the user and, if the user accepts it, send a toolresult back to the agent saying something like "The user has approved your plan, you can now get started", but remember to change the `permission_mode` to toggle off plan mode.

## deleted /cells/12/outputs/0:
-  output:
-    output_type: stream
-    name: stdout
-    text:
-      🤖 Thinking...
-      🤖 Using: Read()
-      ✓ Tool completed
-      🤖 Using: Glob()
-      ✓ Tool completed
-      🤖 Using: Read()
-      ✓ Tool completed
-      🤖 Using: Glob()
-      ✓ Tool completed
-      🤖 Using: Read()
-      ✓ Tool completed
-      🤖 Using: Read()
-      ✓ Tool completed
-      🤖 Using: Glob()
-      ✓ Tool completed
-      🤖 Thinking...
-      🤖 Using: ExitPlanMode()
-      ✓ Tool completed

## modified /cells/12/source:
@@ -4,10 +4,13 @@ async with (
         options=ClaudeAgentOptions(
             model="claude-opus-4-1",  # We're using Opus for this as Opus truly shines when it comes to planning!
             permission_mode="plan",
+            cwd="chief_of_staff_agent",
+            setting_sources=["project"],
+            disallowed_tools=["AskUserQuestion"] # Disable ask user tool that is relevant in interactive mode
         )
     ) as agent
 ):
-    await agent.query("Restructure our engineering team for AI focus.")
+    await agent.query("Based on what you know, how could we re-structure our engineering team for AI focus? Give me something simple")
     async for msg in agent.receive_response():
         print_activity(msg)
         messages.append(msg)

## deleted /cells/13/outputs/0:
-  output:
-    output_type: stream
-    name: stdout
-    text:
-      

## modified /cells/16/source:
@@ -1,4 +1,4 @@
-### Feature 4: Custom Slash Commands
+### Feature 5: Custom Slash Commands
 
 > Note: slash commands are syntactic sugar for users, not new agent capabilities
 

## modified /cells/17/source:
@@ -4,7 +4,11 @@
 
 messages = []
 async with ClaudeSDKClient(
-    options=ClaudeAgentOptions(model="claude-sonnet-4-5", cwd="chief_of_staff_agent")
+    options=ClaudeAgentOptions(
+        model="claude-sonnet-4-5",
+        cwd="chief_of_staff_agent",
+        setting_sources=["project"],
+    )
 ) as agent:
     await agent.query("/slash-command-test this is a test")
     async for msg in agent.receive_response():

## inserted before /cells/18/outputs/0:
+  output:
+    output_type: stream
+    name: stdout
+    text:
+      I'll reverse the sentence word by word for you.
+      
+      The reversed sentence is: **test a is this**

## deleted /cells/18/outputs/0:
-  output:
-    output_type: stream
-    name: stdout
-    text:
-      test a is this

## modified /cells/19/source:
@@ -1,4 +1,4 @@
-### Feature 5: Hooks - Automated Deterministic Actions
+### Feature 6: Hooks - Automated Deterministic Actions
 
 **What**: Hooks are Python scripts that you can set to execute automatically, among other events, before (pre) or after (post) specific tool calls. Hooks run **deterministically**, making them perfect for validation and audit trails.
 

## inserted before /cells/20/outputs/0:
+  output:
+    output_type: stream
+    name: stdout
+    text:
+      🤖 Thinking...
+      🤖 Using: Write()
+      ✓ Tool completed
+      🤖 Thinking...

## deleted /cells/20/outputs/0:
-  output:
-    output_type: stream
-    name: stdout
-    text:
-      🤖 Thinking...
-      🤖 Using: TodoWrite()
-      ✓ Tool completed
-      🤖 Thinking...
-      🤖 Using: TodoWrite()
-      ✓ Tool completed
-      🤖 Using: Bash()
-      ✓ Tool completed
-      🤖 Thinking...
-      🤖 Using: TodoWrite()
-      ✓ Tool completed
-      🤖 Using: Bash()
-      ✓ Tool completed
-      🤖 Thinking...
-      🤖 Using: TodoWrite()
-      ✓ Tool completed
-      🤖 Using: Write()
-      ✓ Tool completed
-      🤖 Using: TodoWrite()
-      ✓ Tool completed
-      🤖 Thinking...

## modified /cells/20/source:
@@ -4,6 +4,7 @@ async with ClaudeSDKClient(
         model="claude-sonnet-4-5",
         cwd="chief_of_staff_agent",
         allowed_tools=["Bash", "Write", "Edit", "MultiEdit"],
+        setting_sources=["project", "local"],
     )
 ) as agent:
     await agent.query(

## inserted before /cells/22:
+  markdown cell:
+    source:
+      ### Feature 6.5: Hooks (Programmatic)
+      
+      **What**: Define hooks programmatically in Python code using `HookMatcher`, rather than in `.claude/hooks/` files. This gives you the same blocking/logging capabilities as file-based hooks (Feature 6), but with runtime control.
+      
+      **Why**: File-based hooks (Feature 6) are great for static configurations loaded from disk. Programmatic hooks let you create dynamic behavior based on runtime state. Use file-based for simple cases, programmatic when you need conditional logic or integration with your application state.
+      
+      **How**: Create async functions that return `permissionDecision` (for PreToolUse) or log data (for PostToolUse). Pass them to `HookMatcher` with a tool pattern.
+  code cell:
+    source:
+      from claude_agent_sdk import HookMatcher
+      from utils.track_report import track_report
+      
+      # PostToolUse hook reuses the same audit logic from Feature 6's file-based hook
+      async def audit_logger(input_data, tool_use_id, context):
+          # Reuse the track_report function from report-tracker.py (Feature 6)
+          tool_name = input_data.get("tool_name")
+          tool_input = input_data.get("tool_input", {})
+          if tool_name in ["Write", "Edit"]:
+              # Call the same tracking function used by file-based hook
+              track_report(tool_name, tool_input, {})    
+          return {}
+      
+      # PreToolUse hook blocks dangerous commands
+      async def security_check(input_data, tool_use_id, context):
+          if input_data["tool_name"] == "Bash":
+              command = input_data["tool_input"].get("command", "")
+              if "rm" in command:
+                  return {
+                      "hookSpecificOutput": {
+                          "hookEventName": "PreToolUse",
+                          "permissionDecision": "deny",
+                          "permissionDecisionReason": "Blocked: dangerous system command"
+                      }
+                  }
+          return {}
+      
+      messages = []
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              allowed_tools=["Bash", "Write"],
+              setting_sources=["project", "local"],
+              hooks={
+                  "PostToolUse": [HookMatcher(matcher="Write|Edit", hooks=[audit_logger])],
+                  "PreToolUse": [HookMatcher(matcher="Bash", hooks=[security_check])]
+              },
+          )
+      ) as agent:
+          await agent.query("Run one of your scripts with dummy data. Create a simple test file in output_reports/test.txt. Then try to delete it")
+          async for msg in agent.receive_response():
+              print_activity(msg)
+              messages.append(msg)

## modified /cells/22/source:
@@ -1,4 +1,4 @@
-### Feature 6: Subagents via Task Tool
+### Feature 7: Subagents via Task Tool
 
 **What**: The Task tool enables your agent to delegate specialized work to other subagents. These subagents each have their own instructions, tools, and expertise.
 

## modified /cells/23/outputs/0/text:
@@ -1,13 +1,5 @@
 🤖 Thinking...
 🤖 Using: Task()
-🤖 Using: Bash()
-🤖 Using: Read()
-✓ Tool completed
-✓ Tool completed
-🤖 Using: Bash()
-🤖 Using: Bash()
-✓ Tool completed
-✓ Tool completed
 🤖 Using: Read()
 🤖 Using: Read()
 🤖 Using: Read()
@@ -18,5 +10,7 @@
 ✓ Tool completed
 🤖 Using: Bash()
 ✓ Tool completed
+🤖 Using: Bash()
+✓ Tool completed
 ✓ Tool completed
 🤖 Thinking...

## modified /cells/23/source:
@@ -5,6 +5,7 @@ async with ClaudeSDKClient(
         allowed_tools=["Task"],  # this enables our Chief agent to invoke subagents
         system_prompt="Delegate financial questions to the financial-analyst subagent. Do not try to answer these questions yourself.",
         cwd="chief_of_staff_agent",
+        setting_sources=["project", "local"],
     )
 ) as agent:
     await agent.query("Should we hire 5 engineers? Analyze the financial impact.")

## inserted before /cells/24:
+  markdown cell:
+    source:
+      Here, when our main agent decides to use a subagent, it will:
+        1. Call the Task tool with parameters like:
+        ```json
+          {
+            "description": "Analyze hiring impact",
+            "prompt": "Analyze the financial impact of hiring 5 engineers...",
+            "subagent_type": "financial-analyst"
+          }
+        ```
+        2. The Task tool executes the subagent in a separate context
+        3. Return results to main Chief of Staff agent to continue processing
+  markdown cell:
+    source:
+      ### Feature 7.5: Programmatic Agent Definitions
+      
+      **What**: Define subagents in Python code using `AgentDefinition` instead of creating `.claude/agents/` files.
+      
+      **Why**: File-based agents (in `.claude/agents/`) are great for static, version-controlled configurations. Programmatic definitions let you create agents dynamically at runtime - different agents for different users, inject runtime config, or generate agents based on application state. Use file-based for standard agents, programmatic for dynamic scenarios.
+      
+      **How**: Pass `agents={}` dict to `ClaudeAgentOptions` with `AgentDefinition(description, prompt, tools, model)` objects. Agent gets same capabilities as file-based but defined in code.
+  code cell:
+    source:
+      from claude_agent_sdk import AgentDefinition
+      
+      messages = []
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              allowed_tools=["Task", "Bash", "Read"],
+              setting_sources=["project", "local"],
+              agents={
+                  "risk-analyzer": AgentDefinition(
+                      description="Analyzes risks and provides mitigation strategies",
+                      prompt="You are a risk analyst but for now always return a concise message saying the risk looks acceptable and we should go ahead with it, nothing else.", # dummy prompt for now
+                      tools=["Read", "Bash"],
+                      model="inherit"
+                  ),
+              },
+          )
+      ) as agent:
+          await agent.query("What's the risk of expanding to a second office? Call the risk-analyzer subagent ONLY")
+          async for msg in agent.receive_response():
+              print_activity(msg)
+              messages.append(msg)
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          🤖 Thinking...
+          🤖 Using: Task()
+          ✓ Tool completed
+          🤖 Thinking...

## inserted before /cells/25:
+  markdown cell:
+    source:
+      ### Feature 8: Session Management
+      
+      **What**: Resume or fork conversations using session IDs. Resume continues the same conversation thread, while fork creates a branch from a specific point.
+      
+      **Why**: Long-running workflows span multiple interactions. Session management lets you pause and resume work, maintain context across days, or explore different approaches from the same starting point. Critical for iterative workflows, debugging, and multi-day agent tasks.
+      
+      **How**: Capture `session_id` from `SystemMessage` with `subtype="init"`. Use `resume=session_id` with `fork_session=False` to continue, or `fork_session=True` to branch. Each forked session gets a new ID.
+  code cell:
+    source:
+      from claude_agent_sdk import SystemMessage
+      
+      # Start a research session and capture ID
+      session_id = None
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              allowed_tools=["Bash", "Read"],
+              setting_sources=["project", "local"]
+          )
+      ) as agent:
+          await agent.query("What's our current total headcount? Just give me the total number, nothing else")
+          async for msg in agent.receive_response():
+              if isinstance(msg, SystemMessage) and msg.subtype == "init":
+                  session_id = msg.data.get('session_id')
+                  print(f"Session started: {session_id}")
+              if isinstance(msg, ResultMessage):
+                  print(f"Result: {msg.result}")
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          Session started: 03a738d3-07c3-4e19-9273-a183121cbd41
+          Result: 50
+  code cell:
+    source:
+      # Resume session (continue original conversation)
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              allowed_tools=["Bash", "Read"],
+              setting_sources=["project", "local"],
+              resume=session_id,
+              fork_session=False  # Continue same session
+          )
+      ) as agent:
+          await agent.query("What is the breakdown?")
+          async for msg in agent.receive_response():
+              if isinstance(msg, ResultMessage):
+                  print(f"Result: {msg.result}")
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          Result: **Engineering**: 25 (50%)
+          - Backend: 12
+          - Frontend: 8
+          - DevOps/SRE: 5
+          
+          **Sales & Marketing**: 12 (24%)
+          
+          **Product**: 5 (10%)
+          
+          **Operations**: 5 (10%)
+          
+          **Executive**: 3 (6%)
+  code cell:
+    source:
+      # Fork session (create new branch from original)  
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              cwd="chief_of_staff_agent",
+              allowed_tools=["Bash", "Read"],
+              setting_sources=["project", "local"],
+              resume=session_id,
+              fork_session=True  # New session ID, branches from original
+          )
+      ) as agent:
+          await agent.query("How many in Operations?")
+          async for msg in agent.receive_response():
+              if isinstance(msg, ResultMessage):
+                  print(f"Result: {msg.result}")
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          Result: 5

## deleted /cells/25:
-  markdown cell:
-    source:
-      Here, when our main agent decides to use a subagent, it will:
-        1. Call the Task tool with parameters like:
-        ```json
-          {
-            "description": "Analyze hiring impact",
-            "prompt": "Analyze the financial impact of hiring 5 engineers...",
-            "subagent_type": "financial-analyst"
-          }
-        ```
-        2. The Task tool executes the subagent in a separate context
-        3. Return results to main Chief of Staff agent to continue processing

## modified /cells/27/source:
@@ -1,10 +1,11 @@
 Let's now put everything we've seen together. We will ask our agent to determine the financial impact of hiring 3 senior engineers and write their insights to `output_reports/hiring_decision.md`. This demonstrates all the features seen above:
 - **Bash Tool**: Used to execute the `hiring_impact.py` script to determine the impact of hiring new engineers
+- **Custom System Prompt**: We use a custom system prompt rather than appending to Claude Code's preset prompt.
 - **Memory**: Reads `CLAUDE.md` in directory as context to understand the current budgets, runway, revenue and other relevant information
 - **Output style**: Different output styles, defined in `chief_of_staff_agent/.claude/output-styles`
 - **Custom Slash Commands**: Uses the shortcut `/budget-impact` that expands to full prompt defined in `chief_of_staff_agent/.claude/commands`
-- **Subagents**: Our `/budget_impact` command guides the chief of staff agent to invoke the financial-analyst subagent defined in `chief_of_staff_agent/.claude/agents` 
-- **Hooks**: Hooks are defined in `chief_of_staff_agent/.claude/hooks` and configured in `chief_of_staff_agent/.claude/settings.local.json`
+- **File Based Subagents**: Our `/budget_impact` command guides the chief of staff agent to invoke the financial-analyst subagent defined in `chief_of_staff_agent/.claude/agents` 
+- **File Based Hooks**: Hooks are defined in `chief_of_staff_agent/.claude/hooks` and configured in `chief_of_staff_agent/.claude/settings.local.json`
     - If one of our agents is updating the financial report, the hook should log this edit/write activity in the `chief_of_staff_agent/audit/report_history.json` logfile
     - If the financial analyst subagent will invoke the `hiring_impact.py` script, this will be logged in `chief_of_staff_agent/audit/tool_usage_log.json` logfile
 
@@ -12,6 +13,6 @@ Let's now put everything we've seen together. We will ask our agent to determine
 
 To have this ready to go, we have encapsulated the agent loop in a python file, similar to what we did in the previous notebook. Check out the agent.py file in the `chief_of_staff_agent` subdirectory. 
 
-All in all, our `send_query()` function takes in 4 parameters (prompt, continue_conversation, permission_mode, and output_style), everything else is set up in the agent file, namely: system prompt, max turns, allowed tools, and the working directory.
+All in all, our `send_query()` function takes in 6 possible parameters (user prompt/query, continue_conversation, resume, fork_session, permission_mode, and output_style), everything else is set up in the agent file, namely: system prompt, max turns, allowed tools, disallowed tools, and the working directory.
 
 To better visualize how this all comes together, check out these [flow and architecture diagrams that Claude made for us :)](./chief_of_staff_agent/flow_diagram.md)

## inserted before /cells/28:
+  markdown cell:
+    source:
+      **Note on programmatic hooks (Feature 6.5) and agents (Feature 7.5)**: These are demonstrated individually but not included in the agent.py module. File-based configurations (.claude/hooks/ and .claude/agents/) work great for most use cases and can be easier to maintain. Use programmatic definitions only when you need runtime logic - for example, different hook behavior based on user role, or dynamic agent creation based on application state.
+  code cell:
+    source:
+      from chief_of_staff_agent.agent import send_query
+      from claude_agent_sdk import SystemMessage, ResultMessage
+      
+      session_id = None
+      result, messages = await send_query(
+          "/budget-impact hiring 5 senior engineers. Save insights by using the Write tool to write a file in output_reports/hiring_decision.md",
+          output_style="executive",
+      )
+      
+      # Capture session ID
+      for msg in messages:
+          if isinstance(msg, SystemMessage) and msg.subtype == "init":
+              session_id = msg.data.get('session_id')
+              print(f"\n📌 Session captured: {session_id}")
+      
+      # Resume the session with a follow-up question (Feature 8)
+      if session_id:
+          result2, messages2 = await send_query(
+              "What if we only get 2 instead?",
+              resume=session_id,
+              fork_session=False  # Continue same session
+          )
+          print(f"\nFollow-up result: {result2 if result2 else 'No result'}...")
+      
+      # The agent.py module integrates:
+      # ✅ Feature 0: CLAUDE.md memory
+      # ✅ Feature 2: Bash tool (runs scripts)
+      # ✅ Feature 3: Output styles
+      # ✅ Feature 5: Slash commands
+      # ✅ Feature 6: File-based hooks (audit logging)
+      # ✅ Feature 7: Subagents (financial-analyst)
+      # ✅ Feature 8: Session management (resume/fork)
+      # ✅ disallowed_tools: Blocks WebFetch by default
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          🤖 Thinking...
+          🤖 Using: Task()
+          🤖 Using: Read()
+          🤖 Using: Read()
+          🤖 Using: Read()
+          🤖 Using: Bash()
+          ✓ Tool completed
+          ✓ Tool completed
+          ✓ Tool completed
+          ✓ Tool completed
+          🤖 Using: Bash()
+          ✓ Tool completed
+          🤖 Using: Bash()
+          ✓ Tool completed
+          ✓ Tool completed
+          🤖 Thinking...
+          🤖 Using: Write()
+          ✓ Tool completed
+          🤖 Thinking...
+          
+          📌 Session captured: 661d58ee-e70a-4735-983a-2c96db7e4f2a
+          🤖 Thinking...
+          🤖 Using: Task()
+          🤖 Using: Bash()
+          🤖 Using: Read()
+          🤖 Using: Read()
+          ✓ Tool completed
+          ✓ Tool completed
+          ✓ Tool completed
+          🤖 Using: Bash()
+          ✓ Tool completed
+          ✓ Tool completed
+          🤖 Thinking...
+          
+          Follow-up result: Perfect! Here's the quick summary of hiring **only 2 senior engineers** instead of 5:
+          
+          ## 📊 Hiring 2 Engineers: Financial Impact
+          
+          ### The Numbers
+          | Metric | 2 Engineers | 5 Engineers | Savings |
+          |--------|-------------|-------------|---------|
+          | **Monthly Burn** | $543,333 | $608,333 | **-$65K/month** |
+          | **Runway** | 18.4 months | 16.4 months | **+2 months** |
+          | **Runway End Date** | May 2025 | March 2025 | +2 months buffer |
+          | **Annual Cost** | $520,000 | $1,300,000 | **-$780K savings** |
+          | **One-Time Costs** | $70,000 | $175,000 | -$105K |
+          
+          ### 🎯 Key Advantages of 2 Engineers
+          
+          ✅ **Better Financial Safety:** 18.4-month runway (much healthier for Series B)  
+          ✅ **Capital Efficiency:** 25% better ROI per engineer  
+          ✅ **Flexibility:** Saves $65K/month for other investments  
+          ✅ **Lower Risk:** Smaller commitment if market conditions worsen  
+          
+          ### ⚠️ Trade-offs
+          
+          ❌ **Slower Velocity:** +6% productivity vs +15% with 5 engineers  
+          ❌ **Q2 Feature Risk:** 60-65% confidence on hitting deadline (vs 85% with 5)  
+          ❌ **Need Scope Reduction:** Must deliver MVP, not full feature  
+          ❌ **Team Pressure:** Existing engineers still stretched  
+          
+          ### 💡 Recommended 2 Roles to Hire
+          
+          **#1 Priority: Senior Backend ML Engineer** ($200-220K)
+          - Critical for AI code review feature (your Q2 priority)
+          - Core product differentiation
+          - 6-8 week ramp time
+          
+          **#2 Priority: Senior Full-Stack Engineer** ($190-210K)
+          - Versatile across frontend and backend
+          - Faster ramp (4-6 weeks)
+          - Maximum flexibility
+          
+          ### 🎬 Strategic Recommendation
+          
+          **HIRE 2 NOW + 3 LATER APPROACH:**
+          
+          **Immediate (June 2024):**
+          - Hire 2 senior engineers
+          - Redefine Q2 feature as "MVP + soft launch"
+          - Plan for full release in Q3
+          
+          **Q3 2024 (Conditional):**
+          - Hire 3 more engineers IF:
+            - ARR reaches $3.5M+ by August
+            - AI feature shows strong customer traction  
+            - Revenue growth stays at 15%+ MoM
+          
+          ### 🤔 Is 2 Enough?
+          
+          **Short answer:** Yes, IF you're willing to:
+          1. Reduce Q2 feature scope to MVP
+          2. Do a "soft launch" with 5-10 beta customers
+          3. Accept slower overall velocity
+          4. Monitor closely and hire more in Q3 if needed
+          
+          **This is the RIGHT choice if:**
+          - You want maximum financial prudence
+          - Series B timeline is uncertain
+          - You prefer to "earn the right" to hire more with revenue results
+          
+          **Stick with 5 if:**
+          - Q2 full launch is non-negotiable
+          - You're highly confident in Series B closing Q4
+          - Competitive pressure is extreme
+          
+          ### 💰 Bottom Line
+          
+          Hiring 2 engineers is a **conservative, low-risk approach** that maintains financial flexibility while still making progress. You save $780K annually and extend runway by 2 months, which could be critical if Series B takes longer than expected.
+          
+          **My recommendation:** Start with 2 now, prove the ROI, then accelerate to 5 total in Q3 when you have more revenue data.
+          
+          Would you like me to update the hiring decision report with this 2-engineer scenario as the primary recommendation?...
+  code cell:
+    source:
+      visualize_conversation(messages)

## deleted /cells/28:
-  code cell:
-    source:
-      from chief_of_staff_agent.agent import send_query
-      
-      result, messages = await send_query(
-          "/budget-impact hiring 3 senior engineers. Save your insights by updating the 'hiring_decision.md' file in /output_reports or creating a new file there",
-          # permission_mode="plan", # Enable this to use planning mode
-          output_style="executive",
-      )
-    outputs:
-      output 0:
-        output_type: stream
-        name: stdout
-        text:
-          🤖 Thinking...
-          🤖 Using: Task()
-          🤖 Using: Bash()
-          ✓ Tool completed
-          🤖 Using: Read()
-          🤖 Using: Read()
-          🤖 Using: Read()
-          ✓ Tool completed
-          ✓ Tool completed
-          ✓ Tool completed
-          ✓ Tool completed
-          🤖 Thinking...
-          🤖 Using: Write()
-          ✓ Tool completed
-          🤖 Thinking...

## modified /cells/29/source:
-  visualize_conversation(messages)
+  visualize_conversation(messages2)

## modified /cells/30/source:
@@ -1 +1,3 @@
-## ConclusionWe've demonstrated how the Claude Code SDK enables you to build sophisticated multi-agent systems with enterprise-grade features. Starting from basic script execution with the Bash tool, we progressively introduced advanced capabilities including persistent memory with CLAUDE.md, custom output styles for different audiences, strategic planning mode, slash commands for user convenience, compliance hooks for guardrailing, and subagent coordination for specialized tasks.By combining these features, we created an AI Chief of Staff capable of handling complex executive decision-making workflows. The system delegates financial analysis to specialized subagents, maintains audit trails through hooks, adapts communication styles for different stakeholders, and provides actionable insights backed by data-driven analysis.This foundation in advanced agentic patterns and multi-agent orchestration prepares you for building production-ready enterprise systems. In the next notebook, we'll explore how to connect our agents to external services through Model Context Protocol (MCP) servers, dramatically expanding their capabilities beyond the built-in tools.Next: [02_The_observability_agent.ipynb](02_The_observability_agent.ipynb) - Learn how to extend your agents with custom integrations and external data sources through MCP.
+## Conclusion
+
+We've demonstrated how the Claude Agent SDK enables you to build sophisticated multi-agent systems with enterprise-grade features. Starting from basic script execution with the Bash tool, we progressively introduced advanced capabilities including persistent memory with CLAUDE.md, custom output styles for different audiences, strategic planning mode, slash commands for user convenience, compliance hooks for guardrailing, and subagent coordination for specialized tasks.By combining these features, we created an AI Chief of Staff capable of handling complex executive decision-making workflows. The system delegates financial analysis to specialized subagents, maintains audit trails through hooks, adapts communication styles for different stakeholders, and provides actionable insights backed by data-driven analysis.This foundation in advanced agentic patterns and multi-agent orchestration prepares you for building production-ready enterprise systems. In the next notebook, we'll explore how to connect our agents to external services through Model Context Protocol (MCP) servers, dramatically expanding their capabilities beyond the built-in tools.Next: [02_The_observability_agent.ipynb](02_The_observability_agent.ipynb) - Learn how to extend your agents with custom integrations and external data sources through MCP.

📓 claude_agent_sdk/02_The_observability_agent.ipynb

View diff
nbdiff claude_agent_sdk/02_The_observability_agent.ipynb (b29ec6f109c0379fa2eb620611a3d504e28fba09) claude_agent_sdk/02_The_observability_agent.ipynb (f93243bff527cddeafa7d668575f74714ee9a3cc)
--- claude_agent_sdk/02_The_observability_agent.ipynb (b29ec6f109c0379fa2eb620611a3d504e28fba09)  (no timestamp)
+++ claude_agent_sdk/02_The_observability_agent.ipynb (f93243bff527cddeafa7d668575f74714ee9a3cc)  (no timestamp)
## modified /cells/0/source:
@@ -4,4 +4,4 @@ from typing import Any
 from dotenv import load_dotenv
 from utils.agent_visualizer import print_activity
 
-from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
+from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, ResultMessage

## modified /cells/6/source:
@@ -1 +1,4 @@
-print(f"\nResult:\n{messages[-1].result}")
+from claude_agent_sdk import ResultMessage
+
+result = next((m.result for m in reversed(messages) if isinstance(m, ResultMessage)), None)
+print(f"\nResult:\n{result}")

## modified /cells/12/source:
@@ -1 +1,4 @@
-print(f"\nResult:\n{messages[-1].result}")
+from claude_agent_sdk import ResultMessage
+
+result = next((m.result for m in reversed(messages) if isinstance(m, ResultMessage)), None)
+print(f"\nResult:\n{result}")

## inserted before /cells/13:
+  markdown cell:
+    source:
+      ### 3. Custom Tools with SDK MCP Servers
+      
+      **What**: SDK MCP Servers let you create custom tools directly in Python using the `@tool` decorator and `create_sdk_mcp_server()`. Unlike external MCP servers (Git, GitHub) that run as separate processes, SDK MCP servers run in-process—same Python runtime as your agent.
+      
+      **Why**: External MCP servers (like Git and GitHub above) are great for integrating existing systems, but they have overhead: subprocess communication, Docker containers, network calls. SDK MCP servers give you:
+      - **Zero subprocess overhead**: Runs in your Python process
+      - **Direct state access**: Can access your application's data, databases, and objects
+      - **Simpler deployment**: No external dependencies or Docker
+      - **Type safety**: Full Python type hints and IDE support
+      - **Custom logic**: Any Python code you can write becomes a tool
+      
+      **When to use**:
+      - **External MCP**: Existing systems (GitHub API, databases, services you don't control)
+      - **SDK MCP**: Custom logic, internal APIs, data transformations, calculations you control
+      
+      **How**: Use `@tool` decorator to define tools, then `create_sdk_mcp_server()` to bundle them. The agent accesses them via `mcp__servername__toolname` in `allowed_tools`.
+      
+      Below we create a monitoring server with two custom tools:
+  code cell:
+    source:
+      from claude_agent_sdk import create_sdk_mcp_server, tool
+      
+      # Define custom tools with @tool decorator
+      # @tool(name, description, input_schema)
+      # Returns dict with content array (MCP tool response format)
+      
+      @tool(
+          "check_service_health",
+          "Check health status of a service",
+          {"service_name": str}  # Input schema: simple type mapping
+      )
+      async def check_service_health(args: dict) -> dict:
+          # In production, this would call real health endpoints
+          # For demo, we use mock data
+          services = {
+              "api": {"status": "healthy", "latency_ms": 45},
+              "database": {"status": "healthy", "latency_ms": 12},
+              "cache": {"status": "degraded", "latency_ms": 203}
+          }
+          
+          service = args.get("service_name", "").lower()
+          if service in services:
+              info = services[service]
+              # Return MCP tool response format
+              return {
+                  "content": [{
+                      "type": "text",
+                      "text": f"{service}: {info['status']} (latency: {info['latency_ms']}ms)"
+                  }]
+              }
+          return {
+              "content": [{
+                  "type": "text",
+                  "text": f"Service '{service}' not found"
+              }]
+          }
+      
+      @tool(
+          "calculate_error_rate",
+          "Calculate error rate percentage",
+          {"errors": int, "total_requests": int}  # Multiple parameters with types
+      )
+      async def calculate_error_rate(args: dict) -> dict:
+          errors = args["errors"]
+          total = args["total_requests"]
+          rate = (errors / total * 100) if total > 0 else 0
+          
+          # Add business logic - determine severity
+          severity = "critical" if rate > 5 else "warning" if rate > 1 else "normal"
+          
+          return {
+              "content": [{
+                  "type": "text",
+                  "text": f"Error rate: {rate:.2f}% ({errors}/{total}) - Severity: {severity}"
+              }]
+          }
+      
+      # Bundle tools into an SDK MCP server
+      # This creates an in-process MCP server (no subprocess\!)
+      monitoring_server = create_sdk_mcp_server(
+          name="monitoring",  # Server name
+          version="1.0.0",
+          tools=[check_service_health, calculate_error_rate]  # List of @tool decorated functions
+      )
+      
+      # Now monitoring_server can be passed to mcp_servers option
+      # Tools become: mcp__monitoring__check_service_health
+      #               mcp__monitoring__calculate_error_rate
+  code cell:
+    source:
+      # Use SDK MCP server in agent
+      messages = []
+      async with ClaudeSDKClient(
+          options=ClaudeAgentOptions(
+              model="claude-sonnet-4-5",
+              mcp_servers={"monitor": monitoring_server},
+              allowed_tools=["mcp__monitor"]  # Enable all monitoring tools
+          )
+      ) as agent:
+          await agent.query(
+              "Check health of all services (api, database, cache) and calculate error rate if we had 47 errors out of 1200 requests"
+          )
+          async for msg in agent.receive_response():
+              print_activity(msg)
+              messages.append(msg)
+              if isinstance(msg, ResultMessage):
+                  print(f"Result: {msg.result}")
+    outputs:
+      output 0:
+        output_type: stream
+        name: stdout
+        text:
+          🤖 Thinking...
+          🤖 Using: mcp__monitor__check_service_health()
+          🤖 Using: mcp__monitor__check_service_health()
+          🤖 Using: mcp__monitor__check_service_health()
+          🤖 Using: mcp__monitor__calculate_error_rate()
+          ✓ Tool completed
+          ✓ Tool completed
+          ✓ Tool completed
+          ✓ Tool completed
+          🤖 Thinking...
+          Result: ## Service Health Status
+          
+          - **api**: healthy (latency: 45ms)
+          - **database**: healthy (latency: 12ms)
+          - **cache**: degraded (latency: 203ms)
+          
+          ## Error Rate Analysis
+          
+          **Error rate: 3.92%** (47 errors out of 1200 requests)
+          - Severity: warning
+          
+          The cache service is showing degraded performance with elevated latency (203ms). The error rate of 3.92% has reached warning levels and may need attention.

## modified /cells/15/source:
@@ -1 +1,4 @@
-print(f"\nResult:\n{messages[-1].result}")
+from claude_agent_sdk import ResultMessage
+
+result = next((m.result for m in reversed(messages) if isinstance(m, ResultMessage)), None)
+print(f"\nResult:\n{result}")

## modified /cells/21/source:
@@ -1,28 +1,30 @@
 ## Conclusion
 
-We've demonstrated how the Claude Code SDK enables seamless integration with external systems through the Model Context Protocol (MCP). Starting with local Git operations through the Git MCP server, we progressively expanded to full GitHub platform integration with access to over 100 GitHub-specific tools. This transformed our agent from a local assistant into a powerful observability system capable of monitoring workflows, analyzing CI/CD failures, and providing actionable insights for production systems.
+We've demonstrated how the Claude Agent SDK enables powerful agent systems from basic integration to production deployment.
 
-By connecting MCP servers to our agent, we created an autonomous observability system that monitors GitHub Actions workflows, distinguishes between real failures and security restrictions, and provides detailed analysis of test failures. The system demonstrates how agents can actively participate in your DevOps workflow, moving from passive monitoring to intelligent incident response.
+**What You've Learned:**
 
-This concludes, for now, our journey through the Claude Code SDK tutorial series. We've progressed from simple research agents to sophisticated multi-agent orchestration, and finally to external system integration through MCP. Together, these patterns provide the foundation for building production-ready agentic systems that can handle real-world complexity while maintaining governance, compliance, and observability.
-
-### What You've Learned Across All Notebooks
-
-**From Notebook 00 (Research Agent)**
+**From Notebook 00 (Research Agent)**:
 - Core SDK fundamentals with `query()` and `ClaudeSDKClient`
 - Basic tool usage with WebSearch and Read
 - Simple agent loops and conversation management
 
-**From Notebook 01 (Chief of Staff)**
-- Advanced features: memory, output styles, planning mode
+**From Notebook 01 (Chief of Staff)**:
 - Multi-agent coordination through subagents
 - Governance through hooks and custom commands
 - Enterprise-ready agent architectures
+- Custom permissions and programmatic agents
+
+**From Notebook 02 (Advanced Topics)**:
+- **External Integration**: Git and GitHub MCP servers for system integration
+- **Custom Tools**: SDK MCP servers for in-process tool creation
+
+You now have the foundation to build production-ready agent systems that integrate with external tools, deploy reliably, handle real-world scale, and extend with reusable capabilities.
 
-**From Notebook 02 (Observability Agent)**
-- External system integration via MCP servers
-- Real-time monitoring and incident response
-- Production workflow automation
-- Scalable agent deployment patterns
+**Next Steps**:
+- Explore [Claude Agent SDK documentation](https://docs.claude.com/en/api/agent-sdk)
+- Browse the [Plugin Hub](https://github.com/jeremylongshore/claude-code-plugins-plus) (227+ plugins)
+- Create your own skills and share with the community
+- Deploy agents to production with confidence
 
-The complete implementations for all three agents are available in their respective directories (`research_agent/`, `chief_of_staff_agent/`, `observability_agent/`), ready to serve as inspiration for integrations into your production systems.
+The complete agent implementations in `research_agent/`, `chief_of_staff_agent/`, and `observability_agent/` directories provide production-ready starting points for your projects.

Generated by nbdime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants