苦猿的大模型日记 · Day51 · 从0学习Claude Code(一)Agent Loop-帮普通人把AI学进简历系列
前言:日记开新模块,把 Claude Code 拆开复刻一遍
咱们这个大模型日记写到第 51 篇,RAG 拆完了,推理加速拆完了,Agent 的通用玩法也过了一遍。从这篇起,日记开一个新模块,干票大的:从0学习 Claude Code。
先说清楚这个模块不是讲什么的------不是教你装软件、不是背快捷键、也不是"10 个 prompt 让你效率翻倍"。
它只干一件事:把 Claude Code 拆开,一个机制一个机制,自己动手用 Python 复刻一遍。权限是怎么拦命令的、子代理是怎么隔离上下文的、上下文快满时是怎么压缩的、记忆是怎么跨会话活下来的------每篇拆一个,代码全部自己敲、自己跑、自己踩坑。Day 编号照常往下排,模块内再用(一)(二)(三)接着数。
第一篇,拆它的心脏:Agent Loop。
一个 while True 循环,加一个 bash 工具,142 行 Python。跑起来之后,模型能在你的终端里自己敲命令、自己执行、自己看结果、自己决定什么时候停------Claude Code 所有花活的地基,就是这 142 行。
读完这篇你会拿到三样东西:
- Claude Code 的真实工作原理:模型负责决策,代码负责执行,这套分工为什么成立
- 一份能直接跑的最小内核代码,GLM、DeepSeek、Kimi 的 Key 都能用,不需要 Anthropic 账号
- 一张后续十几个机制的路线图,知道这个模块往后每一步要补什么
门槛:会 Python 基础语法、有一个任意大模型的 API Key。够了,直接开始。

PART 01:先破神话------Claude Code 的心脏,是一个你早就会写的循环
在动手之前,先把一个流传很广的误解拆掉。
很多人觉得 Claude Code 强,是因为它代码量大、工程复杂------背后肯定是一套精密的调度系统,替模型规划好每一步。方向反了。
Claude Code 的架构拆到最底下,就两层:
Claude Code = 模型 + Harness。
模型是大脑,这个你没得换、也不用你操心。Harness 是载具------你自己写的那部分代码,它只干五件事:
- 给模型工具(能执行命令、能读写文件)
- 给模型知识(系统提示、项目文档)
- 把工具执行结果喂回给模型
- 提供行动接口(终端、编辑器)
- 守住权限边界(哪些命令不许跑)
注意这五件事里没有一件是"替模型做决定"。
Claude Code 从头到尾不决定"下一步干什么"。模型说跑 ls,它就跑 ls;模型说任务完成了,它就停。决策权 100% 在模型,执行权 100% 在代码------这就是它能保持简单的全部秘密。
Agency 不在你的编排代码里
理解了上面这点,你就理解了为什么市面上大量"Agent 平台"是伪 Agent。
不少团队做 Agent 的路径是这样的:打开一个画布,拖几个节点,串几段提示词,A 节点的输出接 B 节点的输入,再写一堆 if-else 兜底。做完发现模型经常不按流程走,于是加更多节点、更多判断------越修越复杂,越复杂越笨。
问题出在认知上:感知、推理、行动的能力(agency),是预训练阶段就写进模型权重里的,不是你的编排代码写出来的。
你画的那个流程图,本质上是把一个会自己走路的模型,绑在一辆固定的推车上,按你画好的路线推。流程图画得越精巧,模型的自主性死得越彻底。
所以从零复刻 Claude Code 的正确姿势是反过来的:不训练模型,只造 Harness。把"下一步干什么"的决策完整地让给模型,你只负责把它的每个决定执行掉、把结果递回去。
Claude Code 被几百万人每天真实使用、被各种复杂项目锤炼,是这套思路目前最好的工程范本。把它拆明白,你写任何领域的 Agent------不限于写代码------都知道骨架该怎么搭。
这也是简历视角下最值钱的部分。面试里"你用过 Agent 框架吗"已经烂大街,"我从零复刻过 Claude Code 的整套机制,权限、子代理、上下文压缩都是自己写的",这句话的含金量完全不一样。

PART 02:痛点还原------模型会"说"不会"做",你一直在当人肉循环
现在看一个你几乎一定经历过的场景。
你在对话框里问模型:"帮我看看这个目录下有哪些 Python 文件,然后跑一下 main.py。"
模型秒回一条漂亮的命令:
ls *.py && python main.py
然后,停了。
它不会自己跑这条命令,也看不见命令的输出。你只能:复制命令 → 切到终端 → 回车 → 看到报错 → 复制报错 → 切回对话框 → 粘贴 → 模型给你改好的命令 → 你再复制回终端......
每一个来回,你都在充当模型和真实世界之间的"中间层"。
这个过程有个准确的叫法:你在当人肉循环。模型负责"想",你负责"做"和"把做的结果念给它听"。
而且这不只是累的问题。手工搬运是有损耗的:
- 来回切窗口五六次之后,你会开始偷懒------输出太长就不贴全了,只截一段;或者看到命令小错,自己顺手改了再跑
- 模型拿到的信息一旦不完整,它的判断链就断了
- 最要命的是速度:一个本来 30 秒能自己完成的任务,人肉循环要 5 分钟
Agent 这个词去掉所有包装,最小定义就一句话:把"你"从这个循环里抠掉。
具体要自动化的是三步:
- 模型说"我要跑这条命令"------而且必须是结构化信号,不是让你肉眼去解析它输出的文本
- 代码替你执行这条命令
- 执行结果原样塞回对话,让模型接着推理
这三步首尾相接、转起来的那一刻,就是 Agent Loop。下一部分,我们把它写出来。

PART 03:动手------142 行,四块代码拼出心脏
整个内核 142 行,拆成四块:工具定义、执行器、主循环、命令行入口。一块一块写。
准备工作(5 分钟)
新建一个目录,两个依赖就够:
pip install anthropic python-dotenv
然后放一个 .env 文件,三个变量:
ANTHROPIC_API_KEY=你的Key
MODEL_ID=glm-4.7
# 可选:如果你想用 GLM / DeepSeek / Kimi 的 Anthropic 兼容接口
ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic
这里有个对普通人很友好的细节:anthropic 这个 SDK 只是一种消息格式 ,跟用谁家模型无关。现在主流国产模型都提供 Anthropic 兼容接口,把 ANTHROPIC_BASE_URL 指过去、MODEL_ID 换成对应型号,就能直接跑。不需要非有 Anthropic 账号。
代码里读配置就三行:
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv(override=True)
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
第一块:工具定义------只给一个 bash
先定系统提示,一句话:
SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."
翻译过来:你是一个在我这个目录下干活的编码代理,用 bash 解决问题,行动,别解释。
别小看"Act, don't explain"这五个词,它是整个产品性格的起点------少废话,直接干。Claude Code 那种"闷头干活"的气质,源头就在系统提示里。
然后是工具定义,全文唯一的工具:
TOOLS = [{
"name": "bash",
"description": "Run a shell command.",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
}]
一个工具就是三样东西:名字 + 描述 + 参数 schema 。模型只看得见 description,它靠这一句"Run a shell command"判断什么时候用、怎么用。
就这么点配置,模型就知道自己手里有一把锤子了。
第二块:run_bash 执行器------带三条最小安全带
模型说要跑命令,总得有人真去跑。执行器长这样:
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, shell=True, cwd=os.getcwd(),
capture_output=True, text=True, errors="replace",
timeout=120)
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 f"Error: {e}"
逻辑很简单:收到命令 → 查黑名单 → 用 subprocess 真实执行 → 把 stdout 和 stderr 拼起来返回。
但里面有三条安全带,每条都值得停下来想一想:
安全带一:黑名单。 rm -rf /、sudo、shutdown、reboot,四条会直接掀桌子的命令,见到就拦。坦白讲这个名单极其粗糙------rm -rf ~/你的毕业设计 它就拦不住。真正像样的权限系统(审批、白名单、分级放行)是后面的篇章要补的机制,现在先裸奔,所以请一定在临时测试目录里玩。
安全带二:120 秒超时。 模型偶尔会生成挂住的命令(比如起了个交互式程序、或者网络请求卡死)。没有超时,你的整个 Agent 就跟着一起死。
安全带三:输出截断到 50000 字符。 这条最有味道。记住一个事实:工具的输出会整段进入上下文 。模型随手跑一条 find /,输出可能几十万字符,一条命令就能把上下文撑爆,后面的对话全部崩掉。截断是最粗暴但最有效的保险------至于"怎么聪明地压缩",正是后面"上下文压缩"那篇要解决的问题。
这个内核里的每一条防御性代码,背后都对应着后续的一个完整机制。现在是创可贴,以后是器官。
第三块:主循环 agent_loop------全文最重要的一段
来了,心脏的心脏。先看完整代码,再逐机制拆:
def agent_loop(messages: list):
while True:
# 1. 把整本账本发给模型
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
# 2. 模型的回复记进账本
messages.append({"role": "assistant", "content": response.content})
# 3. 这轮有没有调工具?没有 → 任务完成,退出
tool_calls = [
block for block in response.content if block.type == "tool_use"
]
if not tool_calls:
return
# 4. 有 → 逐个执行,收集结果
results = []
for block in tool_calls:
print(f"$ {block.input['command']}")
output = run_bash(block.input["command"])
print(output[:200])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
# 5. 结果塞回账本,回到第 1 步
messages.append({"role": "user", "content": results})
三十多行,一个 coding agent 的内核就没了。但这段代码里有三个机制,看懂它们,才算看懂 Agent。
机制一:messages 是一本"只追加"的账本
注意第 2 步和第 5 步:模型的回复 append 进去,工具的结果 append 进去,从头到尾只有 append,没有修改,没有删除。
messages 这本账本,就是模型的全部记忆。它下一轮做的所有判断------"我刚才干了什么"、"报错是什么"、"接下来该干嘛"------依据全部来自这本账本的历史记录。
这也解释了 Agent 为什么有"上下文越长越聪明、也越贵"的特性:账本越厚,模型知道得越多,但每轮要读的字也越多。记忆、上下文压缩、子代理隔离,往后所有机制玩的都是这本账本。
机制二:tool_use_id 是回执单号
第 4 步里,每个结果都带着 tool_use_id: block.id。为什么?
因为模型一轮可以同时申请跑好几条命令。它发出的每个 tool_use 块都带一个唯一 id,就像银行转账的申请单号;你执行完,结果必须带着同一个单号塞回去,API 靠这个 id 配对"哪张回执对应哪笔申请"。
id 对不上,API 直接报错。这是所有人第一次写 Agent 必踩的坑:辛辛苦苦执行完工具,结果塞回去的时候 id 忘了带或者带错了,前功尽弃。记住:tool_result 必须和 tool_use 一一配对。
机制三:循环什么时候停,代码说了不算,模型说了算
看第 3 步那个 if not tool_calls: return。
整个循环没有计数器、没有定时器、没有"最多跑 10 步"的设置。唯一的退出条件:模型这一轮没有申请任何工具。
模型不再要工具 = 模型认为任务做完了 = 循环结束。
这个设计初看朴素,细想是整个架构里最大胆的一笔。传统软件的流程控制权在程序员手里,而这里,"任务什么时候算完成"的定义权交给了模型。Agent 的自由度和边界感,全都浓缩在这一个 if 里。
第四块:命令行入口(10 行,带过)
最后补个壳,让它能在终端里交互:
if __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("agent >> ")
except (EOFError, KeyboardInterrupt):
break
if query.strip().lower() in ("q", "exit", ""):
break
history.append({"role": "user", "content": query})
agent_loop(history)
# 打印模型的最终文字回复
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
print()
注意 history 这个列表在主循环外面------你的每次提问都追加进同一本账本,所以它记得你上一句说了什么,多轮对话是免费自带的。
四块拼完,142 行。保存成 code.py。

PART 04:跑起来------亲眼看着它自己干活、自己修错
光看代码没感觉,跑起来才知道这 142 行有多上头。
python code.py
出现 agent >> 提示符,开测。
实录一:最小任务,看循环转三圈
输入:
帮我在当前目录创建一个 hello.py,内容是打印 Hello, World!,并运行验证
终端上你会看到模型自己开始干活(黄色是它申请的命令):
$ echo 'print("Hello, World!")' > hello.py
$ python hello.py
Hello, World!
Done. hello.py created and verified.
拆一下账本里发生了什么:
- 第 1 圈 :模型申请
echo ... > hello.py写文件 → 结果"(no output)"回流 - 第 2 圈 :模型申请
python hello.py验证 → 结果"Hello, World!"回流 - 第 3 圈:模型没有再申请任何工具 → 循环退出,打印最终回复
注意一个细节:你只说了"创建",它自己决定多跑一步验证。没有人教它,流程图里也没有这个节点------这就是 agency 从模型权重里自己长出来的样子。
实录二:名场面------跑挂了,它自己修
来个有难度的。输入:
写一个 divide.py,计算 10 除以 0 并打印结果,然后运行它
模型会老老实实写文件、运行,然后:
$ python divide.py
Traceback (most recent call last):
File "divide.py", line 1, in <module>
print(10 / 0)
ZeroDivisionError: division by zero
红色的 Traceback 刷出来。接下来发生的事,是这个内核最接近"生命"的一刻------它没有停下来问你怎么办,而是:
$ cat divide.py
$ echo 'try:
print(10 / 0)
except ZeroDivisionError:
print("不能除以零")' > divide.py
$ python divide.py
不能除以零
Fixed: added exception handling.
看明白发生了什么吗?
Traceback 不是给你看的,是给模型看的。
stderr 被原样塞进了 tool_result,下一圈循环,模型读到自己的报错,自己定位、自己改、自己再跑。错误信息就是它的眼睛------这就是"观察-行动闭环":观察(读报错)→ 行动(改代码)→ 再观察(看新结果),直到满意为止。
你平时用 Claude Code 时它"改 bug 改到通为止"的体感,底层就是这个循环在转。
实录三:反例------它不是什么都要动手
最后问个不用动手的:
1+1 等于几?
模型直接回复"2",从头到尾一个工具都没调,循环转一圈就退出。
呼应机制三:调不调工具、什么时候停,全在模型的一念之间。该动手时动手,不该动手时闭嘴直接答------这个分寸感,同样来自模型,不来自你的代码。
一句严肃的安全提醒
再次强调:这段代码会真实执行模型生成的 shell 命令。黑名单只挡得住四条最狠的。请在一个专门的临时目录里玩,别对着有生产代码、有重要文件的目录跑。像样的权限门禁,是后面要单独拆的机制。

PART 05:为什么只给一个 bash?------"One loop & Bash is all you need"
代码跑通了,留一个最值得琢磨的问题收尾。
你的直觉可能是反对的
看到工具定义那段,你八成想过:就给一个 bash?为什么不给它 20 个精细工具------read_file、write_file、search_code、run_python、list_dir......每个工具参数结构化、职责单一,听起来工程上漂亮多了。
我之前自己写过一个小 Agent,就是这么干的:挂了 7 个精细工具,查订单、改地址、查物流,每个都单独定义。运行得也挺好。
所以第一次看清 Claude Code 的内核只给一个 bash 时,我是愣了一下的。想明白之后,服了。
bash 不是一把刀,是一台机床
想给它 20 个工具,先算一笔账:
- 读文件,bash 里是
cat - 写文件,是
echo ... > file - 搜代码,是
grep -rn - 找文件,是
find - 跑脚本,是
python xxx.py - 组合操作,是管道
|、重定向>、链式&&
一个 bash,吞掉了你打算手写的整个工具箱。
而且 shell 的能力空间是组合爆炸的------你永远枚举不完用户会遇到的操作,但任意操作几乎都能用几条 shell 命令现凑出来。专用工具是 20 把固定的刀,bash 是一台能现加工任何刀具的机床。
换来两样更值钱的东西:
一,模型的选择负担最小。 工具列表越短,模型"该用哪个"的决策越干净,选错工具的概率越低。给它 20 个长得差不多的工具,它会在 read_file 和 search_code 之间犹豫,然后选错。
二,能力上限是整个 shell 生态。 精细工具的能力上限是你写工具那天的想象力,bash 的上限是几十年积累的全部命令行工具。
当然这不是说专用工具没价值------参数结构化、好审计、行为可控,这些是 bash 给不了的。Claude Code 的选择是:底座用 bash 换最大灵活性,再靠一层层机制把"可控"补回来。 你后面会看到权限系统怎么补安全、专门的读写工具怎么补精度。
先有自由,再上枷锁,顺序不能反。
它现在还很糙------这份"糙"就是后面的目录
最后诚实地列一下这个 142 行内核的缺陷,每个缺陷都对应往后的一篇:
- 黑名单挡不住
rm -rf ~/重要目录→ 需要权限门禁:工具执行前先过审批 - 一条
find /的输出能撑爆上下文,聊久了账本无限变厚 → 需要上下文压缩 - 长任务会走神,干着干着忘了最初的目标 → 需要 Todo 计划机制
- 所有中间过程全记在一本账上,查个资料把主上下文弄脏 → 需要子代理隔离
- 一次只能干一件事,慢命令一跑全卡住 → 需要后台任务
- 会话关了就全忘 → 需要跨会话记忆
这个模块的路线就此清晰了:循环从第一天写完就再也不变,变的是外面一圈圈长出来的壳------权限、工具分发、Hooks、计划、子代理、上下文压缩、记忆、任务系统、后台任务、定时调度、团队协作、插件、集成、编排、目标闭环。每一篇拆一个器官,拆完你就拥有一个自己完全理解的 Claude Code。
给你的学习建议
别只看。把这 142 行亲手敲一遍(不要复制粘贴),跑通上面三个实录。敲的过程中你大概率会在 tool_use_id 配对上报错一次------那一次报错,比看十篇文章都管用。

结尾:把方向盘交出去
回顾一下这篇干了什么:从一个"模型会说不会做"的日常痛点出发,用 142 行 Python 写出了 Claude Code 的心脏------一个 while 循环、一个 bash 工具、一本只追加的账本。
从"人肉循环"到"自动循环",差的从来不是代码量,是你敢不敢把方向盘交出去:决策让给模型,执行留给自己,结果如实回流。
Agent 的强大从来不写在你的编排代码里,它写在模型的权重里。你能做的最好的事,是给它一双手,然后让开。
互动时间 :作业来了------把这篇的 142 行跑通,截一张"模型自己在终端里敲命令"的图发到评论区。另外留个问题聊聊:如果是你来迭代这个内核,会先补权限门禁 ,还是先补上下文压缩?为什么?
下一篇预告:「从0学习 Claude Code」第二篇------工具分发机制 。只有一个 bash 时,模型用
echo写文件其实经常翻车(引号嵌套、多行内容全是坑)。给它 read / write / edit / glob 几个真正的工具之后:模型会不会一轮同时调好几个?同时调的工具会不会互相踩?工具的 description 到底要怎么写,模型才会"懂事"?下一篇见。
--- END ---
苦猿 · 帮普通人把 AI 学进简历