[{"data":1,"prerenderedAt":3410},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Fminimum-viable-loop":102,"surround-\u002Fchapters\u002Fminimum-viable-loop":3405},[4,8],{"title":5,"path":6,"stem":7},"Home","\u002F","index",{"title":9,"path":10,"stem":11,"children":12,"page":101},"Chapters","\u002Fchapters","2.chapters",[13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97],{"title":14,"path":15,"stem":16},"Chapter 1. What an Agent Actually Is","\u002Fchapters\u002Fwhat-an-agent-actually-is","2.chapters\u002F01.what-an-agent-actually-is",{"title":18,"path":19,"stem":20},"Chapter 2. The Minimum Viable Loop","\u002Fchapters\u002Fminimum-viable-loop","2.chapters\u002F02.minimum-viable-loop",{"title":22,"path":23,"stem":24},"Chapter 3. Messages, Turns, and the Transcript","\u002Fchapters\u002Fmessages-turns-transcript","2.chapters\u002F03.messages-turns-transcript",{"title":26,"path":27,"stem":28},"Chapter 4. The Tool Protocol","\u002Fchapters\u002Ftool-protocol","2.chapters\u002F04.tool-protocol",{"title":30,"path":31,"stem":32},"Chapter 5. Streaming, Interruption, and Error Handling","\u002Fchapters\u002Fstreaming-interruption-errors","2.chapters\u002F05.streaming-interruption-errors",{"title":34,"path":35,"stem":36},"Chapter 6. Safe Tool Execution","\u002Fchapters\u002Fsafe-tool-execution","2.chapters\u002F06.safe-tool-execution",{"title":38,"path":39,"stem":40},"Chapter 7. The Context Window Is a Resource","\u002Fchapters\u002Fcontext-window-as-resource","2.chapters\u002F07.context-window-as-resource",{"title":42,"path":43,"stem":44},"Chapter 8. Compaction","\u002Fchapters\u002Fcompaction","2.chapters\u002F08.compaction",{"title":46,"path":47,"stem":48},"Chapter 9. External State: The Scratchpad","\u002Fchapters\u002Fscratchpad","2.chapters\u002F09.scratchpad",{"title":50,"path":51,"stem":52},"Chapter 10. Retrieval","\u002Fchapters\u002Fretrieval","2.chapters\u002F10.retrieval",{"title":54,"path":55,"stem":56},"Chapter 11. Designing Tools Models Can Actually Use","\u002Fchapters\u002Fdesigning-tools","2.chapters\u002F11.designing-tools",{"title":58,"path":59,"stem":60},"Chapter 12. The Tool Cliff and Dynamic Loading","\u002Fchapters\u002Ftool-cliff","2.chapters\u002F12.tool-cliff",{"title":62,"path":63,"stem":64},"Chapter 13. MCP: Tools From the Outside World","\u002Fchapters\u002Fmcp","2.chapters\u002F13.mcp",{"title":66,"path":67,"stem":68},"Chapter 14. Sandboxing and Permissions","\u002Fchapters\u002Fsandboxing-permissions","2.chapters\u002F14.sandboxing-permissions",{"title":70,"path":71,"stem":72},"Chapter 15. Sub-agents","\u002Fchapters\u002Fsub-agents","2.chapters\u002F15.sub-agents",{"title":74,"path":75,"stem":76},"Chapter 16. Structured Plans and Verified Completion","\u002Fchapters\u002Fplans-verified-completion","2.chapters\u002F16.plans-verified-completion",{"title":78,"path":79,"stem":80},"Chapter 17. Parallelism and Shared State","\u002Fchapters\u002Fparallelism-shared-state","2.chapters\u002F17.parallelism-shared-state",{"title":82,"path":83,"stem":84},"Chapter 18. Observability","\u002Fchapters\u002Fobservability","2.chapters\u002F18.observability",{"title":86,"path":87,"stem":88},"Chapter 19. Evals","\u002Fchapters\u002Fevals","2.chapters\u002F19.evals",{"title":90,"path":91,"stem":92},"Chapter 20. Cost Control","\u002Fchapters\u002Fcost-control","2.chapters\u002F20.cost-control",{"title":94,"path":95,"stem":96},"Chapter 21. Resumability and Durable State","\u002Fchapters\u002Fresumability","2.chapters\u002F21.resumability",{"title":98,"path":99,"stem":100},"Chapter 22. What Transfers, Where to Go","\u002Fchapters\u002Fwhat-transfers","2.chapters\u002F22.what-transfers",false,{"id":103,"title":18,"body":104,"description":117,"extension":3400,"meta":3401,"navigation":3402,"path":19,"seo":3403,"stem":20,"__hash__":3404},"content\u002F2.chapters\u002F02.minimum-viable-loop.md",{"type":105,"value":106,"toc":3384},"minimark",[107,111,118,130,133,141,257,260,265,272,294,297,300,306,312,314,318,329,336,666,669,688,694,701,1005,1019,1022,1024,1028,1031,1885,1920,1923,1958,1961,1963,1967,1970,2503,2506,2523,2529,2532,2579,2581,2585,2588,2593,2596,2681,2691,2701,2705,2791,2798,2803,2807,2953,2963,2968,2972,3087,3097,3114,3118,3170,3173,3176,3181,3183,3187,3190,3267,3270,3273,3275,3279,3285,3291,3305,3314,3323,3326,3328,3332,3360,3362,3380],[108,109,18],"h1",{"id":110},"chapter-2-the-minimum-viable-loop",[112,113,114],"p",{},[115,116,117],"em",{},"Previously: we set up a repo skeleton and agreed on vocabulary. Model is a function; agent is a loop; harness is the engineering around the loop. No code yet.",[112,119,120,121,125,126,129],{},"A ",[122,123,124],"code",{},"while"," loop is the smallest thing that separates a chat interface from an agent, and it's what turns one API call into many — the model reads the output of its previous turn and decides what to do next, a pattern Yao et al. formalized in 2022 as ",[115,127,128],{},"ReAct"," (reason, act, observe) and that nearly every modern LLM agent is some variation on. That decision, made once per iteration, is the whole point: a model that cannot observe its own last action cannot debug, recover, or finish, while a model that can — even badly — is the start of an agent.",[112,131,132],{},"We are going to write that loop now. Forty lines in one file, no frameworks, no abstractions we haven't earned — and then we are going to break it in five specific ways, on purpose, and watch each break ripple through the design. Those five breaks will become the itinerary for the rest of the book, and most of the engineering in subsequent chapters is traceable back to one of them.",[112,134,135,136,140],{},"By the end of this chapter, your harness can answer a question by calling a calculator tool in a loop, against a ",[137,138,139],"strong",{},"mock provider"," rather than a real API. The mock provider is not a placeholder we'll throw away; it is the first piece of real architecture we lay down, the seam that makes your harness provider-agnostic from day one, and the reason every subsequent chapter can add capability without ever hard-coding a vendor's SDK into the core.",[142,143,147,250],"figure",{"className":144},[145,146],"not-prose","my-8",[148,149,156,175,180,201,240],"div",{"className":150},[151,152,153,154,155],"flex","flex-col","items-center","gap-3","text-sm",[148,157,167,174],{"className":158},[159,160,161,162,163,164,165,166],"border","border-default","rounded-lg","py-3","px-5","w-full","max-w-md","text-center",[168,169,173],"span",{"className":170},[171,172],"text-muted","mr-2","1.","Ask the model",[148,176,179],{"className":177},[171,178],"text-xs","↓",[148,181,184,188,189,194,195,200],{"className":182},[159,160,161,162,163,164,165,166,183],"bg-elevated",[168,185,187],{"className":186},[171,172],"2.","Is the response a ",[137,190,193],{"className":191},[192],"text-default","tool call"," or a ",[137,196,199],{"className":197},[198],"text-primary","final answer","?",[148,202,208,226],{"className":203},[204,205,206,164,165,178,207],"grid","grid-cols-2","gap-4","mt-1",[148,209,212,216,222],{"className":210},[151,152,153,211],"gap-2",[148,213,215],{"className":214},[171],"tool call ↓",[148,217,221],{"className":218},[159,160,161,219,220,164,166],"py-2","px-3","Run the tool, append the result",[148,223,225],{"className":224},[171],"↶ back to step 1",[148,227,229,233],{"className":228},[151,152,153,211],[148,230,232],{"className":231},[171],"final answer ↓",[148,234,239],{"className":235},[236,237,161,219,220,164,166,198,238],"border-2","border-primary","font-semibold","Return the answer · done",[148,241,249],{"className":242},[243,178,159,244,245,246,247,248],"mt-4","border-red-500\u002F60","text-red-500","rounded-full","py-1.5","px-4","safety bound: force exit after MAX_ITERATIONS loops",[251,252,256],"figcaption",{"className":253},[178,171,254,166,255],"mt-3","italic","Three decisions per turn: ask, classify, act — loop until a final answer, or MAX_ITERATIONS forces the exit.",[258,259],"hr",{},[261,262,264],"h2",{"id":263},"_21-what-the-loop-has-to-do","2.1 What the Loop Has to Do",[112,266,267,268,271],{},"Three decisions happen on every iteration of the loop. They map cleanly onto the think-act-observe cycle of ReAct and onto the ",[115,269,270],{},"Planning → Tools → Memory → Action"," decomposition that Lilian Weng's widely-read 2023 post \"LLM Powered Autonomous Agents\" offered as a reference model for the field:",[273,274,275,282,288],"ol",{},[276,277,278,281],"li",{},[137,279,280],{},"Ask the model what to do next."," Send the current transcript, get a response.",[276,283,284,287],{},[137,285,286],{},"Decide whether the response is a tool call or a final answer."," If it's a tool call, execute the tool and append the result to the transcript; if it's a final answer, stop and return it.",[276,289,290,293],{},[137,291,292],{},"Bound the loop."," If we somehow hit N iterations without a final answer, stop anyway — a loop without a bound is a bug, not a feature, and in production it's the bug that usually shows up in the cost report before it shows up anywhere else.",[112,295,296],{},"That is the whole shape of the thing. Everything else in this book is accretion on top of those three decisions: compaction, sub-agents, streaming, evals — they all live inside, around, or between steps 1 and 2, while step 3 is where the cost-runaway failure modes get caught and bounded.",[112,298,299],{},"Two subtle points are worth naming before we write any code.",[112,301,302,305],{},[137,303,304],{},"The transcript is the state."," The loop has no other memory of what happened turn to turn; if a fact needs to persist across turns, it either lives in the transcript (and costs tokens forever) or it doesn't survive at all. Later chapters introduce external state — scratchpads, checkpointers, retrieval — but every one of them exists precisely because the transcript is too narrow a container for durable memory.",[112,307,308,311],{},[137,309,310],{},"The provider is a dependency, not the protagonist."," The loop doesn't care whether the response came from Anthropic, OpenAI, a locally-hosted Llama, or a mock; it cares only that something returns a response in a shape it can interpret. Designing that shape is the work of Chapter 3, and for this chapter a mock is all we need — strictly better than a real API for our purposes here, because it runs offline, deterministically, and costs nothing.",[258,313],{},[261,315,317],{"id":316},"_22-the-provider-protocol-introduced-early","2.2 The Provider Protocol, Introduced Early",[112,319,320,321,324,325,328],{},"Most tutorials start by calling ",[122,322,323],{},"anthropic.Anthropic()"," or ",[122,326,327],{},"OpenAI()"," directly in the loop — the right thing to do when you're exploring, and exactly the wrong thing when you're building something you expect to last. The moment a vendor SDK is imported from your core loop, you have taken on the vendor's quirks as part of your design: response envelope shape, streaming protocol, token-counting method, error taxonomy, all of it. Refactoring later means touching every file that ever touched the loop, and by then there are usually many.",[112,330,331,332,335],{},"Instead, we'll define a ",[122,333,334],{},"Provider"," protocol — a small, stable interface — and write a mock implementation of it. Chapter 3 writes real Anthropic and OpenAI adapters to the same protocol, and every subsequent chapter depends only on the protocol, never on a specific vendor's API surface.",[337,338,343],"pre",{"className":339,"code":340,"language":341,"meta":342,"style":342},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Fproviders\u002Fbase.py\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Protocol\n\n\n@dataclass(frozen=True)\nclass ProviderResponse:\n    \"\"\"What a provider gives us back: either text, or a tool call.\"\"\"\n    kind: str  # \"text\" or \"tool_call\"\n    text: str | None = None\n    tool_name: str | None = None\n    tool_args: dict | None = None\n    tool_call_id: str | None = None\n\n\nclass Provider(Protocol):\n    def complete(self, transcript: list[dict], tools: list[dict]) -> ProviderResponse:\n        \"\"\"Given a transcript and available tools, produce one response.\"\"\"\n        ...\n","python","",[122,344,345,353,371,378,392,405,410,415,445,459,473,489,511,529,548,566,571,576,592,649,660],{"__ignoreMap":342},[168,346,349],{"class":347,"line":348},"line",1,[168,350,352],{"class":351},"sutJx","# src\u002Fharness\u002Fproviders\u002Fbase.py\n",[168,354,356,360,364,367],{"class":347,"line":355},2,[168,357,359],{"class":358},"sVHd0","from",[168,361,363],{"class":362},"s_hVV"," __future__",[168,365,366],{"class":358}," import",[168,368,370],{"class":369},"su5hD"," annotations\n",[168,372,374],{"class":347,"line":373},3,[168,375,377],{"emptyLinePlaceholder":376},true,"\n",[168,379,381,383,386,389],{"class":347,"line":380},4,[168,382,359],{"class":358},[168,384,385],{"class":369}," dataclasses ",[168,387,388],{"class":358},"import",[168,390,391],{"class":369}," dataclass\n",[168,393,395,397,400,402],{"class":347,"line":394},5,[168,396,359],{"class":358},[168,398,399],{"class":369}," typing ",[168,401,388],{"class":358},[168,403,404],{"class":369}," Protocol\n",[168,406,408],{"class":347,"line":407},6,[168,409,377],{"emptyLinePlaceholder":376},[168,411,413],{"class":347,"line":412},7,[168,414,377],{"emptyLinePlaceholder":376},[168,416,418,422,426,430,434,438,442],{"class":347,"line":417},8,[168,419,421],{"class":420},"stp6e","@",[168,423,425],{"class":424},"sGLFI","dataclass",[168,427,429],{"class":428},"sP7_E","(",[168,431,433],{"class":432},"s99_P","frozen",[168,435,437],{"class":436},"smGrS","=",[168,439,441],{"class":440},"s39Yj","True",[168,443,444],{"class":428},")\n",[168,446,448,452,456],{"class":347,"line":447},9,[168,449,451],{"class":450},"sbsja","class",[168,453,455],{"class":454},"sbgvK"," ProviderResponse",[168,457,458],{"class":428},":\n",[168,460,462,466,470],{"class":347,"line":461},10,[168,463,465],{"class":464},"s2W-s","    \"\"\"",[168,467,469],{"class":468},"sithA","What a provider gives us back: either text, or a tool call.",[168,471,472],{"class":464},"\"\"\"\n",[168,474,476,479,482,486],{"class":347,"line":475},11,[168,477,478],{"class":369},"    kind",[168,480,481],{"class":428},":",[168,483,485],{"class":484},"sZMiF"," str",[168,487,488],{"class":351},"  # \"text\" or \"tool_call\"\n",[168,490,492,495,497,499,502,505,508],{"class":347,"line":491},12,[168,493,494],{"class":369},"    text",[168,496,481],{"class":428},[168,498,485],{"class":484},[168,500,501],{"class":436}," |",[168,503,504],{"class":440}," None",[168,506,507],{"class":436}," =",[168,509,510],{"class":440}," None\n",[168,512,514,517,519,521,523,525,527],{"class":347,"line":513},13,[168,515,516],{"class":369},"    tool_name",[168,518,481],{"class":428},[168,520,485],{"class":484},[168,522,501],{"class":436},[168,524,504],{"class":440},[168,526,507],{"class":436},[168,528,510],{"class":440},[168,530,532,535,537,540,542,544,546],{"class":347,"line":531},14,[168,533,534],{"class":369},"    tool_args",[168,536,481],{"class":428},[168,538,539],{"class":484}," dict",[168,541,501],{"class":436},[168,543,504],{"class":440},[168,545,507],{"class":436},[168,547,510],{"class":440},[168,549,551,554,556,558,560,562,564],{"class":347,"line":550},15,[168,552,553],{"class":369},"    tool_call_id",[168,555,481],{"class":428},[168,557,485],{"class":484},[168,559,501],{"class":436},[168,561,504],{"class":440},[168,563,507],{"class":436},[168,565,510],{"class":440},[168,567,569],{"class":347,"line":568},16,[168,570,377],{"emptyLinePlaceholder":376},[168,572,574],{"class":347,"line":573},17,[168,575,377],{"emptyLinePlaceholder":376},[168,577,579,581,584,586,589],{"class":347,"line":578},18,[168,580,451],{"class":450},[168,582,583],{"class":454}," Provider",[168,585,429],{"class":428},[168,587,588],{"class":454},"Protocol",[168,590,591],{"class":428},"):\n",[168,593,595,598,601,603,607,610,614,616,619,622,625,628,631,633,635,637,639,642,645,647],{"class":347,"line":594},19,[168,596,597],{"class":450},"    def",[168,599,600],{"class":424}," complete",[168,602,429],{"class":428},[168,604,606],{"class":605},"smCYv","self",[168,608,609],{"class":428},",",[168,611,613],{"class":612},"sFwrP"," transcript",[168,615,481],{"class":428},[168,617,618],{"class":369}," list",[168,620,621],{"class":428},"[",[168,623,624],{"class":484},"dict",[168,626,627],{"class":428},"],",[168,629,630],{"class":612}," tools",[168,632,481],{"class":428},[168,634,618],{"class":369},[168,636,621],{"class":428},[168,638,624],{"class":484},[168,640,641],{"class":428},"])",[168,643,644],{"class":428}," ->",[168,646,455],{"class":369},[168,648,458],{"class":428},[168,650,652,655,658],{"class":347,"line":651},20,[168,653,654],{"class":464},"        \"\"\"",[168,656,657],{"class":468},"Given a transcript and available tools, produce one response.",[168,659,472],{"class":464},[168,661,663],{"class":347,"line":662},21,[168,664,665],{"class":362},"        ...\n",[112,667,668],{},"Two notes on this protocol are worth pausing on before we use it.",[112,670,671,672,675,676,679,680,683,684,687],{},"The ",[122,673,674],{},"transcript"," and ",[122,677,678],{},"tools"," are plain ",[122,681,682],{},"list[dict]"," for now, which is a deliberate simplification; Chapter 3 promotes both of them to typed dataclasses with proper block structure and a ",[122,685,686],{},"Transcript"," wrapper. Using dicts here keeps the mock trivially easy to write, and the protocol stays small enough to read in about five seconds — an honest test of whether an abstraction is paying its way.",[112,689,690,693],{},[122,691,692],{},"ProviderResponse"," collapses two cases into one shape. Real provider responses are richer than this — they carry token counts, finish reasons, multiple content blocks, streaming chunks, reasoning traces — but none of that matters for the loop at this stage. The loop wants to know one thing: did the model ask me to call a tool, or did it give me an answer? Everything else is someone else's problem until we need it, and dragging it in now would be premature.",[112,695,696,697,700],{},"Now the mock. It implements a tiny scripted scenario: ask about ",[122,698,699],{},"2 + 2",", the mock calls a calculator tool, reads the result, and produces the answer.",[337,702,704],{"className":339,"code":703,"language":341,"meta":342,"style":342},"# src\u002Fharness\u002Fproviders\u002Fmock.py\nfrom __future__ import annotations\n\nfrom .base import Provider, ProviderResponse\n\n\nclass MockProvider(Provider):\n    \"\"\"A scripted provider for teaching and testing.\n\n    Walks through a fixed list of responses, one per call.\n    \"\"\"\n\n    def __init__(self, responses: list[ProviderResponse]) -> None:\n        self._responses = list(responses)\n        self._index = 0\n\n    def complete(self, transcript: list[dict], tools: list[dict]) -> ProviderResponse:\n        if self._index >= len(self._responses):\n            raise RuntimeError(\"mock ran out of responses\")\n        response = self._responses[self._index]\n        self._index += 1\n        return response\n",[122,705,706,711,721,725,744,748,752,765,772,776,781,786,790,823,847,862,866,908,936,958,982,996],{"__ignoreMap":342},[168,707,708],{"class":347,"line":348},[168,709,710],{"class":351},"# src\u002Fharness\u002Fproviders\u002Fmock.py\n",[168,712,713,715,717,719],{"class":347,"line":355},[168,714,359],{"class":358},[168,716,363],{"class":362},[168,718,366],{"class":358},[168,720,370],{"class":369},[168,722,723],{"class":347,"line":373},[168,724,377],{"emptyLinePlaceholder":376},[168,726,727,729,732,735,737,739,741],{"class":347,"line":380},[168,728,359],{"class":358},[168,730,731],{"class":428}," .",[168,733,734],{"class":369},"base ",[168,736,388],{"class":358},[168,738,583],{"class":369},[168,740,609],{"class":428},[168,742,743],{"class":369}," ProviderResponse\n",[168,745,746],{"class":347,"line":394},[168,747,377],{"emptyLinePlaceholder":376},[168,749,750],{"class":347,"line":407},[168,751,377],{"emptyLinePlaceholder":376},[168,753,754,756,759,761,763],{"class":347,"line":412},[168,755,451],{"class":450},[168,757,758],{"class":454}," MockProvider",[168,760,429],{"class":428},[168,762,334],{"class":454},[168,764,591],{"class":428},[168,766,767,769],{"class":347,"line":417},[168,768,465],{"class":464},[168,770,771],{"class":468},"A scripted provider for teaching and testing.\n",[168,773,774],{"class":347,"line":447},[168,775,377],{"emptyLinePlaceholder":376},[168,777,778],{"class":347,"line":461},[168,779,780],{"class":468},"    Walks through a fixed list of responses, one per call.\n",[168,782,783],{"class":347,"line":475},[168,784,785],{"class":464},"    \"\"\"\n",[168,787,788],{"class":347,"line":491},[168,789,377],{"emptyLinePlaceholder":376},[168,791,792,794,798,800,802,804,807,809,811,813,815,817,819,821],{"class":347,"line":513},[168,793,597],{"class":450},[168,795,797],{"class":796},"sptTA"," __init__",[168,799,429],{"class":428},[168,801,606],{"class":605},[168,803,609],{"class":428},[168,805,806],{"class":612}," responses",[168,808,481],{"class":428},[168,810,618],{"class":369},[168,812,621],{"class":428},[168,814,692],{"class":369},[168,816,641],{"class":428},[168,818,644],{"class":428},[168,820,504],{"class":440},[168,822,458],{"class":428},[168,824,825,828,831,835,837,839,841,845],{"class":347,"line":531},[168,826,827],{"class":362},"        self",[168,829,830],{"class":428},".",[168,832,834],{"class":833},"skxfh","_responses",[168,836,507],{"class":436},[168,838,618],{"class":484},[168,840,429],{"class":428},[168,842,844],{"class":843},"slqww","responses",[168,846,444],{"class":428},[168,848,849,851,853,856,858],{"class":347,"line":550},[168,850,827],{"class":362},[168,852,830],{"class":428},[168,854,855],{"class":833},"_index",[168,857,507],{"class":436},[168,859,861],{"class":860},"srdBf"," 0\n",[168,863,864],{"class":347,"line":568},[168,865,377],{"emptyLinePlaceholder":376},[168,867,868,870,872,874,876,878,880,882,884,886,888,890,892,894,896,898,900,902,904,906],{"class":347,"line":573},[168,869,597],{"class":450},[168,871,600],{"class":424},[168,873,429],{"class":428},[168,875,606],{"class":605},[168,877,609],{"class":428},[168,879,613],{"class":612},[168,881,481],{"class":428},[168,883,618],{"class":369},[168,885,621],{"class":428},[168,887,624],{"class":484},[168,889,627],{"class":428},[168,891,630],{"class":612},[168,893,481],{"class":428},[168,895,618],{"class":369},[168,897,621],{"class":428},[168,899,624],{"class":484},[168,901,641],{"class":428},[168,903,644],{"class":428},[168,905,455],{"class":369},[168,907,458],{"class":428},[168,909,910,913,916,918,920,923,926,928,930,932,934],{"class":347,"line":578},[168,911,912],{"class":358},"        if",[168,914,915],{"class":362}," self",[168,917,830],{"class":428},[168,919,855],{"class":833},[168,921,922],{"class":436}," >=",[168,924,925],{"class":796}," len",[168,927,429],{"class":428},[168,929,606],{"class":362},[168,931,830],{"class":428},[168,933,834],{"class":833},[168,935,591],{"class":428},[168,937,938,941,944,946,950,954,956],{"class":347,"line":594},[168,939,940],{"class":358},"            raise",[168,942,943],{"class":484}," RuntimeError",[168,945,429],{"class":428},[168,947,949],{"class":948},"sjJ54","\"",[168,951,953],{"class":952},"s_sjI","mock ran out of responses",[168,955,949],{"class":948},[168,957,444],{"class":428},[168,959,960,963,965,967,969,971,973,975,977,979],{"class":347,"line":651},[168,961,962],{"class":369},"        response ",[168,964,437],{"class":436},[168,966,915],{"class":362},[168,968,830],{"class":428},[168,970,834],{"class":833},[168,972,621],{"class":428},[168,974,606],{"class":362},[168,976,830],{"class":428},[168,978,855],{"class":833},[168,980,981],{"class":428},"]\n",[168,983,984,986,988,990,993],{"class":347,"line":662},[168,985,827],{"class":362},[168,987,830],{"class":428},[168,989,855],{"class":833},[168,991,992],{"class":436}," +=",[168,994,995],{"class":860}," 1\n",[168,997,999,1002],{"class":347,"line":998},22,[168,1000,1001],{"class":358},"        return",[168,1003,1004],{"class":369}," response\n",[112,1006,1007,1008,1011,1012,1014,1015,1018],{},"A note on the ",[122,1009,1010],{},"MockProvider(Provider)"," line. In Python, a ",[122,1013,588],{}," is satisfied structurally — any class with matching methods counts, no inheritance required. So why inherit? Two reasons. It documents intent: a reader sees \"this class implements the Provider contract\" without having to diff method signatures. And it turns a silent mismatch into a type-checker error at class definition time: forget to add ",[122,1016,1017],{},"complete",", or change its signature, and mypy\u002Fpyright flag the class instead of letting the bug surface later in the loop. The real-provider adapters in Chapter 3 do the same thing.",[112,1020,1021],{},"That's the whole provider abstraction for this chapter. Thirty lines of code and we have a seam we will keep for the entire book.",[258,1023],{},[261,1025,1027],{"id":1026},"_23-the-loop","2.3 The Loop",[112,1029,1030],{},"Here is the naive loop. I am calling it naive because it's about to break in five ways we already know about. It's still a useful starting point — everything it doesn't do will be motivated by a specific failure.",[337,1032,1034],{"className":339,"code":1033,"language":341,"meta":342,"style":342},"# src\u002Fharness\u002Fagent.py\nfrom __future__ import annotations\n\nfrom typing import Callable\n\nfrom .providers.base import Provider, ProviderResponse\n\n\nMAX_ITERATIONS = 20\n\n\ndef run(\n    provider: Provider,\n    tools: dict[str, Callable[..., str]],\n    tool_schemas: list[dict],\n    user_message: str,\n) -> str:\n    transcript: list[dict] = [{\"role\": \"user\", \"content\": user_message}]\n\n    for _ in range(MAX_ITERATIONS):\n        response = provider.complete(transcript, tool_schemas)\n\n        if response.kind == \"text\":\n            transcript.append({\"role\": \"assistant\", \"content\": response.text})\n            return response.text or \"\"\n\n        if response.kind == \"tool_call\":\n            if response.tool_name is None:\n                raise RuntimeError(\"tool_call response is missing tool_name\")\n            if response.tool_name not in tools:\n                raise RuntimeError(f\"unknown tool: {response.tool_name!r}\")\n\n            tool_fn = tools[response.tool_name]\n            result = tool_fn(**(response.tool_args or {}))\n\n            transcript.append({\n                \"role\": \"assistant\",\n                \"content\": [{\"type\": \"tool_use\", \"name\": response.tool_name,\n                             \"id\": response.tool_call_id, \"input\": response.tool_args}]\n            })\n            transcript.append({\n                \"role\": \"user\",\n                \"content\": [{\"type\": \"tool_result\", \"tool_use_id\": response.tool_call_id,\n                             \"content\": result}]\n            })\n            continue\n\n        raise RuntimeError(f\"unexpected response kind: {response.kind!r}\")\n\n    raise RuntimeError(f\"agent did not finish in {MAX_ITERATIONS} iterations\")\n",[122,1035,1036,1041,1051,1055,1066,1070,1091,1095,1099,1109,1113,1117,1128,1140,1171,1187,1198,1209,1264,1268,1288,1312,1316,1341,1389,1407,1412,1434,1454,1473,1494,1529,1534,1554,1584,1589,1601,1621,1669,1708,1714,1725,1744,1791,1807,1812,1818,1823,1854,1859],{"__ignoreMap":342},[168,1037,1038],{"class":347,"line":348},[168,1039,1040],{"class":351},"# src\u002Fharness\u002Fagent.py\n",[168,1042,1043,1045,1047,1049],{"class":347,"line":355},[168,1044,359],{"class":358},[168,1046,363],{"class":362},[168,1048,366],{"class":358},[168,1050,370],{"class":369},[168,1052,1053],{"class":347,"line":373},[168,1054,377],{"emptyLinePlaceholder":376},[168,1056,1057,1059,1061,1063],{"class":347,"line":380},[168,1058,359],{"class":358},[168,1060,399],{"class":369},[168,1062,388],{"class":358},[168,1064,1065],{"class":369}," Callable\n",[168,1067,1068],{"class":347,"line":394},[168,1069,377],{"emptyLinePlaceholder":376},[168,1071,1072,1074,1076,1079,1081,1083,1085,1087,1089],{"class":347,"line":407},[168,1073,359],{"class":358},[168,1075,731],{"class":428},[168,1077,1078],{"class":369},"providers",[168,1080,830],{"class":428},[168,1082,734],{"class":369},[168,1084,388],{"class":358},[168,1086,583],{"class":369},[168,1088,609],{"class":428},[168,1090,743],{"class":369},[168,1092,1093],{"class":347,"line":412},[168,1094,377],{"emptyLinePlaceholder":376},[168,1096,1097],{"class":347,"line":417},[168,1098,377],{"emptyLinePlaceholder":376},[168,1100,1101,1104,1106],{"class":347,"line":447},[168,1102,1103],{"class":362},"MAX_ITERATIONS",[168,1105,507],{"class":436},[168,1107,1108],{"class":860}," 20\n",[168,1110,1111],{"class":347,"line":461},[168,1112,377],{"emptyLinePlaceholder":376},[168,1114,1115],{"class":347,"line":475},[168,1116,377],{"emptyLinePlaceholder":376},[168,1118,1119,1122,1125],{"class":347,"line":491},[168,1120,1121],{"class":450},"def",[168,1123,1124],{"class":424}," run",[168,1126,1127],{"class":428},"(\n",[168,1129,1130,1133,1135,1137],{"class":347,"line":513},[168,1131,1132],{"class":612},"    provider",[168,1134,481],{"class":428},[168,1136,583],{"class":369},[168,1138,1139],{"class":428},",\n",[168,1141,1142,1145,1147,1149,1151,1154,1156,1159,1161,1164,1166,1168],{"class":347,"line":531},[168,1143,1144],{"class":612},"    tools",[168,1146,481],{"class":428},[168,1148,539],{"class":369},[168,1150,621],{"class":428},[168,1152,1153],{"class":484},"str",[168,1155,609],{"class":428},[168,1157,1158],{"class":369}," Callable",[168,1160,621],{"class":428},[168,1162,1163],{"class":362},"...",[168,1165,609],{"class":428},[168,1167,485],{"class":484},[168,1169,1170],{"class":428},"]],\n",[168,1172,1173,1176,1178,1180,1182,1184],{"class":347,"line":550},[168,1174,1175],{"class":612},"    tool_schemas",[168,1177,481],{"class":428},[168,1179,618],{"class":369},[168,1181,621],{"class":428},[168,1183,624],{"class":484},[168,1185,1186],{"class":428},"],\n",[168,1188,1189,1192,1194,1196],{"class":347,"line":568},[168,1190,1191],{"class":612},"    user_message",[168,1193,481],{"class":428},[168,1195,485],{"class":484},[168,1197,1139],{"class":428},[168,1199,1200,1203,1205,1207],{"class":347,"line":573},[168,1201,1202],{"class":428},")",[168,1204,644],{"class":428},[168,1206,485],{"class":484},[168,1208,458],{"class":428},[168,1210,1211,1214,1216,1218,1220,1222,1225,1227,1230,1232,1235,1237,1239,1242,1245,1247,1249,1251,1254,1256,1258,1261],{"class":347,"line":578},[168,1212,1213],{"class":369},"    transcript",[168,1215,481],{"class":428},[168,1217,618],{"class":369},[168,1219,621],{"class":428},[168,1221,624],{"class":484},[168,1223,1224],{"class":428},"]",[168,1226,507],{"class":436},[168,1228,1229],{"class":428}," [{",[168,1231,949],{"class":948},[168,1233,1234],{"class":952},"role",[168,1236,949],{"class":948},[168,1238,481],{"class":428},[168,1240,1241],{"class":948}," \"",[168,1243,1244],{"class":952},"user",[168,1246,949],{"class":948},[168,1248,609],{"class":428},[168,1250,1241],{"class":948},[168,1252,1253],{"class":952},"content",[168,1255,949],{"class":948},[168,1257,481],{"class":428},[168,1259,1260],{"class":369}," user_message",[168,1262,1263],{"class":428},"}]\n",[168,1265,1266],{"class":347,"line":594},[168,1267,377],{"emptyLinePlaceholder":376},[168,1269,1270,1273,1276,1279,1282,1284,1286],{"class":347,"line":651},[168,1271,1272],{"class":358},"    for",[168,1274,1275],{"class":369}," _ ",[168,1277,1278],{"class":358},"in",[168,1280,1281],{"class":796}," range",[168,1283,429],{"class":428},[168,1285,1103],{"class":796},[168,1287,591],{"class":428},[168,1289,1290,1292,1294,1297,1299,1301,1303,1305,1307,1310],{"class":347,"line":662},[168,1291,962],{"class":369},[168,1293,437],{"class":436},[168,1295,1296],{"class":369}," provider",[168,1298,830],{"class":428},[168,1300,1017],{"class":843},[168,1302,429],{"class":428},[168,1304,674],{"class":843},[168,1306,609],{"class":428},[168,1308,1309],{"class":843}," tool_schemas",[168,1311,444],{"class":428},[168,1313,1314],{"class":347,"line":998},[168,1315,377],{"emptyLinePlaceholder":376},[168,1317,1319,1321,1324,1326,1329,1332,1334,1337,1339],{"class":347,"line":1318},23,[168,1320,912],{"class":358},[168,1322,1323],{"class":369}," response",[168,1325,830],{"class":428},[168,1327,1328],{"class":833},"kind",[168,1330,1331],{"class":436}," ==",[168,1333,1241],{"class":948},[168,1335,1336],{"class":952},"text",[168,1338,949],{"class":948},[168,1340,458],{"class":428},[168,1342,1344,1347,1349,1352,1355,1357,1359,1361,1363,1365,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386],{"class":347,"line":1343},24,[168,1345,1346],{"class":369},"            transcript",[168,1348,830],{"class":428},[168,1350,1351],{"class":843},"append",[168,1353,1354],{"class":428},"({",[168,1356,949],{"class":948},[168,1358,1234],{"class":952},[168,1360,949],{"class":948},[168,1362,481],{"class":428},[168,1364,1241],{"class":948},[168,1366,1367],{"class":952},"assistant",[168,1369,949],{"class":948},[168,1371,609],{"class":428},[168,1373,1241],{"class":948},[168,1375,1253],{"class":952},[168,1377,949],{"class":948},[168,1379,481],{"class":428},[168,1381,1323],{"class":843},[168,1383,830],{"class":428},[168,1385,1336],{"class":833},[168,1387,1388],{"class":428},"})\n",[168,1390,1392,1395,1397,1399,1401,1404],{"class":347,"line":1391},25,[168,1393,1394],{"class":358},"            return",[168,1396,1323],{"class":369},[168,1398,830],{"class":428},[168,1400,1336],{"class":833},[168,1402,1403],{"class":436}," or",[168,1405,1406],{"class":948}," \"\"\n",[168,1408,1410],{"class":347,"line":1409},26,[168,1411,377],{"emptyLinePlaceholder":376},[168,1413,1415,1417,1419,1421,1423,1425,1427,1430,1432],{"class":347,"line":1414},27,[168,1416,912],{"class":358},[168,1418,1323],{"class":369},[168,1420,830],{"class":428},[168,1422,1328],{"class":833},[168,1424,1331],{"class":436},[168,1426,1241],{"class":948},[168,1428,1429],{"class":952},"tool_call",[168,1431,949],{"class":948},[168,1433,458],{"class":428},[168,1435,1437,1440,1442,1444,1447,1450,1452],{"class":347,"line":1436},28,[168,1438,1439],{"class":358},"            if",[168,1441,1323],{"class":369},[168,1443,830],{"class":428},[168,1445,1446],{"class":833},"tool_name",[168,1448,1449],{"class":436}," is",[168,1451,504],{"class":440},[168,1453,458],{"class":428},[168,1455,1457,1460,1462,1464,1466,1469,1471],{"class":347,"line":1456},29,[168,1458,1459],{"class":358},"                raise",[168,1461,943],{"class":484},[168,1463,429],{"class":428},[168,1465,949],{"class":948},[168,1467,1468],{"class":952},"tool_call response is missing tool_name",[168,1470,949],{"class":948},[168,1472,444],{"class":428},[168,1474,1476,1478,1480,1482,1484,1487,1490,1492],{"class":347,"line":1475},30,[168,1477,1439],{"class":358},[168,1479,1323],{"class":369},[168,1481,830],{"class":428},[168,1483,1446],{"class":833},[168,1485,1486],{"class":436}," not",[168,1488,1489],{"class":436}," in",[168,1491,630],{"class":369},[168,1493,458],{"class":428},[168,1495,1497,1499,1501,1503,1506,1509,1512,1515,1517,1519,1522,1525,1527],{"class":347,"line":1496},31,[168,1498,1459],{"class":358},[168,1500,943],{"class":484},[168,1502,429],{"class":428},[168,1504,1505],{"class":450},"f",[168,1507,1508],{"class":952},"\"unknown tool: ",[168,1510,1511],{"class":860},"{",[168,1513,1514],{"class":843},"response",[168,1516,830],{"class":428},[168,1518,1446],{"class":833},[168,1520,1521],{"class":450},"!r",[168,1523,1524],{"class":860},"}",[168,1526,949],{"class":952},[168,1528,444],{"class":428},[168,1530,1532],{"class":347,"line":1531},32,[168,1533,377],{"emptyLinePlaceholder":376},[168,1535,1537,1540,1542,1544,1546,1548,1550,1552],{"class":347,"line":1536},33,[168,1538,1539],{"class":369},"            tool_fn ",[168,1541,437],{"class":436},[168,1543,630],{"class":369},[168,1545,621],{"class":428},[168,1547,1514],{"class":369},[168,1549,830],{"class":428},[168,1551,1446],{"class":833},[168,1553,981],{"class":428},[168,1555,1557,1560,1562,1565,1567,1570,1572,1574,1576,1579,1581],{"class":347,"line":1556},34,[168,1558,1559],{"class":369},"            result ",[168,1561,437],{"class":436},[168,1563,1564],{"class":843}," tool_fn",[168,1566,429],{"class":428},[168,1568,1569],{"class":436},"**",[168,1571,429],{"class":428},[168,1573,1514],{"class":843},[168,1575,830],{"class":428},[168,1577,1578],{"class":833},"tool_args",[168,1580,1403],{"class":436},[168,1582,1583],{"class":428}," {}))\n",[168,1585,1587],{"class":347,"line":1586},35,[168,1588,377],{"emptyLinePlaceholder":376},[168,1590,1592,1594,1596,1598],{"class":347,"line":1591},36,[168,1593,1346],{"class":369},[168,1595,830],{"class":428},[168,1597,1351],{"class":843},[168,1599,1600],{"class":428},"({\n",[168,1602,1604,1607,1609,1611,1613,1615,1617,1619],{"class":347,"line":1603},37,[168,1605,1606],{"class":948},"                \"",[168,1608,1234],{"class":952},[168,1610,949],{"class":948},[168,1612,481],{"class":428},[168,1614,1241],{"class":948},[168,1616,1367],{"class":952},[168,1618,949],{"class":948},[168,1620,1139],{"class":428},[168,1622,1624,1626,1628,1630,1632,1634,1636,1639,1641,1643,1645,1648,1650,1652,1654,1657,1659,1661,1663,1665,1667],{"class":347,"line":1623},38,[168,1625,1606],{"class":948},[168,1627,1253],{"class":952},[168,1629,949],{"class":948},[168,1631,481],{"class":428},[168,1633,1229],{"class":428},[168,1635,949],{"class":948},[168,1637,1638],{"class":952},"type",[168,1640,949],{"class":948},[168,1642,481],{"class":428},[168,1644,1241],{"class":948},[168,1646,1647],{"class":952},"tool_use",[168,1649,949],{"class":948},[168,1651,609],{"class":428},[168,1653,1241],{"class":948},[168,1655,1656],{"class":952},"name",[168,1658,949],{"class":948},[168,1660,481],{"class":428},[168,1662,1323],{"class":843},[168,1664,830],{"class":428},[168,1666,1446],{"class":833},[168,1668,1139],{"class":428},[168,1670,1672,1675,1678,1680,1682,1684,1686,1689,1691,1693,1696,1698,1700,1702,1704,1706],{"class":347,"line":1671},39,[168,1673,1674],{"class":948},"                             \"",[168,1676,1677],{"class":952},"id",[168,1679,949],{"class":948},[168,1681,481],{"class":428},[168,1683,1323],{"class":843},[168,1685,830],{"class":428},[168,1687,1688],{"class":833},"tool_call_id",[168,1690,609],{"class":428},[168,1692,1241],{"class":948},[168,1694,1695],{"class":952},"input",[168,1697,949],{"class":948},[168,1699,481],{"class":428},[168,1701,1323],{"class":843},[168,1703,830],{"class":428},[168,1705,1578],{"class":833},[168,1707,1263],{"class":428},[168,1709,1711],{"class":347,"line":1710},40,[168,1712,1713],{"class":428},"            })\n",[168,1715,1717,1719,1721,1723],{"class":347,"line":1716},41,[168,1718,1346],{"class":369},[168,1720,830],{"class":428},[168,1722,1351],{"class":843},[168,1724,1600],{"class":428},[168,1726,1728,1730,1732,1734,1736,1738,1740,1742],{"class":347,"line":1727},42,[168,1729,1606],{"class":948},[168,1731,1234],{"class":952},[168,1733,949],{"class":948},[168,1735,481],{"class":428},[168,1737,1241],{"class":948},[168,1739,1244],{"class":952},[168,1741,949],{"class":948},[168,1743,1139],{"class":428},[168,1745,1747,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1770,1772,1774,1776,1779,1781,1783,1785,1787,1789],{"class":347,"line":1746},43,[168,1748,1606],{"class":948},[168,1750,1253],{"class":952},[168,1752,949],{"class":948},[168,1754,481],{"class":428},[168,1756,1229],{"class":428},[168,1758,949],{"class":948},[168,1760,1638],{"class":952},[168,1762,949],{"class":948},[168,1764,481],{"class":428},[168,1766,1241],{"class":948},[168,1768,1769],{"class":952},"tool_result",[168,1771,949],{"class":948},[168,1773,609],{"class":428},[168,1775,1241],{"class":948},[168,1777,1778],{"class":952},"tool_use_id",[168,1780,949],{"class":948},[168,1782,481],{"class":428},[168,1784,1323],{"class":843},[168,1786,830],{"class":428},[168,1788,1688],{"class":833},[168,1790,1139],{"class":428},[168,1792,1794,1796,1798,1800,1802,1805],{"class":347,"line":1793},44,[168,1795,1674],{"class":948},[168,1797,1253],{"class":952},[168,1799,949],{"class":948},[168,1801,481],{"class":428},[168,1803,1804],{"class":843}," result",[168,1806,1263],{"class":428},[168,1808,1810],{"class":347,"line":1809},45,[168,1811,1713],{"class":428},[168,1813,1815],{"class":347,"line":1814},46,[168,1816,1817],{"class":358},"            continue\n",[168,1819,1821],{"class":347,"line":1820},47,[168,1822,377],{"emptyLinePlaceholder":376},[168,1824,1826,1829,1831,1833,1835,1838,1840,1842,1844,1846,1848,1850,1852],{"class":347,"line":1825},48,[168,1827,1828],{"class":358},"        raise",[168,1830,943],{"class":484},[168,1832,429],{"class":428},[168,1834,1505],{"class":450},[168,1836,1837],{"class":952},"\"unexpected response kind: ",[168,1839,1511],{"class":860},[168,1841,1514],{"class":843},[168,1843,830],{"class":428},[168,1845,1328],{"class":833},[168,1847,1521],{"class":450},[168,1849,1524],{"class":860},[168,1851,949],{"class":952},[168,1853,444],{"class":428},[168,1855,1857],{"class":347,"line":1856},49,[168,1858,377],{"emptyLinePlaceholder":376},[168,1860,1862,1865,1867,1869,1871,1874,1876,1878,1880,1883],{"class":347,"line":1861},50,[168,1863,1864],{"class":358},"    raise",[168,1866,943],{"class":484},[168,1868,429],{"class":428},[168,1870,1505],{"class":450},[168,1872,1873],{"class":952},"\"agent did not finish in ",[168,1875,1511],{"class":860},[168,1877,1103],{"class":796},[168,1879,1524],{"class":860},[168,1881,1882],{"class":952}," iterations\"",[168,1884,444],{"class":428},[112,1886,1887,1888,1891,1892,1895,1896,1899,1900,1903,1904,1907,1908,1911,1912,1915,1916,1919],{},"Notice the three explicit guard clauses inside the tool-call branch: ",[122,1889,1890],{},"tool_name is None",", ",[122,1893,1894],{},"tool_name not in tools",", and a final ",[122,1897,1898],{},"else"," that raises on an unexpected ",[122,1901,1902],{},"response.kind",". The comment-based assumption ",[122,1905,1906],{},"# response.kind == \"tool_call\""," that a lot of tutorial code relies on is, in practice, a silent ",[122,1909,1910],{},"None","-deref or an opaque ",[122,1913,1914],{},"KeyError"," waiting for its turn. The rule for this book is simple: if the type system doesn't narrow the case for you, narrow it yourself and raise a descriptive error. Later chapters replace these raises with structured ",[122,1917,1918],{},"ToolResult","s that the model can read and recover from, but even then every branch is still enumerated — defensive enumeration of cases is the engineering discipline the harness rests on.",[112,1921,1922],{},"Read that loop twice. Everything after it in the book is about one of the implicit choices you can see right here:",[1924,1925,1926,1929,1936,1943,1949,1952],"ul",{},[276,1927,1928],{},"The transcript is a list of dicts — no type safety, no validation, no block-level structure.",[276,1930,1931,1932,1935],{},"Tools are a ",[122,1933,1934],{},"dict[str, Callable]"," — no schema check, no side-effect declaration, no permission gate.",[276,1937,1938,1939,1942],{},"The tool is called with ",[122,1940,1941],{},"**response.tool_args"," and we trust it implicitly.",[276,1944,1945,1948],{},[122,1946,1947],{},"result"," is a string. If the tool returned 50,000 characters of JSON, it goes straight into the transcript.",[276,1950,1951],{},"There's no streaming, no cancellation, no retry, no observability, no cost counting.",[276,1953,1954,1957],{},[122,1955,1956],{},"MAX_ITERATIONS = 20"," is the only thing standing between this loop and an unbounded cost runaway.",[112,1959,1960],{},"That is the point — we are not going to pretend to be surprised when it breaks.",[258,1962],{},[261,1964,1966],{"id":1965},"_24-the-first-run","2.4 The First Run",[112,1968,1969],{},"Let's make it work before we make it fail. A calculator tool, a mock scenario.",[337,1971,1973],{"className":339,"code":1972,"language":341,"meta":342,"style":342},"# examples\u002Fch02_calculator.py\nfrom harness.agent import run\nfrom harness.providers.base import ProviderResponse\nfrom harness.providers.mock import MockProvider\n\n\ndef calc(expression: str) -> str:\n    # dangerous in real life; fine for a mock\n    return str(eval(expression, {\"__builtins__\": {}}, {}))\n\n\nmock = MockProvider([\n    ProviderResponse(\n        kind=\"tool_call\",\n        tool_name=\"calc\",\n        tool_args={\"expression\": \"2 + 2\"},\n        tool_call_id=\"call-1\",\n    ),\n    ProviderResponse(kind=\"text\", text=\"2 + 2 is 4.\"),\n])\n\ntool_schemas = [{\n    \"name\": \"calc\",\n    \"description\": \"Evaluate a Python arithmetic expression.\",\n    \"input_schema\": {\n        \"type\": \"object\",\n        \"properties\": {\"expression\": {\"type\": \"string\"}},\n        \"required\": [\"expression\"],\n    },\n}]\n\nanswer = run(\n    provider=mock,\n    tools={\"calc\": calc},\n    tool_schemas=tool_schemas,\n    user_message=\"What is 2 + 2?\",\n)\n\nprint(answer)  # -> \"2 + 2 is 4.\"\n",[122,1974,1975,1980,1997,2015,2035,2039,2043,2067,2072,2107,2111,2115,2126,2133,2148,2164,2190,2206,2211,2244,2249,2253,2263,2282,2302,2316,2336,2377,2399,2404,2408,2412,2423,2434,2454,2465,2480,2484,2488],{"__ignoreMap":342},[168,1976,1977],{"class":347,"line":348},[168,1978,1979],{"class":351},"# examples\u002Fch02_calculator.py\n",[168,1981,1982,1984,1987,1989,1992,1994],{"class":347,"line":355},[168,1983,359],{"class":358},[168,1985,1986],{"class":369}," harness",[168,1988,830],{"class":428},[168,1990,1991],{"class":369},"agent ",[168,1993,388],{"class":358},[168,1995,1996],{"class":369}," run\n",[168,1998,1999,2001,2003,2005,2007,2009,2011,2013],{"class":347,"line":373},[168,2000,359],{"class":358},[168,2002,1986],{"class":369},[168,2004,830],{"class":428},[168,2006,1078],{"class":369},[168,2008,830],{"class":428},[168,2010,734],{"class":369},[168,2012,388],{"class":358},[168,2014,743],{"class":369},[168,2016,2017,2019,2021,2023,2025,2027,2030,2032],{"class":347,"line":380},[168,2018,359],{"class":358},[168,2020,1986],{"class":369},[168,2022,830],{"class":428},[168,2024,1078],{"class":369},[168,2026,830],{"class":428},[168,2028,2029],{"class":369},"mock ",[168,2031,388],{"class":358},[168,2033,2034],{"class":369}," MockProvider\n",[168,2036,2037],{"class":347,"line":394},[168,2038,377],{"emptyLinePlaceholder":376},[168,2040,2041],{"class":347,"line":407},[168,2042,377],{"emptyLinePlaceholder":376},[168,2044,2045,2047,2050,2052,2055,2057,2059,2061,2063,2065],{"class":347,"line":412},[168,2046,1121],{"class":450},[168,2048,2049],{"class":424}," calc",[168,2051,429],{"class":428},[168,2053,2054],{"class":612},"expression",[168,2056,481],{"class":428},[168,2058,485],{"class":484},[168,2060,1202],{"class":428},[168,2062,644],{"class":428},[168,2064,485],{"class":484},[168,2066,458],{"class":428},[168,2068,2069],{"class":347,"line":417},[168,2070,2071],{"class":351},"    # dangerous in real life; fine for a mock\n",[168,2073,2074,2077,2079,2081,2084,2086,2088,2090,2093,2095,2098,2100,2102,2105],{"class":347,"line":447},[168,2075,2076],{"class":358},"    return",[168,2078,485],{"class":484},[168,2080,429],{"class":428},[168,2082,2083],{"class":796},"eval",[168,2085,429],{"class":428},[168,2087,2054],{"class":843},[168,2089,609],{"class":428},[168,2091,2092],{"class":428}," {",[168,2094,949],{"class":948},[168,2096,2097],{"class":952},"__builtins__",[168,2099,949],{"class":948},[168,2101,481],{"class":428},[168,2103,2104],{"class":428}," {}},",[168,2106,1583],{"class":428},[168,2108,2109],{"class":347,"line":461},[168,2110,377],{"emptyLinePlaceholder":376},[168,2112,2113],{"class":347,"line":475},[168,2114,377],{"emptyLinePlaceholder":376},[168,2116,2117,2119,2121,2123],{"class":347,"line":491},[168,2118,2029],{"class":369},[168,2120,437],{"class":436},[168,2122,758],{"class":843},[168,2124,2125],{"class":428},"([\n",[168,2127,2128,2131],{"class":347,"line":513},[168,2129,2130],{"class":843},"    ProviderResponse",[168,2132,1127],{"class":428},[168,2134,2135,2138,2140,2142,2144,2146],{"class":347,"line":531},[168,2136,2137],{"class":432},"        kind",[168,2139,437],{"class":436},[168,2141,949],{"class":948},[168,2143,1429],{"class":952},[168,2145,949],{"class":948},[168,2147,1139],{"class":428},[168,2149,2150,2153,2155,2157,2160,2162],{"class":347,"line":550},[168,2151,2152],{"class":432},"        tool_name",[168,2154,437],{"class":436},[168,2156,949],{"class":948},[168,2158,2159],{"class":952},"calc",[168,2161,949],{"class":948},[168,2163,1139],{"class":428},[168,2165,2166,2169,2171,2173,2175,2177,2179,2181,2183,2185,2187],{"class":347,"line":568},[168,2167,2168],{"class":432},"        tool_args",[168,2170,437],{"class":436},[168,2172,1511],{"class":428},[168,2174,949],{"class":948},[168,2176,2054],{"class":952},[168,2178,949],{"class":948},[168,2180,481],{"class":428},[168,2182,1241],{"class":948},[168,2184,699],{"class":952},[168,2186,949],{"class":948},[168,2188,2189],{"class":428},"},\n",[168,2191,2192,2195,2197,2199,2202,2204],{"class":347,"line":573},[168,2193,2194],{"class":432},"        tool_call_id",[168,2196,437],{"class":436},[168,2198,949],{"class":948},[168,2200,2201],{"class":952},"call-1",[168,2203,949],{"class":948},[168,2205,1139],{"class":428},[168,2207,2208],{"class":347,"line":578},[168,2209,2210],{"class":428},"    ),\n",[168,2212,2213,2215,2217,2219,2221,2223,2225,2227,2229,2232,2234,2236,2239,2241],{"class":347,"line":594},[168,2214,2130],{"class":843},[168,2216,429],{"class":428},[168,2218,1328],{"class":432},[168,2220,437],{"class":436},[168,2222,949],{"class":948},[168,2224,1336],{"class":952},[168,2226,949],{"class":948},[168,2228,609],{"class":428},[168,2230,2231],{"class":432}," text",[168,2233,437],{"class":436},[168,2235,949],{"class":948},[168,2237,2238],{"class":952},"2 + 2 is 4.",[168,2240,949],{"class":948},[168,2242,2243],{"class":428},"),\n",[168,2245,2246],{"class":347,"line":651},[168,2247,2248],{"class":428},"])\n",[168,2250,2251],{"class":347,"line":662},[168,2252,377],{"emptyLinePlaceholder":376},[168,2254,2255,2258,2260],{"class":347,"line":998},[168,2256,2257],{"class":369},"tool_schemas ",[168,2259,437],{"class":436},[168,2261,2262],{"class":428}," [{\n",[168,2264,2265,2268,2270,2272,2274,2276,2278,2280],{"class":347,"line":1318},[168,2266,2267],{"class":948},"    \"",[168,2269,1656],{"class":952},[168,2271,949],{"class":948},[168,2273,481],{"class":428},[168,2275,1241],{"class":948},[168,2277,2159],{"class":952},[168,2279,949],{"class":948},[168,2281,1139],{"class":428},[168,2283,2284,2286,2289,2291,2293,2295,2298,2300],{"class":347,"line":1343},[168,2285,2267],{"class":948},[168,2287,2288],{"class":952},"description",[168,2290,949],{"class":948},[168,2292,481],{"class":428},[168,2294,1241],{"class":948},[168,2296,2297],{"class":952},"Evaluate a Python arithmetic expression.",[168,2299,949],{"class":948},[168,2301,1139],{"class":428},[168,2303,2304,2306,2309,2311,2313],{"class":347,"line":1391},[168,2305,2267],{"class":948},[168,2307,2308],{"class":952},"input_schema",[168,2310,949],{"class":948},[168,2312,481],{"class":428},[168,2314,2315],{"class":428}," {\n",[168,2317,2318,2321,2323,2325,2327,2329,2332,2334],{"class":347,"line":1409},[168,2319,2320],{"class":948},"        \"",[168,2322,1638],{"class":952},[168,2324,949],{"class":948},[168,2326,481],{"class":428},[168,2328,1241],{"class":948},[168,2330,2331],{"class":952},"object",[168,2333,949],{"class":948},[168,2335,1139],{"class":428},[168,2337,2338,2340,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367,2369,2372,2374],{"class":347,"line":1414},[168,2339,2320],{"class":948},[168,2341,2342],{"class":952},"properties",[168,2344,949],{"class":948},[168,2346,481],{"class":428},[168,2348,2092],{"class":428},[168,2350,949],{"class":948},[168,2352,2054],{"class":952},[168,2354,949],{"class":948},[168,2356,481],{"class":428},[168,2358,2092],{"class":428},[168,2360,949],{"class":948},[168,2362,1638],{"class":952},[168,2364,949],{"class":948},[168,2366,481],{"class":428},[168,2368,1241],{"class":948},[168,2370,2371],{"class":952},"string",[168,2373,949],{"class":948},[168,2375,2376],{"class":428},"}},\n",[168,2378,2379,2381,2384,2386,2388,2391,2393,2395,2397],{"class":347,"line":1436},[168,2380,2320],{"class":948},[168,2382,2383],{"class":952},"required",[168,2385,949],{"class":948},[168,2387,481],{"class":428},[168,2389,2390],{"class":428}," [",[168,2392,949],{"class":948},[168,2394,2054],{"class":952},[168,2396,949],{"class":948},[168,2398,1186],{"class":428},[168,2400,2401],{"class":347,"line":1456},[168,2402,2403],{"class":428},"    },\n",[168,2405,2406],{"class":347,"line":1475},[168,2407,1263],{"class":428},[168,2409,2410],{"class":347,"line":1496},[168,2411,377],{"emptyLinePlaceholder":376},[168,2413,2414,2417,2419,2421],{"class":347,"line":1531},[168,2415,2416],{"class":369},"answer ",[168,2418,437],{"class":436},[168,2420,1124],{"class":843},[168,2422,1127],{"class":428},[168,2424,2425,2427,2429,2432],{"class":347,"line":1536},[168,2426,1132],{"class":432},[168,2428,437],{"class":436},[168,2430,2431],{"class":843},"mock",[168,2433,1139],{"class":428},[168,2435,2436,2438,2440,2442,2444,2446,2448,2450,2452],{"class":347,"line":1556},[168,2437,1144],{"class":432},[168,2439,437],{"class":436},[168,2441,1511],{"class":428},[168,2443,949],{"class":948},[168,2445,2159],{"class":952},[168,2447,949],{"class":948},[168,2449,481],{"class":428},[168,2451,2049],{"class":843},[168,2453,2189],{"class":428},[168,2455,2456,2458,2460,2463],{"class":347,"line":1586},[168,2457,1175],{"class":432},[168,2459,437],{"class":436},[168,2461,2462],{"class":843},"tool_schemas",[168,2464,1139],{"class":428},[168,2466,2467,2469,2471,2473,2476,2478],{"class":347,"line":1591},[168,2468,1191],{"class":432},[168,2470,437],{"class":436},[168,2472,949],{"class":948},[168,2474,2475],{"class":952},"What is 2 + 2?",[168,2477,949],{"class":948},[168,2479,1139],{"class":428},[168,2481,2482],{"class":347,"line":1603},[168,2483,444],{"class":428},[168,2485,2486],{"class":347,"line":1623},[168,2487,377],{"emptyLinePlaceholder":376},[168,2489,2490,2493,2495,2498,2500],{"class":347,"line":1671},[168,2491,2492],{"class":796},"print",[168,2494,429],{"class":428},[168,2496,2497],{"class":843},"answer",[168,2499,1202],{"class":428},[168,2501,2502],{"class":351},"  # -> \"2 + 2 is 4.\"\n",[112,2504,2505],{},"Run it:",[337,2507,2511],{"className":2508,"code":2509,"language":2510,"meta":342,"style":342},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","uv run examples\u002Fch02_calculator.py\n","bash",[122,2512,2513],{"__ignoreMap":342},[168,2514,2515,2518,2520],{"class":347,"line":348},[168,2516,2517],{"class":454},"uv",[168,2519,1124],{"class":952},[168,2521,2522],{"class":952}," examples\u002Fch02_calculator.py\n",[112,2524,2525,2526,2528],{},"You should see ",[122,2527,2238],{}," printed. Two turns — the model asks for the calculator, we run it, the model reads the result, the model produces a final answer — and that is an agent. A small, contrived, brittle one, but structurally the real thing; every harness in the rest of the book, and every production harness in the wild, is a variation on this same two-turn pattern with progressively more engineering layered between the asks.",[112,2530,2531],{},"Commit:",[337,2533,2535],{"className":2508,"code":2534,"language":2510,"meta":342,"style":342},"git add -A && git commit -m \"ch02: minimum viable loop with mock provider\"\ngit tag ch02-minimum-loop\n",[122,2536,2537,2569],{"__ignoreMap":342},[168,2538,2539,2542,2545,2549,2552,2555,2558,2561,2563,2566],{"class":347,"line":348},[168,2540,2541],{"class":454},"git",[168,2543,2544],{"class":952}," add",[168,2546,2548],{"class":2547},"stzsN"," -A",[168,2550,2551],{"class":428}," &&",[168,2553,2554],{"class":454}," git",[168,2556,2557],{"class":952}," commit",[168,2559,2560],{"class":2547}," -m",[168,2562,1241],{"class":948},[168,2564,2565],{"class":952},"ch02: minimum viable loop with mock provider",[168,2567,2568],{"class":948},"\"\n",[168,2570,2571,2573,2576],{"class":347,"line":355},[168,2572,2541],{"class":454},[168,2574,2575],{"class":952}," tag",[168,2577,2578],{"class":952}," ch02-minimum-loop\n",[258,2580],{},[261,2582,2584],{"id":2583},"_25-five-ways-to-break-it","2.5 Five Ways to Break It",[112,2586,2587],{},"Now the pedagogically useful part. We are going to feed the loop five specific failure scenarios, one at a time, and watch each one reveal a missing piece of engineering the naive version quietly assumed would not matter.",[2589,2590,2592],"h3",{"id":2591},"break-1-the-model-asks-for-a-tool-that-doesnt-exist","Break 1: The model asks for a tool that doesn't exist",[112,2594,2595],{},"Change the mock's first response to call a tool we didn't register:",[337,2597,2599],{"className":339,"code":2598,"language":341,"meta":342,"style":342},"ProviderResponse(\n    kind=\"tool_call\",\n    tool_name=\"calculator\",  # not \"calc\"\n    tool_args={\"expression\": \"2 + 2\"},\n    tool_call_id=\"call-1\",\n),\n",[122,2600,2601,2607,2621,2639,2663,2677],{"__ignoreMap":342},[168,2602,2603,2605],{"class":347,"line":348},[168,2604,692],{"class":843},[168,2606,1127],{"class":428},[168,2608,2609,2611,2613,2615,2617,2619],{"class":347,"line":355},[168,2610,478],{"class":432},[168,2612,437],{"class":436},[168,2614,949],{"class":948},[168,2616,1429],{"class":952},[168,2618,949],{"class":948},[168,2620,1139],{"class":428},[168,2622,2623,2625,2627,2629,2632,2634,2636],{"class":347,"line":373},[168,2624,516],{"class":432},[168,2626,437],{"class":436},[168,2628,949],{"class":948},[168,2630,2631],{"class":952},"calculator",[168,2633,949],{"class":948},[168,2635,609],{"class":428},[168,2637,2638],{"class":351},"  # not \"calc\"\n",[168,2640,2641,2643,2645,2647,2649,2651,2653,2655,2657,2659,2661],{"class":347,"line":380},[168,2642,534],{"class":432},[168,2644,437],{"class":436},[168,2646,1511],{"class":428},[168,2648,949],{"class":948},[168,2650,2054],{"class":952},[168,2652,949],{"class":948},[168,2654,481],{"class":428},[168,2656,1241],{"class":948},[168,2658,699],{"class":952},[168,2660,949],{"class":948},[168,2662,2189],{"class":428},[168,2664,2665,2667,2669,2671,2673,2675],{"class":347,"line":394},[168,2666,553],{"class":432},[168,2668,437],{"class":436},[168,2670,949],{"class":948},[168,2672,2201],{"class":952},[168,2674,949],{"class":948},[168,2676,1139],{"class":428},[168,2678,2679],{"class":347,"line":407},[168,2680,2243],{"class":428},[112,2682,2683,2684,2687,2688,2690],{},"Run it and you get a ",[122,2685,2686],{},"RuntimeError: unknown tool: 'calculator'",". The guard clause we added catches the missing name and the loop crashes deliberately, rather than stumbling into a silent ",[122,2689,1914],{}," deep inside the tool lookup — which is strictly better, because the error now names the actual problem. The model, though, has no chance to recover: the exception unwinds the whole loop, and a misnamed tool in turn three kills a session that had nine turns of useful work behind it.",[112,2692,2693,2696,2697,2700],{},[137,2694,2695],{},"What's missing."," A dispatch layer that catches \"unknown tool\" and returns a structured error to the model as a tool result, so the model gets one more chance to call the right tool. Chapter 4 introduces the ",[122,2698,2699],{},"ToolRegistry"," that does this.",[2589,2702,2704],{"id":2703},"break-2-the-models-tool-arguments-dont-match-the-schema","Break 2: The model's tool arguments don't match the schema",[337,2706,2708],{"className":339,"code":2707,"language":341,"meta":342,"style":342},"ProviderResponse(\n    kind=\"tool_call\",\n    tool_name=\"calc\",\n    tool_args={\"expr\": \"2 + 2\"},  # wrong key name\n    tool_call_id=\"call-1\",\n),\n",[122,2709,2710,2716,2730,2744,2773,2787],{"__ignoreMap":342},[168,2711,2712,2714],{"class":347,"line":348},[168,2713,692],{"class":843},[168,2715,1127],{"class":428},[168,2717,2718,2720,2722,2724,2726,2728],{"class":347,"line":355},[168,2719,478],{"class":432},[168,2721,437],{"class":436},[168,2723,949],{"class":948},[168,2725,1429],{"class":952},[168,2727,949],{"class":948},[168,2729,1139],{"class":428},[168,2731,2732,2734,2736,2738,2740,2742],{"class":347,"line":373},[168,2733,516],{"class":432},[168,2735,437],{"class":436},[168,2737,949],{"class":948},[168,2739,2159],{"class":952},[168,2741,949],{"class":948},[168,2743,1139],{"class":428},[168,2745,2746,2748,2750,2752,2754,2757,2759,2761,2763,2765,2767,2770],{"class":347,"line":380},[168,2747,534],{"class":432},[168,2749,437],{"class":436},[168,2751,1511],{"class":428},[168,2753,949],{"class":948},[168,2755,2756],{"class":952},"expr",[168,2758,949],{"class":948},[168,2760,481],{"class":428},[168,2762,1241],{"class":948},[168,2764,699],{"class":952},[168,2766,949],{"class":948},[168,2768,2769],{"class":428},"},",[168,2771,2772],{"class":351},"  # wrong key name\n",[168,2774,2775,2777,2779,2781,2783,2785],{"class":347,"line":394},[168,2776,553],{"class":432},[168,2778,437],{"class":436},[168,2780,949],{"class":948},[168,2782,2201],{"class":952},[168,2784,949],{"class":948},[168,2786,1139],{"class":428},[168,2788,2789],{"class":347,"line":407},[168,2790,2243],{"class":428},[112,2792,2793,2794,2797],{},"You get ",[122,2795,2796],{},"TypeError: calc() got an unexpected keyword argument 'expr'",", and the loop dies. Same class of failure as Break 1, but one level deeper: a model that misnamed a parameter never gets the chance to see what it did wrong, because the exception unwinds the loop before the next turn can happen.",[112,2799,2800,2802],{},[137,2801,2695],{}," Schema validation before dispatch. The loop should notice the args don't match, return a validation error to the model, and give it a chance to correct. Chapter 6 builds this.",[2589,2804,2806],{"id":2805},"break-3-the-tool-itself-raises","Break 3: The tool itself raises",[337,2808,2810],{"className":339,"code":2809,"language":341,"meta":342,"style":342},"def calc(expression: str) -> str:\n    return str(eval(expression, {\"__builtins__\": {}}, {}))\n\n# And the mock asks for:\nProviderResponse(\n    kind=\"tool_call\",\n    tool_name=\"calc\",\n    tool_args={\"expression\": \"1 \u002F 0\"},  # guaranteed ZeroDivisionError\n    tool_call_id=\"call-1\",\n),\n",[122,2811,2812,2834,2864,2868,2873,2879,2893,2907,2935,2949],{"__ignoreMap":342},[168,2813,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832],{"class":347,"line":348},[168,2815,1121],{"class":450},[168,2817,2049],{"class":424},[168,2819,429],{"class":428},[168,2821,2054],{"class":612},[168,2823,481],{"class":428},[168,2825,485],{"class":484},[168,2827,1202],{"class":428},[168,2829,644],{"class":428},[168,2831,485],{"class":484},[168,2833,458],{"class":428},[168,2835,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862],{"class":347,"line":355},[168,2837,2076],{"class":358},[168,2839,485],{"class":484},[168,2841,429],{"class":428},[168,2843,2083],{"class":796},[168,2845,429],{"class":428},[168,2847,2054],{"class":843},[168,2849,609],{"class":428},[168,2851,2092],{"class":428},[168,2853,949],{"class":948},[168,2855,2097],{"class":952},[168,2857,949],{"class":948},[168,2859,481],{"class":428},[168,2861,2104],{"class":428},[168,2863,1583],{"class":428},[168,2865,2866],{"class":347,"line":373},[168,2867,377],{"emptyLinePlaceholder":376},[168,2869,2870],{"class":347,"line":380},[168,2871,2872],{"class":351},"# And the mock asks for:\n",[168,2874,2875,2877],{"class":347,"line":394},[168,2876,692],{"class":843},[168,2878,1127],{"class":428},[168,2880,2881,2883,2885,2887,2889,2891],{"class":347,"line":407},[168,2882,478],{"class":432},[168,2884,437],{"class":436},[168,2886,949],{"class":948},[168,2888,1429],{"class":952},[168,2890,949],{"class":948},[168,2892,1139],{"class":428},[168,2894,2895,2897,2899,2901,2903,2905],{"class":347,"line":412},[168,2896,516],{"class":432},[168,2898,437],{"class":436},[168,2900,949],{"class":948},[168,2902,2159],{"class":952},[168,2904,949],{"class":948},[168,2906,1139],{"class":428},[168,2908,2909,2911,2913,2915,2917,2919,2921,2923,2925,2928,2930,2932],{"class":347,"line":417},[168,2910,534],{"class":432},[168,2912,437],{"class":436},[168,2914,1511],{"class":428},[168,2916,949],{"class":948},[168,2918,2054],{"class":952},[168,2920,949],{"class":948},[168,2922,481],{"class":428},[168,2924,1241],{"class":948},[168,2926,2927],{"class":952},"1 \u002F 0",[168,2929,949],{"class":948},[168,2931,2769],{"class":428},[168,2933,2934],{"class":351},"  # guaranteed ZeroDivisionError\n",[168,2936,2937,2939,2941,2943,2945,2947],{"class":347,"line":447},[168,2938,553],{"class":432},[168,2940,437],{"class":436},[168,2942,949],{"class":948},[168,2944,2201],{"class":952},[168,2946,949],{"class":948},[168,2948,1139],{"class":428},[168,2950,2951],{"class":347,"line":461},[168,2952,2243],{"class":428},[112,2954,2955,2958,2959,2962],{},[122,2956,2957],{},"ZeroDivisionError",", and again the loop unwinds. That alone would be fine to handle locally, but consider the subtler versions that show up in any non-trivial system: a network tool that times out, a file read that hits a permission error, a shell command that returns exit code 1, a remote API that returns a 503. All of these are ",[115,2960,2961],{},"expected"," failures in a harness that does anything interesting, and the loop currently has no place to put them other than \"crash the session.\"",[112,2964,2965,2967],{},[137,2966,2695],{}," A tool-dispatch wrapper that converts tool exceptions into structured tool-result errors, visible to the model. Chapter 6 again.",[2589,2969,2971],{"id":2970},"break-4-the-model-never-stops","Break 4: The model never stops",[337,2973,2975],{"className":339,"code":2974,"language":341,"meta":342,"style":342},"mock = MockProvider([\n    ProviderResponse(kind=\"tool_call\", tool_name=\"calc\",\n                     tool_args={\"expression\": \"1\"}, tool_call_id=f\"call-{i}\")\n    for i in range(100)\n])\n",[122,2976,2977,2987,3018,3065,3083],{"__ignoreMap":342},[168,2978,2979,2981,2983,2985],{"class":347,"line":348},[168,2980,2029],{"class":369},[168,2982,437],{"class":436},[168,2984,758],{"class":843},[168,2986,2125],{"class":428},[168,2988,2989,2991,2993,2995,2997,2999,3001,3003,3005,3008,3010,3012,3014,3016],{"class":347,"line":355},[168,2990,2130],{"class":843},[168,2992,429],{"class":428},[168,2994,1328],{"class":432},[168,2996,437],{"class":436},[168,2998,949],{"class":948},[168,3000,1429],{"class":952},[168,3002,949],{"class":948},[168,3004,609],{"class":428},[168,3006,3007],{"class":432}," tool_name",[168,3009,437],{"class":436},[168,3011,949],{"class":948},[168,3013,2159],{"class":952},[168,3015,949],{"class":948},[168,3017,1139],{"class":428},[168,3019,3020,3023,3025,3027,3029,3031,3033,3035,3037,3040,3042,3044,3047,3049,3051,3054,3056,3059,3061,3063],{"class":347,"line":373},[168,3021,3022],{"class":432},"                     tool_args",[168,3024,437],{"class":436},[168,3026,1511],{"class":428},[168,3028,949],{"class":948},[168,3030,2054],{"class":952},[168,3032,949],{"class":948},[168,3034,481],{"class":428},[168,3036,1241],{"class":948},[168,3038,3039],{"class":952},"1",[168,3041,949],{"class":948},[168,3043,2769],{"class":428},[168,3045,3046],{"class":432}," tool_call_id",[168,3048,437],{"class":436},[168,3050,1505],{"class":450},[168,3052,3053],{"class":952},"\"call-",[168,3055,1511],{"class":860},[168,3057,3058],{"class":843},"i",[168,3060,1524],{"class":860},[168,3062,949],{"class":952},[168,3064,444],{"class":428},[168,3066,3067,3069,3072,3074,3076,3078,3081],{"class":347,"line":380},[168,3068,1272],{"class":358},[168,3070,3071],{"class":843}," i ",[168,3073,1278],{"class":358},[168,3075,1281],{"class":796},[168,3077,429],{"class":428},[168,3079,3080],{"class":860},"100",[168,3082,444],{"class":428},[168,3084,3085],{"class":347,"line":394},[168,3086,2248],{"class":428},[112,3088,3089,3090,3092,3093,3096],{},"The model keeps calling the tool, iteration after iteration, and ",[122,3091,1956],{}," catches it — but we raise a ",[122,3094,3095],{},"RuntimeError"," that discards the partial transcript along with everything useful for debugging. The number itself is arbitrary, too: twenty is too low for a real task and too high for a true runaway. We need a principled bound, one rooted in cost rather than iteration count, and we need to preserve the transcript when the bound fires so a human can figure out what went wrong.",[112,3098,3099,3101,3102,3105,3106,3109,3110,3113],{},[137,3100,2695],{}," Two things. A ",[137,3103,3104],{},"token budget"," that triggers termination based on cost, not iteration count (Chapter 20). An ",[137,3107,3108],{},"observability layer"," that preserves the transcript for debugging when we do terminate (Chapter 18). And — looking ahead — a ",[137,3111,3112],{},"dedup"," check that notices the model is calling the same tool with the same args over and over, and halts before the budget fires (Chapter 6).",[2589,3115,3117],{"id":3116},"break-5-the-tool-returns-a-novel","Break 5: The tool returns a novel",[337,3119,3121],{"className":339,"code":3120,"language":341,"meta":342,"style":342},"def calc(expression: str) -> str:\n    # imagine a \"read_file\" tool that reads 60,000 tokens of JSON\n    return \"X\" * 200_000  # 200KB of X\n",[122,3122,3123,3145,3150],{"__ignoreMap":342},[168,3124,3125,3127,3129,3131,3133,3135,3137,3139,3141,3143],{"class":347,"line":348},[168,3126,1121],{"class":450},[168,3128,2049],{"class":424},[168,3130,429],{"class":428},[168,3132,2054],{"class":612},[168,3134,481],{"class":428},[168,3136,485],{"class":484},[168,3138,1202],{"class":428},[168,3140,644],{"class":428},[168,3142,485],{"class":484},[168,3144,458],{"class":428},[168,3146,3147],{"class":347,"line":355},[168,3148,3149],{"class":351},"    # imagine a \"read_file\" tool that reads 60,000 tokens of JSON\n",[168,3151,3152,3154,3156,3159,3161,3164,3167],{"class":347,"line":373},[168,3153,2076],{"class":358},[168,3155,1241],{"class":948},[168,3157,3158],{"class":952},"X",[168,3160,949],{"class":948},[168,3162,3163],{"class":436}," *",[168,3165,3166],{"class":860}," 200_000",[168,3168,3169],{"class":351},"  # 200KB of X\n",[112,3171,3172],{},"The loop still works — technically — but the transcript now has 200KB of X in it, and the next turn sends that whole transcript back to the provider. By turn five we're well past the context window, the provider returns an error, and the session crashes in a way that looks mysterious only because nobody was tracking the cost of what the tool was returning.",[112,3174,3175],{},"This is the central problem that shapes the rest of the book. The loop has no awareness of how much context it's using, no strategy for summarizing or truncating tool outputs, no concept of a scratchpad for state that shouldn't be in context at all — and no way to tell the difference between 200 bytes of useful signal and 200KB of noise that happens to look similar at the protocol level.",[112,3177,3178,3180],{},[137,3179,2695],{}," Context accounting (Chapter 7), compaction (Chapter 8), external state (Chapter 9), retrieval (Chapter 10), and deliberate tool design that avoids producing these blobs in the first place (Chapter 11).",[258,3182],{},[261,3184,3186],{"id":3185},"_26-the-itinerary","2.6 The Itinerary",[112,3188,3189],{},"Those five breaks are the book. Look at them laid out:",[3191,3192,3193,3209],"table",{},[3194,3195,3196],"thead",{},[3197,3198,3199,3203,3206],"tr",{},[3200,3201,3202],"th",{},"Break",[3200,3204,3205],{},"What's Missing",[3200,3207,3208],{},"Chapter",[3210,3211,3212,3224,3235,3245,3256],"tbody",{},[3197,3213,3214,3218,3221],{},[3215,3216,3217],"td",{},"1. Tool doesn't exist",[3215,3219,3220],{},"Dispatch layer with structured errors",[3215,3222,3223],{},"4, 6",[3197,3225,3226,3229,3232],{},[3215,3227,3228],{},"2. Args don't match schema",[3215,3230,3231],{},"Pre-dispatch validation",[3215,3233,3234],{},"6",[3197,3236,3237,3240,3243],{},[3215,3238,3239],{},"3. Tool raises",[3215,3241,3242],{},"Wrapped tool execution",[3215,3244,3234],{},[3197,3246,3247,3250,3253],{},[3215,3248,3249],{},"4. Model never stops",[3215,3251,3252],{},"Budget + dedup + observability",[3215,3254,3255],{},"6, 18, 20",[3197,3257,3258,3261,3264],{},[3215,3259,3260],{},"5. Tool returns too much",[3215,3262,3263],{},"Context engineering",[3215,3265,3266],{},"7–11",[112,3268,3269],{},"Every chapter from here to Chapter 11 is motivated by one of these five breaks. The chapters after that — orchestration, observability, evals, cost control, resumability — extend the harness into production territory once the core is solid.",[112,3271,3272],{},"If at any point the design feels over-engineered, come back to this table. Every piece of machinery is there because we watched the absence of it crash a loop.",[258,3274],{},[261,3276,3278],{"id":3277},"_27-a-quick-look-at-how-real-harnesses-handle-this","2.7 A Quick Look at How Real Harnesses Handle This",[112,3280,3281,3282,3284],{},"Before we close the chapter, a sanity check: do real harnesses actually look like this — a ",[122,3283,124],{}," loop with a dispatch inside? The answer, across the ones worth looking at, is yes.",[112,3286,3287,3290],{},[137,3288,3289],{},"Claude Code",", per Anthropic's public documentation, has an agent loop described in roughly 88 lines internally. The core is exactly what we wrote: the model produces a response, if the response contains tool calls the harness dispatches them and appends results, then it loops; otherwise, it returns. What Claude Code adds on top is production-grade error handling, a permission gate in front of every side-effecting tool, and the compaction and checkpointing we'll build in later chapters.",[112,3292,3293,3296,3297,3300,3301,3304],{},[137,3294,3295],{},"smolagents",", Hugging Face's open-source agent library, fits in about a thousand lines total. Its ",[122,3298,3299],{},"MultiStepAgent.run()"," method is a ",[122,3302,3303],{},"for _ in range(self.max_steps)"," loop with the same three decisions we just wrote, plus a richer error taxonomy and an observation-formatting layer.",[112,3306,3307,3310,3311,3313],{},[137,3308,3309],{},"mini-swe-agent",", the minimal variant of SWE-agent, is about a hundred lines and uses the same structure with a single ",[122,3312,2510],{}," tool instead of a registry — a useful reference for how thin a working harness can get when the problem shape is narrow enough to assume a single tool.",[112,3315,3316,3319,3320,3322],{},[137,3317,3318],{},"LangGraph"," looks different on the surface — it's a compiled graph, not a ",[122,3321,124],{}," — but the graph compiles down to a Pregel-style execution model in which a ReAct-style agent is a cycle between an LLM node and a tool node. Same three decisions as our naive loop, different packaging, and the \"ReAct\" naming here refers directly to Yao et al.'s 2022 paper that introduced the reason-act-observe paradigm the graph implements.",[112,3324,3325],{},"The loop is not a simplification for pedagogy; it is the actual shape of the thing, and Wang et al.'s 2024 \"Survey on LLM-based Autonomous Agents\" — which analyzed more than a hundred LLM-agent implementations across research and production — found the same core structure in the overwhelming majority of them. Everything else is engineering around it.",[258,3327],{},[261,3329,3331],{"id":3330},"_28-try-it-yourself","2.8 Try It Yourself",[273,3333,3334,3340,3346],{},[276,3335,3336,3339],{},[137,3337,3338],{},"Reproduce the five breaks."," Take the working calculator example, apply each of the five breakages in turn, and observe the specific failure mode each one produces. Note in your own words what a correct handling would look like — the articulation, before you know the answer, is the learning.",[276,3341,3342,3345],{},[137,3343,3344],{},"Add a sixth break of your own."," Find one more way to break this loop that isn't in the table above. Some candidates worth trying: the provider itself raises (network error, rate-limit response), the mock returns a text response containing JSON tool-call syntax the loop doesn't parse, or the tool returns something that isn't a string. Confirm the failure, then decide whether it belongs to one of the five classes already named or constitutes a new category.",[276,3347,3348,3351,3352,3355,3356,3359],{},[137,3349,3350],{},"Write the smallest possible type-safe version."," Re-implement ",[122,3353,3354],{},"run()"," with ",[122,3357,3358],{},"TypedDict"," message shapes instead of raw dicts. Does the type checker catch any of the five breaks at authoring time? Which ones does it not catch, and why? (The answer to the second question is where most of Chapter 3's motivation comes from.)",[258,3361],{},[3363,3364,3365,3368,3371,3377],"what-you-understand",{},[112,3366,3367],{},"You have written an agent loop from nothing — forty lines, a mock provider, a single tool — and then you ran it and it worked. You then broke it five times on purpose and saw exactly which pieces of engineering are missing, which is more knowledge than most production agents carry about their own failure modes.",[112,3369,3370],{},"The loop itself is the same reason-act-observe pattern Yao et al. formalized as ReAct in 2022 and that Wang et al.'s 2024 survey of LLM agents found in the overwhelming majority of real implementations. Different harnesses layer more or less engineering on top, but the core shape is convergent: ask the model, classify the response, dispatch or return, bound the loop.",[112,3372,3373,3374,3376],{},"You also have a seam. The ",[122,3375,334],{}," protocol is the first and most important architectural decision in the harness, and it's already in place; every subsequent chapter can assume it is there and never need to care which vendor actually answered.",[112,3378,3379],{},"What's still missing is everything the five breaks revealed. Chapter 3 starts with the most foundational of them — the transcript itself. Raw dicts are not enough. We introduce typed messages, typed turns, and typed transcripts, and we build the first real provider adapters against Anthropic and OpenAI so you can retire the mock whenever you're ready.",[3381,3382,3383],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s2W-s, html code.shiki .s2W-s{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sithA, html code.shiki .sithA{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smCYv, html code.shiki .smCYv{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":342,"searchDepth":355,"depth":355,"links":3385},[3386,3387,3388,3389,3390,3397,3398,3399],{"id":263,"depth":355,"text":264},{"id":316,"depth":355,"text":317},{"id":1026,"depth":355,"text":1027},{"id":1965,"depth":355,"text":1966},{"id":2583,"depth":355,"text":2584,"children":3391},[3392,3393,3394,3395,3396],{"id":2591,"depth":373,"text":2592},{"id":2703,"depth":373,"text":2704},{"id":2805,"depth":373,"text":2806},{"id":2970,"depth":373,"text":2971},{"id":3116,"depth":373,"text":3117},{"id":3185,"depth":355,"text":3186},{"id":3277,"depth":355,"text":3278},{"id":3330,"depth":355,"text":3331},"md",{},null,{"title":18,"description":117},"IdOdyTO1aAzTq_5s11BiGN04Vk7yZqZOTeDPLdZ5Cyw",[3406,3408],{"title":14,"path":15,"stem":16,"description":3407,"children":-1},"A few months ago a friend asked me to look at a system he was building. He said: \"My agent keeps forgetting what it's doing.\"",{"title":22,"path":23,"stem":24,"description":3409,"children":-1},"Previously: we built a forty-line loop against a mock provider and watched it break five ways. Break 5 — tool output overwhelming the transcript — hinted that the transcript was doing too much work as a pile of dicts. It's time to give it some structure, and at the same time plug in real providers.",1776848984222]