[{"data":1,"prerenderedAt":4158},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Fsafe-tool-execution":102,"surround-\u002Fchapters\u002Fsafe-tool-execution":4153},[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":34,"body":104,"description":117,"extension":4148,"meta":4149,"navigation":4150,"path":35,"seo":4151,"stem":36,"__hash__":4152},"content\u002F2.chapters\u002F06.safe-tool-execution.md",{"type":105,"value":106,"toc":4136},"minimark",[107,111,118,138,141,219,222,227,230,256,270,287,290,322,324,328,810,813,826,840,842,846,849,2710,2713,2738,2751,2757,2759,2763,2782,2785,2791,2797,2803,2805,2809,2822,2825,2834,2836,2840,2843,3595,3598,3620,3623,3625,3629,3636,3998,4001,4003,4007,4010,4019,4025,4031,4034,4036,4040,4084,4088,4121,4123,4132],[108,109,34],"h1",{"id":110},"chapter-6-safe-tool-execution",[112,113,114],"p",{},[115,116,117],"em",{},"Previously: streaming, interruption, retries. The loop survives network failures and closes cleanly on Ctrl-C. But a misnamed argument still fails inside the tool function, and the registry still can't tell when the model is spinning.",[112,119,120,121,125,126,129,130,133,134,137],{},"Two of the five breaks from Chapter 2 are still open. Break 2: the model passes a wrong argument shape (",[122,123,124],"code",{},"{\"expr\": \"...\"}"," instead of ",[122,127,128],{},"{\"expression\": \"...\"}","), and we discover it only when Python raises ",[122,131,132],{},"TypeError"," inside the function body. Break 4: the model keeps calling the same tool with the same arguments and never converges — a tool-call loop that our ",[122,135,136],{},"MAX_ITERATIONS"," catches too late, after twenty wasted calls.",[112,139,140],{},"Both are fixable at the registry level, cheaply, with patterns every serious harness implements. This chapter closes them. We also tighten the error messages we return to the model, because the error message is how it learns to do better on the next turn.",[142,143,147,194,211],"figure",{"className":144},[145,146],"not-prose","my-8",[148,149,156,167,172,176,179,183,186],"div",{"className":150},[151,152,153,154,155],"flex","items-center","justify-center","gap-2","flex-wrap",[148,157,166],{"className":158},[159,160,161,162,163,164,165],"border","border-default","rounded-full","py-2","px-4","text-sm","text-default","name exists?",[148,168,171],{"className":169},[170],"text-muted","→",[148,173,175],{"className":174},[159,160,161,162,163,164,165],"args match schema?",[148,177,171],{"className":178},[170],[148,180,182],{"className":181},[159,160,161,162,163,164,165],"loop detector?",[148,184,171],{"className":185},[170],[148,187,193],{"className":188},[189,190,161,162,163,164,191,192],"border-2","border-primary","font-semibold","text-primary","execute",[148,195,198,204],{"className":196},[151,152,153,197],"mt-4",[148,199,203],{"className":200},[201,170,202],"text-xs","mr-2","any \"no\" →",[148,205,210],{"className":206},[159,207,208,209,162,163,164],"border-red-500","text-red-500","rounded-lg","structured error → back to model",[212,213,218],"figcaption",{"className":214},[201,170,215,216,217],"mt-3","text-center","italic","Four gates before a tool runs. Every \"no\" short-circuits to a structured error the model can learn from on its next turn.",[220,221],"hr",{},[223,224,226],"h2",{"id":225},"_61-why-validate-before-dispatch","6.1 Why Validate Before Dispatch",[112,228,229],{},"There are two reasons to validate arguments before calling the tool function, and the first of them is backed by specific research on how LLM agents recover from failure.",[112,231,232,236,237,240,241,244,245,247,248,251,252,255],{},[233,234,235],"strong",{},"Better error messages for the model."," When ",[122,238,239],{},"calc"," raises ",[122,242,243],{},"TypeError: calc() got an unexpected keyword argument 'expr'",", the registry currently returns that string to the model. It's not wrong, but it's not great — the model has to reverse-engineer which argument was expected from a Python-flavored error message aimed at a human debugger. A schema-aware validator can say \"tool ",[122,246,239],{}," requires ",[122,249,250],{},"expression"," (string); got ",[122,253,254],{},"expr","\" and the model's next attempt is usually right on the first try. Shinn et al.'s 2023 \"Reflexion: Language Agents with Verbal Reinforcement Learning\" (NeurIPS 2023) demonstrated this effect empirically across several agent benchmarks: agents that received structured feedback about their failures — what specifically was wrong, in the model's own vocabulary — recovered substantially faster than agents that received only raw error traces, and the effect compounded across multi-step tasks. In production, that difference is measurable on any real system: one saved turn per misnamed argument, multiplied by every turn you run.",[112,257,258,261,262,265,266,269],{},[233,259,260],{},"Safety."," A tool like ",[122,263,264],{},"write_file"," that receives ",[122,267,268],{},"{\"path\": \"\u002Fetc\u002Fpasswd\", \"content\": \"...\"}"," should not have reached the function body. Validating the argument shape in the registry gives us one clean place to enforce invariants, before the tool can do any damage. This chapter's validation is structural only — \"is this the shape I expect?\" Chapter 14 adds semantic checks — \"is this path allowed?\" — but it layers on the same machinery.",[112,271,272,273,276,277,279,280,282,283,286],{},"Production harnesses overwhelmingly use Pydantic or ",[122,274,275],{},"jsonschema"," for this. Pydantic is more ergonomic for Python-native types; ",[122,278,275],{}," is the reference implementation for the JSON Schema spec. We'll use ",[122,281,275],{}," because our tool schemas ",[115,284,285],{},"are"," JSON Schemas; the validation is exactly what the library was designed for.",[112,288,289],{},"Add the dependency:",[291,292,297],"pre",{"className":293,"code":294,"language":295,"meta":296,"style":296},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","uv add 'jsonschema>=4.22'\n","bash","",[122,298,299],{"__ignoreMap":296},[300,301,304,308,312,316,319],"span",{"class":302,"line":303},"line",1,[300,305,307],{"class":306},"sbgvK","uv",[300,309,311],{"class":310},"s_sjI"," add",[300,313,315],{"class":314},"sjJ54"," '",[300,317,318],{"class":310},"jsonschema>=4.22",[300,320,321],{"class":314},"'\n",[220,323],{},[223,325,327],{"id":326},"_62-the-validator","6.2 The Validator",[291,329,333],{"className":330,"code":331,"language":332,"meta":296,"style":296},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Ftools\u002Fvalidation.py\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\n\nimport jsonschema\nfrom jsonschema import Draft202012Validator\n\n\n@dataclass(frozen=True)\nclass ValidationError:\n    message: str\n    path: str  # JSON-pointer-ish; e.g. \"args.expression\"\n\n    def __str__(self) -> str:\n        return f\"{self.path}: {self.message}\"\n\n\ndef validate(args: dict, schema: dict) -> list[ValidationError]:\n    \"\"\"Return a list of validation errors. Empty list == valid.\"\"\"\n    validator = Draft202012Validator(schema)\n    errors: list[ValidationError] = []\n    for err in validator.iter_errors(args):\n        path = \"args\" + \"\".join(f\".{p}\" for p in err.absolute_path)\n        errors.append(ValidationError(message=err.message, path=path))\n    return errors\n","python",[122,334,335,341,359,366,380,385,393,406,411,416,446,459,472,486,491,517,562,567,572,618,632,651,674,701,761,801],{"__ignoreMap":296},[300,336,337],{"class":302,"line":303},[300,338,340],{"class":339},"sutJx","# src\u002Fharness\u002Ftools\u002Fvalidation.py\n",[300,342,344,348,352,355],{"class":302,"line":343},2,[300,345,347],{"class":346},"sVHd0","from",[300,349,351],{"class":350},"s_hVV"," __future__",[300,353,354],{"class":346}," import",[300,356,358],{"class":357},"su5hD"," annotations\n",[300,360,362],{"class":302,"line":361},3,[300,363,365],{"emptyLinePlaceholder":364},true,"\n",[300,367,369,371,374,377],{"class":302,"line":368},4,[300,370,347],{"class":346},[300,372,373],{"class":357}," dataclasses ",[300,375,376],{"class":346},"import",[300,378,379],{"class":357}," dataclass\n",[300,381,383],{"class":302,"line":382},5,[300,384,365],{"emptyLinePlaceholder":364},[300,386,388,390],{"class":302,"line":387},6,[300,389,376],{"class":346},[300,391,392],{"class":357}," jsonschema\n",[300,394,396,398,401,403],{"class":302,"line":395},7,[300,397,347],{"class":346},[300,399,400],{"class":357}," jsonschema ",[300,402,376],{"class":346},[300,404,405],{"class":357}," Draft202012Validator\n",[300,407,409],{"class":302,"line":408},8,[300,410,365],{"emptyLinePlaceholder":364},[300,412,414],{"class":302,"line":413},9,[300,415,365],{"emptyLinePlaceholder":364},[300,417,419,423,427,431,435,439,443],{"class":302,"line":418},10,[300,420,422],{"class":421},"stp6e","@",[300,424,426],{"class":425},"sGLFI","dataclass",[300,428,430],{"class":429},"sP7_E","(",[300,432,434],{"class":433},"s99_P","frozen",[300,436,438],{"class":437},"smGrS","=",[300,440,442],{"class":441},"s39Yj","True",[300,444,445],{"class":429},")\n",[300,447,449,453,456],{"class":302,"line":448},11,[300,450,452],{"class":451},"sbsja","class",[300,454,455],{"class":306}," ValidationError",[300,457,458],{"class":429},":\n",[300,460,462,465,468],{"class":302,"line":461},12,[300,463,464],{"class":357},"    message",[300,466,467],{"class":429},":",[300,469,471],{"class":470},"sZMiF"," str\n",[300,473,475,478,480,483],{"class":302,"line":474},13,[300,476,477],{"class":357},"    path",[300,479,467],{"class":429},[300,481,482],{"class":470}," str",[300,484,485],{"class":339},"  # JSON-pointer-ish; e.g. \"args.expression\"\n",[300,487,489],{"class":302,"line":488},14,[300,490,365],{"emptyLinePlaceholder":364},[300,492,494,497,501,503,507,510,513,515],{"class":302,"line":493},15,[300,495,496],{"class":451},"    def",[300,498,500],{"class":499},"sptTA"," __str__",[300,502,430],{"class":429},[300,504,506],{"class":505},"smCYv","self",[300,508,509],{"class":429},")",[300,511,512],{"class":429}," ->",[300,514,482],{"class":470},[300,516,458],{"class":429},[300,518,520,523,526,529,533,535,538,542,545,548,550,552,554,557,559],{"class":302,"line":519},16,[300,521,522],{"class":346},"        return",[300,524,525],{"class":451}," f",[300,527,528],{"class":310},"\"",[300,530,532],{"class":531},"srdBf","{",[300,534,506],{"class":350},[300,536,537],{"class":429},".",[300,539,541],{"class":540},"skxfh","path",[300,543,544],{"class":531},"}",[300,546,547],{"class":310},": ",[300,549,532],{"class":531},[300,551,506],{"class":350},[300,553,537],{"class":429},[300,555,556],{"class":540},"message",[300,558,544],{"class":531},[300,560,561],{"class":310},"\"\n",[300,563,565],{"class":302,"line":564},17,[300,566,365],{"emptyLinePlaceholder":364},[300,568,570],{"class":302,"line":569},18,[300,571,365],{"emptyLinePlaceholder":364},[300,573,575,578,581,583,587,589,592,595,598,600,602,604,606,609,612,615],{"class":302,"line":574},19,[300,576,577],{"class":451},"def",[300,579,580],{"class":425}," validate",[300,582,430],{"class":429},[300,584,586],{"class":585},"sFwrP","args",[300,588,467],{"class":429},[300,590,591],{"class":470}," dict",[300,593,594],{"class":429},",",[300,596,597],{"class":585}," schema",[300,599,467],{"class":429},[300,601,591],{"class":470},[300,603,509],{"class":429},[300,605,512],{"class":429},[300,607,608],{"class":357}," list",[300,610,611],{"class":429},"[",[300,613,614],{"class":357},"ValidationError",[300,616,617],{"class":429},"]:\n",[300,619,621,625,629],{"class":302,"line":620},20,[300,622,624],{"class":623},"s2W-s","    \"\"\"",[300,626,628],{"class":627},"sithA","Return a list of validation errors. Empty list == valid.",[300,630,631],{"class":623},"\"\"\"\n",[300,633,635,638,640,644,646,649],{"class":302,"line":634},21,[300,636,637],{"class":357},"    validator ",[300,639,438],{"class":437},[300,641,643],{"class":642},"slqww"," Draft202012Validator",[300,645,430],{"class":429},[300,647,648],{"class":642},"schema",[300,650,445],{"class":429},[300,652,654,657,659,661,663,665,668,671],{"class":302,"line":653},22,[300,655,656],{"class":357},"    errors",[300,658,467],{"class":429},[300,660,608],{"class":357},[300,662,611],{"class":429},[300,664,614],{"class":357},[300,666,667],{"class":429},"]",[300,669,670],{"class":437}," =",[300,672,673],{"class":429}," []\n",[300,675,677,680,683,686,689,691,694,696,698],{"class":302,"line":676},23,[300,678,679],{"class":346},"    for",[300,681,682],{"class":357}," err ",[300,684,685],{"class":346},"in",[300,687,688],{"class":357}," validator",[300,690,537],{"class":429},[300,692,693],{"class":642},"iter_errors",[300,695,430],{"class":429},[300,697,586],{"class":642},[300,699,700],{"class":429},"):\n",[300,702,704,707,709,712,714,716,719,722,724,727,729,732,735,737,739,741,743,746,749,751,754,756,759],{"class":302,"line":703},24,[300,705,706],{"class":357},"        path ",[300,708,438],{"class":437},[300,710,711],{"class":314}," \"",[300,713,586],{"class":310},[300,715,528],{"class":314},[300,717,718],{"class":437}," +",[300,720,721],{"class":314}," \"\"",[300,723,537],{"class":429},[300,725,726],{"class":642},"join",[300,728,430],{"class":429},[300,730,731],{"class":451},"f",[300,733,734],{"class":310},"\".",[300,736,532],{"class":531},[300,738,112],{"class":642},[300,740,544],{"class":531},[300,742,528],{"class":310},[300,744,745],{"class":346}," for",[300,747,748],{"class":642}," p ",[300,750,685],{"class":346},[300,752,753],{"class":642}," err",[300,755,537],{"class":429},[300,757,758],{"class":540},"absolute_path",[300,760,445],{"class":429},[300,762,764,767,769,772,774,776,778,780,782,785,787,789,791,794,796,798],{"class":302,"line":763},25,[300,765,766],{"class":357},"        errors",[300,768,537],{"class":429},[300,770,771],{"class":642},"append",[300,773,430],{"class":429},[300,775,614],{"class":642},[300,777,430],{"class":429},[300,779,556],{"class":433},[300,781,438],{"class":437},[300,783,784],{"class":642},"err",[300,786,537],{"class":429},[300,788,556],{"class":540},[300,790,594],{"class":429},[300,792,793],{"class":433}," path",[300,795,438],{"class":437},[300,797,541],{"class":642},[300,799,800],{"class":429},"))\n",[300,802,804,807],{"class":302,"line":803},26,[300,805,806],{"class":346},"    return",[300,808,809],{"class":357}," errors\n",[112,811,812],{},"Two design points.",[112,814,815,818,819,822,823,825],{},[233,816,817],{},"We return a list, not raise."," A single call can have multiple problems (wrong type ",[115,820,821],{},"and"," missing required argument ",[115,824,821],{}," extra unknown argument). The model learns faster from one error message listing all three than from three consecutive turns fixing them one at a time.",[112,827,828,831,832,835,836,839],{},[233,829,830],{},"The path is human-readable."," ",[122,833,834],{},"args.expression"," and ",[122,837,838],{},"args.items[0].name"," are the shapes we emit. The model reads these as fluently as humans do; \"at $.items.0.name\" is harder to parse.",[220,841],{},[223,843,845],{"id":844},"_63-threading-validation-through-dispatch","6.3 Threading Validation Through Dispatch",[112,847,848],{},"The registry gains a validation step. When validation fails, we return the errors to the model instead of running the tool.",[291,850,852],{"className":330,"code":851,"language":332,"meta":296,"style":296},"# src\u002Fharness\u002Ftools\u002Fregistry.py (updated)\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass, field\n\nfrom ..messages import ToolResult\nfrom .base import Tool\nfrom .validation import ValidationError, validate\n\n\nMAX_REPEAT_CALLS = 3  # same (tool, args) this many times → halt\n\n\n@dataclass\nclass ToolRegistry:\n    tools: dict[str, Tool] = field(default_factory=dict)\n    _call_history: list[tuple[str, str]] = field(default_factory=list, init=False)\n\n    def __init__(self, tools: list[Tool] | None = None) -> None:\n        self.tools = {}\n        self._call_history = []\n        for t in tools or []:\n            self.add(t)\n\n    def add(self, tool: Tool) -> None:\n        if tool.name in self.tools:\n            raise ValueError(f\"duplicate tool name: {tool.name}\")\n        self.tools[tool.name] = tool\n\n    def schemas(self) -> list[dict]:\n        return [t.schema_for_provider() for t in self.tools.values()]\n\n    def dispatch(self, name: str, args: dict, call_id: str) -> ToolResult:\n        if name not in self.tools:\n            return self._unknown_tool(name, call_id)\n\n        tool = self.tools[name]\n        errors = validate(args, tool.input_schema)\n        if errors:\n            return self._validation_failure(name, errors, call_id)\n\n        self._record(name, args)\n        loop_result = self._check_loop(name, args, call_id)\n        if loop_result is not None:\n            return loop_result\n\n        try:\n            content = tool.run(**args)\n        except Exception as e:\n            return ToolResult(\n                call_id=call_id,\n                content=f\"{name} raised {type(e).__name__}: {e}\",\n                is_error=True,\n            )\n        return ToolResult(call_id=call_id, content=content)\n\n    # --- helpers ---\n\n    def _unknown_tool(self, name: str, call_id: str) -> ToolResult:\n        # Try to suggest a close match. We drop difflib's default cutoff\n        # of 0.6 to 0.5 — the ratio for `calculator` vs `calc` is ~0.57,\n        # and prefix-heavy misspellings like that are exactly the case\n        # we want to catch. 0.5 still rejects unrelated names.\n        import difflib\n        close = difflib.get_close_matches(\n            name, list(self.tools.keys()), n=1, cutoff=0.5,\n        )\n        suggestion = f\" Did you mean {close[0]!r}?\" if close else \"\"\n        return ToolResult(\n            call_id=call_id,\n            content=(\n                f\"unknown tool: {name!r}.{suggestion} \"\n                f\"Available: {sorted(self.tools.keys())}\"\n            ),\n            is_error=True,\n        )\n\n    def _validation_failure(\n        self, name: str, errors: list[ValidationError], call_id: str\n    ) -> ToolResult:\n        summary = \"; \".join(str(e) for e in errors)\n        return ToolResult(\n            call_id=call_id,\n            content=f\"{name}: invalid arguments. {summary}\",\n            is_error=True,\n        )\n\n    def _record(self, name: str, args: dict) -> None:\n        import json\n        self._call_history.append((name, json.dumps(args, sort_keys=True)))\n        if len(self._call_history) > 100:\n            self._call_history = self._call_history[-100:]\n\n    def _check_loop(self, name: str, args: dict, call_id: str) -> ToolResult | None:\n        import json\n        key = (name, json.dumps(args, sort_keys=True))\n        repeats = sum(1 for k in self._call_history[-MAX_REPEAT_CALLS:] if k == key)\n        if repeats >= MAX_REPEAT_CALLS:\n            return ToolResult(\n                call_id=call_id,\n                content=(\n                    f\"tool-call loop detected: {name} called with identical \"\n                    f\"arguments {MAX_REPEAT_CALLS} times in a row. \"\n                    \"Try a different approach or different arguments, or \"\n                    \"stop and return your current best answer.\"\n                ),\n                is_error=True,\n            )\n        return None\n",[122,853,854,859,869,873,889,893,908,923,941,945,949,962,966,970,977,986,1024,1074,1078,1123,1138,1151,1170,1187,1191,1218,1242,1273,1297,1302,1326,1364,1369,1417,1438,1461,1466,1487,1512,1522,1548,1553,1573,1602,1620,1628,1633,1641,1665,1682,1692,1706,1757,1769,1775,1802,1807,1813,1818,1854,1860,1866,1872,1878,1887,1905,1951,1957,2002,2011,2023,2033,2062,2094,2100,2112,2117,2122,2132,2166,2178,2218,2227,2238,2269,2280,2285,2290,2326,2334,2378,2404,2432,2437,2487,2494,2529,2579,2595,2604,2615,2624,2642,2659,2670,2680,2686,2697,2702],{"__ignoreMap":296},[300,855,856],{"class":302,"line":303},[300,857,858],{"class":339},"# src\u002Fharness\u002Ftools\u002Fregistry.py (updated)\n",[300,860,861,863,865,867],{"class":302,"line":343},[300,862,347],{"class":346},[300,864,351],{"class":350},[300,866,354],{"class":346},[300,868,358],{"class":357},[300,870,871],{"class":302,"line":361},[300,872,365],{"emptyLinePlaceholder":364},[300,874,875,877,879,881,884,886],{"class":302,"line":368},[300,876,347],{"class":346},[300,878,373],{"class":357},[300,880,376],{"class":346},[300,882,883],{"class":357}," dataclass",[300,885,594],{"class":429},[300,887,888],{"class":357}," field\n",[300,890,891],{"class":302,"line":382},[300,892,365],{"emptyLinePlaceholder":364},[300,894,895,897,900,903,905],{"class":302,"line":387},[300,896,347],{"class":346},[300,898,899],{"class":429}," ..",[300,901,902],{"class":357},"messages ",[300,904,376],{"class":346},[300,906,907],{"class":357}," ToolResult\n",[300,909,910,912,915,918,920],{"class":302,"line":395},[300,911,347],{"class":346},[300,913,914],{"class":429}," .",[300,916,917],{"class":357},"base ",[300,919,376],{"class":346},[300,921,922],{"class":357}," Tool\n",[300,924,925,927,929,932,934,936,938],{"class":302,"line":408},[300,926,347],{"class":346},[300,928,914],{"class":429},[300,930,931],{"class":357},"validation ",[300,933,376],{"class":346},[300,935,455],{"class":357},[300,937,594],{"class":429},[300,939,940],{"class":357}," validate\n",[300,942,943],{"class":302,"line":413},[300,944,365],{"emptyLinePlaceholder":364},[300,946,947],{"class":302,"line":418},[300,948,365],{"emptyLinePlaceholder":364},[300,950,951,954,956,959],{"class":302,"line":448},[300,952,953],{"class":350},"MAX_REPEAT_CALLS",[300,955,670],{"class":437},[300,957,958],{"class":531}," 3",[300,960,961],{"class":339},"  # same (tool, args) this many times → halt\n",[300,963,964],{"class":302,"line":461},[300,965,365],{"emptyLinePlaceholder":364},[300,967,968],{"class":302,"line":474},[300,969,365],{"emptyLinePlaceholder":364},[300,971,972,974],{"class":302,"line":488},[300,973,422],{"class":421},[300,975,976],{"class":425},"dataclass\n",[300,978,979,981,984],{"class":302,"line":493},[300,980,452],{"class":451},[300,982,983],{"class":306}," ToolRegistry",[300,985,458],{"class":429},[300,987,988,991,993,995,997,1000,1002,1005,1007,1009,1012,1014,1017,1019,1022],{"class":302,"line":519},[300,989,990],{"class":357},"    tools",[300,992,467],{"class":429},[300,994,591],{"class":357},[300,996,611],{"class":429},[300,998,999],{"class":470},"str",[300,1001,594],{"class":429},[300,1003,1004],{"class":357}," Tool",[300,1006,667],{"class":429},[300,1008,670],{"class":437},[300,1010,1011],{"class":642}," field",[300,1013,430],{"class":429},[300,1015,1016],{"class":433},"default_factory",[300,1018,438],{"class":437},[300,1020,1021],{"class":470},"dict",[300,1023,445],{"class":429},[300,1025,1026,1029,1031,1033,1035,1038,1040,1042,1044,1046,1049,1051,1053,1055,1057,1059,1062,1064,1067,1069,1072],{"class":302,"line":564},[300,1027,1028],{"class":357},"    _call_history",[300,1030,467],{"class":429},[300,1032,608],{"class":357},[300,1034,611],{"class":429},[300,1036,1037],{"class":357},"tuple",[300,1039,611],{"class":429},[300,1041,999],{"class":470},[300,1043,594],{"class":429},[300,1045,482],{"class":470},[300,1047,1048],{"class":429},"]]",[300,1050,670],{"class":437},[300,1052,1011],{"class":642},[300,1054,430],{"class":429},[300,1056,1016],{"class":433},[300,1058,438],{"class":437},[300,1060,1061],{"class":470},"list",[300,1063,594],{"class":429},[300,1065,1066],{"class":433}," init",[300,1068,438],{"class":437},[300,1070,1071],{"class":441},"False",[300,1073,445],{"class":429},[300,1075,1076],{"class":302,"line":569},[300,1077,365],{"emptyLinePlaceholder":364},[300,1079,1080,1082,1085,1087,1089,1091,1094,1096,1098,1100,1103,1105,1108,1111,1113,1115,1117,1119,1121],{"class":302,"line":574},[300,1081,496],{"class":451},[300,1083,1084],{"class":499}," __init__",[300,1086,430],{"class":429},[300,1088,506],{"class":505},[300,1090,594],{"class":429},[300,1092,1093],{"class":585}," tools",[300,1095,467],{"class":429},[300,1097,608],{"class":357},[300,1099,611],{"class":429},[300,1101,1102],{"class":357},"Tool",[300,1104,667],{"class":429},[300,1106,1107],{"class":437}," |",[300,1109,1110],{"class":441}," None",[300,1112,670],{"class":437},[300,1114,1110],{"class":441},[300,1116,509],{"class":429},[300,1118,512],{"class":429},[300,1120,1110],{"class":441},[300,1122,458],{"class":429},[300,1124,1125,1128,1130,1133,1135],{"class":302,"line":620},[300,1126,1127],{"class":350},"        self",[300,1129,537],{"class":429},[300,1131,1132],{"class":540},"tools",[300,1134,670],{"class":437},[300,1136,1137],{"class":429}," {}\n",[300,1139,1140,1142,1144,1147,1149],{"class":302,"line":634},[300,1141,1127],{"class":350},[300,1143,537],{"class":429},[300,1145,1146],{"class":540},"_call_history",[300,1148,670],{"class":437},[300,1150,673],{"class":429},[300,1152,1153,1156,1159,1161,1164,1167],{"class":302,"line":653},[300,1154,1155],{"class":346},"        for",[300,1157,1158],{"class":357}," t ",[300,1160,685],{"class":346},[300,1162,1163],{"class":357}," tools ",[300,1165,1166],{"class":437},"or",[300,1168,1169],{"class":429}," []:\n",[300,1171,1172,1175,1177,1180,1182,1185],{"class":302,"line":676},[300,1173,1174],{"class":350},"            self",[300,1176,537],{"class":429},[300,1178,1179],{"class":642},"add",[300,1181,430],{"class":429},[300,1183,1184],{"class":642},"t",[300,1186,445],{"class":429},[300,1188,1189],{"class":302,"line":703},[300,1190,365],{"emptyLinePlaceholder":364},[300,1192,1193,1195,1197,1199,1201,1203,1206,1208,1210,1212,1214,1216],{"class":302,"line":763},[300,1194,496],{"class":451},[300,1196,311],{"class":425},[300,1198,430],{"class":429},[300,1200,506],{"class":505},[300,1202,594],{"class":429},[300,1204,1205],{"class":585}," tool",[300,1207,467],{"class":429},[300,1209,1004],{"class":357},[300,1211,509],{"class":429},[300,1213,512],{"class":429},[300,1215,1110],{"class":441},[300,1217,458],{"class":429},[300,1219,1220,1223,1225,1227,1230,1233,1236,1238,1240],{"class":302,"line":803},[300,1221,1222],{"class":346},"        if",[300,1224,1205],{"class":357},[300,1226,537],{"class":429},[300,1228,1229],{"class":540},"name",[300,1231,1232],{"class":437}," in",[300,1234,1235],{"class":350}," self",[300,1237,537],{"class":429},[300,1239,1132],{"class":540},[300,1241,458],{"class":429},[300,1243,1245,1248,1251,1253,1255,1258,1260,1263,1265,1267,1269,1271],{"class":302,"line":1244},27,[300,1246,1247],{"class":346},"            raise",[300,1249,1250],{"class":470}," ValueError",[300,1252,430],{"class":429},[300,1254,731],{"class":451},[300,1256,1257],{"class":310},"\"duplicate tool name: ",[300,1259,532],{"class":531},[300,1261,1262],{"class":642},"tool",[300,1264,537],{"class":429},[300,1266,1229],{"class":540},[300,1268,544],{"class":531},[300,1270,528],{"class":310},[300,1272,445],{"class":429},[300,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294],{"class":302,"line":1275},28,[300,1277,1127],{"class":350},[300,1279,537],{"class":429},[300,1281,1132],{"class":540},[300,1283,611],{"class":429},[300,1285,1262],{"class":540},[300,1287,537],{"class":429},[300,1289,1229],{"class":540},[300,1291,667],{"class":429},[300,1293,670],{"class":437},[300,1295,1296],{"class":357}," tool\n",[300,1298,1300],{"class":302,"line":1299},29,[300,1301,365],{"emptyLinePlaceholder":364},[300,1303,1305,1307,1310,1312,1314,1316,1318,1320,1322,1324],{"class":302,"line":1304},30,[300,1306,496],{"class":451},[300,1308,1309],{"class":425}," schemas",[300,1311,430],{"class":429},[300,1313,506],{"class":505},[300,1315,509],{"class":429},[300,1317,512],{"class":429},[300,1319,608],{"class":357},[300,1321,611],{"class":429},[300,1323,1021],{"class":470},[300,1325,617],{"class":429},[300,1327,1329,1331,1334,1336,1338,1341,1344,1346,1348,1350,1352,1354,1356,1358,1361],{"class":302,"line":1328},31,[300,1330,522],{"class":346},[300,1332,1333],{"class":429}," [",[300,1335,1184],{"class":357},[300,1337,537],{"class":429},[300,1339,1340],{"class":642},"schema_for_provider",[300,1342,1343],{"class":429},"()",[300,1345,745],{"class":346},[300,1347,1158],{"class":357},[300,1349,685],{"class":346},[300,1351,1235],{"class":350},[300,1353,537],{"class":429},[300,1355,1132],{"class":540},[300,1357,537],{"class":429},[300,1359,1360],{"class":642},"values",[300,1362,1363],{"class":429},"()]\n",[300,1365,1367],{"class":302,"line":1366},32,[300,1368,365],{"emptyLinePlaceholder":364},[300,1370,1372,1374,1377,1379,1381,1383,1386,1388,1390,1392,1395,1397,1399,1401,1404,1406,1408,1410,1412,1415],{"class":302,"line":1371},33,[300,1373,496],{"class":451},[300,1375,1376],{"class":425}," dispatch",[300,1378,430],{"class":429},[300,1380,506],{"class":505},[300,1382,594],{"class":429},[300,1384,1385],{"class":585}," name",[300,1387,467],{"class":429},[300,1389,482],{"class":470},[300,1391,594],{"class":429},[300,1393,1394],{"class":585}," args",[300,1396,467],{"class":429},[300,1398,591],{"class":470},[300,1400,594],{"class":429},[300,1402,1403],{"class":585}," call_id",[300,1405,467],{"class":429},[300,1407,482],{"class":470},[300,1409,509],{"class":429},[300,1411,512],{"class":429},[300,1413,1414],{"class":357}," ToolResult",[300,1416,458],{"class":429},[300,1418,1420,1422,1425,1428,1430,1432,1434,1436],{"class":302,"line":1419},34,[300,1421,1222],{"class":346},[300,1423,1424],{"class":357}," name ",[300,1426,1427],{"class":437},"not",[300,1429,1232],{"class":437},[300,1431,1235],{"class":350},[300,1433,537],{"class":429},[300,1435,1132],{"class":540},[300,1437,458],{"class":429},[300,1439,1441,1444,1446,1448,1451,1453,1455,1457,1459],{"class":302,"line":1440},35,[300,1442,1443],{"class":346},"            return",[300,1445,1235],{"class":350},[300,1447,537],{"class":429},[300,1449,1450],{"class":642},"_unknown_tool",[300,1452,430],{"class":429},[300,1454,1229],{"class":642},[300,1456,594],{"class":429},[300,1458,1403],{"class":642},[300,1460,445],{"class":429},[300,1462,1464],{"class":302,"line":1463},36,[300,1465,365],{"emptyLinePlaceholder":364},[300,1467,1469,1472,1474,1476,1478,1480,1482,1484],{"class":302,"line":1468},37,[300,1470,1471],{"class":357},"        tool ",[300,1473,438],{"class":437},[300,1475,1235],{"class":350},[300,1477,537],{"class":429},[300,1479,1132],{"class":540},[300,1481,611],{"class":429},[300,1483,1229],{"class":540},[300,1485,1486],{"class":429},"]\n",[300,1488,1490,1493,1495,1497,1499,1501,1503,1505,1507,1510],{"class":302,"line":1489},38,[300,1491,1492],{"class":357},"        errors ",[300,1494,438],{"class":437},[300,1496,580],{"class":642},[300,1498,430],{"class":429},[300,1500,586],{"class":642},[300,1502,594],{"class":429},[300,1504,1205],{"class":642},[300,1506,537],{"class":429},[300,1508,1509],{"class":540},"input_schema",[300,1511,445],{"class":429},[300,1513,1515,1517,1520],{"class":302,"line":1514},39,[300,1516,1222],{"class":346},[300,1518,1519],{"class":357}," errors",[300,1521,458],{"class":429},[300,1523,1525,1527,1529,1531,1534,1536,1538,1540,1542,1544,1546],{"class":302,"line":1524},40,[300,1526,1443],{"class":346},[300,1528,1235],{"class":350},[300,1530,537],{"class":429},[300,1532,1533],{"class":642},"_validation_failure",[300,1535,430],{"class":429},[300,1537,1229],{"class":642},[300,1539,594],{"class":429},[300,1541,1519],{"class":642},[300,1543,594],{"class":429},[300,1545,1403],{"class":642},[300,1547,445],{"class":429},[300,1549,1551],{"class":302,"line":1550},41,[300,1552,365],{"emptyLinePlaceholder":364},[300,1554,1556,1558,1560,1563,1565,1567,1569,1571],{"class":302,"line":1555},42,[300,1557,1127],{"class":350},[300,1559,537],{"class":429},[300,1561,1562],{"class":642},"_record",[300,1564,430],{"class":429},[300,1566,1229],{"class":642},[300,1568,594],{"class":429},[300,1570,1394],{"class":642},[300,1572,445],{"class":429},[300,1574,1576,1579,1581,1583,1585,1588,1590,1592,1594,1596,1598,1600],{"class":302,"line":1575},43,[300,1577,1578],{"class":357},"        loop_result ",[300,1580,438],{"class":437},[300,1582,1235],{"class":350},[300,1584,537],{"class":429},[300,1586,1587],{"class":642},"_check_loop",[300,1589,430],{"class":429},[300,1591,1229],{"class":642},[300,1593,594],{"class":429},[300,1595,1394],{"class":642},[300,1597,594],{"class":429},[300,1599,1403],{"class":642},[300,1601,445],{"class":429},[300,1603,1605,1607,1610,1613,1616,1618],{"class":302,"line":1604},44,[300,1606,1222],{"class":346},[300,1608,1609],{"class":357}," loop_result ",[300,1611,1612],{"class":437},"is",[300,1614,1615],{"class":437}," not",[300,1617,1110],{"class":441},[300,1619,458],{"class":429},[300,1621,1623,1625],{"class":302,"line":1622},45,[300,1624,1443],{"class":346},[300,1626,1627],{"class":357}," loop_result\n",[300,1629,1631],{"class":302,"line":1630},46,[300,1632,365],{"emptyLinePlaceholder":364},[300,1634,1636,1639],{"class":302,"line":1635},47,[300,1637,1638],{"class":346},"        try",[300,1640,458],{"class":429},[300,1642,1644,1647,1649,1651,1653,1656,1658,1661,1663],{"class":302,"line":1643},48,[300,1645,1646],{"class":357},"            content ",[300,1648,438],{"class":437},[300,1650,1205],{"class":357},[300,1652,537],{"class":429},[300,1654,1655],{"class":642},"run",[300,1657,430],{"class":429},[300,1659,1660],{"class":437},"**",[300,1662,586],{"class":642},[300,1664,445],{"class":429},[300,1666,1668,1671,1674,1677,1680],{"class":302,"line":1667},49,[300,1669,1670],{"class":346},"        except",[300,1672,1673],{"class":470}," Exception",[300,1675,1676],{"class":346}," as",[300,1678,1679],{"class":357}," e",[300,1681,458],{"class":429},[300,1683,1685,1687,1689],{"class":302,"line":1684},50,[300,1686,1443],{"class":346},[300,1688,1414],{"class":642},[300,1690,1691],{"class":429},"(\n",[300,1693,1695,1698,1700,1703],{"class":302,"line":1694},51,[300,1696,1697],{"class":433},"                call_id",[300,1699,438],{"class":437},[300,1701,1702],{"class":642},"call_id",[300,1704,1705],{"class":429},",\n",[300,1707,1709,1712,1714,1716,1718,1720,1722,1724,1727,1729,1732,1734,1737,1740,1743,1745,1747,1749,1751,1753,1755],{"class":302,"line":1708},52,[300,1710,1711],{"class":433},"                content",[300,1713,438],{"class":437},[300,1715,731],{"class":451},[300,1717,528],{"class":310},[300,1719,532],{"class":531},[300,1721,1229],{"class":642},[300,1723,544],{"class":531},[300,1725,1726],{"class":310}," raised ",[300,1728,532],{"class":531},[300,1730,1731],{"class":470},"type",[300,1733,430],{"class":429},[300,1735,1736],{"class":642},"e",[300,1738,1739],{"class":429},").",[300,1741,1742],{"class":350},"__name__",[300,1744,544],{"class":531},[300,1746,547],{"class":310},[300,1748,532],{"class":531},[300,1750,1736],{"class":642},[300,1752,544],{"class":531},[300,1754,528],{"class":310},[300,1756,1705],{"class":429},[300,1758,1760,1763,1765,1767],{"class":302,"line":1759},53,[300,1761,1762],{"class":433},"                is_error",[300,1764,438],{"class":437},[300,1766,442],{"class":441},[300,1768,1705],{"class":429},[300,1770,1772],{"class":302,"line":1771},54,[300,1773,1774],{"class":429},"            )\n",[300,1776,1778,1780,1782,1784,1786,1788,1790,1792,1795,1797,1800],{"class":302,"line":1777},55,[300,1779,522],{"class":346},[300,1781,1414],{"class":642},[300,1783,430],{"class":429},[300,1785,1702],{"class":433},[300,1787,438],{"class":437},[300,1789,1702],{"class":642},[300,1791,594],{"class":429},[300,1793,1794],{"class":433}," content",[300,1796,438],{"class":437},[300,1798,1799],{"class":642},"content",[300,1801,445],{"class":429},[300,1803,1805],{"class":302,"line":1804},56,[300,1806,365],{"emptyLinePlaceholder":364},[300,1808,1810],{"class":302,"line":1809},57,[300,1811,1812],{"class":339},"    # --- helpers ---\n",[300,1814,1816],{"class":302,"line":1815},58,[300,1817,365],{"emptyLinePlaceholder":364},[300,1819,1821,1823,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852],{"class":302,"line":1820},59,[300,1822,496],{"class":451},[300,1824,1825],{"class":425}," _unknown_tool",[300,1827,430],{"class":429},[300,1829,506],{"class":505},[300,1831,594],{"class":429},[300,1833,1385],{"class":585},[300,1835,467],{"class":429},[300,1837,482],{"class":470},[300,1839,594],{"class":429},[300,1841,1403],{"class":585},[300,1843,467],{"class":429},[300,1845,482],{"class":470},[300,1847,509],{"class":429},[300,1849,512],{"class":429},[300,1851,1414],{"class":357},[300,1853,458],{"class":429},[300,1855,1857],{"class":302,"line":1856},60,[300,1858,1859],{"class":339},"        # Try to suggest a close match. We drop difflib's default cutoff\n",[300,1861,1863],{"class":302,"line":1862},61,[300,1864,1865],{"class":339},"        # of 0.6 to 0.5 — the ratio for `calculator` vs `calc` is ~0.57,\n",[300,1867,1869],{"class":302,"line":1868},62,[300,1870,1871],{"class":339},"        # and prefix-heavy misspellings like that are exactly the case\n",[300,1873,1875],{"class":302,"line":1874},63,[300,1876,1877],{"class":339},"        # we want to catch. 0.5 still rejects unrelated names.\n",[300,1879,1881,1884],{"class":302,"line":1880},64,[300,1882,1883],{"class":346},"        import",[300,1885,1886],{"class":357}," difflib\n",[300,1888,1890,1893,1895,1898,1900,1903],{"class":302,"line":1889},65,[300,1891,1892],{"class":357},"        close ",[300,1894,438],{"class":437},[300,1896,1897],{"class":357}," difflib",[300,1899,537],{"class":429},[300,1901,1902],{"class":642},"get_close_matches",[300,1904,1691],{"class":429},[300,1906,1908,1911,1913,1915,1917,1919,1921,1923,1925,1928,1931,1934,1936,1939,1941,1944,1946,1949],{"class":302,"line":1907},66,[300,1909,1910],{"class":642},"            name",[300,1912,594],{"class":429},[300,1914,608],{"class":470},[300,1916,430],{"class":429},[300,1918,506],{"class":350},[300,1920,537],{"class":429},[300,1922,1132],{"class":540},[300,1924,537],{"class":429},[300,1926,1927],{"class":642},"keys",[300,1929,1930],{"class":429},"()),",[300,1932,1933],{"class":433}," n",[300,1935,438],{"class":437},[300,1937,1938],{"class":531},"1",[300,1940,594],{"class":429},[300,1942,1943],{"class":433}," cutoff",[300,1945,438],{"class":437},[300,1947,1948],{"class":531},"0.5",[300,1950,1705],{"class":429},[300,1952,1954],{"class":302,"line":1953},67,[300,1955,1956],{"class":429},"        )\n",[300,1958,1960,1963,1965,1967,1970,1972,1975,1977,1980,1982,1985,1987,1990,1993,1996,1999],{"class":302,"line":1959},68,[300,1961,1962],{"class":357},"        suggestion ",[300,1964,438],{"class":437},[300,1966,525],{"class":451},[300,1968,1969],{"class":310},"\" Did you mean ",[300,1971,532],{"class":531},[300,1973,1974],{"class":357},"close",[300,1976,611],{"class":429},[300,1978,1979],{"class":531},"0",[300,1981,667],{"class":429},[300,1983,1984],{"class":451},"!r",[300,1986,544],{"class":531},[300,1988,1989],{"class":310},"?\"",[300,1991,1992],{"class":346}," if",[300,1994,1995],{"class":357}," close ",[300,1997,1998],{"class":346},"else",[300,2000,2001],{"class":314}," \"\"\n",[300,2003,2005,2007,2009],{"class":302,"line":2004},69,[300,2006,522],{"class":346},[300,2008,1414],{"class":642},[300,2010,1691],{"class":429},[300,2012,2014,2017,2019,2021],{"class":302,"line":2013},70,[300,2015,2016],{"class":433},"            call_id",[300,2018,438],{"class":437},[300,2020,1702],{"class":642},[300,2022,1705],{"class":429},[300,2024,2026,2029,2031],{"class":302,"line":2025},71,[300,2027,2028],{"class":433},"            content",[300,2030,438],{"class":437},[300,2032,1691],{"class":429},[300,2034,2036,2039,2042,2044,2046,2048,2050,2052,2054,2057,2059],{"class":302,"line":2035},72,[300,2037,2038],{"class":451},"                f",[300,2040,2041],{"class":310},"\"unknown tool: ",[300,2043,532],{"class":531},[300,2045,1229],{"class":642},[300,2047,1984],{"class":451},[300,2049,544],{"class":531},[300,2051,537],{"class":310},[300,2053,532],{"class":531},[300,2055,2056],{"class":642},"suggestion",[300,2058,544],{"class":531},[300,2060,2061],{"class":310}," \"\n",[300,2063,2065,2067,2070,2072,2075,2077,2079,2081,2083,2085,2087,2090,2092],{"class":302,"line":2064},73,[300,2066,2038],{"class":451},[300,2068,2069],{"class":310},"\"Available: ",[300,2071,532],{"class":531},[300,2073,2074],{"class":499},"sorted",[300,2076,430],{"class":429},[300,2078,506],{"class":350},[300,2080,537],{"class":429},[300,2082,1132],{"class":540},[300,2084,537],{"class":429},[300,2086,1927],{"class":642},[300,2088,2089],{"class":429},"())",[300,2091,544],{"class":531},[300,2093,561],{"class":310},[300,2095,2097],{"class":302,"line":2096},74,[300,2098,2099],{"class":429},"            ),\n",[300,2101,2103,2106,2108,2110],{"class":302,"line":2102},75,[300,2104,2105],{"class":433},"            is_error",[300,2107,438],{"class":437},[300,2109,442],{"class":441},[300,2111,1705],{"class":429},[300,2113,2115],{"class":302,"line":2114},76,[300,2116,1956],{"class":429},[300,2118,2120],{"class":302,"line":2119},77,[300,2121,365],{"emptyLinePlaceholder":364},[300,2123,2125,2127,2130],{"class":302,"line":2124},78,[300,2126,496],{"class":451},[300,2128,2129],{"class":425}," _validation_failure",[300,2131,1691],{"class":429},[300,2133,2135,2137,2139,2141,2143,2145,2147,2149,2151,2153,2155,2157,2160,2162,2164],{"class":302,"line":2134},79,[300,2136,1127],{"class":505},[300,2138,594],{"class":429},[300,2140,1385],{"class":585},[300,2142,467],{"class":429},[300,2144,482],{"class":470},[300,2146,594],{"class":429},[300,2148,1519],{"class":585},[300,2150,467],{"class":429},[300,2152,608],{"class":357},[300,2154,611],{"class":429},[300,2156,614],{"class":357},[300,2158,2159],{"class":429},"],",[300,2161,1403],{"class":585},[300,2163,467],{"class":429},[300,2165,471],{"class":470},[300,2167,2169,2172,2174,2176],{"class":302,"line":2168},80,[300,2170,2171],{"class":429},"    )",[300,2173,512],{"class":429},[300,2175,1414],{"class":357},[300,2177,458],{"class":429},[300,2179,2181,2184,2186,2188,2191,2193,2195,2197,2199,2201,2203,2205,2207,2209,2212,2214,2216],{"class":302,"line":2180},81,[300,2182,2183],{"class":357},"        summary ",[300,2185,438],{"class":437},[300,2187,711],{"class":314},[300,2189,2190],{"class":310},"; ",[300,2192,528],{"class":314},[300,2194,537],{"class":429},[300,2196,726],{"class":642},[300,2198,430],{"class":429},[300,2200,999],{"class":470},[300,2202,430],{"class":429},[300,2204,1736],{"class":642},[300,2206,509],{"class":429},[300,2208,745],{"class":346},[300,2210,2211],{"class":642}," e ",[300,2213,685],{"class":346},[300,2215,1519],{"class":642},[300,2217,445],{"class":429},[300,2219,2221,2223,2225],{"class":302,"line":2220},82,[300,2222,522],{"class":346},[300,2224,1414],{"class":642},[300,2226,1691],{"class":429},[300,2228,2230,2232,2234,2236],{"class":302,"line":2229},83,[300,2231,2016],{"class":433},[300,2233,438],{"class":437},[300,2235,1702],{"class":642},[300,2237,1705],{"class":429},[300,2239,2241,2243,2245,2247,2249,2251,2253,2255,2258,2260,2263,2265,2267],{"class":302,"line":2240},84,[300,2242,2028],{"class":433},[300,2244,438],{"class":437},[300,2246,731],{"class":451},[300,2248,528],{"class":310},[300,2250,532],{"class":531},[300,2252,1229],{"class":642},[300,2254,544],{"class":531},[300,2256,2257],{"class":310},": invalid arguments. ",[300,2259,532],{"class":531},[300,2261,2262],{"class":642},"summary",[300,2264,544],{"class":531},[300,2266,528],{"class":310},[300,2268,1705],{"class":429},[300,2270,2272,2274,2276,2278],{"class":302,"line":2271},85,[300,2273,2105],{"class":433},[300,2275,438],{"class":437},[300,2277,442],{"class":441},[300,2279,1705],{"class":429},[300,2281,2283],{"class":302,"line":2282},86,[300,2284,1956],{"class":429},[300,2286,2288],{"class":302,"line":2287},87,[300,2289,365],{"emptyLinePlaceholder":364},[300,2291,2293,2295,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324],{"class":302,"line":2292},88,[300,2294,496],{"class":451},[300,2296,2297],{"class":425}," _record",[300,2299,430],{"class":429},[300,2301,506],{"class":505},[300,2303,594],{"class":429},[300,2305,1385],{"class":585},[300,2307,467],{"class":429},[300,2309,482],{"class":470},[300,2311,594],{"class":429},[300,2313,1394],{"class":585},[300,2315,467],{"class":429},[300,2317,591],{"class":470},[300,2319,509],{"class":429},[300,2321,512],{"class":429},[300,2323,1110],{"class":441},[300,2325,458],{"class":429},[300,2327,2329,2331],{"class":302,"line":2328},89,[300,2330,1883],{"class":346},[300,2332,2333],{"class":357}," json\n",[300,2335,2337,2339,2341,2343,2345,2347,2350,2352,2354,2357,2359,2362,2364,2366,2368,2371,2373,2375],{"class":302,"line":2336},90,[300,2338,1127],{"class":350},[300,2340,537],{"class":429},[300,2342,1146],{"class":540},[300,2344,537],{"class":429},[300,2346,771],{"class":642},[300,2348,2349],{"class":429},"((",[300,2351,1229],{"class":642},[300,2353,594],{"class":429},[300,2355,2356],{"class":642}," json",[300,2358,537],{"class":429},[300,2360,2361],{"class":642},"dumps",[300,2363,430],{"class":429},[300,2365,586],{"class":642},[300,2367,594],{"class":429},[300,2369,2370],{"class":433}," sort_keys",[300,2372,438],{"class":437},[300,2374,442],{"class":441},[300,2376,2377],{"class":429},")))\n",[300,2379,2381,2383,2386,2388,2390,2392,2394,2396,2399,2402],{"class":302,"line":2380},91,[300,2382,1222],{"class":346},[300,2384,2385],{"class":499}," len",[300,2387,430],{"class":429},[300,2389,506],{"class":350},[300,2391,537],{"class":429},[300,2393,1146],{"class":540},[300,2395,509],{"class":429},[300,2397,2398],{"class":437}," >",[300,2400,2401],{"class":531}," 100",[300,2403,458],{"class":429},[300,2405,2407,2409,2411,2413,2415,2417,2419,2421,2423,2426,2429],{"class":302,"line":2406},92,[300,2408,1174],{"class":350},[300,2410,537],{"class":429},[300,2412,1146],{"class":540},[300,2414,670],{"class":437},[300,2416,1235],{"class":350},[300,2418,537],{"class":429},[300,2420,1146],{"class":540},[300,2422,611],{"class":429},[300,2424,2425],{"class":437},"-",[300,2427,2428],{"class":531},"100",[300,2430,2431],{"class":429},":]\n",[300,2433,2435],{"class":302,"line":2434},93,[300,2436,365],{"emptyLinePlaceholder":364},[300,2438,2440,2442,2445,2447,2449,2451,2453,2455,2457,2459,2461,2463,2465,2467,2469,2471,2473,2475,2477,2480,2483,2485],{"class":302,"line":2439},94,[300,2441,496],{"class":451},[300,2443,2444],{"class":425}," _check_loop",[300,2446,430],{"class":429},[300,2448,506],{"class":505},[300,2450,594],{"class":429},[300,2452,1385],{"class":585},[300,2454,467],{"class":429},[300,2456,482],{"class":470},[300,2458,594],{"class":429},[300,2460,1394],{"class":585},[300,2462,467],{"class":429},[300,2464,591],{"class":470},[300,2466,594],{"class":429},[300,2468,1403],{"class":585},[300,2470,467],{"class":429},[300,2472,482],{"class":470},[300,2474,509],{"class":429},[300,2476,512],{"class":429},[300,2478,2479],{"class":357}," ToolResult ",[300,2481,2482],{"class":437},"|",[300,2484,1110],{"class":441},[300,2486,458],{"class":429},[300,2488,2490,2492],{"class":302,"line":2489},95,[300,2491,1883],{"class":346},[300,2493,2333],{"class":357},[300,2495,2497,2500,2502,2505,2507,2509,2511,2513,2515,2517,2519,2521,2523,2525,2527],{"class":302,"line":2496},96,[300,2498,2499],{"class":357},"        key ",[300,2501,438],{"class":437},[300,2503,2504],{"class":429}," (",[300,2506,1229],{"class":357},[300,2508,594],{"class":429},[300,2510,2356],{"class":357},[300,2512,537],{"class":429},[300,2514,2361],{"class":642},[300,2516,430],{"class":429},[300,2518,586],{"class":642},[300,2520,594],{"class":429},[300,2522,2370],{"class":433},[300,2524,438],{"class":437},[300,2526,442],{"class":441},[300,2528,800],{"class":429},[300,2530,2532,2535,2537,2540,2542,2544,2546,2549,2551,2553,2555,2557,2559,2561,2564,2567,2569,2571,2574,2577],{"class":302,"line":2531},97,[300,2533,2534],{"class":357},"        repeats ",[300,2536,438],{"class":437},[300,2538,2539],{"class":499}," sum",[300,2541,430],{"class":429},[300,2543,1938],{"class":531},[300,2545,745],{"class":346},[300,2547,2548],{"class":642}," k ",[300,2550,685],{"class":346},[300,2552,1235],{"class":350},[300,2554,537],{"class":429},[300,2556,1146],{"class":540},[300,2558,611],{"class":429},[300,2560,2425],{"class":437},[300,2562,953],{"class":2563},"swQdS",[300,2565,2566],{"class":429},":]",[300,2568,1992],{"class":346},[300,2570,2548],{"class":642},[300,2572,2573],{"class":437},"==",[300,2575,2576],{"class":642}," key",[300,2578,445],{"class":429},[300,2580,2582,2584,2587,2590,2593],{"class":302,"line":2581},98,[300,2583,1222],{"class":346},[300,2585,2586],{"class":357}," repeats ",[300,2588,2589],{"class":437},">=",[300,2591,2592],{"class":350}," MAX_REPEAT_CALLS",[300,2594,458],{"class":429},[300,2596,2598,2600,2602],{"class":302,"line":2597},99,[300,2599,1443],{"class":346},[300,2601,1414],{"class":642},[300,2603,1691],{"class":429},[300,2605,2607,2609,2611,2613],{"class":302,"line":2606},100,[300,2608,1697],{"class":433},[300,2610,438],{"class":437},[300,2612,1702],{"class":642},[300,2614,1705],{"class":429},[300,2616,2618,2620,2622],{"class":302,"line":2617},101,[300,2619,1711],{"class":433},[300,2621,438],{"class":437},[300,2623,1691],{"class":429},[300,2625,2627,2630,2633,2635,2637,2639],{"class":302,"line":2626},102,[300,2628,2629],{"class":451},"                    f",[300,2631,2632],{"class":310},"\"tool-call loop detected: ",[300,2634,532],{"class":531},[300,2636,1229],{"class":642},[300,2638,544],{"class":531},[300,2640,2641],{"class":310}," called with identical \"\n",[300,2643,2645,2647,2650,2652,2654,2656],{"class":302,"line":2644},103,[300,2646,2629],{"class":451},[300,2648,2649],{"class":310},"\"arguments ",[300,2651,532],{"class":531},[300,2653,953],{"class":499},[300,2655,544],{"class":531},[300,2657,2658],{"class":310}," times in a row. \"\n",[300,2660,2662,2665,2668],{"class":302,"line":2661},104,[300,2663,2664],{"class":314},"                    \"",[300,2666,2667],{"class":310},"Try a different approach or different arguments, or ",[300,2669,561],{"class":314},[300,2671,2673,2675,2678],{"class":302,"line":2672},105,[300,2674,2664],{"class":314},[300,2676,2677],{"class":310},"stop and return your current best answer.",[300,2679,561],{"class":314},[300,2681,2683],{"class":302,"line":2682},106,[300,2684,2685],{"class":429},"                ),\n",[300,2687,2689,2691,2693,2695],{"class":302,"line":2688},107,[300,2690,1762],{"class":433},[300,2692,438],{"class":437},[300,2694,442],{"class":441},[300,2696,1705],{"class":429},[300,2698,2700],{"class":302,"line":2699},108,[300,2701,1774],{"class":429},[300,2703,2705,2707],{"class":302,"line":2704},109,[300,2706,522],{"class":346},[300,2708,2709],{"class":441}," None\n",[112,2711,2712],{},"Three new behaviors.",[112,2714,2715,2718,2719,2722,2723,2725,2726,2729,2730,2733,2734,2737],{},[233,2716,2717],{},"Unknown tools suggest alternatives."," If the model asks for ",[122,2720,2721],{},"calculator"," and we only have ",[122,2724,239],{},", ",[122,2727,2728],{},"difflib.get_close_matches"," produces ",[122,2731,2732],{},"\"Did you mean 'calc'?\"",". In my experience, this recovers about 80% of misnamed tool calls in one turn. It costs us one ",[122,2735,2736],{},"import difflib"," and three lines.",[112,2739,2740,2743,2744,2747,2748,2750],{},[233,2741,2742],{},"Validation errors come back structured."," The model reads ",[122,2745,2746],{},"calc: invalid arguments. args.expression: 'expression' is a required property"," and, in the typical case, fixes it on the next turn. Compare to the pre-validation version where it sees the Python ",[122,2749,132],{}," message — both work, but structured is faster.",[112,2752,2753,2756],{},[233,2754,2755],{},"Tool-call loops are detected and explained."," After three consecutive identical calls, the registry returns a synthetic error explaining what happened. This is the key intervention, and it's the same principle as §6.1's Reflexion framing applied to a different failure mode: the model gets a structured, external hint that it's stuck, rather than more turns of the same unhelpful output it's already producing. Most models recover — they try different arguments, try a different tool, or stop and return their best current answer.",[220,2758],{},[223,2760,2762],{"id":2761},"_64-what-identical-means","6.4 What \"Identical\" Means",[112,2764,2765,2766,2769,2770,2773,2774,2777,2778,2781],{},"The loop detector uses ",[122,2767,2768],{},"(name, json.dumps(args, sort_keys=True))"," as the dedup key. That's exact-match. A model that calls ",[122,2771,2772],{},"calc(\"1+1\")"," and then ",[122,2775,2776],{},"calc(\"1 + 1\")"," would bypass it. That's usually fine — the model is making progress if it's varying the arguments, even trivially. The failure mode we care about is the one where the model has ",[115,2779,2780],{},"nothing"," left to try.",[112,2783,2784],{},"Two extensions are tempting. Neither made it in:",[112,2786,2787,2790],{},[233,2788,2789],{},"Fuzzy match."," Collapse whitespace, normalize casing, round floats. Catches trivial variations but also catches legitimate ones — \"read lines 1-50\" and \"read lines 1-51\" look fuzzy-identical but the second is a real step forward. False positives on progress are worse than false negatives on loops.",[112,2792,2793,2796],{},[233,2794,2795],{},"Semantic match."," An LLM-based judge of whether two calls are \"really the same.\" Expensive, non-deterministic, and a great way to have a bug you can't reproduce.",[112,2798,2799,2800,2802],{},"The exact-match version catches the nasty case — a genuine stuck loop — without stepping on real progress. If you hit a case where it misses, bump ",[122,2801,953],{}," down or look for a heuristic specific to your domain.",[220,2804],{},[223,2806,2808],{"id":2807},"_65-the-max_iterations-question","6.5 The MAX_ITERATIONS Question",[112,2810,2811,2812,2815,2816,2818,2819,537],{},"Up until this chapter, the loop's outer bound has been ",[122,2813,2814],{},"MAX_ITERATIONS = 20",". The loop detector at the registry level gives us a smarter inner bound: we stop before twenty iterations if the model is spinning. But ",[122,2817,136],{}," itself is still a coarse safety net, and the right answer to \"how many iterations is too many\" isn't a number — it's a ",[233,2820,2821],{},"budget",[112,2823,2824],{},"A cost budget based on tokens (Chapter 20) or a time budget (wall-clock seconds) is more honest than iteration count. A short task with a handful of 100K-token tool results hits cost ceilings fast; a long task with tiny tool results can legitimately run 40 iterations cheaply. Counting iterations is a proxy; cost is the thing.",[112,2826,2827,2828,2830,2831,2833],{},"We'll upgrade ",[122,2829,136],{}," to a proper budget in Chapter 20. For now, we keep the iteration cap as a fail-safe and note the ",[122,2832,1587],{}," intervention is the real signal.",[220,2835],{},[223,2837,2839],{"id":2838},"_66-a-small-test-suite","6.6 A Small Test Suite",[112,2841,2842],{},"Now is the time to start testing the loop's error paths deliberately, not just its happy path. We've been relying on examples that run to completion; we need tests that exercise the five-break table from Chapter 2 and confirm they all fail gracefully.",[291,2844,2846],{"className":330,"code":2845,"language":332,"meta":296,"style":296},"# tests\u002Ftest_registry.py\nfrom harness.tools.registry import ToolRegistry\nfrom harness.tools.std import calc\n\n\ndef test_unknown_tool_with_suggestion():\n    registry = ToolRegistry(tools=[calc])\n    result = registry.dispatch(\"calculator\", {\"expression\": \"2+2\"}, \"call-1\")\n    assert result.is_error\n    assert \"Did you mean 'calc'?\" in result.content\n\n\ndef test_validation_missing_required():\n    registry = ToolRegistry(tools=[calc])\n    result = registry.dispatch(\"calc\", {}, \"call-1\")\n    assert result.is_error\n    assert \"expression\" in result.content\n    assert \"required\" in result.content\n\n\ndef test_validation_wrong_type():\n    registry = ToolRegistry(tools=[calc])\n    result = registry.dispatch(\"calc\", {\"expression\": 42}, \"call-1\")\n    assert result.is_error\n    assert \"string\" in result.content.lower() or \"str\" in result.content.lower()\n\n\ndef test_loop_detection():\n    registry = ToolRegistry(tools=[calc])\n    for i in range(3):\n        result = registry.dispatch(\"calc\", {\"expression\": \"1+1\"}, f\"call-{i}\")\n    # the third call should be caught by the loop detector\n    result = registry.dispatch(\"calc\", {\"expression\": \"1+1\"}, \"call-3\")\n    assert result.is_error\n    assert \"tool-call loop\" in result.content\n\n\ndef test_happy_path():\n    registry = ToolRegistry(tools=[calc])\n    result = registry.dispatch(\"calc\", {\"expression\": \"2+2\"}, \"call-1\")\n    assert not result.is_error\n    assert result.content == \"4\"\n",[122,2847,2848,2853,2874,2894,2898,2902,2912,2934,2989,3002,3022,3026,3030,3039,3059,3092,3102,3120,3139,3143,3147,3156,3176,3221,3231,3281,3285,3289,3298,3318,3337,3395,3400,3449,3459,3478,3482,3486,3495,3515,3563,3575],{"__ignoreMap":296},[300,2849,2850],{"class":302,"line":303},[300,2851,2852],{"class":339},"# tests\u002Ftest_registry.py\n",[300,2854,2855,2857,2860,2862,2864,2866,2869,2871],{"class":302,"line":343},[300,2856,347],{"class":346},[300,2858,2859],{"class":357}," harness",[300,2861,537],{"class":429},[300,2863,1132],{"class":357},[300,2865,537],{"class":429},[300,2867,2868],{"class":357},"registry ",[300,2870,376],{"class":346},[300,2872,2873],{"class":357}," ToolRegistry\n",[300,2875,2876,2878,2880,2882,2884,2886,2889,2891],{"class":302,"line":361},[300,2877,347],{"class":346},[300,2879,2859],{"class":357},[300,2881,537],{"class":429},[300,2883,1132],{"class":357},[300,2885,537],{"class":429},[300,2887,2888],{"class":357},"std ",[300,2890,376],{"class":346},[300,2892,2893],{"class":357}," calc\n",[300,2895,2896],{"class":302,"line":368},[300,2897,365],{"emptyLinePlaceholder":364},[300,2899,2900],{"class":302,"line":382},[300,2901,365],{"emptyLinePlaceholder":364},[300,2903,2904,2906,2909],{"class":302,"line":387},[300,2905,577],{"class":451},[300,2907,2908],{"class":425}," test_unknown_tool_with_suggestion",[300,2910,2911],{"class":429},"():\n",[300,2913,2914,2917,2919,2921,2923,2925,2927,2929,2931],{"class":302,"line":395},[300,2915,2916],{"class":357},"    registry ",[300,2918,438],{"class":437},[300,2920,983],{"class":642},[300,2922,430],{"class":429},[300,2924,1132],{"class":433},[300,2926,438],{"class":437},[300,2928,611],{"class":429},[300,2930,239],{"class":642},[300,2932,2933],{"class":429},"])\n",[300,2935,2936,2939,2941,2944,2946,2949,2951,2953,2955,2957,2959,2962,2964,2966,2968,2970,2972,2975,2977,2980,2982,2985,2987],{"class":302,"line":408},[300,2937,2938],{"class":357},"    result ",[300,2940,438],{"class":437},[300,2942,2943],{"class":357}," registry",[300,2945,537],{"class":429},[300,2947,2948],{"class":642},"dispatch",[300,2950,430],{"class":429},[300,2952,528],{"class":314},[300,2954,2721],{"class":310},[300,2956,528],{"class":314},[300,2958,594],{"class":429},[300,2960,2961],{"class":429}," {",[300,2963,528],{"class":314},[300,2965,250],{"class":310},[300,2967,528],{"class":314},[300,2969,467],{"class":429},[300,2971,711],{"class":314},[300,2973,2974],{"class":310},"2+2",[300,2976,528],{"class":314},[300,2978,2979],{"class":429},"},",[300,2981,711],{"class":314},[300,2983,2984],{"class":310},"call-1",[300,2986,528],{"class":314},[300,2988,445],{"class":429},[300,2990,2991,2994,2997,2999],{"class":302,"line":413},[300,2992,2993],{"class":346},"    assert",[300,2995,2996],{"class":357}," result",[300,2998,537],{"class":429},[300,3000,3001],{"class":540},"is_error\n",[300,3003,3004,3006,3008,3011,3013,3015,3017,3019],{"class":302,"line":418},[300,3005,2993],{"class":346},[300,3007,711],{"class":314},[300,3009,3010],{"class":310},"Did you mean 'calc'?",[300,3012,528],{"class":314},[300,3014,1232],{"class":437},[300,3016,2996],{"class":357},[300,3018,537],{"class":429},[300,3020,3021],{"class":540},"content\n",[300,3023,3024],{"class":302,"line":448},[300,3025,365],{"emptyLinePlaceholder":364},[300,3027,3028],{"class":302,"line":461},[300,3029,365],{"emptyLinePlaceholder":364},[300,3031,3032,3034,3037],{"class":302,"line":474},[300,3033,577],{"class":451},[300,3035,3036],{"class":425}," test_validation_missing_required",[300,3038,2911],{"class":429},[300,3040,3041,3043,3045,3047,3049,3051,3053,3055,3057],{"class":302,"line":488},[300,3042,2916],{"class":357},[300,3044,438],{"class":437},[300,3046,983],{"class":642},[300,3048,430],{"class":429},[300,3050,1132],{"class":433},[300,3052,438],{"class":437},[300,3054,611],{"class":429},[300,3056,239],{"class":642},[300,3058,2933],{"class":429},[300,3060,3061,3063,3065,3067,3069,3071,3073,3075,3077,3079,3081,3084,3086,3088,3090],{"class":302,"line":493},[300,3062,2938],{"class":357},[300,3064,438],{"class":437},[300,3066,2943],{"class":357},[300,3068,537],{"class":429},[300,3070,2948],{"class":642},[300,3072,430],{"class":429},[300,3074,528],{"class":314},[300,3076,239],{"class":310},[300,3078,528],{"class":314},[300,3080,594],{"class":429},[300,3082,3083],{"class":429}," {},",[300,3085,711],{"class":314},[300,3087,2984],{"class":310},[300,3089,528],{"class":314},[300,3091,445],{"class":429},[300,3093,3094,3096,3098,3100],{"class":302,"line":519},[300,3095,2993],{"class":346},[300,3097,2996],{"class":357},[300,3099,537],{"class":429},[300,3101,3001],{"class":540},[300,3103,3104,3106,3108,3110,3112,3114,3116,3118],{"class":302,"line":564},[300,3105,2993],{"class":346},[300,3107,711],{"class":314},[300,3109,250],{"class":310},[300,3111,528],{"class":314},[300,3113,1232],{"class":437},[300,3115,2996],{"class":357},[300,3117,537],{"class":429},[300,3119,3021],{"class":540},[300,3121,3122,3124,3126,3129,3131,3133,3135,3137],{"class":302,"line":569},[300,3123,2993],{"class":346},[300,3125,711],{"class":314},[300,3127,3128],{"class":310},"required",[300,3130,528],{"class":314},[300,3132,1232],{"class":437},[300,3134,2996],{"class":357},[300,3136,537],{"class":429},[300,3138,3021],{"class":540},[300,3140,3141],{"class":302,"line":574},[300,3142,365],{"emptyLinePlaceholder":364},[300,3144,3145],{"class":302,"line":620},[300,3146,365],{"emptyLinePlaceholder":364},[300,3148,3149,3151,3154],{"class":302,"line":634},[300,3150,577],{"class":451},[300,3152,3153],{"class":425}," test_validation_wrong_type",[300,3155,2911],{"class":429},[300,3157,3158,3160,3162,3164,3166,3168,3170,3172,3174],{"class":302,"line":653},[300,3159,2916],{"class":357},[300,3161,438],{"class":437},[300,3163,983],{"class":642},[300,3165,430],{"class":429},[300,3167,1132],{"class":433},[300,3169,438],{"class":437},[300,3171,611],{"class":429},[300,3173,239],{"class":642},[300,3175,2933],{"class":429},[300,3177,3178,3180,3182,3184,3186,3188,3190,3192,3194,3196,3198,3200,3202,3204,3206,3208,3211,3213,3215,3217,3219],{"class":302,"line":676},[300,3179,2938],{"class":357},[300,3181,438],{"class":437},[300,3183,2943],{"class":357},[300,3185,537],{"class":429},[300,3187,2948],{"class":642},[300,3189,430],{"class":429},[300,3191,528],{"class":314},[300,3193,239],{"class":310},[300,3195,528],{"class":314},[300,3197,594],{"class":429},[300,3199,2961],{"class":429},[300,3201,528],{"class":314},[300,3203,250],{"class":310},[300,3205,528],{"class":314},[300,3207,467],{"class":429},[300,3209,3210],{"class":531}," 42",[300,3212,2979],{"class":429},[300,3214,711],{"class":314},[300,3216,2984],{"class":310},[300,3218,528],{"class":314},[300,3220,445],{"class":429},[300,3222,3223,3225,3227,3229],{"class":302,"line":703},[300,3224,2993],{"class":346},[300,3226,2996],{"class":357},[300,3228,537],{"class":429},[300,3230,3001],{"class":540},[300,3232,3233,3235,3237,3240,3242,3244,3246,3248,3250,3252,3255,3257,3260,3262,3264,3266,3268,3270,3272,3274,3276,3278],{"class":302,"line":763},[300,3234,2993],{"class":346},[300,3236,711],{"class":314},[300,3238,3239],{"class":310},"string",[300,3241,528],{"class":314},[300,3243,1232],{"class":437},[300,3245,2996],{"class":357},[300,3247,537],{"class":429},[300,3249,1799],{"class":540},[300,3251,537],{"class":429},[300,3253,3254],{"class":642},"lower",[300,3256,1343],{"class":429},[300,3258,3259],{"class":437}," or",[300,3261,711],{"class":314},[300,3263,999],{"class":310},[300,3265,528],{"class":314},[300,3267,1232],{"class":437},[300,3269,2996],{"class":357},[300,3271,537],{"class":429},[300,3273,1799],{"class":540},[300,3275,537],{"class":429},[300,3277,3254],{"class":642},[300,3279,3280],{"class":429},"()\n",[300,3282,3283],{"class":302,"line":803},[300,3284,365],{"emptyLinePlaceholder":364},[300,3286,3287],{"class":302,"line":1244},[300,3288,365],{"emptyLinePlaceholder":364},[300,3290,3291,3293,3296],{"class":302,"line":1275},[300,3292,577],{"class":451},[300,3294,3295],{"class":425}," test_loop_detection",[300,3297,2911],{"class":429},[300,3299,3300,3302,3304,3306,3308,3310,3312,3314,3316],{"class":302,"line":1299},[300,3301,2916],{"class":357},[300,3303,438],{"class":437},[300,3305,983],{"class":642},[300,3307,430],{"class":429},[300,3309,1132],{"class":433},[300,3311,438],{"class":437},[300,3313,611],{"class":429},[300,3315,239],{"class":642},[300,3317,2933],{"class":429},[300,3319,3320,3322,3325,3327,3330,3332,3335],{"class":302,"line":1304},[300,3321,679],{"class":346},[300,3323,3324],{"class":357}," i ",[300,3326,685],{"class":346},[300,3328,3329],{"class":499}," range",[300,3331,430],{"class":429},[300,3333,3334],{"class":531},"3",[300,3336,700],{"class":429},[300,3338,3339,3342,3344,3346,3348,3350,3352,3354,3356,3358,3360,3362,3364,3366,3368,3370,3372,3375,3377,3379,3381,3384,3386,3389,3391,3393],{"class":302,"line":1328},[300,3340,3341],{"class":357},"        result ",[300,3343,438],{"class":437},[300,3345,2943],{"class":357},[300,3347,537],{"class":429},[300,3349,2948],{"class":642},[300,3351,430],{"class":429},[300,3353,528],{"class":314},[300,3355,239],{"class":310},[300,3357,528],{"class":314},[300,3359,594],{"class":429},[300,3361,2961],{"class":429},[300,3363,528],{"class":314},[300,3365,250],{"class":310},[300,3367,528],{"class":314},[300,3369,467],{"class":429},[300,3371,711],{"class":314},[300,3373,3374],{"class":310},"1+1",[300,3376,528],{"class":314},[300,3378,2979],{"class":429},[300,3380,525],{"class":451},[300,3382,3383],{"class":310},"\"call-",[300,3385,532],{"class":531},[300,3387,3388],{"class":642},"i",[300,3390,544],{"class":531},[300,3392,528],{"class":310},[300,3394,445],{"class":429},[300,3396,3397],{"class":302,"line":1366},[300,3398,3399],{"class":339},"    # the third call should be caught by the loop detector\n",[300,3401,3402,3404,3406,3408,3410,3412,3414,3416,3418,3420,3422,3424,3426,3428,3430,3432,3434,3436,3438,3440,3442,3445,3447],{"class":302,"line":1371},[300,3403,2938],{"class":357},[300,3405,438],{"class":437},[300,3407,2943],{"class":357},[300,3409,537],{"class":429},[300,3411,2948],{"class":642},[300,3413,430],{"class":429},[300,3415,528],{"class":314},[300,3417,239],{"class":310},[300,3419,528],{"class":314},[300,3421,594],{"class":429},[300,3423,2961],{"class":429},[300,3425,528],{"class":314},[300,3427,250],{"class":310},[300,3429,528],{"class":314},[300,3431,467],{"class":429},[300,3433,711],{"class":314},[300,3435,3374],{"class":310},[300,3437,528],{"class":314},[300,3439,2979],{"class":429},[300,3441,711],{"class":314},[300,3443,3444],{"class":310},"call-3",[300,3446,528],{"class":314},[300,3448,445],{"class":429},[300,3450,3451,3453,3455,3457],{"class":302,"line":1419},[300,3452,2993],{"class":346},[300,3454,2996],{"class":357},[300,3456,537],{"class":429},[300,3458,3001],{"class":540},[300,3460,3461,3463,3465,3468,3470,3472,3474,3476],{"class":302,"line":1440},[300,3462,2993],{"class":346},[300,3464,711],{"class":314},[300,3466,3467],{"class":310},"tool-call loop",[300,3469,528],{"class":314},[300,3471,1232],{"class":437},[300,3473,2996],{"class":357},[300,3475,537],{"class":429},[300,3477,3021],{"class":540},[300,3479,3480],{"class":302,"line":1463},[300,3481,365],{"emptyLinePlaceholder":364},[300,3483,3484],{"class":302,"line":1468},[300,3485,365],{"emptyLinePlaceholder":364},[300,3487,3488,3490,3493],{"class":302,"line":1489},[300,3489,577],{"class":451},[300,3491,3492],{"class":425}," test_happy_path",[300,3494,2911],{"class":429},[300,3496,3497,3499,3501,3503,3505,3507,3509,3511,3513],{"class":302,"line":1514},[300,3498,2916],{"class":357},[300,3500,438],{"class":437},[300,3502,983],{"class":642},[300,3504,430],{"class":429},[300,3506,1132],{"class":433},[300,3508,438],{"class":437},[300,3510,611],{"class":429},[300,3512,239],{"class":642},[300,3514,2933],{"class":429},[300,3516,3517,3519,3521,3523,3525,3527,3529,3531,3533,3535,3537,3539,3541,3543,3545,3547,3549,3551,3553,3555,3557,3559,3561],{"class":302,"line":1524},[300,3518,2938],{"class":357},[300,3520,438],{"class":437},[300,3522,2943],{"class":357},[300,3524,537],{"class":429},[300,3526,2948],{"class":642},[300,3528,430],{"class":429},[300,3530,528],{"class":314},[300,3532,239],{"class":310},[300,3534,528],{"class":314},[300,3536,594],{"class":429},[300,3538,2961],{"class":429},[300,3540,528],{"class":314},[300,3542,250],{"class":310},[300,3544,528],{"class":314},[300,3546,467],{"class":429},[300,3548,711],{"class":314},[300,3550,2974],{"class":310},[300,3552,528],{"class":314},[300,3554,2979],{"class":429},[300,3556,711],{"class":314},[300,3558,2984],{"class":310},[300,3560,528],{"class":314},[300,3562,445],{"class":429},[300,3564,3565,3567,3569,3571,3573],{"class":302,"line":1550},[300,3566,2993],{"class":346},[300,3568,1615],{"class":437},[300,3570,2996],{"class":357},[300,3572,537],{"class":429},[300,3574,3001],{"class":540},[300,3576,3577,3579,3581,3583,3585,3588,3590,3593],{"class":302,"line":1555},[300,3578,2993],{"class":346},[300,3580,2996],{"class":357},[300,3582,537],{"class":429},[300,3584,1799],{"class":540},[300,3586,3587],{"class":437}," ==",[300,3589,711],{"class":314},[300,3591,3592],{"class":310},"4",[300,3594,561],{"class":314},[112,3596,3597],{},"Run it:",[291,3599,3601],{"className":293,"code":3600,"language":295,"meta":296,"style":296},"uv run pytest tests\u002Ftest_registry.py -q\n",[122,3602,3603],{"__ignoreMap":296},[300,3604,3605,3607,3610,3613,3616],{"class":302,"line":303},[300,3606,307],{"class":306},[300,3608,3609],{"class":310}," run",[300,3611,3612],{"class":310}," pytest",[300,3614,3615],{"class":310}," tests\u002Ftest_registry.py",[300,3617,3619],{"class":3618},"stzsN"," -q\n",[112,3621,3622],{},"All five pass. The test suite isn't comprehensive — Chapter 19 will build proper trajectory evals — but it's enough to catch regressions in the registry, which is now the central component of the harness.",[220,3624],{},[223,3626,3628],{"id":3627},"_67-a-second-tool-worth-writing","6.7 A Second Tool Worth Writing",[112,3630,3631,3632,3635],{},"The registry is robust enough now to handle a tool that genuinely has a narrow contract. Let's add ",[122,3633,3634],{},"json_query",", which takes a JSON string and a JSONPath-like expression. It's a good stress test of validation — the schema has two required arguments, both strings, with a specific shape, and the failure modes (invalid JSON, invalid path expression) are ones the validator and the tool share.",[291,3637,3639],{"className":330,"code":3638,"language":332,"meta":296,"style":296},"# src\u002Fharness\u002Ftools\u002Fstd.py (add)\nimport json\n\n@tool(side_effects={\"read\"})\ndef json_query(data: str, path: str) -> str:\n    \"\"\"Query JSON data with a simple dot-path expression.\n\n    data: a JSON string (object or array).\n    path: a dot-separated path; e.g. \"items.0.name\" or \"user.email\".\n          Array indices are integers; object keys are dot-separated.\n\n    Returns the queried value as JSON, or an error string if the path\n    doesn't exist.\n    Side effects: none.\n    \"\"\"\n    obj = json.loads(data)  # will raise on invalid JSON; registry catches it\n    current = obj\n    for part in path.split(\".\"):\n        if isinstance(current, list):\n            current = current[int(part)]\n        elif isinstance(current, dict):\n            if part not in current:\n                raise KeyError(f\"path not found: {part}\")\n            current = current[part]\n        else:\n            raise TypeError(f\"cannot index {type(current).__name__} with {part}\")\n    return json.dumps(current)\n",[122,3640,3641,3646,3652,3656,3681,3713,3720,3724,3729,3734,3739,3743,3748,3753,3758,3763,3786,3796,3822,3840,3863,3880,3895,3920,3934,3941,3982],{"__ignoreMap":296},[300,3642,3643],{"class":302,"line":303},[300,3644,3645],{"class":339},"# src\u002Fharness\u002Ftools\u002Fstd.py (add)\n",[300,3647,3648,3650],{"class":302,"line":343},[300,3649,376],{"class":346},[300,3651,2333],{"class":357},[300,3653,3654],{"class":302,"line":361},[300,3655,365],{"emptyLinePlaceholder":364},[300,3657,3658,3660,3662,3664,3667,3669,3671,3673,3676,3678],{"class":302,"line":368},[300,3659,422],{"class":421},[300,3661,1262],{"class":425},[300,3663,430],{"class":429},[300,3665,3666],{"class":433},"side_effects",[300,3668,438],{"class":437},[300,3670,532],{"class":429},[300,3672,528],{"class":314},[300,3674,3675],{"class":310},"read",[300,3677,528],{"class":314},[300,3679,3680],{"class":429},"})\n",[300,3682,3683,3685,3688,3690,3693,3695,3697,3699,3701,3703,3705,3707,3709,3711],{"class":302,"line":382},[300,3684,577],{"class":451},[300,3686,3687],{"class":425}," json_query",[300,3689,430],{"class":429},[300,3691,3692],{"class":585},"data",[300,3694,467],{"class":429},[300,3696,482],{"class":470},[300,3698,594],{"class":429},[300,3700,793],{"class":585},[300,3702,467],{"class":429},[300,3704,482],{"class":470},[300,3706,509],{"class":429},[300,3708,512],{"class":429},[300,3710,482],{"class":470},[300,3712,458],{"class":429},[300,3714,3715,3717],{"class":302,"line":387},[300,3716,624],{"class":623},[300,3718,3719],{"class":627},"Query JSON data with a simple dot-path expression.\n",[300,3721,3722],{"class":302,"line":395},[300,3723,365],{"emptyLinePlaceholder":364},[300,3725,3726],{"class":302,"line":408},[300,3727,3728],{"class":627},"    data: a JSON string (object or array).\n",[300,3730,3731],{"class":302,"line":413},[300,3732,3733],{"class":627},"    path: a dot-separated path; e.g. \"items.0.name\" or \"user.email\".\n",[300,3735,3736],{"class":302,"line":418},[300,3737,3738],{"class":627},"          Array indices are integers; object keys are dot-separated.\n",[300,3740,3741],{"class":302,"line":448},[300,3742,365],{"emptyLinePlaceholder":364},[300,3744,3745],{"class":302,"line":461},[300,3746,3747],{"class":627},"    Returns the queried value as JSON, or an error string if the path\n",[300,3749,3750],{"class":302,"line":474},[300,3751,3752],{"class":627},"    doesn't exist.\n",[300,3754,3755],{"class":302,"line":488},[300,3756,3757],{"class":627},"    Side effects: none.\n",[300,3759,3760],{"class":302,"line":493},[300,3761,3762],{"class":623},"    \"\"\"\n",[300,3764,3765,3768,3770,3772,3774,3777,3779,3781,3783],{"class":302,"line":519},[300,3766,3767],{"class":357},"    obj ",[300,3769,438],{"class":437},[300,3771,2356],{"class":357},[300,3773,537],{"class":429},[300,3775,3776],{"class":642},"loads",[300,3778,430],{"class":429},[300,3780,3692],{"class":642},[300,3782,509],{"class":429},[300,3784,3785],{"class":339},"  # will raise on invalid JSON; registry catches it\n",[300,3787,3788,3791,3793],{"class":302,"line":564},[300,3789,3790],{"class":357},"    current ",[300,3792,438],{"class":437},[300,3794,3795],{"class":357}," obj\n",[300,3797,3798,3800,3803,3805,3807,3809,3812,3814,3816,3818,3820],{"class":302,"line":569},[300,3799,679],{"class":346},[300,3801,3802],{"class":357}," part ",[300,3804,685],{"class":346},[300,3806,793],{"class":357},[300,3808,537],{"class":429},[300,3810,3811],{"class":642},"split",[300,3813,430],{"class":429},[300,3815,528],{"class":314},[300,3817,537],{"class":310},[300,3819,528],{"class":314},[300,3821,700],{"class":429},[300,3823,3824,3826,3829,3831,3834,3836,3838],{"class":302,"line":574},[300,3825,1222],{"class":346},[300,3827,3828],{"class":499}," isinstance",[300,3830,430],{"class":429},[300,3832,3833],{"class":642},"current",[300,3835,594],{"class":429},[300,3837,608],{"class":470},[300,3839,700],{"class":429},[300,3841,3842,3845,3847,3850,3852,3855,3857,3860],{"class":302,"line":620},[300,3843,3844],{"class":357},"            current ",[300,3846,438],{"class":437},[300,3848,3849],{"class":357}," current",[300,3851,611],{"class":429},[300,3853,3854],{"class":470},"int",[300,3856,430],{"class":429},[300,3858,3859],{"class":642},"part",[300,3861,3862],{"class":429},")]\n",[300,3864,3865,3868,3870,3872,3874,3876,3878],{"class":302,"line":634},[300,3866,3867],{"class":346},"        elif",[300,3869,3828],{"class":499},[300,3871,430],{"class":429},[300,3873,3833],{"class":642},[300,3875,594],{"class":429},[300,3877,591],{"class":470},[300,3879,700],{"class":429},[300,3881,3882,3885,3887,3889,3891,3893],{"class":302,"line":653},[300,3883,3884],{"class":346},"            if",[300,3886,3802],{"class":357},[300,3888,1427],{"class":437},[300,3890,1232],{"class":437},[300,3892,3849],{"class":357},[300,3894,458],{"class":429},[300,3896,3897,3900,3903,3905,3907,3910,3912,3914,3916,3918],{"class":302,"line":676},[300,3898,3899],{"class":346},"                raise",[300,3901,3902],{"class":470}," KeyError",[300,3904,430],{"class":429},[300,3906,731],{"class":451},[300,3908,3909],{"class":310},"\"path not found: ",[300,3911,532],{"class":531},[300,3913,3859],{"class":642},[300,3915,544],{"class":531},[300,3917,528],{"class":310},[300,3919,445],{"class":429},[300,3921,3922,3924,3926,3928,3930,3932],{"class":302,"line":703},[300,3923,3844],{"class":357},[300,3925,438],{"class":437},[300,3927,3849],{"class":357},[300,3929,611],{"class":429},[300,3931,3859],{"class":357},[300,3933,1486],{"class":429},[300,3935,3936,3939],{"class":302,"line":763},[300,3937,3938],{"class":346},"        else",[300,3940,458],{"class":429},[300,3942,3943,3945,3948,3950,3952,3955,3957,3959,3961,3963,3965,3967,3969,3972,3974,3976,3978,3980],{"class":302,"line":803},[300,3944,1247],{"class":346},[300,3946,3947],{"class":470}," TypeError",[300,3949,430],{"class":429},[300,3951,731],{"class":451},[300,3953,3954],{"class":310},"\"cannot index ",[300,3956,532],{"class":531},[300,3958,1731],{"class":470},[300,3960,430],{"class":429},[300,3962,3833],{"class":642},[300,3964,1739],{"class":429},[300,3966,1742],{"class":350},[300,3968,544],{"class":531},[300,3970,3971],{"class":310}," with ",[300,3973,532],{"class":531},[300,3975,3859],{"class":642},[300,3977,544],{"class":531},[300,3979,528],{"class":310},[300,3981,445],{"class":429},[300,3983,3984,3986,3988,3990,3992,3994,3996],{"class":302,"line":1244},[300,3985,806],{"class":346},[300,3987,2356],{"class":357},[300,3989,537],{"class":429},[300,3991,2361],{"class":642},[300,3993,430],{"class":429},[300,3995,3833],{"class":642},[300,3997,445],{"class":429},[112,3999,4000],{},"Now the registry has five tools. Run them through the loop against your preferred provider and observe: when the model passes malformed arguments, the registry's error message arrives structured, the model corrects, the next call works. Most of the time.",[220,4002],{},[223,4004,4006],{"id":4005},"_68-what-the-registry-still-doesnt-do","6.8 What the Registry Still Doesn't Do",[112,4008,4009],{},"Three things worth naming now, each of which gets a chapter later.",[112,4011,4012,4015,4016,4018],{},[233,4013,4014],{},"No permissions."," Anyone can call ",[122,4017,264],{}," on any path. Chapter 14 builds the permission layer.",[112,4020,4021,4024],{},[233,4022,4023],{},"No observability."," The registry logs nothing; a failed call is invisible in post-hoc analysis. Chapter 18 adds OpenTelemetry spans per dispatch.",[112,4026,4027,4030],{},[233,4028,4029],{},"No cost accounting."," The registry doesn't know — or care — how much the model spent to make each call. Chapter 20 wires in budget-aware dispatch.",[112,4032,4033],{},"Each of these slots in cleanly because the registry is the sole dispatch point. We didn't have to thread permission checks through every tool; we didn't have to teach each tool to log. The registry is the interception layer, by design, and the book's cost of adding these features is proportional to what they do — not to how many tools we have.",[220,4035],{},[223,4037,4039],{"id":4038},"_69-commit","6.9 Commit",[291,4041,4043],{"className":293,"code":4042,"language":295,"meta":296,"style":296},"git add -A && git commit -m \"ch06: schema validation and loop detection at the registry\"\ngit tag ch06-safety\n",[122,4044,4045,4074],{"__ignoreMap":296},[300,4046,4047,4050,4052,4055,4058,4061,4064,4067,4069,4072],{"class":302,"line":303},[300,4048,4049],{"class":306},"git",[300,4051,311],{"class":310},[300,4053,4054],{"class":3618}," -A",[300,4056,4057],{"class":429}," &&",[300,4059,4060],{"class":306}," git",[300,4062,4063],{"class":310}," commit",[300,4065,4066],{"class":3618}," -m",[300,4068,711],{"class":314},[300,4070,4071],{"class":310},"ch06: schema validation and loop detection at the registry",[300,4073,561],{"class":314},[300,4075,4076,4078,4081],{"class":302,"line":343},[300,4077,4049],{"class":306},[300,4079,4080],{"class":310}," tag",[300,4082,4083],{"class":310}," ch06-safety\n",[223,4085,4087],{"id":4086},"_610-try-it-yourself","6.10 Try It Yourself",[4089,4090,4091,4098,4104],"ol",{},[4092,4093,4094,4097],"li",{},[233,4095,4096],{},"Find a legitimate loop."," Construct a prompt where the agent genuinely needs to retry the same tool with the same arguments — for example, polling a tool that represents a slow operation. Does the loop detector fire? If so, is that the right behavior? How would you distinguish a polling loop from a stuck loop?",[4092,4099,4100,4103],{},[233,4101,4102],{},"Measure the recovery rate."," Run your harness against thirty prompts that commonly trigger malformed tool calls. Log how often the model recovers on the next turn after receiving a validation error versus how often it gives up. That number is a proxy for how well-designed your schemas and error messages are.",[4092,4105,4106,4109,4110,4112,4113,4116,4117,4120],{},[233,4107,4108],{},"Write a test for the close-match suggestion."," Prove that renaming ",[122,4111,239],{}," to ",[122,4114,4115],{},"calculate"," breaks the ",[122,4118,4119],{},"Did you mean"," suggestion you hard-coded. What would you change so that the test stays green regardless of the specific name? Your answer is a sketch of what a larger test suite needs.",[220,4122],{},[4124,4125,4126,4129],"what-you-understand",{},[112,4127,4128],{},"Two of the Chapter 2 breaks are now handled at the registry level: malformed tool arguments and tool-call loops. Unknown tools get close-match suggestions, validation errors are structured and readable, and repeat-identical calls are detected before they run. The registry has become the interception point the permissions, observability, and budget layers will all hang off.",[112,4130,4131],{},"What's still missing. Break 5 from Chapter 2 — tool outputs overwhelming the transcript — is the one failure mode we haven't touched. A loop that writes a 200KB blob to the transcript on turn two is still dead on turn four. Chapters 7 through 11 are the book's sustained answer to this: Chapter 7 makes the context window a resource we can see, Chapter 8 compacts when we're filling it too fast, Chapter 9 introduces external state so the transcript never has to hold what it doesn't need, Chapter 10 adds retrieval for the parts we want to pull in on demand, and Chapter 11 closes the loop with deliberate tool design that avoids producing context-bloating outputs in the first place.",[4133,4134,4135],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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 .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--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 .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--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 .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 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 .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":296,"searchDepth":343,"depth":343,"links":4137},[4138,4139,4140,4141,4142,4143,4144,4145,4146,4147],{"id":225,"depth":343,"text":226},{"id":326,"depth":343,"text":327},{"id":844,"depth":343,"text":845},{"id":2761,"depth":343,"text":2762},{"id":2807,"depth":343,"text":2808},{"id":2838,"depth":343,"text":2839},{"id":3627,"depth":343,"text":3628},{"id":4005,"depth":343,"text":4006},{"id":4038,"depth":343,"text":4039},{"id":4086,"depth":343,"text":4087},"md",{},null,{"title":34,"description":117},"50S1KrGn8NTpLhJD-ciWy_BkRcajpdrG3TJF5Xz8xRo",[4154,4156],{"title":30,"path":31,"stem":32,"description":4155,"children":-1},"Previously: tools are first-class and dispatch through a registry. The loop is tight, typed, and provider-agnostic. What it doesn't yet do is stream output to the user, stop cleanly when the user changes their mind, or survive a transient network failure.",{"title":38,"path":39,"stem":40,"description":4157,"children":-1},"Previously: the registry validates arguments and detects loops. All five breaks from Chapter 2 are handled — except Break 5, which is now the subject of the next five chapters. A tool that returns 200KB of JSON still poisons the loop on turn four.",1776848984224]