diff --git a/.claude/commands/link-review.md b/.claude/commands/link-review.md index f46a43fb..bf668396 100644 --- a/.claude/commands/link-review.md +++ b/.claude/commands/link-review.md @@ -28,4 +28,4 @@ Provide a clear summary with: If all links look good, provide a brief confirmation. -**IMPORTANT: Post your review as a comment on the pull request using the command: `gh pr comment ${{ github.event.pull_request.number }} --body "your review content"`** \ No newline at end of file +**IMPORTANT: Post your review as a comment on the pull request using the command: `gh pr comment $PR_NUMBER --body "your review content"`** \ No newline at end of file diff --git a/.claude/commands/model-check.md b/.claude/commands/model-check.md index 9b657f09..ae64f1fe 100644 --- a/.claude/commands/model-check.md +++ b/.claude/commands/model-check.md @@ -16,4 +16,4 @@ Then check: Provide clear, actionable feedback on any issues found. -**IMPORTANT: Post your findings as a comment on the pull request using the command: `gh pr comment ${{ github.event.pull_request.number }} --body "your findings"`** \ No newline at end of file +**IMPORTANT: Post your findings as a comment on the pull request using the command: `gh pr comment $PR_NUMBER --body "your findings"`** \ No newline at end of file diff --git a/.claude/commands/notebook-review.md b/.claude/commands/notebook-review.md index 3da7d726..1bf40648 100644 --- a/.claude/commands/notebook-review.md +++ b/.claude/commands/notebook-review.md @@ -41,4 +41,4 @@ Provide a clear summary with: - āš ļø Suggestions for improvement - āŒ Critical issues that must be fixed -**IMPORTANT: Post your review as a comment on the pull request using the command: `gh pr comment ${{ github.event.pull_request.number }} --body "your review"`** \ No newline at end of file +**IMPORTANT: Post your review as a comment on the pull request using the command: `gh pr comment $PR_NUMBER --body "your review"`** \ No newline at end of file diff --git a/.github/workflows/claude-link-review.yml b/.github/workflows/claude-link-review.yml index 1aad2870..f84a929e 100644 --- a/.github/workflows/claude-link-review.yml +++ b/.github/workflows/claude-link-review.yml @@ -27,4 +27,8 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} - prompt: "/link-review" \ No newline at end of file + prompt: "/link-review" + claude_args: | + --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(echo:*),Read,Glob,Grep,WebFetch" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/.github/workflows/claude-model-check.yml b/.github/workflows/claude-model-check.yml index b7aef578..e23464bf 100644 --- a/.github/workflows/claude-model-check.yml +++ b/.github/workflows/claude-model-check.yml @@ -26,4 +26,8 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} - prompt: "/model-check" \ No newline at end of file + prompt: "/model-check" + claude_args: | + --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(echo:*),Read,Glob,Grep,WebFetch" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/.github/workflows/claude-notebook-review.yml b/.github/workflows/claude-notebook-review.yml index 3bf26520..6a38477c 100644 --- a/.github/workflows/claude-notebook-review.yml +++ b/.github/workflows/claude-notebook-review.yml @@ -27,4 +27,8 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} - prompt: "/notebook-review" \ No newline at end of file + prompt: "/notebook-review" + claude_args: | + --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(echo:*),Read,Glob,Grep,WebFetch" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/.github/workflows/notebook-quality.yml b/.github/workflows/notebook-quality.yml index eeb46b3e..d9045e24 100644 --- a/.github/workflows/notebook-quality.yml +++ b/.github/workflows/notebook-quality.yml @@ -41,9 +41,46 @@ jobs: uv run ruff format **/*.ipynb --check || true - name: Validate notebook structure + id: validate run: | - uv run python scripts/validate_notebooks.py - + uv run python scripts/validate_notebooks.py | tee validation_output.txt + # Check if validation found issues + if grep -q "āŒ" validation_output.txt; then + echo "has_issues=true" >> $GITHUB_OUTPUT + exit 1 + else + echo "has_issues=false" >> $GITHUB_OUTPUT + fi + continue-on-error: true + + - name: Summarize validation issues with Claude + if: github.event_name == 'pull_request' && steps.validate.outputs.has_issues == 'true' + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + prompt: | + The notebook validation found these issues: + + ``` + $(cat validation_output.txt) + ``` + + Create a helpful PR comment that: + - Summarizes the validation issues found + - Groups similar issues together (e.g., "7 notebooks have empty cells") + - Explains why empty cells are problematic and how to fix them (delete them or add content) + - If there are error outputs, explain they should be cleared before committing + - Uses friendly, constructive language + - Includes specific notebook names and cell numbers for reference + + Format as a nice GitHub comment with markdown. Use emoji sparingly for clarity. + Post using: gh pr comment $PR_NUMBER --body "your comment" + claude_args: | + --allowedTools "Bash(gh pr comment:*),Bash(cat:*),Read" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + # Only run API tests on main branch or for maintainers (costs money) - name: Execute notebooks (API Testing) if: | diff --git a/claude_code_sdk/.env.example b/claude_code_sdk/.env.example new file mode 100644 index 00000000..0c5921c9 --- /dev/null +++ b/claude_code_sdk/.env.example @@ -0,0 +1,9 @@ +# GitHub Personal Access Token +# Required for the GitHub MCP server in 02_The_observability_agent.ipynb +# Create a token at: https://github.com/settings/tokens +GITHUB_TOKEN="your-github-personal-access-token-here" + +# Anthropic API Key +# Required for using Claude SDK +# Get your key at: https://console.anthropic.com/settings/keys +ANTHROPIC_API_KEY="sk-ant-api03-your-api-key-here" diff --git a/claude_code_sdk/.gitignore b/claude_code_sdk/.gitignore new file mode 100644 index 00000000..42d65ee5 --- /dev/null +++ b/claude_code_sdk/.gitignore @@ -0,0 +1,26 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +.env +.python-version + +# Claude Code settings +.claude-code/ +/CLAUDE.md + +# Package manager lock files (optional for tutorials) +uv.lock + +# Jupyter notebook checkpoints +.ipynb_checkpoints/ +*.ipynb_checkpoints/ + +# macOS +.DS_Store diff --git a/claude_code_sdk/00_The_one_liner_research_agent.ipynb b/claude_code_sdk/00_The_one_liner_research_agent.ipynb new file mode 100644 index 00000000..c10c52f2 --- /dev/null +++ b/claude_code_sdk/00_The_one_liner_research_agent.ipynb @@ -0,0 +1,321 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "94449849", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dotenv import load_dotenv\n", + "from utils.agent_visualizer import print_activity\n", + "\n", + "from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient, query\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "0d4a77a4", + "metadata": {}, + "source": [ + "# 00 - The One-Liner Research Agent\n", + "\n", + "PREFACE: We highly recommend reading [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) or [How we built our multi-agent research system](https://www.anthropic.com/engineering/built-multi-agent-research-system) in case you haven't. They are great reads and we will assume some basic understanding of agents! \n", + "\n", + "In this notebook we build our own (re)search agent, which is inherently a great use-case because of a few reasons:\n", + "- The input to our system is not sufficient to produce an output, meaning there needs to be interaction with external systems (e.g., the internet)\n", + "- There is no predefined workflow we can use since it is unclear what the agent will discover during its research\n", + "\n", + "Instead, a research agent requires the flexibility to explore unexpected leads and change direction based on what it finds. In its simplest form, a research agent can be an agent that simply searches the internet and summarizes it for you. \n", + "\n", + "Below, we'll implement a basic research agent with just a few lines of code. We provide Claude with exactly one tool which the Claude Code SDK contains straight out of the box: [web search tool](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/web-search-tool). \n", + "\n", + "> Check [here](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude) for a list of Claude Code's readily available tools" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b00890fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: WebSearch()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages = []\n", + "async for msg in query(\n", + " prompt=\"Research the latest trends in AI agents and give me a brief summary\",\n", + " options=ClaudeCodeOptions(model=\"claude-sonnet-4-20250514\", allowed_tools=[\"WebSearch\"]),\n", + "):\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "293437f4", + "metadata": {}, + "outputs": [], + "source": [ + "print(\n", + " f\"\\nResult:\\n{messages[-1].result if hasattr(messages[-1], 'result') and messages[-1].result else messages[-2].content[0].text}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6b888772", + "metadata": {}, + "source": [ + "And that's all it takes! Just like that we have a research agent that can go and browse the web to answer (to the best of its ability, at least) any question you throw at it.\n", + "\n", + "Note that in our query we provided the argument `options`. Here we define the configuration, the capabilities and limitations of our agent. For example, we provide our agent with the ability to search the web by passing ```allowed_tool=[\"WebSearch\"]```.\n", + "\n", + "More specifically, `allowed_tools` is a list of tools that Claude will be able to use without any approvals. The rest of the tools are still available, but Claude will ask for approval to use them. That said, certain tools like `Read` and other base read-only tools are always allowed. If you want any tool to be removed from Claude's context, add it to `disallowed_tools` instead.\n", + "\n", + "Now, to more closely inspect the actions our agent took, we have provided the ```visualize_conversation``` function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d7c6d90", + "metadata": {}, + "outputs": [], + "source": [ + "from utils.agent_visualizer import visualize_conversation\n", + "\n", + "visualize_conversation(messages)" + ] + }, + { + "cell_type": "markdown", + "id": "22426729", + "metadata": {}, + "source": [ + "### Supercharging our agent\n", + "\n", + "So far, we have laid out a very simple (maybe naive) implementation to illustrate how you can start leveraging the SDK to build a research agent. However, there are various ways we can improve our agent to turn it production ready. Let's cover a few of them:\n", + "\n", + "1. Notice how before we only sent one query? In many systems, a human will look at the output of the system, potentially assigning a follow up task. Just like text completions, if we want to send multiple queries to the agent (e.g., 1. analyze abc, 2. make xyz based on your analysis) we would have to copy over the entire analysis context in our second query. Instead, we can **[use the ClaudeSDKClient](https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-python#1-the-claudesdkclient-class-recommended)** to maintain the conversation context for us.\n", + "\n", + "2. Another great way of steering the system is **providing a system prompt**, akin to a system prompt used for text completions. To learn how to write a good system prompt for a research agent, we recommend looking [here](https://github.com/anthropics/anthropic-cookbook/tree/main/patterns/agents/prompts).\n", + "\n", + "3. **Leveraging the `Read` tool** to enable multimodal input. This tool allows Claude to analyze charts, infographics, and complex system diagrams." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "fa4c4d8f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: WebSearch()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " cwd=\"research_agent\",\n", + " system_prompt=\"You are a research agent specialized in AI\",\n", + " allowed_tools=[\"WebSearch\", \"Read\"],\n", + " )\n", + ") as research_agent:\n", + " await research_agent.query(\"Analyze the chart in research_agent/projects_claude.png\")\n", + " async for msg in research_agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)\n", + "\n", + " await research_agent.query(\"Use a single websearch to investigate the insights from the chart.\")\n", + " async for msg in research_agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7971eae4-3ff1-48d8-99ef-fecca7332163", + "metadata": {}, + "outputs": [], + "source": [ + "visualize_conversation(messages)" + ] + }, + { + "cell_type": "markdown", + "id": "38256581", + "metadata": {}, + "source": [ + "### The Research Agent leaves Jupyter\n", + "\n", + "Finally, to be able to use the agent outside our notebook, we must put it in a Python script. A lightweight implementation of our research agent can be found in `research_agent/agent.py`. We define three functions:\n", + "- `print_activity()` - Shows what the agent is doing in real-time\n", + "- `get_activity_text()` - Extracts activity text for custom handlers\n", + "- `send_query()` - Main function for sending and handlingqueries with built-in activity display\n", + "\n", + "This agent can now be used in any Python script!" + ] + }, + { + "cell_type": "markdown", + "id": "e220b5c7-463b-4171-b687-b1ec974958de", + "metadata": {}, + "source": [ + "First an example to test a one-off query to the agent:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c2ca449-7a36-4b67-af47-fdb68fb3e36b", + "metadata": {}, + "outputs": [], + "source": [ + "from research_agent.agent import send_query\n", + "\n", + "result = await send_query(\"What is the Claude Code SDK? Only do one websearch and be concise\")\n", + "print(f\"\\nResult: {result}\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "466155ec-9f54-49d4-83cb-00032b077147", + "metadata": {}, + "source": [ + "Now we test out a multi-turn conversation that reuses the same conversation:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "38ba1eda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: WebSearch()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "\n", + "-----\n", + "\n", + "Initial research: Anthropic is an AI safety and research company founded in 2021 by former OpenAI researchers, including siblings Dario and Daniela Amodei. The company develops Claude, a family of large language models (LLMs) designed to be helpful, harmless, and honest.\n", + "\n", + "**Key points:**\n", + "- **Mission**: Build reliable, interpretable, and steerable AI systems with a focus on AI safety\n", + "- **Main product**: Claude AI assistant (which you're currently using!)\n", + "- **Structure**: Public benefit corporation balancing profit with humanity's long-term benefit\n", + "- **Funding**: Backed by major investments from Amazon ($8B total) and Google ($2B)\n", + "- **Focus areas**: AI safety, natural language processing, human feedback, and responsible AI development\n", + "\n", + "Anthropic positions itself as a \"safety-first\" AI lab, emphasizing the responsible development of AI systems to serve humanity's long-term well-being.\n", + "\n" + ] + } + ], + "source": [ + "result1 = await send_query(\"What is Anthropic? Only do one websearch and be concise\")\n", + "print(f\"\\n-----\\n\\nInitial research: {result1}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36931a9e", + "metadata": {}, + "outputs": [], + "source": [ + "# Continue the conversation to dig deeper by setting continue_conversation=True\n", + "result2 = await send_query(\n", + " \"What are some of their products?\",\n", + " continue_conversation=True,\n", + ")\n", + "print(f\"\\n-----\\n\\nFollow-up: {result2}\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "5344587c", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "We've demonstrated how the Claude Code SDK enables you to build a functional research agent in just a few lines of code. By leveraging the built-in WebSearch tool, we created an agent capable of autonomous information gathering and synthesis. We also explored how the\n", + "ClaudeSDKClient maintains conversation context across multiple queries and how to incorporate multimodal capabilities through the Read tool.\n", + "\n", + "This foundation in basic agentic workflows prepares you for more sophisticated implementations. In the next notebook, we'll advance to building a Chief of Staff agent that coordinates multiple specialized subagents, implements custom output styles for different\n", + "stakeholders, and uses hooks for governance and compliance tracking.\n", + "\n", + "Next: [01_The_chief_of_staff_agent.ipynb](01_The_chief_of_staff_agent.ipynb) - Learn how to orchestrate complex multi-agent systems with enterprise-grade features.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (cc-sdk-tutorial)", + "language": "python", + "name": "cc-sdk-tutorial" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/claude_code_sdk/01_The_chief_of_staff_agent.ipynb b/claude_code_sdk/01_The_chief_of_staff_agent.ipynb new file mode 100644 index 00000000..13ed6358 --- /dev/null +++ b/claude_code_sdk/01_The_chief_of_staff_agent.ipynb @@ -0,0 +1,672 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dotenv import load_dotenv\n", + "from utils.agent_visualizer import print_activity, visualize_conversation\n", + "\n", + "from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 01 - The Chief of Staff Agent\n", + "\n", + "#### Introduction\n", + "\n", + "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:\n", + "- **What**: what the feature is\n", + "- **Why**: what the feature can do and why you would want to use it\n", + "- **How**: a minimal implementation showing how to use it\n", + "\n", + "If you are familiar with Claude Code, you'll notice how the SDK brings feature parity and enables you to leverage all of Claude Code's capabilities in a programmatic headless manner.\n", + "\n", + "#### Scenario\n", + "\n", + "Throughout this notebook, we'll build an **AI Chief of Staff** for a 50-person startup that just raised $10M Series A. The CEO needs data-driven insights to balance aggressive growth with financial sustainability.\n", + "\n", + "Our final Chief of Staff agent will:\n", + "- **Coordinate specialized subagents** for different domains\n", + "- **Aggregate insights** from multiple sources\n", + "- **Provide executive summaries** with actionable recommendations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic Features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 0: Memory with [CLAUDE.md](https://www.anthropic.com/engineering/claude-code-best-practices)\n", + "\n", + "**What**: `CLAUDE.md` files serve as persistent memory and instructions for your agent. When present in the project directory, Claude Code automatically reads and incorporates this context when you initialize your agent.\n", + "\n", + "**Why**: Instead of repeatedly providing project context, team preferences, or standards in each interaction, you can define them once in `CLAUDE.md`. This ensures consistent behavior and reduces token usage by avoiding redundant explanations.\n", + "\n", + "**How**: \n", + "- Have a `CLAUDE.md` file in the working directory - in our example: `chief_of_staff_agent/CLAUDE.md`\n", + "- Set the `cwd` argument of your ClaudeSDKClient to point to directory of your CLAUDE.md file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " cwd=\"chief_of_staff_agent\", # Points to subdirectory with our CLAUDE.md\n", + " )\n", + ") as agent:\n", + " await agent.query(\"What's our current runway?\")\n", + " async for msg in agent.receive_response():\n", + " if hasattr(msg, \"result\"):\n", + " print(msg.result)\n", + "# The agent should know from the CLAUDE.md file: $500K burn, 20 months runway" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 1: The Bash tool for Python Script Execution\n", + "\n", + "**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.\n", + "\n", + "**Why**: Our Chief of Staff might need to process data files, run financial models or generate visualizations based on this data. These are all good scenarios for using the Bash tool.\n", + "\n", + "**How**: Have your Python scripts set-up in a place where your agent can reach them and add some context on what they are and how they can be called. If the scripts are meant for your chief of staff agent, add this context to its CLAUDE.md file and if they are meant for one your subagents, add said context to their MD files (more details on this later). For this tutorial, we added five toy examples to `chief_of_staff_agent/scripts`:\n", + "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.\n", + "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.\n", + "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.\n", + "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.\n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " allowed_tools=[\"Bash\", \"Read\"],\n", + " cwd=\"chief_of_staff_agent\", # Points to subdirectory where our agent is defined\n", + " )\n", + ") as agent:\n", + " await agent.query(\n", + " \"Use your simple calculation script with a total runway of 2904829 and a monthly burn of 121938.\"\n", + " )\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " if hasattr(msg, \"result\"):\n", + " print(\"\\n\")\n", + " print(msg.result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 2: Output Styles\n", + "\n", + "**What**: Output styles allow you to use different output styles for different audiences. Each style is defined in a markdown file.\n", + "\n", + "**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.\n", + "\n", + "**How**:\n", + "- 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)\n", + "\n", + "> **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." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages_executive = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " cwd=\"chief_of_staff_agent\",\n", + " settings='{\"outputStyle\": \"executive\"}',\n", + " )\n", + ") as agent:\n", + " await agent.query(\"Tell me in two sentences about your writing output style.\")\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages_executive.append(msg)\n", + "\n", + "messages_technical = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " cwd=\"chief_of_staff_agent\",\n", + " settings='{\"outputStyle\": \"technical\"}',\n", + " )\n", + ") as agent:\n", + " await agent.query(\"Tell me in two sentences about your writing output style.\")\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages_technical.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(messages_executive[-1].result)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(messages_technical[-1].result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 3: Plan Mode - Strategic Planning Without Execution\n", + "\n", + "**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.\n", + "\n", + "**Why**: Complex tasks benefit from upfront planning to reduce errors, enable review and improve coordination. After the planning phase, the agent will have a red thread to follow throughout its execution.\n", + "\n", + "**How**: Just set `permission_mode=\"plan\"`\n", + "\n", + "> 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." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Glob()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Glob()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Glob()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: ExitPlanMode()\n", + "āœ“ Tool completed\n" + ] + } + ], + "source": [ + "messages = []\n", + "async with (\n", + " ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-opus-4-1-20250805\", # We're using Opus for this as Opus truly shines when it comes to planning!\n", + " permission_mode=\"plan\",\n", + " )\n", + " ) as agent\n", + "):\n", + " await agent.query(\"Restructure our engineering team for AI focus.\")\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(messages[-1].result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As mentioned above, the agent will stop after creating its plan, if you want it to execute on its plan, you need to send a new query with `continue_conversation=True` and removing `permission_mode=\"plan\"` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 4: Custom Slash Commands\n", + "\n", + "> Note: slash commands are syntactic sugar for users, not new agent capabilities\n", + "\n", + "**What**: Custom slash commands are predefined prompt templates that users can trigger with shorthand syntax (e.g., `/budget-impact`). These are **user-facing shortcuts**, not agent capabilities. Think of them as keyboard shortcuts that expand into full, well-crafted prompts.\n", + "\n", + "**Why**: Your Chief of Staff will handle recurring executive questions. Instead of users typing complex prompts repeatedly, they can use already vetted prompts. This improves consistency and standardization.\n", + "\n", + "**How**:\n", + "- Define a markdown file in `.claude/commands/`. For example, we defined one in `.claude/commands/slash-command-test.md`. Notice how the command is defined: frontmatter with two fields (name, description) and the expanded prompt with an option to include arguments passed on in the query.\n", + "- You can add parameters to your prompt using `{{args}}`\n", + "- The user uses the slash command in their prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "# User types: \"/slash-command-test this is a test\"\n", + "# -> behind the scenes EXPANDS to the prompt in .claude/commands/slash-command-test.md\n", + "# In this case the expanded prompt says to simply reverse the sentence word wise\n", + "\n", + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(model=\"claude-sonnet-4-20250514\", cwd=\"chief_of_staff_agent\")\n", + ") as agent:\n", + " await agent.query(\"/slash-command-test this is a test\")\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "test a is this\n" + ] + } + ], + "source": [ + "print(messages[-1].result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 5: Hooks - Automated Deterministic Actions\n", + "\n", + "**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.\n", + "\n", + "**Why**: Imagine scenarios where you want to make sure that your agent has some guardrails (e.g., prevent dangerous operations) or when you want to have an audit trail. Hooks are ideal in combination with agents to allow them enough freedom to achieve their task, while still making sure that the agents behave in a safe way.\n", + "\n", + "**How**:\n", + "- Define hook scripts in `.claude/hooks/` -> _what_ is the behaviour that should be executed when a hook is triggered\n", + "- Define hook configuration in `.claude/settings.local.json` -> _when_ should a hook be triggered\n", + "- In this case, our hooks are configured to watch specific tool calls (WebSearch, Write, Edit, etc.)\n", + "- When those tools are called, the hook script either runs first (pre tool use hook) or after (post tool use hook)\n", + "\n", + "**Example: Report Tracking for Compliance**\n", + "\n", + "A hook to log Write/Edit operations on financial reports for audit and compliance purposes.\n", + "The hook is defined in `chief_of_staff_agent/.claude/hooks/report-tracker.py` and the logic that enforces it is in `chief_of_staff/.claude/settings.local.json`:\n", + "\n", + "\n", + "```json\n", + " \"hooks\": {\n", + " \"PostToolUse\": [\n", + " {\n", + " \"matcher\": \"Write|Edit\",\n", + " \"hooks\": [\n", + " {\n", + " \"type\": \"command\",\n", + " \"command\": \"$CLAUDE_PROJECT_DIR/.claude/hooks/report-tracker.py\"\n", + " }\n", + " ]\n", + " }\n", + " ]\n", + " }\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Write()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " cwd=\"chief_of_staff_agent\",\n", + " allowed_tools=[\"Bash\", \"Write\", \"Edit\", \"MultiEdit\"],\n", + " )\n", + ") as agent:\n", + " await agent.query(\n", + " \"Create a quick Q2 financial forecast report with our current burn rate and runway projections. Save it to our /output_reports folder.\"\n", + " )\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)\n", + "\n", + "# The hook will track this in audit/report_history.json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you now navigate to `./chief_of_staff_agent/audit/report_history.json`, you will find that it has logged that the agent has created and/or made changes to your report. The generated report itself you can find at `./chief_of_staff_agent/output_reports/`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature 6: Subagents via Task Tool\n", + "\n", + "**What**: The Task tool enables your agent to delegate specialized work to other subagents. These subagents each have their own instructions, tools, and expertise.\n", + "\n", + "**Why**: Adding subagents opens up a lot of possibilities:\n", + "1. Specialization: each subagent is an expert in their domain\n", + "2. Separate context: subagents have their own conversation history and tools\n", + "3. Parallellization: multiple subagents can work simultaneously on different aspects.\n", + "\n", + "**How**:\n", + "- Add `\"Task\"` to allowed_tools\n", + "- Use a system prompt to instruct your agent how to delegate tasks (you can also define this its CLAUDE.md more generally)\n", + "- Create a markdown file for each agent in `.claude/agents/`. For example, check the one for `.claude/agents/financial-analyst.md` and notice how a (sub)agent can be defined with such an easy and intuitive markdown file: frontmatter with three fields (name, description, and tools) and its system prompt. The description is useful for the main chief of staff agent to know when to invoke each subagent." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Task()\n", + "šŸ¤– Using: Bash()\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Bash()\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Read()\n", + "šŸ¤– Using: Read()\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " allowed_tools=[\"Task\"], # this enables our Chief agent to invoke subagents\n", + " system_prompt=\"Delegate financial questions to the financial-analyst subagent. Do not try to answer these questions yourself.\",\n", + " cwd=\"chief_of_staff_agent\",\n", + " )\n", + ") as agent:\n", + " await agent.query(\"Should we hire 5 engineers? Analyze the financial impact.\")\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "visualize_conversation(messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, when our main agent decides to use a subagent, it will:\n", + " 1. Call the Task tool with parameters like:\n", + " ```json\n", + " {\n", + " \"description\": \"Analyze hiring impact\",\n", + " \"prompt\": \"Analyze the financial impact of hiring 5 engineers...\",\n", + " \"subagent_type\": \"financial-analyst\"\n", + " }\n", + " ```\n", + " 2. The Task tool executes the subagent in a separate context\n", + " 3. Return results to main Chief of Staff agent to continue processing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Putting It All Together" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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:\n", + "- **Bash Tool**: Used to execute the `hiring_impact.py` script to determine the impact of hiring new engineers\n", + "- **Memory**: Reads `CLAUDE.md` in directory as context to understand the current budgets, runway, revenue and other relevant information\n", + "- **Output style**: Different output styles, defined in `chief_of_staff_agent/.claude/output-styles`\n", + "- **Custom Slash Commands**: Uses the shortcut `/budget-impact` that expands to full prompt defined in `chief_of_staff_agent/.claude/commands`\n", + "- **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` \n", + "- **Hooks**: Hooks are defined in `chief_of_staff_agent/.claude/hooks` and configured in `chief_of_staff_agent/.claude/settings.local.json`\n", + " - 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\n", + " - 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\n", + "\n", + "- **Plan Mode**: If you want the chief of staff to come up with a plan for you to approve before taking any action, uncomment the commented line below\n", + "\n", + "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. \n", + "\n", + "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.\n", + "\n", + "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)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Task()\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: Read()\n", + "šŸ¤– Using: Read()\n", + "šŸ¤– Using: Read()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Write()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "from chief_of_staff_agent.agent import send_query\n", + "\n", + "result, messages = await send_query(\n", + " \"/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\",\n", + " # permission_mode=\"plan\", # Enable this to use planning mode\n", + " output_style=\"executive\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "visualize_conversation(messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "We'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.\n", + "\n", + "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.\n", + "\n", + "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.\n", + "\n", + "Next: [02_Connecting_to_MCP_servers.ipynb](02_Connecting_to_MCP_servers.ipynb) - Learn how to extend your agents with custom integrations and external data sources through MCP." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (cc-sdk-tutorial)", + "language": "python", + "name": "cc-sdk-tutorial" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/claude_code_sdk/02_The_observability_agent.ipynb b/claude_code_sdk/02_The_observability_agent.ipynb new file mode 100644 index 00000000..8ea3a9cb --- /dev/null +++ b/claude_code_sdk/02_The_observability_agent.ipynb @@ -0,0 +1,453 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "7e7958dd", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from typing import Any\n", + "\n", + "from dotenv import load_dotenv\n", + "from utils.agent_visualizer import print_activity\n", + "\n", + "from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient" + ] + }, + { + "cell_type": "markdown", + "id": "ea47572b", + "metadata": {}, + "source": [ + "# 02 - The Observability Agent" + ] + }, + { + "cell_type": "markdown", + "id": "08cc95b6", + "metadata": {}, + "source": "In the previous notebooks we have built a basic research agent and a Chief of Staff multi-agent framework. While the agents we have built are already powerful, they were still limited in what they could do: the web search agent is limited to searching the internet and our Chief of Staff agent was limited to interacting with its own filesystem.\n\nThis is a serious constraint: real-world agents often need to interact with other systems like databases, APIs, file systems, and other specialized services. [MCP (Model Context Protocol)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard for AI-tool integrations that allows for an easy connection between our agents and these external systems. In this notebook, we will explore how to connect MCP servers to our agent.\n\n**Need more details on MCP?** For comprehensive setup instructions, configuration best practices, and troubleshooting tips, see the [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp)." + }, + { + "cell_type": "markdown", + "id": "95247d94", + "metadata": {}, + "source": [ + "## Introduction to the MCP Server\n", + "### 1. The Git MCP server\n", + "\n", + "Let's first give our agent the ability to understand and work with Git repositories. By adding the [Git MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/git) to our agent, it gains access to 13 Git-specific tools that let it examine commit history, check file changes, create branches, and even make commits. This transforms our agent from a passive observer into an active participant in your development workflow. In this example, we'll configure the agent to explore a repository's history using only Git tools. This is pretty simple, but knowing this, it is not difficult to imagine agents that can automatically create pull requests, analyze code evolution patterns, or help manage complex Git workflows across multiple repositories." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21de60c4", + "metadata": {}, + "outputs": [], + "source": [ + "# define our git MCP server (it was downloaded when you ran uv sync as it is defined in the pyproject.toml file)\n", + "git_mcp: dict[str, Any] = {\n", + " \"git\": {\n", + " \"command\": \"uv\",\n", + " \"args\": [\"run\", \"python\", \"-m\", \"mcp_server_git\", \"--repository\", os.getcwd()],\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "23aa5a3d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__git()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: Bash()\n", + "šŸ¤– Using: Bash()\n", + "šŸ¤– Using: Bash()\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "messages = []\n", + "async with (\n", + " ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " mcp_servers=git_mcp,\n", + " allowed_tools=[\n", + " \"mcp__git\"\n", + " ], # For MCP tools, in allowed tools we must add the mcp__serverName__toolName format or mcp__serverName to enable all\n", + " permission_mode=\"acceptEdits\", # auto-accept file edit permissions\n", + " )\n", + " ) as agent\n", + "):\n", + " await agent.query(\n", + " \"Use ONLY your git mcp tools to quickly explore this repo's history and gimme a brief summary.\"\n", + " )\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "691e0812", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"\\nResult:\\n{messages[-1].result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "346a8754", + "metadata": {}, + "source": [ + "### 2. The GitHub MCP server" + ] + }, + { + "cell_type": "markdown", + "id": "1361ba84", + "metadata": {}, + "source": [ + "Now let's level up from local Git operations to full GitHub platform integration. By switching to the [official GitHub MCP server](https://github.com/github/github-mcp-server/tree/main), our agent gains access to over 100 tools that interact with GitHub's entire ecosystem – from managing issues and pull requests to monitoring CI/CD workflows and analyzing code security alerts. This server can work with both public and private repositories, giving your agent the ability to automate complex GitHub workflows that would typically require multiple manual steps." + ] + }, + { + "cell_type": "markdown", + "id": "7fdb4aa2", + "metadata": {}, + "source": "#### Step 1: Set up your GitHub Token\n\nYou need a GitHub Personal Access Token. Get one [here](https://github.com/settings/personal-access-tokens/new) and put in the .env file as ```GITHUB_TOKEN=\"\"```\n> Note: When getting your token, select \"Fine-grained\" token with the default options (i.e., public repos, no account permissions), that'll be the easiest way to get this demo working.\n\nAlso, for this example you will have to have [Docker](https://www.docker.com/products/docker-desktop/) running on your machine. Docker is required because the GitHub MCP server runs in a containerized environment for security and isolation.\n\n**Docker Quick Setup:**\n- Install Docker Desktop from [docker.com](https://www.docker.com/products/docker-desktop/)\n- Ensure Docker is running (you'll see the Docker icon in your system tray)\n- Verify with `docker --version` in your terminal\n- **Troubleshooting:** If Docker won't start, check that virtualization is enabled in your BIOS. For detailed setup instructions, see the [Docker documentation](https://docs.docker.com/get-docker/)\n\n#### Step 2: Define the mcp server and start the agent loop!" + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c1e65281", + "metadata": {}, + "outputs": [], + "source": [ + "# define our github mcp server\n", + "load_dotenv(override=True)\n", + "github_mcp: dict[str, Any] = {\n", + " \"github\": {\n", + " \"command\": \"docker\",\n", + " \"args\": [\n", + " \"run\",\n", + " \"-i\",\n", + " \"--rm\",\n", + " \"-e\",\n", + " \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n", + " \"ghcr.io/github/github-mcp-server\",\n", + " ],\n", + " \"env\": {\"GITHUB_PERSONAL_ACCESS_TOKEN\": os.environ.get(\"GITHUB_TOKEN\")},\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e4c524c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__search_repositories()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__get_file_contents()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "# run our agent\n", + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " mcp_servers=github_mcp,\n", + " allowed_tools=[\"mcp__github\"],\n", + " permission_mode=\"acceptEdits\", # auto-accept permissions\n", + " )\n", + ") as agent:\n", + " await agent.query(\n", + " \"Use ONLY your GitHub MCP tools to search for the anthropics/claude-code-sdk-python repository and and give me a couple facts about it\"\n", + " )\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e0ac04f", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"\\nResult:\\n{messages[-1].result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2a788ed6", + "metadata": {}, + "source": [ + "## Real use case: An observability agent\n", + "\n", + "Now, with such simple setup we can already have an agent acting as self-healing software system!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c8edb208", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: mcp__github__list_workflows()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__list_workflow_runs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__get_workflow_run()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: mcp__github__get_job_logs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__list_workflow_jobs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: WebFetch()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__get_job_logs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: mcp__github__get_job_logs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: mcp__github__get_job_logs()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Using: TodoWrite()\n", + "āœ“ Tool completed\n", + "šŸ¤– Thinking...\n" + ] + } + ], + "source": [ + "load_dotenv(override=True)\n", + "\n", + "prompt = \"\"\"Monitor the GitHub Actions workflows for facebook/react.\n", + "Look at the last triggered CI pipeline. \n", + "1. Analyze the trigger for the pipeline\n", + "2. Identify whether the pipeline passed or not\n", + "3. If it failed, explain which test failed\n", + "4. Identify whether human involvement is required\n", + "\n", + "IMPORTANT: Do not raise a PR, issue, or bug on github yet. Just give me a summary of your findings and plan.\n", + "\n", + "Focus on the 'CI' workflow specifically. Use your Github MCP server tools!\"\"\"\n", + "\n", + "github_mcp: dict[str, Any] = {\n", + " \"github\": {\n", + " \"command\": \"docker\",\n", + " \"args\": [\n", + " \"run\",\n", + " \"-i\",\n", + " \"--rm\",\n", + " \"-e\",\n", + " \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n", + " \"ghcr.io/github/github-mcp-server\",\n", + " ],\n", + " \"env\": {\"GITHUB_PERSONAL_ACCESS_TOKEN\": os.environ.get(\"GITHUB_TOKEN\")},\n", + " }\n", + "}\n", + "\n", + "messages = []\n", + "async with ClaudeSDKClient(\n", + " options=ClaudeCodeOptions(\n", + " model=\"claude-sonnet-4-20250514\",\n", + " mcp_servers=github_mcp,\n", + " allowed_tools=[\"mcp__github\"],\n", + " permission_mode=\"acceptEdits\",\n", + " )\n", + ") as agent:\n", + " await agent.query(prompt)\n", + " async for msg in agent.receive_response():\n", + " print_activity(msg)\n", + " messages.append(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49a39ed7", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"\\nResult:\\n{messages[-1].result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "80cd321f", + "metadata": {}, + "source": [ + "### Observability Agent as Module\n", + "\n", + "The `observability_agent/agent.py` file contains the same minimal helper functions as the research agent or chief of staff agent, just enhanced for GitHub monitoring. \n", + "\n", + "As before, to use it as a module in your Python code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97074fe7", + "metadata": {}, + "outputs": [], + "source": [ + "from observability_agent.agent import send_query\n", + "\n", + "result = await send_query(\n", + " \"Check the CI status for the last 2 runs in anthropics/claude-code-sdk-python. Just do 3 tool calls, be efficient.\"\n", + ")\n", + "print(f\"Monitoring result: {result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0c1578dc", + "metadata": {}, + "source": [ + "We can do multi-turn conversations with this agent as well:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7914f8db", + "metadata": {}, + "outputs": [], + "source": [ + "# Example 2: Multi-turn conversation for deeper monitoring\n", + "result1 = await send_query(\"What's the current CI status for facebook/react?\")\n", + "print(f\"Initial check: {result1[:250]}...\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8014a701", + "metadata": {}, + "outputs": [], + "source": [ + "# Continue the conversation to dig deeper\n", + "result2 = await send_query(\n", + " \"Are there any flaky tests in the recent failures? You can only make one tool call.\",\n", + " continue_conversation=True,\n", + ")\n", + "print(f\"Follow-up analysis: {result2[:250]}...\")" + ] + }, + { + "cell_type": "markdown", + "id": "5a2f48f7", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "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.\n", + "\n", + "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.\n", + "\n", + "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.\n", + "\n", + "### What You've Learned Across All Notebooks\n", + "\n", + "**From Notebook 00 (Research Agent)**\n", + "- Core SDK fundamentals with `query()` and `ClaudeSDKClient`\n", + "- Basic tool usage with WebSearch and Read\n", + "- Simple agent loops and conversation management\n", + "\n", + "**From Notebook 01 (Chief of Staff)**\n", + "- Advanced features: memory, output styles, planning mode\n", + "- Multi-agent coordination through subagents\n", + "- Governance through hooks and custom commands\n", + "- Enterprise-ready agent architectures\n", + "\n", + "**From Notebook 02 (Observability Agent)**\n", + "- External system integration via MCP servers\n", + "- Real-time monitoring and incident response\n", + "- Production workflow automation\n", + "- Scalable agent deployment patterns\n", + "\n", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (cc-sdk-tutorial)", + "language": "python", + "name": "cc-sdk-tutorial" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/claude_code_sdk/README.md b/claude_code_sdk/README.md new file mode 100644 index 00000000..705a32f3 --- /dev/null +++ b/claude_code_sdk/README.md @@ -0,0 +1,127 @@ +# Building Powerful Agents with the Claude Code SDK + +A tutorial series demonstrating how to build sophisticated general-purpose agentic systems using the [Claude Code SDK](https://github.com/anthropics/claude-code-sdk-python), progressing from simple research agents to multi-agent orchestration with external system integration. + +## Getting Started + +#### 1. Install uv, [node](https://nodejs.org/en/download/), and the Claude Code CLI (if you haven't already) + +```curl -LsSf https://astral.sh/uv/install.sh | sh ``` + +```npm install -g @anthropic-ai/claude-code``` + +#### 2. Clone and set up the project + +```git clone https://github.com/anthropics/anthropic-cookbook.git ``` + +```cd anthropic-cookbook/claude_code_sdk``` + +```uv sync ``` + +#### 3. Register venv as Jupyter kernel so that you can use it in the notebooks + +```uv run python -m ipykernel install --user --name="cc-sdk-tutorial" --display-name "Python (cc-sdk-tutorial)" ``` + +#### 4. Anthropic API Key +1. Visit [console.anthropic.com](https://console.anthropic.com/dashboard) +2. Sign up or log in to your account +3. Click on "Get API keys" +4. Copy the key and paste it into your `.env` file as ```ANTHROPIC_API_KEY=``` + +#### 5. GitHub Token for Notebook 02 +If you plan to work through the Observability Agent notebook: +1. Get a GitHub Personal Access Token [here](https://github.com/settings/personal-access-tokens/new) +2. Select "Fine-grained" token with default options (public repos, no account permissions) +3. Add it to your `.env` file as `GITHUB_TOKEN=""` +4. Ensure [Docker](https://www.docker.com/products/docker-desktop/) is running on your machine + +## Tutorial Series Overview + +This tutorial series takes you on a journey from basic agent implementation to sophisticated multi-agent systems capable of handling real-world complexity. Each notebook builds upon the previous one, introducing new concepts and capabilities while maintaining practical, production-ready implementations. + +### What You'll Learn + +Through this series, you'll be exposed to: +- **Core SDK fundamentals** with `query()` and the `ClaudeSDKClient` & `ClaudeCodeOptions` interfaces in the Python SDK +- **Tool usage patterns** from basic WebSearch to complex MCP server integration +- **Multi-agent orchestration** with specialized subagents and coordination +- **Enterprise features** by leveraging hooks for compliance tracking and audit trails +- **External system integration** via Model Context Protocol (MCP) + +Note: This tutorial assumes you have some level of familiarity with Claude Code. Ideally, if you have been using Claude Code to supercharge your coding tasks and would like to leverage its raw agentic power for tasks beyond Software Engineering, this tutorial will help you get started. + +## Notebook Structure & Content + +### [Notebook 00: The One-Liner Research Agent](00_The_one_liner_research_agent.ipynb) + +Start your journey with a simple yet powerful research agent built in just a few lines of code. This notebook introduces core SDK concepts and demonstrates how the Claude Code SDK enables autonomous information gathering and synthesis. + +**Key Concepts:** +- Basic agent loops with `query()` and async iteration +- WebSearch tool for autonomous research +- Multimodal capabilities with the Read tool +- Conversation context management with `ClaudeSDKClient` +- System prompts for agent specialization + +### [Notebook 01: The Chief of Staff Agent](01_The_chief_of_staff_agent.ipynb) + +Build a comprehensive AI Chief of Staff for a startup CEO, showcasing advanced SDK features for production environments. This notebook demonstrates how to create sophisticated agent architectures with governance, compliance, and specialized expertise. + +**Key Features Explored:** +- **Memory & Context:** Persistent instructions with CLAUDE.md files +- **Output Styles:** Tailored communication for different audiences +- **Plan Mode:** Strategic planning without execution for complex tasks +- **Custom Slash Commands:** User-friendly shortcuts for common operations +- **Hooks:** Automated compliance tracking and audit trails +- **Subagent Orchestration:** Coordinating specialized agents for domain expertise +- **Bash Tool Integration:** Python script execution for procedural knowledge and complex computations + +### [Notebook 02: The Observability Agent](02_The_observability_agent.ipynb) + +Expand beyond local capabilities by connecting agents to external systems through the Model Context Protocol. Transform your agent from a passive observer into an active participant in DevOps workflows. + +**Advanced Capabilities:** +- **Git MCP Server:** 13+ tools for repository analysis and version control +- **GitHub MCP Server:** 100+ tools for complete GitHub platform integration +- **Real-time Monitoring:** CI/CD pipeline analysis and failure detection +- **Intelligent Incident Response:** Automated root cause analysis +- **Production Workflow Automation:** From monitoring to actionable insights + +## Complete Agent Implementations + +Each notebook includes an agent implementation in its respective directory: +- **`research_agent/`** - Autonomous research agent with web search and multimodal analysis +- **`chief_of_staff_agent/`** - Multi-agent executive assistant with financial modeling and compliance +- **`observability_agent/`** - DevOps monitoring agent with GitHub integration + +## Background +### The Evolution of Claude Code SDK + +Claude Code has emerged as one of Anthropic's most successful products, but not just for its SOTA coding capabilities. Its true breakthrough lies in something more fundamental: **Claude is exceptionally good at agentic work**. + +What makes Claude Code special isn't just code understanding; it's the ability to: +- Break down complex tasks into manageable steps autonomously +- Use tools effectively and make intelligent decisions about which tools to use and when +- Maintain context and memory across long-running tasks +- Recover gracefully from errors and adapt approaches when needed +- Know when to ask for clarification versus when to proceed with reasonable assumptions + +These capabilities have made Claude Code the closest thing to a "bare metal" harness for Claude's raw agentic power: a minimal yet complete and sophisticated interface that lets the model's capabilities shine with the least possible overhead. + +### Beyond Coding: The Agent Builder's Toolkit + +Originally an internal tool built by Anthropic engineers to accelerate development workflows, the SDK's public release revealed unexpected potential. After the release of the Claude Code SDK and its GitHub integration, developers began using it for tasks far beyond coding: + +- **Research agents** that gather and synthesize information across multiple sources +- **Data analysis agents** that explore datasets and generate insights +- **Workflow automation agents** that handle repetitive business processes +- **Monitoring and observability agents** that watch systems and respond to issues +- **Content generation agents** that create and refine various types of content + +The pattern was clear: the SDK had inadvertently become an effective agent-building framework. Its architecture, designed to handle software development complexity, proved remarkably well-suited for general-purpose agent creation. + +This tutorial series demonstrates how to leverage the Claude Code SDK to build highly efficient agents for any domain or use case, from simple automation to complex enterprise systems. + +## Contributing + +Found an issue or have a suggestion? Please open an issue or submit a pull request! diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/agents/financial-analyst.md b/claude_code_sdk/chief_of_staff_agent/.claude/agents/financial-analyst.md new file mode 100644 index 00000000..2bb3511e --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/agents/financial-analyst.md @@ -0,0 +1,82 @@ +--- +name: financial-analyst +description: Financial analysis expert specializing in startup metrics, burn rate, runway calculations, and investment decisions. Use proactively for any budget, financial projections, or cost analysis questions. +tools: Read, Bash, WebSearch +--- + +You are a senior financial analyst for TechStart Inc, a fast-growing B2B SaaS startup. Your expertise spans financial modeling, burn rate optimization, unit economics, and strategic financial planning. + +## Your Responsibilities + +1. **Financial Analysis** + - Calculate and monitor burn rate, runway, and cash position + - Analyze unit economics (CAC, LTV, payback period) + - Create financial projections and scenarios + - Evaluate ROI on major decisions + +2. **Budget Management** + - Track departmental budgets and spending + - Identify cost optimization opportunities + - Forecast future cash needs + - Analyze hiring impact on burn rate + +3. **Strategic Planning** + - Model different growth scenarios + - Evaluate acquisition opportunities + - Assess fundraising needs and timing + - Analyze competitive positioning from financial perspective + +## Available Data + +You have access to: +- Financial data in `financial_data/` directory: + - `burn_rate.csv`: Monthly burn rate trends + - `revenue_forecast.json`: Revenue projections + - `hiring_costs.csv`: Compensation data by role +- Company context in CLAUDE.md +- Python scripts for financial calculations (via Bash) in the `scripts/` folder: + - `python scripts/hiring_impact.py [salary]` - Calculate hiring impact on burn/runway + - `python scripts/financial_forecast.py` - Advanced financial modeling + - `python scripts/decision_matrix.py` - Strategic decision framework + +## Using the Hiring Impact Tool + +When asked about hiring engineers, ALWAYS use the hiring_impact.py tool: +```bash +python scripts/hiring_impact.py 3 200000 # For 3 engineers at $200K each +python scripts/hiring_impact.py 5 # Uses default $200K salary +``` + +The tool provides: +- Monthly burn rate increase +- New runway calculation +- Velocity impact estimate +- Risk-based recommendation + +## Decision Framework + +When analyzing financial decisions, always consider: +1. Impact on runway (must maintain >12 months) +2. Effect on key metrics (burn multiple, growth efficiency) +3. ROI and payback period +4. Risk factors and mitigation strategies +5. Alternative scenarios and sensitivity analysis + +## Output Guidelines + +- Lead with the most critical insight +- Provide specific numbers and timeframes +- Include confidence levels for projections +- Highlight key assumptions +- Recommend clear action items +- Flag any risks or concerns + +## Example Analyses + +**Hiring Decision:** +"Adding 3 senior engineers at $200K each will increase monthly burn by $50K, reducing runway from 20 to 18 months. However, faster product development could accelerate revenue growth by 20%, reaching cash flow positive 3 months earlier." + +**Acquisition Analysis:** +"Acquiring SmartDev for $8M would consume 80% of cash reserves, reducing runway to 4 months. Would need immediate Series B or revenue synergies of >$500K/month to justify." + +Remember: Always ground recommendations in data and provide multiple scenarios when uncertainty is high. \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/agents/recruiter.md b/claude_code_sdk/chief_of_staff_agent/.claude/agents/recruiter.md new file mode 100644 index 00000000..86baa7f3 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/agents/recruiter.md @@ -0,0 +1,89 @@ +--- +name: recruiter +description: Technical recruiting specialist focused on startup hiring, talent pipeline management, and candidate evaluation. Use proactively for hiring decisions, team composition analysis, and talent market insights. +tools: Read, WebSearch, Bash +--- + +You are an expert technical recruiter specializing in startup talent acquisition. You understand both the technical requirements and cultural fit needed for a fast-growing startup environment. + +## Your Responsibilities + +1. **Talent Pipeline Management** + - Source and evaluate technical candidates + - Manage interview scheduling and coordination + - Track candidate pipeline metrics + - Build relationships with passive candidates + +2. **Hiring Strategy** + - Recommend optimal team composition + - Analyze market rates and compensation + - Advise on senior vs. junior hire tradeoffs + - Identify skill gaps in current team + +3. **Candidate Evaluation** + - Review technical portfolios and GitHub profiles + - Assess culture fit and startup readiness + - Coordinate technical assessments + - Provide hiring recommendations + +4. **Market Intelligence** + - Track talent availability by role and location + - Monitor competitor hiring and compensation + - Identify emerging skill requirements + - Advise on remote vs. in-office strategies + +## Available Scripts + +You have access to: +- WebSearch for researching candidates and market rates +- Python scripts for talent scoring (via Bash) in `scripts/talent_scorer.py` +- Company hiring data in `financial_data/hiring_costs.csv` +- Team structure information in CLAUDE.md + +## Evaluation Criteria + +When assessing candidates, consider: +1. **Technical Skills** (via GitHub analysis) + - Code quality and consistency + - Open source contributions + - Technology stack alignment + - Problem-solving approach + +2. **Startup Fit** + - Comfort with ambiguity + - Ownership mentality + - Growth mindset + - Collaboration skills + +3. **Team Dynamics** + - Complementary skills to existing team + - Mentorship potential (senior) or coachability (junior) + - Cultural add vs. cultural fit + - Long-term retention likelihood + +## Hiring Recommendations Format + +**For Individual Candidates:** +"Strong hire. Senior backend engineer with 8 years experience, deep expertise in our stack (Python, PostgreSQL, AWS). GitHub shows consistent high-quality contributions. Asking $210K, which is within our range. Can mentor juniors and own authentication service rebuild." + +**For Hiring Strategy:** +"Recommend 2 senior + 3 junior engineers over 5 mid-level. Seniors provide immediate impact and mentorship, juniors offer growth potential and lower burn. Total cost: $950K/year vs. $900K for mid-levels, but better long-term team development." + +## Interview Process + +Standard pipeline for engineering roles: +1. Recruiter screen (30 min) - culture fit, motivation +2. Technical screen (60 min) - coding exercise +3. System design (90 min) - architecture discussion +4. Team fit (45 min) - with potential teammates +5. Executive chat (30 min) - with CEO/CTO + +## Key Metrics to Track + +- Time to hire: Target <30 days +- Offer acceptance rate: Target >80% +- Quality of hire: 90-day retention >95% +- Pipeline velocity: 5 qualified candidates per opening +- Diversity metrics: 30% underrepresented groups + +Remember: In a startup, every hire significantly impacts culture and runway. Optimize for high-impact individuals who can grow with the company. \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/commands/budget-impact.md b/claude_code_sdk/chief_of_staff_agent/.claude/commands/budget-impact.md new file mode 100644 index 00000000..25bff63e --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/commands/budget-impact.md @@ -0,0 +1,16 @@ +--- +name: budget-impact +description: Analyze the financial impact of a decision on budget, burn rate, and runway +--- + +Use the financial-analyst subagent to analyze the budget impact of: {{args}} + +Provide a comprehensive analysis including: +1. Total cost (one-time and recurring) +2. Impact on monthly burn rate +3. Change in runway (months) +4. ROI analysis if applicable +5. Alternative options to consider +6. Risk factors + +Format the response with clear sections and specific numbers. Include a final recommendation. \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/commands/slash-command-test.md b/claude_code_sdk/chief_of_staff_agent/.claude/commands/slash-command-test.md new file mode 100644 index 00000000..d34639ad --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/commands/slash-command-test.md @@ -0,0 +1,6 @@ +--- +name: slash-command-test +description: example of how a slash-command works +--- + +Reverse the following sentence word wise: {{args}} diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/commands/strategic-brief.md b/claude_code_sdk/chief_of_staff_agent/.claude/commands/strategic-brief.md new file mode 100644 index 00000000..85d6a537 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/commands/strategic-brief.md @@ -0,0 +1,36 @@ +--- +name: strategic-brief +description: Generate a comprehensive strategic brief by coordinating analysis from both financial and talent perspectives +--- + +Create a strategic brief on: {{args}} + +Coordinate with both the financial-analyst and recruiter subagents to provide: + +## Executive Summary +- Key recommendation (1-2 sentences) +- Critical metrics impact + +## Financial Analysis (via financial-analyst) +- Cost/investment required +- ROI and payback period +- Impact on runway and burn rate +- Financial risks and mitigation + +## Talent Perspective (via recruiter) +- Team capabilities required +- Hiring implications +- Retention considerations +- Competitive talent landscape + +## Strategic Recommendation +- Recommended action plan +- Success metrics +- Timeline and milestones +- Risk mitigation strategies + +## Alternative Options +- At least 2 alternative approaches +- Pros/cons of each + +Format for board-level presentation with clear sections and data-driven insights. \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/commands/talent-scan.md b/claude_code_sdk/chief_of_staff_agent/.claude/commands/talent-scan.md new file mode 100644 index 00000000..96add023 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/commands/talent-scan.md @@ -0,0 +1,20 @@ +--- +name: talent-scan +description: Scan the talent market for specific roles and provide hiring recommendations +--- + +Use the recruiter subagent to perform a talent market scan for: {{args}} + +Analyze and report on: +1. Talent availability in target markets +2. Competitive salary ranges +3. Required skills and experience levels +4. Estimated time to hire +5. Recommended sourcing channels +6. Top candidate profiles (if using GitHub) + +Provide specific recommendations on: +- Senior vs. junior hiring mix +- Remote vs. in-office strategy +- Compensation packages +- Interview process optimizations \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/hooks/report-tracker.py b/claude_code_sdk/chief_of_staff_agent/.claude/hooks/report-tracker.py new file mode 100755 index 00000000..a76107aa --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/hooks/report-tracker.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +PostToolUse hook: Tracks ALL file writes and edits +Maintains history of all document changes for compliance +""" + +import json +import os +import sys +from datetime import datetime + + +def track_report(tool_name, tool_input, tool_response): + """Log ALL file creation/modification for audit trail""" + + # Debug: Log that hook was called + print(f"šŸ” Hook called for tool: {tool_name}", file=sys.stderr) + + # Get file path from tool input + file_path = tool_input.get("file_path", "") + + if not file_path: + print("āš ļø No file_path in tool_input", file=sys.stderr) + return + + print(f"šŸ“ Tracking file: {file_path}", file=sys.stderr) + + # Track ALL file writes/edits (no filtering) + + # Prepare history file path + history_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "../../audit/report_history.json" + ) + + try: + # Load existing history or create new + if os.path.exists(history_file): + with open(history_file) as f: + history = json.load(f) + else: + history = {"reports": []} + + # Determine action type + action = "created" if tool_name == "Write" else "modified" + + # Calculate word count if content available + content = tool_input.get("content", "") or tool_input.get("new_string", "") + word_count = len(content.split()) if content else 0 + + # Create history entry + entry = { + "timestamp": datetime.now().isoformat(), + "file": os.path.basename(file_path), + "path": file_path, + "action": action, + "word_count": word_count, + "tool": tool_name, + } + + # Add to history + history["reports"].append(entry) + + # Keep only last 50 entries + history["reports"] = history["reports"][-50:] + + # Save updated history + os.makedirs(os.path.dirname(history_file), exist_ok=True) + with open(history_file, "w") as f: + json.dump(history, f, indent=2) + + print(f"šŸ“Š File tracked: {os.path.basename(file_path)} ({action})") + + except Exception as e: + print(f"Report tracking error: {e}", file=sys.stderr) + + +# Main execution +if __name__ == "__main__": + try: + # Read input from stdin + input_data = json.load(sys.stdin) + + tool_name = input_data.get("tool_name", "") + tool_input = input_data.get("tool_input", {}) + tool_response = input_data.get("tool_response", {}) + + # Track the report + track_report(tool_name, tool_input, tool_response) + + # Always exit successfully + sys.exit(0) + + except Exception as e: + print(f"Hook error: {e}", file=sys.stderr) + sys.exit(0) diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/hooks/script-usage-logger.py b/claude_code_sdk/chief_of_staff_agent/.claude/hooks/script-usage-logger.py new file mode 100755 index 00000000..ce7d05c2 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/hooks/script-usage-logger.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +PostToolUse hook: Logs when Python scripts are executed via the Bash tool +Distinguishes between: +- Tools: The Claude SDK tools (Bash, Write, Edit, etc.) +- Scripts: Python scripts executed through the Bash tool +""" + +import json +import os +import sys +from datetime import datetime + + +def log_script_usage(tool_name, tool_input, tool_response): + """Log execution of Python scripts via Bash tool""" + + # Only track Bash tool (which is used to execute scripts) + if tool_name != "Bash": + return + + # Get the command from tool input + command = tool_input.get("command", "") + + # Check if it's executing a Python script from scripts/ directory + # Support both: "python scripts/file.py" and "./scripts/file.py" + import re + + # Try to match either pattern: python scripts/... or ./scripts/... or scripts/... + script_match = re.search(r"(?:python\s+)?(?:\./)?scripts/(\w+\.py)", command) + if not script_match: + return + + # Only proceed if it's a scripts/ directory execution + if "scripts/" not in command: + return + + script_file = script_match.group(1) + + # Prepare log file path + log_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "../../audit/script_usage_log.json" + ) + + try: + # Load existing log or create new + if os.path.exists(log_file): + with open(log_file) as f: + log_data = json.load(f) + else: + log_data = {"script_executions": []} + + # Create log entry + entry = { + "timestamp": datetime.now().isoformat(), + "script": script_file, + "command": command, + "description": tool_input.get("description", "No description"), + "tool_used": "Bash", # The tool used to execute the script + "success": tool_response.get("success", True) if tool_response else True, + } + + # Add to log + log_data["script_executions"].append(entry) + + # Keep only last 100 entries + log_data["script_executions"] = log_data["script_executions"][-100:] + + # Save updated log + os.makedirs(os.path.dirname(log_file), exist_ok=True) + with open(log_file, "w") as f: + json.dump(log_data, f, indent=2) + + print(f"šŸ“œ Script executed: {script_file}") + + except Exception as e: + print(f"Script logging error: {e}", file=sys.stderr) + + +# Main execution +if __name__ == "__main__": + try: + # Read input from stdin + input_data = json.load(sys.stdin) + + tool_name = input_data.get("tool_name", "") + tool_input = input_data.get("tool_input", {}) + tool_response = input_data.get("tool_response", {}) + + # Log the script usage (when executed via Bash tool) + log_script_usage(tool_name, tool_input, tool_response) + + # Always exit successfully + sys.exit(0) + + except Exception as e: + print(f"Hook error: {e}", file=sys.stderr) + sys.exit(0) diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/executive.md b/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/executive.md new file mode 100644 index 00000000..6ed29b4f --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/executive.md @@ -0,0 +1,50 @@ +--- +name: executive +description: Concise, KPI-focused communication for C-suite executives +--- + +## Communication Principles + +- **Lead with the decision or recommendation** - First sentence should be the key takeaway +- **Use bullet points** - Maximum 3-5 points per section +- **Numbers over narrative** - Specific metrics, percentages, and timeframes +- **Action-oriented** - Clear next steps and decisions required +- **Visual hierarchy** - Bold key numbers and dates + +## Format Template + +**RECOMMENDATION:** [One sentence decision/action] + +**KEY METRICS:** +- Metric 1: **X%** change +- Metric 2: **$Y** impact +- Metric 3: **Z months** timeline + +**RATIONALE:** [2-3 sentences max] + +**NEXT STEPS:** +1. Immediate action +2. Near-term action +3. Follow-up required + +**RISKS:** [If any, one line each] + +## Example Output + +**RECOMMENDATION:** Hire 3 senior engineers now to maintain product velocity. + +**KEY METRICS:** +- Burn increase: **$50K/month** +- Runway impact: **-2 months** (20→18) +- ROI: **3.2x** in 12 months + +**RATIONALE:** Engineering bottleneck is delaying feature launches. Senior hires can mentor juniors and own critical services. Delay risks losing 2 enterprise deals worth $500K ARR. + +**NEXT STEPS:** +1. Approve budget increase today +2. Begin sourcing immediately +3. First hire by end of month + +**RISKS:** Competing offers from FAANG may require 10% salary premium. + +Remember: Executives have 30 seconds to read this. Make every word count. diff --git a/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/technical.md b/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/technical.md new file mode 100644 index 00000000..eeccc5f9 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/.claude/output-styles/technical.md @@ -0,0 +1,86 @@ +--- +name: technical +description: Detailed, data-rich analysis for technical teams and analysts +--- + +You are providing detailed technical analysis with comprehensive data and methodologies. + +## Communication Principles + +- **Data-first approach** - Include all relevant metrics and calculations +- **Methodology transparency** - Explain how you arrived at conclusions +- **Multiple scenarios** - Show sensitivity analysis and edge cases +- **Technical depth** - Include formulas, assumptions, and constraints +- **Structured sections** - Clear organization for deep-dive analysis + +## Format Template + +### Analysis Overview +[Brief context and scope] + +### Methodology +- Data sources used +- Key assumptions +- Calculation methods +- Confidence intervals + +### Detailed Findings + +#### Finding 1: [Title] +- **Data Points:** + - Metric A: value ± margin + - Metric B: value (methodology) + - Metric C: trend analysis +- **Analysis:** [Detailed explanation] +- **Implications:** [Technical consequences] + +#### Finding 2: [Continue pattern] + +### Scenario Analysis +| Scenario | Variable 1 | Variable 2 | Outcome | Probability | +|----------|-----------|-----------|---------|-------------| +| Base | X | Y | Z | 60% | +| Optimistic| X+20% | Y+10% | Z+35% | 25% | +| Pessimistic| X-15% | Y-20% | Z-40% | 15% | + +### Technical Recommendations +1. **Primary:** [Detailed action with rationale] +2. **Alternative:** [Backup approach with tradeoffs] +3. **Monitoring:** [Metrics to track] + +### Appendix +- Formulas used +- Raw data tables +- Additional charts/visualizations + +## Example Output + +### Hiring Impact Analysis + +#### Methodology +- Data: 6 months historical burn rate, 120 comparable salary datapoints +- Model: Linear regression with seasonal adjustment +- Confidence: 85% (±10% margin on projections) + +#### Financial Impact +- **Base Salary Cost:** $600K/year (3 Ɨ $200K) +- **Loaded Cost:** $780K/year (1.3x multiplier for benefits, taxes, equipment) +- **Monthly Burn Increase:** $65K ($780K / 12) +- **Runway Impact:** + - Current: 20 months at $500K/month = $10M remaining + - New: $10M / $565K = 17.7 months (-2.3 months) + +#### Productivity Analysis +- **Current Velocity:** 15 story points/sprint +- **Projected with Seniors:** 22 points/sprint (+46%) +- **Break-even:** Month 8 (when productivity gains offset costs) +- **NPV:** $1.2M over 24 months at 10% discount rate + +### Sensitivity Analysis +| Salary Range | Productivity Gain | NPV | Runway Impact | +|-------------|------------------|-----|---------------| +| $180K (-10%) | +40% | $950K | -2.0 months | +| $200K (base) | +46% | $1.2M | -2.3 months | +| $220K (+10%) | +50% | $1.3M | -2.6 months | + +Remember: Technical audience wants to verify your work. Show your math. diff --git a/claude_code_sdk/chief_of_staff_agent/CLAUDE.md b/claude_code_sdk/chief_of_staff_agent/CLAUDE.md new file mode 100644 index 00000000..f77cda40 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/CLAUDE.md @@ -0,0 +1,95 @@ +# CLAUDE.md - Chief of Staff Context + +## Company Overview +- **Company**: TechStart Inc +- **Stage**: Series A (Closed $10M in January 2024) +- **Industry**: B2B SaaS - AI-powered developer tools +- **Founded**: 2022 +- **HQ**: San Francisco, CA + +## Financial Snapshot +- **Monthly Burn Rate**: $500,000 +- **Current Runway**: 20 months (until September 2025) +- **ARR**: $2.4M (growing 15% MoM) +- **Cash in Bank**: $10M +- **Revenue per Employee**: $48K + +## Team Structure +- **Total Headcount**: 50 +- **Engineering**: 25 (50%) + - Backend: 12 + - Frontend: 8 + - DevOps/SRE: 5 +- **Sales & Marketing**: 12 (24%) +- **Product**: 5 (10%) +- **Operations**: 5 (10%) +- **Executive**: 3 (6%) + +## Key Metrics +- **Customer Count**: 120 enterprise customers +- **NPS Score**: 72 +- **Monthly Churn**: 2.5% +- **CAC**: $15,000 +- **LTV**: $85,000 +- **CAC Payback Period**: 10 months + +## Current Priorities (Q2 2024) +1. **Hiring**: Add 10 engineers to accelerate product development +2. **Product**: Launch AI code review feature by end of Q2 +3. **Sales**: Expand into European market +4. **Fundraising**: Begin Series B conversations (target: $30M) + +## Compensation Benchmarks +- **Senior Engineer**: $180K - $220K + 0.1-0.3% equity +- **Junior Engineer**: $100K - $130K + 0.05-0.1% equity +- **Engineering Manager**: $200K - $250K + 0.3-0.5% equity +- **VP Engineering**: $250K - $300K + 0.5-1% equity + +## Board Composition +- **CEO**: Sarah Chen (Founder) +- **Investor 1**: Mark Williams (Sequoia Capital) +- **Investor 2**: Jennifer Park (Andreessen Horowitz) +- **Independent**: Michael Torres (Former CTO of GitHub) + +## Competitive Landscape +- **Main Competitors**: DevTools AI, CodeAssist Pro, SmartDev Inc +- **Our Differentiation**: Superior AI accuracy, 10x faster processing +- **Market Size**: $5B (growing 25% annually) + +## Recent Decisions +- Approved hiring 3 senior backend engineers (March 2024) +- Launched freemium tier (February 2024) +- Opened European entity (January 2024) +- Closed Series A funding (January 2024) + +## Upcoming Decisions +- Whether to acquire competitor SmartDev Inc ($8M asking price) +- Hiring plan for Q3 (engineering vs. sales focus) +- Office expansion vs. remote-first strategy +- Stock option refresh for early employees + +## Risk Factors +- High dependency on AWS (70% of COGS) +- Key engineer retention (3 critical team members) +- Increasing competition from Big Tech +- Potential economic downturn impact on enterprise sales + +## Available Scripts + +### simple_calculation.py +Quick financial metrics calculator for runway and burn rate analysis. +Script located at `./scripts/simple_calculation.py` + +**Usage:** +```bash +python scripts/simple_calculation.py +``` + +**Example:** +```bash +python scripts/simple_calculation.py 10000000 500000 +``` + +**Output:** JSON with monthly_burn, runway_months, total_runway_dollars, quarterly_burn, and burn_rate_daily + +Remember: As Chief of Staff, you have access to financial data in the financial_data/ directory and can delegate specialized analysis to your subagents (financial-analyst and recruiter). \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/agent.py b/claude_code_sdk/chief_of_staff_agent/agent.py new file mode 100644 index 00000000..9e747402 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/agent.py @@ -0,0 +1,122 @@ +""" +Chief of Staff Agent +""" + +import asyncio +import json +import os +from collections.abc import Callable +from typing import Any, Literal + +from dotenv import load_dotenv + +from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient + +load_dotenv() + + +def get_activity_text(msg) -> str | None: + """Extract activity text from a message""" + try: + if "Assistant" in msg.__class__.__name__: + if hasattr(msg, "content") and msg.content: + first_content = msg.content[0] if isinstance(msg.content, list) else msg.content + if hasattr(first_content, "name"): + return f"šŸ¤– Using: {first_content.name}()" + return "šŸ¤– Thinking..." + elif "User" in msg.__class__.__name__: + return "āœ“ Tool completed" + except (AttributeError, IndexError): + pass + return None + + +def print_activity(msg) -> None: + """Print activity to console""" + activity = get_activity_text(msg) + if activity: + print(activity) + + +async def send_query( + prompt: str, + continue_conversation: bool = False, + permission_mode: Literal["default", "plan", "acceptEdits"] = "default", + output_style: str | None = None, + activity_handler: Callable[[Any], None | Any] = print_activity, +) -> tuple[str | None, list]: + """ + Send a query to the Chief of Staff agent with all features integrated. + + Args: + prompt: The query to send (can include slash commands like /budget-impact) + activity_handler: Callback for activity updates (default: print_activity) + continue_conversation: Continue the previous conversation if True + permission_mode: "default" (execute), "plan" (think only), or "acceptEdits" + output_style: Override output style (e.g., "executive", "technical", "board-report") + + Returns: + Tuple of (result, messages) - result is the final text, messages is the full conversation + + Features automatically included/leveraged: + - Memory: CLAUDE.md context loaded from chief_of_staff/CLAUDE.md + - Subagents: financial-analyst and recruiter via Task tool (defined in .claude/agents) + - Custom scripts: Python scripts in tools/ via Bash + - Slash commands: Expanded from .claude/commands/ + - Output styles: Custom output styles defined in .claude/output-styles + - Hooks: Triggered based on settings.local.json, defined in .claude/hooks + """ + + system_prompt = """You are the Chief of Staff for TechStart Inc, a 50-person startup. + + Apart from your tools and two subagents, you also have custom Python scripts in the scripts/ directory you can run with Bash: + - python scripts/financial_forecast.py: Advanced financial modeling + - python scripts/talent_scorer.py: Candidate scoring algorithm + - python scripts/decision_matrix.py: Strategic decision framework + + You have access to company data in the financial_data/ directory. + """ + + # build options with optional output style + options_dict = { + "model": "claude-sonnet-4-20250514", + "allowed_tools": [ + "Task", # enables subagent delegation + "Read", + "Write", + "Edit", + "Bash", + "WebSearch", + ], + "continue_conversation": continue_conversation, + "system_prompt": system_prompt, + "permission_mode": permission_mode, + "cwd": os.path.dirname(os.path.abspath(__file__)), + } + + # add output style if specified + if output_style: + options_dict["settings"] = json.dumps({"outputStyle": output_style}) + + options = ClaudeCodeOptions(**options_dict) + + result = None + messages = [] # this is to append the messages ONLY for this agent turn + + try: + async with ClaudeSDKClient(options=options) as agent: + await agent.query(prompt=prompt) + async for msg in agent.receive_response(): + messages.append(msg) + if asyncio.iscoroutinefunction(activity_handler): + await activity_handler(msg) + else: + activity_handler(msg) + + if hasattr(msg, "result"): + result = msg.result + except Exception as e: + print(f"āŒ Query error: {e}") + raise + + return result, messages diff --git a/claude_code_sdk/chief_of_staff_agent/audit/report_history.json b/claude_code_sdk/chief_of_staff_agent/audit/report_history.json new file mode 100644 index 00000000..f3d43b85 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/audit/report_history.json @@ -0,0 +1,20 @@ +{ + "reports": [ + { + "timestamp": "2025-09-12T11:23:27.090810", + "file": "Q2_2024_Financial_Forecast.md", + "path": "/Users/rodrigoolivares/code/cc-sdk-tutorial/chief_of_staff_agent/output_reports/Q2_2024_Financial_Forecast.md", + "action": "created", + "word_count": 437, + "tool": "Write" + }, + { + "timestamp": "2025-09-12T11:28:54.692744", + "file": "hiring_decision.md", + "path": "/Users/rodrigoolivares/code/cc-sdk-tutorial/chief_of_staff_agent/output_reports/hiring_decision.md", + "action": "created", + "word_count": 720, + "tool": "Write" + } + ] +} \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/audit/script_usage_log.json b/claude_code_sdk/chief_of_staff_agent/audit/script_usage_log.json new file mode 100644 index 00000000..c1a910c2 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/audit/script_usage_log.json @@ -0,0 +1,36 @@ +{ + "script_executions": [ + { + "timestamp": "2025-09-12T11:09:48.519841", + "script": "simple_calculation.py", + "command": "python scripts/simple_calculation.py 2904829 121938", + "description": "Run runway calculation with provided values", + "tool_used": "Bash", + "success": true + }, + { + "timestamp": "2025-09-12T11:12:16.209491", + "script": "simple_calculation.py", + "command": "python scripts/simple_calculation.py 2904829 121938", + "description": "Run runway calculation with $2.9M runway and $122K monthly burn", + "tool_used": "Bash", + "success": true + }, + { + "timestamp": "2025-09-12T11:23:02.810007", + "script": "simple_calculation.py", + "command": "python scripts/simple_calculation.py 10000000 500000", + "description": "Calculate financial metrics using our script", + "tool_used": "Bash", + "success": true + }, + { + "timestamp": "2025-09-12T11:27:59.994999", + "script": "hiring_impact.py", + "command": "python scripts/hiring_impact.py 3 200000", + "description": "Calculate hiring impact for 3 engineers at $200K", + "tool_used": "Bash", + "success": true + } + ] +} \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/financial_data/burn_rate.csv b/claude_code_sdk/chief_of_staff_agent/financial_data/burn_rate.csv new file mode 100644 index 00000000..673ffa55 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/financial_data/burn_rate.csv @@ -0,0 +1,7 @@ +Month,Burn_Rate,Headcount,Revenue,Net_Burn +2024-01,450000,45,180000,270000 +2024-02,475000,47,195000,280000 +2024-03,490000,49,210000,280000 +2024-04,500000,50,240000,260000 +2024-05,510000,51,265000,245000 +2024-06,525000,53,290000,235000 \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/financial_data/hiring_costs.csv b/claude_code_sdk/chief_of_staff_agent/financial_data/hiring_costs.csv new file mode 100644 index 00000000..5fe53c64 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/financial_data/hiring_costs.csv @@ -0,0 +1,11 @@ +Role,Level,Base_Salary,Equity_Percent,Total_Comp,Recruiting_Fee,Onboarding_Cost,Monthly_Loaded_Cost +Backend Engineer,Senior,200000,0.002,220000,30000,5000,18333 +Backend Engineer,Junior,115000,0.0008,125000,17250,5000,10417 +Frontend Engineer,Senior,190000,0.002,210000,28500,5000,17500 +Frontend Engineer,Junior,110000,0.0008,120000,16500,5000,10000 +DevOps Engineer,Senior,210000,0.0025,235000,31500,5000,19583 +ML Engineer,Senior,240000,0.003,270000,36000,5000,22500 +Engineering Manager,Manager,225000,0.004,260000,33750,5000,21667 +Product Manager,Senior,180000,0.002,200000,27000,5000,16667 +Designer,Senior,170000,0.0015,185000,25500,5000,15417 +Data Scientist,Senior,200000,0.002,220000,30000,5000,18333 \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/financial_data/revenue_forecast.json b/claude_code_sdk/chief_of_staff_agent/financial_data/revenue_forecast.json new file mode 100644 index 00000000..fc4ed5e4 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/financial_data/revenue_forecast.json @@ -0,0 +1,53 @@ +{ + "current_arr": 2400000, + "growth_rate_monthly": 0.15, + "forecast_months": [ + { + "month": "2024-07", + "projected_arr": 2760000, + "new_customers": 12, + "churn_risk": "low" + }, + { + "month": "2024-08", + "projected_arr": 3174000, + "new_customers": 15, + "churn_risk": "low" + }, + { + "month": "2024-09", + "projected_arr": 3650000, + "new_customers": 18, + "churn_risk": "medium" + }, + { + "month": "2024-10", + "projected_arr": 4197500, + "new_customers": 20, + "churn_risk": "medium" + }, + { + "month": "2024-11", + "projected_arr": 4827125, + "new_customers": 22, + "churn_risk": "low" + }, + { + "month": "2024-12", + "projected_arr": 5551194, + "new_customers": 25, + "churn_risk": "low" + } + ], + "key_assumptions": [ + "Maintain 15% MoM growth rate", + "Product-market fit remains strong", + "No major competitor enters market", + "Engineering team scales as planned" + ], + "risks": [ + "Economic downturn could reduce enterprise spending", + "Key customer concentration (top 5 = 30% of revenue)", + "Technical debt may slow feature development" + ] +} \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/flow_diagram.md b/claude_code_sdk/chief_of_staff_agent/flow_diagram.md new file mode 100644 index 00000000..b7a658d1 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/flow_diagram.md @@ -0,0 +1,49 @@ +# Chief of Staff Agent Architecture + +```mermaid +graph TD + User[User] --> Chief[Chief of Staff Agent] + Chief --> Memory[CLAUDE.md] + Chief --> FinData[financial_data/] + Chief --> Tools + Chief --> Commands[Slash Commands] + Chief --> Styles[Output Styles] + Chief --> Hooks[Hooks] + + Tools --> Task[Task Tool] + Task --> FA[Financial Analyst] + Task --> Recruiter[Recruiter] + + FA --> Scripts1[Python Scripts] + Recruiter --> Scripts2[Python Scripts] + + style Chief fill:#f9f,stroke:#333,stroke-width:3px + style Task fill:#bbf,stroke:#333,stroke-width:2px + style FA fill:#bfb,stroke:#333,stroke-width:2px + style Recruiter fill:#bfb,stroke:#333,stroke-width:2px +``` + +## Expected Agent Communication Flow + +```mermaid +sequenceDiagram + participant User + participant Chief as Chief of Staff + participant Task as Task Tool + participant FA as Financial Analyst + participant Scripts as Python Scripts + participant Hooks as Post-Write Hook + User->>Chief: /budget-impact hiring 5 engineers + Chief->>Chief: Expand slash command + Chief->>Task: Delegate financial analysis + Task->>FA: Analyze hiring impact + FA->>Scripts: Execute hiring_impact.py + Scripts-->>FA: Return analysis results + FA->>FA: Generate report + FA-->>Task: Return findings + Task-->>Chief: Subagent results + Chief->>Chief: Write report to disk + Chief->>Hooks: Trigger post-write hook + Hooks->>Hooks: Log to audit trail + Chief-->>User: Executive summary +``` diff --git a/claude_code_sdk/chief_of_staff_agent/output_reports/Q2_2024_Financial_Forecast.md b/claude_code_sdk/chief_of_staff_agent/output_reports/Q2_2024_Financial_Forecast.md new file mode 100644 index 00000000..446b2f5c --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/output_reports/Q2_2024_Financial_Forecast.md @@ -0,0 +1,83 @@ +# Q2 2024 Financial Forecast Report +**TechStart Inc - Chief of Staff Analysis** +*Report Date: September 12, 2025* + +## Executive Summary +TechStart Inc maintains a strong financial position in Q2 2024 with 20 months of runway and accelerating revenue growth. Current burn rate is well-controlled at $500K monthly while revenue grows 15% month-over-month. + +## Current Financial Position (as of Q2 2024) + +### Cash & Runway +- **Cash in Bank**: $10,000,000 +- **Monthly Burn Rate**: $500,000 +- **Daily Burn Rate**: $16,667 +- **Current Runway**: 20 months (until September 2025) +- **Quarterly Burn**: $1,500,000 + +### Revenue Metrics +- **Annual Recurring Revenue (ARR)**: $2,400,000 +- **Monthly Growth Rate**: 15% +- **Revenue per Employee**: $48,000 +- **Customer Count**: 120 enterprise customers + +## Q2 2024 Projections + +### Cash Flow Forecast +| Month | Starting Cash | Monthly Burn | Revenue Growth | Ending Cash | +|-------|---------------|--------------|----------------|-------------| +| Apr 2024 | $10,000,000 | $500,000 | +15% | $9,500,000 | +| May 2024 | $9,500,000 | $500,000 | +15% | $9,000,000 | +| Jun 2024 | $9,000,000 | $500,000 | +15% | $8,500,000 | + +### Key Financial Ratios +- **CAC/LTV Ratio**: 0.18 (Healthy - under 0.3) +- **CAC Payback Period**: 10 months +- **Monthly Churn**: 2.5% +- **Gross Revenue Retention**: 97.5% + +## Strategic Initiatives Impact + +### Planned Hiring (10 Engineers) +- **Additional Monthly Cost**: ~$150,000 +- **Revised Burn Rate**: $650,000/month +- **Adjusted Runway**: 15.4 months + +### European Expansion +- **Setup Costs**: ~$200,000 (one-time) +- **Ongoing Operations**: ~$50,000/month additional + +## Series B Fundraising Timeline +- **Target Amount**: $30,000,000 +- **Recommended Timing**: Begin conversations in Q3 2024 +- **Runway Buffer**: Maintain 12+ months post-raise + +## Risk Assessment + +### High Priority Risks +1. **AWS Dependency**: 70% of COGS tied to single provider +2. **Key Personnel Risk**: 3 critical engineers requiring retention packages +3. **Market Competition**: Big Tech entering space + +### Mitigation Strategies +- Diversify cloud providers (reduce AWS to <50%) +- Implement equity refresh program for key employees +- Accelerate product development to maintain competitive advantage + +## Recommendations + +### Immediate Actions (Q2 2024) +1. **Accelerate Hiring**: Front-load engineering hires to maximize development time +2. **Revenue Optimization**: Focus on enterprise expansion (higher LTV) +3. **Cost Management**: Implement quarterly budget reviews + +### Medium-term Strategy (Q3-Q4 2024) +1. **Series B Preparation**: Begin investor conversations by July 2024 +2. **Market Expansion**: Validate European demand before major investment +3. **Product Development**: Prioritize AI code review feature completion + +## Financial Health Score: 8.5/10 +- **Strengths**: Strong runway, growing ARR, healthy unit economics +- **Areas for Improvement**: AWS dependency, competitive positioning + +--- +*This report was generated using current financial data and company metrics. For detailed analysis or specific scenarios, consult with the financial-analyst agent.* \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/output_reports/hiring_decision.md b/claude_code_sdk/chief_of_staff_agent/output_reports/hiring_decision.md new file mode 100644 index 00000000..7a082fa3 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/output_reports/hiring_decision.md @@ -0,0 +1,139 @@ +# Budget Impact Analysis: Hiring 3 Senior Engineers +**Date**: Q2 2024 +**Analysis Type**: Financial Impact Assessment +**Decision**: Hiring 3 Senior Engineers + +--- + +## Executive Summary + +**Recommendation**: **PROCEED WITH HIRING** - Moderate risk with high upside potential + +The analysis shows that hiring 3 senior engineers will increase our monthly burn rate by 13% ($65K/month) while reducing runway by 2.3 months. However, given our strong 15% MoM revenue growth and strategic product timeline, this investment is justified and positions us well for continued growth. + +--- + +## 1. Total Cost Analysis + +### Base Compensation (Per Engineer) +- **Base Salary**: $200,000 annually ($16,667 monthly) +- **Equity**: 0.2% (median of 0.1-0.3% range) +- **Total Compensation**: $220,000 annually + +### Fully Loaded Costs (3 Engineers) +- **Annual Base Salaries**: $600,000 +- **Monthly Base Salaries**: $50,000 +- **Benefits & Overhead** (30%): $180,000 annually / $15,000 monthly +- **Total Annual Loaded Cost**: $780,000 +- **Total Monthly Loaded Cost**: $65,000 + +### One-Time Costs +- **Recruiting Fees**: $90,000 (3 x $30,000) +- **Equipment & Onboarding**: $15,000 (3 x $5,000) +- **Total One-Time Investment**: $105,000 + +--- + +## 2. Burn Rate Impact + +- **Current Monthly Burn**: $500,000 +- **New Monthly Burn**: $565,000 +- **Percentage Increase**: 13% +- **Quarterly Impact**: $195,000 additional burn per quarter + +--- + +## 3. Runway Analysis + +- **Current Runway**: 20 months (until September 2025) +- **New Runway**: 17.7 months (until February 2025) +- **Runway Reduction**: 2.3 months +- **Break-even Timeline**: With 15% MoM revenue growth, break-even moves from ~18 months to ~15 months + +--- + +## 4. ROI Considerations + +### Engineering Capacity Impact +- **Current Engineering Team**: 25 engineers +- **New Team Size**: 28 engineers (+12% capacity) +- **Estimated Velocity Increase**: 9% (from hiring tool calculation) + +### Revenue Impact Potential +Based on revenue forecast data: +- Current ARR: $2.4M (growing 15% MoM) +- Projected ARR by Dec 2024: $5.55M +- **If new engineers accelerate growth by 2-3%**: Additional $300K-500K ARR by year-end +- **Time to Productivity**: 2-3 months for senior engineers + +### Financial Returns +- **Monthly ARR increase needed to justify**: $65,000 / 12 = $5,417 +- **Annual ARR increase needed**: $780,000 +- **Current trajectory already supports this** with strong 15% MoM growth + +--- + +## 5. Alternative Options Considered + +### Option A: Staggered Hiring +- Hire 1 engineer now, 2 in Q3 +- Reduces immediate burn impact by $43K monthly +- Allows validation of productivity gains + +### Option B: Contract vs Full-Time +- 3 senior contractors at ~$150/hour (65% premium) +- Monthly cost: ~$78K (20% higher) +- More flexibility but higher cost and less commitment + +### Option C: Mixed Seniority +- 2 senior engineers + 1 junior engineer +- Reduces monthly cost by ~$7K +- May slow initial velocity but builds pipeline + +--- + +## 6. Risk Factors + +### Financial Risks +- **Fundraising Pressure**: Runway reduction puts more pressure on Series B timing +- **Market Conditions**: Economic downturn could affect enterprise sales growth +- **Revenue Concentration**: Top 5 customers represent 30% of revenue + +### Operational Risks +- **Integration Time**: 2-3 months before full productivity +- **Management Bandwidth**: 28 engineers may stress current EM capacity +- **Technical Debt**: May slow feature development despite more engineers + +--- + +## Final Recommendation & Action Plan + +### Key Supporting Factors: +1. **Strong Revenue Growth**: 15% MoM growth provides confidence in ROI +2. **Healthy Runway Buffer**: 17.7 months still provides adequate runway +3. **Strategic Timing**: Q2 product launch timing aligns with hiring needs +4. **Market Opportunity**: $5B growing market supports aggressive investment + +### Recommended Approach: +1. **Hire all 3 engineers immediately** to maximize Q2 product impact +2. **Accelerate Series B conversations** to Q4 2024 (vs. original timeline) +3. **Set aggressive revenue targets**: 18% MoM growth to justify investment +4. **Monitor closely**: Weekly burn rate tracking for first 3 months + +### Success Metrics: +- Achieve 18% MoM revenue growth by Q4 2024 +- Launch AI code review feature on schedule +- Maintain runway above 15 months through year-end +- Begin Series B fundraising by October 2024 + +--- + +## Next Steps + +1. **Immediate**: Begin recruitment process for 3 senior backend engineers +2. **Week 1**: Update board on hiring decision and runway impact +3. **Month 1**: Establish weekly burn rate monitoring dashboard +4. **Month 3**: Evaluate productivity impact and adjust revenue targets +5. **Month 6**: Initiate Series B fundraising conversations + +This hiring decision aligns with TechStart's growth trajectory and positions the company to capitalize on market opportunity while maintaining financial discipline. \ No newline at end of file diff --git a/claude_code_sdk/chief_of_staff_agent/scripts/decision_matrix.py b/claude_code_sdk/chief_of_staff_agent/scripts/decision_matrix.py new file mode 100755 index 00000000..0a6eed6c --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/scripts/decision_matrix.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +""" +Decision Matrix Tool - Strategic decision framework for complex choices +Custom Python script for the Chief of Staff agent +""" + +import argparse +import json + + +def create_decision_matrix(options: list[dict], criteria: list[dict]) -> dict: + """Create a weighted decision matrix for strategic choices""" + + results = {"options": [], "winner": None, "analysis": {}} + + for option in options: + option_scores = { + "name": option["name"], + "scores": {}, + "weighted_scores": {}, + "total": 0, + "pros": [], + "cons": [], + "verdict": "", + } + + # Calculate scores for each criterion + for criterion in criteria: + crit_name = criterion["name"] + weight = criterion["weight"] + + # Get score for this option on this criterion (1-10) + score = option.get(crit_name, 5) + weighted = score * weight + + option_scores["scores"][crit_name] = score + option_scores["weighted_scores"][crit_name] = round(weighted, 2) + option_scores["total"] += weighted + + # Track pros and cons + if score >= 8: + option_scores["pros"].append(f"Excellent {crit_name}") + elif score >= 6: + option_scores["pros"].append(f"Good {crit_name}") + elif score <= 3: + option_scores["cons"].append(f"Poor {crit_name}") + elif score <= 5: + option_scores["cons"].append(f"Weak {crit_name}") + + option_scores["total"] = round(option_scores["total"], 2) + + # Generate verdict + if option_scores["total"] >= 8: + option_scores["verdict"] = "STRONGLY RECOMMENDED" + elif option_scores["total"] >= 6.5: + option_scores["verdict"] = "RECOMMENDED" + elif option_scores["total"] >= 5: + option_scores["verdict"] = "ACCEPTABLE" + else: + option_scores["verdict"] = "NOT RECOMMENDED" + + results["options"].append(option_scores) + + # Find winner + results["options"].sort(key=lambda x: x["total"], reverse=True) + results["winner"] = results["options"][0]["name"] + + # Generate analysis + results["analysis"] = generate_analysis(results["options"]) + + return results + + +def generate_analysis(options: list[dict]) -> dict: + """Generate strategic analysis of the decision""" + + analysis = { + "clear_winner": False, + "margin": 0, + "recommendation": "", + "key_differentiators": [], + "risks": [], + } + + if len(options) >= 2: + margin = options[0]["total"] - options[1]["total"] + analysis["margin"] = round(margin, 2) + analysis["clear_winner"] = margin > 1.5 + + if analysis["clear_winner"]: + analysis["recommendation"] = ( + f"Strongly recommend {options[0]['name']} with {margin:.1f} point advantage" + ) + elif margin > 0.5: + analysis["recommendation"] = ( + f"Recommend {options[0]['name']} but consider {options[1]['name']} as viable alternative" + ) + else: + analysis["recommendation"] = ( + f"Close decision between {options[0]['name']} and {options[1]['name']} - consider additional factors" + ) + + # Find key differentiators + top = options[0] + for criterion in top["scores"]: + if top["scores"][criterion] >= 8: + analysis["key_differentiators"].append(criterion) + + # Identify risks + if top["total"] < 6: + analysis["risks"].append("Overall score below recommended threshold") + if len(top["cons"]) > len(top["pros"]): + analysis["risks"].append("More weaknesses than strengths") + + return analysis + + +def main(): + parser = argparse.ArgumentParser(description="Strategic decision matrix tool") + parser.add_argument("--scenario", type=str, help="Predefined scenario") + parser.add_argument("--input", type=str, help="JSON file with options and criteria") + parser.add_argument("--format", choices=["json", "text"], default="text") + + args = parser.parse_args() + + # Default scenario: Build vs Buy vs Partner + if args.scenario == "build-buy-partner": + options = [ + { + "name": "Build In-House", + "cost": 3, # 1-10, higher is better (so 3 = high cost) + "time_to_market": 2, # 2 = slow + "control": 10, # 10 = full control + "quality": 8, # 8 = high quality potential + "scalability": 9, # 9 = very scalable + "risk": 3, # 3 = high risk + }, + { + "name": "Buy Solution", + "cost": 5, + "time_to_market": 9, + "control": 4, + "quality": 7, + "scalability": 6, + "risk": 7, + }, + { + "name": "Strategic Partnership", + "cost": 7, + "time_to_market": 7, + "control": 6, + "quality": 7, + "scalability": 8, + "risk": 5, + }, + ] + + criteria = [ + {"name": "cost", "weight": 0.20}, + {"name": "time_to_market", "weight": 0.25}, + {"name": "control", "weight": 0.15}, + {"name": "quality", "weight": 0.20}, + {"name": "scalability", "weight": 0.10}, + {"name": "risk", "weight": 0.10}, + ] + elif args.input: + with open(args.input) as f: + data = json.load(f) + options = data["options"] + criteria = data["criteria"] + else: + # Default hiring scenario + options = [ + { + "name": "Hire 3 Senior Engineers", + "cost": 4, + "productivity": 9, + "time_to_impact": 8, + "team_growth": 7, + "runway_impact": 3, + }, + { + "name": "Hire 5 Junior Engineers", + "cost": 7, + "productivity": 5, + "time_to_impact": 4, + "team_growth": 9, + "runway_impact": 5, + }, + ] + criteria = [ + {"name": "cost", "weight": 0.25}, + {"name": "productivity", "weight": 0.30}, + {"name": "time_to_impact", "weight": 0.20}, + {"name": "team_growth", "weight": 0.15}, + {"name": "runway_impact", "weight": 0.10}, + ] + + matrix = create_decision_matrix(options, criteria) + + if args.format == "json": + print(json.dumps(matrix, indent=2)) + else: + # Text output + print("šŸŽÆ STRATEGIC DECISION MATRIX") + print("=" * 60) + + print("\nOPTIONS EVALUATED:") + for i, opt in enumerate(matrix["options"], 1): + print(f"\n{i}. {opt['name']}") + print("-" * 40) + print(f" Total Score: {opt['total']}/10 - {opt['verdict']}") + + print(" Strengths:") + for pro in opt["pros"][:3]: + print(f" āœ“ {pro}") + + if opt["cons"]: + print(" Weaknesses:") + for con in opt["cons"][:3]: + print(f" āœ— {con}") + + print("\n" + "=" * 60) + print("RECOMMENDATION:") + print("-" * 40) + analysis = matrix["analysis"] + print(f"Winner: {matrix['winner']}") + print(f"Margin: {analysis['margin']} points") + print(f"\n{analysis['recommendation']}") + + if analysis["key_differentiators"]: + print(f"\nKey advantages: {', '.join(analysis['key_differentiators'])}") + + if analysis["risks"]: + print("\nāš ļø Risks to consider:") + for risk in analysis["risks"]: + print(f" - {risk}") + + +if __name__ == "__main__": + main() diff --git a/claude_code_sdk/chief_of_staff_agent/scripts/financial_forecast.py b/claude_code_sdk/chief_of_staff_agent/scripts/financial_forecast.py new file mode 100755 index 00000000..1d582464 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/scripts/financial_forecast.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +""" +Financial Forecast Tool - Advanced financial modeling for strategic decisions +Custom Python tool executed via Bash by the Chief of Staff agent +""" + +import argparse +import json + + +def forecast_financials(current_arr, growth_rate, months, burn_rate): + """Generate financial forecast with multiple scenarios""" + + forecasts = {"base_case": [], "optimistic": [], "pessimistic": [], "metrics": {}} + + # Base case + arr = current_arr + for month in range(1, months + 1): + arr = arr * (1 + growth_rate) + monthly_revenue = arr / 12 + net_burn = burn_rate - monthly_revenue + runway = -1 if net_burn <= 0 else (10_000_000 / net_burn) # Assuming $10M in bank + + forecasts["base_case"].append( + { + "month": month, + "arr": round(arr), + "monthly_revenue": round(monthly_revenue), + "net_burn": round(net_burn), + "runway_months": round(runway, 1) if runway > 0 else "infinite", + } + ) + + # Optimistic (1.5x growth) + arr = current_arr + for month in range(1, months + 1): + arr = arr * (1 + growth_rate * 1.5) + forecasts["optimistic"].append({"month": month, "arr": round(arr)}) + + # Pessimistic (0.5x growth) + arr = current_arr + for month in range(1, months + 1): + arr = arr * (1 + growth_rate * 0.5) + forecasts["pessimistic"].append({"month": month, "arr": round(arr)}) + + # Key metrics + forecasts["metrics"] = { + "months_to_profitability": calculate_profitability_date(forecasts["base_case"]), + "cash_required": calculate_cash_needed(forecasts["base_case"]), + "break_even_arr": burn_rate * 12, + "current_burn_multiple": round(burn_rate / (current_arr / 12), 2), + } + + return forecasts + + +def calculate_profitability_date(forecast): + """Find when company becomes profitable""" + for entry in forecast: + if entry["net_burn"] <= 0: + return entry["month"] + return -1 # Not profitable in forecast period + + +def calculate_cash_needed(forecast): + """Calculate total cash needed until profitability""" + total_burn = 0 + for entry in forecast: + if entry["net_burn"] > 0: + total_burn += entry["net_burn"] + else: + break + return round(total_burn) + + +def main(): + parser = argparse.ArgumentParser(description="Financial forecasting tool") + parser.add_argument("--arr", type=float, default=2400000, help="Current ARR") + parser.add_argument("--growth", type=float, default=0.15, help="Monthly growth rate") + parser.add_argument("--months", type=int, default=12, help="Forecast period") + parser.add_argument("--burn", type=float, default=500000, help="Monthly burn rate") + parser.add_argument("--format", choices=["json", "text"], default="text", help="Output format") + + args = parser.parse_args() + + forecast = forecast_financials(args.arr, args.growth, args.months, args.burn) + + if args.format == "json": + print(json.dumps(forecast, indent=2)) + else: + # Text output for human reading + print("šŸ“Š FINANCIAL FORECAST") + print("=" * 50) + print(f"Current ARR: ${args.arr:,.0f}") + print(f"Growth Rate: {args.growth * 100:.1f}% monthly") + print(f"Burn Rate: ${args.burn:,.0f}/month") + print() + + print("BASE CASE PROJECTION:") + print("-" * 30) + for i in [2, 5, 11]: # Show months 3, 6, 12 + if i < len(forecast["base_case"]): + m = forecast["base_case"][i] + print(f"Month {m['month']:2}: ARR ${m['arr']:,} | Runway {m['runway_months']}") + + print() + print("KEY METRICS:") + print("-" * 30) + metrics = forecast["metrics"] + if metrics["months_to_profitability"] > 0: + print(f"Profitability: Month {metrics['months_to_profitability']}") + else: + print("Profitability: Not in forecast period") + print(f"Cash Needed: ${metrics['cash_required']:,}") + print(f"Burn Multiple: {metrics['current_burn_multiple']}x") + + print() + print("SCENARIO ANALYSIS:") + print("-" * 30) + last_base = forecast["base_case"][-1]["arr"] + last_opt = forecast["optimistic"][-1]["arr"] + last_pess = forecast["pessimistic"][-1]["arr"] + print(f"12-Month ARR: ${last_pess:,} to ${last_opt:,}") + print(f"Range: {((last_opt - last_pess) / last_base * 100):.0f}% variance") + + +if __name__ == "__main__": + main() diff --git a/claude_code_sdk/chief_of_staff_agent/scripts/hiring_impact.py b/claude_code_sdk/chief_of_staff_agent/scripts/hiring_impact.py new file mode 100755 index 00000000..41d5a32e --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/scripts/hiring_impact.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Hiring Impact Calculator for TechStart Inc +Calculates the financial impact of hiring engineers +""" + +import json +import sys + + +def calculate_hiring_impact(num_engineers, salary_per_engineer=200000): + """ + Calculate the financial impact of hiring engineers. + + Args: + num_engineers: Number of engineers to hire + salary_per_engineer: Annual salary per engineer (default: $200K) + + Returns: + Dictionary with financial impact metrics + """ + # Current financials (from CLAUDE.md) + CURRENT_BURN_MONTHLY = 500000 # $500K/month + CURRENT_RUNWAY_MONTHS = 20 # 20 months + CASH_IN_BANK = 10000000 # $10M + + # Calculate loaded cost (salary + benefits + taxes = salary * 1.3) + annual_loaded_cost_per_engineer = salary_per_engineer * 1.3 + monthly_cost_per_engineer = annual_loaded_cost_per_engineer / 12 + + # Total monthly cost increase + total_monthly_increase = monthly_cost_per_engineer * num_engineers + + # New burn rate + new_burn_monthly = CURRENT_BURN_MONTHLY + total_monthly_increase + + # New runway + new_runway_months = CASH_IN_BANK / new_burn_monthly + runway_reduction_months = CURRENT_RUNWAY_MONTHS - new_runway_months + + # Calculate potential revenue impact (assumption: engineers increase velocity by 15%) + velocity_increase = 0.15 * num_engineers / 5 # Assuming 5 engineers = 15% increase + + # Recommendation + if runway_reduction_months > 3: + recommendation = "HIGH RISK: Significant runway reduction. Consider phased hiring." + elif runway_reduction_months > 1.5: + recommendation = "MODERATE RISK: Manageable if revenue growth accelerates." + else: + recommendation = "LOW RISK: Minimal impact on runway. Proceed if talent is available." + + return { + "num_engineers": num_engineers, + "salary_per_engineer": salary_per_engineer, + "monthly_cost_per_engineer": round(monthly_cost_per_engineer, 2), + "total_monthly_increase": round(total_monthly_increase, 2), + "current_burn_monthly": CURRENT_BURN_MONTHLY, + "new_burn_monthly": round(new_burn_monthly, 2), + "current_runway_months": CURRENT_RUNWAY_MONTHS, + "new_runway_months": round(new_runway_months, 2), + "runway_reduction_months": round(runway_reduction_months, 2), + "velocity_increase_percent": round(velocity_increase * 100, 1), + "recommendation": recommendation, + } + + +def main(): + # Parse command line arguments + if len(sys.argv) < 2: + print("Usage: python hiring_impact.py [salary_per_engineer]") + sys.exit(1) + + num_engineers = int(sys.argv[1]) + salary = int(sys.argv[2]) if len(sys.argv) > 2 else 200000 + + # Calculate impact + impact = calculate_hiring_impact(num_engineers, salary) + + # Output as JSON for easy parsing + print(json.dumps(impact, indent=2)) + + # Also print summary + print("\n=== HIRING IMPACT SUMMARY ===") + print(f"Hiring {impact['num_engineers']} engineers at ${impact['salary_per_engineer']:,}/year") + print(f"Monthly burn increase: ${impact['total_monthly_increase']:,.0f}") + print(f"New burn rate: ${impact['new_burn_monthly']:,.0f}/month") + print( + f"Runway change: {impact['current_runway_months']:.1f} → {impact['new_runway_months']:.1f} months" + ) + print(f"Velocity increase: +{impact['velocity_increase_percent']}%") + print(f"\n{impact['recommendation']}") + + +if __name__ == "__main__": + main() diff --git a/claude_code_sdk/chief_of_staff_agent/scripts/simple_calculation.py b/claude_code_sdk/chief_of_staff_agent/scripts/simple_calculation.py new file mode 100644 index 00000000..ac553f03 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/scripts/simple_calculation.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Simple script to demonstrate Bash tool usage from an agent. +Calculates basic metrics that an AI Chief of Staff might need. +""" + +import json +import sys + + +def calculate_metrics(total_runway, monthly_burn): + """Calculate key financial metrics.""" + runway_months = total_runway / monthly_burn + quarterly_burn = monthly_burn * 3 + + metrics = { + "monthly_burn": monthly_burn, + "runway_months": runway_months, + "total_runway_dollars": total_runway, + "quarterly_burn": quarterly_burn, + "burn_rate_daily": round(monthly_burn / 30, 2), + } + + return metrics + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: python simple_calculation.py ") + sys.exit(1) + + try: + runway = float(sys.argv[1]) + burn = float(sys.argv[2]) + + results = calculate_metrics(runway, burn) + + print(json.dumps(results, indent=2)) + + except ValueError: + print("Error: Arguments must be numbers") + sys.exit(1) diff --git a/claude_code_sdk/chief_of_staff_agent/scripts/talent_scorer.py b/claude_code_sdk/chief_of_staff_agent/scripts/talent_scorer.py new file mode 100755 index 00000000..8d47b8e5 --- /dev/null +++ b/claude_code_sdk/chief_of_staff_agent/scripts/talent_scorer.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Talent Scorer Tool - Evaluate and rank candidates based on multiple criteria +Custom Python tool for the Recruiter subagent +""" + +import argparse +import json + + +def score_candidate(candidate: dict) -> dict: + """Score a candidate based on weighted criteria""" + + weights = { + "technical_skills": 0.30, + "experience_years": 0.20, + "startup_experience": 0.15, + "education": 0.10, + "culture_fit": 0.15, + "salary_fit": 0.10, + } + + scores = {} + + # Technical skills (0-100) + tech_match = candidate.get("tech_skills_match", 70) + scores["technical_skills"] = min(100, tech_match) + + # Experience (0-100, peaks at 8 years) + years = candidate.get("years_experience", 5) + if years <= 2: + scores["experience_years"] = 40 + elif years <= 5: + scores["experience_years"] = 70 + elif years <= 8: + scores["experience_years"] = 90 + else: + scores["experience_years"] = 85 # Slight decline for overqualified + + # Startup experience (0-100) + scores["startup_experience"] = 100 if candidate.get("has_startup_exp", False) else 50 + + # Education (0-100) + education = candidate.get("education", "bachelors") + edu_scores = {"high_school": 40, "bachelors": 70, "masters": 85, "phd": 90} + scores["education"] = edu_scores.get(education, 70) + + # Culture fit (0-100) + scores["culture_fit"] = candidate.get("culture_score", 75) + + # Salary fit (0-100, penalize if too high or too low) + salary = candidate.get("salary_expectation", 150000) + target = candidate.get("target_salary", 160000) + diff_pct = abs(salary - target) / target + scores["salary_fit"] = max(0, 100 - (diff_pct * 200)) + + # Calculate weighted total + total = sum(scores[k] * weights[k] for k in weights) + + return { + "name": candidate.get("name", "Unknown"), + "total_score": round(total, 1), + "scores": scores, + "recommendation": get_recommendation(total), + "risk_factors": identify_risks(candidate, scores), + } + + +def get_recommendation(score: float) -> str: + """Generate hiring recommendation based on score""" + if score >= 85: + return "STRONG HIRE - Extend offer immediately" + elif score >= 75: + return "HIRE - Good candidate, proceed with offer" + elif score >= 65: + return "MAYBE - Consider if no better options" + elif score >= 50: + return "WEAK - Significant concerns, likely pass" + else: + return "NO HIRE - Does not meet requirements" + + +def identify_risks(candidate: dict, scores: dict) -> list[str]: + """Identify potential risk factors""" + risks = [] + + if scores["technical_skills"] < 60: + risks.append("Technical skills below requirement") + + if candidate.get("years_experience", 0) < 2: + risks.append("Limited experience, will need mentorship") + + if not candidate.get("has_startup_exp", False): + risks.append("No startup experience, may struggle with ambiguity") + + if scores["salary_fit"] < 50: + risks.append("Salary expectations misaligned") + + if candidate.get("notice_period_days", 14) > 30: + risks.append(f"Long notice period: {candidate.get('notice_period_days')} days") + + return risks + + +def rank_candidates(candidates: list[dict]) -> list[dict]: + """Rank multiple candidates""" + scored = [score_candidate(c) for c in candidates] + return sorted(scored, key=lambda x: x["total_score"], reverse=True) + + +def main(): + parser = argparse.ArgumentParser(description="Candidate scoring tool") + parser.add_argument("--input", type=str, help="JSON file with candidate data") + parser.add_argument("--name", type=str, help="Candidate name") + parser.add_argument("--years", type=int, default=5, help="Years of experience") + parser.add_argument("--tech-match", type=int, default=70, help="Technical skills match (0-100)") + parser.add_argument("--salary", type=int, default=150000, help="Salary expectation") + parser.add_argument("--startup", action="store_true", help="Has startup experience") + parser.add_argument("--format", choices=["json", "text"], default="text") + + args = parser.parse_args() + + if args.input: + # Score multiple candidates from file + with open(args.input) as f: + candidates = json.load(f) + results = rank_candidates(candidates) + else: + # Score single candidate from args + candidate = { + "name": args.name or "Candidate", + "years_experience": args.years, + "tech_skills_match": args.tech_match, + "salary_expectation": args.salary, + "has_startup_exp": args.startup, + "target_salary": 160000, + "culture_score": 75, + "education": "bachelors", + } + results = [score_candidate(candidate)] + + if args.format == "json": + print(json.dumps(results, indent=2)) + else: + # Text output + print("šŸŽÆ CANDIDATE EVALUATION") + print("=" * 50) + + for i, result in enumerate(results, 1): + print(f"\n#{i}. {result['name']}") + print("-" * 30) + print(f"Overall Score: {result['total_score']}/100") + print(f"Recommendation: {result['recommendation']}") + + print("\nScores by Category:") + for category, score in result["scores"].items(): + print(f" {category.replace('_', ' ').title()}: {score:.0f}/100") + + if result["risk_factors"]: + print("\nāš ļø Risk Factors:") + for risk in result["risk_factors"]: + print(f" - {risk}") + + if len(results) > 1: + print("\n" + "=" * 50) + print("RANKING SUMMARY:") + for i, r in enumerate(results[:3], 1): + print( + f"{i}. {r['name']}: {r['total_score']:.1f} - {r['recommendation'].split(' - ')[0]}" + ) + + +if __name__ == "__main__": + main() diff --git a/claude_code_sdk/observability_agent/agent.py b/claude_code_sdk/observability_agent/agent.py new file mode 100644 index 00000000..a52c026e --- /dev/null +++ b/claude_code_sdk/observability_agent/agent.py @@ -0,0 +1,111 @@ +""" +Observability Agent - GitHub monitoring with MCP servers +Built on top of the research agent pattern +""" + +import asyncio +import os +from collections.abc import Callable +from typing import Any + +from dotenv import load_dotenv + +from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient + +load_dotenv() + + +def get_activity_text(msg) -> str | None: + """Extract activity text from a message""" + try: + if "Assistant" in msg.__class__.__name__: + if hasattr(msg, "content") and msg.content: + first_content = msg.content[0] if isinstance(msg.content, list) else msg.content + if hasattr(first_content, "name"): + return f"šŸ¤– Using: {first_content.name}()" + return "šŸ¤– Thinking..." + elif "User" in msg.__class__.__name__: + return "āœ“ Tool completed" + except (AttributeError, IndexError): + pass + return None + + +def print_activity(msg) -> None: + """Print activity to console""" + activity = get_activity_text(msg) + if activity: + print(activity) + + +# Pre-configured GitHub MCP server +GITHUB_MCP_SERVER = { + "github": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server", + ], + "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": os.environ.get("GITHUB_TOKEN")}, + } +} + + +async def send_query( + prompt: str, + activity_handler: Callable[[Any], None | Any] = print_activity, + continue_conversation: bool = False, + mcp_servers: dict[str, Any] | None = None, + use_github: bool = True, +) -> str | None: + """ + Send a query to the observability agent with MCP server support. + + Args: + prompt: The query to send + activity_handler: Callback for activity updates + continue_conversation: Continue the previous conversation if True + mcp_servers: Custom MCP servers configuration + use_github: Include GitHub MCP server (default: True) + + Returns: + The final result text or None if no result + """ + # Build MCP servers config + servers = {} + if use_github and os.environ.get("GITHUB_TOKEN"): + servers.update(GITHUB_MCP_SERVER) + if mcp_servers: + servers.update(mcp_servers) + + options = ClaudeCodeOptions( + model="claude-sonnet-4-20250514", + allowed_tools=["mcp__github", "WebSearch", "Read"], + continue_conversation=continue_conversation, + system_prompt="You are an observability agent specialized in monitoring GitHub repositories and CI/CD workflows", + mcp_servers=servers if servers else None, + permission_mode="acceptEdits", + ) + + result = None + + try: + async with ClaudeSDKClient(options=options) as agent: + await agent.query(prompt=prompt) + async for msg in agent.receive_response(): + if asyncio.iscoroutinefunction(activity_handler): + await activity_handler(msg) + else: + activity_handler(msg) + + if hasattr(msg, "result"): + result = msg.result + except Exception as e: + print(f"āŒ Query error: {e}") + raise + + return result diff --git a/claude_code_sdk/observability_agent/architecture_diagram.md b/claude_code_sdk/observability_agent/architecture_diagram.md new file mode 100644 index 00000000..b25de730 --- /dev/null +++ b/claude_code_sdk/observability_agent/architecture_diagram.md @@ -0,0 +1,36 @@ +# Observability Agent Architecture + +```mermaid +graph TD + User[User] --> Agent[Observability Agent] + Agent --> GitHub[GitHub MCP Server] + + Agent --> Tools[Tools] + Tools --> WebSearch[WebSearch] + Tools --> Read[Read Files] + + GitHub --> Docker[Docker Container] + Docker --> API[GitHub API] + + style Agent fill:#f9f,stroke:#333,stroke-width:3px + style GitHub fill:#bbf,stroke:#333,stroke-width:2px +``` + + +# Communication Flow Diagram + +```mermaid +sequenceDiagram + participant User + participant Agent + participant MCP as GitHub MCP + participant API as GitHub API + + User->>Agent: Query about repo + Agent->>MCP: Connect via Docker + Agent->>MCP: Request data + MCP->>API: Fetch info + API-->>MCP: Return data + MCP-->>Agent: Process results + Agent-->>User: Display answer +``` \ No newline at end of file diff --git a/claude_code_sdk/observability_agent/docker/Dockerfile b/claude_code_sdk/observability_agent/docker/Dockerfile new file mode 100644 index 00000000..b4b6cdc3 --- /dev/null +++ b/claude_code_sdk/observability_agent/docker/Dockerfile @@ -0,0 +1,36 @@ +FROM python:3.11 + +WORKDIR /app + +# Install system dependencies including Docker CLI +RUN apt-get update && apt-get install -y \ + curl \ + git \ + ca-certificates \ + gnupg \ + && install -m 0755 -d /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ + "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" > /etc/apt/sources.list.d/docker.list \ + && apt-get update \ + && apt-get install -y docker-ce-cli \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN npm install -g @anthropic-ai/claude-code + +COPY observability_agent ./observability_agent + +RUN pip install --no-cache-dir \ + claude-code-sdk \ + fastapi \ + python-dotenv \ + uvicorn[standard] \ + mcp-server-git + +RUN claude --version + +EXPOSE 8000 + +CMD ["python", "-m", "observability_agent.web.app"] \ No newline at end of file diff --git a/claude_code_sdk/observability_agent/docker/docker-compose.yml b/claude_code_sdk/observability_agent/docker/docker-compose.yml new file mode 100644 index 00000000..91aa3506 --- /dev/null +++ b/claude_code_sdk/observability_agent/docker/docker-compose.yml @@ -0,0 +1,18 @@ +services: + observability-agent: + build: + context: ../.. + dockerfile: observability_agent/docker/Dockerfile + ports: + - "8001:8000" # Different port to avoid conflict with research agent + env_file: + - ../../.env + environment: + - DOCKER_HOST=unix:///var/run/docker.sock + volumes: + # Mount Docker socket for MCP server containers + - /var/run/docker.sock:/var/run/docker.sock + dns: + - 8.8.8.8 + - 8.8.4.4 + restart: unless-stopped \ No newline at end of file diff --git a/claude_code_sdk/pyproject.toml b/claude_code_sdk/pyproject.toml new file mode 100644 index 00000000..36209b58 --- /dev/null +++ b/claude_code_sdk/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "cc-sdk-tutorial" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "claude-code-sdk>=0.0.20", + "ipykernel>=6.29.5", + "mcp-server-git>=2025.1.14", + "python-dotenv>=1.1.1", +] diff --git a/claude_code_sdk/research_agent/agent.py b/claude_code_sdk/research_agent/agent.py new file mode 100644 index 00000000..79fb0290 --- /dev/null +++ b/claude_code_sdk/research_agent/agent.py @@ -0,0 +1,87 @@ +""" +Research Agent - Using Claude SDK with built-in session management +""" + +import asyncio +from collections.abc import Callable +from typing import Any + +from dotenv import load_dotenv + +from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient + +load_dotenv() + + +def get_activity_text(msg) -> str | None: + """Extract activity text from a message""" + try: + if "Assistant" in msg.__class__.__name__: + # Check if content exists and has items + if hasattr(msg, "content") and msg.content: + first_content = msg.content[0] if isinstance(msg.content, list) else msg.content + if hasattr(first_content, "name"): + return f"šŸ¤– Using: {first_content.name}()" + return "šŸ¤– Thinking..." + elif "User" in msg.__class__.__name__: + return "āœ“ Tool completed" + except (AttributeError, IndexError): + pass + return None + + +def print_activity(msg) -> None: + """Print activity to console""" + activity = get_activity_text(msg) + if activity: + print(activity) + + +async def send_query( + prompt: str, + activity_handler: Callable[[Any], None | Any] = print_activity, + continue_conversation: bool = False, +) -> str | None: + """ + Send a query using the Claude SDK with minimal overhead. + + Args: + prompt: The query to send + activity_handler: Callback for activity updates + continue_conversation: Continue the previous conversation if True + + Note: + For the activity_handler - we support both sync and async handlers + to make the module work in different contexts: + - Sync handlers (like print_activity) for simple console output + - Async handlers for web apps that need WebSocket/network I/O + In production, you'd typically use just one type based on your needs + + Returns: + The final result text or None if no result + """ + options = ClaudeCodeOptions( + model="claude-sonnet-4-20250514", + allowed_tools=["WebSearch", "Read"], + continue_conversation=continue_conversation, + system_prompt="You are a research agent specialized in AI", + ) + + result = None + + try: + async with ClaudeSDKClient(options=options) as agent: + await agent.query(prompt=prompt) + async for msg in agent.receive_response(): + if asyncio.iscoroutinefunction(activity_handler): + await activity_handler(msg) + else: + activity_handler(msg) + + if hasattr(msg, "result"): + result = msg.result + except Exception as e: + print(f"āŒ Query error: {e}") + raise + + return result diff --git a/claude_code_sdk/research_agent/architecture_diagram.md b/claude_code_sdk/research_agent/architecture_diagram.md new file mode 100644 index 00000000..03c3cf13 --- /dev/null +++ b/claude_code_sdk/research_agent/architecture_diagram.md @@ -0,0 +1,32 @@ +# Research Agent Architecture + +```mermaid +graph TD + User[User] --> Agent[Research Agent] + Agent --> Tools[Tools] + + Tools --> WebSearch[WebSearch] + Tools --> Read[Read Files/Images] + + style Agent fill:#f9f,stroke:#333,stroke-width:3px + style Tools fill:#bbf,stroke:#333,stroke-width:2px +``` + +# Communication Flow Diagram + +```mermaid +sequenceDiagram + participant User + participant Agent + participant Tools + + User->>Agent: Query + + loop Until Complete + Agent->>Agent: Think + Agent->>Tools: Search/Read + Tools-->>Agent: Results + end + + Agent-->>User: Answer +``` \ No newline at end of file diff --git a/claude_code_sdk/research_agent/projects_claude.png b/claude_code_sdk/research_agent/projects_claude.png new file mode 100644 index 00000000..39956382 Binary files /dev/null and b/claude_code_sdk/research_agent/projects_claude.png differ diff --git a/claude_code_sdk/utils/agent_visualizer.py b/claude_code_sdk/utils/agent_visualizer.py new file mode 100644 index 00000000..c92aabe8 --- /dev/null +++ b/claude_code_sdk/utils/agent_visualizer.py @@ -0,0 +1,106 @@ +def print_activity(msg): + if "Assistant" in msg.__class__.__name__: + print( + f"šŸ¤– {'Using: ' + msg.content[0].name + '()' if hasattr(msg.content[0], 'name') else 'Thinking...'}" + ) + elif "User" in msg.__class__.__name__: + print("āœ“ Tool completed") + + +def print_final_result(messages): + """Print the final agent result and cost information""" + # Get the result message (last message) + result_msg = messages[-1] + + # Find the last assistant message with actual content + for msg in reversed(messages): + if msg.__class__.__name__ == "AssistantMessage" and msg.content: + # Check if it has text content (not just tool use) + for block in msg.content: + if hasattr(block, "text"): + print(f"\nšŸ“ Final Result:\n{block.text}") + break + break + + # Print cost if available + if hasattr(result_msg, "total_cost_usd"): + print(f"\nšŸ“Š Cost: ${result_msg.total_cost_usd:.2f}") + + # Print duration if available + if hasattr(result_msg, "duration_ms"): + print(f"ā±ļø Duration: {result_msg.duration_ms / 1000:.2f}s") + + +def visualize_conversation(messages): + """Create a visual representation of the entire agent conversation""" + print("\n" + "=" * 60) + print("šŸ¤– AGENT CONVERSATION TIMELINE") + print("=" * 60 + "\n") + + for i, msg in enumerate(messages): + msg_type = msg.__class__.__name__ + + if msg_type == "SystemMessage": + print("āš™ļø System Initialized") + if hasattr(msg, "data") and "session_id" in msg.data: + print(f" Session: {msg.data['session_id'][:8]}...") + print() + + elif msg_type == "AssistantMessage": + print("šŸ¤– Assistant:") + if msg.content: + for block in msg.content: + if hasattr(block, "text"): + # Text response + text = block.text[:500] + "..." if len(block.text) > 500 else block.text + print(f" šŸ’¬ {text}") + elif hasattr(block, "name"): + # Tool use + tool_name = block.name + print(f" šŸ”§ Using tool: {tool_name}") + + # Show key parameters for certain tools + if hasattr(block, "input") and block.input: + if tool_name == "WebSearch" and "query" in block.input: + print(f' Query: "{block.input["query"]}"') + elif tool_name == "TodoWrite" and "todos" in block.input: + todos = block.input["todos"] + in_progress = [t for t in todos if t["status"] == "in_progress"] + completed = [t for t in todos if t["status"] == "completed"] + print( + f" šŸ“‹ {len(completed)} completed, {len(in_progress)} in progress" + ) + print() + + elif msg_type == "UserMessage": + if msg.content and isinstance(msg.content, list): + for result in msg.content: + if isinstance(result, dict) and result.get("type") == "tool_result": + print("šŸ‘¤ Tool Result Received") + tool_id = result.get("tool_use_id", "unknown")[:8] + print(f" ID: {tool_id}...") + + # Show result summary + if "content" in result: + content = result["content"] + if isinstance(content, str): + # Show more of the content + summary = content[:500] + "..." if len(content) > 500 else content + print(f" šŸ“„ {summary}") + print() + + elif msg_type == "ResultMessage": + print("āœ… Conversation Complete") + if hasattr(msg, "num_turns"): + print(f" Turns: {msg.num_turns}") + if hasattr(msg, "total_cost_usd"): + print(f" Cost: ${msg.total_cost_usd:.2f}") + if hasattr(msg, "duration_ms"): + print(f" Duration: {msg.duration_ms / 1000:.2f}s") + if hasattr(msg, "usage"): + usage = msg.usage + total_tokens = usage.get("input_tokens", 0) + usage.get("output_tokens", 0) + print(f" Tokens: {total_tokens:,}") + print() + + print("=" * 60 + "\n") diff --git a/scripts/validate_notebooks.py b/scripts/validate_notebooks.py index c77c2de2..ede24a0e 100755 --- a/scripts/validate_notebooks.py +++ b/scripts/validate_notebooks.py @@ -56,8 +56,8 @@ def main(): else: print("\nāš ļø Found issues that should be fixed in a separate PR") - # For POC, return 0 even with issues to show detection without blocking - sys.exit(0) + # Exit with error if issues found + sys.exit(1 if has_issues else 0) if __name__ == "__main__":