**Lecture 33: Tool Use & the Agent Loop — Building a Complete Single Agent** **Total Time: 50 minutes** **Spectrum coverage: Router (#1) + Tool Call (#2) + Multi-step (#1+2+3)** **Files: tools.py, agent.py, context.py, nano_claude.py** **Outcome: working coding agent by end of lecture** **[3 min] 0. Housekeeping** - Roadmap image + reading (Anthropic tool use docs, nano-claude-code source) - Recap Lec 32: API basics (config.py), agency spectrum, 5 things about LLM APIs - Today: build the remaining 4 files → complete multi-step single agent **[12 min] 1. Tool Use — tools.py (359 lines)** - **How Tool Use Works** (3-step cycle): 1. Send tools (JSON schemas) + user message → Claude API 2. LLM returns tool call: stop_reason="tool_use", name + arguments 3. Your code executes locally, sends result back → loop - Key: LLM never executes anything. Your code decides what to run. - **Tool Schema** (JSON Schema format from tools.py): - name, description (LLM reads this to decide when to use), input_schema (JSON Schema) - Example: Read tool — file_path (required), offset, limit (optional) - **8 Tools in nano-claude-code**: | Tool | Purpose | Safe? | |------|---------|-------| | Read | Read file | ✓ auto | | Write | Create/overwrite | ✗ ask | | Edit | String replacement | ✗ ask | | Bash | Shell command | ✗ ask | | Glob | Pattern matching | ✓ auto | | Grep | Regex search | ✓ auto | | WebFetch | HTTP GET | ✗ ask | | WebSearch | DuckDuckGo | ✗ ask | - **Tool Implementation**: each tool ~15 lines. Dispatcher dict maps name → function. **[15 min] 2. The Agent Loop — agent.py (173 lines)** - **The Core Loop** (covers Router #1 + Multi-step #1+2+3): ```python def run(user_msg, state, config, sys_prompt): client = anthropic.Anthropic(api_key=config["api_key"]) state.messages.append({"role": "user", "content": user_msg}) while True: # 1. Call Claude API (streaming) with client.messages.stream(model=..., tools=..., messages=...) as stream: for event in stream: yield event # → REPL # 2. Router: check stop_reason if stop_reason != "tool_use": break # Done — LLM finished # 3. Execute each tool call for tool in tool_uses: result = execute_tool(tool.name, tool.input) yield ToolEnd(tool.name, result) # 4. Feed results back → loop (feedback) state.messages.append({"role": "user", "content": tool_results}) ``` - **Generator Events**: TextChunk, ThinkingChunk, ToolStart, ToolEnd, PermissionRequest, TurnDone - Decouples agent logic from UI — same loop powers CLI, web, IDE - **Permission System**: 3 modes (auto, accept-all, manual) - auto: Read/Glob/Grep auto-approved. Write/Edit/Bash need approval. - Permission gate in agent loop: yield PermissionRequest → REPL sets granted **[10 min] 3. Context & REPL — context.py + nano_claude.py** - **context.py (100 lines)**: build_system_prompt() — built fresh each turn from: 1. Static instructions 2. Date, CWD, platform 3. Git branch + status 4. CLAUDE.md files (global + project) - Why: without context, agent is generic. With context, it knows your project. - **nano_claude.py (553 lines)**: the user interface - Readline input, streaming output, ANSI colors, Rich markdown - 15 slash commands: /help, /clear, /model, /save, /load, /cost, /exit... - CLI flags: -m (model), -p (print mode), --accept-all, --thinking - Consumes generator events from agent.py, renders them **[8 min] 4. Full Architecture** - 5-file diagram: nano_claude.py → agent.py ← context.py + tools.py + config.py → Claude API - ~1,200 lines of Python = working coding agent - Demo: show nano-claude-code running (terminal) **[2 min] 5. Wrap-Up** - **Today**: tool schemas (tools.py), agent while-loop (agent.py), context (context.py), REPL (nano_claude.py) - **All 5 files complete**: you have a working coding agent - **Next Lec 34**: Multi-Agent — spawn sub-agents for parallel work + biomedical agent applications (subagent.py)