ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

learn-claude-code s01 精讲:Agent Loop —— 一个循环加 Bash 的最小 Agent Harness 内核

2026/9/7 17:12:50 拓冰建站 浏览量
learn-claude-code s01 精讲:Agent Loop —— 一个循环加 Bash 的最小 Agent Harness 内核 learn-claude-code s01 精讲Agent Loop —— 一个循环加 Bash 的最小 Agent Harness 内核【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code本篇围绕 learn-claude-code 仓库第 1 章 s01_agent_loop/README.md 展开它用不到 30 行 Python 代码构建了一个最小可运行的 Agent Harness 内核回答“如何把 LLM 从只会输出命令变成能持续执行命令的 Agent”这个问题。读完后你能完整掌握 Agent Loop 的控制信号stop_reason/tool_use、消息累积机制、工具结果的回填格式并能基于 s01_agent_loop/code.py 亲自运行一个带交互式会话的最小编码 Agent。一、问题模型只会“说”命令不会“执行”命令章节开篇描述的场景非常具体你让模型“列出目录文件并运行 XXX.py”模型确实能输出一条 bash 命令但输出完毕它就停住了——它不会自己执行命令更不会基于执行结果继续推理。于是出现了这样一种工作流你手动执行命令把输出粘贴回聊天框模型再产出下一条命令你再执行、再粘贴。每一轮往返中你本人充当了模型与世界之间的中间层。s01 要做的事情就是把这个由人肉充当的“中间层”自动化。这一点在仓库的另一版文档 docs/en/s01-the-agent-loop.md 中表述得更直白语言模型能推理代码但它碰不到真实世界——读不了文件、跑不了测试、看不到报错。“Without a loop, every tool call requires you to manually copy-paste results back. You become the loop.”没有循环每次工具调用都要你手动复制粘贴结果回来你自己就成了那个循环。二、解法一个 while True 循环只靠两个信号驱动s01 给出的完整解法是一个while True循环模型调用工具就继续不再调用就停。整个过程只依赖两个控制信号原文明确给出了这张信号表信号含义循环动作stop_reason tool_use模型“举手”我需要工具执行工具 → 把结果喂回去 → 继续循环stop_reason ! tool_use模型说我做完了退出循环整个流程只有一条出口条件stop_reason不再是tool_use。这意味着“何时停”的决策权完全在模型侧代码侧只负责执行与回传——这正是仓库 README 中反复强调的分工原则“The model decides. The harness executes.”模型决策Harness 执行。三、五步拆解 agent_loop 的实现README 把实现拆成五步逐步展开以下按原文骨架完整还原并与仓库真实源码对齐。Step 1以用户问题作为第一条消息messages [{role: user, content: query}]Step 2把消息和工具定义发给 LLMresponse client.messages.create( modelMODEL, systemSYSTEM, messagesmessages, toolsTOOLS, max_tokens8000, )Step 3追加模型回复检查是否调用了工具——没调用就结束messages.append({role: assistant, content: response.content}) if response.stop_reason ! tool_use: returnStep 4执行模型请求的工具收集结果results [] for block in response.content: if block.type tool_use: output run_bash(block.input[command]) results.append({ type: tool_result, tool_use_id: block.id, content: output, })注意这里三个细节只处理block.type tool_use的内容块模型回复里可能同时有 text 块tool_use_id必须原样回传block.id这是 API 配对工具调用与结果的唯一依据结果统一包装成tool_result类型。Step 5把工具结果作为一条新消息追加回到 Step 2messages.append({role: user, content: results})一个容易被忽略但关键的约定工具结果是以role: user的身份回传的而不是 assistant。这样messages列表的交替结构user / assistant / user / assistant …保持不变。组装成完整函数就是原文给出的最小内核def agent_loop(messages): while True: response client.messages.create( modelMODEL, systemSYSTEM, messagesmessages, toolsTOOLS, max_tokens8000, ) messages.append({role: assistant, content: response.content}) if response.stop_reason ! tool_use: return results [] for block in response.content: if block.type tool_use: output run_bash(block.input[command]) results.append({ type: tool_result, tool_use_id: block.id, content: output, }) messages.append({role: user, content: results})不到 30 行——这就是最小可运行的 agent harness 内核。它本身不是智能而是让模型能够持续行动的最小运行时框架模型决定是否调用工具、调用哪个Harness 执行调用工具并把结果作为新消息追加。后续 16 章全部是在这个循环之上叠加机制而循环本身从未改变。对照仓库中的 docs/en/s01-the-agent-loop.md本章前后系统的变化可以总结为组件之前之后Agent 循环无while Truestop_reason判断工具无bash一个工具消息无不断累积的messages列表控制流无stop_reason ! tool_use退出四、深入 code.pyREADME 之外的真实实现细节README 展示的是教学骨架s01_agent_loop/code.py 是可以直接运行的完整版本。两者核心一致但源码里多出了若干工程细节值得逐一看清。4.1 唯一的工具bash工具定义只有一个bashSchema 极简见 code.py#L57-L65TOOLS [{ name: bash, description: Run a shell command., input_schema: { type: object, properties: {command: {type: string}}, required: [command], }, }]4.2 工具执行器 run_bash黑名单、超时与截断真正的执行器run_bashcode.py#L69-L81承担了三层防护def run_bash(command: str) - str: dangerous [rm -rf /, sudo, shutdown, reboot, /dev/] if any(d in command for d in dangerous): return Error: Dangerous command blocked try: r subprocess.run(command, shellTrue, cwdos.getcwd(), capture_outputTrue, textTrue, timeout120) out (r.stdout r.stderr).strip() return out[:50000] if out else (no output) except subprocess.TimeoutExpired: return Error: Timeout (120s) except (FileNotFoundError, OSError) as e: return fError: {e}从源码结构看这里的设计取舍很清晰危险命令黑名单rm -rf /、sudo、shutdown、reboot、 /dev/五类子串直接拦截返回错误文本而不抛异常——注意它返回的是字符串意味着错误会作为tool_result喂回模型由模型自行决定如何修正而不是让进程崩溃固定执行边界subprocess.run以shellTrue、cwdos.getcwd()执行即 Agent 的活动范围被限定在启动时的当前目录timeout120秒防止命令挂死整个循环输出截断stdout 与 stderr 合并后截取前 50000 字符无输出时回退为(no output)。这一层保护了上下文——否则一条cat大文件就可能撑爆下一轮请求。4.3 系统提示词与工作目录系统提示词只有一句话code.py#L54SYSTEM fYou are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, dont explain.把当前工作目录直接注入提示词让模型知道自己在哪个目录操作“Act, dont explain” 则约束模型直接行动而非解释。4.4 环境配置.env 与兼容服务商启动配置集中在文件头部code.py#L46-L54load_dotenv(overrideTrue) if os.getenv(ANTHROPIC_BASE_URL): os.environ.pop(ANTHROPIC_AUTH_TOKEN, None) client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL)) MODEL os.environ[MODEL_ID]通过load_dotenv(overrideTrue)读取.env若设置了ANTHROPIC_BASE_URL接入 Anthropic 兼容网关会主动移除ANTHROPIC_AUTH_TOKEN避免两套鉴权凭证冲突MODEL_ID是必填环境变量缺失时脚本会直接抛KeyError——这是运行时的第一个硬性前提。仓库根目录的 .env.example 给出了完整模板必填的ANTHROPIC_API_KEY与MODEL_ID模板默认claude-sonnet-4-6可选的ANTHROPIC_BASE_URL并附有一张兼容服务商对照注释表MiniMax、GLM/智谱、Kimi/月之暗面、DeepSeek 的对应MODEL_ID与 Base URL区分国际与大陆端点。依赖清单 requirements.txt 只有三项anthropic0.25.0、python-dotenv1.0.0、pyyaml6.0。4.5 入口一个支持多轮会话的 REPLREADME 的agent_loop(messages)接收一个消息列表而 code.py 的入口把它包进了一个交互式 REPLif __name__ __main__: print(s01: Agent Loop) print(Enter a question, press Enter to send. Type q to quit.\n) history [] while True: try: query input(\033[36ms01 \033[0m) except (EOFError, KeyboardInterrupt): break if query.strip().lower() in (q, exit, ): break history.append({role: user, content: query}) agent_loop(history) # Print the models final text response response_content history[-1][content] if isinstance(response_content, list): for block in response_content: if getattr(block, type, None) text: print(block.text) print()两个值得注意的点跨轮上下文保持history在 REPL 外定义每条新指令以 user 消息追加进同一个列表后再调用agent_loop(history)。也就是说第二句话依然能看到第一句话的完整工具调用与结果历史——多轮会话能力不是额外开发的它是“消息列表只增不减”这一设计的自然产物最终文本的打印策略agent_loop返回时history[-1]必然是最后一条 assistant 消息因为循环以“追加 assistant 轮后return”结束入口从其中取出text类型的块打印到终端。循环执行期间每条命令会以黄色高亮、其输出前 200 字符会实时打印方便观察“模型何时调用工具、何时停下”。另外文件头部有一段readline配置code.py#L33-L41是为 macOS 上 libedit 的 UTF-8 退格问题做的修复属于纯终端体验优化。五、运行与验证5.1 环境准备安全提示原文强调该代码会执行模型生成的 shell 命令。请在临时测试目录中运行避免影响项目文件。完整的权限控制留到 s03 章节引入。首次运行pip install -r requirements.txt cp .env.example .env # 编辑 .env填入 ANTHROPIC_API_KEY 和 MODEL_ID5.2 启动python s01_agent_loop/code.py进入s01 提示符后原文建议尝试这三条指令来观察循环行为Create a file called hello.py that prints Hello, World!List all Python files in this directoryWhat is the current git branch?观察要点原文也写明了注意模型什么时候调用了工具循环继续什么时候没调用循环结束。前一条会触发echo ... hello.py之类的写入命令第三条会触发git branch——它们会分别让你看到“bash 是唯一工具时读写文件、查仓库状态都要借道 shell”这一事实。5.3 仓库中如何保证这一章代码可用tests/test_chapter_readmes.py 会对所有 17 个章节做三重检查英文/中文/日文三语 README 齐备、语言导航行一致并且用py_compile验证每个章节的code.py均可在 Python 3.11 下编译通过见 test_every_chapter_script_compiles_on_python_311仓库同时保留了一条旧版 12 课轨道其中 agents/s01_agent_loop.py 是本章的遗留可运行副本与新版 s01_agent_loop/code.py 核心逻辑一致同样只有 bash 工具、同样的run_bash黑名单与 120 秒超时由 tests/test_agents_smoke.py 参数化地校验其可编译性。两条轨道的章节编号不完全对应按 README.md 的说明新读者应以根目录s01_agent_loop/到s17_goal_loop/的 17 课轨道为准。六、边界与下一步为什么必须只有 Bash 起步s01 的刻意“残缺”本身就是教学设计此刻模型只有 bash 一件工具——读文件要靠cat写文件要靠echo ... 找文件要靠find。丑且易错但足以验证“一个循环 一个工具 一个 Agent”这个命题。README 的 “Whats Next” 明确指向下一站s02 Tool Uses02_tool_use/给模型 5 个正规工具后会发生什么模型会一次调用多个工具吗并行执行的工具会互相踩踏吗从 s02_tool_use/code.py 可以看到演化的第一步把 s01 中硬编码的run_bash调用替换为TOOL_HANDLERS分派映射bash: run_bash, read_file: run_read, ...循环本体原封不动——这正印证了本章的结论后续所有机制都叠加在循环之上循环本身从不改变s03 Permissions03_permission/s01 的run_bash只有五条子串黑名单属于演示级别防护s03 将引入正式的权限规则与审批管线回答“哪些命令可以直接跑、哪些必须停、哪些需要人工批准”。七、小结s01 给出的结论可以压缩成三句话Agent 的核心是一个while True循环唯一的出口条件是stop_reason ! tool_use停与不停由模型决定Harness 只负责执行与回传消息列表只增不减assistant 轮追加response.content工具结果以tool_result结构携带tool_use_id配对作为 user 消息回传多轮会话能力由此免费获得内核不到 30 行但工程细节决定可用性code.py 中的危险命令拦截、120 秒超时、50000 字符输出截断、.env配置与兼容服务商接入构成了这个内核在真实环境里可运行的底线。这就是整个课程的地基先有一个能转起来的循环再谈工具、权限、规划、记忆与协作。【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考