[{"data":1,"prerenderedAt":3618},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Ftool-cliff":102,"surround-\u002Fchapters\u002Ftool-cliff":3613},[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":58,"body":104,"description":117,"extension":3608,"meta":3609,"navigation":3610,"path":59,"seo":3611,"stem":60,"__hash__":3612},"content\u002F2.chapters\u002F12.tool-cliff.md",{"type":105,"value":106,"toc":3597},"minimark",[107,111,118,129,132,143,149,164,177,265,268,273,276,282,288,294,297,299,303,306,1412,1415,1436,1446,1463,1465,1469,1472,1478,1484,1487,1906,1909,1911,1915,1946,2817,2820,2826,2832,2842,2845,2847,2851,2861,3235,3241,3362,3365,3368,3370,3374,3418,3421,3435,3444,3450,3471,3473,3477,3480,3483,3490,3493,3495,3499,3546,3550,3582,3584,3593],[108,109,58],"h1",{"id":110},"chapter-12-the-tool-cliff-and-dynamic-loading",[112,113,114],"p",{},[115,116,117],"em",{},"Previously: tool design for a non-human reader. The harness now has a handful of well-designed tools. What happens when you need thirty of them?",[112,119,120,121,128],{},"The \"tool cliff\" is a non-linear performance collapse. ",[122,123,127],"a",{"href":124,"rel":125},"https:\u002F\u002Fwww.jenova.ai\u002Fen\u002Fresources\u002Fmcp-tool-scalability-problem",[126],"nofollow","Jenova AI's 2025 \"AI Tool Overload\" analysis"," (the same empirical finding we cited in Chapter 4) and several independent replications since then found that models routinely handle 10 tools near-perfectly, degrade noticeably at 20, and fall off a cliff somewhere between 30 and 50: tool-selection accuracy drops sharply, argument shapes get confused across tools, and context consumption from tool schemas alone eats 5–7% of the window before the user says anything.",[112,130,131],{},"Three distinct problems hide inside that one observation.",[112,133,134,138,139,142],{},[135,136,137],"strong",{},"Token cost of schemas."," Each tool's schema in the prompt is 100–500 tokens. Fifty tools is 10K–25K tokens of overhead ",[115,140,141],{},"per turn",", before the user gets a word in edgewise.",[112,144,145,148],{},[135,146,147],{},"Attention dilution."," The model has to \"choose the right tool\" from a list. The longer the list, the harder the choice. Selection accuracy drops even when the right tool's description would be unambiguous if it were the only option.",[112,150,151,154,155,159,160,163],{},[135,152,153],{},"Name and parameter collision."," Two tools called ",[156,157,158],"code",{},"search_docs"," and ",[156,161,162],{},"search_code"," with similar parameter shapes get confused. The model calls one expecting the other's behavior. This is a specific failure mode: the model isn't picking the wrong tool because it doesn't know the difference; it's picking the right tool and passing the wrong arguments because it's blending two similar schemas.",[112,165,166,167,170,171,176],{},"The fix is ",[115,168,169],{},"dynamic tool loading",". Instead of showing the model all tools at every turn, we show it a small selection relevant to the current task. ",[122,172,175],{"href":173,"rel":174},"https:\u002F\u002Feclipsesource.com\u002Fblogs\u002F2026\u002F01\u002F22\u002Fmcp-context-overload\u002F",[126],"EclipseSource's 2026 \"MCP and Context Overload\" analysis"," frames this as \"tool selection as a retrieval problem\" — and that's exactly how we'll implement it.",[178,179,183,184,183,258],"figure",{"className":180},[181,182],"not-prose","my-8","\n  ",[185,186,191,192,191,233,191,252,183],"div",{"className":187},[188,189,190],"flex","flex-col","gap-2","\n    ",[185,193,201,202,201,212,201,217,201,222,191],{"className":194,"style":200},[195,188,196,197,198,199],"relative","rounded-md","overflow-hidden","border","border-default","height:40px;","\n      ",[185,203,211],{"className":204,"style":210},[188,205,206,207,208,209],"items-center","justify-center","text-xs","font-semibold","text-default","width:20%; background:color-mix(in oklab, green 35%, transparent);","flat 0–20",[185,213,216],{"className":214,"style":215},[188,205,206,207,208,209],"width:30%; background:color-mix(in oklab, orange 40%, transparent);","degrading 20–50",[185,218,221],{"className":219,"style":220},[188,205,206,207,208,209],"width:50%; background:color-mix(in oklab, red 40%, transparent);","cliff 50+",[185,223,232],{"className":224,"style":231},[225,226,227,228,229,188,205,206,207,208,230],"absolute","rounded-full","border-2","border-primary","bg-elevated","text-primary","left:11%; top:-10px; width:22px; height:22px;","•",[185,234,201,239,201,243,201,246,201,249,191],{"className":235},[188,236,207,237,238],"justify-between","text-muted","px-1",[240,241,242],"span",{},"0",[240,244,245],{},"20",[240,247,248],{},"50",[240,250,251],{},"100 tools",[185,253,257],{"className":254},[207,230,208,255,256],"text-center","pt-1","selector keeps us here (~10–15 loaded)",[259,260,264],"figcaption",{"className":261},[207,237,262,255,263],"mt-3","italic","Tool-count vs selection accuracy: dynamic loading stays in the flat zone.",[266,267],"hr",{},[269,270,272],"h2",{"id":271},"_121-three-approaches","12.1 Three Approaches",[112,274,275],{},"Before committing to one, it's worth seeing the design space.",[112,277,278,281],{},[135,279,280],{},"Static subsetting."," Define a few fixed tool subsets (\"read-only mode\", \"code-editing mode\") and switch between them explicitly. Simple, predictable, needs no retrieval. The cost: the agent can't mix tool subsets mid-task. Works well for sharply-divided workloads (chat mode vs code mode in Cursor).",[112,283,284,287],{},[135,285,286],{},"Dynamic top-K by embedding."," Embed the tool description and the current task. Fetch the top-K most relevant tools. The agent sees K schemas per turn. Accurate, but introduces an embedding dependency and a latency hit. Production systems use this at scale.",[112,289,290,293],{},[135,291,292],{},"Dynamic top-K by BM25."," Same as above but keyword-based. Cheaper, no embedding model, works well when tool descriptions use domain vocabulary. Less accurate on paraphrase queries — but we control the queries (they come from the agent or from a classifier), so we can write them in the same vocabulary as the tools.",[112,295,296],{},"We'll build the BM25 version. The upgrade to embeddings is a twenty-line swap if you hit its limits.",[266,298],{},[269,300,302],{"id":301},"_122-the-selector","12.2 The Selector",[112,304,305],{},"BM25 is the same ranking function Chapter 10 used for document retrieval, formalized in Robertson and Zaragoza's 2009 \"The Probabilistic Relevance Framework\" we cited there. Tool selection is a retrieval problem — rank documents (tools) by relevance to a query (the current task) — and the same machinery applies with only the corpus changed.",[307,308,313],"pre",{"className":309,"code":310,"language":311,"meta":312,"style":312},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Ftools\u002Fselector.py\nfrom __future__ import annotations\n\nimport re\nfrom dataclasses import dataclass\n\nfrom rank_bm25 import BM25Okapi\n\nfrom .base import Tool\n\n\ndef _tokenize(text: str) -> list[str]:\n    return re.findall(r\"\\w+\", text.lower())\n\n\n@dataclass\nclass ToolCatalog:\n    \"\"\"A catalog of tools, with a BM25 index over names + descriptions.\"\"\"\n\n    tools: list[Tool]\n\n    def __post_init__(self) -> None:\n        self._tokenized = [\n            _tokenize(f\"{t.name} {t.description}\") for t in self.tools\n        ]\n        self._bm25 = BM25Okapi(self._tokenized)\n        self._by_name = {t.name: t for t in self.tools}\n\n    def select(self, query: str, k: int = 7, must_include: set[str] | None = None) -> list[Tool]:\n        \"\"\"Return up to k tools most relevant to the query.\n\n        must_include: tool names that must appear in the result regardless\n        of score — typically \"core\" tools the agent always has.\n        \"\"\"\n        must_include = must_include or set()\n        pinned = [self._by_name[n] for n in must_include if n in self._by_name]\n\n        scores = self._bm25.get_scores(_tokenize(query))\n        ranked = sorted(enumerate(scores), key=lambda x: -x[1])\n\n        remaining_slots = max(0, k - len(pinned))\n        picks: list[Tool] = list(pinned)\n        seen = {t.name for t in pinned}\n        for i, score in ranked:\n            if remaining_slots \u003C= 0:\n                break\n            tool = self.tools[i]\n            if tool.name in seen:\n                continue\n            if score \u003C= 0:\n                continue\n            picks.append(tool)\n            seen.add(tool.name)\n            remaining_slots -= 1\n\n        return picks\n\n    def get(self, name: str) -> Tool | None:\n        return self._by_name.get(name)\n\n    def all_names(self) -> list[str]:\n        return list(self._by_name.keys())\n","python","",[156,314,315,323,341,348,357,370,375,388,393,410,415,420,463,512,517,522,532,545,559,564,582,587,612,630,692,698,724,765,770,844,853,858,864,870,876,896,944,949,981,1034,1039,1072,1098,1125,1146,1163,1169,1190,1210,1216,1229,1234,1252,1273,1285,1290,1299,1304,1339,1361,1366,1390],{"__ignoreMap":312},[240,316,319],{"class":317,"line":318},"line",1,[240,320,322],{"class":321},"sutJx","# src\u002Fharness\u002Ftools\u002Fselector.py\n",[240,324,326,330,334,337],{"class":317,"line":325},2,[240,327,329],{"class":328},"sVHd0","from",[240,331,333],{"class":332},"s_hVV"," __future__",[240,335,336],{"class":328}," import",[240,338,340],{"class":339},"su5hD"," annotations\n",[240,342,344],{"class":317,"line":343},3,[240,345,347],{"emptyLinePlaceholder":346},true,"\n",[240,349,351,354],{"class":317,"line":350},4,[240,352,353],{"class":328},"import",[240,355,356],{"class":339}," re\n",[240,358,360,362,365,367],{"class":317,"line":359},5,[240,361,329],{"class":328},[240,363,364],{"class":339}," dataclasses ",[240,366,353],{"class":328},[240,368,369],{"class":339}," dataclass\n",[240,371,373],{"class":317,"line":372},6,[240,374,347],{"emptyLinePlaceholder":346},[240,376,378,380,383,385],{"class":317,"line":377},7,[240,379,329],{"class":328},[240,381,382],{"class":339}," rank_bm25 ",[240,384,353],{"class":328},[240,386,387],{"class":339}," BM25Okapi\n",[240,389,391],{"class":317,"line":390},8,[240,392,347],{"emptyLinePlaceholder":346},[240,394,396,398,402,405,407],{"class":317,"line":395},9,[240,397,329],{"class":328},[240,399,401],{"class":400},"sP7_E"," .",[240,403,404],{"class":339},"base ",[240,406,353],{"class":328},[240,408,409],{"class":339}," Tool\n",[240,411,413],{"class":317,"line":412},10,[240,414,347],{"emptyLinePlaceholder":346},[240,416,418],{"class":317,"line":417},11,[240,419,347],{"emptyLinePlaceholder":346},[240,421,423,427,431,434,438,441,445,448,451,454,457,460],{"class":317,"line":422},12,[240,424,426],{"class":425},"sbsja","def",[240,428,430],{"class":429},"sGLFI"," _tokenize",[240,432,433],{"class":400},"(",[240,435,437],{"class":436},"sFwrP","text",[240,439,440],{"class":400},":",[240,442,444],{"class":443},"sZMiF"," str",[240,446,447],{"class":400},")",[240,449,450],{"class":400}," ->",[240,452,453],{"class":339}," list",[240,455,456],{"class":400},"[",[240,458,459],{"class":443},"str",[240,461,462],{"class":400},"]:\n",[240,464,466,469,472,475,479,481,484,488,492,496,498,501,504,506,509],{"class":317,"line":465},13,[240,467,468],{"class":328},"    return",[240,470,471],{"class":339}," re",[240,473,474],{"class":400},".",[240,476,478],{"class":477},"slqww","findall",[240,480,433],{"class":400},[240,482,483],{"class":425},"r",[240,485,487],{"class":486},"sjJ54","\"",[240,489,491],{"class":490},"stzsN","\\w",[240,493,495],{"class":494},"smGrS","+",[240,497,487],{"class":486},[240,499,500],{"class":400},",",[240,502,503],{"class":477}," text",[240,505,474],{"class":400},[240,507,508],{"class":477},"lower",[240,510,511],{"class":400},"())\n",[240,513,515],{"class":317,"line":514},14,[240,516,347],{"emptyLinePlaceholder":346},[240,518,520],{"class":317,"line":519},15,[240,521,347],{"emptyLinePlaceholder":346},[240,523,525,529],{"class":317,"line":524},16,[240,526,528],{"class":527},"stp6e","@",[240,530,531],{"class":429},"dataclass\n",[240,533,535,538,542],{"class":317,"line":534},17,[240,536,537],{"class":425},"class",[240,539,541],{"class":540},"sbgvK"," ToolCatalog",[240,543,544],{"class":400},":\n",[240,546,548,552,556],{"class":317,"line":547},18,[240,549,551],{"class":550},"s2W-s","    \"\"\"",[240,553,555],{"class":554},"sithA","A catalog of tools, with a BM25 index over names + descriptions.",[240,557,558],{"class":550},"\"\"\"\n",[240,560,562],{"class":317,"line":561},19,[240,563,347],{"emptyLinePlaceholder":346},[240,565,567,570,572,574,576,579],{"class":317,"line":566},20,[240,568,569],{"class":339},"    tools",[240,571,440],{"class":400},[240,573,453],{"class":339},[240,575,456],{"class":400},[240,577,578],{"class":339},"Tool",[240,580,581],{"class":400},"]\n",[240,583,585],{"class":317,"line":584},21,[240,586,347],{"emptyLinePlaceholder":346},[240,588,590,593,596,598,602,604,606,610],{"class":317,"line":589},22,[240,591,592],{"class":425},"    def",[240,594,595],{"class":332}," __post_init__",[240,597,433],{"class":400},[240,599,601],{"class":600},"smCYv","self",[240,603,447],{"class":400},[240,605,450],{"class":400},[240,607,609],{"class":608},"s39Yj"," None",[240,611,544],{"class":400},[240,613,615,618,620,624,627],{"class":317,"line":614},23,[240,616,617],{"class":332},"        self",[240,619,474],{"class":400},[240,621,623],{"class":622},"skxfh","_tokenized",[240,625,626],{"class":494}," =",[240,628,629],{"class":400}," [\n",[240,631,633,636,638,641,644,648,651,653,656,659,662,664,666,669,671,673,675,678,681,684,687,689],{"class":317,"line":632},24,[240,634,635],{"class":477},"            _tokenize",[240,637,433],{"class":400},[240,639,640],{"class":425},"f",[240,642,487],{"class":643},"s_sjI",[240,645,647],{"class":646},"srdBf","{",[240,649,650],{"class":477},"t",[240,652,474],{"class":400},[240,654,655],{"class":622},"name",[240,657,658],{"class":646},"}",[240,660,661],{"class":646}," {",[240,663,650],{"class":477},[240,665,474],{"class":400},[240,667,668],{"class":622},"description",[240,670,658],{"class":646},[240,672,487],{"class":643},[240,674,447],{"class":400},[240,676,677],{"class":328}," for",[240,679,680],{"class":339}," t ",[240,682,683],{"class":328},"in",[240,685,686],{"class":332}," self",[240,688,474],{"class":400},[240,690,691],{"class":622},"tools\n",[240,693,695],{"class":317,"line":694},25,[240,696,697],{"class":400},"        ]\n",[240,699,701,703,705,708,710,713,715,717,719,721],{"class":317,"line":700},26,[240,702,617],{"class":332},[240,704,474],{"class":400},[240,706,707],{"class":622},"_bm25",[240,709,626],{"class":494},[240,711,712],{"class":477}," BM25Okapi",[240,714,433],{"class":400},[240,716,601],{"class":332},[240,718,474],{"class":400},[240,720,623],{"class":622},[240,722,723],{"class":400},")\n",[240,725,727,729,731,734,736,738,740,742,744,746,748,751,753,755,757,759,762],{"class":317,"line":726},27,[240,728,617],{"class":332},[240,730,474],{"class":400},[240,732,733],{"class":622},"_by_name",[240,735,626],{"class":494},[240,737,661],{"class":400},[240,739,650],{"class":339},[240,741,474],{"class":400},[240,743,655],{"class":622},[240,745,440],{"class":400},[240,747,680],{"class":339},[240,749,750],{"class":328},"for",[240,752,680],{"class":339},[240,754,683],{"class":328},[240,756,686],{"class":332},[240,758,474],{"class":400},[240,760,761],{"class":622},"tools",[240,763,764],{"class":400},"}\n",[240,766,768],{"class":317,"line":767},28,[240,769,347],{"emptyLinePlaceholder":346},[240,771,773,775,778,780,782,784,787,789,791,793,796,798,801,803,806,808,811,813,816,818,820,823,826,828,830,832,834,836,838,840,842],{"class":317,"line":772},29,[240,774,592],{"class":425},[240,776,777],{"class":429}," select",[240,779,433],{"class":400},[240,781,601],{"class":600},[240,783,500],{"class":400},[240,785,786],{"class":436}," query",[240,788,440],{"class":400},[240,790,444],{"class":443},[240,792,500],{"class":400},[240,794,795],{"class":436}," k",[240,797,440],{"class":400},[240,799,800],{"class":443}," int",[240,802,626],{"class":494},[240,804,805],{"class":646}," 7",[240,807,500],{"class":400},[240,809,810],{"class":436}," must_include",[240,812,440],{"class":400},[240,814,815],{"class":339}," set",[240,817,456],{"class":400},[240,819,459],{"class":443},[240,821,822],{"class":400},"]",[240,824,825],{"class":494}," |",[240,827,609],{"class":608},[240,829,626],{"class":494},[240,831,609],{"class":608},[240,833,447],{"class":400},[240,835,450],{"class":400},[240,837,453],{"class":339},[240,839,456],{"class":400},[240,841,578],{"class":339},[240,843,462],{"class":400},[240,845,847,850],{"class":317,"line":846},30,[240,848,849],{"class":550},"        \"\"\"",[240,851,852],{"class":554},"Return up to k tools most relevant to the query.\n",[240,854,856],{"class":317,"line":855},31,[240,857,347],{"emptyLinePlaceholder":346},[240,859,861],{"class":317,"line":860},32,[240,862,863],{"class":554},"        must_include: tool names that must appear in the result regardless\n",[240,865,867],{"class":317,"line":866},33,[240,868,869],{"class":554},"        of score — typically \"core\" tools the agent always has.\n",[240,871,873],{"class":317,"line":872},34,[240,874,875],{"class":550},"        \"\"\"\n",[240,877,879,882,885,888,891,893],{"class":317,"line":878},35,[240,880,881],{"class":339},"        must_include ",[240,883,884],{"class":494},"=",[240,886,887],{"class":339}," must_include ",[240,889,890],{"class":494},"or",[240,892,815],{"class":443},[240,894,895],{"class":400},"()\n",[240,897,899,902,904,907,909,911,913,915,918,920,922,925,927,929,932,934,936,938,940,942],{"class":317,"line":898},36,[240,900,901],{"class":339},"        pinned ",[240,903,884],{"class":494},[240,905,906],{"class":400}," [",[240,908,601],{"class":332},[240,910,474],{"class":400},[240,912,733],{"class":622},[240,914,456],{"class":400},[240,916,917],{"class":622},"n",[240,919,822],{"class":400},[240,921,677],{"class":328},[240,923,924],{"class":339}," n ",[240,926,683],{"class":328},[240,928,887],{"class":339},[240,930,931],{"class":328},"if",[240,933,924],{"class":339},[240,935,683],{"class":494},[240,937,686],{"class":332},[240,939,474],{"class":400},[240,941,733],{"class":622},[240,943,581],{"class":400},[240,945,947],{"class":317,"line":946},37,[240,948,347],{"emptyLinePlaceholder":346},[240,950,952,955,957,959,961,963,965,968,970,973,975,978],{"class":317,"line":951},38,[240,953,954],{"class":339},"        scores ",[240,956,884],{"class":494},[240,958,686],{"class":332},[240,960,474],{"class":400},[240,962,707],{"class":622},[240,964,474],{"class":400},[240,966,967],{"class":477},"get_scores",[240,969,433],{"class":400},[240,971,972],{"class":477},"_tokenize",[240,974,433],{"class":400},[240,976,977],{"class":477},"query",[240,979,980],{"class":400},"))\n",[240,982,984,987,989,993,995,998,1000,1003,1006,1010,1012,1015,1018,1020,1023,1026,1028,1031],{"class":317,"line":983},39,[240,985,986],{"class":339},"        ranked ",[240,988,884],{"class":494},[240,990,992],{"class":991},"sptTA"," sorted",[240,994,433],{"class":400},[240,996,997],{"class":991},"enumerate",[240,999,433],{"class":400},[240,1001,1002],{"class":477},"scores",[240,1004,1005],{"class":400},"),",[240,1007,1009],{"class":1008},"s99_P"," key",[240,1011,884],{"class":494},[240,1013,1014],{"class":425},"lambda",[240,1016,1017],{"class":436}," x",[240,1019,440],{"class":400},[240,1021,1022],{"class":494}," -",[240,1024,1025],{"class":477},"x",[240,1027,456],{"class":400},[240,1029,1030],{"class":646},"1",[240,1032,1033],{"class":400},"])\n",[240,1035,1037],{"class":317,"line":1036},40,[240,1038,347],{"emptyLinePlaceholder":346},[240,1040,1042,1045,1047,1050,1052,1054,1056,1059,1062,1065,1067,1070],{"class":317,"line":1041},41,[240,1043,1044],{"class":339},"        remaining_slots ",[240,1046,884],{"class":494},[240,1048,1049],{"class":991}," max",[240,1051,433],{"class":400},[240,1053,242],{"class":646},[240,1055,500],{"class":400},[240,1057,1058],{"class":477}," k ",[240,1060,1061],{"class":494},"-",[240,1063,1064],{"class":991}," len",[240,1066,433],{"class":400},[240,1068,1069],{"class":477},"pinned",[240,1071,980],{"class":400},[240,1073,1075,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096],{"class":317,"line":1074},42,[240,1076,1077],{"class":339},"        picks",[240,1079,440],{"class":400},[240,1081,453],{"class":339},[240,1083,456],{"class":400},[240,1085,578],{"class":339},[240,1087,822],{"class":400},[240,1089,626],{"class":494},[240,1091,453],{"class":443},[240,1093,433],{"class":400},[240,1095,1069],{"class":477},[240,1097,723],{"class":400},[240,1099,1101,1104,1106,1108,1110,1112,1114,1116,1118,1120,1123],{"class":317,"line":1100},43,[240,1102,1103],{"class":339},"        seen ",[240,1105,884],{"class":494},[240,1107,661],{"class":400},[240,1109,650],{"class":339},[240,1111,474],{"class":400},[240,1113,655],{"class":622},[240,1115,677],{"class":328},[240,1117,680],{"class":339},[240,1119,683],{"class":328},[240,1121,1122],{"class":339}," pinned",[240,1124,764],{"class":400},[240,1126,1128,1131,1134,1136,1139,1141,1144],{"class":317,"line":1127},44,[240,1129,1130],{"class":328},"        for",[240,1132,1133],{"class":339}," i",[240,1135,500],{"class":400},[240,1137,1138],{"class":339}," score ",[240,1140,683],{"class":328},[240,1142,1143],{"class":339}," ranked",[240,1145,544],{"class":400},[240,1147,1149,1152,1155,1158,1161],{"class":317,"line":1148},45,[240,1150,1151],{"class":328},"            if",[240,1153,1154],{"class":339}," remaining_slots ",[240,1156,1157],{"class":494},"\u003C=",[240,1159,1160],{"class":646}," 0",[240,1162,544],{"class":400},[240,1164,1166],{"class":317,"line":1165},46,[240,1167,1168],{"class":328},"                break\n",[240,1170,1172,1175,1177,1179,1181,1183,1185,1188],{"class":317,"line":1171},47,[240,1173,1174],{"class":339},"            tool ",[240,1176,884],{"class":494},[240,1178,686],{"class":332},[240,1180,474],{"class":400},[240,1182,761],{"class":622},[240,1184,456],{"class":400},[240,1186,1187],{"class":622},"i",[240,1189,581],{"class":400},[240,1191,1193,1195,1198,1200,1202,1205,1208],{"class":317,"line":1192},48,[240,1194,1151],{"class":328},[240,1196,1197],{"class":339}," tool",[240,1199,474],{"class":400},[240,1201,655],{"class":622},[240,1203,1204],{"class":494}," in",[240,1206,1207],{"class":339}," seen",[240,1209,544],{"class":400},[240,1211,1213],{"class":317,"line":1212},49,[240,1214,1215],{"class":328},"                continue\n",[240,1217,1219,1221,1223,1225,1227],{"class":317,"line":1218},50,[240,1220,1151],{"class":328},[240,1222,1138],{"class":339},[240,1224,1157],{"class":494},[240,1226,1160],{"class":646},[240,1228,544],{"class":400},[240,1230,1232],{"class":317,"line":1231},51,[240,1233,1215],{"class":328},[240,1235,1237,1240,1242,1245,1247,1250],{"class":317,"line":1236},52,[240,1238,1239],{"class":339},"            picks",[240,1241,474],{"class":400},[240,1243,1244],{"class":477},"append",[240,1246,433],{"class":400},[240,1248,1249],{"class":477},"tool",[240,1251,723],{"class":400},[240,1253,1255,1258,1260,1263,1265,1267,1269,1271],{"class":317,"line":1254},53,[240,1256,1257],{"class":339},"            seen",[240,1259,474],{"class":400},[240,1261,1262],{"class":477},"add",[240,1264,433],{"class":400},[240,1266,1249],{"class":477},[240,1268,474],{"class":400},[240,1270,655],{"class":622},[240,1272,723],{"class":400},[240,1274,1276,1279,1282],{"class":317,"line":1275},54,[240,1277,1278],{"class":339},"            remaining_slots ",[240,1280,1281],{"class":494},"-=",[240,1283,1284],{"class":646}," 1\n",[240,1286,1288],{"class":317,"line":1287},55,[240,1289,347],{"emptyLinePlaceholder":346},[240,1291,1293,1296],{"class":317,"line":1292},56,[240,1294,1295],{"class":328},"        return",[240,1297,1298],{"class":339}," picks\n",[240,1300,1302],{"class":317,"line":1301},57,[240,1303,347],{"emptyLinePlaceholder":346},[240,1305,1307,1309,1312,1314,1316,1318,1321,1323,1325,1327,1329,1332,1335,1337],{"class":317,"line":1306},58,[240,1308,592],{"class":425},[240,1310,1311],{"class":429}," get",[240,1313,433],{"class":400},[240,1315,601],{"class":600},[240,1317,500],{"class":400},[240,1319,1320],{"class":436}," name",[240,1322,440],{"class":400},[240,1324,444],{"class":443},[240,1326,447],{"class":400},[240,1328,450],{"class":400},[240,1330,1331],{"class":339}," Tool ",[240,1333,1334],{"class":494},"|",[240,1336,609],{"class":608},[240,1338,544],{"class":400},[240,1340,1342,1344,1346,1348,1350,1352,1355,1357,1359],{"class":317,"line":1341},59,[240,1343,1295],{"class":328},[240,1345,686],{"class":332},[240,1347,474],{"class":400},[240,1349,733],{"class":622},[240,1351,474],{"class":400},[240,1353,1354],{"class":477},"get",[240,1356,433],{"class":400},[240,1358,655],{"class":477},[240,1360,723],{"class":400},[240,1362,1364],{"class":317,"line":1363},60,[240,1365,347],{"emptyLinePlaceholder":346},[240,1367,1369,1371,1374,1376,1378,1380,1382,1384,1386,1388],{"class":317,"line":1368},61,[240,1370,592],{"class":425},[240,1372,1373],{"class":429}," all_names",[240,1375,433],{"class":400},[240,1377,601],{"class":600},[240,1379,447],{"class":400},[240,1381,450],{"class":400},[240,1383,453],{"class":339},[240,1385,456],{"class":400},[240,1387,459],{"class":443},[240,1389,462],{"class":400},[240,1391,1393,1395,1397,1399,1401,1403,1405,1407,1410],{"class":317,"line":1392},62,[240,1394,1295],{"class":328},[240,1396,453],{"class":443},[240,1398,433],{"class":400},[240,1400,601],{"class":332},[240,1402,474],{"class":400},[240,1404,733],{"class":622},[240,1406,474],{"class":400},[240,1408,1409],{"class":477},"keys",[240,1411,511],{"class":400},[112,1413,1414],{},"The catalog is a searchable tool registry. Two features worth naming.",[112,1416,1417,1423,1424,1427,1428,1431,1432,1435],{},[135,1418,1419,1422],{},[156,1420,1421],{},"must_include"," for pinned tools."," Some tools should always be present — ",[156,1425,1426],{},"scratchpad_read",", ",[156,1429,1430],{},"scratchpad_list",", maybe a ",[156,1433,1434],{},"help"," tool. Pinning keeps them available regardless of what the query retrieved. This is how we prevent the selector from accidentally hiding essential capabilities.",[112,1437,1438,1441,1442,1445],{},[135,1439,1440],{},"Score floor."," We don't include tools with score ≤ 0. If the query doesn't match ",[115,1443,1444],{},"any"," tool, we return just the pinned ones. The model learns that an empty selection means \"nothing in the catalog looks relevant.\"",[112,1447,1448,1454,1455,1458,1459,1462],{},[135,1449,1450,1451,1453],{},"Why ",[156,1452,1421],{}," is load-bearing, not a nice-to-have."," The score floor has an uncomfortable failure mode: a query that matches nothing produces an empty selection. On the ",[115,1456,1457],{},"first"," user turn — \"hi\", \"help\", \"what can you do?\" — every tool scores 0 and the selector returns nothing. The agent sees zero tools, can only respond with text, and has no path to discover what the harness can do. The same failure mode fires on mid-task pivots: five turns of file work, then \"now post a summary to Slack\", and BM25's transcript-derived query is dominated by filesystem vocabulary rather than ",[156,1460,1461],{},"slack",". Pinning a single discovery tool — we build it in §12.5 — closes both holes. Without it, you've shipped a selector-backed agent that can go blind in ways the rest of the chapter's machinery can't recover from.",[266,1464],{},[269,1466,1468],{"id":1467},"_123-two-strategies-for-picking-the-query","12.3 Two Strategies for Picking the Query",[112,1470,1471],{},"The selector needs a query — a string that describes what tools would be useful right now. Two workable strategies.",[112,1473,1474,1477],{},[135,1475,1476],{},"Classify the user's message."," The user says \"read the log file and find errors.\" A small classifier (could be a cheap model, could be rules) extracts \"read file\" and \"find errors\" as the task intent, and those keywords drive the BM25 query. Works well when user turns are clean task descriptions; falls apart on conversational multi-step interactions.",[112,1479,1480,1483],{},[135,1481,1482],{},"Use the agent's running transcript."," Take the last user message, the last assistant thought, and maybe the last tool call, and use that text as the query. This is more robust — the agent's own reasoning naturally reaches for relevant vocabulary — but it requires that the agent is making progress at all (on the first turn, you have only the user's message).",[112,1485,1486],{},"We use a hybrid: the user's original message as a base, augmented by the last couple of turns if they exist. This gives us a query that reflects both initial intent and current direction.",[307,1488,1490],{"className":309,"code":1489,"language":311,"meta":312,"style":312},"# src\u002Fharness\u002Ftools\u002Fselector.py (continued)\n\nfrom ..messages import Transcript, TextBlock, ToolCall\n\n\ndef query_from_transcript(transcript: Transcript) -> str:\n    \"\"\"Derive a search query from the transcript: user intent plus recent activity.\"\"\"\n    parts: list[str] = []\n    # first user message is the anchor\n    if transcript.messages:\n        first = transcript.messages[0]\n        for b in first.blocks:\n            if isinstance(b, TextBlock):\n                parts.append(b.text)\n    # last 3 assistant blocks (text or tool calls) for current focus\n    recent = [m for m in transcript.messages[-6:] if m.role == \"assistant\"]\n    for m in recent:\n        for b in m.blocks:\n            if isinstance(b, TextBlock):\n                parts.append(b.text[:500])\n            elif isinstance(b, ToolCall):\n                parts.append(f\"{b.name} {list(b.args.keys())}\")\n    return \" \".join(parts)\n",[156,1491,1492,1497,1501,1526,1530,1534,1558,1567,1587,1592,1607,1626,1645,1664,1683,1688,1747,1761,1777,1793,1817,1835,1886],{"__ignoreMap":312},[240,1493,1494],{"class":317,"line":318},[240,1495,1496],{"class":321},"# src\u002Fharness\u002Ftools\u002Fselector.py (continued)\n",[240,1498,1499],{"class":317,"line":325},[240,1500,347],{"emptyLinePlaceholder":346},[240,1502,1503,1505,1508,1511,1513,1516,1518,1521,1523],{"class":317,"line":343},[240,1504,329],{"class":328},[240,1506,1507],{"class":400}," ..",[240,1509,1510],{"class":339},"messages ",[240,1512,353],{"class":328},[240,1514,1515],{"class":339}," Transcript",[240,1517,500],{"class":400},[240,1519,1520],{"class":339}," TextBlock",[240,1522,500],{"class":400},[240,1524,1525],{"class":339}," ToolCall\n",[240,1527,1528],{"class":317,"line":350},[240,1529,347],{"emptyLinePlaceholder":346},[240,1531,1532],{"class":317,"line":359},[240,1533,347],{"emptyLinePlaceholder":346},[240,1535,1536,1538,1541,1543,1546,1548,1550,1552,1554,1556],{"class":317,"line":372},[240,1537,426],{"class":425},[240,1539,1540],{"class":429}," query_from_transcript",[240,1542,433],{"class":400},[240,1544,1545],{"class":436},"transcript",[240,1547,440],{"class":400},[240,1549,1515],{"class":339},[240,1551,447],{"class":400},[240,1553,450],{"class":400},[240,1555,444],{"class":443},[240,1557,544],{"class":400},[240,1559,1560,1562,1565],{"class":317,"line":377},[240,1561,551],{"class":550},[240,1563,1564],{"class":554},"Derive a search query from the transcript: user intent plus recent activity.",[240,1566,558],{"class":550},[240,1568,1569,1572,1574,1576,1578,1580,1582,1584],{"class":317,"line":390},[240,1570,1571],{"class":339},"    parts",[240,1573,440],{"class":400},[240,1575,453],{"class":339},[240,1577,456],{"class":400},[240,1579,459],{"class":443},[240,1581,822],{"class":400},[240,1583,626],{"class":494},[240,1585,1586],{"class":400}," []\n",[240,1588,1589],{"class":317,"line":395},[240,1590,1591],{"class":321},"    # first user message is the anchor\n",[240,1593,1594,1597,1600,1602,1605],{"class":317,"line":412},[240,1595,1596],{"class":328},"    if",[240,1598,1599],{"class":339}," transcript",[240,1601,474],{"class":400},[240,1603,1604],{"class":622},"messages",[240,1606,544],{"class":400},[240,1608,1609,1612,1614,1616,1618,1620,1622,1624],{"class":317,"line":417},[240,1610,1611],{"class":339},"        first ",[240,1613,884],{"class":494},[240,1615,1599],{"class":339},[240,1617,474],{"class":400},[240,1619,1604],{"class":622},[240,1621,456],{"class":400},[240,1623,242],{"class":646},[240,1625,581],{"class":400},[240,1627,1628,1630,1633,1635,1638,1640,1643],{"class":317,"line":422},[240,1629,1130],{"class":328},[240,1631,1632],{"class":339}," b ",[240,1634,683],{"class":328},[240,1636,1637],{"class":339}," first",[240,1639,474],{"class":400},[240,1641,1642],{"class":622},"blocks",[240,1644,544],{"class":400},[240,1646,1647,1649,1652,1654,1657,1659,1661],{"class":317,"line":465},[240,1648,1151],{"class":328},[240,1650,1651],{"class":991}," isinstance",[240,1653,433],{"class":400},[240,1655,1656],{"class":477},"b",[240,1658,500],{"class":400},[240,1660,1520],{"class":477},[240,1662,1663],{"class":400},"):\n",[240,1665,1666,1669,1671,1673,1675,1677,1679,1681],{"class":317,"line":514},[240,1667,1668],{"class":339},"                parts",[240,1670,474],{"class":400},[240,1672,1244],{"class":477},[240,1674,433],{"class":400},[240,1676,1656],{"class":477},[240,1678,474],{"class":400},[240,1680,437],{"class":622},[240,1682,723],{"class":400},[240,1684,1685],{"class":317,"line":519},[240,1686,1687],{"class":321},"    # last 3 assistant blocks (text or tool calls) for current focus\n",[240,1689,1690,1693,1695,1697,1700,1702,1705,1707,1709,1711,1713,1715,1717,1720,1723,1726,1729,1731,1734,1737,1740,1743,1745],{"class":317,"line":524},[240,1691,1692],{"class":339},"    recent ",[240,1694,884],{"class":494},[240,1696,906],{"class":400},[240,1698,1699],{"class":339},"m ",[240,1701,750],{"class":328},[240,1703,1704],{"class":339}," m ",[240,1706,683],{"class":328},[240,1708,1599],{"class":339},[240,1710,474],{"class":400},[240,1712,1604],{"class":622},[240,1714,456],{"class":400},[240,1716,1061],{"class":494},[240,1718,1719],{"class":646},"6",[240,1721,1722],{"class":400},":]",[240,1724,1725],{"class":328}," if",[240,1727,1728],{"class":339}," m",[240,1730,474],{"class":400},[240,1732,1733],{"class":622},"role",[240,1735,1736],{"class":494}," ==",[240,1738,1739],{"class":486}," \"",[240,1741,1742],{"class":643},"assistant",[240,1744,487],{"class":486},[240,1746,581],{"class":400},[240,1748,1749,1752,1754,1756,1759],{"class":317,"line":534},[240,1750,1751],{"class":328},"    for",[240,1753,1704],{"class":339},[240,1755,683],{"class":328},[240,1757,1758],{"class":339}," recent",[240,1760,544],{"class":400},[240,1762,1763,1765,1767,1769,1771,1773,1775],{"class":317,"line":547},[240,1764,1130],{"class":328},[240,1766,1632],{"class":339},[240,1768,683],{"class":328},[240,1770,1728],{"class":339},[240,1772,474],{"class":400},[240,1774,1642],{"class":622},[240,1776,544],{"class":400},[240,1778,1779,1781,1783,1785,1787,1789,1791],{"class":317,"line":561},[240,1780,1151],{"class":328},[240,1782,1651],{"class":991},[240,1784,433],{"class":400},[240,1786,1656],{"class":477},[240,1788,500],{"class":400},[240,1790,1520],{"class":477},[240,1792,1663],{"class":400},[240,1794,1795,1797,1799,1801,1803,1805,1807,1809,1812,1815],{"class":317,"line":566},[240,1796,1668],{"class":339},[240,1798,474],{"class":400},[240,1800,1244],{"class":477},[240,1802,433],{"class":400},[240,1804,1656],{"class":477},[240,1806,474],{"class":400},[240,1808,437],{"class":622},[240,1810,1811],{"class":400},"[:",[240,1813,1814],{"class":646},"500",[240,1816,1033],{"class":400},[240,1818,1819,1822,1824,1826,1828,1830,1833],{"class":317,"line":584},[240,1820,1821],{"class":328},"            elif",[240,1823,1651],{"class":991},[240,1825,433],{"class":400},[240,1827,1656],{"class":477},[240,1829,500],{"class":400},[240,1831,1832],{"class":477}," ToolCall",[240,1834,1663],{"class":400},[240,1836,1837,1839,1841,1843,1845,1847,1849,1851,1853,1855,1857,1859,1861,1864,1866,1868,1870,1873,1875,1877,1880,1882,1884],{"class":317,"line":589},[240,1838,1668],{"class":339},[240,1840,474],{"class":400},[240,1842,1244],{"class":477},[240,1844,433],{"class":400},[240,1846,640],{"class":425},[240,1848,487],{"class":643},[240,1850,647],{"class":646},[240,1852,1656],{"class":477},[240,1854,474],{"class":400},[240,1856,655],{"class":622},[240,1858,658],{"class":646},[240,1860,661],{"class":646},[240,1862,1863],{"class":443},"list",[240,1865,433],{"class":400},[240,1867,1656],{"class":477},[240,1869,474],{"class":400},[240,1871,1872],{"class":622},"args",[240,1874,474],{"class":400},[240,1876,1409],{"class":477},[240,1878,1879],{"class":400},"())",[240,1881,658],{"class":646},[240,1883,487],{"class":643},[240,1885,723],{"class":400},[240,1887,1888,1890,1892,1894,1896,1899,1901,1904],{"class":317,"line":614},[240,1889,468],{"class":328},[240,1891,1739],{"class":486},[240,1893,1739],{"class":486},[240,1895,474],{"class":400},[240,1897,1898],{"class":477},"join",[240,1900,433],{"class":400},[240,1902,1903],{"class":477},"parts",[240,1905,723],{"class":400},[112,1907,1908],{},"Not sophisticated. Works surprisingly well.",[266,1910],{},[269,1912,1914],{"id":1913},"_124-threading-the-selector-through-the-loop","12.4 Threading the Selector Through the Loop",[112,1916,1917,1918,1921,1922,1925,1926,1929,1930,1933,1934,1937,1938,1941,1942,1945],{},"The loop now picks tools per turn instead of using a fixed registry. ",[135,1919,1920],{},"Signature note:"," this chapter changes ",[156,1923,1924],{},"arun","'s tool parameter from ",[156,1927,1928],{},"registry:"," to ",[156,1931,1932],{},"catalog:",". Earlier chapters' examples (Chs 8–11) that called ",[156,1935,1936],{},"arun(..., registry=registry, ...)"," need to be updated to ",[156,1939,1940],{},"arun(..., catalog=ToolCatalog(tools=list(registry.tools.values())), ...)"," — or use the convenience ",[156,1943,1944],{},"ToolCatalog.from_registry(registry)"," if you wire one up.",[307,1947,1949],{"className":309,"code":1948,"language":311,"meta":312,"style":312},"# src\u002Fharness\u002Fagent.py (selector-aware version)\nfrom .tools.selector import ToolCatalog, query_from_transcript\n\n\nasync def arun(\n    provider: Provider,\n    catalog: ToolCatalog,\n    user_message: str,\n    transcript: Transcript | None = None,\n    system: str | None = None,\n    on_event: \"callable | None\" = None,\n    on_tool_call: \"callable | None\" = None,\n    on_tool_result: \"callable | None\" = None,\n    on_snapshot: \"callable | None\" = None,\n    accountant: ContextAccountant | None = None,\n    compactor: Compactor | None = None,\n    pinned_tools: set[str] | None = None,\n    tools_per_turn: int = 7,\n) -> str:\n    if transcript is None:\n        transcript = Transcript(system=system)\n    transcript.append(Message.user_text(user_message))\n    accountant = accountant or ContextAccountant()\n    compactor = compactor or Compactor(accountant, provider)\n\n    for _ in range(MAX_ITERATIONS):\n        # Select tools for this turn.\n        query = query_from_transcript(transcript)\n        selected = catalog.select(query, k=tools_per_turn,\n                                   must_include=pinned_tools)\n        registry = ToolRegistry(tools=selected)\n\n        snapshot = accountant.snapshot(transcript, tools=registry.schemas())\n        if on_snapshot is not None:\n            on_snapshot(snapshot)\n\n        if snapshot.state == \"red\":\n            await compactor.compact_if_needed(transcript, registry.schemas())\n\n        response = await _one_turn(provider, registry, transcript, on_event=on_event)\n\n        if response.is_final:\n            transcript.append(Message.from_assistant_response(response))\n            return response.text or \"\"\n\n        transcript.append(Message.from_assistant_response(response))\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",[156,1950,1951,1956,1978,1982,1986,2000,2013,2024,2035,2055,2074,2094,2113,2132,2151,2171,2191,2216,2231,2241,2255,2275,2300,2317,2344,2348,2367,2372,2387,2417,2429,2450,2454,2490,2507,2518,2522,2545,2573,2577,2615,2619,2633,2658,2675,2679,2702,2720,2763,2787,2791],{"__ignoreMap":312},[240,1952,1953],{"class":317,"line":318},[240,1954,1955],{"class":321},"# src\u002Fharness\u002Fagent.py (selector-aware version)\n",[240,1957,1958,1960,1962,1964,1966,1969,1971,1973,1975],{"class":317,"line":325},[240,1959,329],{"class":328},[240,1961,401],{"class":400},[240,1963,761],{"class":339},[240,1965,474],{"class":400},[240,1967,1968],{"class":339},"selector ",[240,1970,353],{"class":328},[240,1972,541],{"class":339},[240,1974,500],{"class":400},[240,1976,1977],{"class":339}," query_from_transcript\n",[240,1979,1980],{"class":317,"line":343},[240,1981,347],{"emptyLinePlaceholder":346},[240,1983,1984],{"class":317,"line":350},[240,1985,347],{"emptyLinePlaceholder":346},[240,1987,1988,1991,1994,1997],{"class":317,"line":359},[240,1989,1990],{"class":425},"async",[240,1992,1993],{"class":425}," def",[240,1995,1996],{"class":429}," arun",[240,1998,1999],{"class":400},"(\n",[240,2001,2002,2005,2007,2010],{"class":317,"line":372},[240,2003,2004],{"class":436},"    provider",[240,2006,440],{"class":400},[240,2008,2009],{"class":339}," Provider",[240,2011,2012],{"class":400},",\n",[240,2014,2015,2018,2020,2022],{"class":317,"line":377},[240,2016,2017],{"class":436},"    catalog",[240,2019,440],{"class":400},[240,2021,541],{"class":339},[240,2023,2012],{"class":400},[240,2025,2026,2029,2031,2033],{"class":317,"line":390},[240,2027,2028],{"class":436},"    user_message",[240,2030,440],{"class":400},[240,2032,444],{"class":443},[240,2034,2012],{"class":400},[240,2036,2037,2040,2042,2045,2047,2049,2051,2053],{"class":317,"line":395},[240,2038,2039],{"class":436},"    transcript",[240,2041,440],{"class":400},[240,2043,2044],{"class":339}," Transcript ",[240,2046,1334],{"class":494},[240,2048,609],{"class":608},[240,2050,626],{"class":494},[240,2052,609],{"class":608},[240,2054,2012],{"class":400},[240,2056,2057,2060,2062,2064,2066,2068,2070,2072],{"class":317,"line":412},[240,2058,2059],{"class":436},"    system",[240,2061,440],{"class":400},[240,2063,444],{"class":443},[240,2065,825],{"class":494},[240,2067,609],{"class":608},[240,2069,626],{"class":494},[240,2071,609],{"class":608},[240,2073,2012],{"class":400},[240,2075,2076,2079,2081,2083,2086,2088,2090,2092],{"class":317,"line":417},[240,2077,2078],{"class":436},"    on_event",[240,2080,440],{"class":400},[240,2082,1739],{"class":486},[240,2084,2085],{"class":643},"callable | None",[240,2087,487],{"class":486},[240,2089,626],{"class":494},[240,2091,609],{"class":608},[240,2093,2012],{"class":400},[240,2095,2096,2099,2101,2103,2105,2107,2109,2111],{"class":317,"line":422},[240,2097,2098],{"class":436},"    on_tool_call",[240,2100,440],{"class":400},[240,2102,1739],{"class":486},[240,2104,2085],{"class":643},[240,2106,487],{"class":486},[240,2108,626],{"class":494},[240,2110,609],{"class":608},[240,2112,2012],{"class":400},[240,2114,2115,2118,2120,2122,2124,2126,2128,2130],{"class":317,"line":465},[240,2116,2117],{"class":436},"    on_tool_result",[240,2119,440],{"class":400},[240,2121,1739],{"class":486},[240,2123,2085],{"class":643},[240,2125,487],{"class":486},[240,2127,626],{"class":494},[240,2129,609],{"class":608},[240,2131,2012],{"class":400},[240,2133,2134,2137,2139,2141,2143,2145,2147,2149],{"class":317,"line":514},[240,2135,2136],{"class":436},"    on_snapshot",[240,2138,440],{"class":400},[240,2140,1739],{"class":486},[240,2142,2085],{"class":643},[240,2144,487],{"class":486},[240,2146,626],{"class":494},[240,2148,609],{"class":608},[240,2150,2012],{"class":400},[240,2152,2153,2156,2158,2161,2163,2165,2167,2169],{"class":317,"line":519},[240,2154,2155],{"class":436},"    accountant",[240,2157,440],{"class":400},[240,2159,2160],{"class":339}," ContextAccountant ",[240,2162,1334],{"class":494},[240,2164,609],{"class":608},[240,2166,626],{"class":494},[240,2168,609],{"class":608},[240,2170,2012],{"class":400},[240,2172,2173,2176,2178,2181,2183,2185,2187,2189],{"class":317,"line":524},[240,2174,2175],{"class":436},"    compactor",[240,2177,440],{"class":400},[240,2179,2180],{"class":339}," Compactor ",[240,2182,1334],{"class":494},[240,2184,609],{"class":608},[240,2186,626],{"class":494},[240,2188,609],{"class":608},[240,2190,2012],{"class":400},[240,2192,2193,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214],{"class":317,"line":534},[240,2194,2195],{"class":436},"    pinned_tools",[240,2197,440],{"class":400},[240,2199,815],{"class":339},[240,2201,456],{"class":400},[240,2203,459],{"class":443},[240,2205,822],{"class":400},[240,2207,825],{"class":494},[240,2209,609],{"class":608},[240,2211,626],{"class":494},[240,2213,609],{"class":608},[240,2215,2012],{"class":400},[240,2217,2218,2221,2223,2225,2227,2229],{"class":317,"line":547},[240,2219,2220],{"class":436},"    tools_per_turn",[240,2222,440],{"class":400},[240,2224,800],{"class":443},[240,2226,626],{"class":494},[240,2228,805],{"class":646},[240,2230,2012],{"class":400},[240,2232,2233,2235,2237,2239],{"class":317,"line":561},[240,2234,447],{"class":400},[240,2236,450],{"class":400},[240,2238,444],{"class":443},[240,2240,544],{"class":400},[240,2242,2243,2245,2248,2251,2253],{"class":317,"line":566},[240,2244,1596],{"class":328},[240,2246,2247],{"class":339}," transcript ",[240,2249,2250],{"class":494},"is",[240,2252,609],{"class":608},[240,2254,544],{"class":400},[240,2256,2257,2260,2262,2264,2266,2269,2271,2273],{"class":317,"line":584},[240,2258,2259],{"class":339},"        transcript ",[240,2261,884],{"class":494},[240,2263,1515],{"class":477},[240,2265,433],{"class":400},[240,2267,2268],{"class":1008},"system",[240,2270,884],{"class":494},[240,2272,2268],{"class":477},[240,2274,723],{"class":400},[240,2276,2277,2279,2281,2283,2285,2288,2290,2293,2295,2298],{"class":317,"line":589},[240,2278,2039],{"class":339},[240,2280,474],{"class":400},[240,2282,1244],{"class":477},[240,2284,433],{"class":400},[240,2286,2287],{"class":477},"Message",[240,2289,474],{"class":400},[240,2291,2292],{"class":477},"user_text",[240,2294,433],{"class":400},[240,2296,2297],{"class":477},"user_message",[240,2299,980],{"class":400},[240,2301,2302,2305,2307,2310,2312,2315],{"class":317,"line":614},[240,2303,2304],{"class":339},"    accountant ",[240,2306,884],{"class":494},[240,2308,2309],{"class":339}," accountant ",[240,2311,890],{"class":494},[240,2313,2314],{"class":477}," ContextAccountant",[240,2316,895],{"class":400},[240,2318,2319,2322,2324,2327,2329,2332,2334,2337,2339,2342],{"class":317,"line":632},[240,2320,2321],{"class":339},"    compactor ",[240,2323,884],{"class":494},[240,2325,2326],{"class":339}," compactor ",[240,2328,890],{"class":494},[240,2330,2331],{"class":477}," Compactor",[240,2333,433],{"class":400},[240,2335,2336],{"class":477},"accountant",[240,2338,500],{"class":400},[240,2340,2341],{"class":477}," provider",[240,2343,723],{"class":400},[240,2345,2346],{"class":317,"line":694},[240,2347,347],{"emptyLinePlaceholder":346},[240,2349,2350,2352,2355,2357,2360,2362,2365],{"class":317,"line":700},[240,2351,1751],{"class":328},[240,2353,2354],{"class":339}," _ ",[240,2356,683],{"class":328},[240,2358,2359],{"class":991}," range",[240,2361,433],{"class":400},[240,2363,2364],{"class":991},"MAX_ITERATIONS",[240,2366,1663],{"class":400},[240,2368,2369],{"class":317,"line":726},[240,2370,2371],{"class":321},"        # Select tools for this turn.\n",[240,2373,2374,2377,2379,2381,2383,2385],{"class":317,"line":767},[240,2375,2376],{"class":339},"        query ",[240,2378,884],{"class":494},[240,2380,1540],{"class":477},[240,2382,433],{"class":400},[240,2384,1545],{"class":477},[240,2386,723],{"class":400},[240,2388,2389,2392,2394,2397,2399,2402,2404,2406,2408,2410,2412,2415],{"class":317,"line":772},[240,2390,2391],{"class":339},"        selected ",[240,2393,884],{"class":494},[240,2395,2396],{"class":339}," catalog",[240,2398,474],{"class":400},[240,2400,2401],{"class":477},"select",[240,2403,433],{"class":400},[240,2405,977],{"class":477},[240,2407,500],{"class":400},[240,2409,795],{"class":1008},[240,2411,884],{"class":494},[240,2413,2414],{"class":477},"tools_per_turn",[240,2416,2012],{"class":400},[240,2418,2419,2422,2424,2427],{"class":317,"line":846},[240,2420,2421],{"class":1008},"                                   must_include",[240,2423,884],{"class":494},[240,2425,2426],{"class":477},"pinned_tools",[240,2428,723],{"class":400},[240,2430,2431,2434,2436,2439,2441,2443,2445,2448],{"class":317,"line":855},[240,2432,2433],{"class":339},"        registry ",[240,2435,884],{"class":494},[240,2437,2438],{"class":477}," ToolRegistry",[240,2440,433],{"class":400},[240,2442,761],{"class":1008},[240,2444,884],{"class":494},[240,2446,2447],{"class":477},"selected",[240,2449,723],{"class":400},[240,2451,2452],{"class":317,"line":860},[240,2453,347],{"emptyLinePlaceholder":346},[240,2455,2456,2459,2461,2464,2466,2469,2471,2473,2475,2478,2480,2483,2485,2488],{"class":317,"line":866},[240,2457,2458],{"class":339},"        snapshot ",[240,2460,884],{"class":494},[240,2462,2463],{"class":339}," accountant",[240,2465,474],{"class":400},[240,2467,2468],{"class":477},"snapshot",[240,2470,433],{"class":400},[240,2472,1545],{"class":477},[240,2474,500],{"class":400},[240,2476,2477],{"class":1008}," tools",[240,2479,884],{"class":494},[240,2481,2482],{"class":477},"registry",[240,2484,474],{"class":400},[240,2486,2487],{"class":477},"schemas",[240,2489,511],{"class":400},[240,2491,2492,2495,2498,2500,2503,2505],{"class":317,"line":872},[240,2493,2494],{"class":328},"        if",[240,2496,2497],{"class":339}," on_snapshot ",[240,2499,2250],{"class":494},[240,2501,2502],{"class":494}," not",[240,2504,609],{"class":608},[240,2506,544],{"class":400},[240,2508,2509,2512,2514,2516],{"class":317,"line":878},[240,2510,2511],{"class":477},"            on_snapshot",[240,2513,433],{"class":400},[240,2515,2468],{"class":477},[240,2517,723],{"class":400},[240,2519,2520],{"class":317,"line":898},[240,2521,347],{"emptyLinePlaceholder":346},[240,2523,2524,2526,2529,2531,2534,2536,2538,2541,2543],{"class":317,"line":946},[240,2525,2494],{"class":328},[240,2527,2528],{"class":339}," snapshot",[240,2530,474],{"class":400},[240,2532,2533],{"class":622},"state",[240,2535,1736],{"class":494},[240,2537,1739],{"class":486},[240,2539,2540],{"class":643},"red",[240,2542,487],{"class":486},[240,2544,544],{"class":400},[240,2546,2547,2550,2553,2555,2558,2560,2562,2564,2567,2569,2571],{"class":317,"line":951},[240,2548,2549],{"class":328},"            await",[240,2551,2552],{"class":339}," compactor",[240,2554,474],{"class":400},[240,2556,2557],{"class":477},"compact_if_needed",[240,2559,433],{"class":400},[240,2561,1545],{"class":477},[240,2563,500],{"class":400},[240,2565,2566],{"class":477}," registry",[240,2568,474],{"class":400},[240,2570,2487],{"class":477},[240,2572,511],{"class":400},[240,2574,2575],{"class":317,"line":983},[240,2576,347],{"emptyLinePlaceholder":346},[240,2578,2579,2582,2584,2587,2590,2592,2595,2597,2599,2601,2603,2605,2608,2610,2613],{"class":317,"line":1036},[240,2580,2581],{"class":339},"        response ",[240,2583,884],{"class":494},[240,2585,2586],{"class":328}," await",[240,2588,2589],{"class":477}," _one_turn",[240,2591,433],{"class":400},[240,2593,2594],{"class":477},"provider",[240,2596,500],{"class":400},[240,2598,2566],{"class":477},[240,2600,500],{"class":400},[240,2602,1599],{"class":477},[240,2604,500],{"class":400},[240,2606,2607],{"class":1008}," on_event",[240,2609,884],{"class":494},[240,2611,2612],{"class":477},"on_event",[240,2614,723],{"class":400},[240,2616,2617],{"class":317,"line":1041},[240,2618,347],{"emptyLinePlaceholder":346},[240,2620,2621,2623,2626,2628,2631],{"class":317,"line":1074},[240,2622,2494],{"class":328},[240,2624,2625],{"class":339}," response",[240,2627,474],{"class":400},[240,2629,2630],{"class":622},"is_final",[240,2632,544],{"class":400},[240,2634,2635,2638,2640,2642,2644,2646,2648,2651,2653,2656],{"class":317,"line":1100},[240,2636,2637],{"class":339},"            transcript",[240,2639,474],{"class":400},[240,2641,1244],{"class":477},[240,2643,433],{"class":400},[240,2645,2287],{"class":477},[240,2647,474],{"class":400},[240,2649,2650],{"class":477},"from_assistant_response",[240,2652,433],{"class":400},[240,2654,2655],{"class":477},"response",[240,2657,980],{"class":400},[240,2659,2660,2663,2665,2667,2669,2672],{"class":317,"line":1127},[240,2661,2662],{"class":328},"            return",[240,2664,2625],{"class":339},[240,2666,474],{"class":400},[240,2668,437],{"class":622},[240,2670,2671],{"class":494}," or",[240,2673,2674],{"class":486}," \"\"\n",[240,2676,2677],{"class":317,"line":1148},[240,2678,347],{"emptyLinePlaceholder":346},[240,2680,2681,2684,2686,2688,2690,2692,2694,2696,2698,2700],{"class":317,"line":1165},[240,2682,2683],{"class":339},"        transcript",[240,2685,474],{"class":400},[240,2687,1244],{"class":477},[240,2689,433],{"class":400},[240,2691,2287],{"class":477},[240,2693,474],{"class":400},[240,2695,2650],{"class":477},[240,2697,433],{"class":400},[240,2699,2655],{"class":477},[240,2701,980],{"class":400},[240,2703,2704,2706,2709,2711,2713,2715,2718],{"class":317,"line":1171},[240,2705,1130],{"class":328},[240,2707,2708],{"class":339}," ref ",[240,2710,683],{"class":328},[240,2712,2625],{"class":339},[240,2714,474],{"class":400},[240,2716,2717],{"class":622},"tool_calls",[240,2719,544],{"class":400},[240,2721,2722,2725,2727,2729,2731,2734,2736,2739,2741,2743,2745,2748,2750,2752,2754,2756,2758,2761],{"class":317,"line":1192},[240,2723,2724],{"class":339},"            result ",[240,2726,884],{"class":494},[240,2728,2566],{"class":339},[240,2730,474],{"class":400},[240,2732,2733],{"class":477},"dispatch",[240,2735,433],{"class":400},[240,2737,2738],{"class":477},"ref",[240,2740,474],{"class":400},[240,2742,655],{"class":622},[240,2744,500],{"class":400},[240,2746,2747],{"class":477}," ref",[240,2749,474],{"class":400},[240,2751,1872],{"class":622},[240,2753,500],{"class":400},[240,2755,2747],{"class":477},[240,2757,474],{"class":400},[240,2759,2760],{"class":622},"id",[240,2762,723],{"class":400},[240,2764,2765,2767,2769,2771,2773,2775,2777,2780,2782,2785],{"class":317,"line":1212},[240,2766,2637],{"class":339},[240,2768,474],{"class":400},[240,2770,1244],{"class":477},[240,2772,433],{"class":400},[240,2774,2287],{"class":477},[240,2776,474],{"class":400},[240,2778,2779],{"class":477},"tool_result",[240,2781,433],{"class":400},[240,2783,2784],{"class":477},"result",[240,2786,980],{"class":400},[240,2788,2789],{"class":317,"line":1218},[240,2790,347],{"emptyLinePlaceholder":346},[240,2792,2793,2796,2799,2801,2803,2806,2808,2810,2812,2815],{"class":317,"line":1231},[240,2794,2795],{"class":328},"    raise",[240,2797,2798],{"class":443}," RuntimeError",[240,2800,433],{"class":400},[240,2802,640],{"class":425},[240,2804,2805],{"class":643},"\"agent did not finish in ",[240,2807,647],{"class":646},[240,2809,2364],{"class":991},[240,2811,658],{"class":646},[240,2813,2814],{"class":643}," iterations\"",[240,2816,723],{"class":400},[112,2818,2819],{},"One concern: what if the model wants to call a tool that wasn't selected this turn? Two cases.",[112,2821,2822,2825],{},[135,2823,2824],{},"The tool was filtered out."," This is OK and informative — the model gets an \"unknown tool\" error from the registry, and next turn the query (which now includes the model's attempted tool name) is likely to bring that tool back into the selection. Try-fail-retry is the mechanism, and it converges fast.",[112,2827,2828,2831],{},[135,2829,2830],{},"The tool doesn't exist in the catalog."," Same error. No recovery possible; the model has genuinely hallucinated.",[112,2833,2834,2837,2838,2841],{},[135,2835,2836],{},"The model doesn't know the tool exists."," This is the one try-fail-retry ",[115,2839,2840],{},"cannot"," fix: the model can't attempt a tool whose name it hasn't seen, and the selector only surfaces what the current query matches. On first turns or mid-task pivots, that's often nothing useful. §12.5 builds a discovery tool you pin into every turn so the model always has a way to ask \"what can I do?\" — without it, the other two recovery paths above are dead letters.",[112,2843,2844],{},"The registry already handles the first two cases with the same close-match suggestion from Chapter 6. The catalog approach doesn't need new error paths for them — but it does need the discovery tool for the third.",[266,2846],{},[269,2848,2850],{"id":2849},"_125-the-discovery-tool","12.5 The Discovery Tool",[112,2852,2853,2854,2857,2858,440],{},"Two scenarios break the selector if you only rely on per-turn BM25 matching. The first: a vague opener — \"hi\", \"help\", \"what can you do?\" — produces a query that scores every tool at zero, and the selection is empty. The second: mid-task pivots — the user asks for a capability BM25 doesn't associate with the current transcript (\"now post a summary to Slack\" after five turns of file work). In both cases, the fix is the same. The agent needs a tool it can ",[115,2855,2856],{},"always"," call to see the full catalog, so it can decide for itself whether the capability it needs exists. That tool is ",[156,2859,2860],{},"list_available_tools",[307,2862,2864],{"className":309,"code":2863,"language":311,"meta":312,"style":312},"# src\u002Fharness\u002Ftools\u002Fselector.py (continued)\n\ndef discovery_tool(catalog: ToolCatalog) -> Tool:\n    from .decorator import tool as tool_decorator\n\n    @tool_decorator(side_effects={\"read\"})\n    def list_available_tools(filter_term: str | None = None) -> str:\n        \"\"\"List tools available in this harness.\n\n        filter_term: optional substring to match against tool name or\n                     description. Use this to narrow a large catalog.\n\n        Returns a newline-separated list of `name — one-line summary`.\n\n        Use this when you think a capability you need exists but isn't in\n        your current tool list. After discovering a tool name, you can call\n        it directly — the tool will be loaded for your next turn.\n        \"\"\"\n        results = []\n        for t in catalog.tools:\n            first_line = t.description.split(\"\\n\", 1)[0]\n            text = f\"{t.name} — {first_line}\"\n            if filter_term and filter_term.lower() not in text.lower():\n                continue\n            results.append(text)\n        return \"\\n\".join(results) if results else \"(no matching tools)\"\n\n    return list_available_tools\n",[156,2865,2866,2870,2874,2899,2920,2924,2951,2983,2990,2994,2999,3004,3008,3013,3017,3022,3027,3032,3036,3045,3061,3101,3136,3169,3173,3188,3224,3228],{"__ignoreMap":312},[240,2867,2868],{"class":317,"line":318},[240,2869,1496],{"class":321},[240,2871,2872],{"class":317,"line":325},[240,2873,347],{"emptyLinePlaceholder":346},[240,2875,2876,2878,2881,2883,2886,2888,2890,2892,2894,2897],{"class":317,"line":343},[240,2877,426],{"class":425},[240,2879,2880],{"class":429}," discovery_tool",[240,2882,433],{"class":400},[240,2884,2885],{"class":436},"catalog",[240,2887,440],{"class":400},[240,2889,541],{"class":339},[240,2891,447],{"class":400},[240,2893,450],{"class":400},[240,2895,2896],{"class":339}," Tool",[240,2898,544],{"class":400},[240,2900,2901,2904,2906,2909,2911,2914,2917],{"class":317,"line":350},[240,2902,2903],{"class":328},"    from",[240,2905,401],{"class":400},[240,2907,2908],{"class":339},"decorator ",[240,2910,353],{"class":328},[240,2912,2913],{"class":339}," tool ",[240,2915,2916],{"class":328},"as",[240,2918,2919],{"class":339}," tool_decorator\n",[240,2921,2922],{"class":317,"line":359},[240,2923,347],{"emptyLinePlaceholder":346},[240,2925,2926,2929,2932,2934,2937,2939,2941,2943,2946,2948],{"class":317,"line":372},[240,2927,2928],{"class":527},"    @",[240,2930,2931],{"class":429},"tool_decorator",[240,2933,433],{"class":400},[240,2935,2936],{"class":1008},"side_effects",[240,2938,884],{"class":494},[240,2940,647],{"class":400},[240,2942,487],{"class":486},[240,2944,2945],{"class":643},"read",[240,2947,487],{"class":486},[240,2949,2950],{"class":400},"})\n",[240,2952,2953,2955,2958,2960,2963,2965,2967,2969,2971,2973,2975,2977,2979,2981],{"class":317,"line":377},[240,2954,592],{"class":425},[240,2956,2957],{"class":429}," list_available_tools",[240,2959,433],{"class":400},[240,2961,2962],{"class":436},"filter_term",[240,2964,440],{"class":400},[240,2966,444],{"class":443},[240,2968,825],{"class":494},[240,2970,609],{"class":608},[240,2972,626],{"class":494},[240,2974,609],{"class":608},[240,2976,447],{"class":400},[240,2978,450],{"class":400},[240,2980,444],{"class":443},[240,2982,544],{"class":400},[240,2984,2985,2987],{"class":317,"line":390},[240,2986,849],{"class":550},[240,2988,2989],{"class":554},"List tools available in this harness.\n",[240,2991,2992],{"class":317,"line":395},[240,2993,347],{"emptyLinePlaceholder":346},[240,2995,2996],{"class":317,"line":412},[240,2997,2998],{"class":554},"        filter_term: optional substring to match against tool name or\n",[240,3000,3001],{"class":317,"line":417},[240,3002,3003],{"class":554},"                     description. Use this to narrow a large catalog.\n",[240,3005,3006],{"class":317,"line":422},[240,3007,347],{"emptyLinePlaceholder":346},[240,3009,3010],{"class":317,"line":465},[240,3011,3012],{"class":554},"        Returns a newline-separated list of `name — one-line summary`.\n",[240,3014,3015],{"class":317,"line":514},[240,3016,347],{"emptyLinePlaceholder":346},[240,3018,3019],{"class":317,"line":519},[240,3020,3021],{"class":554},"        Use this when you think a capability you need exists but isn't in\n",[240,3023,3024],{"class":317,"line":524},[240,3025,3026],{"class":554},"        your current tool list. After discovering a tool name, you can call\n",[240,3028,3029],{"class":317,"line":534},[240,3030,3031],{"class":554},"        it directly — the tool will be loaded for your next turn.\n",[240,3033,3034],{"class":317,"line":547},[240,3035,875],{"class":550},[240,3037,3038,3041,3043],{"class":317,"line":561},[240,3039,3040],{"class":339},"        results ",[240,3042,884],{"class":494},[240,3044,1586],{"class":400},[240,3046,3047,3049,3051,3053,3055,3057,3059],{"class":317,"line":566},[240,3048,1130],{"class":328},[240,3050,680],{"class":339},[240,3052,683],{"class":328},[240,3054,2396],{"class":339},[240,3056,474],{"class":400},[240,3058,761],{"class":622},[240,3060,544],{"class":400},[240,3062,3063,3066,3068,3071,3073,3075,3077,3080,3082,3084,3087,3089,3091,3094,3097,3099],{"class":317,"line":584},[240,3064,3065],{"class":339},"            first_line ",[240,3067,884],{"class":494},[240,3069,3070],{"class":339}," t",[240,3072,474],{"class":400},[240,3074,668],{"class":622},[240,3076,474],{"class":400},[240,3078,3079],{"class":477},"split",[240,3081,433],{"class":400},[240,3083,487],{"class":486},[240,3085,3086],{"class":332},"\\n",[240,3088,487],{"class":486},[240,3090,500],{"class":400},[240,3092,3093],{"class":646}," 1",[240,3095,3096],{"class":400},")[",[240,3098,242],{"class":646},[240,3100,581],{"class":400},[240,3102,3103,3106,3108,3111,3113,3115,3117,3119,3121,3123,3126,3128,3131,3133],{"class":317,"line":589},[240,3104,3105],{"class":339},"            text ",[240,3107,884],{"class":494},[240,3109,3110],{"class":425}," f",[240,3112,487],{"class":643},[240,3114,647],{"class":646},[240,3116,650],{"class":339},[240,3118,474],{"class":400},[240,3120,655],{"class":622},[240,3122,658],{"class":646},[240,3124,3125],{"class":643}," — ",[240,3127,647],{"class":646},[240,3129,3130],{"class":339},"first_line",[240,3132,658],{"class":646},[240,3134,3135],{"class":643},"\"\n",[240,3137,3138,3140,3143,3146,3149,3151,3153,3156,3158,3160,3162,3164,3166],{"class":317,"line":614},[240,3139,1151],{"class":328},[240,3141,3142],{"class":339}," filter_term ",[240,3144,3145],{"class":494},"and",[240,3147,3148],{"class":339}," filter_term",[240,3150,474],{"class":400},[240,3152,508],{"class":477},[240,3154,3155],{"class":400},"()",[240,3157,2502],{"class":494},[240,3159,1204],{"class":494},[240,3161,503],{"class":339},[240,3163,474],{"class":400},[240,3165,508],{"class":477},[240,3167,3168],{"class":400},"():\n",[240,3170,3171],{"class":317,"line":632},[240,3172,1215],{"class":328},[240,3174,3175,3178,3180,3182,3184,3186],{"class":317,"line":694},[240,3176,3177],{"class":339},"            results",[240,3179,474],{"class":400},[240,3181,1244],{"class":477},[240,3183,433],{"class":400},[240,3185,437],{"class":477},[240,3187,723],{"class":400},[240,3189,3190,3192,3194,3196,3198,3200,3202,3204,3207,3209,3211,3214,3217,3219,3222],{"class":317,"line":700},[240,3191,1295],{"class":328},[240,3193,1739],{"class":486},[240,3195,3086],{"class":332},[240,3197,487],{"class":486},[240,3199,474],{"class":400},[240,3201,1898],{"class":477},[240,3203,433],{"class":400},[240,3205,3206],{"class":477},"results",[240,3208,447],{"class":400},[240,3210,1725],{"class":328},[240,3212,3213],{"class":339}," results ",[240,3215,3216],{"class":328},"else",[240,3218,1739],{"class":486},[240,3220,3221],{"class":643},"(no matching tools)",[240,3223,3135],{"class":486},[240,3225,3226],{"class":317,"line":726},[240,3227,347],{"emptyLinePlaceholder":346},[240,3229,3230,3232],{"class":317,"line":767},[240,3231,468],{"class":328},[240,3233,3234],{"class":339}," list_available_tools\n",[112,3236,3237,3238,3240],{},"Pin this tool. \"Pinning\" means it shows up in every turn's selection regardless of BM25 score — which is exactly how you build it into the ",[156,3239,1924],{}," call:",[307,3242,3244],{"className":309,"code":3243,"language":311,"meta":312,"style":312},"# wiring: build the catalog, include the discovery tool in it, then pin\n# its name so every turn's selection contains at least this one entry.\ncatalog = ToolCatalog(tools=all_tools + [discovery_tool(catalog)])\nawait arun(\n    provider=provider,\n    catalog=catalog,\n    user_message=user_message,\n    pinned_tools={\"list_available_tools\"},  # always surfaces, score be damned\n    tools_per_turn=7,\n)\n",[156,3245,3246,3251,3256,3288,3297,3307,3317,3327,3347,3358],{"__ignoreMap":312},[240,3247,3248],{"class":317,"line":318},[240,3249,3250],{"class":321},"# wiring: build the catalog, include the discovery tool in it, then pin\n",[240,3252,3253],{"class":317,"line":325},[240,3254,3255],{"class":321},"# its name so every turn's selection contains at least this one entry.\n",[240,3257,3258,3261,3263,3265,3267,3269,3271,3274,3276,3278,3281,3283,3285],{"class":317,"line":343},[240,3259,3260],{"class":339},"catalog ",[240,3262,884],{"class":494},[240,3264,541],{"class":477},[240,3266,433],{"class":400},[240,3268,761],{"class":1008},[240,3270,884],{"class":494},[240,3272,3273],{"class":477},"all_tools ",[240,3275,495],{"class":494},[240,3277,906],{"class":400},[240,3279,3280],{"class":477},"discovery_tool",[240,3282,433],{"class":400},[240,3284,2885],{"class":477},[240,3286,3287],{"class":400},")])\n",[240,3289,3290,3293,3295],{"class":317,"line":350},[240,3291,3292],{"class":328},"await",[240,3294,1996],{"class":477},[240,3296,1999],{"class":400},[240,3298,3299,3301,3303,3305],{"class":317,"line":359},[240,3300,2004],{"class":1008},[240,3302,884],{"class":494},[240,3304,2594],{"class":477},[240,3306,2012],{"class":400},[240,3308,3309,3311,3313,3315],{"class":317,"line":372},[240,3310,2017],{"class":1008},[240,3312,884],{"class":494},[240,3314,2885],{"class":477},[240,3316,2012],{"class":400},[240,3318,3319,3321,3323,3325],{"class":317,"line":377},[240,3320,2028],{"class":1008},[240,3322,884],{"class":494},[240,3324,2297],{"class":477},[240,3326,2012],{"class":400},[240,3328,3329,3331,3333,3335,3337,3339,3341,3344],{"class":317,"line":390},[240,3330,2195],{"class":1008},[240,3332,884],{"class":494},[240,3334,647],{"class":400},[240,3336,487],{"class":486},[240,3338,2860],{"class":643},[240,3340,487],{"class":486},[240,3342,3343],{"class":400},"},",[240,3345,3346],{"class":321},"  # always surfaces, score be damned\n",[240,3348,3349,3351,3353,3356],{"class":317,"line":395},[240,3350,2220],{"class":1008},[240,3352,884],{"class":494},[240,3354,3355],{"class":646},"7",[240,3357,2012],{"class":400},[240,3359,3360],{"class":317,"line":412},[240,3361,723],{"class":400},[112,3363,3364],{},"The tool's docstring instruction (\"call it directly after discovery\") works because the next turn's query will include the tool name the model just tried, and normal BM25 will surface it without needing a second discovery round-trip.",[112,3366,3367],{},"This is Cursor's pattern, approximately: the agent has a codebase search tool as a first-class primitive, and uses it to discover what's relevant. We've generalized the idea to tool discovery.",[266,3369],{},[269,3371,3373],{"id":3372},"_126-does-this-actually-work","12.6 Does This Actually Work?",[112,3375,3376,3377,1427,3380,1427,3383,1427,3386,1427,3389,1427,3392,1427,3395,1427,3398,1427,3401,1427,3404,1427,3407,3410,3411,1427,3413,1427,3415,3417],{},"The selector is cheap to try. Build a catalog with thirty tools (invent some plausible ones: ",[156,3378,3379],{},"github_search",[156,3381,3382],{},"npm_info",[156,3384,3385],{},"read_file_viewport",[156,3387,3388],{},"edit_lines",[156,3390,3391],{},"run_tests",[156,3393,3394],{},"diff",[156,3396,3397],{},"git_status",[156,3399,3400],{},"git_diff",[156,3402,3403],{},"git_log",[156,3405,3406],{},"http_get",[156,3408,3409],{},"http_post",", ... any ten are enough), pin ",[156,3412,2860],{},[156,3414,1430],{},[156,3416,1426],{},", and watch what happens in a real task.",[112,3419,3420],{},"Three observations typically hold.",[112,3422,3423,3426,3427,1427,3429,3431,3432,3434],{},[135,3424,3425],{},"Selection is mostly right."," For a clear task (\"read this file and fix the bug\"), the top-7 selection includes ",[156,3428,3385],{},[156,3430,3388],{},", maybe ",[156,3433,3391],{},". The irrelevant twenty tools stay out of context.",[112,3436,3437,3440,3441,3443],{},[135,3438,3439],{},"The model rarely hits missing tools."," When it does, it often recovers by calling ",[156,3442,2860],{}," and trying again. Pinning that discovery tool pays for itself many times.",[112,3445,3446,3449],{},[135,3447,3448],{},"Schema overhead drops roughly linearly with the selected-tool count."," Going from 30 tools to 7 reduces tool-schema tokens by about 75% on our examples. That's real context budget returned.",[112,3451,3452,3453,3456,3457,3460,3461,3464,3465,3467,3468,3470],{},"One counter-observation: even with ",[156,3454,3455],{},"tools_per_turn=7"," or higher, the selector will occasionally miss a tool the model needs mid-task — a Slack tool when the transcript is dominated by file operations, say. This is the case §12.5's discovery tool handles: the model calls ",[156,3458,3459],{},"list_available_tools(\"slack\")",", sees ",[156,3462,3463],{},"slack_post"," exists, calls it, and the next turn's query (now containing ",[156,3466,3463],{},") surfaces it through normal selection. Tuning ",[156,3469,2414],{}," reduces but doesn't eliminate this — pinning discovery is the reliable fix. Chapter 19's eval harness is how you tune both knobs empirically.",[266,3472],{},[269,3474,3476],{"id":3475},"_127-when-not-to-use-a-selector","12.7 When Not to Use a Selector",[112,3478,3479],{},"If your harness has five tools, use them all, all the time. The selector costs more (BM25 index, query building) than it saves. The cliff doesn't exist below ~20 tools.",[112,3481,3482],{},"If your tools are sharply siloed — a codebase search tool, a shell tool, a deployment tool — and the user clearly wants one silo at a time, a simple mode switch is cleaner than dynamic retrieval. Cursor's \"agent mode\" vs \"ask mode\" is this pattern.",[112,3484,3485,3486,3489],{},"If you have 200+ tools, BM25 starts to miss; you want embeddings. The interface (",[156,3487,3488],{},"catalog.select(query, k)",") doesn't change. The implementation does.",[112,3491,3492],{},"We use the selector in this book's harness from Chapter 13 onward — where we integrate MCP tools (potentially many) — because that's the point where the tool count crosses over into selector-justifying territory.",[266,3494],{},[269,3496,3498],{"id":3497},"_128-commit","12.8 Commit",[307,3500,3504],{"className":3501,"code":3502,"language":3503,"meta":312,"style":312},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","git add -A && git commit -m \"ch12: dynamic tool loading with ToolCatalog\"\ngit tag ch12-selector\n","bash",[156,3505,3506,3536],{"__ignoreMap":312},[240,3507,3508,3511,3514,3517,3520,3523,3526,3529,3531,3534],{"class":317,"line":318},[240,3509,3510],{"class":540},"git",[240,3512,3513],{"class":643}," add",[240,3515,3516],{"class":490}," -A",[240,3518,3519],{"class":400}," &&",[240,3521,3522],{"class":540}," git",[240,3524,3525],{"class":643}," commit",[240,3527,3528],{"class":490}," -m",[240,3530,1739],{"class":486},[240,3532,3533],{"class":643},"ch12: dynamic tool loading with ToolCatalog",[240,3535,3135],{"class":486},[240,3537,3538,3540,3543],{"class":317,"line":325},[240,3539,3510],{"class":540},[240,3541,3542],{"class":643}," tag",[240,3544,3545],{"class":643}," ch12-selector\n",[269,3547,3549],{"id":3548},"_129-try-it-yourself","12.9 Try It Yourself",[3551,3552,3553,3563,3576],"ol",{},[3554,3555,3556,3559,3560,3562],"li",{},[135,3557,3558],{},"Calibrate the selector on your own corpus."," Build a catalog of fifteen realistic tools and run the agent through ten representative tasks. Log the selected top-7 per turn. How often did the model try to call a tool that wasn't selected? How often did the fallback (",[156,3561,2860],{},") recover?",[3554,3564,3565,3568,3569,1427,3572,3575],{},[135,3566,3567],{},"Break it with poor descriptions."," Rename your tools to ",[156,3570,3571],{},"tool_1",[156,3573,3574],{},"tool_2",", ... and give them vague descriptions. Run the selector. Observe the degradation. This is a direct measure of how much description quality matters — a lesson that applies regardless of whether you use a selector.",[3554,3577,3578,3581],{},[135,3579,3580],{},"Swap BM25 for embeddings."," Use a small embedding model (sentence-transformers works offline) to produce the catalog's search index. Measure whether this changes selection quality on paraphrase-heavy queries. If it does, how much? Is the cost worth it?",[266,3583],{},[3585,3586,3587,3590],"what-you-understand",{},[112,3588,3589],{},"The harness can scale past the tool cliff without changing any tool definitions. The selector loads 7 tools per turn from a larger catalog; the discovery tool lets the agent surface anything the selector misses; the per-turn query is derived cheaply from the transcript. Token costs drop, selection quality stays usable. Below 20 tools, don't bother; above 30, this is how you keep the model sharp.",[112,3591,3592],{},"What's still missing. Every tool in the catalog is one we wrote. Real harnesses want to integrate external tools — git, GitHub, Slack, a database — without writing custom wrappers each time. The Model Context Protocol exists for this; Chapter 13 builds an MCP client that plugs into the registry and the selector, so any MCP server's tools become indistinguishable from the ones we built by hand.",[3594,3595,3596],"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 .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .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 .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .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 .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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 .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);}",{"title":312,"searchDepth":325,"depth":325,"links":3598},[3599,3600,3601,3602,3603,3604,3605,3606,3607],{"id":271,"depth":325,"text":272},{"id":301,"depth":325,"text":302},{"id":1467,"depth":325,"text":1468},{"id":1913,"depth":325,"text":1914},{"id":2849,"depth":325,"text":2850},{"id":3372,"depth":325,"text":3373},{"id":3475,"depth":325,"text":3476},{"id":3497,"depth":325,"text":3498},{"id":3548,"depth":325,"text":3549},"md",{},null,{"title":58,"description":117},"V0dC35P6-qi7nbYVTQRxCIj-gpc1rY_BY-EUXHfS6dQ",[3614,3616],{"title":54,"path":55,"stem":56,"description":3615,"children":-1},"Previously: context-engineering pillars are in place — accounting, compaction, scratchpad, retrieval. What's left is the source of most of the context pressure we've been managing: tools that return too much because they were designed for humans, not models.",{"title":62,"path":63,"stem":64,"description":3617,"children":-1},"Previously: the harness can scale past the tool cliff via dynamic loading. All tools are still ones we wrote. This chapter plugs in external tool servers.",1776848984233]