import%20marimo%0A%0A__generated_with%20%3D%20%220.25.0%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Build%20an%20agent%0A%0A%20%20%20%20**Lecture%204B%20companion.**%20An%20agent%20is%20a%20language%20model%20in%20a%20loop%3A%20the%20model%20asks%20for%20a%20tool%2C%0A%20%20%20%20a%20program%20(the%20*harness*)%20runs%20it%20and%20appends%20the%20result%2C%20and%20the%20model%20decides%20the%20next%20step.%0A%20%20%20%20Here%20we%20write%20the%20whole%20thing%20ourselves%3A%20a%20tiny%20project%20with%20a%20bug%2C%20five%20tools%2C%20a%20harness%20that%0A%20%20%20%20checks%20every%20edit%2C%20and%20the%20loop.%0A%0A%20%20%20%20Install%20once%2C%20from%20the%20%60Lecture_4B%60%20folder%3A%0A%0A%20%20%20%20%60%60%60bash%0A%20%20%20%20python3.12%20-m%20venv%20.venv%0A%20%20%20%20.venv%2Fbin%2Fpython%20-m%20pip%20install%20-r%20requirements.txt%0A%20%20%20%20.venv%2Fbin%2Fmarimo%20edit%20Build_an_agent.py%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**Two%20modes.**%20*Scripted*%20replays%20a%20fixed%20sequence%20of%20model%20replies%20and%20needs%20nothing%20else%20%E2%80%94%20use%20it%0A%20%20%20%20to%20study%20the%20mechanics.%20*Live*%20calls%20Claude%20through%20the%20Anthropic%20API%3B%20it%20needs%20credentials%0A%20%20%20%20(%60ANTHROPIC_API_KEY%60%2C%20or%20a%20profile%20from%20%60ant%20auth%20login%60)%20and%20costs%20a%20few%20cents%20per%20run.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20json%0A%20%20%20%20import%20re%0A%20%20%20%20import%20shutil%0A%20%20%20%20import%20subprocess%0A%20%20%20%20import%20sys%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20types%20import%20SimpleNamespace%0A%0A%20%20%20%20return%20Path%2C%20SimpleNamespace%2C%20json%2C%20re%2C%20shutil%2C%20subprocess%2C%20sys%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%201.%20A%20small%20project%20with%20a%20bug%0A%0A%20%20%20%20Relativistic%20kinematics%20in%20natural%20units%3A%20%24E%5E2%3Dm%5E2%2Bp%5E2%24%2C%20so%20the%20invariant%20mass%20is%0A%20%20%20%20%24m%3D%5Csqrt%7BE%5E2-p%5E2%7D%24.%20The%20code%20has%20a%20sign%20error%2C%20and%20one%20test%20fails.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20shutil)%3A%0A%20%20%20%20FILES%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22kinematics.py%22%3A%20'''%22%22%22Relativistic%20kinematics%20in%20natural%20units%20(c%20%3D%201)%3B%20energies%20and%20momenta%20in%20GeV.%22%22%22%0A%20%20%20%20import%20math%0A%0A%0A%20%20%20%20def%20energy(m%2C%20p)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Energy%20of%20a%20particle%20with%20mass%20m%20and%20momentum%20p.%22%22%22%0A%20%20%20%20%20%20%20%20return%20math.sqrt(m**2%20%2B%20p**2)%0A%0A%0A%20%20%20%20def%20invariant_mass(E%2C%20p)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Invariant%20mass%20of%20a%20system%20with%20total%20energy%20E%20and%20total%20momentum%20p.%22%22%22%0A%20%20%20%20%20%20%20%20return%20math.sqrt(E**2%20%2B%20p**2)%0A%20%20%20%20'''%2C%0A%20%20%20%20%20%20%20%20%22test_kinematics.py%22%3A%20'''from%20kinematics%20import%20energy%2C%20invariant_mass%0A%0A%0A%20%20%20%20def%20test_energy()%3A%0A%20%20%20%20%20%20%20%20assert%20abs(energy(0.938%2C%200.0)%20-%200.938)%20%3C%201e-9%0A%0A%0A%20%20%20%20def%20test_invariant_mass()%3A%0A%20%20%20%20%20%20%20%20%23%20a%20J%2Fpsi-like%20system%3A%20E%20%3D%203.2%20GeV%2C%20p%20%3D%200.8%20GeV%2C%20so%20m%20%3D%20sqrt(9.6)%20%3D%203.0984%20GeV%0A%20%20%20%20%20%20%20%20assert%20abs(invariant_mass(3.2%2C%200.8)%20-%203.0984)%20%3C%201e-3%0A%0A%0A%20%20%20%20if%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%20%20%20%20failed%20%3D%200%0A%20%20%20%20%20%20%20%20for%20test%20in%20(test_energy%2C%20test_invariant_mass)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(%22PASSED%22%2C%20test.__name__)%0A%20%20%20%20%20%20%20%20%20%20%20%20except%20AssertionError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20failed%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(%22FAILED%22%2C%20test.__name__)%0A%20%20%20%20%20%20%20%20raise%20SystemExit(failed)%0A%20%20%20%20'''%2C%0A%20%20%20%20%7D%0A%20%20%20%20WORKSPACE%20%3D%20Path(mo.notebook_dir())%20%2F%20%22agent_workspace%22%0A%0A%20%20%20%20def%20reset_workspace()%3A%0A%20%20%20%20%20%20%20%20%22%22%22A%20fresh%20copy%20of%20the%20project%20for%20every%20run.%22%22%22%0A%20%20%20%20%20%20%20%20shutil.rmtree(WORKSPACE%2C%20ignore_errors%3DTrue)%0A%20%20%20%20%20%20%20%20WORKSPACE.mkdir()%0A%20%20%20%20%20%20%20%20for%20name%2C%20content%20in%20FILES.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20(WORKSPACE%20%2F%20name).write_text(content)%0A%0A%20%20%20%20reset_workspace()%0A%20%20%20%20mo.md(f%22Project%20written%20to%20%60%7BWORKSPACE%7D%60%3A%5Cn%5Cn%60%60%60python%5Cn%7BFILES%5B'kinematics.py'%5D%7D%60%60%60%22)%0A%20%20%20%20return%20FILES%2C%20WORKSPACE%2C%20reset_workspace%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%202.%20Tools%20and%20harness%0A%0A%20%20%20%20Each%20tool%20is%20a%20Python%20function%20plus%20a%20JSON%20description%20that%20the%20model%20reads.%20The%20harness%20adds%20three%20things%0A%20%20%20%20the%20model%20does%20not%20control%3A%0A%0A%20%20%20%20-%20**sandbox**%3A%20tools%20only%20see%20files%20inside%20the%20workspace%3B%0A%20%20%20%20-%20**safe%20editing**%3A%20%60str_replace%60%20changes%20text%20only%20if%20%60old_string%60%20occurs%20**exactly%20once**%3B%0A%20%20%20%20-%20**validation**%3A%20after%20every%20edit%20the%20harness%20compiles%20the%20file%20and%20appends%20the%20result%20%E2%80%94%20a%20linter%20the%20model%20cannot%20forget%20to%20run.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(WORKSPACE%2C%20re%2C%20subprocess%2C%20sys)%3A%0A%20%20%20%20def%20inside(path)%3A%0A%20%20%20%20%20%20%20%20p%20%3D%20(WORKSPACE%20%2F%20path).resolve()%0A%20%20%20%20%20%20%20%20if%20WORKSPACE.resolve()%20not%20in%20p.parents%20and%20p%20!%3D%20WORKSPACE.resolve()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22%7Bpath%7D%20is%20outside%20the%20workspace%22)%0A%20%20%20%20%20%20%20%20return%20p%0A%0A%20%20%20%20def%20list_files()%3A%0A%20%20%20%20%20%20%20%20return%20%22%5Cn%22.join(sorted(p.name%20for%20p%20in%20WORKSPACE.iterdir()%20if%20p.is_file()))%0A%0A%20%20%20%20def%20read_file(path)%3A%0A%20%20%20%20%20%20%20%20lines%20%3D%20inside(path).read_text().splitlines()%0A%20%20%20%20%20%20%20%20return%20%22%5Cn%22.join(f%22%7Bi%20%2B%201%3A4d%7D%20%20%7Bline%7D%22%20for%20i%2C%20line%20in%20enumerate(lines))%0A%0A%20%20%20%20def%20grep(pattern)%3A%0A%20%20%20%20%20%20%20%20hits%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20f%20in%20sorted(WORKSPACE.glob(%22*.py%22))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20line%20in%20enumerate(f.read_text().splitlines())%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20re.search(pattern%2C%20line)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20hits.append(f%22%7Bf.name%7D%3A%7Bi%20%2B%201%7D%3A%20%7Bline.strip()%7D%22)%0A%20%20%20%20%20%20%20%20return%20%22%5Cn%22.join(hits)%20or%20%22no%20matches%22%0A%0A%20%20%20%20def%20lint(path)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Harness%20check%3A%20does%20the%20file%20still%20compile%3F%22%22%22%0A%20%20%20%20%20%20%20%20r%20%3D%20subprocess.run(%5Bsys.executable%2C%20%22-m%22%2C%20%22py_compile%22%2C%20str(path)%5D%2C%20capture_output%3DTrue%2C%20text%3DTrue)%0A%20%20%20%20%20%20%20%20return%20%22lint%3A%20OK%22%20if%20r.returncode%20%3D%3D%200%20else%20%22lint%3A%20FAILED%5Cn%22%20%2B%20r.stderr.strip().splitlines()%5B-1%5D%0A%0A%20%20%20%20def%20str_replace(path%2C%20old_string%2C%20new_string)%3A%0A%20%20%20%20%20%20%20%20p%20%3D%20inside(path)%0A%20%20%20%20%20%20%20%20text%20%3D%20p.read_text()%0A%20%20%20%20%20%20%20%20n%20%3D%20text.count(old_string)%0A%20%20%20%20%20%20%20%20if%20n%20!%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22old_string%20found%20%7Bn%7D%20times%20in%20%7Bpath%7D%3B%20it%20must%20occur%20exactly%20once.%20Add%20more%20context.%22)%0A%20%20%20%20%20%20%20%20p.write_text(text.replace(old_string%2C%20new_string))%0A%20%20%20%20%20%20%20%20return%20f%22Edited%20%7Bpath%7D.%5Cn%22%20%2B%20lint(p)%0A%0A%20%20%20%20def%20run_tests()%3A%0A%20%20%20%20%20%20%20%20r%20%3D%20subprocess.run(%5Bsys.executable%2C%20%22test_kinematics.py%22%5D%2C%20cwd%3DWORKSPACE%2C%20capture_output%3DTrue%2C%20text%3DTrue%2C%20timeout%3D30)%0A%20%20%20%20%20%20%20%20return%20(r.stdout%20%2B%20r.stderr).strip()%20%2B%20f%22%5Cn(exit%20code%20%7Br.returncode%7D)%22%0A%0A%20%20%20%20TOOL_FUNCTIONS%20%3D%20%7B%22list_files%22%3A%20list_files%2C%20%22read_file%22%3A%20read_file%2C%20%22grep%22%3A%20grep%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22str_replace%22%3A%20str_replace%2C%20%22run_tests%22%3A%20run_tests%7D%0A%0A%20%20%20%20def%20obj(**props)%3A%0A%20%20%20%20%20%20%20%20return%20%7B%22type%22%3A%20%22object%22%2C%20%22properties%22%3A%20props%2C%20%22required%22%3A%20list(props)%7D%0A%0A%20%20%20%20TOOLS%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%7B%22name%22%3A%20%22list_files%22%2C%20%22description%22%3A%20%22List%20the%20files%20in%20the%20project.%22%2C%20%22input_schema%22%3A%20obj()%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22name%22%3A%20%22read_file%22%2C%20%22description%22%3A%20%22Read%20a%20file%2C%20with%20line%20numbers.%22%2C%20%22input_schema%22%3A%20obj(path%3D%7B%22type%22%3A%20%22string%22%7D)%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22name%22%3A%20%22grep%22%2C%20%22description%22%3A%20%22Search%20all%20Python%20files%20for%20a%20regular%20expression.%22%2C%20%22input_schema%22%3A%20obj(pattern%3D%7B%22type%22%3A%20%22string%22%7D)%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22name%22%3A%20%22str_replace%22%2C%20%22description%22%3A%20%22Replace%20old_string%20by%20new_string%20in%20a%20file.%20old_string%20must%20occur%20exactly%20once%3B%20include%20enough%20surrounding%20text%20to%20make%20it%20unique.%22%2C%0A%20%20%20%20%20%20%20%20%20%22input_schema%22%3A%20obj(path%3D%7B%22type%22%3A%20%22string%22%7D%2C%20old_string%3D%7B%22type%22%3A%20%22string%22%7D%2C%20new_string%3D%7B%22type%22%3A%20%22string%22%7D)%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22name%22%3A%20%22run_tests%22%2C%20%22description%22%3A%20%22Run%20the%20test%20suite.%22%2C%20%22input_schema%22%3A%20obj()%7D%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20def%20run_tool(call)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Execute%20one%20tool_use%20block%20and%20return%20the%20tool_result%20block%20for%20the%20model.%22%22%22%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fn%20%3D%20TOOL_FUNCTIONS%5Bcall.name%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20out%2C%20err%20%3D%20fn(**call.input)%2C%20False%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20errors%20go%20back%20to%20the%20model%2C%20they%20do%20not%20crash%20the%20loop%0A%20%20%20%20%20%20%20%20%20%20%20%20out%2C%20err%20%3D%20f%22Error%3A%20%7Be%7D%22%2C%20True%0A%20%20%20%20%20%20%20%20return%20%7B%22type%22%3A%20%22tool_result%22%2C%20%22tool_use_id%22%3A%20call.id%2C%20%22content%22%3A%20out%2C%20%22is_error%22%3A%20err%7D%0A%0A%20%20%20%20return%20TOOLS%2C%20run_tool%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%203.%20Two%20models%20behind%20the%20same%20interface%0A%0A%20%20%20%20Both%20take%20the%20message%20history%20and%20return%20a%20reply%20whose%20%60content%60%20is%20a%20list%20of%20blocks%3A%0A%20%20%20%20%60thinking%60%2C%20%60text%60%2C%20or%20%60tool_use%60.%20The%20scripted%20model%20ignores%20the%20history%20and%20replays%20a%20prepared%0A%20%20%20%20trajectory%20%E2%80%94%20including%20two%20mistakes%20that%20the%20harness%20catches.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(SimpleNamespace%2C%20TOOLS)%3A%0A%20%20%20%20SYSTEM%20%3D%20(%22You%20are%20a%20coding%20agent%20working%20in%20a%20small%20Python%20project.%20Use%20the%20tools%20to%20inspect%2C%20edit%20and%20test%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22the%20code.%20Make%20the%20smallest%20change%20that%20fixes%20the%20problem%2C%20run%20the%20tests%20to%20confirm%2C%20then%20reply%20with%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22a%20two-sentence%20summary.%22)%0A%0A%20%20%20%20def%20block(type%2C%20**kw)%3A%0A%20%20%20%20%20%20%20%20return%20SimpleNamespace(type%3Dtype%2C%20**kw)%0A%0A%20%20%20%20def%20call(i%2C%20name%2C%20**inp)%3A%0A%20%20%20%20%20%20%20%20return%20block(%22tool_use%22%2C%20id%3Df%22call_%7Bi%7D%22%2C%20name%3Dname%2C%20input%3Dinp)%0A%0A%20%20%20%20SCRIPT%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bblock(%22thinking%22%2C%20thinking%3D%22Start%20by%20running%20the%20tests%20to%20see%20what%20fails.%22)%2C%20call(1%2C%20%22run_tests%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bblock(%22thinking%22%2C%20thinking%3D%22test_invariant_mass%20fails.%20Find%20the%20function.%22)%2C%20call(2%2C%20%22grep%22%2C%20pattern%3D%22def%20invariant_mass%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bcall(3%2C%20%22read_file%22%2C%20path%3D%22kinematics.py%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bblock(%22thinking%22%2C%20thinking%3D%22m%C2%B2%20%3D%20E%C2%B2%20%E2%88%92%20p%C2%B2%2C%20but%20line%2012%20adds%20p%C2%B2.%20Replace%20the%20plus%20by%20a%20minus.%22)%2C%0A%20%20%20%20%20%20%20%20%20call(4%2C%20%22str_replace%22%2C%20path%3D%22kinematics.py%22%2C%20old_string%3D%22%2B%20p**2%22%2C%20new_string%3D%22-%20p**2%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bblock(%22thinking%22%2C%20thinking%3D%22%60%2B%20p**2%60%20also%20occurs%20in%20%60energy()%60.%20Use%20the%20whole%20expression.%22)%2C%0A%20%20%20%20%20%20%20%20%20call(5%2C%20%22str_replace%22%2C%20path%3D%22kinematics.py%22%2C%20old_string%3D%22math.sqrt(E**2%20%2B%20p**2)%22%2C%20new_string%3D%22math.sqrt(E**2%20-%20p**2%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bblock(%22thinking%22%2C%20thinking%3D%22The%20linter%20says%20the%20parenthesis%20is%20not%20closed.%20Fix%20my%20edit.%22)%2C%0A%20%20%20%20%20%20%20%20%20call(6%2C%20%22str_replace%22%2C%20path%3D%22kinematics.py%22%2C%20old_string%3D%22math.sqrt(E**2%20-%20p**2%22%2C%20new_string%3D%22math.sqrt(E**2%20-%20p**2)%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bcall(7%2C%20%22run_tests%22)%5D%2C%0A%20%20%20%20%20%20%20%20%5Bblock(%22text%22%2C%20text%3D%22invariant_mass%20added%20p%C2%B2%20instead%20of%20subtracting%20it%3B%20it%20now%20returns%20%E2%88%9A(E%C2%B2%20%E2%88%92%20p%C2%B2).%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Both%20tests%20pass.%22)%5D%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20def%20scripted_model(messages)%3A%0A%20%20%20%20%20%20%20%20step%20%3D%20sum(1%20for%20m%20in%20messages%20if%20m%5B%22role%22%5D%20%3D%3D%20%22assistant%22)%0A%20%20%20%20%20%20%20%20content%20%3D%20SCRIPT%5Bmin(step%2C%20len(SCRIPT)%20-%201)%5D%0A%20%20%20%20%20%20%20%20stop%20%3D%20%22tool_use%22%20if%20any(b.type%20%3D%3D%20%22tool_use%22%20for%20b%20in%20content)%20else%20%22end_turn%22%0A%20%20%20%20%20%20%20%20return%20SimpleNamespace(content%3Dcontent%2C%20stop_reason%3Dstop%2C%20usage%3DNone)%0A%0A%20%20%20%20def%20live_model(messages)%3A%0A%20%20%20%20%20%20%20%20import%20anthropic%0A%20%20%20%20%20%20%20%20client%20%3D%20anthropic.Anthropic()%20%20%20%20%20%20%20%20%20%20%23%20credentials%20from%20ANTHROPIC_API_KEY%20or%20%60ant%20auth%20login%60%0A%20%20%20%20%20%20%20%20return%20client.beta.messages.create(%0A%20%20%20%20%20%20%20%20%20%20%20%20model%3D%22claude-opus-5%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_tokens%3D16000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20betas%3D%5B%22server-side-fallback-2026-07-01%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fallbacks%3D%22default%22%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20a%20declined%20request%20is%20retried%20on%20a%20fallback%20model%0A%20%20%20%20%20%20%20%20%20%20%20%20thinking%3D%7B%22type%22%3A%20%22adaptive%22%2C%20%22display%22%3A%20%22summarized%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20output_config%3D%7B%22effort%22%3A%20%22medium%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20system%3DSYSTEM%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tools%3DTOOLS%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20messages%3Dmessages%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20return%20live_model%2C%20scripted_model%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%204.%20The%20loop%0A%0A%20%20%20%20This%20is%20the%20whole%20agent.%20Everything%20the%20model%20%E2%80%9Cremembers%E2%80%9D%20is%20the%20%60messages%60%20list%2C%20which%20is%20sent%20in%20full%20on%20every%20call.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(run_tool)%3A%0A%20%20%20%20def%20agent_loop(task%2C%20model%2C%20max_steps%3D12%2C%20log%3Dprint)%3A%0A%20%20%20%20%20%20%20%20messages%20%3D%20%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20task%7D%5D%0A%20%20%20%20%20%20%20%20for%20step%20in%20range(max_steps)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20reply%20%3D%20model(messages)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20one%20call%20to%20the%20language%20model%0A%20%20%20%20%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20reply.content%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20log(step%2C%20%22model%22%2C%20reply)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20reply.stop_reason%20in%20(%22refusal%22%2C%20%22max_tokens%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20calls%20%3D%20%5Bb%20for%20b%20in%20reply.content%20if%20b.type%20%3D%3D%20%22tool_use%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20calls%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20no%20tool%20requested%3A%20the%20model%20is%20done%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20results%20%3D%20%5Brun_tool(c)%20for%20c%20in%20calls%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20the%20harness%20acts%0A%20%20%20%20%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20results%7D)%20%20%20%20%20%23%20results%20go%20back%20into%20the%20context%0A%20%20%20%20%20%20%20%20%20%20%20%20log(step%2C%20%22tools%22%2C%20results)%0A%20%20%20%20%20%20%20%20return%20messages%0A%0A%20%20%20%20return%20(agent_loop%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mode%20%3D%20mo.ui.dropdown(%5B%22scripted%22%2C%20%22live%20(Claude%20API)%22%5D%2C%20value%3D%22scripted%22%2C%20label%3D%22Model%22)%0A%20%20%20%20task%20%3D%20mo.ui.text_area(value%3D%22The%20tests%20in%20test_kinematics.py%20fail.%20Find%20and%20fix%20the%20bug.%22%2C%20full_width%3DTrue%2C%20rows%3D2%2C%20label%3D%22Task%22)%0A%20%20%20%20max_steps%20%3D%20mo.ui.slider(2%2C%2020%2C%20value%3D12%2C%20label%3D%22Max%20steps%22%2C%20show_value%3DTrue)%0A%20%20%20%20go%20%3D%20mo.ui.run_button(label%3D%22Run%20the%20agent%22)%0A%20%20%20%20mo.vstack(%5Btask%2C%20mo.hstack(%5Bmode%2C%20max_steps%2C%20go%5D%2C%20justify%3D%22start%22%2C%20gap%3D2)%5D)%0A%20%20%20%20return%20go%2C%20max_steps%2C%20mode%2C%20task%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20agent_loop%2C%0A%20%20%20%20go%2C%0A%20%20%20%20json%2C%0A%20%20%20%20live_model%2C%0A%20%20%20%20max_steps%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20mode%2C%0A%20%20%20%20reset_workspace%2C%0A%20%20%20%20scripted_model%2C%0A%20%20%20%20task%2C%0A)%3A%0A%20%20%20%20mo.stop(not%20go.value%2C%20mo.md(%22*Press%20%E2%80%9CRun%20the%20agent%E2%80%9D.*%22))%0A%20%20%20%20reset_workspace()%0A%20%20%20%20_cards%2C%20_usage%20%3D%20%5B%5D%2C%20%7B%22in%22%3A%200%2C%20%22out%22%3A%200%7D%0A%0A%20%20%20%20def%20_fmt(x)%3A%0A%20%20%20%20%20%20%20%20return%20x%20if%20isinstance(x%2C%20str)%20else%20json.dumps(x)%0A%0A%20%20%20%20def%20_log(step%2C%20who%2C%20payload)%3A%0A%20%20%20%20%20%20%20%20if%20who%20%3D%3D%20%22model%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20u%20%3D%20getattr(payload%2C%20%22usage%22%2C%20None)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20u%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_usage%5B%22in%22%5D%20%2B%3D%20u.input_tokens%3B%20_usage%5B%22out%22%5D%20%2B%3D%20u.output_tokens%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20b%20in%20payload.content%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20b.type%20%3D%3D%20%22thinking%22%20and%20b.thinking%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cards.append(mo.md(f%22**%7Bstep%20%2B%201%7D%20%C2%B7%20thinking**%20%E2%80%94%20*%7Bb.thinking%7D*%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20b.type%20%3D%3D%20%22text%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cards.append(mo.md(f%22**%7Bstep%20%2B%201%7D%20%C2%B7%20answer**%20%E2%80%94%20%7Bb.text%7D%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20b.type%20%3D%3D%20%22tool_use%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cards.append(mo.md(f%22**%7Bstep%20%2B%201%7D%20%C2%B7%20tool%20call**%20%60%7Bb.name%7D(%7B_fmt(b.input)%7D)%60%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20payload.stop_reason%20%3D%3D%20%22refusal%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cards.append(mo.md(f%22**refused**%20%E2%80%94%20%7Bgetattr(payload%2C%20'stop_details'%2C%20'')%7D%22))%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20r%20in%20payload%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20%22error%22%20if%20r%5B%22is_error%22%5D%20else%20%22result%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cards.append(mo.md(f%22**%7Bstep%20%2B%201%7D%20%C2%B7%20%7Bflag%7D**%5Cn%60%60%60%5Cn%7Br%5B'content'%5D%7D%5Cn%60%60%60%22))%0A%0A%20%20%20%20_model%20%3D%20scripted_model%20if%20mode.value%20%3D%3D%20%22scripted%22%20else%20live_model%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20transcript%20%3D%20agent_loop(task.value%2C%20_model%2C%20max_steps.value%2C%20_log)%0A%20%20%20%20%20%20%20%20_summary%20%3D%20f%22%7Blen(transcript)%7D%20messages%22%20%2B%20(f%22%2C%20%7B_usage%5B'in'%5D%3A%2C%7D%20input%20and%20%7B_usage%5B'out'%5D%3A%2C%7D%20output%20tokens%20billed%22%20if%20_usage%5B%22in%22%5D%20else%20%22%22)%0A%20%20%20%20except%20Exception%20as%20e%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20e.g.%20no%20credentials%20in%20live%20mode%0A%20%20%20%20%20%20%20%20transcript%2C%20_summary%20%3D%20%5B%5D%2C%20(f%22Stopped%3A%20%7Btype(e).__name__%7D%3A%20%7Be%7D.%20Live%20mode%20needs%20credentials%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22set%20ANTHROPIC_API_KEY%20or%20run%20%60ant%20auth%20login%60%2C%20then%20restart%20the%20notebook.%22)%0A%20%20%20%20mo.vstack(_cards%20%2B%20%5Bmo.md(f%22*%7B_summary%7D*%22)%5D)%0A%20%20%20%20return%20(transcript%2C)%0A%0A%0A%40app.cell%0Adef%20_(FILES%2C%20WORKSPACE%2C%20mo%2C%20transcript)%3A%0A%20%20%20%20import%20difflib%0A%20%20%20%20_new%20%3D%20(WORKSPACE%20%2F%20%22kinematics.py%22).read_text()%0A%20%20%20%20_diff%20%3D%20%22%22.join(difflib.unified_diff(FILES%5B%22kinematics.py%22%5D.splitlines(True)%2C%20_new.splitlines(True)%2C%20%22before%22%2C%20%22after%22))%0A%20%20%20%20mo.md(f%22%23%23%23%20What%20changed%20on%20disk%5Cn%5Cn%60%60%60diff%5Cn%7B_diff%20or%20'(nothing)'%7D%5Cn%60%60%60%5Cn%5Cn%7Blen(transcript)%7D%20messages%20were%20sent%20back%20and%20forth.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Exercises%0A%0A%20%20%20%201.%20In%20scripted%20mode%2C%20find%20the%20two%20steps%20where%20the%20harness%20corrected%20the%20model.%20Which%20check%20caught%20which%20mistake%3F%0A%20%20%20%202.%20Remove%20the%20%60lint(p)%60%20call%20from%20%60str_replace%60.%20What%20happens%20at%20step%206%20of%20the%20scripted%20run%2C%20and%20when%20would%20the%20model%20notice%3F%0A%20%20%20%203.%20Change%20%60str_replace%60%20to%20replace%20*all*%20occurrences.%20Why%20is%20that%20dangerous%20here%3F%0A%20%20%20%204.%20Live%20mode%3A%20give%20a%20vaguer%20task%20(%E2%80%9Cimprove%20the%20code%E2%80%9D).%20How%20many%20steps%20does%20the%20model%20take%3F%20What%20does%20it%20change%3F%0A%20%20%20%205.%20Add%20a%20tool%20%60write_file%60.%20Should%20the%20harness%20allow%20overwriting%20the%20test%20file%3F%20Add%20a%20rule%20that%20forbids%20it.%0A%20%20%20%206.%20The%20whole%20history%20is%20resent%20on%20every%20call.%20Using%20the%20token%20counts%20printed%20in%20live%20mode%2C%20estimate%20the%20cost%20of%20a%2050-step%20session.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
d944a41848b3c4e23b283ca0f3b6b217