起因

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


参考