最近群里在讨论 Claude Code,问得最多的不是“这东西怎么装”,而是“它的源码到底怎么读”。网上教程大多停留在怎么安装、怎么让它写代码,一旦你想搞清楚它为什么能自主调用工具、为什么每执行一步都要向你确认、为什么上下文快满时会提示压缩,你就绕不开一个概念:Agent Harness。
这篇文章不打算贴一堆“看完更懵的源码片段”,而是把 Claude Code 当成一个典型的 Agent Harness 来拆。先讲清楚 agent 和 harness 的区别,再沿着“入口 → 主循环 → 工具层 → 权限层 → 上下文管理 → 会话持久化”这条主线,给出源码阅读路线;最后补上安装部署、功能测试、批量调用和常见问题排查。读完你不仅能上手用,也能把同一套分析视角迁移到 Codex、Cursor 这类终端编程智能体上。
1. 核心能力速览
Claude Code 是 Anthropic 推出的终端 AI 编程智能体,也是目前最值得当样板研究的一个 Agent Harness 实现。它跑在终端里,模型本身在云端推理,本机只需要一个轻量 CLI。
| 能力项 | 说明 |
|---|---|
| 项目定位 | 终端 AI 编程智能体,可自主读写文件、执行命令、运行测试 |
| 开发方 | Anthropic |
| 核心功能 | 文件读写编辑、Bash 命令执行、代码搜索、跨文件修改、子 Agent、MCP 工具接入 |
| 运行平台 | macOS、Linux、Windows(原生或 WSL) |
| 硬件门槛 | 常规模式不需要独立显卡;本地部署兼容推理服务时才有显存需求 |
| 启动方式 | CLI、VSCode 插件、桌面版 |
| 交互能力 | 交互式会话、非交互 Headless 模式、会话恢复 |
| 接口能力 | claude -p打印模式、官方 Agent SDK、环境变量路由兼容端点 |
| 批量任务 | 可通过脚本循环调用 Headless 模式批量执行 |
| 典型场景 | 代码补全、Bug 修复、跨文件重构、测试补充、文档生成 |
从这张表能看出两件事。第一,Claude Code 的硬门槛非常低,不依赖 GPU,普通开发机都能跑;第二,它不是一个“聊天玩具”,它天生设计成可以被脚本和接口驱动,这正是 harness 该有的样子。后面所有内容都会围绕“Agent 循环”和“Harness 外壳”这两个关键词展开。
2. Agent 与 Harness:先把概念理清楚
网上关于“harness 和 agent 区别”的讨论非常多,但大部分解释都停留在比喻层面。这里直接给一个能落地的定义。
Agent(智能体)指的是模型本身加上“能自主决策下一步做什么”的能力。它看到用户需求后,不是一次性吐出答案,而是反复思考:我要不要先读文件?要不要执行命令?要不要改代码?每次行动之后,把观察结果拿回来继续推理,直到任务完成。这个“思考 → 行动 → 观察 → 再思考”的循环,是 agent 的灵魂。
Harness(框架 / 外壳)指的是把模型变成“能干活”的那一层工程外壳。模型本质上只是一个神经网络,它自己不能操作文件系统、不能执行终端命令、不能感知代码仓库。是 harness 给它提供了手脚,一般至少包含五个部分:
- 工具注册表:模型可以调用哪些工具,每个工具的参数 schema 是什么。
- 主循环:一轮轮调用模型,解析工具调用结果,追加到上下文。
- 权限闸门:工具会修改文件、执行命令,所以必须要有 allow / deny 策略。
- 上下文管理:把对话历史、文件内容、工具输出塞进有限的上下文窗口,必要时压缩。
- 会话持久化:把整个任务的过程保存下来,支持断点续跑和事后审计。
OpenAI 在 Codex 的官方博客里有一段很有名的表述,说 Agent Harness 就是“model 和 world 之间的那一层”。你可以把两个不同的模型塞进同一个 harness,它们能完成差不多的任务;同一个模型换掉 harness,能力表现可能天差地别。Claude Code 就是 Anthropic 为 Claude 系列模型专门打造的那层 harness。
把概念映射到实际操作上,你会发现这些事情全都对应 Claude Code 的可见功能。工具调用对应它能在你的终端里执行命令;权限闸门对应每次执行危险操作前的确认;上下文管理对应它偶尔告诉你“上下文快满了”;会话持久化对应/resume恢复上一次任务。读源码时,顺着这些可见功能去找对应实现,比逐行通读高效得多。
3. Claude Code 的 Agent Harness 架构拆解
3.1 主循环是核心
一个 Agent Harness 最核心的骨架是主循环。Claude Code 交互式运行时的行为,可以简化成下面这个伪代码。把这段代码看懂,后面所有源码阅读都有方向了。
def agent_loop(user_request: str): messages = build_initial_messages(user_request) while True: # 1. 调用模型,带上工具定义和完整对话历史 response = model_complete(messages, tools=TOOL_SCHEMAS) # 2. 模型可能返回文本,也可能返回一组工具调用 if response.has_tool_calls(): for call in response.tool_calls: result = execute_tool(call.name, call.arguments) messages.append(tool_call_message(call)) messages.append(tool_result_message(result)) else: # 3. 没有工具调用,说明任务完成,输出最终文本 emit(response.text) break这个循环体现了三个关键点。第一,模型每次输出可能是普通文本,也可能是一组工具调用,harness 负责解析并执行;第二,工具执行结果会被追加回消息列表,成为模型下一轮推理的输入;第三,循环不会无限跑下去,模型必须在某次输出中给出最终文本,或者被用户中断、被步数上限打断。Claude Code 源码里真正复杂的部分,都是在这个主循环之上扩展出来的:权限、压缩、会话、错误恢复。
3.2 工具层:模型的手脚
Claude Code 里最常见的工具包括:读写文件、编辑文件、执行 Bash 命令、搜索文件、搜索代码、查看目录结构、维护待办列表,以及创建子 Agent 来拆分任务。每一个工具在设计上都要包含三样东西:
name:机器可读的工具名,比如Read、Bash。description:给模型看的自然语言说明,什么时候该用、怎么用。input_schema:参数结构,比如要读哪个文件、要执行哪条命令。
源码阅读时最容易踩的坑,是盯着模型调用层不放,却忽略了工具层。其实真正决定一个 agent 上限的往往是工具层。工具的 description 写得清不清楚、参数校验规不规范,直接决定模型会不会误用工具。你发一条命令给 Claude Code,发现它做了错误的工具调用,很多时候不是模型不够聪明,而是工具 schema 没有给出足够的约束。
3.3 权限与安全层
Claude Code 默认模式下,读操作一般直接执行,写文件、执行命令这类有副作用的操作会要求用户确认。它提供几种权限模式,常用的包括:
- 默认模式:每个危险操作都要确认。
acceptEdits:自动接受文件编辑类操作。bypassPermissions:跳过所有确认,适合无人值守的批处理。
此外还能用配置文件预设 allow / deny 规则,把某些命令直接放行或直接禁止。从源码视角看,权限层就是夹在主循环和工具执行之间的一个拦截器:
def execute_tool(name, arguments, permission_policy): if not permission_policy.allow(name, arguments): decision = ask_user(f"允许执行 {name} 吗?") if decision != "allow": return "TOOL_CALL_DENIED" return run_tool(name, arguments)这一层在整个 harness 里价值很高。没有权限闸门的 agent 看起来跑得更快,但它随时可能删掉你的文件、执行危险命令。Claude Code 在体验上“每一步都要确认”会让新手觉得麻烦,这恰恰是它作为终端级 harness 的安全底线。
3.4 上下文管理层
模型有上下文窗口限制,而一次稍微复杂的编程任务会产生大量对话历史、文件内容和工具输出。harness 需要做几件事:把系统提示词、项目规则、用户需求、历史对话组装成一次请求;当历史过长时做压缩,把前面的对话概括成摘要后继续;控制单次工具输出的长度,避免一个超长命令结果把窗口塞爆。
从用户视角能看到的现象是:任务执行到一半,Claude Code 提示“上下文即将用完,是否压缩”,然后继续干活。读源码时,找到这个压缩点和它触发压缩的条件,基本就理解了上下文管理的设计思路。这也是区分“普通聊天应用”和“正经 agent harness”的关键分界点。
3.5 会话与状态层
Claude Code 会把会话记录按 JSONL 形式保存到本地配置目录,支持/resume列出历史会话并恢复。这对工程应用来说有两个直接价值:批量任务的任务现场不会因为中断全部丢失;外部脚本可以通过读取会话记录做审计和复盘。会话层虽然不起眼,但它是把 agent 从一个“临时对话”变成“可持续运行的工具”的基础设施。
4. 源码阅读路线:从入口到主循环
既然要“手撕源码”,就得有一套阅读路线,而不是打开仓库随机乱翻。Claude Code 的实际实现包含大量细节,但你可以先定位四个问题:入口、主循环、工具注册、权限判断。找到这四个问题的答案,整个 harness 的地图就出来了。
- 第一步,找入口。CLI 工具一定会有一个 main 函数或启动脚本,负责解析命令行参数、读取配置、初始化日志和会话。参数解析是最好读懂的起点,能帮你快速看到支持哪些 flag、哪些环境变量。
- 第二步,找主循环。交互式 agent 一定有一个循环负责“调模型 → 取结果 → 执行工具 → 回填结果”。在代码里搜索类似
agent_loop、run_loop、tool_call这样的命名,一般几分钟就能定位。 - 第三步,找工具注册表。所有工具都要在一个地方登记,包括工具名、描述、参数 schema、执行函数。搜索工具名(比如
Bash、Read、Edit)可以反向找到注册表。 - 第四步,找权限判断。在工具执行前通常有一段权限检查代码。搜索
permission、allow、deny关键字,能在很短时间内定位到安全层的实现。
读的时候不要按文件顺序通读,而是按执行链路走:用户输入 → 模型请求 → 工具调用 → 工具结果 → 下一次模型请求。这条链路每经过一个模块,就停下来记录这个模块的职责。最后你得到的不是“代码抄写笔记”,而是一张架构图。
对于 Claude Code 这种大型项目,建议先读官方文档确认三件事:CLI 命令列表、环境变量列表、配置文件格式,然后用运行时的可观察行为去反查源码位置。比如你在交互模式里点了“允许执行”,接下来权限层必然会被触发,直接在源码里打断点或加日志,很快就能把整条链路串起来。
5. 安装部署与启动方式
5.1 安装
Claude Code 常见的安装方式有两种:npm 全局安装和官方原生安装脚本。
# 方式一:npm 全局安装 npm install -g @anthropic-ai/claude-code # 方式二:官方原生安装脚本 curl -fsSL https://claude.ai/install.sh | bash安装完成后验证版本:
claude --version5.2 登录与启动
直接在终端执行claude,首次启动会进入登录流程。也可以设置环境变量ANTHROPIC_API_KEY跳过交互式登录:
export ANTHROPIC_API_KEY="你的 API Key" claude启动后进入交互式终端,输入需求即可开始对话。常用斜杠命令包括/resume恢复历史会话、/compact手动压缩上下文、/permissions查看权限状态,具体以会话内/help输出为准。
5.3 VSCode 集成
如果希望直接在编辑器里用,可以安装官方 Claude Code 插件,安装后即可在 VSCode 中调用。遇到集成异常,运行claude /doctor做诊断,它会检查登录状态、配置和运行环境。
5.4 非交互模式与命令行参数
# 打印模式,适合脚本调用,执行完直接退出 claude -p "总结当前目录的 README.md" # 指定输出格式为 JSON claude -p "把上面的功能整理成清单" --output-format json-p是 Claude Code 对外暴露的非交互接口,也是它区别于“聊天玩具”的重要特性。后面的批量任务和接口式调用都依赖这个模式。
5.5 本地部署与其他模型路由
Claude Code 默认连 Anthropic 官方模型服务,需要网络和 API Key。社区里常见做法是通过环境变量把请求路由到其他兼容端点,例如:
export ANTHROPIC_BASE_URL="http://127.0.0.1:8000" export ANTHROPIC_MODEL="your-model-name" claude需要明确的是,这段是社区实践,不是官方承诺的功能。兼容端点必须实现 Anthropic 消息格式,否则会出现模型名不被识别、请求格式不兼容等一堆问题。同时,本地部署如果接到自建推理服务,硬件门槛就完全不一样了,显存需求取决于你接的是哪套模型,不要再套用“Claude Code 不吃显卡”的结论。
6. 功能测试与效果验证
装好之后,建议按下面的顺序做一轮功能验证。每个测试都有明确的输入、操作、预期结果和判断标准。
6.1 最小可用链路测试
claude --version claude -p "用一句话介绍你自己"预期结果:版本号正常输出;-p模式能在几秒内返回文本。如果这里的调用失败,优先排查登录状态、API Key 和网络连接。
6.2 文件读写与代码生成测试
mkdir -p /tmp/claude-test && cd /tmp/claude-test claude -p "在当前目录创建 calculator.py,实现加减乘除四个函数,并写一个简单的 main 演示。"预期结果:目录下出现calculator.py,内容完整。判断成功的标准是:文件存在、函数定义正确、没有明显语法错误。这一步能验证工具层的文件写入链路是否正常。
6.3 命令执行与权限确认测试
继续在同一个目录下执行:
claude -p "运行 python3 calculator.py,检查输出是否正确。"预期结果:Claude Code 尝试执行 Bash 命令,交互模式下会弹出权限确认。如果选择允许,模型会拿到命令输出,然后给出结论。这一步重点验证的是权限闸门是否生效,以及工具结果是否能正确回填给模型。
6.4 跨文件重构测试
claude -p "把 calculator.py 的加减乘除函数拆到单独文件 operations.py,并在 calculator.py 中 import 使用。"预期结果:新增operations.py,calculator.py被改写,两个文件能协同运行。判断标准:执行python3 calculator.py后功能不变。这一步能验证模型在多个工具调用之间的连贯性,也是衡量 harness 稳定性的重要指标。
6.5 会话恢复测试
在一个交互式会话中让 Claude Code 执行一个较长任务,中途用/resume退出再恢复,观察它是否还记得之前的任务状态。预期结果是会话能恢复到中断位置,继续完成剩余工作。如果恢复后上下文缺失,需要检查本地会话文件的写入权限。
| 测试项 | 输入 | 预期结果 | 判断标准 |
|---|---|---|---|
| 基础问答 | claude -p | 正常返回文本 | 输出非空、无报错 |
| 文件生成 | 创建脚本 | 文件生成 | 文件存在且内容正确 |
| 命令执行 | 运行脚本 | 输出结果 | 命令执行成功、结果回填 |
| 跨文件重构 | 拆分模块 | 多个文件协同 | 功能不变 |
| 会话恢复 | /resume | 上下文保留 | 能继续完成任务 |
7. 接口 API 与批量任务
7.1 Headless 模式就是接口
Claude Code 的-p模式可以理解为一个“一次任务一张请求”的接口。它没有常驻 HTTP 服务,但完全可以通过脚本驱动,把所有需要自动化处理的任务变成可编排的流水线。如果你的诉求是批量修改多个仓库、批量生成文档、批量跑代码检查,这个模式足够用。
7.2 Bash 批量调用示例
for repo in repo-a repo-b repo-c; do cd "/path/to/$repo" echo "=== 处理 $repo ===" claude -p "检查当前项目的 Python 文件,列出明显的代码质量问题。" --output-format json done这个例子能直接跑,但只适合少量目录。真正的批处理建议用 Python 或 Node 脚本管理,方便加日志、超时和重试。
7.3 Python 批量调用示例
import subprocess import time tasks = [ "为 project.py 补充 docstring", "修复 project.py 中可能的空指针问题", "为 project.py 编写单元测试", ] for i, task in enumerate(tasks, 1): print(f"[{i}/{len(tasks)}] 执行任务: {task}") try: result = subprocess.run( ["claude", "-p", task, "--output-format", "json"], capture_output=True, text=True, timeout=600, cwd="/path/to/your/project", ) print("退出码:", result.returncode) print("输出摘要:", result.stdout[:500]) except subprocess.TimeoutExpired: print("任务超时,跳过") time.sleep(2) # 控制请求频率,降低限流概率这里有几个工程要点。第一,timeout必须设置,避免单个任务把整个队列卡死;第二,每个任务最好都能独立执行,任务之间不要有隐藏的先后依赖;第三,输出要做截断和落盘,方便事后审计。
7.4 批量任务的工程建议
批量跑 Claude Code 时,最怕的不是模型答错,而是三点:限流、超时、上下文污染。
- 限流:大量并发请求容易触发 529 或 429 错误,脚本里要加退避重试。
- 超时:长任务可能跑很久,必须设置单任务超时,超时后进入重试队列。
- 上下文污染:如果一个会话里连续塞入多个不相关任务,模型容易混淆,批量任务尽量一条
-p对应一个独立任务。
如果你的批量任务需要更精细的程序化控制,可以参考 Anthropic 官方 Agent SDK,它把 harness 封装成可编程接口,支持更细粒度的工具定义和事件回调。具体 API 以官方文档为准,这里不展开写死。
8. 资源占用与性能观察
Claude Code 的常规使用方式不加载本地模型,所以本机 CPU 和内存占用都很低,也没有显存压力。真正需要关注的是“Token 消耗”和“请求延迟”,这是它区别于本地模型工具的地方。
可以观察的几个维度:
- 进程状态:用
ps aux | grep claude或系统监视器看 CLI 进程的内存占用,一般很小。 - 网络请求:观察 Claude Code 进程是否有持续的 HTTPS 请求,这对应模型推理的远端调用。
- 上下文消耗:会话内可以通过
/cost或类似命令查看 token 消耗;长任务中上下文会增长,快满时会触发压缩提示。 - 任务耗时:同样一个任务,仓库越大、工具调用次数越多,耗时越长。性能瓶颈通常不在本机,而在模型推理延迟和工具执行往返次数。
如果你把 Claude Code 路由到本地兼容推理服务,情况就完全不同了。本地模型需要 GPU 推理,显存占用取决于模型尺寸和并发数,这时才需要像优化传统推理服务那样去观察显存、吞吐和排队延迟。所以,别把“Claude Code 不吃显卡”这句话泛化到所有部署方式上。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 返回 529 错误 | 服务端过载或限流 | 观察错误码和请求频率 | 退避重试、降低并发、换个时间段 |
| 模型名不被识别 | 兼容端点的模型名与工具不匹配 | 检查ANTHROPIC_MODEL环境变量 | 换成端点支持的模型名 |
| 登录失败或 401 | API Key 失效或过期 | 检查环境变量和登录状态 | 重新登录或更新 API Key |
| npm 安装失败 | Node 版本过低或权限不足 | node -v、查看 npm 日志 | 升级 Node、检查全局安装权限 |
| VSCode 插件连不上 | 网络或插件配置问题 | 运行claude /doctor | 按诊断结果修复环境 |
| 权限确认过于频繁 | 默认权限模式偏保守 | 查看/permissions | 在配置中增加 allow 规则 |
| 批量任务卡住 | 单任务超时或被限流 | 查看脚本日志 | 拆小任务、加超时和重试 |
| 上下文很快变满 | 任务复杂、历史过长 | 手动/compact | 提前分段任务、减少多余输出 |
529 是很多新手第一次跑批量任务时遇到的头号问题。它的本质不是你的代码写错了,而是请求太密或者服务端繁忙。处理方式就是退避重试,指数退避比固定间隔更有效。
10. 最佳实践与使用建议
10.1 工程化建议
- 用 CLAUDE.md 做项目记忆。在仓库里维护一份
CLAUDE.md,写清楚项目结构、构建命令、代码规范,Claude Code 会在每次任务开始时自动读入,这是提升稳定性的成本最低的手段。 - 配置权限规则。在配置里预设 allow / deny 规则,把高频安全命令直接放行,把危险命令永远禁止,减少不必要的确认打断。
- 用 Git 分支托管 agent 改动。所有自动修改先提交到单独分支,人工 review 后再合入主干,永远不要把 agent 的输出直接推上生产环境。
- 批量任务要落盘。每条任务的输入、输出、耗时、错误信息都写进日志,方便失败后重放。
- 敏感信息不要进入对话。API Key、数据库密码、未脱敏的用户数据都不应该出现在任务文本里,避免进入模型请求和会话日志。
10.2 合规与安全边界
Claude Code 能直接操作文件系统和执行命令,使用时要特别注意授权边界。处理他人代码、版权素材、私有业务数据之前,先确认是否有合法授权;在团队或公司环境中使用,要遵守数据合规要求,不要拿生产环境的敏感代码去测试不熟悉的模型端点或第三方兼容服务。它只是个工具,工具没有边界意识,使用的人必须有。
10.3 总结与下一步
回到标题的问题。手撕 Claude Code 源码,最终目标不是背下它的实现细节,而是理解 Agent Harness 的通用结构:一个主循环、一组工具、一层权限闸门、一套上下文管理、一份会话持久化。这套结构在 Claude Code、Codex、Cursor 里反复出现,你只要吃透过一个,后面再接触新工具都会很快。
建议的上手路径是:先用claude -p跑通最小链路,再做一个跨文件修改任务,观察权限确认和工具调用过程;然后读源码时沿着“入口 → 主循环 → 工具注册 → 权限判断”四个定位点去走,把观察到的行为和代码位置一一对应。最容易踩的坑集中在两个地方:批量任务限流导致的 529,以及本地兼容端点时的模型名不匹配。先把这两个点想清楚,