A complete agent loop implementation for a customer-support bot. The agent looks up customer records, processes refunds, and escalates high-value cases to humans — demonstrating the stop_reason exit signal, structured error taxonomy, pre/post tool hooks, and context accumulation in practice.
| Domain | Name | Weight | Coverage in This Exercise |
|---|---|---|---|
| D1 | Agent Architecture | 27% | Primary — agent loop, stop_reason, hooks, context accumulation |
| D2 | Tools & MCP | 18% | Tool descriptions, disambiguation pattern, dispatch table |
| D5 | Context & Reliability | 15% | Structured errors, TRANSIENT vs BUSINESS, retry logic |
API mode detection. Reads ANTHROPIC_API_KEY from the environment. If set, the agent loop uses the real Claude API (RealClaudeClient). Without it, the pre-scripted MockClaudeClient runs instead — no API key needed for study.
get_api_key() — reads env var, returns None if absentis_live_mode() — boolean check used by run_demo.py to select the clientmode_banner() — prints LIVE vs SIMULATION banner at startup.env file via python-dotenv (optional dependency)Defines the structured error taxonomy for tool call failures. The core insight: returning a plain string like "Error: refund too large" forces Claude to parse natural language to decide what to do next. Returning a structured dict with isRetryable and errorCategory enables deterministic branching in the agent loop.
isRetryable=TrueisRetryable=False — the rule itself must change firstmake_error_response(error) — serializes to MCP dict format with isError: Trueis_retryable(tool_result) — quick check in the agent loop# Deterministic branch — no NLP parsing needed if result["content"]["isRetryable"]: result = run_tool(tool_name, args) # retry once elif result["content"]["errorCategory"] == "BUSINESS": escalate() # don't retry
Defines 4 tools in the MCP / Claude tool-use format (name, description, input_schema) plus mock implementations. The central lesson: Claude picks tools by reading their descriptions. Two tools with ambiguous descriptions cause probabilistic wrong selections. Explicit disambiguation prevents this.
MOCK_TOOL_IMPLEMENTATIONS — dispatch table: tool name → function; agent loop uses this dict to route calls# BAD — ambiguous "description": "Gets a customer record." # GOOD — deterministic disambiguation "description": ( "Use for INITIAL lookup when you only have the email address. " "Do NOT use if you already have the customer_id — " "use get_customer_by_id instead." )
Pre- and post-tool-use hooks. The key architectural insight: prompt instructions are probabilistic ("never process refunds above $500" can be ignored under adversarial input or context dilution). Hook code is deterministic — it always runs before/after the tool regardless of what the model decides.
amount > 500, returns {intercepted: True, redirect_to: "escalate_to_human", suggested_args: {...}}def pre_tool_use_hook(tool_name, tool_args): if tool_name == "process_refund": amount = float(tool_args.get("amount", 0)) if amount > ESCALATION_THRESHOLD: # 500.0 return { "intercepted": True, "redirect_to": "escalate_to_human", "suggested_args": { ... } } return None # allow the call
The complete agent loop: messages → API → tool_use → tool result → repeat until stop_reason == "end_turn". Also defines MockClaudeClient (pre-scripted responses for study) and RealClaudeClient (wraps the real Anthropic SDK with the same interface).
messages list grows with every iteration; all tokens re-sent on every API callmax_iterations=10 safety ceiling prevents infinite loops if stop_reason never reaches end_turntool_id so conversation thread stays coherentrole: "user" (Anthropic API convention)for iteration in range(max_iterations): response = client.messages_create(...) if response["stop_reason"] == "end_turn": return _extract_text(response["content"]) # ← exit here elif response["stop_reason"] == "tool_use": # run pre-hook → execute or redirect → run post-hook # append assistant message + tool results to messages continue # loop back
Runs 3 back-to-back scenarios illustrating the full range of agent behavior. Each scenario prints the step-by-step loop trace so you can observe exactly what fires at each iteration.
Click a question to reveal the options and answer. Source: explanation Ex1.md
max_iterations ceiling is the standard safeguard against runaway agent loops. When hit, it returns a predictable message rather than looping indefinitely. Options A and C address root causes but don't protect against infinite loops. Option D would break the agent's ability to complete its task.if isRetryable: retry(). No parsing required.