你有没有想过:Claude Code、Cursor、Copilot 这些 AI 编程助手,它们的核心到底是什么?
是不是一大堆复杂的规则引擎?是不是层层嵌套的决策树?是不是精心设计的 prompt chain?
都不是。
它们的内核,只有一个简单的循环。
今天,我用 30 分钟带你手写一个最小化的 AI Agent。读完这篇,你会明白:为什么"智能"不在代码里,而在模型里。
一个让很多人困惑的问题
先问一个问题:当你对 Claude Code 说"帮我重构这个函数"时,是谁在决定先做什么、后做什么?
很多人会以为是代码里的流程控制。但实际上:
所有的决策,都在模型内部发生。
代码只是执行器。模型说"先读文件",代码就读文件。模型说"再改代码",代码就改代码。模型说"完成了",代码就停止。
这不是比喻。这是事实。
用一个类比理解
想象你在玩一个游戏:
玩家(模型)坐在驾驶舱方向盘、油门、刹车(工具)都在手里路(代码库)在眼前展开玩家决定往哪开车(代码)负责真的开过去
Agent = 模型 + 工具 + 循环
模型是驾驶员,工具是方向盘,循环是发动机。
最小 Agent:只需要 20 行代码
我们用 TypeScript 写一个最小 Agent:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// 核心循环:就这么简单async function agentLoop(messages: Message[]) {while (true) {// 1. 调用模型const response = await client.messages.create({model: MODEL,system: SYSTEM,messages: messages,tools: TOOLS, // 告诉模型它有什么工具})// 2. 记录模型的回复messages.push({ role: 'assistant', content: response.content })// 3. 关键判断:模型想停止吗?if (response.stop_reason !== 'tool_use') {return // 模型说"够了",退出循环}// 4. 模型想用工具?那就执行const results = await executeTools(response.content)// 5. 把执行结果喂回给模型messages.push({ role: 'user', content: results })// 继续循环,模型会看到结果,然后决定下一步...}}
这就是全部。
没有决策树,没有流程图,没有复杂的状态机。
stop_reason:模型在说话
重点来了。stop_reason 这个字段,是模型告诉代码"我接下来想干什么":
tool_use | ||
end_turn |
这不是代码决定的。是模型决定的。
模型 trained 了无数次,学会了什么时候该用工具、什么时候该停下。代码只是听话。
一个工具就够了
我们给这个 Agent 一个最简单的工具:Bash。
const TOOLS = [{name: 'bash',description: 'Run a shell command.',input_schema: {type: 'object',properties: {command: { type: 'string' }},required: ['command'],},}]
就这一个。模型想列出文件?它会调用 bash 执行 ls。模型想创建文件?它会调用 bash 执行 touch。
一个工具,无限可能。
安全第一
当然,我们不能让模型执行任意命令:
const DANGEROUS_COMMANDS = ['rm -rf /','sudo','shutdown',]functionrunBash(command: string) {// 安全检查for(const dangerous of DANGEROUS_COMMANDS) {if(command.includes(dangerous)) {return 'Error: Dangerous command blocked'}}// 执行命令const result = execSync(command, { cwd: WORKDIR })return result}
这是 Harness Engineering 的核心原则之一:给模型能力,但要设边界。
动手试试
现在我们把这个 Agent 跑起来。
环境准备
# 克隆项目git clone https://github.com/OPBR/build-claude-codecd build-claude-code# 安装依赖pnpm install# 配置 API Keycp .env.example .env# 编辑 .env,填入你的 ANTHROPIC_API_KEY
运行
ounter(linepnpm s01
你会看到:
╔════════════════════════════════════╗║ s01 - Agent Loop ║║ "One loop & Bash is all you need" ║╚════════════════════════════════════╝s01 >> 列出当前目录的文件$ ls -la...执行结果...s01 >> 创建一个 hello.txt,内容是 "Hello World"$ echo "Hello World" > hello.txt...执行结果...
这只是第一步
这个最小 Agent 只有一个工具(Bash)。但它已经展示了 Agent 的核心模式:
ounter(line模型决策 → 代码执行 → 结果反馈 → 循环继续
接下来,我们会逐步添加:
s02:添加更多工具(读文件、写文件、编辑文件) s03:任务规划(TodoWrite) s04:子代理(干净的上下文隔离) s05:技能加载(按需注入知识) ...一直到完整的多代理协作系统
FAQ
Q:为什么不用复杂的流程控制?
A:因为模型的智能已经足够。你不需要用代码去"指导"模型怎么做。模型 trained 了亿万次,它知道。你只需要给它工具和边界。
Q:stop_reason 还有其他值吗?
A:有,比如 max_tokens(超出长度限制)、stop_sequence(遇到预设停止词)。但在 Agent 场景,我们主要关注 tool_use 和 end_turn。
Q:能不能用 OpenAI 的 API?
A:完全可以。核心模式是一样的。只是 API 格式略有不同(OpenAI 用 finish_reason,Anthropic 用 stop_reason)。
Q:为什么不把所有工具都加进去?
A:渐进式学习。先理解核心循环,再逐步添加复杂性。你会发现,添加工具从不改变循环本身——只是增加 handler。
小结
今天我们实现了:
✅ 理解了 Agent 的核心是一个循环 ✅ 手写了 20 行代码的最小 Agent ✅ 理解了 stop_reason 的关键作用 ✅ 跑起来了一个能执行 Bash 命令的 Agent
关键洞察:
代码不决策,只执行。 模型决策,代码服从。 这就是 Agent 的本质。
下一步
想继续深入?
阅读 s02:看看如何添加更多工具,而不改变循环 阅读源码: src/core/agent-loop.ts只有 100 行动手改造:尝试添加一个新工具(比如 read_file)
项目地址:https://github.com/opbr/build-claude-code
Agency comes from the model. The harness makes agency real.
你在构建 Agent 时遇到过什么问题?评论区聊聊!
相关阅读:
Claude Code 官方文档 learn-claude-code - Python 实现
夜雨聆风