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

                                   },
                      "source":  [
                                     "*Module 2 Self-Driven Lab*\r\n",
                                     "\r\n",
                                     "# Agentic Loops \u0026 SDK Foundations\r\n",
                                     "\r\n",
                                     "**Objective:** master the stateless nature of history bookkeeping and the transition to model-driven orchestration.\r\n",
                                     "\r\n",
                                     "## Challenge Outline\r\n",
                                     "\r\n",
                                     "Build a complete notebook that demonstrates the following outcomes:\r\n",
                                     "\r\n",
                                     "- **History bookkeeping:** create a manual message array. Send a three-turn conversation where you manually append each user and assistant message. Verify that omitting one assistant turn causes the API to reject the request.\r\n",
                                     "- **Tool result mapping:** simulate a `tool_use` event. Manually append the `tool_use` block to history, followed by a `tool_result` block. Ensure the `tool_use_id` matches exactly.\r\n",
                                     "- **SDK transition:** refactor the same loop using the Claude Agent SDK. Observe how the SDK handles history, tool dispatch, and termination automatically.\r\n",
                                     "- **Loop safety:** implement an iteration cap of 5 turns as a defensive guardrail. Log a hard error if the cap is reached before `stop_reason: \"end_turn\"`.\r\n",
                                     "\r\n",
                                     "Your solution should include enough code, output, or written observations to prove each outcome worked. Keep the notebook focused on final behavior and evidence rather than a guided walkthrough.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "## Student Workspace\n",
                                     "\n",
                                     "Use the sections below to build your solution. Each section maps to one required outcome from the challenge outline.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### Part 1: History bookkeeping\n",
                                     "\n",
                                     "create a manual message array. Send a three-turn conversation where you manually append each user and assistant message. Verify that omitting one assistant turn causes the API to reject the request.\n"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Part 1: History bookkeeping\n",
                                     "# Add your implementation, outputs, or notes here.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### Part 2: Tool result mapping\n",
                                     "\n",
                                     "simulate a `tool_use` event. Manually append the `tool_use` block to history, followed by a `tool_result` block. Ensure the `tool_use_id` matches exactly.\n"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Part 2: Tool result mapping\n",
                                     "# Add your implementation, outputs, or notes here.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### Part 3: SDK transition\n",
                                     "\n",
                                     "refactor the same loop using the Claude Agent SDK. Observe how the SDK handles history, tool dispatch, and termination automatically.\n"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Part 3: SDK transition\n",
                                     "# Add your implementation, outputs, or notes here.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### Part 4: Loop safety\n",
                                     "\n",
                                     "implement an iteration cap of 5 turns as a defensive guardrail. Log a hard error if the cap is reached before `stop_reason: \"end_turn\"`.\n"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Part 4: Loop safety\n",
                                     "# Add your implementation, outputs, or notes here.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "### Verification Notes\n",
                                     "\n",
                                     "Summarize the evidence that each part worked. Capture API signals, validation outcomes, errors, recovery behavior, cost observations, or comparisons required by this lab.\n"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Verification notes\n",
                                     "# Record the evidence that proves each lab outcome worked.\n"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "---\n",
                                     "\n",
                                     "## Answer Key\n",
                                     "\n",
                                     "The cells below contain the completed reference implementation/content for this module. Use this section only after attempting the self-driven lab."
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "*Module 2 of 12*\n",
                                     "\n",
                                     "# Agentic Loops \u0026 SDK Foundations\n",
                                     "\n",
                                     "This module covers the lower-level orchestration required for production Claude systems: the **Messages API** and how tool results re-enter conversation history, the **Agent SDK** for model-driven workflows, **Zero Data Retention (ZDR) eligibility** per feature, and the agentic loop pattern that governs every multi-turn workflow.\n",
                                     "\n",
                                     "\n",
                                     "\u003e **Tip.** **Prerequisites:** If you haven\u0027t installed VS Code, Jupyter, and the Anthropic SDK yet, complete [Module 1: Dev Environment \u0026 Foundations](module-1.html) before continuing.\n",
                                     "\n",
                                     "## 1. Setting Up the Messages API (Stateless Design)\n",
                                     "\n",
                                     "The Claude API is **stateless**, it does not persist history between calls. To build a conversation, you must maintain a local array of messages and send the full history back to the API with every request. The same pattern carries through tool use: when Claude requests a tool, you append the assistant\u0027s `tool_use` block, then append a `tool_result` block on the next turn so the model can reason about what the tool returned.\n",
                                     "\n",
                                     "**Implementation Task: Local Message History**\n",
                                     "\n",
                                     "Start by initializing your conversation locally and capturing the assistant\u0027s reply."
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "import anthropic\n",
                                     "from dotenv import load_dotenv\n",
                                     "\n",
                                     "load_dotenv()  # reads ANTHROPIC_API_KEY from your .env file\n",
                                     "\n",
                                     "client = anthropic.Anthropic()\n",
                                     "\n",
                                     "# Start with your local message history\n",
                                     "messages = [\n",
                                     "    {\"role\": \"user\", \"content\": \"Analyze the target audience for an AI consulting firm.\"}\n",
                                     "]\n",
                                     "\n",
                                     "# Create the first request\n",
                                     "# Note: Adaptive thinking is required for Opus 4.7\n",
                                     "response = client.messages.create(\n",
                                     "    model=\"claude-opus-4-7\",\n",
                                     "    max_tokens=4096,\n",
                                     "    thinking={\"type\": \"adaptive\"},  # Required for Opus 4.7; replaces budget_tokens\n",
                                     "    inference_geo=\"us\",              # Restrict compute to US infrastructure (1.1x pricing)\n",
                                     "    messages=messages\n",
                                     ")\n",
                                     "\n",
                                     "# Build the synthetic conversation locally by adding the assistant\u0027s response\n",
                                     "messages.append({\"role\": \"assistant\", \"content\": response.content})\n",
                                     "\n",
                                     "# Add a manual follow-up to the local history\n",
                                     "messages.append({\"role\": \"user\", \"content\": \"Now, generate three LinkedIn post ideas for this audience.\"})"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "**Implementation Task: Feeding Tool Results Back Into History**\n",
                                     "\n",
                                     "When `stop_reason == \"tool_use\"`, the assistant\u0027s last message contains one or more `tool_use` content blocks. You execute each tool locally, then send the output back as a `tool_result` block on a *user* turn. Re-calling `messages.create` with the updated history lets the model read the tool output and decide the next action, this is the spine of every agentic loop."
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "# Assume the prior `response` came back with stop_reason == \"tool_use\"\n",
                                     "# 1. Append the assistant turn verbatim, including its tool_use block(s).\n",
                                     "messages.append({\"role\": \"assistant\", \"content\": response.content})\n",
                                     "\n",
                                     "# 2. Locate the tool_use block and run the tool in your own code.\n",
                                     "tool_use = next(b for b in response.content if b.type == \"tool_use\")\n",
                                     "tool_output = run_local_tool(tool_use.name, tool_use.input)  # your dispatcher\n",
                                     "\n",
                                     "# 3. Append the tool_result on a USER turn, keyed by the tool_use_id.\n",
                                     "messages.append({\n",
                                     "    \"role\": \"user\",\n",
                                     "    \"content\": [{\n",
                                     "        \"type\": \"tool_result\",\n",
                                     "        \"tool_use_id\": tool_use.id,\n",
                                     "        \"content\": tool_output,\n",
                                     "    }],\n",
                                     "})\n",
                                     "\n",
                                     "# 4. Re-call the API so Claude can reason about the tool output and decide next steps.\n",
                                     "response = client.messages.create(\n",
                                     "    model=\"claude-opus-4-7\",\n",
                                     "    max_tokens=4096,\n",
                                     "    thinking={\"type\": \"adaptive\"},\n",
                                     "    messages=messages,\n",
                                     ")"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "Tool results are **user-role messages**, not a separate role. The `tool_use_id` on the result must match the `id` from the assistant\u0027s `tool_use` block, and you must include the assistant\u0027s full `content` (including any thinking blocks) in history before the result, or the next call will reject the conversation.\n",
                                     "\n",
                                     "## 2. Designing Agentic Workflows with the Agent SDK\n",
                                     "\n",
                                     "Before writing any code, decide which type of system you actually need. The exam draws a sharp line between two architectures:\n",
                                     "\n",
                                     "- **Workflows (pre-configured decision trees)**, You hard-code the control flow. The model fills in the leaves (classify, summarize, extract), but humans pick the branches. Predictable, cheap, easy to test, no autonomy.\n",
                                     "- **Agents (model-driven decision-making)**, You expose tools and a goal. The *model* decides which tool to call, when to stop, and how to recover. Higher cost and variance, but able to handle open-ended tasks where you can\u0027t enumerate the branches in advance.\n",
                                     "\n",
                                     "Pick the workflow when the steps are knowable in advance. Reach for an agent only when the model genuinely needs to choose its own path.\n",
                                     "\n",
                                     "**Implementation Task: Stand Up the Agent SDK**\n",
                                     "\n",
                                     "The [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) (`claude-agent-sdk`) is Anthropic\u0027s reference harness for the agentic loop. It owns history bookkeeping, tool dispatch, and termination so you can focus on the system prompt, allowed tools, and permission policy.\n",
                                     "\n",
                                     "```bash\n",
                                     "pip install claude-agent-sdk\n",
                                     "```"
                                 ]
                  },
                  {
                      "cell_type":  "code",
                      "execution_count":  null,
                      "metadata":  {

                                   },
                      "outputs":  [

                                  ],
                      "source":  [
                                     "import asyncio\n",
                                     "from claude_agent_sdk import query, ClaudeAgentOptions\n",
                                     "\n",
                                     "# Configure the agent: persona, model, and the tool surface it may use.\n",
                                     "options = ClaudeAgentOptions(\n",
                                     "    model=\"claude-opus-4-7\",\n",
                                     "    system_prompt=(\n",
                                     "        \"You are an expert Marketing Strategist. Use thinking to ensure \"\n",
                                     "        \"your plans are data-driven, and cite sources when you research.\"\n",
                                     "    ),\n",
                                     "    allowed_tools=[\"WebSearch\", \"WebFetch\"],\n",
                                     "    permission_mode=\"acceptEdits\",  # Auto-approve safe tool calls\n",
                                     ")\n",
                                     "\n",
                                     "async def main():\n",
                                     "    # The SDK runs the agentic loop for you: tool dispatch, history, stop_reason.\n",
                                     "    async for message in query(\n",
                                     "        prompt=\"Generate three LinkedIn post ideas for AI consulting buyers.\",\n",
                                     "        options=options,\n",
                                     "    ):\n",
                                     "        print(message)\n",
                                     "\n",
                                     "asyncio.run(main())"
                                 ]
                  },
                  {
                      "cell_type":  "markdown",
                      "metadata":  {

                                   },
                      "source":  [
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "The Agent SDK does not replace the Messages API, it sits on top of it. Knowing how to drop down to raw `messages.create` with your own `tool_use` / `tool_result` bookkeeping (Section 1) is what lets you debug an SDK-driven agent when it misbehaves.\n",
                                     "\n",
                                     "## 3. Data Residency \u0026 ZDR Eligibility\n",
                                     "\n",
                                     "The `inference_geo` parameter controls where model computation runs on a per-request basis.\n",
                                     "\n",
                                     "- **`\"us\"`**, Inference is restricted to US-based infrastructure only.\n",
                                     "- **`\"global\"`**, Default. Runs in any available geography for best performance.\n",
                                     "\n",
                                     "Setting `inference_geo: \"us\"` incurs a **1.1x pricing multiplier** on all token categories (input, output, and cache) for models starting with Claude Opus 4.6.\n",
                                     "\n",
                                     "**ZDR Eligibility, Feature by Feature**\n",
                                     "\n",
                                     "Zero Data Retention is not an account-level switch, it is decided per feature. The exam expects you to recognize which features keep a workload ZDR-compliant and which ones break it.\n",
                                     "\n",
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "If a regulated workload **must** stay ZDR, you cannot reach for the MCP Connector or Message Batches just because they are convenient, you have to fall back to direct tool calls and synchronous Messages requests. Conversely, Adaptive Thinking and Structured Outputs are safe to layer on without breaking ZDR.\n",
                                     "\n",
                                     "## 4. Orchestration Pattern: The Agentic Loop\n",
                                     "\n",
                                     "For the exam, you must understand that the `stop_reason` field determines the loop\u0027s behavior:\n",
                                     "\n",
                                     "- **`\"tool_use\"`**, The loop continues. Execute the tool, append the `tool_result`, and re-call the API.\n",
                                     "- **`\"end_turn\"`**, The loop terminates. This is the only reliable signal that the task is complete.\n",
                                     "- **`\"max_tokens\"`** / **`\"pause_turn\"`**, Recoverable interruptions, continue the request rather than treating them as completion.\n",
                                     "\n",
                                     "**Anti-Patterns to Avoid**\n",
                                     "\n",
                                     "- **Parsing natural-language completion signals**, Do not check whether the assistant text contains \"I\u0027m done\", \"task complete\", or similar phrases. Those strings are not part of the API contract, they will drift across model versions and prompts and silently break your loop.\n",
                                     "- **Using an iteration cap as the primary stopping mechanism**, Writing `for _ in range(10):` as the loop body and exiting whenever the counter runs out hides bugs (the agent looked complete but actually got cut off) and bills you for tool calls that never finish a task. Iteration caps belong in the loop as a *safety guardrail* against runaway agents, not as the signal that the work is done.\n",
                                     "\n",
                                     "\u003e **Tip.** Architect Tip for the Exam\n",
                                     "The correct pattern: drive the loop off `stop_reason == \"end_turn\"`, keep an iteration cap as a defensive backstop, and log a hard error (rather than silently returning) if the cap is what stopped the run. That separation, structured signal for completion, counter for safety, is what the exam is testing."
                                 ]
                  }
              ],
    "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
}
