起因
2026年3月上中旬learn-claude-code这个仓库很火,当时27k Star。当时只有s01~s06,只讲基础循环、工具调用、简单记忆。
2026‑03‑31,Claude Code源码泄露,51.2 万行 TypeScript 源码,完整 CLI 实现。4月中下旬更新了s07~s12,对标泄漏源码的v2.1.88核心流程。
偶尔得知这个仓库,花点时间学习一下,当前59.4K Star。
瑕不掩瑜
暂时还没有看Claude Code源码相关的资料,当前教程里有些东西只是演示原理,真实工程里肯定不会这么干的,比如:
- SDK只用了最初的anthropic,没有用最新的sdk;
- 通信方式是直接阻塞时调用anthropic sdk,生产级产品里底层是WebSocket + HTTPS 混合架构,增加了本地KAIROS守护进程
learn-claude-code教程有更新,把字符串线框图改成了图片,增加了8个小节,还增加了cluade code生产级实现细节,666 —- 2026.05.27补充
大语言模型是怎么学会感知环境、推理目标、采取行动的?
- 预训练:大语言模型的核心训练任务是“预测下一个词”;海量文本中包含感知描述、目标制定、行动计划的例子。模型学会了模式匹配,比如“看到X,应该做Y”的统计关联。涌现推理能力。
- 对齐和指令微调:让模型能听懂指令,遵循格式,学会用文本输出“思考”和“行动”。
- 构建“感知-推理-行动”循环:在应用层面,系统将环境信息(如网页文字、API返回)作为提示的一部分输入给LLM,LLM输出推理和行动指令(如调用搜索工具),系统执行该行动后把新结果再次输入,形成循环。模型通过上下文学习适应这种模式。
代码中为什么没看到从环境中读取ANTHROPIC_API_KEY传给模型?
并非遗漏,而是因为Anthropic的Python SDK 默认会自动从环境变量ANTHROPIC_API_KEY中读取API密钥
为什么每次循环都传完整history给LLM,而不是只传新增内容?
演示脚本中是每次传输全量历史; Claude Code本地CLI工具也是有客户端发送完整历史。
LLM 是无状态的,LLM本身不保存任何对话状态。每次API调用都是独立的,模型只会根据当前messages参数重的内容来生成回复。它不会记住之前的调用。
LLM的本质是一个无状态的函数:text_out = f(text_in)。
典型Web应用(如ChatGPT Web),是服务端存储历史,客户端只发送当前用户输入或会话ID,服务端从数据库/缓存读取历史,拼接后调用LLM,再将更新后的历史保存回去。
为什么现在Agent都采用CLI的方式,而不是GUI的方式?
当然很多都是事后的总结,我想可能最先编程工具是提供给内部开发者或者一些爱好者用的,CLI是很自然的方式。
- 技术哲学的同频共振:Unix哲学提出“一切都是文本流”;如今的大语言模型也恰好“只理解纯文本”。
- 降低复杂性。听有人提过,相对于Cusror,Claude Code的版本优化更新速度是很快的
- …
s01 Agent Loop - 一个循环 + Bash
顺序约束:对话历史中的 role 必须严格交替 user ↔ assistant,且第一条消息角色必须为 user。如果需要连续多轮工具调用,可以在 user 角色下放置多个工具结果块。
Claude Code最好关闭自动更新,0528有更新v2.1.154版本,新版本重构为messages数组内的 {“role”: “system”, “content”: “…”} 格式,三方API(如 DeepSeek)的实现出现报错 400。 —- 2026.05.29补充
代码中有2个循环,主循环和Agent循环;前者是用户驱动,负责多轮对话;后者是模型自动驱动,负责单轮内的工具调用链;职责分离。
- 主循环:负责接收用户输入,管理多轮对话。没有主循环,用户则只能问一个问题,无法进行多轮对话。
- Agent循环:负责处理模型工具调用,直到模型给出最终答案。没有Agent循环,模型一次只能调用一个工具,无法实现多步推理。
主循环:
if __name__ == "__main__":
history = []
while True: # 主循环
query = input(...)
if exit_condition: break
history.append({"role": "user", "content": query})
agent_loop(history) # 进入内层循环,直到模型停止
# 打印最终回复
print(...)
Agent 循环:
def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
# Append assistant turn
messages.append({"role": "assistant", "content": response.content})
# If the model didn't call a tool, we're done
if response.stop_reason != "tool_use":
return
# Execute each tool call, collect results
results = []
for block in response.content:
if block.type == "tool_use":
print(f"\033[33m$ {block.input['command']}\033[0m")
output = run_bash(block.input["command"])
print(output[:200])
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": output})
messages.append({"role": "user", "content": results})
s02 Tool Use — 单个工具到多个工具
s01只有bash一个工具,s02增加了read_file、write_file、edit_file、glob
s01调用工具是硬编码run_bash,现在改为查表后调用
为什么要增加这几个工具,bash不是也能做读、写、编辑文件、匹配文件/目录路径吗?
- bash+这四个工具,可以覆盖大部分的编程任务。
- bash都能做这些事,但是bash太强大了,可能不安全,且与外部环境、编码等强关联,所以用专用工具覆盖高频安全操作,用通用shell处理剩余的长尾需求。
s03 Permission — 判断能不能做
工具执行之前先进行判断权限
s04 Hooks — 工具调用前后留扩展插槽
注册需要时机的Hook,在相应的时机调用Hook