30分钟手写一个 AI Agent:揭秘 Claude Code 的核心循环

9 阅读5分钟

你有没有想过:Claude Code、Cursor、Copilot 这些 AI 编程助手,它们的核心到底是什么?

是不是一大堆复杂的规则引擎?是不是层层嵌套的决策树?是不是精心设计的 prompt chain?

都不是。

它们的内核,只有一个简单的循环。

今天,我用 30 分钟带你手写一个最小化的 AI Agent。读完这篇,你会明白:为什么"智能"不在代码里,而在模型里。


一个让很多人困惑的问题

先问一个问题:当你对 Claude Code 说"帮我重构这个函数"时,是谁在决定先做什么、后做什么?

很多人会以为是代码里的流程控制。但实际上:

所有的决策,都在模型内部发生。

代码只是执行器。模型说"先读文件",代码就读文件。模型说"再改代码",代码就改代码。模型说"完成了",代码就停止。

这不是比喻。这是事实。


用一个类比理解

想象你在玩一个游戏:

玩家(模型)坐在驾驶舱
方向盘、油门、刹车(工具)都在手里
路(代码库)在眼前展开

玩家决定往哪开
车(代码)负责真的开过去

Agent = 模型 + 工具 + 循环

模型是驾驶员,工具是方向盘,循环是发动机。


最小 Agent:只需要 20 行代码

我们用 TypeScript 写一个最小 Agent:

// 核心循环:就这么简单
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 这个字段,是模型告诉代码"我接下来想干什么":

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',
]

function runBash(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-code
cd build-claude-code

# 安装依赖
pnpm install

# 配置 API Key
cp .env.example .env
# 编辑 .env,填入你的 ANTHROPIC_API_KEY

运行

pnpm 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 的核心模式:

模型决策 → 代码执行 → 结果反馈 → 循环继续

接下来,我们会逐步添加:

  • s02:添加更多工具(读文件、写文件、编辑文件)
  • s03:任务规划(TodoWrite)
  • s04:子代理(干净的上下文隔离)
  • s05:技能加载(按需注入知识)
  • ...一直到完整的多代理协作系统

FAQ

Q:为什么不用复杂的流程控制?

A:因为模型的智能已经足够。你不需要用代码去"指导"模型怎么做。模型 trained 了亿万次,它知道。你只需要给它工具和边界。

Q:stop_reason 还有其他值吗?

A:有,比如 max_tokens(超出长度限制)、stop_sequence(遇到预设停止词)。但在 Agent 场景,我们主要关注 tool_useend_turn

Q:能不能用 OpenAI 的 API?

A:完全可以。核心模式是一样的。只是 API 格式略有不同(OpenAI 用 finish_reason,Anthropic 用 stop_reason)。

Q:为什么不把所有工具都加进去?

A:渐进式学习。先理解核心循环,再逐步添加复杂性。你会发现,添加工具从不改变循环本身——只是增加 handler。


小结

今天我们实现了:

✅ 理解了 Agent 的核心是一个循环 ✅ 手写了 20 行代码的最小 Agent ✅ 理解了 stop_reason 的关键作用 ✅ 跑起来了一个能执行 Bash 命令的 Agent

关键洞察

代码不决策,只执行。 模型决策,代码服从。 这就是 Agent 的本质。


下一步

想继续深入?

  1. 阅读 s02:看看如何添加更多工具,而不改变循环
  2. 阅读源码src/core/agent-loop.ts 只有 100 行
  3. 动手改造:尝试添加一个新工具(比如 read_file

项目地址:github.com/opbr/build-…

🚀 写在最后

本文是 《从 0 到 1 构建 Claude Code》 系列专栏的第一篇。我们将持续深度拆解 Agentic Programming 的核心机制。公众号合集

如果你对 LLM 原生开发、TypeScript 架构设计 感兴趣,欢迎关注我的公众号,我们一起在 AI 时代完成技术进化。

AI 老弟公众号
长按二维码关注:小木樱桃

相关阅读