[{"data":1,"prerenderedAt":3522},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Fdesigning-tools":102,"surround-\u002Fchapters\u002Fdesigning-tools":3517},[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":54,"body":104,"description":117,"extension":3512,"meta":3513,"navigation":3514,"path":55,"seo":3515,"stem":56,"__hash__":3516},"content\u002F2.chapters\u002F11.designing-tools.md",{"type":105,"value":106,"toc":3501},"minimark",[107,111,118,125,141,247,250,255,258,269,275,285,295,298,300,304,1223,1226,1232,1242,1256,1273,1275,1279,2253,2256,2292,2295,2318,2320,2324,2335,2413,2416,2674,2684,2686,2690,2693,2701,2707,3237,3254,3265,3267,3271,3290,3293,3332,3335,3337,3341,3357,3360,3377,3383,3385,3389,3437,3441,3476,3478,3497],[108,109,54],"h1",{"id":110},"chapter-11-designing-tools-models-can-actually-use",[112,113,114],"p",{},[115,116,117],"em",{},"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.",[112,119,120,121,124],{},"Yang et al.'s 2024 SWE-agent paper (cited in Chapter 4, where we first used its \"tool design is interface design\" framing) made a sharper central claim: the interface between the LLM and the computer — what the paper names the ",[115,122,123],{},"Agent-Computer Interface",", or ACI — matters as much as the LLM itself. Their headline empirical result was that the same model, evaluated on Jimenez et al.'s 2024 SWE-bench benchmark of real GitHub issues, went from near-zero to 12.5% pass@1 by changing nothing but the ACI. Most of that improvement came from tool designs that constrained what the model could see and do in ways that matched its actual cognitive affordances: small viewport into a file rather than the whole file, line-range edits rather than full-file rewrites, errors that suggested what to do next rather than just saying what went wrong.",[112,126,127,128,132,133,136,137,140],{},"Our ",[129,130,131],"code",{},"read_file"," returns the whole file. Our ",[129,134,135],{},"write_file"," overwrites the whole file. That's wrong for models in the same way ",[129,138,139],{},"cat \u002Fetc\u002Fpasswd"," piped to a user in Notepad would be wrong: too much data, no structure, no navigation. This chapter rebuilds the file tools — and establishes the discipline — around the ACI principles.",[142,143,147,148,147,239],"figure",{"className":144},[145,146],"not-prose","my-8","\n  ",[149,150,155,156,155,207,147],"div",{"className":151},[152,153,154],"grid","grid-cols-2","gap-4","\n    ",[149,157,162,163,162,171,162,203,155],{"className":158},[159,160,161],"flex","flex-col","gap-2","\n      ",[149,164,170],{"className":165},[166,167,168,169],"text-xs","uppercase","tracking-wide","text-muted","read_file(path)",[149,172,182,183,182,191,182,195,182,199,162],{"className":173,"style":181},[174,175,176,177,159,160,178,179,166,180],"border","border-default","rounded-md","p-2","gap-1","font-mono","text-default","background:color-mix(in oklab, currentColor 10%, transparent);","\n        ",[149,184,190],{"style":185,"className":186},"background:color-mix(in oklab, currentColor 22%, transparent);",[187,188,189],"rounded","px-2","py-1","lines 1–100",[149,192,194],{"style":185,"className":193},[187,188,189],"lines 101–200",[149,196,198],{"style":185,"className":197},[187,188,189],"lines 201–300",[149,200,202],{"style":185,"className":201},[187,188,189],"lines 301–400  ← overflows budget",[149,204,206],{"className":205},[166,169],"400 lines dumped, no navigation.",[149,208,162,210,162,215,162,235,155],{"className":209},[159,160,161],[149,211,214],{"className":212},[166,167,168,213],"text-primary","read_file_viewport(path, offset)",[149,216,182,221,182,226,182,231,162],{"className":217},[218,219,220,177,159,160,178,179,166,213],"border-2","border-primary","rounded-lg",[149,222,225],{"className":223},[187,188,189,224],"font-semibold","lines 100–199 (viewport)",[149,227,230],{"className":228},[166,169,229,188],"font-normal","scroll → offset=200",[149,232,234],{"className":233},[166,169,229,188],"scroll → offset=300",[149,236,238],{"className":237},[166,169],"100-line window, agent navigates.",[240,241,246],"figcaption",{"className":242},[166,169,243,244,245],"mt-3","text-center","italic","Viewport reads keep the context budget bounded; the agent scrolls on demand.",[248,249],"hr",{},[251,252,254],"h2",{"id":253},"_111-four-principles-of-aci-design","11.1 Four Principles of ACI Design",[112,256,257],{},"These are the SWE-agent findings, lightly reframed for our purposes.",[112,259,260,264,265,268],{},[261,262,263],"strong",{},"Viewport, not dump."," A model reading a 2000-line file through a single tool call processes those 2000 lines with no structural affordances — it can't scroll, it can't search visually, it can't hold a mental map of where it is. Better: a tool that returns a window (50–100 lines) with explicit position indicators and a ",[129,266,267],{},"scroll"," command to move.",[112,270,271,274],{},[261,272,273],{},"Targeted edit, not rewrite."," A model that wants to change line 47 of a 2000-line file shouldn't have to return all 2000 lines. It should return the change. Targeted edits also make the intent auditable — the diff is minimal, the review is easy, the revert is trivial.",[112,276,277,280,281,284],{},[261,278,279],{},"Explicit envelopes."," Every tool result needs a machine-readable frame: what was returned, what was truncated, what the next step would be. ",[129,282,283],{},"[file: \u002Fetc\u002Fpasswd; lines 1-100 of 423; call again with offset=100 for more]"," is cheap to write and saves the model from having to guess.",[112,286,287,290,291,294],{},[261,288,289],{},"Error messages as instructions."," \"File not found\" is information. \"File not found: \u002Fetc\u002Fpasswd. Did you mean \u002Fetc\u002Fpasswd.bak (found by fuzzy search)? Or, use ",[129,292,293],{},"list_files('\u002Fetc')"," to see available files\" is instruction. The model does better with instruction.",[112,296,297],{},"The rest of the chapter applies these to our file tools.",[248,299],{},[251,301,303],{"id":302},"_112-the-viewport-file-reader","11.2 The Viewport File Reader",[305,306,311],"pre",{"className":307,"code":308,"language":309,"meta":310,"style":310},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Ftools\u002Ffiles.py\nfrom __future__ import annotations\n\nfrom pathlib import Path\n\nfrom .base import Tool\nfrom .decorator import tool\n\n\nVIEWPORT_DEFAULT = 100\nVIEWPORT_MAX = 500\n\n\n@tool(side_effects={\"read\"})\ndef read_file_viewport(path: str, offset: int = 0, limit: int = VIEWPORT_DEFAULT) -> str:\n    \"\"\"Read a slice of a text file, like `less` or `head -n ... | tail -n ...`.\n\n    path: filesystem path.\n    offset: zero-based line number to start reading from. Default 0.\n    limit: max lines to return. Default 100, max 500.\n\n    Returns a rendered viewport with line numbers. The last line of the\n    output describes what's visible and what's NOT, so you can call this\n    tool again with a different offset to keep reading.\n\n    Side effects: reads the filesystem.\n\n    Use this in preference to reading whole files. For files \u003C50 lines,\n    the whole file fits in one call.\n    \"\"\"\n    limit = min(max(1, limit), VIEWPORT_MAX)\n    p = Path(path)\n    if not p.exists():\n        raise FileNotFoundError(f\"file does not exist: {path}\")\n    if not p.is_file():\n        raise IsADirectoryError(f\"not a regular file: {path}\")\n\n    lines = p.read_text(encoding=\"utf-8\", errors=\"replace\").splitlines()\n    total = len(lines)\n    start = max(0, offset)\n    end = min(total, start + limit)\n    visible = lines[start:end]\n\n    width = len(str(total))\n    numbered = [f\"{i + 1:>{width}}  {line}\" for i, line in enumerate(visible, start=start)]\n    body = \"\\n\".join(numbered)\n    footer = (f\"\\n[file {path}; lines {start + 1}-{end} of {total}\"\n              + (f\"; MORE below — call with offset={end}\" if end \u003C total else \"; end of file\")\n              + (f\"; MORE above — call with offset=0\" if start > 0 else \"\")\n              + \"]\")\n    return body + footer\n","python","",[129,312,313,322,340,347,361,366,383,398,403,408,422,433,438,443,480,544,555,560,566,572,578,583,589,595,601,606,612,617,623,629,635,671,688,709,737,753,778,783,835,853,875,902,927,932,954,1033,1061,1121,1166,1195,1209],{"__ignoreMap":310},[314,315,318],"span",{"class":316,"line":317},"line",1,[314,319,321],{"class":320},"sutJx","# src\u002Fharness\u002Ftools\u002Ffiles.py\n",[314,323,325,329,333,336],{"class":316,"line":324},2,[314,326,328],{"class":327},"sVHd0","from",[314,330,332],{"class":331},"s_hVV"," __future__",[314,334,335],{"class":327}," import",[314,337,339],{"class":338},"su5hD"," annotations\n",[314,341,343],{"class":316,"line":342},3,[314,344,346],{"emptyLinePlaceholder":345},true,"\n",[314,348,350,352,355,358],{"class":316,"line":349},4,[314,351,328],{"class":327},[314,353,354],{"class":338}," pathlib ",[314,356,357],{"class":327},"import",[314,359,360],{"class":338}," Path\n",[314,362,364],{"class":316,"line":363},5,[314,365,346],{"emptyLinePlaceholder":345},[314,367,369,371,375,378,380],{"class":316,"line":368},6,[314,370,328],{"class":327},[314,372,374],{"class":373},"sP7_E"," .",[314,376,377],{"class":338},"base ",[314,379,357],{"class":327},[314,381,382],{"class":338}," Tool\n",[314,384,386,388,390,393,395],{"class":316,"line":385},7,[314,387,328],{"class":327},[314,389,374],{"class":373},[314,391,392],{"class":338},"decorator ",[314,394,357],{"class":327},[314,396,397],{"class":338}," tool\n",[314,399,401],{"class":316,"line":400},8,[314,402,346],{"emptyLinePlaceholder":345},[314,404,406],{"class":316,"line":405},9,[314,407,346],{"emptyLinePlaceholder":345},[314,409,411,414,418],{"class":316,"line":410},10,[314,412,413],{"class":331},"VIEWPORT_DEFAULT",[314,415,417],{"class":416},"smGrS"," =",[314,419,421],{"class":420},"srdBf"," 100\n",[314,423,425,428,430],{"class":316,"line":424},11,[314,426,427],{"class":331},"VIEWPORT_MAX",[314,429,417],{"class":416},[314,431,432],{"class":420}," 500\n",[314,434,436],{"class":316,"line":435},12,[314,437,346],{"emptyLinePlaceholder":345},[314,439,441],{"class":316,"line":440},13,[314,442,346],{"emptyLinePlaceholder":345},[314,444,446,450,454,457,461,464,467,471,475,477],{"class":316,"line":445},14,[314,447,449],{"class":448},"stp6e","@",[314,451,453],{"class":452},"sGLFI","tool",[314,455,456],{"class":373},"(",[314,458,460],{"class":459},"s99_P","side_effects",[314,462,463],{"class":416},"=",[314,465,466],{"class":373},"{",[314,468,470],{"class":469},"sjJ54","\"",[314,472,474],{"class":473},"s_sjI","read",[314,476,470],{"class":469},[314,478,479],{"class":373},"})\n",[314,481,483,487,490,492,496,499,503,506,509,511,514,516,519,521,524,526,528,530,533,536,539,541],{"class":316,"line":482},15,[314,484,486],{"class":485},"sbsja","def",[314,488,489],{"class":452}," read_file_viewport",[314,491,456],{"class":373},[314,493,495],{"class":494},"sFwrP","path",[314,497,498],{"class":373},":",[314,500,502],{"class":501},"sZMiF"," str",[314,504,505],{"class":373},",",[314,507,508],{"class":494}," offset",[314,510,498],{"class":373},[314,512,513],{"class":501}," int",[314,515,417],{"class":416},[314,517,518],{"class":420}," 0",[314,520,505],{"class":373},[314,522,523],{"class":494}," limit",[314,525,498],{"class":373},[314,527,513],{"class":501},[314,529,417],{"class":416},[314,531,532],{"class":331}," VIEWPORT_DEFAULT",[314,534,535],{"class":373},")",[314,537,538],{"class":373}," ->",[314,540,502],{"class":501},[314,542,543],{"class":373},":\n",[314,545,547,551],{"class":316,"line":546},16,[314,548,550],{"class":549},"s2W-s","    \"\"\"",[314,552,554],{"class":553},"sithA","Read a slice of a text file, like `less` or `head -n ... | tail -n ...`.\n",[314,556,558],{"class":316,"line":557},17,[314,559,346],{"emptyLinePlaceholder":345},[314,561,563],{"class":316,"line":562},18,[314,564,565],{"class":553},"    path: filesystem path.\n",[314,567,569],{"class":316,"line":568},19,[314,570,571],{"class":553},"    offset: zero-based line number to start reading from. Default 0.\n",[314,573,575],{"class":316,"line":574},20,[314,576,577],{"class":553},"    limit: max lines to return. Default 100, max 500.\n",[314,579,581],{"class":316,"line":580},21,[314,582,346],{"emptyLinePlaceholder":345},[314,584,586],{"class":316,"line":585},22,[314,587,588],{"class":553},"    Returns a rendered viewport with line numbers. The last line of the\n",[314,590,592],{"class":316,"line":591},23,[314,593,594],{"class":553},"    output describes what's visible and what's NOT, so you can call this\n",[314,596,598],{"class":316,"line":597},24,[314,599,600],{"class":553},"    tool again with a different offset to keep reading.\n",[314,602,604],{"class":316,"line":603},25,[314,605,346],{"emptyLinePlaceholder":345},[314,607,609],{"class":316,"line":608},26,[314,610,611],{"class":553},"    Side effects: reads the filesystem.\n",[314,613,615],{"class":316,"line":614},27,[314,616,346],{"emptyLinePlaceholder":345},[314,618,620],{"class":316,"line":619},28,[314,621,622],{"class":553},"    Use this in preference to reading whole files. For files \u003C50 lines,\n",[314,624,626],{"class":316,"line":625},29,[314,627,628],{"class":553},"    the whole file fits in one call.\n",[314,630,632],{"class":316,"line":631},30,[314,633,634],{"class":549},"    \"\"\"\n",[314,636,638,641,643,647,649,652,654,657,659,662,665,668],{"class":316,"line":637},31,[314,639,640],{"class":338},"    limit ",[314,642,463],{"class":416},[314,644,646],{"class":645},"sptTA"," min",[314,648,456],{"class":373},[314,650,651],{"class":645},"max",[314,653,456],{"class":373},[314,655,656],{"class":420},"1",[314,658,505],{"class":373},[314,660,523],{"class":661},"slqww",[314,663,664],{"class":373},"),",[314,666,667],{"class":645}," VIEWPORT_MAX",[314,669,670],{"class":373},")\n",[314,672,674,677,679,682,684,686],{"class":316,"line":673},32,[314,675,676],{"class":338},"    p ",[314,678,463],{"class":416},[314,680,681],{"class":661}," Path",[314,683,456],{"class":373},[314,685,495],{"class":661},[314,687,670],{"class":373},[314,689,691,694,697,700,703,706],{"class":316,"line":690},33,[314,692,693],{"class":327},"    if",[314,695,696],{"class":416}," not",[314,698,699],{"class":338}," p",[314,701,702],{"class":373},".",[314,704,705],{"class":661},"exists",[314,707,708],{"class":373},"():\n",[314,710,712,715,718,720,723,726,728,730,733,735],{"class":316,"line":711},34,[314,713,714],{"class":327},"        raise",[314,716,717],{"class":501}," FileNotFoundError",[314,719,456],{"class":373},[314,721,722],{"class":485},"f",[314,724,725],{"class":473},"\"file does not exist: ",[314,727,466],{"class":420},[314,729,495],{"class":661},[314,731,732],{"class":420},"}",[314,734,470],{"class":473},[314,736,670],{"class":373},[314,738,740,742,744,746,748,751],{"class":316,"line":739},35,[314,741,693],{"class":327},[314,743,696],{"class":416},[314,745,699],{"class":338},[314,747,702],{"class":373},[314,749,750],{"class":661},"is_file",[314,752,708],{"class":373},[314,754,756,758,761,763,765,768,770,772,774,776],{"class":316,"line":755},36,[314,757,714],{"class":327},[314,759,760],{"class":501}," IsADirectoryError",[314,762,456],{"class":373},[314,764,722],{"class":485},[314,766,767],{"class":473},"\"not a regular file: ",[314,769,466],{"class":420},[314,771,495],{"class":661},[314,773,732],{"class":420},[314,775,470],{"class":473},[314,777,670],{"class":373},[314,779,781],{"class":316,"line":780},37,[314,782,346],{"emptyLinePlaceholder":345},[314,784,786,789,791,793,795,798,800,803,805,807,810,812,814,817,819,821,824,826,829,832],{"class":316,"line":785},38,[314,787,788],{"class":338},"    lines ",[314,790,463],{"class":416},[314,792,699],{"class":338},[314,794,702],{"class":373},[314,796,797],{"class":661},"read_text",[314,799,456],{"class":373},[314,801,802],{"class":459},"encoding",[314,804,463],{"class":416},[314,806,470],{"class":469},[314,808,809],{"class":473},"utf-8",[314,811,470],{"class":469},[314,813,505],{"class":373},[314,815,816],{"class":459}," errors",[314,818,463],{"class":416},[314,820,470],{"class":469},[314,822,823],{"class":473},"replace",[314,825,470],{"class":469},[314,827,828],{"class":373},").",[314,830,831],{"class":661},"splitlines",[314,833,834],{"class":373},"()\n",[314,836,838,841,843,846,848,851],{"class":316,"line":837},39,[314,839,840],{"class":338},"    total ",[314,842,463],{"class":416},[314,844,845],{"class":645}," len",[314,847,456],{"class":373},[314,849,850],{"class":661},"lines",[314,852,670],{"class":373},[314,854,856,859,861,864,866,869,871,873],{"class":316,"line":855},40,[314,857,858],{"class":338},"    start ",[314,860,463],{"class":416},[314,862,863],{"class":645}," max",[314,865,456],{"class":373},[314,867,868],{"class":420},"0",[314,870,505],{"class":373},[314,872,508],{"class":661},[314,874,670],{"class":373},[314,876,878,881,883,885,887,890,892,895,898,900],{"class":316,"line":877},41,[314,879,880],{"class":338},"    end ",[314,882,463],{"class":416},[314,884,646],{"class":645},[314,886,456],{"class":373},[314,888,889],{"class":661},"total",[314,891,505],{"class":373},[314,893,894],{"class":661}," start ",[314,896,897],{"class":416},"+",[314,899,523],{"class":661},[314,901,670],{"class":373},[314,903,905,908,910,913,916,919,921,924],{"class":316,"line":904},42,[314,906,907],{"class":338},"    visible ",[314,909,463],{"class":416},[314,911,912],{"class":338}," lines",[314,914,915],{"class":373},"[",[314,917,918],{"class":338},"start",[314,920,498],{"class":373},[314,922,923],{"class":338},"end",[314,925,926],{"class":373},"]\n",[314,928,930],{"class":316,"line":929},43,[314,931,346],{"emptyLinePlaceholder":345},[314,933,935,938,940,942,944,947,949,951],{"class":316,"line":934},44,[314,936,937],{"class":338},"    width ",[314,939,463],{"class":416},[314,941,845],{"class":645},[314,943,456],{"class":373},[314,945,946],{"class":501},"str",[314,948,456],{"class":373},[314,950,889],{"class":661},[314,952,953],{"class":373},"))\n",[314,955,957,960,962,965,967,969,971,974,976,979,982,984,987,990,993,995,997,999,1002,1005,1007,1010,1013,1016,1018,1021,1023,1026,1028,1030],{"class":316,"line":956},45,[314,958,959],{"class":338},"    numbered ",[314,961,463],{"class":416},[314,963,964],{"class":373}," [",[314,966,722],{"class":485},[314,968,470],{"class":473},[314,970,466],{"class":420},[314,972,973],{"class":338},"i ",[314,975,897],{"class":416},[314,977,978],{"class":420}," 1",[314,980,981],{"class":485},":>",[314,983,466],{"class":420},[314,985,986],{"class":338},"width",[314,988,989],{"class":420},"}}",[314,991,992],{"class":420},"  {",[314,994,316],{"class":338},[314,996,732],{"class":420},[314,998,470],{"class":473},[314,1000,1001],{"class":327}," for",[314,1003,1004],{"class":338}," i",[314,1006,505],{"class":373},[314,1008,1009],{"class":338}," line ",[314,1011,1012],{"class":327},"in",[314,1014,1015],{"class":645}," enumerate",[314,1017,456],{"class":373},[314,1019,1020],{"class":661},"visible",[314,1022,505],{"class":373},[314,1024,1025],{"class":459}," start",[314,1027,463],{"class":416},[314,1029,918],{"class":661},[314,1031,1032],{"class":373},")]\n",[314,1034,1036,1039,1041,1044,1047,1049,1051,1054,1056,1059],{"class":316,"line":1035},46,[314,1037,1038],{"class":338},"    body ",[314,1040,463],{"class":416},[314,1042,1043],{"class":469}," \"",[314,1045,1046],{"class":331},"\\n",[314,1048,470],{"class":469},[314,1050,702],{"class":373},[314,1052,1053],{"class":661},"join",[314,1055,456],{"class":373},[314,1057,1058],{"class":661},"numbered",[314,1060,670],{"class":373},[314,1062,1064,1067,1069,1072,1074,1076,1078,1081,1083,1085,1087,1090,1092,1095,1097,1100,1103,1105,1107,1109,1112,1114,1116,1118],{"class":316,"line":1063},47,[314,1065,1066],{"class":338},"    footer ",[314,1068,463],{"class":416},[314,1070,1071],{"class":373}," (",[314,1073,722],{"class":485},[314,1075,470],{"class":473},[314,1077,1046],{"class":331},[314,1079,1080],{"class":473},"[file ",[314,1082,466],{"class":420},[314,1084,495],{"class":338},[314,1086,732],{"class":420},[314,1088,1089],{"class":473},"; lines ",[314,1091,466],{"class":420},[314,1093,1094],{"class":338},"start ",[314,1096,897],{"class":416},[314,1098,1099],{"class":420}," 1}",[314,1101,1102],{"class":473},"-",[314,1104,466],{"class":420},[314,1106,923],{"class":338},[314,1108,732],{"class":420},[314,1110,1111],{"class":473}," of ",[314,1113,466],{"class":420},[314,1115,889],{"class":338},[314,1117,732],{"class":420},[314,1119,1120],{"class":473},"\"\n",[314,1122,1124,1127,1129,1131,1134,1136,1138,1140,1142,1145,1148,1151,1154,1157,1159,1162,1164],{"class":316,"line":1123},48,[314,1125,1126],{"class":416},"              +",[314,1128,1071],{"class":373},[314,1130,722],{"class":485},[314,1132,1133],{"class":473},"\"; MORE below — call with offset=",[314,1135,466],{"class":420},[314,1137,923],{"class":338},[314,1139,732],{"class":420},[314,1141,470],{"class":473},[314,1143,1144],{"class":327}," if",[314,1146,1147],{"class":338}," end ",[314,1149,1150],{"class":416},"\u003C",[314,1152,1153],{"class":338}," total ",[314,1155,1156],{"class":327},"else",[314,1158,1043],{"class":469},[314,1160,1161],{"class":473},"; end of file",[314,1163,470],{"class":469},[314,1165,670],{"class":373},[314,1167,1169,1171,1173,1175,1178,1180,1182,1185,1187,1190,1193],{"class":316,"line":1168},49,[314,1170,1126],{"class":416},[314,1172,1071],{"class":373},[314,1174,722],{"class":485},[314,1176,1177],{"class":473},"\"; MORE above — call with offset=0\"",[314,1179,1144],{"class":327},[314,1181,894],{"class":338},[314,1183,1184],{"class":416},">",[314,1186,518],{"class":420},[314,1188,1189],{"class":327}," else",[314,1191,1192],{"class":469}," \"\"",[314,1194,670],{"class":373},[314,1196,1198,1200,1202,1205,1207],{"class":316,"line":1197},50,[314,1199,1126],{"class":416},[314,1201,1043],{"class":469},[314,1203,1204],{"class":473},"]",[314,1206,470],{"class":469},[314,1208,670],{"class":373},[314,1210,1212,1215,1218,1220],{"class":316,"line":1211},51,[314,1213,1214],{"class":327},"    return",[314,1216,1217],{"class":338}," body ",[314,1219,897],{"class":416},[314,1221,1222],{"class":338}," footer\n",[112,1224,1225],{},"Four things earned by the design.",[112,1227,1228,1231],{},[261,1229,1230],{},"Line numbers in the rendered output."," The model reads line numbers alongside content and can refer back to them in subsequent edits. The line-range edit tool (next section) uses these directly.",[112,1233,1234,1237,1238,1241],{},[261,1235,1236],{},"The footer tells the model what's missing."," ",[129,1239,1240],{},"lines 1-100 of 423; MORE below — call with offset=100",". The model doesn't have to infer that there's more; it's told, with the exact call that would fetch it. This maps directly to the \"explicit envelopes\" principle.",[112,1243,1244,1247,1248,1251,1252,1255],{},[261,1245,1246],{},"The offset is zero-based, display is one-based."," Displaying one-based is natural for humans and models (editors use one-based); the offset parameter is zero-based because it's a programmatic slice. We make this difference visible by labeling ",[129,1249,1250],{},"lines X-Y"," in one-based in the footer, while the ",[129,1253,1254],{},"offset"," parameter takes zero-based values. This is a small inconsistency, but it matches what editors and compilers do and is easy to explain in the docstring.",[112,1257,1258,1237,1261,1264,1265,1268,1269,1272],{},[261,1259,1260],{},"Error messages are specific.",[129,1262,1263],{},"file does not exist: ..."," and ",[129,1266,1267],{},"not a regular file: ..."," give the model enough to fix the call. A more aggressive version (which we'll add in Chapter 14's sandboxing) would also check allowed paths and say ",[115,1270,1271],{},"why"," a path is rejected.",[248,1274],{},[251,1276,1278],{"id":1277},"_113-the-line-range-editor","11.3 The Line-Range Editor",[305,1280,1282],{"className":307,"code":1281,"language":309,"meta":310,"style":310},"# src\u002Fharness\u002Ftools\u002Ffiles.py (continued)\n\n@tool(side_effects={\"write\"})\ndef edit_lines(\n    path: str,\n    start_line: int,\n    end_line: int,\n    replacement: str,\n) -> str:\n    \"\"\"Replace a line range in a file with new content.\n\n    path: filesystem path (file must exist).\n    start_line: one-based starting line (inclusive).\n    end_line: one-based ending line (inclusive).\n    replacement: text to insert in place of the removed lines. Empty string\n                 deletes the range without replacement. Include trailing\n                 newlines if you want blank lines.\n\n    Returns a confirmation with the diff summary and the lines around the\n    edit (for verification).\n\n    Side effects: writes the file. Preserves content outside the range.\n\n    To INSERT new lines at position N without removing: use start_line=N,\n    end_line=N-1 and replacement=your_new_content.\n    To APPEND: use start_line=last+1, end_line=last.\n    \"\"\"\n    p = Path(path)\n    if not p.exists():\n        raise FileNotFoundError(f\"file does not exist: {path}\")\n\n    original = p.read_text(encoding=\"utf-8\")\n    lines = original.splitlines(keepends=True)\n    total = len(lines)\n\n    if start_line \u003C 1 or start_line > total + 1:\n        raise ValueError(f\"start_line {start_line} out of range (1..{total + 1})\")\n    if end_line \u003C start_line - 1 or end_line > total:\n        raise ValueError(f\"end_line {end_line} out of range ({start_line - 1}..{total})\")\n\n    # normalize: start is zero-based slice, end is zero-based exclusive\n    s = start_line - 1\n    e = end_line  # slice end is exclusive of end_line, so this works for deletes too\n\n    replacement_lines = replacement.splitlines(keepends=True)\n    if replacement and not replacement.endswith(\"\\n\"):\n        # make sure we don't glue onto the next line without a newline\n        if e \u003C total:\n            replacement_lines[-1] = replacement_lines[-1] + \"\\n\"\n\n    new_lines = lines[:s] + replacement_lines + lines[e:]\n    p.write_text(\"\".join(new_lines), encoding=\"utf-8\")\n\n    removed = end_line - start_line + 1 if end_line >= start_line else 0\n    added = len(replacement_lines)\n\n    # render context around the edit\n    context_start = max(0, s - 2)\n    context_end = min(len(new_lines), s + len(replacement_lines) + 2)\n    preview = \"\".join(\n        f\"{i + 1:>5}  {new_lines[i]}\" for i in range(context_start, context_end)\n    )\n    return (f\"edited {path}: removed {removed} lines, \"\n            f\"added {added} lines at {start_line}..{end_line}\\n\"\n            f\"context:\\n{preview}\")\n",[129,1283,1284,1289,1293,1316,1326,1338,1349,1360,1371,1381,1388,1392,1397,1402,1407,1412,1417,1422,1426,1431,1436,1440,1445,1449,1454,1459,1464,1468,1482,1496,1518,1522,1549,1575,1589,1593,1619,1657,1683,1728,1732,1737,1751,1764,1768,1792,1822,1827,1841,1876,1880,1914,1954,1959,1991,2008,2013,2019,2045,2084,2100,2158,2164,2195,2232],{"__ignoreMap":310},[314,1285,1286],{"class":316,"line":317},[314,1287,1288],{"class":320},"# src\u002Fharness\u002Ftools\u002Ffiles.py (continued)\n",[314,1290,1291],{"class":316,"line":324},[314,1292,346],{"emptyLinePlaceholder":345},[314,1294,1295,1297,1299,1301,1303,1305,1307,1309,1312,1314],{"class":316,"line":342},[314,1296,449],{"class":448},[314,1298,453],{"class":452},[314,1300,456],{"class":373},[314,1302,460],{"class":459},[314,1304,463],{"class":416},[314,1306,466],{"class":373},[314,1308,470],{"class":469},[314,1310,1311],{"class":473},"write",[314,1313,470],{"class":469},[314,1315,479],{"class":373},[314,1317,1318,1320,1323],{"class":316,"line":349},[314,1319,486],{"class":485},[314,1321,1322],{"class":452}," edit_lines",[314,1324,1325],{"class":373},"(\n",[314,1327,1328,1331,1333,1335],{"class":316,"line":363},[314,1329,1330],{"class":494},"    path",[314,1332,498],{"class":373},[314,1334,502],{"class":501},[314,1336,1337],{"class":373},",\n",[314,1339,1340,1343,1345,1347],{"class":316,"line":368},[314,1341,1342],{"class":494},"    start_line",[314,1344,498],{"class":373},[314,1346,513],{"class":501},[314,1348,1337],{"class":373},[314,1350,1351,1354,1356,1358],{"class":316,"line":385},[314,1352,1353],{"class":494},"    end_line",[314,1355,498],{"class":373},[314,1357,513],{"class":501},[314,1359,1337],{"class":373},[314,1361,1362,1365,1367,1369],{"class":316,"line":400},[314,1363,1364],{"class":494},"    replacement",[314,1366,498],{"class":373},[314,1368,502],{"class":501},[314,1370,1337],{"class":373},[314,1372,1373,1375,1377,1379],{"class":316,"line":405},[314,1374,535],{"class":373},[314,1376,538],{"class":373},[314,1378,502],{"class":501},[314,1380,543],{"class":373},[314,1382,1383,1385],{"class":316,"line":410},[314,1384,550],{"class":549},[314,1386,1387],{"class":553},"Replace a line range in a file with new content.\n",[314,1389,1390],{"class":316,"line":424},[314,1391,346],{"emptyLinePlaceholder":345},[314,1393,1394],{"class":316,"line":435},[314,1395,1396],{"class":553},"    path: filesystem path (file must exist).\n",[314,1398,1399],{"class":316,"line":440},[314,1400,1401],{"class":553},"    start_line: one-based starting line (inclusive).\n",[314,1403,1404],{"class":316,"line":445},[314,1405,1406],{"class":553},"    end_line: one-based ending line (inclusive).\n",[314,1408,1409],{"class":316,"line":482},[314,1410,1411],{"class":553},"    replacement: text to insert in place of the removed lines. Empty string\n",[314,1413,1414],{"class":316,"line":546},[314,1415,1416],{"class":553},"                 deletes the range without replacement. Include trailing\n",[314,1418,1419],{"class":316,"line":557},[314,1420,1421],{"class":553},"                 newlines if you want blank lines.\n",[314,1423,1424],{"class":316,"line":562},[314,1425,346],{"emptyLinePlaceholder":345},[314,1427,1428],{"class":316,"line":568},[314,1429,1430],{"class":553},"    Returns a confirmation with the diff summary and the lines around the\n",[314,1432,1433],{"class":316,"line":574},[314,1434,1435],{"class":553},"    edit (for verification).\n",[314,1437,1438],{"class":316,"line":580},[314,1439,346],{"emptyLinePlaceholder":345},[314,1441,1442],{"class":316,"line":585},[314,1443,1444],{"class":553},"    Side effects: writes the file. Preserves content outside the range.\n",[314,1446,1447],{"class":316,"line":591},[314,1448,346],{"emptyLinePlaceholder":345},[314,1450,1451],{"class":316,"line":597},[314,1452,1453],{"class":553},"    To INSERT new lines at position N without removing: use start_line=N,\n",[314,1455,1456],{"class":316,"line":603},[314,1457,1458],{"class":553},"    end_line=N-1 and replacement=your_new_content.\n",[314,1460,1461],{"class":316,"line":608},[314,1462,1463],{"class":553},"    To APPEND: use start_line=last+1, end_line=last.\n",[314,1465,1466],{"class":316,"line":614},[314,1467,634],{"class":549},[314,1469,1470,1472,1474,1476,1478,1480],{"class":316,"line":619},[314,1471,676],{"class":338},[314,1473,463],{"class":416},[314,1475,681],{"class":661},[314,1477,456],{"class":373},[314,1479,495],{"class":661},[314,1481,670],{"class":373},[314,1483,1484,1486,1488,1490,1492,1494],{"class":316,"line":625},[314,1485,693],{"class":327},[314,1487,696],{"class":416},[314,1489,699],{"class":338},[314,1491,702],{"class":373},[314,1493,705],{"class":661},[314,1495,708],{"class":373},[314,1497,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516],{"class":316,"line":631},[314,1499,714],{"class":327},[314,1501,717],{"class":501},[314,1503,456],{"class":373},[314,1505,722],{"class":485},[314,1507,725],{"class":473},[314,1509,466],{"class":420},[314,1511,495],{"class":661},[314,1513,732],{"class":420},[314,1515,470],{"class":473},[314,1517,670],{"class":373},[314,1519,1520],{"class":316,"line":637},[314,1521,346],{"emptyLinePlaceholder":345},[314,1523,1524,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547],{"class":316,"line":673},[314,1525,1526],{"class":338},"    original ",[314,1528,463],{"class":416},[314,1530,699],{"class":338},[314,1532,702],{"class":373},[314,1534,797],{"class":661},[314,1536,456],{"class":373},[314,1538,802],{"class":459},[314,1540,463],{"class":416},[314,1542,470],{"class":469},[314,1544,809],{"class":473},[314,1546,470],{"class":469},[314,1548,670],{"class":373},[314,1550,1551,1553,1555,1558,1560,1562,1564,1567,1569,1573],{"class":316,"line":690},[314,1552,788],{"class":338},[314,1554,463],{"class":416},[314,1556,1557],{"class":338}," original",[314,1559,702],{"class":373},[314,1561,831],{"class":661},[314,1563,456],{"class":373},[314,1565,1566],{"class":459},"keepends",[314,1568,463],{"class":416},[314,1570,1572],{"class":1571},"s39Yj","True",[314,1574,670],{"class":373},[314,1576,1577,1579,1581,1583,1585,1587],{"class":316,"line":711},[314,1578,840],{"class":338},[314,1580,463],{"class":416},[314,1582,845],{"class":645},[314,1584,456],{"class":373},[314,1586,850],{"class":661},[314,1588,670],{"class":373},[314,1590,1591],{"class":316,"line":739},[314,1592,346],{"emptyLinePlaceholder":345},[314,1594,1595,1597,1600,1602,1604,1607,1609,1611,1613,1615,1617],{"class":316,"line":755},[314,1596,693],{"class":327},[314,1598,1599],{"class":338}," start_line ",[314,1601,1150],{"class":416},[314,1603,978],{"class":420},[314,1605,1606],{"class":416}," or",[314,1608,1599],{"class":338},[314,1610,1184],{"class":416},[314,1612,1153],{"class":338},[314,1614,897],{"class":416},[314,1616,978],{"class":420},[314,1618,543],{"class":373},[314,1620,1621,1623,1626,1628,1630,1633,1635,1638,1640,1643,1645,1648,1650,1652,1655],{"class":316,"line":780},[314,1622,714],{"class":327},[314,1624,1625],{"class":501}," ValueError",[314,1627,456],{"class":373},[314,1629,722],{"class":485},[314,1631,1632],{"class":473},"\"start_line ",[314,1634,466],{"class":420},[314,1636,1637],{"class":661},"start_line",[314,1639,732],{"class":420},[314,1641,1642],{"class":473}," out of range (1..",[314,1644,466],{"class":420},[314,1646,1647],{"class":661},"total ",[314,1649,897],{"class":416},[314,1651,1099],{"class":420},[314,1653,1654],{"class":473},")\"",[314,1656,670],{"class":373},[314,1658,1659,1661,1664,1666,1668,1670,1672,1674,1676,1678,1681],{"class":316,"line":785},[314,1660,693],{"class":327},[314,1662,1663],{"class":338}," end_line ",[314,1665,1150],{"class":416},[314,1667,1599],{"class":338},[314,1669,1102],{"class":416},[314,1671,978],{"class":420},[314,1673,1606],{"class":416},[314,1675,1663],{"class":338},[314,1677,1184],{"class":416},[314,1679,1680],{"class":338}," total",[314,1682,543],{"class":373},[314,1684,1685,1687,1689,1691,1693,1696,1698,1701,1703,1706,1708,1711,1713,1715,1718,1720,1722,1724,1726],{"class":316,"line":837},[314,1686,714],{"class":327},[314,1688,1625],{"class":501},[314,1690,456],{"class":373},[314,1692,722],{"class":485},[314,1694,1695],{"class":473},"\"end_line ",[314,1697,466],{"class":420},[314,1699,1700],{"class":661},"end_line",[314,1702,732],{"class":420},[314,1704,1705],{"class":473}," out of range (",[314,1707,466],{"class":420},[314,1709,1710],{"class":661},"start_line ",[314,1712,1102],{"class":416},[314,1714,1099],{"class":420},[314,1716,1717],{"class":473},"..",[314,1719,466],{"class":420},[314,1721,889],{"class":661},[314,1723,732],{"class":420},[314,1725,1654],{"class":473},[314,1727,670],{"class":373},[314,1729,1730],{"class":316,"line":855},[314,1731,346],{"emptyLinePlaceholder":345},[314,1733,1734],{"class":316,"line":877},[314,1735,1736],{"class":320},"    # normalize: start is zero-based slice, end is zero-based exclusive\n",[314,1738,1739,1742,1744,1746,1748],{"class":316,"line":904},[314,1740,1741],{"class":338},"    s ",[314,1743,463],{"class":416},[314,1745,1599],{"class":338},[314,1747,1102],{"class":416},[314,1749,1750],{"class":420}," 1\n",[314,1752,1753,1756,1758,1761],{"class":316,"line":929},[314,1754,1755],{"class":338},"    e ",[314,1757,463],{"class":416},[314,1759,1760],{"class":338}," end_line  ",[314,1762,1763],{"class":320},"# slice end is exclusive of end_line, so this works for deletes too\n",[314,1765,1766],{"class":316,"line":934},[314,1767,346],{"emptyLinePlaceholder":345},[314,1769,1770,1773,1775,1778,1780,1782,1784,1786,1788,1790],{"class":316,"line":956},[314,1771,1772],{"class":338},"    replacement_lines ",[314,1774,463],{"class":416},[314,1776,1777],{"class":338}," replacement",[314,1779,702],{"class":373},[314,1781,831],{"class":661},[314,1783,456],{"class":373},[314,1785,1566],{"class":459},[314,1787,463],{"class":416},[314,1789,1572],{"class":1571},[314,1791,670],{"class":373},[314,1793,1794,1796,1799,1802,1804,1806,1808,1811,1813,1815,1817,1819],{"class":316,"line":1035},[314,1795,693],{"class":327},[314,1797,1798],{"class":338}," replacement ",[314,1800,1801],{"class":416},"and",[314,1803,696],{"class":416},[314,1805,1777],{"class":338},[314,1807,702],{"class":373},[314,1809,1810],{"class":661},"endswith",[314,1812,456],{"class":373},[314,1814,470],{"class":469},[314,1816,1046],{"class":331},[314,1818,470],{"class":469},[314,1820,1821],{"class":373},"):\n",[314,1823,1824],{"class":316,"line":1063},[314,1825,1826],{"class":320},"        # make sure we don't glue onto the next line without a newline\n",[314,1828,1829,1832,1835,1837,1839],{"class":316,"line":1123},[314,1830,1831],{"class":327},"        if",[314,1833,1834],{"class":338}," e ",[314,1836,1150],{"class":416},[314,1838,1680],{"class":338},[314,1840,543],{"class":373},[314,1842,1843,1846,1848,1850,1852,1854,1856,1859,1861,1863,1865,1867,1870,1872,1874],{"class":316,"line":1168},[314,1844,1845],{"class":338},"            replacement_lines",[314,1847,915],{"class":373},[314,1849,1102],{"class":416},[314,1851,656],{"class":420},[314,1853,1204],{"class":373},[314,1855,417],{"class":416},[314,1857,1858],{"class":338}," replacement_lines",[314,1860,915],{"class":373},[314,1862,1102],{"class":416},[314,1864,656],{"class":420},[314,1866,1204],{"class":373},[314,1868,1869],{"class":416}," +",[314,1871,1043],{"class":469},[314,1873,1046],{"class":331},[314,1875,1120],{"class":469},[314,1877,1878],{"class":316,"line":1197},[314,1879,346],{"emptyLinePlaceholder":345},[314,1881,1882,1885,1887,1889,1892,1895,1897,1899,1902,1904,1906,1908,1911],{"class":316,"line":1211},[314,1883,1884],{"class":338},"    new_lines ",[314,1886,463],{"class":416},[314,1888,912],{"class":338},[314,1890,1891],{"class":373},"[:",[314,1893,1894],{"class":338},"s",[314,1896,1204],{"class":373},[314,1898,1869],{"class":416},[314,1900,1901],{"class":338}," replacement_lines ",[314,1903,897],{"class":416},[314,1905,912],{"class":338},[314,1907,915],{"class":373},[314,1909,1910],{"class":338},"e",[314,1912,1913],{"class":373},":]\n",[314,1915,1917,1920,1922,1925,1927,1930,1932,1934,1936,1939,1941,1944,1946,1948,1950,1952],{"class":316,"line":1916},52,[314,1918,1919],{"class":338},"    p",[314,1921,702],{"class":373},[314,1923,1924],{"class":661},"write_text",[314,1926,456],{"class":373},[314,1928,1929],{"class":469},"\"\"",[314,1931,702],{"class":373},[314,1933,1053],{"class":661},[314,1935,456],{"class":373},[314,1937,1938],{"class":661},"new_lines",[314,1940,664],{"class":373},[314,1942,1943],{"class":459}," encoding",[314,1945,463],{"class":416},[314,1947,470],{"class":469},[314,1949,809],{"class":473},[314,1951,470],{"class":469},[314,1953,670],{"class":373},[314,1955,1957],{"class":316,"line":1956},53,[314,1958,346],{"emptyLinePlaceholder":345},[314,1960,1962,1965,1967,1969,1971,1973,1975,1977,1979,1981,1984,1986,1988],{"class":316,"line":1961},54,[314,1963,1964],{"class":338},"    removed ",[314,1966,463],{"class":416},[314,1968,1663],{"class":338},[314,1970,1102],{"class":416},[314,1972,1599],{"class":338},[314,1974,897],{"class":416},[314,1976,978],{"class":420},[314,1978,1144],{"class":327},[314,1980,1663],{"class":338},[314,1982,1983],{"class":416},">=",[314,1985,1599],{"class":338},[314,1987,1156],{"class":327},[314,1989,1990],{"class":420}," 0\n",[314,1992,1994,1997,1999,2001,2003,2006],{"class":316,"line":1993},55,[314,1995,1996],{"class":338},"    added ",[314,1998,463],{"class":416},[314,2000,845],{"class":645},[314,2002,456],{"class":373},[314,2004,2005],{"class":661},"replacement_lines",[314,2007,670],{"class":373},[314,2009,2011],{"class":316,"line":2010},56,[314,2012,346],{"emptyLinePlaceholder":345},[314,2014,2016],{"class":316,"line":2015},57,[314,2017,2018],{"class":320},"    # render context around the edit\n",[314,2020,2022,2025,2027,2029,2031,2033,2035,2038,2040,2043],{"class":316,"line":2021},58,[314,2023,2024],{"class":338},"    context_start ",[314,2026,463],{"class":416},[314,2028,863],{"class":645},[314,2030,456],{"class":373},[314,2032,868],{"class":420},[314,2034,505],{"class":373},[314,2036,2037],{"class":661}," s ",[314,2039,1102],{"class":416},[314,2041,2042],{"class":420}," 2",[314,2044,670],{"class":373},[314,2046,2048,2051,2053,2055,2057,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082],{"class":316,"line":2047},59,[314,2049,2050],{"class":338},"    context_end ",[314,2052,463],{"class":416},[314,2054,646],{"class":645},[314,2056,456],{"class":373},[314,2058,2059],{"class":645},"len",[314,2061,456],{"class":373},[314,2063,1938],{"class":661},[314,2065,664],{"class":373},[314,2067,2037],{"class":661},[314,2069,897],{"class":416},[314,2071,845],{"class":645},[314,2073,456],{"class":373},[314,2075,2005],{"class":661},[314,2077,535],{"class":373},[314,2079,1869],{"class":416},[314,2081,2042],{"class":420},[314,2083,670],{"class":373},[314,2085,2087,2090,2092,2094,2096,2098],{"class":316,"line":2086},60,[314,2088,2089],{"class":338},"    preview ",[314,2091,463],{"class":416},[314,2093,1192],{"class":469},[314,2095,702],{"class":373},[314,2097,1053],{"class":661},[314,2099,1325],{"class":373},[314,2101,2103,2106,2108,2110,2112,2114,2116,2119,2121,2123,2125,2127,2130,2132,2134,2136,2138,2141,2143,2146,2148,2151,2153,2156],{"class":316,"line":2102},61,[314,2104,2105],{"class":485},"        f",[314,2107,470],{"class":473},[314,2109,466],{"class":420},[314,2111,973],{"class":661},[314,2113,897],{"class":416},[314,2115,978],{"class":420},[314,2117,2118],{"class":485},":>5",[314,2120,732],{"class":420},[314,2122,992],{"class":420},[314,2124,1938],{"class":661},[314,2126,915],{"class":373},[314,2128,2129],{"class":661},"i",[314,2131,1204],{"class":373},[314,2133,732],{"class":420},[314,2135,470],{"class":473},[314,2137,1001],{"class":327},[314,2139,2140],{"class":661}," i ",[314,2142,1012],{"class":327},[314,2144,2145],{"class":645}," range",[314,2147,456],{"class":373},[314,2149,2150],{"class":661},"context_start",[314,2152,505],{"class":373},[314,2154,2155],{"class":661}," context_end",[314,2157,670],{"class":373},[314,2159,2161],{"class":316,"line":2160},62,[314,2162,2163],{"class":373},"    )\n",[314,2165,2167,2169,2171,2173,2176,2178,2180,2182,2185,2187,2190,2192],{"class":316,"line":2166},63,[314,2168,1214],{"class":327},[314,2170,1071],{"class":373},[314,2172,722],{"class":485},[314,2174,2175],{"class":473},"\"edited ",[314,2177,466],{"class":420},[314,2179,495],{"class":338},[314,2181,732],{"class":420},[314,2183,2184],{"class":473},": removed ",[314,2186,466],{"class":420},[314,2188,2189],{"class":338},"removed",[314,2191,732],{"class":420},[314,2193,2194],{"class":473}," lines, \"\n",[314,2196,2198,2201,2204,2206,2209,2211,2214,2216,2218,2220,2222,2224,2226,2228,2230],{"class":316,"line":2197},64,[314,2199,2200],{"class":485},"            f",[314,2202,2203],{"class":473},"\"added ",[314,2205,466],{"class":420},[314,2207,2208],{"class":338},"added",[314,2210,732],{"class":420},[314,2212,2213],{"class":473}," lines at ",[314,2215,466],{"class":420},[314,2217,1637],{"class":338},[314,2219,732],{"class":420},[314,2221,1717],{"class":473},[314,2223,466],{"class":420},[314,2225,1700],{"class":338},[314,2227,732],{"class":420},[314,2229,1046],{"class":331},[314,2231,1120],{"class":473},[314,2233,2235,2237,2240,2242,2244,2247,2249,2251],{"class":316,"line":2234},65,[314,2236,2200],{"class":485},[314,2238,2239],{"class":473},"\"context:",[314,2241,1046],{"class":331},[314,2243,466],{"class":420},[314,2245,2246],{"class":338},"preview",[314,2248,732],{"class":420},[314,2250,470],{"class":473},[314,2252,670],{"class":373},[112,2254,2255],{},"The edit tool is more complicated than read because editing has more edge cases. We handle:",[2257,2258,2259,2266,2274,2283],"ul",{},[2260,2261,2262,2265],"li",{},[261,2263,2264],{},"Pure replacement."," Lines 5–10 become other content.",[2260,2267,2268,2271,2272,828],{},[261,2269,2270],{},"Pure delete."," Lines 5–10 removed (replacement = ",[129,2273,1929],{},[2260,2275,2276,1237,2279,2282],{},[261,2277,2278],{},"Insert.",[129,2280,2281],{},"start_line=5, end_line=4, replacement=\"new content\""," inserts before line 5 without removing anything.",[2260,2284,2285,1237,2288,2291],{},[261,2286,2287],{},"Append.",[129,2289,2290],{},"start_line=total+1, end_line=total, replacement=\"...\""," adds to the end.",[112,2293,2294],{},"The return value shows the context around the edit — a few lines before and after — so the model can verify. This is the SWE-agent trick of making tool outputs self-validating: the agent doesn't have to read the file back to confirm; the edit tool shows the result.",[112,2296,2297,2298,2301,2302,2305,2306,2309,2310,2313,2314,2317],{},"Two things worth highlighting. ",[261,2299,2300],{},"Line-ending preservation"," — we use ",[129,2303,2304],{},"splitlines(keepends=True)"," and add ",[129,2307,2308],{},"\"\\n\""," to replacement content if the next line expects one. This prevents the edit from silently mangling newlines, a common bug in naive diff-apply code. ",[261,2311,2312],{},"Bounds checks with explicit ranges"," — \"",[129,2315,2316],{},"start_line 500 out of range (1..423)","\" tells the model the specific valid range. A model that miscounts lines (which they do) gets enough signal to correct on the next turn.",[248,2319],{},[251,2321,2323],{"id":2322},"_114-replacing-the-old-tools","11.4 Replacing the Old Tools",[112,2325,2326,2327,1264,2329,2331,2332,2334],{},"The Chapter 4 ",[129,2328,131],{},[129,2330,135],{}," go into a deprecated path. We don't delete them — ",[129,2333,135],{}," is still useful for creating files that don't exist, and there are cases where rewriting a whole file is the right call. But the default tools shipped with the harness switch to viewport-and-edit:",[305,2336,2338],{"className":307,"code":2337,"language":309,"meta":310,"style":310},"# src\u002Fharness\u002Ftools\u002Fstd.py (updated)\nfrom .files import read_file_viewport, edit_lines\n\n# calc and bash unchanged\n# read_file stays available but is no longer in the \"standard\" set\n# write_file stays available but is no longer in the \"standard\" set\n\nSTANDARD_TOOLS = [calc, bash, read_file_viewport, edit_lines]\n",[129,2339,2340,2345,2363,2367,2372,2377,2382,2386],{"__ignoreMap":310},[314,2341,2342],{"class":316,"line":317},[314,2343,2344],{"class":320},"# src\u002Fharness\u002Ftools\u002Fstd.py (updated)\n",[314,2346,2347,2349,2351,2354,2356,2358,2360],{"class":316,"line":324},[314,2348,328],{"class":327},[314,2350,374],{"class":373},[314,2352,2353],{"class":338},"files ",[314,2355,357],{"class":327},[314,2357,489],{"class":338},[314,2359,505],{"class":373},[314,2361,2362],{"class":338}," edit_lines\n",[314,2364,2365],{"class":316,"line":342},[314,2366,346],{"emptyLinePlaceholder":345},[314,2368,2369],{"class":316,"line":349},[314,2370,2371],{"class":320},"# calc and bash unchanged\n",[314,2373,2374],{"class":316,"line":363},[314,2375,2376],{"class":320},"# read_file stays available but is no longer in the \"standard\" set\n",[314,2378,2379],{"class":316,"line":368},[314,2380,2381],{"class":320},"# write_file stays available but is no longer in the \"standard\" set\n",[314,2383,2384],{"class":316,"line":385},[314,2385,346],{"emptyLinePlaceholder":345},[314,2387,2388,2391,2393,2395,2398,2400,2403,2405,2407,2409,2411],{"class":316,"line":400},[314,2389,2390],{"class":331},"STANDARD_TOOLS",[314,2392,417],{"class":416},[314,2394,964],{"class":373},[314,2396,2397],{"class":338},"calc",[314,2399,505],{"class":373},[314,2401,2402],{"class":338}," bash",[314,2404,505],{"class":373},[314,2406,489],{"class":338},[314,2408,505],{"class":373},[314,2410,1322],{"class":338},[314,2412,926],{"class":373},[112,2414,2415],{},"Swap these into an agent:",[305,2417,2419],{"className":307,"code":2418,"language":309,"meta":310,"style":310},"# examples\u002Fch11_viewport.py\nimport asyncio\n\nfrom harness.agent import arun\nfrom harness.providers.anthropic import AnthropicProvider\nfrom harness.tools.registry import ToolRegistry\nfrom harness.tools.std import STANDARD_TOOLS\n\n\nasync def main() -> None:\n    provider = AnthropicProvider()\n    registry = ToolRegistry(tools=STANDARD_TOOLS)\n    await arun(\n        provider=provider,\n        registry=registry,\n        user_message=(\n            \"Read \u002Fetc\u002Fpasswd. There's probably a user called 'nobody' — \"\n            \"find its entry and tell me the shell and home directory.\"\n        ),\n    )\n\n\nasyncio.run(main())\n",[129,2420,2421,2426,2433,2437,2454,2475,2496,2516,2520,2524,2545,2557,2577,2587,2599,2611,2620,2630,2639,2644,2648,2652,2656],{"__ignoreMap":310},[314,2422,2423],{"class":316,"line":317},[314,2424,2425],{"class":320},"# examples\u002Fch11_viewport.py\n",[314,2427,2428,2430],{"class":316,"line":324},[314,2429,357],{"class":327},[314,2431,2432],{"class":338}," asyncio\n",[314,2434,2435],{"class":316,"line":342},[314,2436,346],{"emptyLinePlaceholder":345},[314,2438,2439,2441,2444,2446,2449,2451],{"class":316,"line":349},[314,2440,328],{"class":327},[314,2442,2443],{"class":338}," harness",[314,2445,702],{"class":373},[314,2447,2448],{"class":338},"agent ",[314,2450,357],{"class":327},[314,2452,2453],{"class":338}," arun\n",[314,2455,2456,2458,2460,2462,2465,2467,2470,2472],{"class":316,"line":363},[314,2457,328],{"class":327},[314,2459,2443],{"class":338},[314,2461,702],{"class":373},[314,2463,2464],{"class":338},"providers",[314,2466,702],{"class":373},[314,2468,2469],{"class":338},"anthropic ",[314,2471,357],{"class":327},[314,2473,2474],{"class":338}," AnthropicProvider\n",[314,2476,2477,2479,2481,2483,2486,2488,2491,2493],{"class":316,"line":368},[314,2478,328],{"class":327},[314,2480,2443],{"class":338},[314,2482,702],{"class":373},[314,2484,2485],{"class":338},"tools",[314,2487,702],{"class":373},[314,2489,2490],{"class":338},"registry ",[314,2492,357],{"class":327},[314,2494,2495],{"class":338}," ToolRegistry\n",[314,2497,2498,2500,2502,2504,2506,2508,2511,2513],{"class":316,"line":385},[314,2499,328],{"class":327},[314,2501,2443],{"class":338},[314,2503,702],{"class":373},[314,2505,2485],{"class":338},[314,2507,702],{"class":373},[314,2509,2510],{"class":338},"std ",[314,2512,357],{"class":327},[314,2514,2515],{"class":331}," STANDARD_TOOLS\n",[314,2517,2518],{"class":316,"line":400},[314,2519,346],{"emptyLinePlaceholder":345},[314,2521,2522],{"class":316,"line":405},[314,2523,346],{"emptyLinePlaceholder":345},[314,2525,2526,2529,2532,2535,2538,2540,2543],{"class":316,"line":410},[314,2527,2528],{"class":485},"async",[314,2530,2531],{"class":485}," def",[314,2533,2534],{"class":452}," main",[314,2536,2537],{"class":373},"()",[314,2539,538],{"class":373},[314,2541,2542],{"class":1571}," None",[314,2544,543],{"class":373},[314,2546,2547,2550,2552,2555],{"class":316,"line":424},[314,2548,2549],{"class":338},"    provider ",[314,2551,463],{"class":416},[314,2553,2554],{"class":661}," AnthropicProvider",[314,2556,834],{"class":373},[314,2558,2559,2562,2564,2567,2569,2571,2573,2575],{"class":316,"line":435},[314,2560,2561],{"class":338},"    registry ",[314,2563,463],{"class":416},[314,2565,2566],{"class":661}," ToolRegistry",[314,2568,456],{"class":373},[314,2570,2485],{"class":459},[314,2572,463],{"class":416},[314,2574,2390],{"class":645},[314,2576,670],{"class":373},[314,2578,2579,2582,2585],{"class":316,"line":440},[314,2580,2581],{"class":327},"    await",[314,2583,2584],{"class":661}," arun",[314,2586,1325],{"class":373},[314,2588,2589,2592,2594,2597],{"class":316,"line":445},[314,2590,2591],{"class":459},"        provider",[314,2593,463],{"class":416},[314,2595,2596],{"class":661},"provider",[314,2598,1337],{"class":373},[314,2600,2601,2604,2606,2609],{"class":316,"line":482},[314,2602,2603],{"class":459},"        registry",[314,2605,463],{"class":416},[314,2607,2608],{"class":661},"registry",[314,2610,1337],{"class":373},[314,2612,2613,2616,2618],{"class":316,"line":546},[314,2614,2615],{"class":459},"        user_message",[314,2617,463],{"class":416},[314,2619,1325],{"class":373},[314,2621,2622,2625,2628],{"class":316,"line":557},[314,2623,2624],{"class":469},"            \"",[314,2626,2627],{"class":473},"Read \u002Fetc\u002Fpasswd. There's probably a user called 'nobody' — ",[314,2629,1120],{"class":469},[314,2631,2632,2634,2637],{"class":316,"line":562},[314,2633,2624],{"class":469},[314,2635,2636],{"class":473},"find its entry and tell me the shell and home directory.",[314,2638,1120],{"class":469},[314,2640,2641],{"class":316,"line":568},[314,2642,2643],{"class":373},"        ),\n",[314,2645,2646],{"class":316,"line":574},[314,2647,2163],{"class":373},[314,2649,2650],{"class":316,"line":580},[314,2651,346],{"emptyLinePlaceholder":345},[314,2653,2654],{"class":316,"line":585},[314,2655,346],{"emptyLinePlaceholder":345},[314,2657,2658,2661,2663,2666,2668,2671],{"class":316,"line":591},[314,2659,2660],{"class":338},"asyncio",[314,2662,702],{"class":373},[314,2664,2665],{"class":661},"run",[314,2667,456],{"class":373},[314,2669,2670],{"class":661},"main",[314,2672,2673],{"class":373},"())\n",[112,2675,2676,2677,2680,2681,2683],{},"Run it. The model calls ",[129,2678,2679],{},"read_file_viewport(\"\u002Fetc\u002Fpasswd\", offset=0, limit=100)","; sees the whole file (it's under 100 lines on a typical system); finds the line for \"nobody\"; reports back. Compare against the old ",[129,2682,131],{}," — same outcome, but the token cost of a larger file would be dramatically different. For a 5,000-line log file, the viewport keeps the tool result under 500 lines; a full read would eat much of the context window in one call.",[248,2685],{},[251,2687,2689],{"id":2688},"_115-truncation-envelopes-for-other-tools","11.5 Truncation Envelopes for Other Tools",[112,2691,2692],{},"The viewport pattern is specific to files, but the explicit-envelope principle generalizes. Every tool output that can be large should have the same shape:",[305,2694,2699],{"className":2695,"code":2697,"language":2698,"meta":310},[2696],"language-text","\u003Ccontent>\n[tool_result: \u003CN> items\u002Flines\u002Fbytes returned; \u003CM> more omitted.\n Call \u003Csuggestion> to see more.]\n","text",[129,2700,2697],{"__ignoreMap":310},[112,2702,2703,2704,498],{},"Apply it to ",[129,2705,2706],{},"bash",[305,2708,2710],{"className":307,"code":2709,"language":309,"meta":310,"style":310},"# src\u002Fharness\u002Ftools\u002Fstd.py (bash, updated)\n\nBASH_OUTPUT_LIMIT = 4000  # characters\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    [... description ...]\n    \"\"\"\n    import subprocess\n    timeout = min(int(timeout_seconds), 300)\n    result = subprocess.run(\n        command, shell=True, capture_output=True, text=True, timeout=timeout,\n    )\n    out = result.stdout\n    err = result.stderr\n\n    out_truncated = len(out) > BASH_OUTPUT_LIMIT\n    err_truncated = len(err) > BASH_OUTPUT_LIMIT \u002F\u002F 2\n    if out_truncated:\n        out = out[:BASH_OUTPUT_LIMIT] + f\"\\n...[truncated at {BASH_OUTPUT_LIMIT} chars]\"\n    if err_truncated:\n        err = err[:BASH_OUTPUT_LIMIT \u002F\u002F 2] + f\"\\n...[truncated]\"\n\n    note = \"\"\n    if out_truncated or err_truncated:\n        note = (\"\\n[note: output was truncated. For large output, \"\n                \"pipe through `head`, `tail`, `grep`, or save to a file \"\n                \"and use read_file_viewport.]\")\n\n    return (f\"exit={result.returncode}\\n\"\n            f\"---stdout---\\n{out}\\n\"\n            f\"---stderr---\\n{err}\"\n            + note)\n",[129,2711,2712,2717,2721,2734,2738,2742,2773,2810,2817,2822,2826,2834,2860,2876,2920,2924,2940,2954,2958,2980,3007,3016,3053,3062,3093,3097,3107,3121,3139,3149,3160,3164,3191,3210,3227],{"__ignoreMap":310},[314,2713,2714],{"class":316,"line":317},[314,2715,2716],{"class":320},"# src\u002Fharness\u002Ftools\u002Fstd.py (bash, updated)\n",[314,2718,2719],{"class":316,"line":324},[314,2720,346],{"emptyLinePlaceholder":345},[314,2722,2723,2726,2728,2731],{"class":316,"line":342},[314,2724,2725],{"class":331},"BASH_OUTPUT_LIMIT",[314,2727,417],{"class":416},[314,2729,2730],{"class":420}," 4000",[314,2732,2733],{"class":320},"  # characters\n",[314,2735,2736],{"class":316,"line":349},[314,2737,346],{"emptyLinePlaceholder":345},[314,2739,2740],{"class":316,"line":363},[314,2741,346],{"emptyLinePlaceholder":345},[314,2743,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2769,2771],{"class":316,"line":368},[314,2745,449],{"class":448},[314,2747,453],{"class":452},[314,2749,456],{"class":373},[314,2751,460],{"class":459},[314,2753,463],{"class":416},[314,2755,466],{"class":373},[314,2757,470],{"class":469},[314,2759,474],{"class":473},[314,2761,470],{"class":469},[314,2763,505],{"class":373},[314,2765,1043],{"class":469},[314,2767,2768],{"class":473},"network",[314,2770,470],{"class":469},[314,2772,479],{"class":373},[314,2774,2775,2777,2779,2781,2784,2786,2788,2790,2793,2795,2797,2799,2802,2804,2806,2808],{"class":316,"line":385},[314,2776,486],{"class":485},[314,2778,2402],{"class":452},[314,2780,456],{"class":373},[314,2782,2783],{"class":494},"command",[314,2785,498],{"class":373},[314,2787,502],{"class":501},[314,2789,505],{"class":373},[314,2791,2792],{"class":494}," timeout_seconds",[314,2794,498],{"class":373},[314,2796,513],{"class":501},[314,2798,417],{"class":416},[314,2800,2801],{"class":420}," 30",[314,2803,535],{"class":373},[314,2805,538],{"class":373},[314,2807,502],{"class":501},[314,2809,543],{"class":373},[314,2811,2812,2814],{"class":316,"line":400},[314,2813,550],{"class":549},[314,2815,2816],{"class":553},"Run a shell command in the current working directory.\n",[314,2818,2819],{"class":316,"line":405},[314,2820,2821],{"class":553},"    [... description ...]\n",[314,2823,2824],{"class":316,"line":410},[314,2825,634],{"class":549},[314,2827,2828,2831],{"class":316,"line":424},[314,2829,2830],{"class":327},"    import",[314,2832,2833],{"class":338}," subprocess\n",[314,2835,2836,2839,2841,2843,2845,2848,2850,2853,2855,2858],{"class":316,"line":435},[314,2837,2838],{"class":338},"    timeout ",[314,2840,463],{"class":416},[314,2842,646],{"class":645},[314,2844,456],{"class":373},[314,2846,2847],{"class":501},"int",[314,2849,456],{"class":373},[314,2851,2852],{"class":661},"timeout_seconds",[314,2854,664],{"class":373},[314,2856,2857],{"class":420}," 300",[314,2859,670],{"class":373},[314,2861,2862,2865,2867,2870,2872,2874],{"class":316,"line":440},[314,2863,2864],{"class":338},"    result ",[314,2866,463],{"class":416},[314,2868,2869],{"class":338}," subprocess",[314,2871,702],{"class":373},[314,2873,2665],{"class":661},[314,2875,1325],{"class":373},[314,2877,2878,2881,2883,2886,2888,2890,2892,2895,2897,2899,2901,2904,2906,2908,2910,2913,2915,2918],{"class":316,"line":445},[314,2879,2880],{"class":661},"        command",[314,2882,505],{"class":373},[314,2884,2885],{"class":459}," shell",[314,2887,463],{"class":416},[314,2889,1572],{"class":1571},[314,2891,505],{"class":373},[314,2893,2894],{"class":459}," capture_output",[314,2896,463],{"class":416},[314,2898,1572],{"class":1571},[314,2900,505],{"class":373},[314,2902,2903],{"class":459}," text",[314,2905,463],{"class":416},[314,2907,1572],{"class":1571},[314,2909,505],{"class":373},[314,2911,2912],{"class":459}," timeout",[314,2914,463],{"class":416},[314,2916,2917],{"class":661},"timeout",[314,2919,1337],{"class":373},[314,2921,2922],{"class":316,"line":482},[314,2923,2163],{"class":373},[314,2925,2926,2929,2931,2934,2936],{"class":316,"line":546},[314,2927,2928],{"class":338},"    out ",[314,2930,463],{"class":416},[314,2932,2933],{"class":338}," result",[314,2935,702],{"class":373},[314,2937,2939],{"class":2938},"skxfh","stdout\n",[314,2941,2942,2945,2947,2949,2951],{"class":316,"line":557},[314,2943,2944],{"class":338},"    err ",[314,2946,463],{"class":416},[314,2948,2933],{"class":338},[314,2950,702],{"class":373},[314,2952,2953],{"class":2938},"stderr\n",[314,2955,2956],{"class":316,"line":562},[314,2957,346],{"emptyLinePlaceholder":345},[314,2959,2960,2963,2965,2967,2969,2972,2974,2977],{"class":316,"line":568},[314,2961,2962],{"class":338},"    out_truncated ",[314,2964,463],{"class":416},[314,2966,845],{"class":645},[314,2968,456],{"class":373},[314,2970,2971],{"class":661},"out",[314,2973,535],{"class":373},[314,2975,2976],{"class":416}," >",[314,2978,2979],{"class":331}," BASH_OUTPUT_LIMIT\n",[314,2981,2982,2985,2987,2989,2991,2994,2996,2998,3001,3004],{"class":316,"line":574},[314,2983,2984],{"class":338},"    err_truncated ",[314,2986,463],{"class":416},[314,2988,845],{"class":645},[314,2990,456],{"class":373},[314,2992,2993],{"class":661},"err",[314,2995,535],{"class":373},[314,2997,2976],{"class":416},[314,2999,3000],{"class":331}," BASH_OUTPUT_LIMIT",[314,3002,3003],{"class":416}," \u002F\u002F",[314,3005,3006],{"class":420}," 2\n",[314,3008,3009,3011,3014],{"class":316,"line":580},[314,3010,693],{"class":327},[314,3012,3013],{"class":338}," out_truncated",[314,3015,543],{"class":373},[314,3017,3018,3021,3023,3026,3028,3030,3032,3034,3037,3039,3041,3044,3046,3048,3050],{"class":316,"line":585},[314,3019,3020],{"class":338},"        out ",[314,3022,463],{"class":416},[314,3024,3025],{"class":338}," out",[314,3027,1891],{"class":373},[314,3029,2725],{"class":331},[314,3031,1204],{"class":373},[314,3033,1869],{"class":416},[314,3035,3036],{"class":485}," f",[314,3038,470],{"class":473},[314,3040,1046],{"class":331},[314,3042,3043],{"class":473},"...[truncated at ",[314,3045,466],{"class":420},[314,3047,2725],{"class":331},[314,3049,732],{"class":420},[314,3051,3052],{"class":473}," chars]\"\n",[314,3054,3055,3057,3060],{"class":316,"line":591},[314,3056,693],{"class":327},[314,3058,3059],{"class":338}," err_truncated",[314,3061,543],{"class":373},[314,3063,3064,3067,3069,3072,3074,3076,3078,3080,3082,3084,3086,3088,3090],{"class":316,"line":597},[314,3065,3066],{"class":338},"        err ",[314,3068,463],{"class":416},[314,3070,3071],{"class":338}," err",[314,3073,1891],{"class":373},[314,3075,2725],{"class":331},[314,3077,3003],{"class":416},[314,3079,2042],{"class":420},[314,3081,1204],{"class":373},[314,3083,1869],{"class":416},[314,3085,3036],{"class":485},[314,3087,470],{"class":473},[314,3089,1046],{"class":331},[314,3091,3092],{"class":473},"...[truncated]\"\n",[314,3094,3095],{"class":316,"line":603},[314,3096,346],{"emptyLinePlaceholder":345},[314,3098,3099,3102,3104],{"class":316,"line":608},[314,3100,3101],{"class":338},"    note ",[314,3103,463],{"class":416},[314,3105,3106],{"class":469}," \"\"\n",[314,3108,3109,3111,3114,3117,3119],{"class":316,"line":614},[314,3110,693],{"class":327},[314,3112,3113],{"class":338}," out_truncated ",[314,3115,3116],{"class":416},"or",[314,3118,3059],{"class":338},[314,3120,543],{"class":373},[314,3122,3123,3126,3128,3130,3132,3134,3137],{"class":316,"line":619},[314,3124,3125],{"class":338},"        note ",[314,3127,463],{"class":416},[314,3129,1071],{"class":373},[314,3131,470],{"class":469},[314,3133,1046],{"class":331},[314,3135,3136],{"class":473},"[note: output was truncated. For large output, ",[314,3138,1120],{"class":469},[314,3140,3141,3144,3147],{"class":316,"line":625},[314,3142,3143],{"class":469},"                \"",[314,3145,3146],{"class":473},"pipe through `head`, `tail`, `grep`, or save to a file ",[314,3148,1120],{"class":469},[314,3150,3151,3153,3156,3158],{"class":316,"line":631},[314,3152,3143],{"class":469},[314,3154,3155],{"class":473},"and use read_file_viewport.]",[314,3157,470],{"class":469},[314,3159,670],{"class":373},[314,3161,3162],{"class":316,"line":637},[314,3163,346],{"emptyLinePlaceholder":345},[314,3165,3166,3168,3170,3172,3175,3177,3180,3182,3185,3187,3189],{"class":316,"line":673},[314,3167,1214],{"class":327},[314,3169,1071],{"class":373},[314,3171,722],{"class":485},[314,3173,3174],{"class":473},"\"exit=",[314,3176,466],{"class":420},[314,3178,3179],{"class":338},"result",[314,3181,702],{"class":373},[314,3183,3184],{"class":2938},"returncode",[314,3186,732],{"class":420},[314,3188,1046],{"class":331},[314,3190,1120],{"class":473},[314,3192,3193,3195,3198,3200,3202,3204,3206,3208],{"class":316,"line":690},[314,3194,2200],{"class":485},[314,3196,3197],{"class":473},"\"---stdout---",[314,3199,1046],{"class":331},[314,3201,466],{"class":420},[314,3203,2971],{"class":338},[314,3205,732],{"class":420},[314,3207,1046],{"class":331},[314,3209,1120],{"class":473},[314,3211,3212,3214,3217,3219,3221,3223,3225],{"class":316,"line":711},[314,3213,2200],{"class":485},[314,3215,3216],{"class":473},"\"---stderr---",[314,3218,1046],{"class":331},[314,3220,466],{"class":420},[314,3222,2993],{"class":338},[314,3224,732],{"class":420},[314,3226,1120],{"class":473},[314,3228,3229,3232,3235],{"class":316,"line":739},[314,3230,3231],{"class":416},"            +",[314,3233,3234],{"class":338}," note",[314,3236,670],{"class":373},[112,3238,3239,3240,3242,3243,3246,3247,3246,3250,3253],{},"The ",[129,3241,2706],{}," tool now caps output, labels the truncation explicitly, and tells the model what to do about it. The suggestion (\"pipe through ",[129,3244,3245],{},"head",", ",[129,3248,3249],{},"tail",[129,3251,3252],{},"grep","\") is a small LLM-aware design: it's the idiomatic shell way to reduce output, and the model already knows those tools.",[112,3255,3256,3257,3260,3261,3264],{},"Apply the same to any tool that could return a lot: ",[129,3258,3259],{},"search_docs"," from Chapter 10, ",[129,3262,3263],{},"scratchpad_read"," if an entry gets huge, any HTTP GET tool you add. Consistency of the envelope across tools is itself a feature — the model learns the shape once and applies it everywhere.",[248,3266],{},[251,3268,3270],{"id":3269},"_116-description-hygiene","11.6 Description Hygiene",[112,3272,3273,3274,3281,3282,3285,3286,3289],{},"The tool descriptions in this chapter are longer than the Chapter 4 versions. Deliberately. A tool with three paragraphs of description — covering what it does, when to use it, how to call it, what the output envelope means — is less likely to be misused than a one-line description. The AWS Heroes 2024 post ",[3275,3276,3280],"a",{"href":3277,"rel":3278},"https:\u002F\u002Fdev.to\u002Faws-heroes\u002Fmcp-tool-design-why-your-ai-agent-is-failing-and-how-to-fix-it-40fc",[3279],"nofollow","\"MCP Tool Design: Why Your AI Agent Is Failing\""," put it bluntly: \"",[129,3283,3284],{},"Sends a notification","\" gets abused. \"",[129,3287,3288],{},"Sends an email to the address in args.to. Delivery is asynchronous. Idempotent on message_id. Do not call twice for the same logical message.","\" doesn't.",[112,3291,3292],{},"A checklist for a good tool description:",[3294,3295,3296,3302,3308,3314,3320,3326],"ol",{},[2260,3297,3298,3301],{},[261,3299,3300],{},"What it does."," One sentence.",[2260,3303,3304,3307],{},[261,3305,3306],{},"What it requires."," Preconditions: file exists, user exists, process running.",[2260,3309,3310,3313],{},[261,3311,3312],{},"What it does not do."," Scope limits: \"does not fetch URLs\"; \"does not modify git state.\"",[2260,3315,3316,3319],{},[261,3317,3318],{},"Side effects."," Read\u002Fwrite\u002Fnetwork\u002Fmutate, in plain English.",[2260,3321,3322,3325],{},[261,3323,3324],{},"Output envelope."," What the return value looks like, including truncation behavior.",[2260,3327,3328,3331],{},[261,3329,3330],{},"When to prefer it."," \"Use this rather than X when...\"",[112,3333,3334],{},"The viewport reader docstring hits all six. Every tool we've written from Chapter 4 onward will be retrofitted to the same standard as we revisit them.",[248,3336],{},[251,3338,3340],{"id":3339},"_117-what-swe-agent-got-wrong-and-why-its-instructive","11.7 What SWE-agent Got Wrong (And Why It's Instructive)",[112,3342,3343,3344,3246,3347,3246,3350,3353,3354,3356],{},"The original SWE-agent ACI includes custom commands like ",[129,3345,3346],{},"find_file",[129,3348,3349],{},"search_dir",[129,3351,3352],{},"create",", and a detailed file-viewer state machine. The mini-SWE-agent follow-up threw most of it out and used just ",[129,3355,2706],{}," — and achieved comparable SWE-bench results with ~100 lines of code.",[112,3358,3359],{},"What changed? Frontier models got better at using general-purpose tools. The elaborate ACI commands that SWE-agent built to compensate for GPT-4's clumsiness aren't necessary for Claude 3.5 and beyond, which can drive a shell competently as long as the outputs are framed well.",[112,3361,3362,3363,3366,3367,3370,3371,3373,3374,3376],{},"The lesson: ",[261,3364,3365],{},"design tools to augment the model's weaknesses, not to reinvent capabilities it already has",". Viewport reads are still worth it — no model, however good, does well with 50,000-token tool outputs. Line-range edits are still worth it — they're how diffs work, and they make the agent's intent auditable. But re-implementing ",[129,3368,3369],{},"ls"," or ",[129,3372,3252],{}," when the model can call ",[129,3375,2706],{}," is rarely worth the maintenance burden.",[112,3378,3379,3380,3382],{},"Our design hits the sweet spot: we add what constrains token flow (viewport, envelopes) and let the model use ",[129,3381,2706],{}," for the general-purpose cases.",[248,3384],{},[251,3386,3388],{"id":3387},"_118-commit","11.8 Commit",[305,3390,3393],{"className":3391,"code":3392,"language":2706,"meta":310,"style":310},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","git add -A && git commit -m \"ch11: viewport reader, line-range editor, truncation envelopes\"\ngit tag ch11-tools\n",[129,3394,3395,3427],{"__ignoreMap":310},[314,3396,3397,3401,3404,3408,3411,3414,3417,3420,3422,3425],{"class":316,"line":317},[314,3398,3400],{"class":3399},"sbgvK","git",[314,3402,3403],{"class":473}," add",[314,3405,3407],{"class":3406},"stzsN"," -A",[314,3409,3410],{"class":373}," &&",[314,3412,3413],{"class":3399}," git",[314,3415,3416],{"class":473}," commit",[314,3418,3419],{"class":3406}," -m",[314,3421,1043],{"class":469},[314,3423,3424],{"class":473},"ch11: viewport reader, line-range editor, truncation envelopes",[314,3426,1120],{"class":469},[314,3428,3429,3431,3434],{"class":316,"line":324},[314,3430,3400],{"class":3399},[314,3432,3433],{"class":473}," tag",[314,3435,3436],{"class":473}," ch11-tools\n",[251,3438,3440],{"id":3439},"_119-try-it-yourself","11.9 Try It Yourself",[3294,3442,3443,3456,3466],{},[2260,3444,3445,3448,3449,3451,3452,3455],{},[261,3446,3447],{},"Measure the token impact."," Run a task that reads a 1000-line file, first with ",[129,3450,131],{}," (Chapter 4 version), then with ",[129,3453,3454],{},"read_file_viewport",". Compare total tokens consumed. Compare quality of the output. Is viewport always better, or only for large files?",[2260,3457,3458,3461,3462,3465],{},[261,3459,3460],{},"Extend the edit tool."," Add a ",[129,3463,3464],{},"dry_run"," parameter that returns the diff but doesn't write. The agent can use this to verify before committing. What tradeoffs does the dry-run option introduce?",[2260,3467,3468,3471,3472,3475],{},[261,3469,3470],{},"Write a bad tool on purpose."," Write ",[129,3473,3474],{},"read_file_unbounded"," that returns the whole file with no envelope, and hand it to the agent alongside the viewport version. Watch which one the model picks. Does it drift toward the worse tool when the prompt is short? What does that tell you about description discipline?",[248,3477],{},[3479,3480,3481,3484],"what-you-understand",{},[112,3482,3483],{},"Tools are interfaces for a specific non-human reader. Your file tools now respect that: viewport reads, line-range edits, explicit truncation envelopes, descriptions that name scope and side effects. The bash tool caps and labels its output. The tools the agent reaches for first are the ones designed for it.",[112,3485,3486,3487,3492,3493,3496],{},"What's still missing. The harness has 6 tools (calc, bash, read viewport, edit, plus the scratchpad trio and search_docs). That's a small number; well under any cliff. But an agent system that wants 30 tools — a realistic number for anything past a demo — runs into the scalability problem ",[3275,3488,3491],{"href":3489,"rel":3490},"https:\u002F\u002Fwww.jenova.ai\u002Fen\u002Fresources\u002Fmcp-tool-scalability-problem",[3279],"Jenova AI documented in 2025",": model tool-selection accuracy drops off a cliff past 20–30 tools. Chapter 12 builds the ",[129,3494,3495],{},"ToolSelector"," that scales past the cliff without changing any of the tools we've already written.",[3498,3499,3500],"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 .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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 .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}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 .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 .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--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 .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 .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 .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":310,"searchDepth":324,"depth":324,"links":3502},[3503,3504,3505,3506,3507,3508,3509,3510,3511],{"id":253,"depth":324,"text":254},{"id":302,"depth":324,"text":303},{"id":1277,"depth":324,"text":1278},{"id":2322,"depth":324,"text":2323},{"id":2688,"depth":324,"text":2689},{"id":3269,"depth":324,"text":3270},{"id":3339,"depth":324,"text":3340},{"id":3387,"depth":324,"text":3388},{"id":3439,"depth":324,"text":3440},"md",{},null,{"title":54,"description":117},"4JmHuQJX6k1vxsgMVv-3JDZBxAn-BTWUPaENkEeMbr8",[3518,3520],{"title":50,"path":51,"stem":52,"description":3519,"children":-1},"Previously: the scratchpad gave the agent durable state for what it produces. What it doesn't cover is what the agent needs to read from but didn't write — a codebase it's exploring, documentation, a knowledge base that's larger than the context window could hold even empty.",{"title":58,"path":59,"stem":60,"description":3521,"children":-1},"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?",1776848984232]