[{"data":1,"prerenderedAt":5496},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Ftool-protocol":102,"surround-\u002Fchapters\u002Ftool-protocol":5491},[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":26,"body":104,"description":5485,"extension":5486,"meta":5487,"navigation":5488,"path":27,"seo":5489,"stem":28,"__hash__":5490},"content\u002F2.chapters\u002F04.tool-protocol.md",{"type":105,"value":106,"toc":5474},"minimark",[107,111,123,126,133,150,153,215,218,223,234,257,263,277,283,285,289,770,775,806,817,819,823,829,2124,2161,2164,2529,2532,2534,2538,2541,3343,3346,3349,3368,3382,3384,3388,3395,3930,3933,3935,3939,3942,4897,4900,4912,4927,4933,4935,4939,4942,5312,5315,5318,5364,5366,5370,5383,5386,5397,5400,5402,5406,5445,5447,5470],[108,109,26],"h1",{"id":110},"chapter-4-the-tool-protocol",[112,113,114],"p",{},[115,116,117,118,122],"em",{},"Previously: typed messages, typed transcripts, three provider adapters. The loop no longer crashes on unknown tools, but its fix is ad hoc — a ",[119,120,121],"code",{},"try\u002Fexcept"," in the dispatch. We owe ourselves a proper tool abstraction.",[112,124,125],{},"A tool is a contract. It has a name a model can guess, a description a model can read, a schema a model must match, a callable we execute on a match, and a side-effect profile that determines who has to ask permission before we run it. The tools you ship are the surface through which your agent reaches into the world, and every well-documented production failure in this space — hallucinated tool calls, output truncation, tool cliff, prompt injection — is a failure of the tool surface.",[112,127,128,129,132],{},"This chapter builds the ",[119,130,131],{},"Tool"," abstraction we'll use for the rest of the book. By the end, three things are true:",[134,135,136,140,147],"ol",{},[137,138,139],"li",{},"Tools carry their own schema, description, and side-effect declaration.",[137,141,142,143,146],{},"A ",[119,144,145],{},"ToolRegistry"," dispatches calls and rejects unknown names before they reach your code.",[137,148,149],{},"The tool schema sent to the provider is derived from the tool, not hand-maintained.",[112,151,152],{},"We still don't validate argument shapes before dispatch — that's Chapter 6. We're building the object; Chapter 6 attaches the validator.",[154,155,159,206],"figure",{"className":156},[157,158],"not-prose","my-8",[160,161,168,179,184,192,195,199,202],"div",{"className":162},[163,164,165,166,167],"flex","items-center","justify-center","gap-2","flex-wrap",[160,169,178],{"className":170},[171,172,173,174,175,176,177],"border","border-default","rounded-full","py-2","px-4","text-sm","text-default","Model emits tool_call",[160,180,183],{"className":181},[182],"text-muted","→",[160,185,191],{"className":186},[187,188,173,174,175,176,189,190],"border-2","border-primary","font-semibold","text-primary","Registry validates schema",[160,193,183],{"className":194},[182],[160,196,198],{"className":197},[171,172,173,174,175,176,177],"Tool.run",[160,200,183],{"className":201},[182],[160,203,205],{"className":204},[171,172,173,174,175,176,177],"Structured result",[207,208,214],"figcaption",{"className":209},[210,182,211,212,213],"text-xs","mt-3","text-center","italic","Four steps from model output to tool result. The validator (amber) is the load-bearing step — it rejects unknown names and bad shapes before your code runs.",[216,217],"hr",{},[219,220,222],"h2",{"id":221},"_41-the-tool-as-an-interface-not-a-function","4.1 The Tool as an Interface, Not a Function",[112,224,225,226,233],{},"The research arc here is short but worth naming. Schick et al.'s 2023 \"Toolformer: Language Models Can Teach Themselves to Use Tools\" was the paper that opened the tool-use research area as a distinct subfield — it demonstrated that language models could learn to call external APIs (calculators, translators, search) in the middle of text generation, which in turn established tool use as a research problem rather than a prompting trick. Yang et al.'s 2024 ",[227,228,232],"a",{"href":229,"rel":230},"https:\u002F\u002Farxiv.org\u002Fabs\u002F2405.15793",[231],"nofollow","\"SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering\""," made the sharp design point that followed from Toolformer's opening: tool design is interface design for a non-human user with very specific cognitive constraints. A model reads your description before it decides what to call, it doesn't remember your README, it can't ask a clarifying question, and it infers behavior from names. Three consequences follow:",[112,235,236,240,241,244,245,248,249,252,253,256],{},[237,238,239],"strong",{},"Names are semantic."," ",[119,242,243],{},"send_message"," is very different from ",[119,246,247],{},"send_email",". ",[119,250,251],{},"read_file"," implies non-destructive; ",[119,254,255],{},"open_file"," is ambiguous. If two tools could be confused, they will be.",[112,258,259,262],{},[237,260,261],{},"Descriptions are contracts."," \"Sends a notification\" doesn't tell a model whether the notification costs money, reaches production customers, is rate-limited, or requires a subject line. A well-specified tool description covers what it does, what it does not do, what it requires as preconditions, and what side effects it produces. \"Sends a notification\" is a bug report waiting to happen.",[112,264,265,268,269,272,273,276],{},[237,266,267],{},"Schemas are hard edges."," A tool with an optional argument that's actually required, or a string argument that should have been an enum, gets misused in proportion to how soft its edges are. Every ",[119,270,271],{},"string"," where an ",[119,274,275],{},"enum"," would fit is a chance for the model to be creative in ways you don't want.",[112,278,279,280,282],{},"We'll encode all three — name, description, schema — as first-class fields of ",[119,281,131],{},". Side effects get a field too, because the permission layer in Chapter 14 will need to gate on them.",[216,284],{},[219,286,288],{"id":287},"_42-the-tool-dataclass","4.2 The Tool Dataclass",[290,291,296],"pre",{"className":292,"code":293,"language":294,"meta":295,"style":295},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Ftools\u002Fbase.py\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass, field\nfrom typing import Callable, Literal\n\n\nSideEffect = Literal[\"read\", \"write\", \"network\", \"mutate\"]\n\n\n@dataclass(frozen=True)\nclass Tool:\n    \"\"\"A callable exposed to the model.\n\n    name        -- stable identifier the model calls by.\n    description -- contract text the model reads. Must state scope,\n                   preconditions, and side effects in plain English.\n    input_schema -- JSON Schema for the arguments dict.\n    run         -- the callable. Accepts kwargs matching the schema;\n                   returns a string (what the model will see as the result).\n    side_effects -- declared effect tags. Used by the permission layer.\n    \"\"\"\n    name: str\n    description: str\n    input_schema: dict\n    run: Callable[..., str]\n    side_effects: frozenset[SideEffect] = field(default_factory=frozenset)\n\n    def schema_for_provider(self) -> dict:\n        \"\"\"The dict shape providers expect (Anthropic-flavored).\"\"\"\n        return {\n            \"name\": self.name,\n            \"description\": self.description,\n            \"input_schema\": self.input_schema,\n        }\n","python","",[119,297,298,307,325,332,353,371,376,381,438,443,448,475,489,500,505,511,517,523,529,535,541,547,553,566,576,587,609,647,652,678,690,699,724,744,764],{"__ignoreMap":295},[299,300,303],"span",{"class":301,"line":302},"line",1,[299,304,306],{"class":305},"sutJx","# src\u002Fharness\u002Ftools\u002Fbase.py\n",[299,308,310,314,318,321],{"class":301,"line":309},2,[299,311,313],{"class":312},"sVHd0","from",[299,315,317],{"class":316},"s_hVV"," __future__",[299,319,320],{"class":312}," import",[299,322,324],{"class":323},"su5hD"," annotations\n",[299,326,328],{"class":301,"line":327},3,[299,329,331],{"emptyLinePlaceholder":330},true,"\n",[299,333,335,337,340,343,346,350],{"class":301,"line":334},4,[299,336,313],{"class":312},[299,338,339],{"class":323}," dataclasses ",[299,341,342],{"class":312},"import",[299,344,345],{"class":323}," dataclass",[299,347,349],{"class":348},"sP7_E",",",[299,351,352],{"class":323}," field\n",[299,354,356,358,361,363,366,368],{"class":301,"line":355},5,[299,357,313],{"class":312},[299,359,360],{"class":323}," typing ",[299,362,342],{"class":312},[299,364,365],{"class":323}," Callable",[299,367,349],{"class":348},[299,369,370],{"class":323}," Literal\n",[299,372,374],{"class":301,"line":373},6,[299,375,331],{"emptyLinePlaceholder":330},[299,377,379],{"class":301,"line":378},7,[299,380,331],{"emptyLinePlaceholder":330},[299,382,384,387,391,394,397,401,405,407,409,412,415,417,419,421,424,426,428,430,433,435],{"class":301,"line":383},8,[299,385,386],{"class":323},"SideEffect ",[299,388,390],{"class":389},"smGrS","=",[299,392,393],{"class":323}," Literal",[299,395,396],{"class":348},"[",[299,398,400],{"class":399},"sjJ54","\"",[299,402,404],{"class":403},"s_sjI","read",[299,406,400],{"class":399},[299,408,349],{"class":348},[299,410,411],{"class":399}," \"",[299,413,414],{"class":403},"write",[299,416,400],{"class":399},[299,418,349],{"class":348},[299,420,411],{"class":399},[299,422,423],{"class":403},"network",[299,425,400],{"class":399},[299,427,349],{"class":348},[299,429,411],{"class":399},[299,431,432],{"class":403},"mutate",[299,434,400],{"class":399},[299,436,437],{"class":348},"]\n",[299,439,441],{"class":301,"line":440},9,[299,442,331],{"emptyLinePlaceholder":330},[299,444,446],{"class":301,"line":445},10,[299,447,331],{"emptyLinePlaceholder":330},[299,449,451,455,459,462,466,468,472],{"class":301,"line":450},11,[299,452,454],{"class":453},"stp6e","@",[299,456,458],{"class":457},"sGLFI","dataclass",[299,460,461],{"class":348},"(",[299,463,465],{"class":464},"s99_P","frozen",[299,467,390],{"class":389},[299,469,471],{"class":470},"s39Yj","True",[299,473,474],{"class":348},")\n",[299,476,478,482,486],{"class":301,"line":477},12,[299,479,481],{"class":480},"sbsja","class",[299,483,485],{"class":484},"sbgvK"," Tool",[299,487,488],{"class":348},":\n",[299,490,492,496],{"class":301,"line":491},13,[299,493,495],{"class":494},"s2W-s","    \"\"\"",[299,497,499],{"class":498},"sithA","A callable exposed to the model.\n",[299,501,503],{"class":301,"line":502},14,[299,504,331],{"emptyLinePlaceholder":330},[299,506,508],{"class":301,"line":507},15,[299,509,510],{"class":498},"    name        -- stable identifier the model calls by.\n",[299,512,514],{"class":301,"line":513},16,[299,515,516],{"class":498},"    description -- contract text the model reads. Must state scope,\n",[299,518,520],{"class":301,"line":519},17,[299,521,522],{"class":498},"                   preconditions, and side effects in plain English.\n",[299,524,526],{"class":301,"line":525},18,[299,527,528],{"class":498},"    input_schema -- JSON Schema for the arguments dict.\n",[299,530,532],{"class":301,"line":531},19,[299,533,534],{"class":498},"    run         -- the callable. Accepts kwargs matching the schema;\n",[299,536,538],{"class":301,"line":537},20,[299,539,540],{"class":498},"                   returns a string (what the model will see as the result).\n",[299,542,544],{"class":301,"line":543},21,[299,545,546],{"class":498},"    side_effects -- declared effect tags. Used by the permission layer.\n",[299,548,550],{"class":301,"line":549},22,[299,551,552],{"class":494},"    \"\"\"\n",[299,554,556,559,562],{"class":301,"line":555},23,[299,557,558],{"class":323},"    name",[299,560,561],{"class":348},":",[299,563,565],{"class":564},"sZMiF"," str\n",[299,567,569,572,574],{"class":301,"line":568},24,[299,570,571],{"class":323},"    description",[299,573,561],{"class":348},[299,575,565],{"class":564},[299,577,579,582,584],{"class":301,"line":578},25,[299,580,581],{"class":323},"    input_schema",[299,583,561],{"class":348},[299,585,586],{"class":564}," dict\n",[299,588,590,593,595,597,599,602,604,607],{"class":301,"line":589},26,[299,591,592],{"class":323},"    run",[299,594,561],{"class":348},[299,596,365],{"class":323},[299,598,396],{"class":348},[299,600,601],{"class":316},"...",[299,603,349],{"class":348},[299,605,606],{"class":564}," str",[299,608,437],{"class":348},[299,610,612,615,617,620,622,625,628,631,635,637,640,642,645],{"class":301,"line":611},27,[299,613,614],{"class":323},"    side_effects",[299,616,561],{"class":348},[299,618,619],{"class":323}," frozenset",[299,621,396],{"class":348},[299,623,624],{"class":323},"SideEffect",[299,626,627],{"class":348},"]",[299,629,630],{"class":389}," =",[299,632,634],{"class":633},"slqww"," field",[299,636,461],{"class":348},[299,638,639],{"class":464},"default_factory",[299,641,390],{"class":389},[299,643,644],{"class":564},"frozenset",[299,646,474],{"class":348},[299,648,650],{"class":301,"line":649},28,[299,651,331],{"emptyLinePlaceholder":330},[299,653,655,658,661,663,667,670,673,676],{"class":301,"line":654},29,[299,656,657],{"class":480},"    def",[299,659,660],{"class":457}," schema_for_provider",[299,662,461],{"class":348},[299,664,666],{"class":665},"smCYv","self",[299,668,669],{"class":348},")",[299,671,672],{"class":348}," ->",[299,674,675],{"class":564}," dict",[299,677,488],{"class":348},[299,679,681,684,687],{"class":301,"line":680},30,[299,682,683],{"class":494},"        \"\"\"",[299,685,686],{"class":498},"The dict shape providers expect (Anthropic-flavored).",[299,688,689],{"class":494},"\"\"\"\n",[299,691,693,696],{"class":301,"line":692},31,[299,694,695],{"class":312},"        return",[299,697,698],{"class":348}," {\n",[299,700,702,705,708,710,712,715,718,721],{"class":301,"line":701},32,[299,703,704],{"class":399},"            \"",[299,706,707],{"class":403},"name",[299,709,400],{"class":399},[299,711,561],{"class":348},[299,713,714],{"class":316}," self",[299,716,717],{"class":348},".",[299,719,707],{"class":720},"skxfh",[299,722,723],{"class":348},",\n",[299,725,727,729,732,734,736,738,740,742],{"class":301,"line":726},33,[299,728,704],{"class":399},[299,730,731],{"class":403},"description",[299,733,400],{"class":399},[299,735,561],{"class":348},[299,737,714],{"class":316},[299,739,717],{"class":348},[299,741,731],{"class":720},[299,743,723],{"class":348},[299,745,747,749,752,754,756,758,760,762],{"class":301,"line":746},34,[299,748,704],{"class":399},[299,750,751],{"class":403},"input_schema",[299,753,400],{"class":399},[299,755,561],{"class":348},[299,757,714],{"class":316},[299,759,717],{"class":348},[299,761,751],{"class":720},[299,763,723],{"class":348},[299,765,767],{"class":301,"line":766},35,[299,768,769],{"class":348},"        }\n",[112,771,772,773,561],{},"Four tags in ",[119,774,624],{},[776,777,778,785,792,799],"ul",{},[137,779,780,784],{},[237,781,782],{},[119,783,404],{}," — the tool only reads state. Safe to retry, safe to run in parallel, never needs an idempotency key.",[137,786,787,791],{},[237,788,789],{},[119,790,414],{}," — modifies local state (files, scratchpad). Needs write ownership; usually safe to retry with idempotency.",[137,793,794,798],{},[237,795,796],{},[119,797,423],{}," — reaches an external service. Needs egress permission; retries require vendor-side idempotency.",[137,800,801,805],{},[237,802,803],{},[119,804,432],{}," — has externally-visible irreversible side effects (sending an email, charging a card, deleting a row). The permission layer may require human approval; retries need real idempotency keys.",[112,807,808,809,812,813,816],{},"These tags aren't load-bearing yet. In Chapter 14, the ",[119,810,811],{},"PermissionManager"," uses them to decide what gets gated. In Chapter 21, the ",[119,814,815],{},"Checkpointer"," uses them to decide what needs idempotency protection. We declare them now so every tool we write has them from the start.",[216,818],{},[219,820,822],{"id":821},"_43-the-decorator","4.3 The Decorator",[112,824,825,826,828],{},"Building ",[119,827,131],{}," instances by hand every time is tedious, and the boilerplate obscures what's actually specific to each tool. A decorator gives us lighter ergonomics and lets the tool's function signature, docstring, and type hints do most of the work:",[290,830,832],{"className":292,"code":831,"language":294,"meta":295,"style":295},"# src\u002Fharness\u002Ftools\u002Fdecorator.py\nfrom __future__ import annotations\n\nimport inspect\nimport typing\nfrom typing import Callable, get_type_hints\n\nfrom .base import SideEffect, Tool\n\n\ndef tool(\n    name: str | None = None,\n    description: str | None = None,\n    side_effects: set[SideEffect] | frozenset[SideEffect] = frozenset(),\n) -> Callable[[Callable[..., str]], Tool]:\n    \"\"\"Turn a plain function into a Tool.\n\n    The input schema is inferred from type hints. The function's docstring\n    is used as the description if not provided explicitly.\n    \"\"\"\n    def wrap(fn: Callable[..., str]) -> Tool:\n        actual_name = name or fn.__name__\n        actual_description = description or (fn.__doc__ or \"\").strip()\n        if not actual_description:\n            raise ValueError(f\"tool {actual_name!r} has no description\")\n\n        schema = _schema_from_signature(fn)\n\n        return Tool(\n            name=actual_name,\n            description=actual_description,\n            input_schema=schema,\n            run=fn,\n            side_effects=frozenset(side_effects),\n        )\n    return wrap\n\n\ndef _schema_from_signature(fn: Callable[..., str]) -> dict:\n    sig = inspect.signature(fn)\n    hints = get_type_hints(fn)\n    properties: dict[str, dict] = {}\n    required: list[str] = []\n    for pname, param in sig.parameters.items():\n        if pname == \"self\":\n            continue\n        hint = hints.get(pname, str)\n        properties[pname] = _type_to_schema(hint)\n        if param.default is inspect.Parameter.empty:\n            required.append(pname)\n    return {\n        \"type\": \"object\",\n        \"properties\": properties,\n        \"required\": required,\n    }\n\n\ndef _type_to_schema(t: type) -> dict:\n    origin = typing.get_origin(t)\n    if origin is typing.Union or origin is types_union():\n        args = [a for a in typing.get_args(t) if a is not type(None)]\n        if len(args) == 1:\n            return _type_to_schema(args[0])\n    if t is str:\n        return {\"type\": \"string\"}\n    if t is int:\n        return {\"type\": \"integer\"}\n    if t is float:\n        return {\"type\": \"number\"}\n    if t is bool:\n        return {\"type\": \"boolean\"}\n    if origin is list:\n        return {\"type\": \"array\", \"items\": _type_to_schema(typing.get_args(t)[0])}\n    return {\"type\": \"string\"}  # fallback\n\n\ndef types_union():\n    import types\n    return types.UnionType\n",[119,833,834,839,849,853,860,867,882,886,906,910,914,925,946,964,996,1026,1033,1037,1042,1047,1051,1084,1105,1142,1155,1189,1193,1209,1213,1221,1232,1244,1256,1267,1284,1289,1298,1303,1308,1339,1361,1378,1404,1426,1459,1478,1484,1511,1535,1565,1582,1589,1611,1628,1645,1651,1656,1661,1686,1708,1738,1792,1816,1836,1850,1875,1889,1913,1927,1951,1965,1989,2002,2057,2083,2088,2093,2102,2111],{"__ignoreMap":295},[299,835,836],{"class":301,"line":302},[299,837,838],{"class":305},"# src\u002Fharness\u002Ftools\u002Fdecorator.py\n",[299,840,841,843,845,847],{"class":301,"line":309},[299,842,313],{"class":312},[299,844,317],{"class":316},[299,846,320],{"class":312},[299,848,324],{"class":323},[299,850,851],{"class":301,"line":327},[299,852,331],{"emptyLinePlaceholder":330},[299,854,855,857],{"class":301,"line":334},[299,856,342],{"class":312},[299,858,859],{"class":323}," inspect\n",[299,861,862,864],{"class":301,"line":355},[299,863,342],{"class":312},[299,865,866],{"class":323}," typing\n",[299,868,869,871,873,875,877,879],{"class":301,"line":373},[299,870,313],{"class":312},[299,872,360],{"class":323},[299,874,342],{"class":312},[299,876,365],{"class":323},[299,878,349],{"class":348},[299,880,881],{"class":323}," get_type_hints\n",[299,883,884],{"class":301,"line":378},[299,885,331],{"emptyLinePlaceholder":330},[299,887,888,890,893,896,898,901,903],{"class":301,"line":383},[299,889,313],{"class":312},[299,891,892],{"class":348}," .",[299,894,895],{"class":323},"base ",[299,897,342],{"class":312},[299,899,900],{"class":323}," SideEffect",[299,902,349],{"class":348},[299,904,905],{"class":323}," Tool\n",[299,907,908],{"class":301,"line":440},[299,909,331],{"emptyLinePlaceholder":330},[299,911,912],{"class":301,"line":445},[299,913,331],{"emptyLinePlaceholder":330},[299,915,916,919,922],{"class":301,"line":450},[299,917,918],{"class":480},"def",[299,920,921],{"class":457}," tool",[299,923,924],{"class":348},"(\n",[299,926,927,930,932,934,937,940,942,944],{"class":301,"line":477},[299,928,558],{"class":929},"sFwrP",[299,931,561],{"class":348},[299,933,606],{"class":564},[299,935,936],{"class":389}," |",[299,938,939],{"class":470}," None",[299,941,630],{"class":389},[299,943,939],{"class":470},[299,945,723],{"class":348},[299,947,948,950,952,954,956,958,960,962],{"class":301,"line":491},[299,949,571],{"class":929},[299,951,561],{"class":348},[299,953,606],{"class":564},[299,955,936],{"class":389},[299,957,939],{"class":470},[299,959,630],{"class":389},[299,961,939],{"class":470},[299,963,723],{"class":348},[299,965,966,968,970,973,975,977,979,981,983,985,987,989,991,993],{"class":301,"line":502},[299,967,614],{"class":929},[299,969,561],{"class":348},[299,971,972],{"class":323}," set",[299,974,396],{"class":348},[299,976,624],{"class":323},[299,978,627],{"class":348},[299,980,936],{"class":389},[299,982,619],{"class":323},[299,984,396],{"class":348},[299,986,624],{"class":323},[299,988,627],{"class":348},[299,990,630],{"class":389},[299,992,619],{"class":564},[299,994,995],{"class":348},"(),\n",[299,997,998,1000,1002,1004,1007,1010,1012,1014,1016,1018,1021,1023],{"class":301,"line":507},[299,999,669],{"class":348},[299,1001,672],{"class":348},[299,1003,365],{"class":323},[299,1005,1006],{"class":348},"[[",[299,1008,1009],{"class":323},"Callable",[299,1011,396],{"class":348},[299,1013,601],{"class":316},[299,1015,349],{"class":348},[299,1017,606],{"class":564},[299,1019,1020],{"class":348},"]],",[299,1022,485],{"class":323},[299,1024,1025],{"class":348},"]:\n",[299,1027,1028,1030],{"class":301,"line":513},[299,1029,495],{"class":494},[299,1031,1032],{"class":498},"Turn a plain function into a Tool.\n",[299,1034,1035],{"class":301,"line":519},[299,1036,331],{"emptyLinePlaceholder":330},[299,1038,1039],{"class":301,"line":525},[299,1040,1041],{"class":498},"    The input schema is inferred from type hints. The function's docstring\n",[299,1043,1044],{"class":301,"line":531},[299,1045,1046],{"class":498},"    is used as the description if not provided explicitly.\n",[299,1048,1049],{"class":301,"line":537},[299,1050,552],{"class":494},[299,1052,1053,1055,1058,1060,1063,1065,1067,1069,1071,1073,1075,1078,1080,1082],{"class":301,"line":543},[299,1054,657],{"class":480},[299,1056,1057],{"class":457}," wrap",[299,1059,461],{"class":348},[299,1061,1062],{"class":929},"fn",[299,1064,561],{"class":348},[299,1066,365],{"class":323},[299,1068,396],{"class":348},[299,1070,601],{"class":316},[299,1072,349],{"class":348},[299,1074,606],{"class":564},[299,1076,1077],{"class":348},"])",[299,1079,672],{"class":348},[299,1081,485],{"class":323},[299,1083,488],{"class":348},[299,1085,1086,1089,1091,1094,1097,1100,1102],{"class":301,"line":549},[299,1087,1088],{"class":323},"        actual_name ",[299,1090,390],{"class":389},[299,1092,1093],{"class":323}," name ",[299,1095,1096],{"class":389},"or",[299,1098,1099],{"class":323}," fn",[299,1101,717],{"class":348},[299,1103,1104],{"class":316},"__name__\n",[299,1106,1107,1110,1112,1115,1117,1120,1122,1124,1127,1130,1133,1136,1139],{"class":301,"line":555},[299,1108,1109],{"class":323},"        actual_description ",[299,1111,390],{"class":389},[299,1113,1114],{"class":323}," description ",[299,1116,1096],{"class":389},[299,1118,1119],{"class":348}," (",[299,1121,1062],{"class":323},[299,1123,717],{"class":348},[299,1125,1126],{"class":316},"__doc__",[299,1128,1129],{"class":389}," or",[299,1131,1132],{"class":399}," \"\"",[299,1134,1135],{"class":348},").",[299,1137,1138],{"class":633},"strip",[299,1140,1141],{"class":348},"()\n",[299,1143,1144,1147,1150,1153],{"class":301,"line":568},[299,1145,1146],{"class":312},"        if",[299,1148,1149],{"class":389}," not",[299,1151,1152],{"class":323}," actual_description",[299,1154,488],{"class":348},[299,1156,1157,1160,1163,1165,1168,1171,1175,1178,1181,1184,1187],{"class":301,"line":578},[299,1158,1159],{"class":312},"            raise",[299,1161,1162],{"class":564}," ValueError",[299,1164,461],{"class":348},[299,1166,1167],{"class":480},"f",[299,1169,1170],{"class":403},"\"tool ",[299,1172,1174],{"class":1173},"srdBf","{",[299,1176,1177],{"class":633},"actual_name",[299,1179,1180],{"class":480},"!r",[299,1182,1183],{"class":1173},"}",[299,1185,1186],{"class":403}," has no description\"",[299,1188,474],{"class":348},[299,1190,1191],{"class":301,"line":589},[299,1192,331],{"emptyLinePlaceholder":330},[299,1194,1195,1198,1200,1203,1205,1207],{"class":301,"line":611},[299,1196,1197],{"class":323},"        schema ",[299,1199,390],{"class":389},[299,1201,1202],{"class":633}," _schema_from_signature",[299,1204,461],{"class":348},[299,1206,1062],{"class":633},[299,1208,474],{"class":348},[299,1210,1211],{"class":301,"line":649},[299,1212,331],{"emptyLinePlaceholder":330},[299,1214,1215,1217,1219],{"class":301,"line":654},[299,1216,695],{"class":312},[299,1218,485],{"class":633},[299,1220,924],{"class":348},[299,1222,1223,1226,1228,1230],{"class":301,"line":680},[299,1224,1225],{"class":464},"            name",[299,1227,390],{"class":389},[299,1229,1177],{"class":633},[299,1231,723],{"class":348},[299,1233,1234,1237,1239,1242],{"class":301,"line":692},[299,1235,1236],{"class":464},"            description",[299,1238,390],{"class":389},[299,1240,1241],{"class":633},"actual_description",[299,1243,723],{"class":348},[299,1245,1246,1249,1251,1254],{"class":301,"line":701},[299,1247,1248],{"class":464},"            input_schema",[299,1250,390],{"class":389},[299,1252,1253],{"class":633},"schema",[299,1255,723],{"class":348},[299,1257,1258,1261,1263,1265],{"class":301,"line":726},[299,1259,1260],{"class":464},"            run",[299,1262,390],{"class":389},[299,1264,1062],{"class":633},[299,1266,723],{"class":348},[299,1268,1269,1272,1274,1276,1278,1281],{"class":301,"line":746},[299,1270,1271],{"class":464},"            side_effects",[299,1273,390],{"class":389},[299,1275,644],{"class":564},[299,1277,461],{"class":348},[299,1279,1280],{"class":633},"side_effects",[299,1282,1283],{"class":348},"),\n",[299,1285,1286],{"class":301,"line":766},[299,1287,1288],{"class":348},"        )\n",[299,1290,1292,1295],{"class":301,"line":1291},36,[299,1293,1294],{"class":312},"    return",[299,1296,1297],{"class":323}," wrap\n",[299,1299,1301],{"class":301,"line":1300},37,[299,1302,331],{"emptyLinePlaceholder":330},[299,1304,1306],{"class":301,"line":1305},38,[299,1307,331],{"emptyLinePlaceholder":330},[299,1309,1311,1313,1315,1317,1319,1321,1323,1325,1327,1329,1331,1333,1335,1337],{"class":301,"line":1310},39,[299,1312,918],{"class":480},[299,1314,1202],{"class":457},[299,1316,461],{"class":348},[299,1318,1062],{"class":929},[299,1320,561],{"class":348},[299,1322,365],{"class":323},[299,1324,396],{"class":348},[299,1326,601],{"class":316},[299,1328,349],{"class":348},[299,1330,606],{"class":564},[299,1332,1077],{"class":348},[299,1334,672],{"class":348},[299,1336,675],{"class":564},[299,1338,488],{"class":348},[299,1340,1342,1345,1347,1350,1352,1355,1357,1359],{"class":301,"line":1341},40,[299,1343,1344],{"class":323},"    sig ",[299,1346,390],{"class":389},[299,1348,1349],{"class":323}," inspect",[299,1351,717],{"class":348},[299,1353,1354],{"class":633},"signature",[299,1356,461],{"class":348},[299,1358,1062],{"class":633},[299,1360,474],{"class":348},[299,1362,1364,1367,1369,1372,1374,1376],{"class":301,"line":1363},41,[299,1365,1366],{"class":323},"    hints ",[299,1368,390],{"class":389},[299,1370,1371],{"class":633}," get_type_hints",[299,1373,461],{"class":348},[299,1375,1062],{"class":633},[299,1377,474],{"class":348},[299,1379,1381,1384,1386,1388,1390,1393,1395,1397,1399,1401],{"class":301,"line":1380},42,[299,1382,1383],{"class":323},"    properties",[299,1385,561],{"class":348},[299,1387,675],{"class":323},[299,1389,396],{"class":348},[299,1391,1392],{"class":564},"str",[299,1394,349],{"class":348},[299,1396,675],{"class":564},[299,1398,627],{"class":348},[299,1400,630],{"class":389},[299,1402,1403],{"class":348}," {}\n",[299,1405,1407,1410,1412,1415,1417,1419,1421,1423],{"class":301,"line":1406},43,[299,1408,1409],{"class":323},"    required",[299,1411,561],{"class":348},[299,1413,1414],{"class":323}," list",[299,1416,396],{"class":348},[299,1418,1392],{"class":564},[299,1420,627],{"class":348},[299,1422,630],{"class":389},[299,1424,1425],{"class":348}," []\n",[299,1427,1429,1432,1435,1437,1440,1443,1446,1448,1451,1453,1456],{"class":301,"line":1428},44,[299,1430,1431],{"class":312},"    for",[299,1433,1434],{"class":323}," pname",[299,1436,349],{"class":348},[299,1438,1439],{"class":323}," param ",[299,1441,1442],{"class":312},"in",[299,1444,1445],{"class":323}," sig",[299,1447,717],{"class":348},[299,1449,1450],{"class":720},"parameters",[299,1452,717],{"class":348},[299,1454,1455],{"class":633},"items",[299,1457,1458],{"class":348},"():\n",[299,1460,1462,1464,1467,1470,1472,1474,1476],{"class":301,"line":1461},45,[299,1463,1146],{"class":312},[299,1465,1466],{"class":323}," pname ",[299,1468,1469],{"class":389},"==",[299,1471,411],{"class":399},[299,1473,666],{"class":403},[299,1475,400],{"class":399},[299,1477,488],{"class":348},[299,1479,1481],{"class":301,"line":1480},46,[299,1482,1483],{"class":312},"            continue\n",[299,1485,1487,1490,1492,1495,1497,1500,1502,1505,1507,1509],{"class":301,"line":1486},47,[299,1488,1489],{"class":323},"        hint ",[299,1491,390],{"class":389},[299,1493,1494],{"class":323}," hints",[299,1496,717],{"class":348},[299,1498,1499],{"class":633},"get",[299,1501,461],{"class":348},[299,1503,1504],{"class":633},"pname",[299,1506,349],{"class":348},[299,1508,606],{"class":564},[299,1510,474],{"class":348},[299,1512,1514,1517,1519,1521,1523,1525,1528,1530,1533],{"class":301,"line":1513},48,[299,1515,1516],{"class":323},"        properties",[299,1518,396],{"class":348},[299,1520,1504],{"class":323},[299,1522,627],{"class":348},[299,1524,630],{"class":389},[299,1526,1527],{"class":633}," _type_to_schema",[299,1529,461],{"class":348},[299,1531,1532],{"class":633},"hint",[299,1534,474],{"class":348},[299,1536,1538,1540,1543,1545,1548,1551,1553,1555,1558,1560,1563],{"class":301,"line":1537},49,[299,1539,1146],{"class":312},[299,1541,1542],{"class":323}," param",[299,1544,717],{"class":348},[299,1546,1547],{"class":720},"default",[299,1549,1550],{"class":389}," is",[299,1552,1349],{"class":323},[299,1554,717],{"class":348},[299,1556,1557],{"class":720},"Parameter",[299,1559,717],{"class":348},[299,1561,1562],{"class":720},"empty",[299,1564,488],{"class":348},[299,1566,1568,1571,1573,1576,1578,1580],{"class":301,"line":1567},50,[299,1569,1570],{"class":323},"            required",[299,1572,717],{"class":348},[299,1574,1575],{"class":633},"append",[299,1577,461],{"class":348},[299,1579,1504],{"class":633},[299,1581,474],{"class":348},[299,1583,1585,1587],{"class":301,"line":1584},51,[299,1586,1294],{"class":312},[299,1588,698],{"class":348},[299,1590,1592,1595,1598,1600,1602,1604,1607,1609],{"class":301,"line":1591},52,[299,1593,1594],{"class":399},"        \"",[299,1596,1597],{"class":403},"type",[299,1599,400],{"class":399},[299,1601,561],{"class":348},[299,1603,411],{"class":399},[299,1605,1606],{"class":403},"object",[299,1608,400],{"class":399},[299,1610,723],{"class":348},[299,1612,1614,1616,1619,1621,1623,1626],{"class":301,"line":1613},53,[299,1615,1594],{"class":399},[299,1617,1618],{"class":403},"properties",[299,1620,400],{"class":399},[299,1622,561],{"class":348},[299,1624,1625],{"class":323}," properties",[299,1627,723],{"class":348},[299,1629,1631,1633,1636,1638,1640,1643],{"class":301,"line":1630},54,[299,1632,1594],{"class":399},[299,1634,1635],{"class":403},"required",[299,1637,400],{"class":399},[299,1639,561],{"class":348},[299,1641,1642],{"class":323}," required",[299,1644,723],{"class":348},[299,1646,1648],{"class":301,"line":1647},55,[299,1649,1650],{"class":348},"    }\n",[299,1652,1654],{"class":301,"line":1653},56,[299,1655,331],{"emptyLinePlaceholder":330},[299,1657,1659],{"class":301,"line":1658},57,[299,1660,331],{"emptyLinePlaceholder":330},[299,1662,1664,1666,1668,1670,1673,1675,1678,1680,1682,1684],{"class":301,"line":1663},58,[299,1665,918],{"class":480},[299,1667,1527],{"class":457},[299,1669,461],{"class":348},[299,1671,1672],{"class":929},"t",[299,1674,561],{"class":348},[299,1676,1677],{"class":564}," type",[299,1679,669],{"class":348},[299,1681,672],{"class":348},[299,1683,675],{"class":564},[299,1685,488],{"class":348},[299,1687,1689,1692,1694,1697,1699,1702,1704,1706],{"class":301,"line":1688},59,[299,1690,1691],{"class":323},"    origin ",[299,1693,390],{"class":389},[299,1695,1696],{"class":323}," typing",[299,1698,717],{"class":348},[299,1700,1701],{"class":633},"get_origin",[299,1703,461],{"class":348},[299,1705,1672],{"class":633},[299,1707,474],{"class":348},[299,1709,1711,1714,1717,1720,1722,1724,1727,1729,1731,1733,1736],{"class":301,"line":1710},60,[299,1712,1713],{"class":312},"    if",[299,1715,1716],{"class":323}," origin ",[299,1718,1719],{"class":389},"is",[299,1721,1696],{"class":323},[299,1723,717],{"class":348},[299,1725,1726],{"class":720},"Union",[299,1728,1129],{"class":389},[299,1730,1716],{"class":323},[299,1732,1719],{"class":389},[299,1734,1735],{"class":633}," types_union",[299,1737,1458],{"class":348},[299,1739,1741,1744,1746,1749,1752,1755,1758,1760,1762,1764,1767,1769,1771,1773,1776,1778,1780,1782,1784,1786,1789],{"class":301,"line":1740},61,[299,1742,1743],{"class":323},"        args ",[299,1745,390],{"class":389},[299,1747,1748],{"class":348}," [",[299,1750,1751],{"class":323},"a ",[299,1753,1754],{"class":312},"for",[299,1756,1757],{"class":323}," a ",[299,1759,1442],{"class":312},[299,1761,1696],{"class":323},[299,1763,717],{"class":348},[299,1765,1766],{"class":633},"get_args",[299,1768,461],{"class":348},[299,1770,1672],{"class":633},[299,1772,669],{"class":348},[299,1774,1775],{"class":312}," if",[299,1777,1757],{"class":323},[299,1779,1719],{"class":389},[299,1781,1149],{"class":389},[299,1783,1677],{"class":564},[299,1785,461],{"class":348},[299,1787,1788],{"class":470},"None",[299,1790,1791],{"class":348},")]\n",[299,1793,1795,1797,1801,1803,1806,1808,1811,1814],{"class":301,"line":1794},62,[299,1796,1146],{"class":312},[299,1798,1800],{"class":1799},"sptTA"," len",[299,1802,461],{"class":348},[299,1804,1805],{"class":633},"args",[299,1807,669],{"class":348},[299,1809,1810],{"class":389}," ==",[299,1812,1813],{"class":1173}," 1",[299,1815,488],{"class":348},[299,1817,1819,1822,1824,1826,1828,1830,1833],{"class":301,"line":1818},63,[299,1820,1821],{"class":312},"            return",[299,1823,1527],{"class":633},[299,1825,461],{"class":348},[299,1827,1805],{"class":633},[299,1829,396],{"class":348},[299,1831,1832],{"class":1173},"0",[299,1834,1835],{"class":348},"])\n",[299,1837,1839,1841,1844,1846,1848],{"class":301,"line":1838},64,[299,1840,1713],{"class":312},[299,1842,1843],{"class":323}," t ",[299,1845,1719],{"class":389},[299,1847,606],{"class":564},[299,1849,488],{"class":348},[299,1851,1853,1855,1858,1860,1862,1864,1866,1868,1870,1872],{"class":301,"line":1852},65,[299,1854,695],{"class":312},[299,1856,1857],{"class":348}," {",[299,1859,400],{"class":399},[299,1861,1597],{"class":403},[299,1863,400],{"class":399},[299,1865,561],{"class":348},[299,1867,411],{"class":399},[299,1869,271],{"class":403},[299,1871,400],{"class":399},[299,1873,1874],{"class":348},"}\n",[299,1876,1878,1880,1882,1884,1887],{"class":301,"line":1877},66,[299,1879,1713],{"class":312},[299,1881,1843],{"class":323},[299,1883,1719],{"class":389},[299,1885,1886],{"class":564}," int",[299,1888,488],{"class":348},[299,1890,1892,1894,1896,1898,1900,1902,1904,1906,1909,1911],{"class":301,"line":1891},67,[299,1893,695],{"class":312},[299,1895,1857],{"class":348},[299,1897,400],{"class":399},[299,1899,1597],{"class":403},[299,1901,400],{"class":399},[299,1903,561],{"class":348},[299,1905,411],{"class":399},[299,1907,1908],{"class":403},"integer",[299,1910,400],{"class":399},[299,1912,1874],{"class":348},[299,1914,1916,1918,1920,1922,1925],{"class":301,"line":1915},68,[299,1917,1713],{"class":312},[299,1919,1843],{"class":323},[299,1921,1719],{"class":389},[299,1923,1924],{"class":564}," float",[299,1926,488],{"class":348},[299,1928,1930,1932,1934,1936,1938,1940,1942,1944,1947,1949],{"class":301,"line":1929},69,[299,1931,695],{"class":312},[299,1933,1857],{"class":348},[299,1935,400],{"class":399},[299,1937,1597],{"class":403},[299,1939,400],{"class":399},[299,1941,561],{"class":348},[299,1943,411],{"class":399},[299,1945,1946],{"class":403},"number",[299,1948,400],{"class":399},[299,1950,1874],{"class":348},[299,1952,1954,1956,1958,1960,1963],{"class":301,"line":1953},70,[299,1955,1713],{"class":312},[299,1957,1843],{"class":323},[299,1959,1719],{"class":389},[299,1961,1962],{"class":564}," bool",[299,1964,488],{"class":348},[299,1966,1968,1970,1972,1974,1976,1978,1980,1982,1985,1987],{"class":301,"line":1967},71,[299,1969,695],{"class":312},[299,1971,1857],{"class":348},[299,1973,400],{"class":399},[299,1975,1597],{"class":403},[299,1977,400],{"class":399},[299,1979,561],{"class":348},[299,1981,411],{"class":399},[299,1983,1984],{"class":403},"boolean",[299,1986,400],{"class":399},[299,1988,1874],{"class":348},[299,1990,1992,1994,1996,1998,2000],{"class":301,"line":1991},72,[299,1993,1713],{"class":312},[299,1995,1716],{"class":323},[299,1997,1719],{"class":389},[299,1999,1414],{"class":564},[299,2001,488],{"class":348},[299,2003,2005,2007,2009,2011,2013,2015,2017,2019,2022,2024,2026,2028,2030,2032,2034,2036,2038,2041,2043,2045,2047,2049,2052,2054],{"class":301,"line":2004},73,[299,2006,695],{"class":312},[299,2008,1857],{"class":348},[299,2010,400],{"class":399},[299,2012,1597],{"class":403},[299,2014,400],{"class":399},[299,2016,561],{"class":348},[299,2018,411],{"class":399},[299,2020,2021],{"class":403},"array",[299,2023,400],{"class":399},[299,2025,349],{"class":348},[299,2027,411],{"class":399},[299,2029,1455],{"class":403},[299,2031,400],{"class":399},[299,2033,561],{"class":348},[299,2035,1527],{"class":633},[299,2037,461],{"class":348},[299,2039,2040],{"class":633},"typing",[299,2042,717],{"class":348},[299,2044,1766],{"class":633},[299,2046,461],{"class":348},[299,2048,1672],{"class":633},[299,2050,2051],{"class":348},")[",[299,2053,1832],{"class":1173},[299,2055,2056],{"class":348},"])}\n",[299,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080],{"class":301,"line":2059},74,[299,2061,1294],{"class":312},[299,2063,1857],{"class":348},[299,2065,400],{"class":399},[299,2067,1597],{"class":403},[299,2069,400],{"class":399},[299,2071,561],{"class":348},[299,2073,411],{"class":399},[299,2075,271],{"class":403},[299,2077,400],{"class":399},[299,2079,1183],{"class":348},[299,2081,2082],{"class":305},"  # fallback\n",[299,2084,2086],{"class":301,"line":2085},75,[299,2087,331],{"emptyLinePlaceholder":330},[299,2089,2091],{"class":301,"line":2090},76,[299,2092,331],{"emptyLinePlaceholder":330},[299,2094,2096,2098,2100],{"class":301,"line":2095},77,[299,2097,918],{"class":480},[299,2099,1735],{"class":457},[299,2101,1458],{"class":348},[299,2103,2105,2108],{"class":301,"line":2104},78,[299,2106,2107],{"class":312},"    import",[299,2109,2110],{"class":323}," types\n",[299,2112,2114,2116,2119,2121],{"class":301,"line":2113},79,[299,2115,1294],{"class":312},[299,2117,2118],{"class":323}," types",[299,2120,717],{"class":348},[299,2122,2123],{"class":720},"UnionType\n",[112,2125,2126,2129,2130,2132,2133,2132,2136,2132,2139,2132,2142,2145,2146,2149,2150,2152,2153,2156,2157,2160],{},[119,2127,2128],{},"_schema_from_signature"," is deliberately simple. It handles ",[119,2131,1392],{},", ",[119,2134,2135],{},"int",[119,2137,2138],{},"float",[119,2140,2141],{},"bool",[119,2143,2144],{},"list[T]",", and ",[119,2147,2148],{},"Optional[T]"," — enough to cover every tool we write in the book. If you need enums, nested objects, or constraints (minLength, pattern, minimum), you can either extend this function or pass an explicit ",[119,2151,751],{}," to a manual ",[119,2154,2155],{},"Tool(...)"," call. Most serious production harnesses use Pydantic or ",[119,2158,2159],{},"pydantic.TypeAdapter"," for this; we'll adopt that in Chapter 6 when schema validation starts mattering.",[112,2162,2163],{},"Usage:",[290,2165,2167],{"className":292,"code":2166,"language":294,"meta":295,"style":295},"from harness.tools.decorator import tool\n\n\n@tool(side_effects={\"read\"})\ndef calc(expression: str) -> str:\n    \"\"\"Evaluate a Python arithmetic expression.\n\n    Accepts: standard Python syntax with +, -, *, \u002F, **, parentheses.\n    Rejects: imports, function calls, attribute access.\n    Side effects: none.\n    \"\"\"\n    import ast\n    tree = ast.parse(expression, mode=\"eval\")\n    for node in ast.walk(tree):\n        if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,\n                                 ast.Num, ast.Constant, ast.operator,\n                                 ast.unaryop, ast.Load)):\n            raise ValueError(f\"not allowed in expression: {type(node).__name__}\")\n    return str(eval(compile(tree, \"\u003Cexpr>\", mode=\"eval\"), {\"__builtins__\": {}}))\n",[119,2168,2169,2191,2195,2199,2223,2247,2254,2258,2263,2268,2273,2277,2284,2319,2343,2389,2419,2440,2472],{"__ignoreMap":295},[299,2170,2171,2173,2176,2178,2181,2183,2186,2188],{"class":301,"line":302},[299,2172,313],{"class":312},[299,2174,2175],{"class":323}," harness",[299,2177,717],{"class":348},[299,2179,2180],{"class":323},"tools",[299,2182,717],{"class":348},[299,2184,2185],{"class":323},"decorator ",[299,2187,342],{"class":312},[299,2189,2190],{"class":323}," tool\n",[299,2192,2193],{"class":301,"line":309},[299,2194,331],{"emptyLinePlaceholder":330},[299,2196,2197],{"class":301,"line":327},[299,2198,331],{"emptyLinePlaceholder":330},[299,2200,2201,2203,2206,2208,2210,2212,2214,2216,2218,2220],{"class":301,"line":334},[299,2202,454],{"class":453},[299,2204,2205],{"class":457},"tool",[299,2207,461],{"class":348},[299,2209,1280],{"class":464},[299,2211,390],{"class":389},[299,2213,1174],{"class":348},[299,2215,400],{"class":399},[299,2217,404],{"class":403},[299,2219,400],{"class":399},[299,2221,2222],{"class":348},"})\n",[299,2224,2225,2227,2230,2232,2235,2237,2239,2241,2243,2245],{"class":301,"line":355},[299,2226,918],{"class":480},[299,2228,2229],{"class":457}," calc",[299,2231,461],{"class":348},[299,2233,2234],{"class":929},"expression",[299,2236,561],{"class":348},[299,2238,606],{"class":564},[299,2240,669],{"class":348},[299,2242,672],{"class":348},[299,2244,606],{"class":564},[299,2246,488],{"class":348},[299,2248,2249,2251],{"class":301,"line":373},[299,2250,495],{"class":494},[299,2252,2253],{"class":498},"Evaluate a Python arithmetic expression.\n",[299,2255,2256],{"class":301,"line":378},[299,2257,331],{"emptyLinePlaceholder":330},[299,2259,2260],{"class":301,"line":383},[299,2261,2262],{"class":498},"    Accepts: standard Python syntax with +, -, *, \u002F, **, parentheses.\n",[299,2264,2265],{"class":301,"line":440},[299,2266,2267],{"class":498},"    Rejects: imports, function calls, attribute access.\n",[299,2269,2270],{"class":301,"line":445},[299,2271,2272],{"class":498},"    Side effects: none.\n",[299,2274,2275],{"class":301,"line":450},[299,2276,552],{"class":494},[299,2278,2279,2281],{"class":301,"line":477},[299,2280,2107],{"class":312},[299,2282,2283],{"class":323}," ast\n",[299,2285,2286,2289,2291,2294,2296,2299,2301,2303,2305,2308,2310,2312,2315,2317],{"class":301,"line":491},[299,2287,2288],{"class":323},"    tree ",[299,2290,390],{"class":389},[299,2292,2293],{"class":323}," ast",[299,2295,717],{"class":348},[299,2297,2298],{"class":633},"parse",[299,2300,461],{"class":348},[299,2302,2234],{"class":633},[299,2304,349],{"class":348},[299,2306,2307],{"class":464}," mode",[299,2309,390],{"class":389},[299,2311,400],{"class":399},[299,2313,2314],{"class":403},"eval",[299,2316,400],{"class":399},[299,2318,474],{"class":348},[299,2320,2321,2323,2326,2328,2330,2332,2335,2337,2340],{"class":301,"line":502},[299,2322,1431],{"class":312},[299,2324,2325],{"class":323}," node ",[299,2327,1442],{"class":312},[299,2329,2293],{"class":323},[299,2331,717],{"class":348},[299,2333,2334],{"class":633},"walk",[299,2336,461],{"class":348},[299,2338,2339],{"class":633},"tree",[299,2341,2342],{"class":348},"):\n",[299,2344,2345,2347,2349,2352,2354,2357,2359,2361,2364,2366,2369,2371,2373,2375,2378,2380,2382,2384,2387],{"class":301,"line":507},[299,2346,1146],{"class":312},[299,2348,1149],{"class":389},[299,2350,2351],{"class":1799}," isinstance",[299,2353,461],{"class":348},[299,2355,2356],{"class":633},"node",[299,2358,349],{"class":348},[299,2360,1119],{"class":348},[299,2362,2363],{"class":633},"ast",[299,2365,717],{"class":348},[299,2367,2368],{"class":720},"Expression",[299,2370,349],{"class":348},[299,2372,2293],{"class":633},[299,2374,717],{"class":348},[299,2376,2377],{"class":720},"BinOp",[299,2379,349],{"class":348},[299,2381,2293],{"class":633},[299,2383,717],{"class":348},[299,2385,2386],{"class":720},"UnaryOp",[299,2388,723],{"class":348},[299,2390,2391,2394,2396,2399,2401,2403,2405,2408,2410,2412,2414,2417],{"class":301,"line":513},[299,2392,2393],{"class":633},"                                 ast",[299,2395,717],{"class":348},[299,2397,2398],{"class":720},"Num",[299,2400,349],{"class":348},[299,2402,2293],{"class":633},[299,2404,717],{"class":348},[299,2406,2407],{"class":720},"Constant",[299,2409,349],{"class":348},[299,2411,2293],{"class":633},[299,2413,717],{"class":348},[299,2415,2416],{"class":720},"operator",[299,2418,723],{"class":348},[299,2420,2421,2423,2425,2428,2430,2432,2434,2437],{"class":301,"line":519},[299,2422,2393],{"class":633},[299,2424,717],{"class":348},[299,2426,2427],{"class":720},"unaryop",[299,2429,349],{"class":348},[299,2431,2293],{"class":633},[299,2433,717],{"class":348},[299,2435,2436],{"class":720},"Load",[299,2438,2439],{"class":348},")):\n",[299,2441,2442,2444,2446,2448,2450,2453,2455,2457,2459,2461,2463,2466,2468,2470],{"class":301,"line":525},[299,2443,1159],{"class":312},[299,2445,1162],{"class":564},[299,2447,461],{"class":348},[299,2449,1167],{"class":480},[299,2451,2452],{"class":403},"\"not allowed in expression: ",[299,2454,1174],{"class":1173},[299,2456,1597],{"class":564},[299,2458,461],{"class":348},[299,2460,2356],{"class":633},[299,2462,1135],{"class":348},[299,2464,2465],{"class":316},"__name__",[299,2467,1183],{"class":1173},[299,2469,400],{"class":403},[299,2471,474],{"class":348},[299,2473,2474,2476,2478,2480,2482,2484,2487,2489,2491,2493,2495,2498,2500,2502,2504,2506,2508,2510,2512,2515,2517,2519,2522,2524,2526],{"class":301,"line":531},[299,2475,1294],{"class":312},[299,2477,606],{"class":564},[299,2479,461],{"class":348},[299,2481,2314],{"class":1799},[299,2483,461],{"class":348},[299,2485,2486],{"class":1799},"compile",[299,2488,461],{"class":348},[299,2490,2339],{"class":633},[299,2492,349],{"class":348},[299,2494,411],{"class":399},[299,2496,2497],{"class":403},"\u003Cexpr>",[299,2499,400],{"class":399},[299,2501,349],{"class":348},[299,2503,2307],{"class":464},[299,2505,390],{"class":389},[299,2507,400],{"class":399},[299,2509,2314],{"class":403},[299,2511,400],{"class":399},[299,2513,2514],{"class":348},"),",[299,2516,1857],{"class":348},[299,2518,400],{"class":399},[299,2520,2521],{"class":403},"__builtins__",[299,2523,400],{"class":399},[299,2525,561],{"class":348},[299,2527,2528],{"class":348}," {}}))\n",[112,2530,2531],{},"Notice the docstring is doing real work. The model reads it; \"accepts \u002F rejects \u002F side effects\" is a pattern that makes the tool hard to misuse. Compare that to \"evaluates math\" and you can see the difference it makes to the model's selection behavior.",[216,2533],{},[219,2535,2537],{"id":2536},"_44-the-registry","4.4 The Registry",[112,2539,2540],{},"The registry holds tools, renders their schemas for providers, and dispatches calls by name.",[290,2542,2544],{"className":292,"code":2543,"language":294,"meta":295,"style":295},"# src\u002Fharness\u002Ftools\u002Fregistry.py\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\n\nfrom ..messages import ToolResult\nfrom .base import Tool\n\n\nclass UnknownToolError(Exception):\n    pass\n\n\n@dataclass\nclass ToolRegistry:\n    tools: dict[str, Tool]\n\n    def __init__(self, tools: list[Tool] | None = None) -> None:\n        self.tools = {}\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 ToolResult(\n                call_id=call_id,\n                content=(f\"unknown tool: {name}. \"\n                         f\"available: {sorted(self.tools.keys())}\"),\n                is_error=True,\n            )\n        tool = self.tools[name]\n        try:\n            content = tool.run(**args)\n        except TypeError as e:\n            return ToolResult(\n                call_id=call_id,\n                content=f\"argument error for {name}: {e}\",\n                is_error=True,\n            )\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",[119,2545,2546,2551,2561,2565,2576,2580,2595,2607,2611,2615,2629,2634,2638,2642,2649,2658,2677,2681,2723,2736,2753,2769,2773,2800,2821,2848,2870,2874,2898,2935,2939,2986,3005,3013,3025,3048,3083,3094,3099,3118,3125,3148,3164,3172,3182,3213,3223,3227,3240,3248,3258,3303,3313,3317],{"__ignoreMap":295},[299,2547,2548],{"class":301,"line":302},[299,2549,2550],{"class":305},"# src\u002Fharness\u002Ftools\u002Fregistry.py\n",[299,2552,2553,2555,2557,2559],{"class":301,"line":309},[299,2554,313],{"class":312},[299,2556,317],{"class":316},[299,2558,320],{"class":312},[299,2560,324],{"class":323},[299,2562,2563],{"class":301,"line":327},[299,2564,331],{"emptyLinePlaceholder":330},[299,2566,2567,2569,2571,2573],{"class":301,"line":334},[299,2568,313],{"class":312},[299,2570,339],{"class":323},[299,2572,342],{"class":312},[299,2574,2575],{"class":323}," dataclass\n",[299,2577,2578],{"class":301,"line":355},[299,2579,331],{"emptyLinePlaceholder":330},[299,2581,2582,2584,2587,2590,2592],{"class":301,"line":373},[299,2583,313],{"class":312},[299,2585,2586],{"class":348}," ..",[299,2588,2589],{"class":323},"messages ",[299,2591,342],{"class":312},[299,2593,2594],{"class":323}," ToolResult\n",[299,2596,2597,2599,2601,2603,2605],{"class":301,"line":378},[299,2598,313],{"class":312},[299,2600,892],{"class":348},[299,2602,895],{"class":323},[299,2604,342],{"class":312},[299,2606,905],{"class":323},[299,2608,2609],{"class":301,"line":383},[299,2610,331],{"emptyLinePlaceholder":330},[299,2612,2613],{"class":301,"line":440},[299,2614,331],{"emptyLinePlaceholder":330},[299,2616,2617,2619,2622,2624,2627],{"class":301,"line":445},[299,2618,481],{"class":480},[299,2620,2621],{"class":484}," UnknownToolError",[299,2623,461],{"class":348},[299,2625,2626],{"class":564},"Exception",[299,2628,2342],{"class":348},[299,2630,2631],{"class":301,"line":450},[299,2632,2633],{"class":312},"    pass\n",[299,2635,2636],{"class":301,"line":477},[299,2637,331],{"emptyLinePlaceholder":330},[299,2639,2640],{"class":301,"line":491},[299,2641,331],{"emptyLinePlaceholder":330},[299,2643,2644,2646],{"class":301,"line":502},[299,2645,454],{"class":453},[299,2647,2648],{"class":457},"dataclass\n",[299,2650,2651,2653,2656],{"class":301,"line":507},[299,2652,481],{"class":480},[299,2654,2655],{"class":484}," ToolRegistry",[299,2657,488],{"class":348},[299,2659,2660,2663,2665,2667,2669,2671,2673,2675],{"class":301,"line":513},[299,2661,2662],{"class":323},"    tools",[299,2664,561],{"class":348},[299,2666,675],{"class":323},[299,2668,396],{"class":348},[299,2670,1392],{"class":564},[299,2672,349],{"class":348},[299,2674,485],{"class":323},[299,2676,437],{"class":348},[299,2678,2679],{"class":301,"line":519},[299,2680,331],{"emptyLinePlaceholder":330},[299,2682,2683,2685,2688,2690,2692,2694,2697,2699,2701,2703,2705,2707,2709,2711,2713,2715,2717,2719,2721],{"class":301,"line":525},[299,2684,657],{"class":480},[299,2686,2687],{"class":1799}," __init__",[299,2689,461],{"class":348},[299,2691,666],{"class":665},[299,2693,349],{"class":348},[299,2695,2696],{"class":929}," tools",[299,2698,561],{"class":348},[299,2700,1414],{"class":323},[299,2702,396],{"class":348},[299,2704,131],{"class":323},[299,2706,627],{"class":348},[299,2708,936],{"class":389},[299,2710,939],{"class":470},[299,2712,630],{"class":389},[299,2714,939],{"class":470},[299,2716,669],{"class":348},[299,2718,672],{"class":348},[299,2720,939],{"class":470},[299,2722,488],{"class":348},[299,2724,2725,2728,2730,2732,2734],{"class":301,"line":531},[299,2726,2727],{"class":316},"        self",[299,2729,717],{"class":348},[299,2731,2180],{"class":720},[299,2733,630],{"class":389},[299,2735,1403],{"class":348},[299,2737,2738,2741,2743,2745,2748,2750],{"class":301,"line":537},[299,2739,2740],{"class":312},"        for",[299,2742,1843],{"class":323},[299,2744,1442],{"class":312},[299,2746,2747],{"class":323}," tools ",[299,2749,1096],{"class":389},[299,2751,2752],{"class":348}," []:\n",[299,2754,2755,2758,2760,2763,2765,2767],{"class":301,"line":543},[299,2756,2757],{"class":316},"            self",[299,2759,717],{"class":348},[299,2761,2762],{"class":633},"add",[299,2764,461],{"class":348},[299,2766,1672],{"class":633},[299,2768,474],{"class":348},[299,2770,2771],{"class":301,"line":549},[299,2772,331],{"emptyLinePlaceholder":330},[299,2774,2775,2777,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798],{"class":301,"line":555},[299,2776,657],{"class":480},[299,2778,2779],{"class":457}," add",[299,2781,461],{"class":348},[299,2783,666],{"class":665},[299,2785,349],{"class":348},[299,2787,921],{"class":929},[299,2789,561],{"class":348},[299,2791,485],{"class":323},[299,2793,669],{"class":348},[299,2795,672],{"class":348},[299,2797,939],{"class":470},[299,2799,488],{"class":348},[299,2801,2802,2804,2806,2808,2810,2813,2815,2817,2819],{"class":301,"line":568},[299,2803,1146],{"class":312},[299,2805,921],{"class":323},[299,2807,717],{"class":348},[299,2809,707],{"class":720},[299,2811,2812],{"class":389}," in",[299,2814,714],{"class":316},[299,2816,717],{"class":348},[299,2818,2180],{"class":720},[299,2820,488],{"class":348},[299,2822,2823,2825,2827,2829,2831,2834,2836,2838,2840,2842,2844,2846],{"class":301,"line":578},[299,2824,1159],{"class":312},[299,2826,1162],{"class":564},[299,2828,461],{"class":348},[299,2830,1167],{"class":480},[299,2832,2833],{"class":403},"\"duplicate tool name: ",[299,2835,1174],{"class":1173},[299,2837,2205],{"class":633},[299,2839,717],{"class":348},[299,2841,707],{"class":720},[299,2843,1183],{"class":1173},[299,2845,400],{"class":403},[299,2847,474],{"class":348},[299,2849,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868],{"class":301,"line":589},[299,2851,2727],{"class":316},[299,2853,717],{"class":348},[299,2855,2180],{"class":720},[299,2857,396],{"class":348},[299,2859,2205],{"class":720},[299,2861,717],{"class":348},[299,2863,707],{"class":720},[299,2865,627],{"class":348},[299,2867,630],{"class":389},[299,2869,2190],{"class":323},[299,2871,2872],{"class":301,"line":611},[299,2873,331],{"emptyLinePlaceholder":330},[299,2875,2876,2878,2881,2883,2885,2887,2889,2891,2893,2896],{"class":301,"line":649},[299,2877,657],{"class":480},[299,2879,2880],{"class":457}," schemas",[299,2882,461],{"class":348},[299,2884,666],{"class":665},[299,2886,669],{"class":348},[299,2888,672],{"class":348},[299,2890,1414],{"class":323},[299,2892,396],{"class":348},[299,2894,2895],{"class":564},"dict",[299,2897,1025],{"class":348},[299,2899,2900,2902,2904,2906,2908,2911,2914,2917,2919,2921,2923,2925,2927,2929,2932],{"class":301,"line":654},[299,2901,695],{"class":312},[299,2903,1748],{"class":348},[299,2905,1672],{"class":323},[299,2907,717],{"class":348},[299,2909,2910],{"class":633},"schema_for_provider",[299,2912,2913],{"class":348},"()",[299,2915,2916],{"class":312}," for",[299,2918,1843],{"class":323},[299,2920,1442],{"class":312},[299,2922,714],{"class":316},[299,2924,717],{"class":348},[299,2926,2180],{"class":720},[299,2928,717],{"class":348},[299,2930,2931],{"class":633},"values",[299,2933,2934],{"class":348},"()]\n",[299,2936,2937],{"class":301,"line":680},[299,2938,331],{"emptyLinePlaceholder":330},[299,2940,2941,2943,2946,2948,2950,2952,2955,2957,2959,2961,2964,2966,2968,2970,2973,2975,2977,2979,2981,2984],{"class":301,"line":692},[299,2942,657],{"class":480},[299,2944,2945],{"class":457}," dispatch",[299,2947,461],{"class":348},[299,2949,666],{"class":665},[299,2951,349],{"class":348},[299,2953,2954],{"class":929}," name",[299,2956,561],{"class":348},[299,2958,606],{"class":564},[299,2960,349],{"class":348},[299,2962,2963],{"class":929}," args",[299,2965,561],{"class":348},[299,2967,675],{"class":564},[299,2969,349],{"class":348},[299,2971,2972],{"class":929}," call_id",[299,2974,561],{"class":348},[299,2976,606],{"class":564},[299,2978,669],{"class":348},[299,2980,672],{"class":348},[299,2982,2983],{"class":323}," ToolResult",[299,2985,488],{"class":348},[299,2987,2988,2990,2992,2995,2997,2999,3001,3003],{"class":301,"line":701},[299,2989,1146],{"class":312},[299,2991,1093],{"class":323},[299,2993,2994],{"class":389},"not",[299,2996,2812],{"class":389},[299,2998,714],{"class":316},[299,3000,717],{"class":348},[299,3002,2180],{"class":720},[299,3004,488],{"class":348},[299,3006,3007,3009,3011],{"class":301,"line":726},[299,3008,1821],{"class":312},[299,3010,2983],{"class":633},[299,3012,924],{"class":348},[299,3014,3015,3018,3020,3023],{"class":301,"line":746},[299,3016,3017],{"class":464},"                call_id",[299,3019,390],{"class":389},[299,3021,3022],{"class":633},"call_id",[299,3024,723],{"class":348},[299,3026,3027,3030,3032,3034,3036,3039,3041,3043,3045],{"class":301,"line":766},[299,3028,3029],{"class":464},"                content",[299,3031,390],{"class":389},[299,3033,461],{"class":348},[299,3035,1167],{"class":480},[299,3037,3038],{"class":403},"\"unknown tool: ",[299,3040,1174],{"class":1173},[299,3042,707],{"class":633},[299,3044,1183],{"class":1173},[299,3046,3047],{"class":403},". \"\n",[299,3049,3050,3053,3056,3058,3061,3063,3065,3067,3069,3071,3074,3077,3079,3081],{"class":301,"line":1291},[299,3051,3052],{"class":480},"                         f",[299,3054,3055],{"class":403},"\"available: ",[299,3057,1174],{"class":1173},[299,3059,3060],{"class":1799},"sorted",[299,3062,461],{"class":348},[299,3064,666],{"class":316},[299,3066,717],{"class":348},[299,3068,2180],{"class":720},[299,3070,717],{"class":348},[299,3072,3073],{"class":633},"keys",[299,3075,3076],{"class":348},"())",[299,3078,1183],{"class":1173},[299,3080,400],{"class":403},[299,3082,1283],{"class":348},[299,3084,3085,3088,3090,3092],{"class":301,"line":1300},[299,3086,3087],{"class":464},"                is_error",[299,3089,390],{"class":389},[299,3091,471],{"class":470},[299,3093,723],{"class":348},[299,3095,3096],{"class":301,"line":1305},[299,3097,3098],{"class":348},"            )\n",[299,3100,3101,3104,3106,3108,3110,3112,3114,3116],{"class":301,"line":1310},[299,3102,3103],{"class":323},"        tool ",[299,3105,390],{"class":389},[299,3107,714],{"class":316},[299,3109,717],{"class":348},[299,3111,2180],{"class":720},[299,3113,396],{"class":348},[299,3115,707],{"class":720},[299,3117,437],{"class":348},[299,3119,3120,3123],{"class":301,"line":1341},[299,3121,3122],{"class":312},"        try",[299,3124,488],{"class":348},[299,3126,3127,3130,3132,3134,3136,3139,3141,3144,3146],{"class":301,"line":1363},[299,3128,3129],{"class":323},"            content ",[299,3131,390],{"class":389},[299,3133,921],{"class":323},[299,3135,717],{"class":348},[299,3137,3138],{"class":633},"run",[299,3140,461],{"class":348},[299,3142,3143],{"class":389},"**",[299,3145,1805],{"class":633},[299,3147,474],{"class":348},[299,3149,3150,3153,3156,3159,3162],{"class":301,"line":1380},[299,3151,3152],{"class":312},"        except",[299,3154,3155],{"class":564}," TypeError",[299,3157,3158],{"class":312}," as",[299,3160,3161],{"class":323}," e",[299,3163,488],{"class":348},[299,3165,3166,3168,3170],{"class":301,"line":1406},[299,3167,1821],{"class":312},[299,3169,2983],{"class":633},[299,3171,924],{"class":348},[299,3173,3174,3176,3178,3180],{"class":301,"line":1428},[299,3175,3017],{"class":464},[299,3177,390],{"class":389},[299,3179,3022],{"class":633},[299,3181,723],{"class":348},[299,3183,3184,3186,3188,3190,3193,3195,3197,3199,3202,3204,3207,3209,3211],{"class":301,"line":1461},[299,3185,3029],{"class":464},[299,3187,390],{"class":389},[299,3189,1167],{"class":480},[299,3191,3192],{"class":403},"\"argument error for ",[299,3194,1174],{"class":1173},[299,3196,707],{"class":633},[299,3198,1183],{"class":1173},[299,3200,3201],{"class":403},": ",[299,3203,1174],{"class":1173},[299,3205,3206],{"class":633},"e",[299,3208,1183],{"class":1173},[299,3210,400],{"class":403},[299,3212,723],{"class":348},[299,3214,3215,3217,3219,3221],{"class":301,"line":1480},[299,3216,3087],{"class":464},[299,3218,390],{"class":389},[299,3220,471],{"class":470},[299,3222,723],{"class":348},[299,3224,3225],{"class":301,"line":1486},[299,3226,3098],{"class":348},[299,3228,3229,3231,3234,3236,3238],{"class":301,"line":1513},[299,3230,3152],{"class":312},[299,3232,3233],{"class":564}," Exception",[299,3235,3158],{"class":312},[299,3237,3161],{"class":323},[299,3239,488],{"class":348},[299,3241,3242,3244,3246],{"class":301,"line":1537},[299,3243,1821],{"class":312},[299,3245,2983],{"class":633},[299,3247,924],{"class":348},[299,3249,3250,3252,3254,3256],{"class":301,"line":1567},[299,3251,3017],{"class":464},[299,3253,390],{"class":389},[299,3255,3022],{"class":633},[299,3257,723],{"class":348},[299,3259,3260,3262,3264,3266,3268,3270,3272,3274,3277,3279,3281,3283,3285,3287,3289,3291,3293,3295,3297,3299,3301],{"class":301,"line":1584},[299,3261,3029],{"class":464},[299,3263,390],{"class":389},[299,3265,1167],{"class":480},[299,3267,400],{"class":403},[299,3269,1174],{"class":1173},[299,3271,707],{"class":633},[299,3273,1183],{"class":1173},[299,3275,3276],{"class":403}," raised ",[299,3278,1174],{"class":1173},[299,3280,1597],{"class":564},[299,3282,461],{"class":348},[299,3284,3206],{"class":633},[299,3286,1135],{"class":348},[299,3288,2465],{"class":316},[299,3290,1183],{"class":1173},[299,3292,3201],{"class":403},[299,3294,1174],{"class":1173},[299,3296,3206],{"class":633},[299,3298,1183],{"class":1173},[299,3300,400],{"class":403},[299,3302,723],{"class":348},[299,3304,3305,3307,3309,3311],{"class":301,"line":1591},[299,3306,3087],{"class":464},[299,3308,390],{"class":389},[299,3310,471],{"class":470},[299,3312,723],{"class":348},[299,3314,3315],{"class":301,"line":1613},[299,3316,3098],{"class":348},[299,3318,3319,3321,3323,3325,3327,3329,3331,3333,3336,3338,3341],{"class":301,"line":1630},[299,3320,695],{"class":312},[299,3322,2983],{"class":633},[299,3324,461],{"class":348},[299,3326,3022],{"class":464},[299,3328,390],{"class":389},[299,3330,3022],{"class":633},[299,3332,349],{"class":348},[299,3334,3335],{"class":464}," content",[299,3337,390],{"class":389},[299,3339,3340],{"class":633},"content",[299,3342,474],{"class":348},[112,3344,3345],{},"The registry's contract is narrow: it knows tools, it knows schemas, it dispatches. What it does not do yet: validate arguments against the schema before dispatch, detect loops, enforce permissions, measure cost. All of those are chapters of their own. The registry is the seam they'll plug into.",[112,3347,3348],{},"Two design choices worth naming.",[112,3350,3351,3361,3362,3364,3365,3367],{},[237,3352,3353,3356,3357,3360],{},[119,3354,3355],{},"dispatch"," returns a ",[119,3358,3359],{},"ToolResult",", never raises."," The loop should not have to wrap every call in ",[119,3363,121],{},"; that's what the registry is for. An unknown tool is not an exception; it's a structured error the model can read and recover from. This is the explicit handling the Chapter 2 ",[119,3366,121],{}," was approximating.",[112,3369,3370,3373,3374,3377,3378,3381],{},[237,3371,3372],{},"The error messages name the available tools."," When the model calls ",[119,3375,3376],{},"calculator"," instead of ",[119,3379,3380],{},"calc",", the error tells it so. That's not decoration — it's how the model corrects itself on the next turn. A registry that says \"unknown tool\" without naming alternatives is throwing away a free learning signal.",[216,3383],{},[219,3385,3387],{"id":3386},"_45-the-loop-threaded-through-the-registry","4.5 The Loop, Threaded Through the Registry",[112,3389,3390,3391,3394],{},"The Chapter 3 loop built a ",[119,3392,3393],{},"dict[str, Callable]"," and called it directly. Now it uses the registry.",[290,3396,3398],{"className":292,"code":3397,"language":294,"meta":295,"style":295},"# src\u002Fharness\u002Fagent.py\nfrom __future__ import annotations\n\nfrom .messages import Message, Transcript, ToolCall\nfrom .providers.base import Provider\nfrom .tools.registry import ToolRegistry\n\n\nMAX_ITERATIONS = 20\n\n\ndef run(\n    provider: Provider,\n    registry: ToolRegistry,\n    user_message: str,\n    transcript: Transcript | None = None,\n    system: str | None = None,\n) -> str:\n    if transcript is None:\n        transcript = Transcript(system=system)\n    transcript.append(Message.user_text(user_message))\n\n    for _ in range(MAX_ITERATIONS):\n        response = provider.complete(transcript, registry.schemas())\n\n        if response.is_final:\n            transcript.append(Message.from_assistant_response(response))\n            return response.text or \"\"\n\n        # One assistant message with every ToolCall block from this turn.\n        transcript.append(Message.from_assistant_response(response))\n\n        # Dispatch each call in arrival order (Chapter 5 details the\n        # ProviderResponse.tool_calls tuple; here it's usually one call).\n        for ref in response.tool_calls:\n            result = registry.dispatch(ref.name, ref.args, ref.id)\n            transcript.append(Message.tool_result(result))\n\n    raise RuntimeError(f\"agent did not finish in {MAX_ITERATIONS} iterations\")\n",[119,3399,3400,3405,3415,3419,3442,3460,3478,3482,3486,3496,3500,3504,3513,3525,3536,3547,3568,3587,3597,3610,3630,3656,3660,3678,3711,3715,3729,3754,3770,3774,3779,3802,3806,3811,3816,3834,3876,3900,3904],{"__ignoreMap":295},[299,3401,3402],{"class":301,"line":302},[299,3403,3404],{"class":305},"# src\u002Fharness\u002Fagent.py\n",[299,3406,3407,3409,3411,3413],{"class":301,"line":309},[299,3408,313],{"class":312},[299,3410,317],{"class":316},[299,3412,320],{"class":312},[299,3414,324],{"class":323},[299,3416,3417],{"class":301,"line":327},[299,3418,331],{"emptyLinePlaceholder":330},[299,3420,3421,3423,3425,3427,3429,3432,3434,3437,3439],{"class":301,"line":334},[299,3422,313],{"class":312},[299,3424,892],{"class":348},[299,3426,2589],{"class":323},[299,3428,342],{"class":312},[299,3430,3431],{"class":323}," Message",[299,3433,349],{"class":348},[299,3435,3436],{"class":323}," Transcript",[299,3438,349],{"class":348},[299,3440,3441],{"class":323}," ToolCall\n",[299,3443,3444,3446,3448,3451,3453,3455,3457],{"class":301,"line":355},[299,3445,313],{"class":312},[299,3447,892],{"class":348},[299,3449,3450],{"class":323},"providers",[299,3452,717],{"class":348},[299,3454,895],{"class":323},[299,3456,342],{"class":312},[299,3458,3459],{"class":323}," Provider\n",[299,3461,3462,3464,3466,3468,3470,3473,3475],{"class":301,"line":373},[299,3463,313],{"class":312},[299,3465,892],{"class":348},[299,3467,2180],{"class":323},[299,3469,717],{"class":348},[299,3471,3472],{"class":323},"registry ",[299,3474,342],{"class":312},[299,3476,3477],{"class":323}," ToolRegistry\n",[299,3479,3480],{"class":301,"line":378},[299,3481,331],{"emptyLinePlaceholder":330},[299,3483,3484],{"class":301,"line":383},[299,3485,331],{"emptyLinePlaceholder":330},[299,3487,3488,3491,3493],{"class":301,"line":440},[299,3489,3490],{"class":316},"MAX_ITERATIONS",[299,3492,630],{"class":389},[299,3494,3495],{"class":1173}," 20\n",[299,3497,3498],{"class":301,"line":445},[299,3499,331],{"emptyLinePlaceholder":330},[299,3501,3502],{"class":301,"line":450},[299,3503,331],{"emptyLinePlaceholder":330},[299,3505,3506,3508,3511],{"class":301,"line":477},[299,3507,918],{"class":480},[299,3509,3510],{"class":457}," run",[299,3512,924],{"class":348},[299,3514,3515,3518,3520,3523],{"class":301,"line":491},[299,3516,3517],{"class":929},"    provider",[299,3519,561],{"class":348},[299,3521,3522],{"class":323}," Provider",[299,3524,723],{"class":348},[299,3526,3527,3530,3532,3534],{"class":301,"line":502},[299,3528,3529],{"class":929},"    registry",[299,3531,561],{"class":348},[299,3533,2655],{"class":323},[299,3535,723],{"class":348},[299,3537,3538,3541,3543,3545],{"class":301,"line":507},[299,3539,3540],{"class":929},"    user_message",[299,3542,561],{"class":348},[299,3544,606],{"class":564},[299,3546,723],{"class":348},[299,3548,3549,3552,3554,3557,3560,3562,3564,3566],{"class":301,"line":513},[299,3550,3551],{"class":929},"    transcript",[299,3553,561],{"class":348},[299,3555,3556],{"class":323}," Transcript ",[299,3558,3559],{"class":389},"|",[299,3561,939],{"class":470},[299,3563,630],{"class":389},[299,3565,939],{"class":470},[299,3567,723],{"class":348},[299,3569,3570,3573,3575,3577,3579,3581,3583,3585],{"class":301,"line":519},[299,3571,3572],{"class":929},"    system",[299,3574,561],{"class":348},[299,3576,606],{"class":564},[299,3578,936],{"class":389},[299,3580,939],{"class":470},[299,3582,630],{"class":389},[299,3584,939],{"class":470},[299,3586,723],{"class":348},[299,3588,3589,3591,3593,3595],{"class":301,"line":525},[299,3590,669],{"class":348},[299,3592,672],{"class":348},[299,3594,606],{"class":564},[299,3596,488],{"class":348},[299,3598,3599,3601,3604,3606,3608],{"class":301,"line":531},[299,3600,1713],{"class":312},[299,3602,3603],{"class":323}," transcript ",[299,3605,1719],{"class":389},[299,3607,939],{"class":470},[299,3609,488],{"class":348},[299,3611,3612,3615,3617,3619,3621,3624,3626,3628],{"class":301,"line":537},[299,3613,3614],{"class":323},"        transcript ",[299,3616,390],{"class":389},[299,3618,3436],{"class":633},[299,3620,461],{"class":348},[299,3622,3623],{"class":464},"system",[299,3625,390],{"class":389},[299,3627,3623],{"class":633},[299,3629,474],{"class":348},[299,3631,3632,3634,3636,3638,3640,3643,3645,3648,3650,3653],{"class":301,"line":543},[299,3633,3551],{"class":323},[299,3635,717],{"class":348},[299,3637,1575],{"class":633},[299,3639,461],{"class":348},[299,3641,3642],{"class":633},"Message",[299,3644,717],{"class":348},[299,3646,3647],{"class":633},"user_text",[299,3649,461],{"class":348},[299,3651,3652],{"class":633},"user_message",[299,3654,3655],{"class":348},"))\n",[299,3657,3658],{"class":301,"line":549},[299,3659,331],{"emptyLinePlaceholder":330},[299,3661,3662,3664,3667,3669,3672,3674,3676],{"class":301,"line":555},[299,3663,1431],{"class":312},[299,3665,3666],{"class":323}," _ ",[299,3668,1442],{"class":312},[299,3670,3671],{"class":1799}," range",[299,3673,461],{"class":348},[299,3675,3490],{"class":1799},[299,3677,2342],{"class":348},[299,3679,3680,3683,3685,3688,3690,3693,3695,3698,3700,3703,3705,3708],{"class":301,"line":568},[299,3681,3682],{"class":323},"        response ",[299,3684,390],{"class":389},[299,3686,3687],{"class":323}," provider",[299,3689,717],{"class":348},[299,3691,3692],{"class":633},"complete",[299,3694,461],{"class":348},[299,3696,3697],{"class":633},"transcript",[299,3699,349],{"class":348},[299,3701,3702],{"class":633}," registry",[299,3704,717],{"class":348},[299,3706,3707],{"class":633},"schemas",[299,3709,3710],{"class":348},"())\n",[299,3712,3713],{"class":301,"line":578},[299,3714,331],{"emptyLinePlaceholder":330},[299,3716,3717,3719,3722,3724,3727],{"class":301,"line":589},[299,3718,1146],{"class":312},[299,3720,3721],{"class":323}," response",[299,3723,717],{"class":348},[299,3725,3726],{"class":720},"is_final",[299,3728,488],{"class":348},[299,3730,3731,3734,3736,3738,3740,3742,3744,3747,3749,3752],{"class":301,"line":611},[299,3732,3733],{"class":323},"            transcript",[299,3735,717],{"class":348},[299,3737,1575],{"class":633},[299,3739,461],{"class":348},[299,3741,3642],{"class":633},[299,3743,717],{"class":348},[299,3745,3746],{"class":633},"from_assistant_response",[299,3748,461],{"class":348},[299,3750,3751],{"class":633},"response",[299,3753,3655],{"class":348},[299,3755,3756,3758,3760,3762,3765,3767],{"class":301,"line":649},[299,3757,1821],{"class":312},[299,3759,3721],{"class":323},[299,3761,717],{"class":348},[299,3763,3764],{"class":720},"text",[299,3766,1129],{"class":389},[299,3768,3769],{"class":399}," \"\"\n",[299,3771,3772],{"class":301,"line":654},[299,3773,331],{"emptyLinePlaceholder":330},[299,3775,3776],{"class":301,"line":680},[299,3777,3778],{"class":305},"        # One assistant message with every ToolCall block from this turn.\n",[299,3780,3781,3784,3786,3788,3790,3792,3794,3796,3798,3800],{"class":301,"line":692},[299,3782,3783],{"class":323},"        transcript",[299,3785,717],{"class":348},[299,3787,1575],{"class":633},[299,3789,461],{"class":348},[299,3791,3642],{"class":633},[299,3793,717],{"class":348},[299,3795,3746],{"class":633},[299,3797,461],{"class":348},[299,3799,3751],{"class":633},[299,3801,3655],{"class":348},[299,3803,3804],{"class":301,"line":701},[299,3805,331],{"emptyLinePlaceholder":330},[299,3807,3808],{"class":301,"line":726},[299,3809,3810],{"class":305},"        # Dispatch each call in arrival order (Chapter 5 details the\n",[299,3812,3813],{"class":301,"line":746},[299,3814,3815],{"class":305},"        # ProviderResponse.tool_calls tuple; here it's usually one call).\n",[299,3817,3818,3820,3823,3825,3827,3829,3832],{"class":301,"line":766},[299,3819,2740],{"class":312},[299,3821,3822],{"class":323}," ref ",[299,3824,1442],{"class":312},[299,3826,3721],{"class":323},[299,3828,717],{"class":348},[299,3830,3831],{"class":720},"tool_calls",[299,3833,488],{"class":348},[299,3835,3836,3839,3841,3843,3845,3847,3849,3852,3854,3856,3858,3861,3863,3865,3867,3869,3871,3874],{"class":301,"line":1291},[299,3837,3838],{"class":323},"            result ",[299,3840,390],{"class":389},[299,3842,3702],{"class":323},[299,3844,717],{"class":348},[299,3846,3355],{"class":633},[299,3848,461],{"class":348},[299,3850,3851],{"class":633},"ref",[299,3853,717],{"class":348},[299,3855,707],{"class":720},[299,3857,349],{"class":348},[299,3859,3860],{"class":633}," ref",[299,3862,717],{"class":348},[299,3864,1805],{"class":720},[299,3866,349],{"class":348},[299,3868,3860],{"class":633},[299,3870,717],{"class":348},[299,3872,3873],{"class":720},"id",[299,3875,474],{"class":348},[299,3877,3878,3880,3882,3884,3886,3888,3890,3893,3895,3898],{"class":301,"line":1300},[299,3879,3733],{"class":323},[299,3881,717],{"class":348},[299,3883,1575],{"class":633},[299,3885,461],{"class":348},[299,3887,3642],{"class":633},[299,3889,717],{"class":348},[299,3891,3892],{"class":633},"tool_result",[299,3894,461],{"class":348},[299,3896,3897],{"class":633},"result",[299,3899,3655],{"class":348},[299,3901,3902],{"class":301,"line":1305},[299,3903,331],{"emptyLinePlaceholder":330},[299,3905,3906,3909,3912,3914,3916,3919,3921,3923,3925,3928],{"class":301,"line":1310},[299,3907,3908],{"class":312},"    raise",[299,3910,3911],{"class":564}," RuntimeError",[299,3913,461],{"class":348},[299,3915,1167],{"class":480},[299,3917,3918],{"class":403},"\"agent did not finish in ",[299,3920,1174],{"class":1173},[299,3922,3490],{"class":1799},[299,3924,1183],{"class":1173},[299,3926,3927],{"class":403}," iterations\"",[299,3929,474],{"class":348},[112,3931,3932],{},"The loop has gotten smaller, not larger. That's the point of the abstraction: the complexity moves into the registry, where it belongs, and the loop keeps its focus on the three decisions from Chapter 2.",[216,3934],{},[219,3936,3938],{"id":3937},"_46-a-real-toolset","4.6 A Real Toolset",[112,3940,3941],{},"Let's build the tools we'll actually use in later chapters, so each one is deliberate.",[290,3943,3945],{"className":292,"code":3944,"language":294,"meta":295,"style":295},"# src\u002Fharness\u002Ftools\u002Fstd.py\nfrom __future__ import annotations\n\nimport ast\nimport subprocess\nfrom pathlib import Path\n\nfrom .decorator import tool\n\n\n@tool(side_effects={\"read\"})\ndef calc(expression: str) -> str:\n    \"\"\"Evaluate a Python arithmetic expression.\n\n    Accepts: +, -, *, \u002F, **, parentheses, integer and float literals.\n    Does NOT allow function calls, imports, attribute access, subscripts,\n    comprehensions, names, or anything else not explicitly listed here.\n    Side effects: none. Safe to retry.\n    \"\"\"\n    ALLOWED = (\n        ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant,\n        ast.operator, ast.unaryop, ast.Load,\n    )\n    tree = ast.parse(expression, mode=\"eval\")\n    for node in ast.walk(tree):\n        if not isinstance(node, ALLOWED):\n            raise ValueError(f\"forbidden in expression: {type(node).__name__}\")\n    return str(eval(compile(tree, \"\u003Cexpr>\", mode=\"eval\"),\n                    {\"__builtins__\": {}}, {}))\n\n\n@tool(side_effects={\"read\"})\ndef read_file(path: str) -> str:\n    \"\"\"Read a UTF-8 text file and return its contents.\n\n    path: relative or absolute filesystem path.\n    Side effects: reads the filesystem, no writes.\n    Returns the file contents. For very large files, prefer chapter 11's\n    viewport reader.\n    \"\"\"\n    return Path(path).read_text(encoding=\"utf-8\")\n\n\n@tool(side_effects={\"write\"})\ndef write_file(path: str, content: str) -> str:\n    \"\"\"Overwrite a file with the given content.\n\n    path: relative or absolute filesystem path. The file will be CREATED\n    or OVERWRITTEN; its previous contents are lost.\n    Side effects: writes to the filesystem. Not safe to call twice with\n    different content expecting either version to survive.\n    \"\"\"\n    Path(path).write_text(content, encoding=\"utf-8\")\n    return f\"wrote {len(content)} bytes to {path}\"\n\n\n@tool(side_effects={\"read\", \"network\"})\ndef bash(command: str, timeout_seconds: int = 30) -> str:\n    \"\"\"Run a shell command in the current working directory.\n\n    command: a shell command line.\n    timeout_seconds: hard time limit; default 30, cap 300.\n    Side effects: MAY read\u002Fwrite files, MAY make network calls — depends on\n    the command. Caller is responsible for the blast radius.\n    Returns combined stdout+stderr with the exit code appended.\n    \"\"\"\n    timeout = min(int(timeout_seconds), 300)\n    result = subprocess.run(\n        command, shell=True, capture_output=True, text=True,\n        timeout=timeout,\n    )\n    return (f\"exit={result.returncode}\\n\"\n            f\"---stdout---\\n{result.stdout}\\n\"\n            f\"---stderr---\\n{result.stderr}\")\n",[119,3946,3947,3952,3962,3966,3972,3979,3991,3995,4007,4011,4015,4037,4059,4065,4069,4074,4079,4084,4089,4093,4103,4138,4164,4169,4199,4219,4238,4269,4309,4328,4332,4336,4358,4382,4389,4393,4398,4403,4408,4413,4417,4449,4453,4457,4479,4510,4517,4521,4526,4531,4536,4541,4545,4578,4613,4617,4621,4651,4689,4696,4700,4705,4710,4715,4720,4725,4729,4755,4771,4805,4817,4821,4848,4873],{"__ignoreMap":295},[299,3948,3949],{"class":301,"line":302},[299,3950,3951],{"class":305},"# src\u002Fharness\u002Ftools\u002Fstd.py\n",[299,3953,3954,3956,3958,3960],{"class":301,"line":309},[299,3955,313],{"class":312},[299,3957,317],{"class":316},[299,3959,320],{"class":312},[299,3961,324],{"class":323},[299,3963,3964],{"class":301,"line":327},[299,3965,331],{"emptyLinePlaceholder":330},[299,3967,3968,3970],{"class":301,"line":334},[299,3969,342],{"class":312},[299,3971,2283],{"class":323},[299,3973,3974,3976],{"class":301,"line":355},[299,3975,342],{"class":312},[299,3977,3978],{"class":323}," subprocess\n",[299,3980,3981,3983,3986,3988],{"class":301,"line":373},[299,3982,313],{"class":312},[299,3984,3985],{"class":323}," pathlib ",[299,3987,342],{"class":312},[299,3989,3990],{"class":323}," Path\n",[299,3992,3993],{"class":301,"line":378},[299,3994,331],{"emptyLinePlaceholder":330},[299,3996,3997,3999,4001,4003,4005],{"class":301,"line":383},[299,3998,313],{"class":312},[299,4000,892],{"class":348},[299,4002,2185],{"class":323},[299,4004,342],{"class":312},[299,4006,2190],{"class":323},[299,4008,4009],{"class":301,"line":440},[299,4010,331],{"emptyLinePlaceholder":330},[299,4012,4013],{"class":301,"line":445},[299,4014,331],{"emptyLinePlaceholder":330},[299,4016,4017,4019,4021,4023,4025,4027,4029,4031,4033,4035],{"class":301,"line":450},[299,4018,454],{"class":453},[299,4020,2205],{"class":457},[299,4022,461],{"class":348},[299,4024,1280],{"class":464},[299,4026,390],{"class":389},[299,4028,1174],{"class":348},[299,4030,400],{"class":399},[299,4032,404],{"class":403},[299,4034,400],{"class":399},[299,4036,2222],{"class":348},[299,4038,4039,4041,4043,4045,4047,4049,4051,4053,4055,4057],{"class":301,"line":477},[299,4040,918],{"class":480},[299,4042,2229],{"class":457},[299,4044,461],{"class":348},[299,4046,2234],{"class":929},[299,4048,561],{"class":348},[299,4050,606],{"class":564},[299,4052,669],{"class":348},[299,4054,672],{"class":348},[299,4056,606],{"class":564},[299,4058,488],{"class":348},[299,4060,4061,4063],{"class":301,"line":491},[299,4062,495],{"class":494},[299,4064,2253],{"class":498},[299,4066,4067],{"class":301,"line":502},[299,4068,331],{"emptyLinePlaceholder":330},[299,4070,4071],{"class":301,"line":507},[299,4072,4073],{"class":498},"    Accepts: +, -, *, \u002F, **, parentheses, integer and float literals.\n",[299,4075,4076],{"class":301,"line":513},[299,4077,4078],{"class":498},"    Does NOT allow function calls, imports, attribute access, subscripts,\n",[299,4080,4081],{"class":301,"line":519},[299,4082,4083],{"class":498},"    comprehensions, names, or anything else not explicitly listed here.\n",[299,4085,4086],{"class":301,"line":525},[299,4087,4088],{"class":498},"    Side effects: none. Safe to retry.\n",[299,4090,4091],{"class":301,"line":531},[299,4092,552],{"class":494},[299,4094,4095,4098,4100],{"class":301,"line":537},[299,4096,4097],{"class":316},"    ALLOWED",[299,4099,630],{"class":389},[299,4101,4102],{"class":348}," (\n",[299,4104,4105,4108,4110,4112,4114,4116,4118,4120,4122,4124,4126,4128,4130,4132,4134,4136],{"class":301,"line":543},[299,4106,4107],{"class":323},"        ast",[299,4109,717],{"class":348},[299,4111,2368],{"class":720},[299,4113,349],{"class":348},[299,4115,2293],{"class":323},[299,4117,717],{"class":348},[299,4119,2377],{"class":720},[299,4121,349],{"class":348},[299,4123,2293],{"class":323},[299,4125,717],{"class":348},[299,4127,2386],{"class":720},[299,4129,349],{"class":348},[299,4131,2293],{"class":323},[299,4133,717],{"class":348},[299,4135,2407],{"class":720},[299,4137,723],{"class":348},[299,4139,4140,4142,4144,4146,4148,4150,4152,4154,4156,4158,4160,4162],{"class":301,"line":549},[299,4141,4107],{"class":323},[299,4143,717],{"class":348},[299,4145,2416],{"class":720},[299,4147,349],{"class":348},[299,4149,2293],{"class":323},[299,4151,717],{"class":348},[299,4153,2427],{"class":720},[299,4155,349],{"class":348},[299,4157,2293],{"class":323},[299,4159,717],{"class":348},[299,4161,2436],{"class":720},[299,4163,723],{"class":348},[299,4165,4166],{"class":301,"line":555},[299,4167,4168],{"class":348},"    )\n",[299,4170,4171,4173,4175,4177,4179,4181,4183,4185,4187,4189,4191,4193,4195,4197],{"class":301,"line":568},[299,4172,2288],{"class":323},[299,4174,390],{"class":389},[299,4176,2293],{"class":323},[299,4178,717],{"class":348},[299,4180,2298],{"class":633},[299,4182,461],{"class":348},[299,4184,2234],{"class":633},[299,4186,349],{"class":348},[299,4188,2307],{"class":464},[299,4190,390],{"class":389},[299,4192,400],{"class":399},[299,4194,2314],{"class":403},[299,4196,400],{"class":399},[299,4198,474],{"class":348},[299,4200,4201,4203,4205,4207,4209,4211,4213,4215,4217],{"class":301,"line":578},[299,4202,1431],{"class":312},[299,4204,2325],{"class":323},[299,4206,1442],{"class":312},[299,4208,2293],{"class":323},[299,4210,717],{"class":348},[299,4212,2334],{"class":633},[299,4214,461],{"class":348},[299,4216,2339],{"class":633},[299,4218,2342],{"class":348},[299,4220,4221,4223,4225,4227,4229,4231,4233,4236],{"class":301,"line":589},[299,4222,1146],{"class":312},[299,4224,1149],{"class":389},[299,4226,2351],{"class":1799},[299,4228,461],{"class":348},[299,4230,2356],{"class":633},[299,4232,349],{"class":348},[299,4234,4235],{"class":1799}," ALLOWED",[299,4237,2342],{"class":348},[299,4239,4240,4242,4244,4246,4248,4251,4253,4255,4257,4259,4261,4263,4265,4267],{"class":301,"line":611},[299,4241,1159],{"class":312},[299,4243,1162],{"class":564},[299,4245,461],{"class":348},[299,4247,1167],{"class":480},[299,4249,4250],{"class":403},"\"forbidden in expression: ",[299,4252,1174],{"class":1173},[299,4254,1597],{"class":564},[299,4256,461],{"class":348},[299,4258,2356],{"class":633},[299,4260,1135],{"class":348},[299,4262,2465],{"class":316},[299,4264,1183],{"class":1173},[299,4266,400],{"class":403},[299,4268,474],{"class":348},[299,4270,4271,4273,4275,4277,4279,4281,4283,4285,4287,4289,4291,4293,4295,4297,4299,4301,4303,4305,4307],{"class":301,"line":649},[299,4272,1294],{"class":312},[299,4274,606],{"class":564},[299,4276,461],{"class":348},[299,4278,2314],{"class":1799},[299,4280,461],{"class":348},[299,4282,2486],{"class":1799},[299,4284,461],{"class":348},[299,4286,2339],{"class":633},[299,4288,349],{"class":348},[299,4290,411],{"class":399},[299,4292,2497],{"class":403},[299,4294,400],{"class":399},[299,4296,349],{"class":348},[299,4298,2307],{"class":464},[299,4300,390],{"class":389},[299,4302,400],{"class":399},[299,4304,2314],{"class":403},[299,4306,400],{"class":399},[299,4308,1283],{"class":348},[299,4310,4311,4314,4316,4318,4320,4322,4325],{"class":301,"line":654},[299,4312,4313],{"class":348},"                    {",[299,4315,400],{"class":399},[299,4317,2521],{"class":403},[299,4319,400],{"class":399},[299,4321,561],{"class":348},[299,4323,4324],{"class":348}," {}},",[299,4326,4327],{"class":348}," {}))\n",[299,4329,4330],{"class":301,"line":680},[299,4331,331],{"emptyLinePlaceholder":330},[299,4333,4334],{"class":301,"line":692},[299,4335,331],{"emptyLinePlaceholder":330},[299,4337,4338,4340,4342,4344,4346,4348,4350,4352,4354,4356],{"class":301,"line":701},[299,4339,454],{"class":453},[299,4341,2205],{"class":457},[299,4343,461],{"class":348},[299,4345,1280],{"class":464},[299,4347,390],{"class":389},[299,4349,1174],{"class":348},[299,4351,400],{"class":399},[299,4353,404],{"class":403},[299,4355,400],{"class":399},[299,4357,2222],{"class":348},[299,4359,4360,4362,4365,4367,4370,4372,4374,4376,4378,4380],{"class":301,"line":726},[299,4361,918],{"class":480},[299,4363,4364],{"class":457}," read_file",[299,4366,461],{"class":348},[299,4368,4369],{"class":929},"path",[299,4371,561],{"class":348},[299,4373,606],{"class":564},[299,4375,669],{"class":348},[299,4377,672],{"class":348},[299,4379,606],{"class":564},[299,4381,488],{"class":348},[299,4383,4384,4386],{"class":301,"line":746},[299,4385,495],{"class":494},[299,4387,4388],{"class":498},"Read a UTF-8 text file and return its contents.\n",[299,4390,4391],{"class":301,"line":766},[299,4392,331],{"emptyLinePlaceholder":330},[299,4394,4395],{"class":301,"line":1291},[299,4396,4397],{"class":498},"    path: relative or absolute filesystem path.\n",[299,4399,4400],{"class":301,"line":1300},[299,4401,4402],{"class":498},"    Side effects: reads the filesystem, no writes.\n",[299,4404,4405],{"class":301,"line":1305},[299,4406,4407],{"class":498},"    Returns the file contents. For very large files, prefer chapter 11's\n",[299,4409,4410],{"class":301,"line":1310},[299,4411,4412],{"class":498},"    viewport reader.\n",[299,4414,4415],{"class":301,"line":1341},[299,4416,552],{"class":494},[299,4418,4419,4421,4424,4426,4428,4430,4433,4435,4438,4440,4442,4445,4447],{"class":301,"line":1363},[299,4420,1294],{"class":312},[299,4422,4423],{"class":633}," Path",[299,4425,461],{"class":348},[299,4427,4369],{"class":633},[299,4429,1135],{"class":348},[299,4431,4432],{"class":633},"read_text",[299,4434,461],{"class":348},[299,4436,4437],{"class":464},"encoding",[299,4439,390],{"class":389},[299,4441,400],{"class":399},[299,4443,4444],{"class":403},"utf-8",[299,4446,400],{"class":399},[299,4448,474],{"class":348},[299,4450,4451],{"class":301,"line":1380},[299,4452,331],{"emptyLinePlaceholder":330},[299,4454,4455],{"class":301,"line":1406},[299,4456,331],{"emptyLinePlaceholder":330},[299,4458,4459,4461,4463,4465,4467,4469,4471,4473,4475,4477],{"class":301,"line":1428},[299,4460,454],{"class":453},[299,4462,2205],{"class":457},[299,4464,461],{"class":348},[299,4466,1280],{"class":464},[299,4468,390],{"class":389},[299,4470,1174],{"class":348},[299,4472,400],{"class":399},[299,4474,414],{"class":403},[299,4476,400],{"class":399},[299,4478,2222],{"class":348},[299,4480,4481,4483,4486,4488,4490,4492,4494,4496,4498,4500,4502,4504,4506,4508],{"class":301,"line":1461},[299,4482,918],{"class":480},[299,4484,4485],{"class":457}," write_file",[299,4487,461],{"class":348},[299,4489,4369],{"class":929},[299,4491,561],{"class":348},[299,4493,606],{"class":564},[299,4495,349],{"class":348},[299,4497,3335],{"class":929},[299,4499,561],{"class":348},[299,4501,606],{"class":564},[299,4503,669],{"class":348},[299,4505,672],{"class":348},[299,4507,606],{"class":564},[299,4509,488],{"class":348},[299,4511,4512,4514],{"class":301,"line":1480},[299,4513,495],{"class":494},[299,4515,4516],{"class":498},"Overwrite a file with the given content.\n",[299,4518,4519],{"class":301,"line":1486},[299,4520,331],{"emptyLinePlaceholder":330},[299,4522,4523],{"class":301,"line":1513},[299,4524,4525],{"class":498},"    path: relative or absolute filesystem path. The file will be CREATED\n",[299,4527,4528],{"class":301,"line":1537},[299,4529,4530],{"class":498},"    or OVERWRITTEN; its previous contents are lost.\n",[299,4532,4533],{"class":301,"line":1567},[299,4534,4535],{"class":498},"    Side effects: writes to the filesystem. Not safe to call twice with\n",[299,4537,4538],{"class":301,"line":1584},[299,4539,4540],{"class":498},"    different content expecting either version to survive.\n",[299,4542,4543],{"class":301,"line":1591},[299,4544,552],{"class":494},[299,4546,4547,4550,4552,4554,4556,4559,4561,4563,4565,4568,4570,4572,4574,4576],{"class":301,"line":1613},[299,4548,4549],{"class":633},"    Path",[299,4551,461],{"class":348},[299,4553,4369],{"class":633},[299,4555,1135],{"class":348},[299,4557,4558],{"class":633},"write_text",[299,4560,461],{"class":348},[299,4562,3340],{"class":633},[299,4564,349],{"class":348},[299,4566,4567],{"class":464}," encoding",[299,4569,390],{"class":389},[299,4571,400],{"class":399},[299,4573,4444],{"class":403},[299,4575,400],{"class":399},[299,4577,474],{"class":348},[299,4579,4580,4582,4585,4588,4590,4593,4595,4597,4599,4601,4604,4606,4608,4610],{"class":301,"line":1630},[299,4581,1294],{"class":312},[299,4583,4584],{"class":480}," f",[299,4586,4587],{"class":403},"\"wrote ",[299,4589,1174],{"class":1173},[299,4591,4592],{"class":1799},"len",[299,4594,461],{"class":348},[299,4596,3340],{"class":633},[299,4598,669],{"class":348},[299,4600,1183],{"class":1173},[299,4602,4603],{"class":403}," bytes to ",[299,4605,1174],{"class":1173},[299,4607,4369],{"class":323},[299,4609,1183],{"class":1173},[299,4611,4612],{"class":403},"\"\n",[299,4614,4615],{"class":301,"line":1647},[299,4616,331],{"emptyLinePlaceholder":330},[299,4618,4619],{"class":301,"line":1653},[299,4620,331],{"emptyLinePlaceholder":330},[299,4622,4623,4625,4627,4629,4631,4633,4635,4637,4639,4641,4643,4645,4647,4649],{"class":301,"line":1658},[299,4624,454],{"class":453},[299,4626,2205],{"class":457},[299,4628,461],{"class":348},[299,4630,1280],{"class":464},[299,4632,390],{"class":389},[299,4634,1174],{"class":348},[299,4636,400],{"class":399},[299,4638,404],{"class":403},[299,4640,400],{"class":399},[299,4642,349],{"class":348},[299,4644,411],{"class":399},[299,4646,423],{"class":403},[299,4648,400],{"class":399},[299,4650,2222],{"class":348},[299,4652,4653,4655,4658,4660,4663,4665,4667,4669,4672,4674,4676,4678,4681,4683,4685,4687],{"class":301,"line":1663},[299,4654,918],{"class":480},[299,4656,4657],{"class":457}," bash",[299,4659,461],{"class":348},[299,4661,4662],{"class":929},"command",[299,4664,561],{"class":348},[299,4666,606],{"class":564},[299,4668,349],{"class":348},[299,4670,4671],{"class":929}," timeout_seconds",[299,4673,561],{"class":348},[299,4675,1886],{"class":564},[299,4677,630],{"class":389},[299,4679,4680],{"class":1173}," 30",[299,4682,669],{"class":348},[299,4684,672],{"class":348},[299,4686,606],{"class":564},[299,4688,488],{"class":348},[299,4690,4691,4693],{"class":301,"line":1688},[299,4692,495],{"class":494},[299,4694,4695],{"class":498},"Run a shell command in the current working directory.\n",[299,4697,4698],{"class":301,"line":1710},[299,4699,331],{"emptyLinePlaceholder":330},[299,4701,4702],{"class":301,"line":1740},[299,4703,4704],{"class":498},"    command: a shell command line.\n",[299,4706,4707],{"class":301,"line":1794},[299,4708,4709],{"class":498},"    timeout_seconds: hard time limit; default 30, cap 300.\n",[299,4711,4712],{"class":301,"line":1818},[299,4713,4714],{"class":498},"    Side effects: MAY read\u002Fwrite files, MAY make network calls — depends on\n",[299,4716,4717],{"class":301,"line":1838},[299,4718,4719],{"class":498},"    the command. Caller is responsible for the blast radius.\n",[299,4721,4722],{"class":301,"line":1852},[299,4723,4724],{"class":498},"    Returns combined stdout+stderr with the exit code appended.\n",[299,4726,4727],{"class":301,"line":1877},[299,4728,552],{"class":494},[299,4730,4731,4734,4736,4739,4741,4743,4745,4748,4750,4753],{"class":301,"line":1891},[299,4732,4733],{"class":323},"    timeout ",[299,4735,390],{"class":389},[299,4737,4738],{"class":1799}," min",[299,4740,461],{"class":348},[299,4742,2135],{"class":564},[299,4744,461],{"class":348},[299,4746,4747],{"class":633},"timeout_seconds",[299,4749,2514],{"class":348},[299,4751,4752],{"class":1173}," 300",[299,4754,474],{"class":348},[299,4756,4757,4760,4762,4765,4767,4769],{"class":301,"line":1915},[299,4758,4759],{"class":323},"    result ",[299,4761,390],{"class":389},[299,4763,4764],{"class":323}," subprocess",[299,4766,717],{"class":348},[299,4768,3138],{"class":633},[299,4770,924],{"class":348},[299,4772,4773,4776,4778,4781,4783,4785,4787,4790,4792,4794,4796,4799,4801,4803],{"class":301,"line":1929},[299,4774,4775],{"class":633},"        command",[299,4777,349],{"class":348},[299,4779,4780],{"class":464}," shell",[299,4782,390],{"class":389},[299,4784,471],{"class":470},[299,4786,349],{"class":348},[299,4788,4789],{"class":464}," capture_output",[299,4791,390],{"class":389},[299,4793,471],{"class":470},[299,4795,349],{"class":348},[299,4797,4798],{"class":464}," text",[299,4800,390],{"class":389},[299,4802,471],{"class":470},[299,4804,723],{"class":348},[299,4806,4807,4810,4812,4815],{"class":301,"line":1953},[299,4808,4809],{"class":464},"        timeout",[299,4811,390],{"class":389},[299,4813,4814],{"class":633},"timeout",[299,4816,723],{"class":348},[299,4818,4819],{"class":301,"line":1967},[299,4820,4168],{"class":348},[299,4822,4823,4825,4827,4829,4832,4834,4836,4838,4841,4843,4846],{"class":301,"line":1991},[299,4824,1294],{"class":312},[299,4826,1119],{"class":348},[299,4828,1167],{"class":480},[299,4830,4831],{"class":403},"\"exit=",[299,4833,1174],{"class":1173},[299,4835,3897],{"class":323},[299,4837,717],{"class":348},[299,4839,4840],{"class":720},"returncode",[299,4842,1183],{"class":1173},[299,4844,4845],{"class":316},"\\n",[299,4847,4612],{"class":403},[299,4849,4850,4853,4856,4858,4860,4862,4864,4867,4869,4871],{"class":301,"line":2004},[299,4851,4852],{"class":480},"            f",[299,4854,4855],{"class":403},"\"---stdout---",[299,4857,4845],{"class":316},[299,4859,1174],{"class":1173},[299,4861,3897],{"class":323},[299,4863,717],{"class":348},[299,4865,4866],{"class":720},"stdout",[299,4868,1183],{"class":1173},[299,4870,4845],{"class":316},[299,4872,4612],{"class":403},[299,4874,4875,4877,4880,4882,4884,4886,4888,4891,4893,4895],{"class":301,"line":2059},[299,4876,4852],{"class":480},[299,4878,4879],{"class":403},"\"---stderr---",[299,4881,4845],{"class":316},[299,4883,1174],{"class":1173},[299,4885,3897],{"class":323},[299,4887,717],{"class":348},[299,4889,4890],{"class":720},"stderr",[299,4892,1183],{"class":1173},[299,4894,400],{"class":403},[299,4896,474],{"class":348},[112,4898,4899],{},"Three things to notice.",[112,4901,4902,4907,4908,4911],{},[237,4903,4904,4906],{},[119,4905,3380],{}," is now safe enough to use."," The AST walk forbids calls, attributes, imports, and names — ",[119,4909,4910],{},"eval(\"__import__('os').system('rm -rf \u002F')\")"," fails parse-validation before evaluation. It's not a sandbox (Chapter 14 builds one), but it's defensible against accidents.",[112,4913,4914,4923,4924,4926],{},[237,4915,4916,4919,4920,717],{},[119,4917,4918],{},"bash"," is tagged ",[119,4921,4922],{},"read, network"," That's a conservative guess. In reality, ",[119,4925,4918],{}," is tagged everything — it can mutate, read, write, network, depending on the command. We'll revisit this in Chapter 14; for now we mark it with the tags we know apply most of the time. The permission layer can tighten it later.",[112,4928,4929,4932],{},[237,4930,4931],{},"Docstrings do double duty."," They're the description the model reads and the documentation the human reads. Notice how each one declares preconditions, side effects, and failure modes — that pattern is what makes a tool hard to misuse.",[216,4934],{},[219,4936,4938],{"id":4937},"_47-running-against-all-three-providers","4.7 Running Against All Three Providers",[112,4940,4941],{},"An end-to-end example using the Chapter 3 providers and the new tool system:",[290,4943,4945],{"className":292,"code":4944,"language":294,"meta":295,"style":295},"# examples\u002Fch04_tools.py\nimport os\n\nfrom harness.agent import run\nfrom harness.providers.anthropic import AnthropicProvider\nfrom harness.providers.openai import OpenAIProvider\nfrom harness.providers.local import LocalProvider\nfrom harness.tools.registry import ToolRegistry\nfrom harness.tools.std import calc, read_file, write_file, bash\n\n\nprovider = {\n    \"anthropic\": AnthropicProvider,\n    \"openai\": OpenAIProvider,\n    \"local\": LocalProvider,\n}[os.environ.get(\"PROVIDER\", \"anthropic\")]()\n\nregistry = ToolRegistry(tools=[calc, read_file, write_file, bash])\n\nanswer = run(\n    provider=provider,\n    registry=registry,\n    user_message=(\n        \"Write the string 'hello world' to \u002Ftmp\u002Fch04-test.txt, \"\n        \"then read it back, then tell me what the file contained.\"\n    ),\n)\nprint(answer)\n",[119,4946,4947,4952,4959,4963,4979,4999,5019,5039,5057,5089,5093,5097,5106,5123,5139,5155,5192,5196,5228,5232,5243,5254,5265,5273,5282,5291,5296,5300],{"__ignoreMap":295},[299,4948,4949],{"class":301,"line":302},[299,4950,4951],{"class":305},"# examples\u002Fch04_tools.py\n",[299,4953,4954,4956],{"class":301,"line":309},[299,4955,342],{"class":312},[299,4957,4958],{"class":323}," os\n",[299,4960,4961],{"class":301,"line":327},[299,4962,331],{"emptyLinePlaceholder":330},[299,4964,4965,4967,4969,4971,4974,4976],{"class":301,"line":334},[299,4966,313],{"class":312},[299,4968,2175],{"class":323},[299,4970,717],{"class":348},[299,4972,4973],{"class":323},"agent ",[299,4975,342],{"class":312},[299,4977,4978],{"class":323}," run\n",[299,4980,4981,4983,4985,4987,4989,4991,4994,4996],{"class":301,"line":355},[299,4982,313],{"class":312},[299,4984,2175],{"class":323},[299,4986,717],{"class":348},[299,4988,3450],{"class":323},[299,4990,717],{"class":348},[299,4992,4993],{"class":323},"anthropic ",[299,4995,342],{"class":312},[299,4997,4998],{"class":323}," AnthropicProvider\n",[299,5000,5001,5003,5005,5007,5009,5011,5014,5016],{"class":301,"line":373},[299,5002,313],{"class":312},[299,5004,2175],{"class":323},[299,5006,717],{"class":348},[299,5008,3450],{"class":323},[299,5010,717],{"class":348},[299,5012,5013],{"class":323},"openai ",[299,5015,342],{"class":312},[299,5017,5018],{"class":323}," OpenAIProvider\n",[299,5020,5021,5023,5025,5027,5029,5031,5034,5036],{"class":301,"line":378},[299,5022,313],{"class":312},[299,5024,2175],{"class":323},[299,5026,717],{"class":348},[299,5028,3450],{"class":323},[299,5030,717],{"class":348},[299,5032,5033],{"class":323},"local ",[299,5035,342],{"class":312},[299,5037,5038],{"class":323}," LocalProvider\n",[299,5040,5041,5043,5045,5047,5049,5051,5053,5055],{"class":301,"line":383},[299,5042,313],{"class":312},[299,5044,2175],{"class":323},[299,5046,717],{"class":348},[299,5048,2180],{"class":323},[299,5050,717],{"class":348},[299,5052,3472],{"class":323},[299,5054,342],{"class":312},[299,5056,3477],{"class":323},[299,5058,5059,5061,5063,5065,5067,5069,5072,5074,5076,5078,5080,5082,5084,5086],{"class":301,"line":440},[299,5060,313],{"class":312},[299,5062,2175],{"class":323},[299,5064,717],{"class":348},[299,5066,2180],{"class":323},[299,5068,717],{"class":348},[299,5070,5071],{"class":323},"std ",[299,5073,342],{"class":312},[299,5075,2229],{"class":323},[299,5077,349],{"class":348},[299,5079,4364],{"class":323},[299,5081,349],{"class":348},[299,5083,4485],{"class":323},[299,5085,349],{"class":348},[299,5087,5088],{"class":323}," bash\n",[299,5090,5091],{"class":301,"line":445},[299,5092,331],{"emptyLinePlaceholder":330},[299,5094,5095],{"class":301,"line":450},[299,5096,331],{"emptyLinePlaceholder":330},[299,5098,5099,5102,5104],{"class":301,"line":477},[299,5100,5101],{"class":323},"provider ",[299,5103,390],{"class":389},[299,5105,698],{"class":348},[299,5107,5108,5111,5114,5116,5118,5121],{"class":301,"line":491},[299,5109,5110],{"class":399},"    \"",[299,5112,5113],{"class":403},"anthropic",[299,5115,400],{"class":399},[299,5117,561],{"class":348},[299,5119,5120],{"class":323}," AnthropicProvider",[299,5122,723],{"class":348},[299,5124,5125,5127,5130,5132,5134,5137],{"class":301,"line":502},[299,5126,5110],{"class":399},[299,5128,5129],{"class":403},"openai",[299,5131,400],{"class":399},[299,5133,561],{"class":348},[299,5135,5136],{"class":323}," OpenAIProvider",[299,5138,723],{"class":348},[299,5140,5141,5143,5146,5148,5150,5153],{"class":301,"line":507},[299,5142,5110],{"class":399},[299,5144,5145],{"class":403},"local",[299,5147,400],{"class":399},[299,5149,561],{"class":348},[299,5151,5152],{"class":323}," LocalProvider",[299,5154,723],{"class":348},[299,5156,5157,5160,5163,5165,5168,5170,5172,5174,5176,5179,5181,5183,5185,5187,5189],{"class":301,"line":513},[299,5158,5159],{"class":348},"}[",[299,5161,5162],{"class":323},"os",[299,5164,717],{"class":348},[299,5166,5167],{"class":720},"environ",[299,5169,717],{"class":348},[299,5171,1499],{"class":633},[299,5173,461],{"class":348},[299,5175,400],{"class":399},[299,5177,5178],{"class":403},"PROVIDER",[299,5180,400],{"class":399},[299,5182,349],{"class":348},[299,5184,411],{"class":399},[299,5186,5113],{"class":403},[299,5188,400],{"class":399},[299,5190,5191],{"class":348},")]()\n",[299,5193,5194],{"class":301,"line":519},[299,5195,331],{"emptyLinePlaceholder":330},[299,5197,5198,5200,5202,5204,5206,5208,5210,5212,5214,5216,5218,5220,5222,5224,5226],{"class":301,"line":525},[299,5199,3472],{"class":323},[299,5201,390],{"class":389},[299,5203,2655],{"class":633},[299,5205,461],{"class":348},[299,5207,2180],{"class":464},[299,5209,390],{"class":389},[299,5211,396],{"class":348},[299,5213,3380],{"class":633},[299,5215,349],{"class":348},[299,5217,4364],{"class":633},[299,5219,349],{"class":348},[299,5221,4485],{"class":633},[299,5223,349],{"class":348},[299,5225,4657],{"class":633},[299,5227,1835],{"class":348},[299,5229,5230],{"class":301,"line":531},[299,5231,331],{"emptyLinePlaceholder":330},[299,5233,5234,5237,5239,5241],{"class":301,"line":537},[299,5235,5236],{"class":323},"answer ",[299,5238,390],{"class":389},[299,5240,3510],{"class":633},[299,5242,924],{"class":348},[299,5244,5245,5247,5249,5252],{"class":301,"line":543},[299,5246,3517],{"class":464},[299,5248,390],{"class":389},[299,5250,5251],{"class":633},"provider",[299,5253,723],{"class":348},[299,5255,5256,5258,5260,5263],{"class":301,"line":549},[299,5257,3529],{"class":464},[299,5259,390],{"class":389},[299,5261,5262],{"class":633},"registry",[299,5264,723],{"class":348},[299,5266,5267,5269,5271],{"class":301,"line":555},[299,5268,3540],{"class":464},[299,5270,390],{"class":389},[299,5272,924],{"class":348},[299,5274,5275,5277,5280],{"class":301,"line":568},[299,5276,1594],{"class":399},[299,5278,5279],{"class":403},"Write the string 'hello world' to \u002Ftmp\u002Fch04-test.txt, ",[299,5281,4612],{"class":399},[299,5283,5284,5286,5289],{"class":301,"line":578},[299,5285,1594],{"class":399},[299,5287,5288],{"class":403},"then read it back, then tell me what the file contained.",[299,5290,4612],{"class":399},[299,5292,5293],{"class":301,"line":589},[299,5294,5295],{"class":348},"    ),\n",[299,5297,5298],{"class":301,"line":611},[299,5299,474],{"class":348},[299,5301,5302,5305,5307,5310],{"class":301,"line":649},[299,5303,5304],{"class":1799},"print",[299,5306,461],{"class":348},[299,5308,5309],{"class":633},"answer",[299,5311,474],{"class":348},[112,5313,5314],{},"Switching providers is still one environment variable, and the tool definitions are unchanged regardless of which provider answers — that's the layering from Chapter 3 paying off against a more interesting workload than the single-tool calculator demo.",[112,5316,5317],{},"Commit:",[290,5319,5322],{"className":5320,"code":5321,"language":4918,"meta":295,"style":295},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","git add -A && git commit -m \"ch04: Tool abstraction + ToolRegistry + std toolset\"\ngit tag ch04-tools\n",[119,5323,5324,5354],{"__ignoreMap":295},[299,5325,5326,5329,5331,5335,5338,5341,5344,5347,5349,5352],{"class":301,"line":302},[299,5327,5328],{"class":484},"git",[299,5330,2779],{"class":403},[299,5332,5334],{"class":5333},"stzsN"," -A",[299,5336,5337],{"class":348}," &&",[299,5339,5340],{"class":484}," git",[299,5342,5343],{"class":403}," commit",[299,5345,5346],{"class":5333}," -m",[299,5348,411],{"class":399},[299,5350,5351],{"class":403},"ch04: Tool abstraction + ToolRegistry + std toolset",[299,5353,4612],{"class":399},[299,5355,5356,5358,5361],{"class":301,"line":309},[299,5357,5328],{"class":484},[299,5359,5360],{"class":403}," tag",[299,5362,5363],{"class":403}," ch04-tools\n",[216,5365],{},[219,5367,5369],{"id":5368},"_48-a-note-on-tool-count","4.8 A Note on Tool Count",[112,5371,5372,5373,5378,5379,5382],{},"We have four tools, and that is intentional. ",[227,5374,5377],{"href":5375,"rel":5376},"https:\u002F\u002Fwww.jenova.ai\u002Fen\u002Fresources\u002Fmcp-tool-scalability-problem",[231],"Jenova AI's 2025 \"AI Tool Overload\" analysis"," put hard numbers on the problem: model tool-selection accuracy drops off a cliff somewhere between 20 and 50 tools, and the cliff is steep enough that a 100-tool agent performs worse than a 10-tool one by a wide margin. The name of that analysis is not an accident — it sits inside the broader context of Anthropic's ",[237,5380,5381],{},"Model Context Protocol"," (MCP), introduced in November 2024 as the industry's attempt at a standardized tool-interop protocol. MCP lets an agent connect to third-party tool servers without custom adapters per vendor, which is enormously useful, but it also makes the tool-count problem easier to accidentally trigger: once tools become cheap to add via an external registry, adding them starts to feel free. Chapter 13 picks up MCP in detail, including the selector machinery that keeps an MCP-heavy agent below the cliff.",[112,5384,5385],{},"Our discipline for the rest of the book follows from the Jenova finding directly:",[776,5387,5388,5391,5394],{},[137,5389,5390],{},"Every new tool has to earn a chapter's worth of justification.",[137,5392,5393],{},"When two tools do similar things, merge them or kill one.",[137,5395,5396],{},"Tool schemas are part of the context budget; we'll start counting them in Chapter 7.",[112,5398,5399],{},"Chapter 12 builds the dynamic tool loader that scales past this cliff for systems that genuinely need 50+ tools. Until then, we hold the line at a handful.",[216,5401],{},[219,5403,5405],{"id":5404},"_49-try-it-yourself","4.9 Try It Yourself",[134,5407,5408,5418,5435],{},[137,5409,5410,5413,5414,5417],{},[237,5411,5412],{},"Write a tool that tells a model about itself."," Add a ",[119,5415,5416],{},"list_tools"," tool that returns the names and descriptions of every tool in the registry. Watch what happens when the agent gets confused and calls this tool to re-read its options. Is the behavior useful or noisy?",[137,5419,5420,5423,5424,5427,5428,5430,5431,5434],{},[237,5421,5422],{},"Stress-test schema inference."," Write a tool whose function signature has a ",[119,5425,5426],{},"list[dict[str, int]]"," parameter. What does ",[119,5429,2128],{}," produce? Is it correct? If not, write the version that would be. (Spoiler: you've just reinvented a small fraction of Pydantic's ",[119,5432,5433],{},"TypeAdapter",".)",[137,5436,5437,5440,5441,5444],{},[237,5438,5439],{},"Break the contract and watch the model react."," Take ",[119,5442,5443],{},"write_file"," and change its docstring to say \"appends to the file.\" Don't change the implementation. Prompt the agent to \"add a note to \u002Ftmp\u002Fnote.txt without losing what's there.\" Observe how the agent uses the tool based on its (now-lying) description. What does this tell you about how much the model trusts descriptions?",[216,5446],{},[5448,5449,5450,5456],"what-you-understand",{},[112,5451,5452,5453,5455],{},"Tools are first-class objects with schemas, side-effect declarations, and docstring-driven descriptions. A ",[119,5454,145],{}," dispatches by name and converts failures into structured tool results rather than letting exceptions tear down the loop. Your agent now has a defensible toolset — calc, read_file, write_file, bash — each with a clear contract and a declared blast radius.",[112,5457,5458,5459,3377,5462,5465,5466,5469],{},"What's still missing. The registry dispatches unknown tools gracefully, but it doesn't check argument shapes before running. A malformed tool call (",[119,5460,5461],{},"{\"expr\": \"...\"}",[119,5463,5464],{},"{\"expression\": \"...\"}",") fails inside the function with a ",[119,5467,5468],{},"TypeError",", which we convert to a string, but the model gets the error late. Chapter 6 adds pre-dispatch validation and tool-call loop detection. Before that, though, Chapter 5 closes out the provider-facing work we started: streaming, interruption, and error handling.",[5471,5472,5473],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .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 .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 .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s2W-s, html code.shiki .s2W-s{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sithA, html code.shiki .sithA{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .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 .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--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 .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":295,"searchDepth":309,"depth":309,"links":5475},[5476,5477,5478,5479,5480,5481,5482,5483,5484],{"id":221,"depth":309,"text":222},{"id":287,"depth":309,"text":288},{"id":821,"depth":309,"text":822},{"id":2536,"depth":309,"text":2537},{"id":3386,"depth":309,"text":3387},{"id":3937,"depth":309,"text":3938},{"id":4937,"depth":309,"text":4938},{"id":5368,"depth":309,"text":5369},{"id":5404,"depth":309,"text":5405},"Previously: typed messages, typed transcripts, three provider adapters. The loop no longer crashes on unknown tools, but its fix is ad hoc — a try\u002Fexcept in the dispatch. We owe ourselves a proper tool abstraction.","md",{},null,{"title":26,"description":5485},"rGUCN_Ksh7JuWA5MKmJpQwuE752TgJuuScxoeoQEz6I",[5492,5494],{"title":22,"path":23,"stem":24,"description":5493,"children":-1},"Previously: we built a forty-line loop against a mock provider and watched it break five ways. Break 5 — tool output overwhelming the transcript — hinted that the transcript was doing too much work as a pile of dicts. It's time to give it some structure, and at the same time plug in real providers.",{"title":30,"path":31,"stem":32,"description":5495,"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.",1776848984223]