﻿{
    "cells":  [
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "*Module 4 of 12*\n",
                                     "\n",
                                     "# Multi-Agent Orchestration (Agent SDK)\n",
                                     "\n",
                                     "Module 2 set up a single agent. This module scales it out: a **hub-and-spoke** architecture in which a coordinator agent spawns specialized subagents through the Claude Agent SDK\u0027s `Task` tool. You\u0027ll learn how to spawn subagents in parallel, why their context is isolated by default, how to give subagents narrowly scoped cross-role tools, and how to compose their outputs back into the coordinator\u0027s plan.\n",
                                     "\n",
                                     "\n",
                                     "## 1. Hub-and-Spoke Architecture\n",
                                     "\n",
                                     "A single agent gets brittle as the task grows: the context window fills, instructions interfere with each other, and one specialist persona has to do the work of five. The hub-and-spoke pattern fixes this by splitting the work across processes:\n",
                                     "\n",
                                     "- **Coordinator (the hub)**, owns the user-facing goal, decides what work to delegate, and composes subagent results into the final answer. Usually the higher-capability model (Opus).\n",
                                     "- **Subagents (the spokes)**, each spawned for a single specialized job (research, drafting, validation), with their own system prompt, tool surface, and isolated message history. Often a cheaper model (Sonnet or Haiku).\n",
                                     "\n",
                                     "The Claude Agent SDK exposes this via the built-in `Task` tool, when the coordinator emits a `Task` call, the SDK starts a new subagent process, runs it to completion, and returns its final message back to the coordinator as a tool result.\n",
                                     "\n",
                                     "## 2. Enabling the `Task` Tool on the Coordinator\n",
                                     "\n",
                                     "The coordinator can only spawn subagents if `Task` is in its `allowed_tools` list. Without it, the SDK silently has no way to delegate, the coordinator will try to do everything in its own context.\n",
                                     "\n",
                                     "*coordinator setup*"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition\n",
                                     "\n",
                                     "# Define the subagent personas the coordinator is allowed to spawn.\n",
                                     "agents = {\n",
                                     "    \"researcher\": AgentDefinition(\n",
                                     "        description=\"Web research specialist. Use for market intel, prospect lookups, source gathering.\",\n",
                                     "        prompt=(\n",
                                     "            \"You are a research analyst. Use WebSearch and WebFetch to gather \"\n",
                                     "            \"primary sources. Return a structured findings document with citations.\"\n",
                                     "        ),\n",
                                     "        tools=[\"WebSearch\", \"WebFetch\"],\n",
                                     "        model=\"haiku\",\n",
                                     "    ),\n",
                                     "    \"copywriter\": AgentDefinition(\n",
                                     "        description=\"Marketing copy specialist. Use for drafting outreach, social posts, landing pages.\",\n",
                                     "        prompt=(\n",
                                     "            \"You are a B2B marketing copywriter. Given research findings in your \"\n",
                                     "            \"prompt, produce three distinct copy variants in the brand voice.\"\n",
                                     "        ),\n",
                                     "        tools=[],   # no tools, pure generation\n",
                                     "        model=\"sonnet\",\n",
                                     "    ),\n",
                                     "    \"synthesizer\": AgentDefinition(\n",
                                     "        description=\"Synthesis specialist. Use after research is complete to merge findings and flag gaps.\",\n",
                                     "        prompt=(\n",
                                     "            \"You synthesize cited findings into a concise account plan. You may use \"\n",
                                     "            \"verify_fact only for simple high-frequency checks such as public dates \"\n",
                                     "            \"or company names; route complex research gaps back to the coordinator.\"\n",
                                     "        ),\n",
                                     "        tools=[\"verify_fact\"],\n",
                                     "        model=\"sonnet\",\n",
                                     "    ),\n",
                                     "}\n",
                                     "\n",
                                     "coordinator_options = ClaudeAgentOptions(\n",
                                     "    model=\"claude-opus-4-7\",\n",
                                     "    system_prompt=(\n",
                                     "        \"You are a marketing coordinator. Decompose the user goal into independent \"\n",
                                     "        \"research and drafting jobs, delegate each to the appropriate subagent via \"\n",
                                     "        \"the Task tool, then synthesize their outputs into the final deliverable.\"\n",
                                     "    ),\n",
                                     "    # CRITICAL: Task must be in allowed_tools or the coordinator cannot delegate.\n",
                                     "    allowed_tools=[\"Task\"],\n",
                                     "    agents=agents,\n",
                                     ")"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "## 3. Spawning Subagents in Parallel\n",
                                     "\n",
                                     "The coordinator can emit **multiple `Task` calls in a single response**. The SDK starts each subagent concurrently and returns their results together, so independent work runs in parallel rather than serially. This is the single biggest performance win the pattern offers, two researchers running in parallel finish in roughly the time of one.\n",
                                     "\n",
                                     "*driving the coordinator*"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "import asyncio\n",
                                     "\n",
                                     "USER_GOAL = (\n",
                                     "    \"Build an outreach kit for AI consulting prospects in EMEA fintech: \"\n",
                                     "    \"research the top 5 firms, then draft tailored intro emails for each.\"\n",
                                     ")\n",
                                     "\n",
                                     "async def run_coordinator():\n",
                                     "    async for message in query(prompt=USER_GOAL, options=coordinator_options):\n",
                                     "        # The SDK surfaces each Task spawn, each subagent message, and\n",
                                     "        # the coordinator\u0027s final synthesis through the same async stream.\n",
                                     "        print(message)\n",
                                     "\n",
                                     "asyncio.run(run_coordinator())"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "To make parallelism happen, prompt the coordinator explicitly: \"*Spawn the research subagents for all five firms in a single response so they execute concurrently.*\" Without that nudge the model often defaults to one-at-a-time delegation.\n",
                                     "\n",
                                     "## 4. Context Isolation, and Why It\u0027s a Feature\n",
                                     "\n",
                                     "Each subagent starts with a **fresh, isolated message history**. It does *not* see the coordinator\u0027s transcript, prior tool calls, or the user\u0027s original goal unless you put that information into its prompt explicitly. This is intentional, it keeps each subagent\u0027s context window small and on-task, and it prevents one specialist\u0027s noise from polluting another\u0027s reasoning.\n",
                                     "\n",
                                     "The consequence: anything the subagent needs to know, you must **pass into its prompt** as part of the `Task` call. The most common bug in early hub-and-spoke implementations is the coordinator delegating \"draft the email\" without including the research findings, the subagent has nothing to work from and hallucinates.\n",
                                     "\n",
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "The hand-off contract is: **findings â†’ prompt**. After a research subagent returns, the coordinator must summarize the relevant fields (company name, recent news, decision maker) into the next `Task` call\u0027s prompt, not assume the drafting subagent can \"see\" what the researcher found. The exam tests this directly.\n",
                                     "\n",
                                     "```coordinator system-prompt excerpt\n",
                                     "When delegating to the copywriter subagent:\n",
                                     "1. Quote the relevant research findings verbatim in the Task prompt\n",
                                     "   (company name, recent funding, named contact, public AI signal).\n",
                                     "2. Restate the brand voice constraints in every Task prompt; do not\n",
                                     "   assume the subagent inherits them from your system prompt.\n",
                                     "3. Include the original user goal as one line of context, the subagent\n",
                                     "   does not see the user\u0027s message.\n",
                                     "```\n",
                                     "\n",
                                     "## 5. Cost Routing Across the Hub and Spokes\n",
                                     "\n",
                                     "Hub-and-spoke is also a cost-control pattern. Route by capability needed:\n",
                                     "\n",
                                     "- **Coordinator (Opus 4.7)**, decomposition and synthesis are the high-leverage steps; spend the tokens here.\n",
                                     "- **Research / extraction subagents (Haiku 4.5)**, fast, cheap, and well-suited to parallel fan-out where you want many concurrent runs.\n",
                                     "- **Drafting / writing subagents (Sonnet 4.6)**, balance of quality and cost when output length matters more than reasoning depth.\n",
                                     "\n",
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "If the coordinator\u0027s `allowed_tools` omits `\"Task\"`, no delegation can happen, the coordinator will silently absorb all the work into its own context. Conversely, granting subagents access to `\"Task\"` lets them spawn *their own* subagents (recursive delegation), powerful but harder to reason about. Default to a flat hub-and-spoke unless you genuinely need depth.\n",
                                     "\n",
                                     "## 6. Scoped Cross-Role Tools\n",
                                     "\n",
                                     "Subagents should not inherit the coordinator\u0027s whole toolbox, but \"no tools\" is often too strict. A synthesis subagent frequently needs to verify a date, confirm a company name, or check whether a cited page still exists. Routing every tiny check back through the coordinator adds latency and bloats the coordinator\u0027s context.\n",
                                     "\n",
                                     "The pattern is **scoped cross-role tooling**: give the subagent one narrow tool for the repetitive need, and keep broad discovery tools with the coordinator or research agents.\n",
                                     "\n",
                                     "*limited verification tool*"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "def verify_fact(claim: str, source_url: str) -\u003e dict:\n",
                                     "    \"\"\"Return whether a simple claim is supported by one cited source.\"\"\"\n",
                                     "    # Implementation would fetch exactly source_url and check the claim.\n",
                                     "    # It must not perform open-ended web search.\n",
                                     "    return {\"supported\": True, \"checked_url\": source_url}\n",
                                     "\n",
                                     "synthesizer = AgentDefinition(\n",
                                     "    description=\"Synthesizes research findings and verifies simple cited facts.\",\n",
                                     "    prompt=(\n",
                                     "        \"Use verify_fact for simple date/name checks against an already cited URL. \"\n",
                                     "        \"Do not use it for new research. If a claim needs open-ended investigation, \"\n",
                                     "        \"return a gap for the coordinator.\"\n",
                                     "    ),\n",
                                     "    tools=[\"verify_fact\"],\n",
                                     "    model=\"sonnet\",\n",
                                     ")"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### 6a. Tool Count Reliability\n",
                                     "\n",
                                     "More tools are not automatically better. A subagent with 18 loosely related tools has a larger routing surface, more overlapping descriptions, and more chances to pick a plausible but wrong tool. A subagent with 4 role-specific tools is easier for the model to reason about and easier for you to test.\n",
                                     "\n",
                                     "*lab task: compare tool surfaces*"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "too_many_tools = [\n",
                                     "    \"web_search\", \"web_fetch\", \"query_crm\", \"update_crm\", \"delete_lead\", \"send_email\",\n",
                                     "    \"verify_fact\", \"summarize_pdf\", \"extract_table\", \"run_sql\", \"forecast_pipeline\",\n",
                                     "    \"lookup_contact\", \"lookup_account\", \"calendar_check\", \"create_ticket\",\n",
                                     "    \"read_repo\", \"write_file\", \"run_tests\",\n",
                                     "]\n",
                                     "\n",
                                     "scoped_synthesis_tools = [\"verify_fact\", \"read_resource\", \"write_report\", \"escalate_gap\"]"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "For the lab, run the same synthesis prompt with both tool lists and record misroutes: unnecessary searches, writes attempted by a read-only role, or calls to generic tools when a narrow tool exists. The expected architecture is not \"give every agent everything\"; it is \"give each role the smallest complete tool surface.\""
                                 ]
                  }
              ],
    "metadata":  {
                     "kernelspec":  {
                                        "display_name":  "Python 3",
                                        "language":  "python",
                                        "name":  "python3"
                                    },
                     "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.0"
                                       }
                 },
    "nbformat":  4,
    "nbformat_minor":  5
}
