阅读视图

发现新文章,点击刷新页面。

AI编程能力边界探索:基于 Claude Code 的 Spec Coding 项目实战|得物技术

一、前言

10 天,2.5 万行代码,提效 36%。 基于 Claude Code 的 Spec Coding(规格驱动编码) 深度实战。通过 2,754 次工具调用,我们不仅完成了从 0 到 1 的前端项目搭建,更在“约束+示范+视觉”的三层规范体系下,摸清了 AI 编程的真实能力边界。本文将复盘这场实战,拆解如何用结构化工作流消除 AI 的不确定性,重构开发者的核心竞争力。

二、Spec Coding

什么是 Spec Coding 工作流

众所周知,Spec Coding(规格驱动编码)的核心思想是:在写代码之前,先写规格文档。通过 openspec 工具,每个功能变更都经历以下阶段:

Spec 工作流的实际价值

减少返工: 在 proposal 阶段明确为什么以及怎么做,避免实现完才发现方向不对。适合复杂功能: 对于需要跨多个文件多个层次的功能,tasks 分组让 AI 聚焦在当前步骤。可审计: 每个 Change 的完整决策链(proposal→design→specs→tasks)都留有记录,方便回溯。

三、项目是什么

一个标准企业级中后台搭建,包括表格、表单、卡片列表、数据看板等中后台常见核心功能,项目从零搭建到完成以下全部功能,全程使用 Claude Code 辅助开发。

四、数据概览

在这次使用Claude Code 做 Spec Coding的从0到1项目探索中,我们积累了一份完整的原始数据,以下所有数字均来自Claude Code对 109 个 .jsonl 会话文件的整体数据统计:

2,754 次工具调用的分布揭示了 AI 的"工作方式", AI 自主完成的 738 次文件读取、550 次代码编辑、662 次终端命令执行,以及 208 次任务进度标记——几乎覆盖了一个研发日常工作的全部动作类型。

五、开发时间线:10 天的演进过程

阶段一:设计阶段

在动工之前,我们完成了产品方向的确认和 UI 设计稿、产品PRD的输出。过程主要使用 Cursor + 设计规范 Rules,直接从概念沟通到生成高保真 UI 稿(HTML文件),再生成标准的 PRD 需求描述,覆盖系统所有核心页面。这一阶段的产出是一套可直接用于开发对齐的视觉参考,也是后续 AI 生成代码时的重要上下文来源。

阶段二:项目搭建(2个工作日,20 条指令)

此阶段我们以问答式交互为主,聚焦于项目基础设施的搭建和简单需求的尝试。我们向 AI 提出架构问题,由 AI 给出方案,我们决策后执行。在这个过程中,AI 帮助我们熟悉技术栈、搭建项目结构、配置开发环境,并完成了第一个核心列表页面的开发,成功打通了前后端的数据链路。

阶段三:功能开发(4个工作日,89 条指令)

这是整个项目开发强度最高的阶段,我们引入了“规格驱动编码”(Spec Coding)的工作流,约 80% 的功能代码在此阶段完成。我们不再是简单地给 AI 下达指令,而是先与 AI 共同定义清晰的功能规格(Specification),然后 AI 基于这份“蓝图”自主进行编码。通过这种方式,我们高效地完成了包括授权管理、数据分析看板、文档树状结构等多个复杂功能的开发。

阶段四:细节打磨与生产部署(4个工作日,108 条指令)

最后阶段的工作重心转向功能迭代、系统重构和生产环境的部署排障。我们与 AI 一起,对已有功能进行了多轮优化,例如完善了核心业务流程、重构了侧边栏导航、修复了登录跳转逻辑等。同时,我们也对项目首页进行了深度的代码重构,解决了前期快速迭代中积累的技术债。最后,在部署阶段,我们遇到了复杂的构建问题,通过与 AI 的多轮分析和尝试,最终定位并解决了问题,成功将应用部署上线。

六、典型案例

案例一:AI 驱动产品设计

没有产品经理、没有 UI 设计师,一个工程师如何用 AI 独立完成从产品定义到高保真原型、再到研发文档的全流程。

背景:

传统意义上,从 0 到 1 开发一个企业级知识问答平台需要三个角色:产品经理(需求分析 + 用户路径 + PRD)、UI 设计师(交互稿 + 高保真设计稿)、工程师(编码实现)。这个项目设计过程中,通过让 AI 在不同阶段扮演不同角色,覆盖了全部三个职责。

让 AI 扮演产品经理:

在 Rules 中植入「首席产品专家」Persona 提示词,将 AI 从工程师的「急于执行」模式切换为产品经理的「先想清楚」模式,与 AI 聊清楚我们想干什么。

让 AI 扮演 UI 设计师:

在 Rules 中定义设计规范,通过对话式生成逐页产出高保真 HTML 文件,而不是源码:

让 AI 生成研发可读的 PRD:

基于产品经理角色,将 HTML 设计稿作为上下文,最后生成精确到组件行为级别的 PRD:

案例二:SDD 驱动前端功能研发

在已有系统上增量交付一个完整功能模块,SDD 如何保证「增量」功能快速开发,并系统性提升前后端联调效率。比如其中有个SSD需求开发「定时任务管理」完整模块,并且对接 6 个后端接口。这是 SDD 工作流第一次被完整运用于新功能模块开发,也是验证「SDD + MCP」前后端联调提效的关键场景。

页面功能开发: opsx:new 到 archive,人工指令 < 10 条,AI代码占比100%,交付完整任务管理模块(独立路由 + 完整 CRUD + 执行记录 + 检索结果)。

前后端联调: SDD + MCP 的联调路径:接口 URL → MCP直连文档 → 一次性获取字段、枚举、必填项 →  接口文件一次生成 → ****联调一次通过,6 个接口零联调返工。

研发效率: 同日额外交付了两个完整模块,3个独立完整模块,单日全部开发完成,按纯人工开发,当天人效提升3倍。

案例三:SDD 驱动系统重构

重构与新功能的根本差异:

新功能开发是「从无到有」:AI 可以大胆生成,错了删掉重来。重构是「在活体系统上动手术」:这种高风险对 AI 执行提出了截然不同的要求——不仅要知道改什么,更要知道不能改什么,以及按什么顺序改。 SDD 的价值正在于此:在动代码之前,把这三件事全部写清楚。

知识问答首页重构:

架构债务: 大量首页业务组件与公共组件混放、useChat 导出 20+ 方法(4 种无关职责混合)、ChatInterface 接收 17 个 props(参数3 层传递)。

执行TASKS: 9 组 34 个子任务,从「grep 确认组件当前归属」→「按新分层迁移」→「更新所有 import 路径」→「tsc 类型检查」→「冒烟验证」,每一步有明确输入和验收标准。

执行结果: 34个任务全部完成(含 4 个验证任务),AI 全程独立执行,人工干预 < 5 条指令。7个业务组件与公共组件完成解耦,useChat 拆为 3 个单职责 hook,ChatInterface 从 17 个 props 缩减至 6-8 个。

案例四:复杂问题排障

并不是所有编程相关的问题AI都可以解决,哪类工程问题从结构上超出了 AI 的能力边界?这里举一个遇到的场景。

其中有一天遇到一个测试环境构建失败的问题,结果过程约 4 小时,7 个会话、15+ 次方案尝试、59 条指令。整个项目单日指令最多的一天,也是 AI 独立解决能力最受限的一天。

这一天有一个值得注意的特征:AI 每次分析都是正确的——问题不在于 AI 的分析能力不足,而在于问题的结构性特征超出了 AI 的信息范围和反馈机制:

  • 云服务器构建时发生,本地无法复现: 每次验证方案必须提交代码等待 CI(一轮约 10 分钟),AI 分析的是日志截图,无法感知「现在的 CI 环境还有哪些隐性配置」。
  • 多根因互相掩盖,解决一层才暴露下一层: AI 每次分析都正确,但正确分析的只是当前暴露的那一层,问题全貌无法被单次分析覆盖。
  • 隐性行为无文档,根因藏在依赖源码内部: Prisma postinstall 境外下载没有任何显式错误,引导AI 不得不深入阅读 node_modules 源码第 2319 行才能发现根因。这类「运行时行为藏在依赖内部、没有文档描述」的问题,超出了 AI 通过训练数据或当前上下文主动推断的范围。

最后确认的原因:

  • .npmrc 历史副作用: 早期为跳过 @next/swc-darwin-arm64 在 Linux 下载而加入的 omit=optional,无意间也跳过了 @tailwindcss/oxide-linux-x64-gnu(Tailwind v4 的 native binding),postinstall 陷入循环等待
  • Prisma v6 境外下载沉默卡死: AI 需要阅读 node_modules/@prisma/fetch-engine/dist/index.js 第 2319 行才能发现这个行为——postinstall 不报错、不超时,只是无限等待。
  • pnpm 跨平台 lockfile 不一致: macOS arm64 生成的 lockfile 不含 Linux x64 的 native package;切回 npm 则 lockfile 被忽略,安装结果每次不同。

最终解法(4 小时探索后得出):

七、代码规范落地:CLAUDE.md 和 Rules 的实际效果

规范体系设计思想:三层结构

本项目的规范体系是三个层次的协同约束, 每层解决不同的问题:

第一层:约束层(.claude/rules/)      ← 告诉 AI「禁止什么、必须怎样」
第二层:示范层(.claude/code-design/)← 告诉 AI「标准产出长什么样」
第三层:视觉层(.claude/ui-design/)  ← 告诉 AI「页面应该长什么样」

为什么需要三层?

只有「约束层」时,AI 知道规则但缺乏参考实现,容易在复杂场景下产生符合规则但不符合团队风格的代码。加入「示范层」和「视觉层」后,AI 可以直接对齐团队的标准产出,减少「虽然合法但不地道」的代码。

第一层:约束层(.claude/rules/)

7 个规范文件,分别约束不同维度:

.claude/rules/
├── ts.md          # TypeScript 规范(禁止 any、使用可选链等)
├── code-names.md  # 命名规范(kebab-case/camelCase/PascalCase)
├── comment.md     # 注释规范(JSDoc、@ai-context/@ai-rules 文件头)
├── lint.md        # 代码风格(单引号、文件末尾换行)
├── style.md       # 样式规范(Tailwind CSS、less 文件)
├── pages.md       # 页面目录结构规范(constants/services/hooks/components 分层)
└── service.md     # API 接口生成规范(fetch{Name}Api 命名、UniversalResp 泛型)

第二层:示范层(.claude/code-design/)

将项目常见场景预置完整的「标准模板代码」,AI 在生成新页面时可以直接参照,后续可以切换为skills:

.claude/code-design/
├── pro-table/          # 通用列表页模板(含搜索、分页、批量操作、行操作)
├── pro-form/           # 通用表单页模板(含创建/编辑双模式、字段验证)
├── editable-pro-table/ # 可编辑表格模板(含行内编辑、添加/保存/删除)
├── drawer/             # 抽屉组件模板(含标准打开/关闭逻辑)
├── compontent/         # 通用组件模板(含 README、Props 定义、使用示例)
└── utils/              # 工具函数模板

示范代码的作用不只是「看个格式」。以 pro-table 为例,当开发者让 AI「参考 .claude/code-design/pro-table 生成知识治理列表页」时,AI 直接继承了这套模式,一次就能生成符合团队风格的代码,无需多轮调整。

第三层:视觉层(.claude/ui-design/)

注意存放 HTML 设计稿,覆盖主要页面的视觉参考:

.claude/ui-design/
├── knowledge-spaces.html  # 知识空间列表页设计稿
├── search-strategy.html   # 检索配置页设计稿
├── space-detail.html      # 空间详情页设计稿
└── xxx设计稿

这些 HTML 文件可以直接在浏览器中打开预览,AI 也可以读取其中的结构和样式信息。实践中,提供 HTML 设计稿后,AI 生成的 UI 与设计意图的吻合度明显高于纯文字描述,尤其是布局结构、颜色方案、间距配置等细节。

规范约束的实际效果

正面效果(规范被遵循的案例):

  • 接口命名一致性: 所有接口函数均以 fetch{Name}Api 命名,类型以 I{Name}Req/Res 格式,整个项目 205 个文件保持高度一致。
  • 目录分层被遵守: constants/、services/、hooks/、components/ 分层在每个新页面中都被正确创建。
  • 代码模板被继承: CURD页面均参照了 pro-table 模板的 hooks 分离方式,代码结构高度一致。
  • 使用可选链: 几乎所有数据访问都使用了 ?. 和 ??,有效避免运行时报错。

需要人工干预的案例:

  • 2/24,AI 生成知识空间列表后,将所有代码写在单文件中,未按规范分层。通过一条追问后,AI 重构为正确结构。
  • 2/27,AI 错误地使用了 .less 后缀,但项目实际配置使用 SCSS,在收到错误提示后立即修正。
  • 出现 antd v5 废弃 API(destroyOnClose、dropdownStyle),AI 习惯于使用训练数据中更常见的旧 API,需要通过报警信息触发修正。

结论:

规范体系对 AI 的约束是有效的,但规范文件只是「约束」而非「能力」——只有「约束层」时,AI 知道不能做什么,但遇到复杂场景仍可能生成不够地道的代码;加入「示范层」和「视觉层」后,AI 有了对齐的锚点,输出质量和一致性明显提升。

八、MCP 工具:消除信息断层

在 AI 辅助前端开发中,有两类高频信息断层,在此项目中进行了接入:

接口文档断层: 接口文档在 API平台,AI 无法直接访问,只能靠用户手工复制字段,容易遗漏、版本不一致。需求文档断层: PRD、设计文档存在飞书云文档中,每次引用都需要用户打开→复制→粘贴到对话框,打断思路。

MCP 一:接口文档直连

通过该工具,AI 可以根据接口 URL 自动拉取完整接口文档——包括入参字段、出参结构、枚举值定义、必填项标注。累计被调用了 21 次,完成39个接口联调 ,覆盖了几乎所有接口的初次接入和更新迭代场景。服务端接口未生效之前,并且支持同步生成mock数据,减少后端依赖。interface.ts 类型定义质量非常高,字段注释完整,无需人工校对。

MCP 二:飞书云文档直读

通过该MCP工具,AI 可以直接读取飞书云文档的内容(PRD、设计说明、技术文档等),无需用户手工打开→复制→粘贴。

典型应用场景:

九、AI Spec Coding 经验总结

重新理解「AI 辅助编程」是什么

流行的说法是「AI 是你的 Copilot」。这个比喻在日常补全层面成立,但在 Spec Coding 实践之后,我更倾向于另一个模型:AI 是一个极度服从、无限耐心、但没有内部业务知识常识的「顶级执行者 」。

这个比喻捕捉了三个关键特征:

极度服从: AI 会一字不差地执行你写的规范,不会主动质疑「这样做合理吗」。这是优势,也是风险——规范写得越准确,执行越可靠;规范有歧义,AI 会选一个「看起来合理」的解释,而不是停下来问你。

无限耐心: 34 个任务的重构、9 组联调任务、跨会话的上下文恢复——这些在人类身上需要消耗大量意志力的事情,AI 做起来没有摩擦成本。本项目 208 次 TodoWrite 调用背后,是 AI 持续更新进度状态、从不嫌烦的特性。

没有内部业务常识: AI 不知道你们公司的部署环境是什么样的,不知道这个接口上周刚换过版本,不知道「这个交互做成这样用户会抱怨」。它只知道你告诉它的。这也是 3/4 生产构建排障花了大量时间的根本原因。

AI 的能力边界在哪里

从 10 天、2,754 次工具调用中,我们归纳出一个更精确的能力边界框架,而不是简单的「能做/不能做」:

真实项目中的并不是所有的需求都值得写一份 Spec。在真实的项目迭代中,我们需要根据需求颗粒度来选择协作模式。

小颗粒需求:对话框即扫即改

  • 场景:改个文案、修个显隐逻辑、调整 CSS 间距。
  • 策略:直接在 Cursor Chat  中对话。
  • 理由:沟通成本低于编写规范的成本,AI 的即时反馈效率最高。

中颗粒标准化需求:基于Rules 或者 Skills 预设规范生成

  • 场景:增加一个标准的 CRUD 页面、创建一个简单的业务组件。
  • 策略:利用预设的 Cursor Rules 或 Skills(如 pro-table.mdc)。
  • 理由:这类需求有强烈的“模式感”。只要规则定义清晰(如“执行流程:识别场景 -> 读取示例 -> 生成类型 -> 完成 UI”),AI 就能基于标准化模板高质量输出。

中大颗粒复杂功能:OpenSpec 深度协作

  • 场景:重构核心逻辑、新增带有复杂业务逻辑的模块、无参考代码的新功能。
  • 策略:OpenSpec 标准流 (SDD)。
  • 理由:业务逻辑复杂时,AI 极易产生幻觉或需求偏移。通过 Spec 强制进行“先设计后编码”,可以确保 AI 的每一步都在既定轨道上,且 Spec 记录了设计的决策过程,对于后期维护价值巨大。

AI 失效的三种模式

经过本项目的实践,AI Coding 的失效不是随机的,而是可归类的:

模式一:规范真空

任务涉及的领域没有规范约束,AI 自行填充「合理默认值」。

  • 表现:生成的代码功能正确,但风格/结构偏离团队约定。
  • 发生频率:高(尤其在新功能开发初期)。
  • 应对:在 CLAUDE.md 或 code-design 中补充对应规范,一次修复,全局生效。

模式二:信息孤岛

AI 掌握的信息是当前会话的快照,看不到系统外的状态。

  • 表现:本地正常,CI 失败;AI 分析每次都对,但解的都是当前暴露的问题。
  • 发生频率:低,但代价高。
  • 应对:跨平台、跨环境的依赖要在架构设计阶段提前锁定;环境差异要写成规范前置处理。

模式三:任务目标模糊

AI 把「该问人的问题」当成「执行问题」来解决。

  • 表现:用户说「优化一下首页」,AI 悄悄改了组件结构,而不是先澄清目标。
  • 发生频率:中。
  • 应对:Spec 工作流的 proposal 阶段强制要求先描述「Why」,避免 AI 自行填充目标。

开发者角色的重构

AI Coding 不是让开发者「消失」,而是让开发者的工作向上迁移:

这意味着:

规范设计能力成为 AI 时代开发者的核心竞争力——能写出让 AI 可靠执行的规范,价值比能写出同等功能代码更高。

系统性思维变得更重要——生产构建问题的排障经历说明,AI 可以帮你解决每一个局部问题,但无法帮你看到真实业务全局。

质量意识前移——过去 Code Review 在代码写完后进行,现在需要在 方案设计/任务执行 阶段就介入,而不是等 AI 执行完再纠错。

值得期待的方向

基于本项目的数据和经验,后续在以下方向可作深入探索:

规范体系的结构化积累: 每次踩坑后补充到 CLAUDE.md/rules,形成团队共享的「AI 执行约束库」。目前 7 条规范文件是手动维护的,下一步可以建立「踩坑→提炼规范→自动追加」的闭环。

MCP 工具链的纵向延伸: 本项目 MCP 仅覆盖了接口文档、飞书文档。后续针对设计稿、测试用例、发布平台、日志平台接入,可以进一步形成完整的AI Coding链路。

多 Agent 并行开发: 本项目开发过程中,发现大型任务执行等待时间较长,下一步可以尝试多Agen并发生成,同时开发不同功能模块。

一句话总结

AI Coding 的本质不仅仅是用 AI 写代码,而是用结构化的规范和工作流把不确定性消除在执行之前——AI 负责在确定性空间里高速执行,人负责维护和扩展那个确定性空间的边界。

10 天、217 条指令、2,754 次工具调用、25,546 行净增代码——这个数字背后,是一套让 AI 可以「看见」、「理解」、「遵守」团队约定的规范体系。规范是杠杆,AI 是力,Spec 工作流是支点。

本报告由claude code基于claude code 109 个真实历史会话、2,754 次工具调用记录生成,人工补充并校准,数据来源:~/.claude/projects/-Users-admin-Desktop-code-knowledge-qa/。

往期回顾

1.搜索 C++ 引擎回归能力建设:从自测到工程化准出|得物技术

2.得物社区搜推公式融合调参框架-加乘树3.0实战 

3.深入剖析Spark UI界面:参数与界面详解|得物技术

4.Sentinel Java客户端限流原理解析|得物技术

5.社区推荐重排技术:双阶段框架的实践与演进|得物技术

文 /阳凯

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

Claude Code vs. Codex:终极指南

翻译自:Claude Code vs. Codex: The Definitive Guide

我用了几个月 Claude Code,后来转投 Codex,最近又换回了 Claude。选它的原因跟 benchmark 跑分无关。我也拿同一个任务测试过两者。

本文内容:我会聊聊 Claude Code 和 Codex 的各个方面,驱动它们的旗舰模型 Opus 4.6 vs. GPT-5.3-Codex 有什么区别,哪些因素真正影响你的 AI 编程体验,以及一个小型案例——我是如何用这两个工具搭建同一个 RAG pipeline 的。

先说清楚,这篇文章大概需要 12 分钟阅读时间。如果你打算每个月花 200 美元订阅其中之一,这时间花得值。

Opus 4.6 vs. GPT-5.3-Codex:任务完成时间跨度

Codex 和 Claude Code 之间有一个可靠的对比维度:任务完成时间跨度(Completion Time Horizon),详见此处

这个指标回答的问题是:这个模型能可靠地完成多长时间的任务? 任务完成时间跨度指的是模型以一定可靠性成功完成任务的时长(按人类专家完成时间衡量)。所以一个"2小时跨度 50%成功率"意味着:给你一个人类专家需要 2 小时的任务,AI 大约有五成把握能搞定。

Image

这项研究为每个模型配置了合适的 scaffold,包括 Claude Code 和 Codex。所以虽然焦点在模型本身而非 scaffold,但我们也能借此了解这些 scaffold 有多可靠。它告诉我们这些编程 agent 能处理多难、多长的任务。

如图所示,Opus 4.6GPT-5.3-Codex 之间差距很大。Opus 4.6 在 50% 成功率下的任务完成时长是 12 小时,而 GPT-5.3-Codex 是 5 小时 50 分钟。这个差距在 80% 成功率时有所缩小。

这清楚地表明两个模型之间存在差距,进而也体现在 Claude Code 和 Codex 上——它们处理困难任务的能力有所不同。但这个差距不一定直接映射到你用它们做的事上,心里有点数就行。

Claude Code 更快,但速度没那么重要

Claude 比 Codex 快是出了名的。但跟编程 agent 打交道是长期过程。

如果一个 agent 完成任务只用了一半时间,但之后需要你花 10 分钟调试那破玩意儿,而另一个虽然多花了点时间实现,但完成后不用你盯着——那多出来的时间 100% 值得花。

不是说 Claude Code 或 Codex 更容易犯错的——只是你自己评估这些 agent,或者听别人吹嘘它们的编程速度时,这句话值得记在心里。

任务类型对 agent 很重要

Codex 和 Claude Code 的表现取决于你用它做什么任务。在 AI 工程任务中,可能一个表现更好;但在 Web 开发任务中,同一个模型可能被吊打。

哪个编程任务更适合 Codex 或 Claude Code?这个研究做得还不够。

比如说,低级编程(low-level programming)该用哪个就不清楚。理想情况下,你应该在简单可验证的环境中先测试两者,再决定all in。但对大多数人来说,花 300-400 美元两个都买下来不太现实。

要全面对比两个 agent 在各种编程任务中的表现,是个有趣的研究方向。但也没那么轻松,因为这些 agent 和驱动它们的模型每隔几个月就会大幅变脸。

两者是如何诞生的

Claude Code 最初是 Anthropic 的 @bcherny 做的副业项目,做了个终端原型,能跟 Claude API 交互、读文件、跑 bash 命令。

内部团队到第五天就有一半人开始用了。然后 Claude Code 在 2025 年 2 月 24 日以研究预览版发布,用的是 Claude 3.7 Sonnet。花了一段时间被开发者大规模采用,之后 Anthropic 也发布了 VS Code 扩展。

OpenAI 这边,最初的 Codex 模型是 12B 参数的 GPT-3 微调版,基于 GitHub 代码,最终驱动了第一版 GitHub Copilot。但新的 Codex 是完全不同的产品。

Codex CLI 在 2025 年 4 月 16 日首发是终端 agent,之后随着更好的模型不断进化。最新版 GPT-5.3-Codex(2026年2月5日)被 OpenAI 称为"第一个参与创造自己的模型"。

@GergelyOrosz 做了两个很有意思的采访,分别关于 Claude Code 和 Codex 的开发者,涉及技术栈、开发方式、以及各自是怎么起步的。值得一看。

👉 Codex 是怎么构建的

2025年9月24日

Claude Code 是怎么构建的?Claude Code 自己写 90% 的代码,工程师每天大概提交 5 个 PR,人均 PR 产出比去年增长了 67%,而团队规模翻了一倍。更多细节在今天的深度报道里:newsletter.pragmaticengineer.com/p/how-claud… @bcherny, @_catwu, @sidbid)

技术栈和驱动模型

Claude Code 用 TypeScript 写的,用 React + Ink 做终端 UI。打包成单个 Bun 可执行文件(Anthropic 在 2025 年 12 月收购 Bun 就是为了这个)。它用的 Opus 和 Sonnet 模型都支持 100 万 token 的上下文窗口。

Codex CLI 用 Rust 写的,追求性能、正确性和可移植性。OpenAI 甚至把这个 Rust TUI 库 Ratatui 的维护者挖来了团队。

两个 CLI 工具都是围绕模型包了层薄薄的外壳,通过 API 调用。我注意到用 Claude Code CLI 时有些小"故障",在 Codex 上不太明显——考虑到技术栈,这也意料之中。

不过这些故障也就是轻微烦人而已,真的不影响编程体验。

Benchmark 很接近,但有细节差异:Token 经济性

最大的性能差异不是准确率,而是 Token 效率。Morphism 的 Opus vs Codex 全面评测 揭示了一个有趣的差距。

Image

在相同任务上,Claude Code 比 Codex 多消耗 3.2–4.2 倍的 Token。 做一个 Figma 插件,Codex 用了 150 万 Token,Claude 用了 620 万。

如果这是真的,意味着你花同样的钱订阅 Claude Code,更容易撞到 Token 上限。

感觉最重要

Claude 像个帮你干活的高级工程师,Codex 像个承包商,你把任务丢给它,然后回来取结果。

这是开发者描述两者差异的普遍方式。

据报 Claude Code 有很强的交互感,还有深度推理能力——这跟 Opus 的定位相符。它会问你问题,展示推理过程,解释它的做法。虽然我那一次对比实验里没这种情况,但从用了好几个月的经验来看,我能确认这是真的。

Codex 以第一次尝试的准确率著称,代价是实现速度稍微慢一点。

话虽如此,如果你在 AGENTS.md 里具体说明你想要什么,两者行为的差异会大幅缩小。如果你明确要求模型在开始干活前跟你确认实现计划,它就会照做——不管你用的是"高级工程师"agent 还是"承包商"agent。

这不是说两者真的没区别——区别是有的

只是没你在 X 上看到的那么夸张。

快速数据

VS Code Marketplace 上,Claude Code 有 610 万安装量,评分 4/5;Codex 有 540 万安装量,评分 3.5/5。

GitHub 上,Claude Code 大约 65–72K 星,Codex 约 64K 星。

Image

为什么我现在换回 Claude Code

Anthropic 的生态拉力强

选 Codex 还是 Claude Code 不只是编程问题。你订阅任何一个,等于订阅了整个 Anthropic/OpenAI 生态,这个因素值得考虑。

Image

我个人觉得 Claude 正在变成一个像 Apple 那样火热的生态,现在有 Claude Cowork、Claude Chat 和 Claude Code。Anthropic 似乎也在用 Claude app 慢慢搭建一个更安全、更温顺的 OpenClaw(主动式个人 agent),零敲碎打的功能正在逐步推出。

3月7日

今天我们在 Claude Code 桌面版推出本地定时任务。创建一个你想定期运行的任务计划,只要电脑醒着它们就会跑。

OpenAI 这边,目前我没看到什么诱人的东西。除了 Codex,其他的都挺无聊。我没感觉到一个生态,只感觉是零散的碎片,而且外面有更好的替代品。

我已经在用 Claude Chat 而不是 ChatGPT 了。对我来说,跟 Opus 相比,ChatGPT 现在基本没法用。UI、聊天风格、模型选择,没有一个让我有动力用 ChatGPT。

所以呢,因为我已经在高频使用 Claude Chat,打算折腾 cowork,目前没看到从 Claude Code 迁移到 Codex 有什么决定性的改进。换回 Claude Code、每月省下 200 美元,这决定做得相当轻松。

这成了影响我决定换回 Claude 的重大因素。

价格

Claude Code 和 Codex 的价格基本一样:

入门:都是 20 美元/月

高级用户:Claude Code 有个 Max 5x 档,100 美元/月

重度用户:都是 200 美元/月

Claude Code 真正亮眼的是它有个 100 美元的中档,而不是从 20 美元疯涨到 200 美元。而且我相信 Max 5x 计划(100 美元/月)对大多数开发者来说足够了。

所以可以说,Claude Code 实际上更便宜,因为它允许你选一个更便宜且够用的档,而不是逼你爬上价格阶梯。

技能和插件:开发者生态

技能(Skills)在 Claude Code 和 Codex 之间是兼容的,所以用哪个都感觉不出差别。但大多数技能中心和仓库都以 Claude Code 命名,可能有点混淆。

其他大多数事情也这样。你在 Reddit、X 或博客上看到的关于编程 agent 的帖子,大多关于 Claude Code 而不是 Codex——尽管两者原理相同。这本身就说明了很多问题:受欢迎程度和社区规模。

Codex 比 Claude Code 晚很久才支持技能和插件。但插件没有技能那么兼容。而且 Codex 的插件支持刚起步,没多少可用。

也就是说,很多开发者,包括我,根本不用插件。所以除非你特别需要各种插件支持,这方面不用纠结,也别把它当作选择依据。

RAG Pipeline:案例研究

我选了一个可以量化评估的任务来对比。问题是做一个落地页这种任务没法量化:一个人可能觉得好看,另一个可能说是紫色渐变的垃圾。

所以我选了个简单的 RAG pipeline 任务,因为生成的答案可以用数字衡量准确性。

如果你想做类似的对比,其他好想法包括:训练 vision model 或微调 LLM,或者测量低级程序的性能。

搭建检索 pipeline 是 AI 工程师的常见任务,你工作中可能用到 Claude Code 或 Codex。我让这两个编程 agent 给我搭一个论文问答 RAG pipeline。流程很简单:

  1. 取一批论文,提取文本
  2. 把内容分块(chunk)
  3. 把每块 embedding 到向量空间
  4. 用户提问时,找到跟问题 embedding 最接近的块
  5. 以原始形式检索出相关块(不是 embedding)
  6. 用这个上下文回答用户问题

这个任务足够简单,可以一次session 做完,但细节很复杂,对输出影响很大:用哪种分块策略、选什么 embedding 模型、用什么向量存储、如何处理"哪个块更接近查询"的置信度、是否重写用户问题来帮助找到更相似的块……

实验设置

我从 @huggingface 过去一周的每日论文里选了 5 篇,建了一个测试集(100 道题及标准答案),用来测试 Claude 或 Codex 的实现质量。

对两个 coding agent,我都是这么要求的:

  • 做一个 Python RAG pipeline
  • 用 PyMuPDF 处理所有 PDF
  • 为这个用例选一个好的分块策略
  • 创建 embedding 和持久化本地向量索引(你选)
  • llama-3.1-8b-instant 生成最终答案
  • 如果没有足够证据,不要 hallucinate,返回 fallback

对 Codex 和 Claude Code,我都用了最流行且默认的模型:gpt-5.3-codexOpus 4.6,都用 High effort(推理深度)。都没有 AGENTS.md。

它们怎么实现的 pipeline

我没注意到两个 agent 思考任务的方式有什么明显差别,除了 Codex 更啰嗦,会解释它的计划以及要做什么。Claude 直接写文件,执行命令,不说那么多。

Codex 完成任务比 Claude 花了更长时间。

更重要的是,Claude 端到端测试了脚本,确保 pipeline 能用。

Codex 则是做完了实现,但没有测试或运行程序,只是告诉我 pip install 依赖然后运行脚本。自然,我跑的时候报错了,Codex 解决了。Claude 的脚本跑起来一点问题没有。

我注意到 Codex 有这个模式:很多脏活累活它留给你做,而不是自己动手。

Codex 会告诉你并主动处理环境问题或实现困难,Claude 则自行修复——这取决于你的偏好,可能是好事也可能是坏事。

我还注意到,Codex 在新会话中第一个 token 的响应时间可以高达一分钟,Claude Code 这边短得多。

Claude Code vs. Codex 实现

两个 coding agent 的方案惊人地相似:

  • 都选了 all-MiniLM-L6-v2 作为 embedding 模型
  • 都选了 k=5 做 Top-K 检索
  • 都在 system prompt 里限制 LLM 只准用提供的上下文

但这些地方它们走了不同的路:

  • 向量存储:Claude Code 选了 ChromaDB,Codex 选了 FAISS——一个更底层的相似度搜索库,更省内存更快。
  • 分块:Claude Code 用了递归字符分割。先试 \n\n,然后 \n,然后 ".",然后 " "。目标是 1000 字符,200 字符重叠。Codex 用了句子级别的词分割,每块最多 220 词,40 词重叠。Claude Code 按结构分割(段落→行→句子→词),按字符计量。Codex 先按句子切,然后打包进词预算的箱子里。Codex 的方法尊重句子边界,避免句子中间切断,但 220 词对这种上下文可能太小(学术文本)。
  • 检索:两者都选了 Top-5 块。Claude Code 返回原始 L2 距离,Codex 返回内积(cosine)分数。
  • 置信度:Claude Code 对最佳 L2 距离用单一阈值(>1.2 = 不相关),然后检查低置信度与高置信度块的距离平均值。Codex 用多标准三档:强、中等、不足。
  • 代码架构:Claude Code:扁平函数,各模块常量,无模型一致性输入验证。Codex:OOP pipeline 类,集中配置,dataclasses,argparse CLI,模型一致性验证。Codex 明显工程化程度更高、可配置性更好。在更大更严肃的代码库里,这很关键。

结果

gpt-4 做 LLM-as-a-judge,两个 pipeline 的答案按四个标准比较:正确性、完整性、相关性、简洁性。

Image

100 道题中,Claude Code 赢了 42 道,Codex 赢了 33 道,25 道平手。 Claude 赢主要是因为它的置信度阈值更松,可能还有生成温度稍高(0.2 vs Codex 的 0.1)。

加点盐

这只是个非常简单的设置。我主要是好奇两个 coding agent 实现同一个封闭任务时有什么不同的做法。在专业环境里,是开发者拍板整体架构:分块方法、向量数据库、检索策略等。而且在专业环境里,做这类系统需要更多测试和迭代改进,以及更可靠的测试集和验证。

不过可以预期,一个不太有经验的初级开发者做 RAG pipeline,会把这些决策交给 AI。

选一个吧

我觉得选 Claude Code 还是 Codex,没有绝对错误的选择。两者都比现有格局的模型强,完成任务的水平差不多。

我的两大因素是:Anthropic 生态,以及 100 美元/月的价位段。即使我需要升到 200 美元/月 档位,还是会为了前者留在 Anthropic 的 Claude Code。

最重要的是你用这些 scaffold 做什么,以及怎么用。

这个比任何 benchmark 都能更好地判断哪个更适合你——没有标准答案,只能凭感觉。你把两个都试过之后,哪个用起来更舒服,答案就在你心里。

有开发者比如 @steipete 坚决站 Codex,也有人相信 Opus 就是被 OpenAI 模型吊打。

我觉得两边都对,因为他们的工作流不同,对这些 coding agent 的"品味"也不同。

如果你犹豫不定,建议先试两个的 20 美元/月 版本,用跟你相关的编程领域,最好在几个可验证的任务上测试。

最后,跟其他 AI 相关的东西一样,格局几个月就变一次。你现在喜欢哪个,三个月后 agent 行为可能漂移,或者新模型出来了。

AI 领域很少有全球通用的标准答案,这个话题也不是 ;)

MCP 从入门到实战完整教程(Windows 版)

MCP(Model Context Protocol,模型上下文协议)是 Anthropic 推出的开放标准协议,为 AI 应用提供了统一的方式来连接外部数据源和工具。你可以把 MCP 理解为 AI 世界的"USB-C 接口"——一个协议,即可让 AI 模型访问文件系统、数据库、搜索引擎等各类外部资源。本教程将带你在 Windows 系统上从概念到实战,全面掌握 MCP。


一、MCP 核心概念

架构总览

MCP 采用客户端-服务端架构,包含三个核心角色:

  • Host(宿主):发起连接的 AI 应用,例如 Claude Desktop、Claude CLI、Cursor 等
  • Client(客户端):Host 内部的 MCP 客户端,负责与 Server 建立一对一连接
  • Server(服务端):轻量级程序,通过 MCP 协议向 Client 暴露特定能力
┌─────────────────────────────────────────┐
│  Host(Claude Desktop / CLI)            │
│                                         │
│  ┌──────────┐  ┌──────────┐            │
│  │ Client A │  │ Client B │  ...       │
│  └────┬─────┘  └────┬─────┘            │
└───────┼──────────────┼──────────────────┘
        │              │
   ┌────▼─────┐  ┌────▼─────┐
   │ Server A │  │ Server B │
   │(filesystem)│ │(search)  │
   └──────────┘  └──────────┘

通信方式

MCP 支持两种传输方式:

传输方式 说明 适用场景
stdio 通过标准输入/输出通信 本地 Server,最常用
SSE 通过 HTTP Server-Sent Events 通信 远程 Server,需网络访问

三大原语

MCP Server 可以向 Host 暴露三种能力:

  • Tools(工具):模型可以调用的函数,例如"搜索网页"、"读取文件"、"执行 SQL"
  • Resources(资源):模型可以读取的数据,类似 REST API 的 GET 端点
  • Prompts(提示模板):预定义的交互模板,帮助用户快速完成特定任务

其中 Tools 是目前最常用的原语,大多数 MCP Server 都以 Tool 的形式提供能力。


02-content-pain-point.png

二、环境准备

基础环境

确保你的 Windows 系统已安装以下工具:

  • Node.js 18+npm:用于运行基于 Node.js 的 MCP Server
  • Python 3.10+uv(可选):用于运行基于 Python 的 MCP Server
  • Claude DesktopClaude CLI:作为 MCP 的 Host

安装 Node.js

如果尚未安装,前往 nodejs.org 下载最新 LTS 版本。验证安装:

node --version
npm --version

安装 Python 和 uv(可选)

部分 MCP Server 使用 Python 编写,需要通过 uvx 运行:

# 安装 uv(Python 包管理工具)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

安装完成后重新打开 PowerShell 验证:

uv --version
uvx --version

安装 Claude CLI

如果尚未安装 Claude CLI:

npm install -g @anthropic-ai/claude-code

04-content-setup-steps.png

三、在 Claude CLI 中配置 MCP

Claude CLI 提供了命令行和配置文件两种方式来管理 MCP Server。

方式一:使用 claude mcp add 命令

# 添加 filesystem server
claude mcp add filesystem -s user -- npx -y @modelcontextprotocol/server-filesystem C:\Users\你的用户名\Documents

参数说明:

参数 说明
filesystem Server 名称(自定义,用于标识)
-s user 作用域:user(全局)或 project(当前项目)
-- 分隔符,之后的内容为 Server 启动命令

常用管理命令

# 查看已配置的 MCP Server
claude mcp list

# 查看某个 Server 的详细信息
claude mcp get filesystem

# 移除某个 Server
claude mcp remove filesystem

方式二:手动编辑配置文件

Claude CLI 的 MCP 配置存储在 settings.json 中:

  • 全局配置C:\Users\你的用户名\.claude\settings.json
  • 项目配置项目根目录\.claude\settings.json

手动添加 MCP Server 示例:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\你的用户名\\Documents"
      ]
    }
  }
}

在 CLI 中验证

启动 Claude CLI 后,使用 /mcp 命令查看当前连接的 MCP Server 状态:

/mcp

输出中可以看到每个 Server 的名称、状态和提供的工具数量。


05-content-server-list.png


四、实战演示

场景一:用 Filesystem MCP 管理项目文件

配置好 Filesystem Server 后,你可以直接让 Claude 操作项目文件:

> 读取 C:\Users\我\projects\myapp\package.json,列出所有依赖的版本

> 在 C:\Users\我\Documents\notes 目录下创建一个 todo.md,内容是本周的工作计划

> 找出 src 目录下所有包含 "TODO" 注释的文件

场景二:用 Brave Search MCP 联网搜索

配置好 Brave Search Server 后,Claude 具备了实时联网能力:

> 搜索 2026 年最新的 React 状态管理方案对比

> 搜索 Windows 11 最新的 PowerShell 更新内容

场景三:用 GitHub MCP 管理仓库

配置好 GitHub Server 后,可以直接通过对话管理仓库:

> 列出我的 GitHub 仓库中所有 open 状态的 issue

> 为 myapp 仓库创建一个新的 issue,标题是"优化首页加载速度"

> 查看 myapp 仓库最近的 5 个 pull request

场景四:多个 MCP Server 协同工作

MCP 的强大之处在于多个 Server 可以协同工作:

> 搜索最新的 Tailwind CSS v4 变更内容,然后帮我更新项目中的 tailwind.config.ts 文件

这条指令中,Claude 会先调用 Brave Search 搜索信息,再调用 Filesystem 读取并修改文件。


五、开发自定义 MCP Server(入门)

如果现有的 MCP Server 不能满足需求,你可以用 TypeScript SDK 快速开发自己的 Server。

初始化项目

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

编写 Server 代码

创建 src/index.ts

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

// 创建 MCP Server 实例
const server = new McpServer({
    name: 'my-weather-server',
    version: '1.0.0',
});

// 注册一个 Tool:查询天气
server.tool(
    'get-weather',
    '获取指定城市的天气信息',
    {
        city: z.string().trim().min(1, '城市名称不能为空').describe('城市名称,例如:北京'),
    },
    async ({ city }) => {
        // 使用心知天气 API 获取实时天气
        const SENIVERSE_API_KEY = 'YOUR_API_KEY_HERE';

        const weatherResp = await fetch(
            `https://api.seniverse.com/v3/weather/now.json?key=${encodeURIComponent(SENIVERSE_API_KEY)}&location=${encodeURIComponent(city)}&language=zh-Hans&unit=c`,
        );
        if (!weatherResp.ok) {
            throw new Error(`心知天气查询失败: HTTP ${weatherResp.status}`);
        }

        const weatherJson = (await weatherResp.json()) as {
            results?: Array<{
                location?: { name?: string };
                now?: { text?: string; temperature?: string; humidity?: string };
            }>;
        };

        const result = weatherJson.results?.[0];
        const now = result?.now;
        if (!result || !now) {
            throw new Error(`未找到城市或天气数据: ${city}`);
        }

        const weatherData = {
            city: result.location?.name ?? city,
            temperature: now.temperature !== undefined ? `${now.temperature}°C` : 'N/A',
            condition: now.text ?? 'N/A',
            humidity: now.humidity !== undefined ? `${now.humidity}%` : 'N/A',
        };

        return {
            content: [
                {
                    type: 'text' as const,
                    text: JSON.stringify(weatherData, null, 2),
                },
            ],
        };
    },
);

// 启动 Server(使用 stdio 传输)
async function main(): Promise<void> {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error('Weather MCP Server is running');
}

main().catch(console.error);

编译与测试

修改 tsconfig.json,确保以下配置:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

编译项目:

npx tsc

接入 Claude CLI

claude mcp add my-weather -s user -- node C:\Users\你的用户名\my-mcp-server\dist\index.js

重启 Claude Desktop 或 Claude CLI 后,就可以使用了:

> 查询北京的天气

Claude 会调用你的 get-weather 工具并返回结果。


六、常见问题与排错

Q: Server 启动报错 npx 不是内部或外部命令

Node.js 未正确安装或 PATH 未配置。在 PowerShell 中验证:

where.exe npx
# 应输出 npx 的完整路径

如果无输出,重新安装 Node.js 并确保勾选"Add to PATH"选项。

Q: Server 启动报错 uvx 不是内部或外部命令

需要安装 uv 工具。参考"环境准备"章节中的安装步骤。

Q: 配置了 env 中的 API Key 但仍然报认证失败?

  1. 检查 Key 是否正确,有无多余空格
  2. 确保 Key 没有过期
  3. 修改配置后必须重启 Claude CLI

Q: Claude CLI 中 /mcp 显示 Server 状态为 disconnected?

尝试以下步骤:

# 查看 Server 详细信息
claude mcp get <server-name>

# 移除并重新添加
claude mcp remove <server-name>
claude mcp add <server-name> -s user -- <command> <args>

七、总结与资源链接

MCP 为 AI 应用提供了标准化的外部集成方式,让 Claude 从一个"只能对话"的模型变成了能够操作文件、搜索网络、管理代码仓库的全能助手。通过本教程,你已经掌握了:

  • MCP 的核心架构和概念
  • 在 Claude Desktop 和 Claude CLI 中配置 MCP Server 的方法
  • 常用 MCP Server 的配置与使用
  • 开发自定义 MCP Server 的基础流程

推荐资源:

如果你喜欢本教程,记得点赞+收藏!关注我获取更多Cluade相关技巧

为什么每个程序员都应该试试 cmux:AI 加持的终端效率革命

一个来自 lazygit 作者的终端管理神器,让你的终端效率直接起飞


前言

作为一名程序员,你是否经历过这样的场景:

  • 同时开着多个项目,每个项目都要开一个终端窗口
  • 切换分支的时候手忙脚乱,鼠标在终端和 Git GUI 之间来回穿梭
  • 想看日志又不想关掉当前的开发环境,只能硬着头皮开新标签页
  • 文件管理靠 cd + ls,每次都要输入一长串路径

以上这些破事我全遇到过。传统终端的操作方式真的该淘汰了。今天要介绍的工具叫 cmux,来自 lazygit 的作者,用完之后你会觉得以前的日子简直是原始人。

cmux 是什么?

cmux(Composite Mux)是一个终端复用器,类似于 tmux 但更加现代化。它的目标是:让你在一个终端窗口里完成所有操作,再也不用来回切换窗口。

直接看效果:

image.png

左边是项目导航,右边是终端面板,底部还集成了浏览器。一个窗口就是一个完整的工作空间,不用再满屏幕找窗口了。

核心功能一览

1. 项目分类导航

左侧边栏可以按项目维度组织终端会话。比如你有三个项目,每个项目开 2-3 个终端,切换项目就像在文件管理器里切换目录一样。项目再多也不乱。

2. 终端灵活拆分

这是 cmux 最实用的功能:

操作 快捷键
纵向拆分(上下排列) Ctrl + Shift + D
水平拆分(左右排列) Ctrl + D
新建标签页 Ctrl + T
跨终端切换 Ctrl + Option + 方向键

上面跑着开发服务器,中间写着代码,底部开着数据库终端。一个窗口搞定全部,不用满世界找窗口。

3. oh-my-zsh 无缝集成

cmux 能自动识别你的 oh-my-zsh 配置,包括主题和插件。之前配置的个性化设置直接迁移,不用重新折腾。

4. 内置浏览器

没错,cmux 还带了个小型浏览器。查文档的时候不用再切换到 Chrome,看完直接切回来继续干活。

image.png


插件生态

cmux 支持自动识别终端里的各种效率工具。装上这几个插件,你的终端会变得特别好用。

lazygit - 终端里的 Git 客户端

官方网站: github.com/jesseduffie…

image.png

用命令行操作 Git 的痛点:

  • 想看某个文件的修改历史,要敲 git log --oneline --graph --all -- filename
  • 想看某次提交的具体内容,grep 半天找不到重点
  • 合并分支的时候提心吊胆,生怕冲突没处理好
  • 只想看哪些文件改了,也要输入 git status

lazygit 就是为了解决这些问题而生的。

安装和使用:

# 安装
brew install lazygit

# 运行
lazygit

lazygit 能做什么?

  • 单个文件内部分行暂存:按空格键暂存选中行,v 键选择多行,a 键暂存整个 hunk
  • 交互式变基:按 i 键进入变基模式,可以 squash、fixup、drop、edit 提交,还能调整提交顺序
  • Cherry-pick:shift+c 复制提交,shift+v 粘贴
  • 二分查找:按 b 键开始 git bisect,精确定位问题提交
  • 强删工作区:shift+d 可以一键清空所有未提交的修改
  • 修正旧提交:shift+a 可以用当前暂存的修改去修正历史提交
  • 筛选视图:按 / 键筛选分支、提交等列表
  • Worktree 管理:按 w 键创建 worktree,多分支并行开发
  • 撤销/重做:z 撤销,ctrl+z 重做
  • 提交图:窗口够大时会显示彩色提交图
  • 比较提交:shift+w 比较两个提交之间的差异

我用 lazygit 之后,再也没打开过 SourceTree。键盘操作确实比鼠标快太多了。

在 cmux 中装上 lazygit,你可以一边写着代码,一边用 lazygit 管理版本。遇到需要提交的时候,Ctrl + Option + 方向键 切换到底部终端,输入提交信息,一气呵成。


fresh - 现代终端文本编辑器

image.png

官方网站: github.com/sinelaw/fre…

很多开发者习惯用 VS Code 或 Sublime Text 这种图形化编辑器,但有时候在终端里工作更高效。fresh 就是为了解决这个问题——把 VS Code 的体验带到终端里。

安装和使用:

# 快速安装
curl https://raw.githubusercontent.com/sinelaw/fresh/refs/heads/master/scripts/install.sh | sh

# 或者用 homebrew
brew tap sinelaw/fresh
brew install fresh-editor

# 运行
fresh

fresh 能做什么?

  • 零配置:安装后直接能用,不需要任何配置
  • 熟悉的热键:Ctrl+S、Ctrl+Z、Ctrl+F 这些标准快捷键都能用
  • 完整鼠标支持:像图形编辑器一样用鼠标操作
  • 命令面板:一个快捷键就能搜索文件、运行命令、切换标签页、跳转到任意行
  • 多光标编辑:同时选中多处进行批量编辑,和图形编辑器一样的体验
  • 文件管理:内置文件浏览器,支持标签页、git 状态显示、模糊搜索
  • LSP 支持:跳转到定义、引用、悬停显示文档、代码诊断、自动补全
  • 内置终端:集成终端模拟器,支持键盘捕获模式和会话持久化
  • Vim 模式:也支持 Vim 风格的 Normal/Insert/Visual 模式
  • 主题系统:内置多套主题,支持可视化主题编辑器
  • 插件系统:用 TypeScript 编写插件,Sandboxed QuickJS 环境运行
  • 多语言:支持 11 种以上语言,包括中文

这是一个终端文本编辑器,和 VS Code/Sublime Text 体验类似,但运行在终端里。特别适合那种想在终端里完成所有工作的人。


yazi - 快到飞起的终端文件管理器

官方网站: github.com/sxyazi/yazi

image.png

传统的终端文件管理:

cd /long/path/to/some/directory
ls -la
cd subfolder
ls
cd ..

每次都要 cd,路径一长简直崩溃。

yazi 带来了全新的体验,基于 Rust + 异步 I/O,速度极快。

安装和使用:

# 安装
brew install yazi

# 运行
yazi

或者直接作为命令行工具用:

# 用 yazi 打开当前目录
yazi .

# 选择文件后自动 cd 进入
yazi

yazi 的核心特性:

  • 全异步支持:所有 I/O 操作都是异步的,CPU 任务分布在多个线程
  • 内置图片预览:支持多种终端协议(kitty、iTerm2、WeTerm 等)
  • 内置代码高亮:结合预加载机制,文件加载速度极快
  • 插件系统:用 Lua 编写插件,支持 UI 重写、功能扩展
  • 虚拟文件系统:支持远程文件管理、自定义搜索引擎
  • 数据分发服务:基于客户端-服务器架构,实现跨实例通信和状态持久化
  • 包管理器:一条命令安装插件和主题
  • 集成 ripgrep、fd、fzf、zoxide
  • Vim 风格交互:输入、选择、确认、通知组件,cd 路径自动补全
  • 多标签页支持:跨目录选择、可滚动预览(视频、PDF、压缩包、代码、目录等)
  • 批量重命名:批量修改文件名
  • 归档解压:直接在终端内解压文件
  • Git 集成:显示文件修改状态
  • 主题系统:支持鼠标、回收站、自定义布局

yazi 最惊艳的功能是预览,支持图片、代码高亮、Markdown 渲染、PDF、压缩包等内容直接预览。

在 cmux 中,按 Ctrl + Option + 方向键 切换到文件管理面板,用 yazi 快速定位文件,确认后自动在当前目录执行操作。整个过程行云流水,完全不需要鼠标。


为什么选择 cmux?

特性 tmux iTerm2 cmux
学习曲线
项目导航 需要配置 一般 原生支持
终端拆分 需要配置 原生支持 原生支持
插件集成 需要配置 需要配置 自动识别
内置浏览器

说白了:cmux 就像给 iTerm2 装上了项目管理器和插件市场,让终端真正变成了 IDE

安装与配置

# macOS
brew install cmux

# Linux
# 参考官方文档

首次启动后,按照提示配置 oh-my-zsh 路径,cmux 会自动识别你的主题和插件。


写在最后

很多人觉得终端就是「古老的命令行」,用起来糙。但 cmux 证明了:终端也可以很现代,也可以很高效。

如果你也受够了窗口开太多、切换太麻烦的问题,试试 cmux + 这三个插件的组合。工具选对了,效率才能翻倍。


参考链接

Everything Claude Code 新手教学指南(中文版)

本文档是对 Everything Claude Code 项目的系统性教学汇总,面向新手,包含术语解释、架构图解、深度分析和知识点详解。


目录

  1. 目录结构与术语解释
  2. 宏观框架理解
  3. 高频 Agent/Skill 分析与 Orchestrate 重点讲解
  4. 教练组深度报告
  5. 新手友好知识点图解

一、目录结构与术语解释

项目目录结构

everything-claude-code/
├── agents/        <- 13个专业"子代理",相当于你的团队成员
├── skills/        <- 55"技能包",是领域知识库
├── commands/      <- 33"斜杠命令",用户直接输入的快捷指令
├── hooks/         <- "钩子",自动化触发器(编辑后自动格式化等)
├── rules/         <- "规则",永远遵守的编码标准和约束
├── mcp-configs/   <- "MCP服务器配置",连接外部系统的桥梁
├── scripts/       <- Node.js脚本,hooks的具体实现逻辑
├── contexts/      <- 示例CLAUDE.md,不同工作模式的预设上下文
├── schemas/       <- JSON Schema定义,约束配置文件格式
├── tests/         <- 测试套件
├── docs/          <- 多语言文档(中文、日文等)
└── examples/      <- 不同项目类型的CLAUDE.md示例

关键术语对照表

术语 中文 类比
Agent 子代理/专员 公司里的专业岗位(架构师、测试员、审计员)
Skill 技能/知识包 岗位培训手册,详细告诉你"怎么做"
Command 斜杠命令 快捷键,一键触发复杂工作流
Hook 钩子/触发器 自动化流水线上的传感器,事件发生时自动执行
Rule 规则 公司制度手册,所有人必须遵守
MCP 模型上下文协议 统一的"外部系统接口标准"
Instinct 本能/直觉 从经验中提炼的"肌肉记忆"
Orchestrate 编排 乐队指挥,按顺序调度多个Agent协作
Handoff 交接 Agent之间的工作交接文档
TDD 测试驱动开发 先写考试题,再写答案

二、宏观框架理解

框架1:与外部系统的沟通架构

                    +---------------------------+
                    |     你(开发者)            |
                    |  输入: /plan /tdd /e2e     |
                    +-------------+-------------+
                                  |
                    +-------------v-------------+
                    |     Claude Code 主进程      |
                    |  (读取 CLAUDE.md + Rules) |
                    +-------------+-------------+
                                  |
              +-------------------+-------------------+
              |                   |                   |
    +---------v--------+  +------v---------+  +------v-----------+
    |  Agents (子代理)  |  | Hooks (自动化)  |  | MCP (外部连接)    |
    |  planner         |  | 编辑后格式化    |  | GitHub           |
    |  code-reviewer   |  | TypeScript检查  |  | Supabase         |
    |  tdd-guide       |  | 构建分析        |  | Vercel           |
    |  security-review |  | 持续学习        |  | Context7         |
    |  architect       |  | 会话持久化      |  | Exa搜索          |
    +------------------+  +----------------+  +------------------+
              |                                      |
              |              +------------------------+
              |              |
    +---------v--------------v---------+
    |         Skills (知识库)           |
    |  coding-standards, tdd-workflow, |
    |  postgres-patterns, etc.         |
    |  <- Agent执行时查阅的参考手册     |
    +----------------------------------+

MCP 是什么? 可以理解为"万能适配器"。Claude Code 本身不能直接操作 GitHub、数据库、部署平台,但通过 MCP 协议,可以连接到这些外部系统。mcp-configs/mcp-servers.json 就是这些适配器的配置清单。

框架2:5层功能分类

整个系统可以分为 5 个层次:

+-------------------------------------------------------------+
| 第5层:编排层 (Orchestration)                                 |
|   /orchestrate -> 串联多个Agent按流水线执行                    |
|   /multi-workflow -> 多模型协作(Claude + Gemini + Codex)     |
+-------------------------------------------------------------+
| 第4层:命令层 (Commands)                                      |
|   用户接口:/plan /tdd /e2e /code-review /build-fix 等        |
|   每个命令对应一个或多个Agent                                  |
+-------------------------------------------------------------+
| 第3层:执行层 (Agents)                                        |
|   实际干活的专员:planner, tdd-guide, code-reviewer 等         |
|   每个Agent有指定的模型(opus/sonnet/haiku)和工具权限          |
+-------------------------------------------------------------+
| 第2层:知识层 (Skills + Rules)                                |
|   Skills = 操作手册(怎么做),Rules = 制度手册(必须怎样)      |
|   Agent执行时参考这一层                                        |
+-------------------------------------------------------------+
| 第1层:自动化层 (Hooks + MCP)                                 |
|   Hooks = 被动自动化(事件驱动)                               |
|   MCP = 主动连接外部系统                                      |
+-------------------------------------------------------------+

框架3:一个典型Feature的完整生命周期

你说: "帮我实现用户认证功能"

  1. /plan(或/orchestrate feature)
     |
     v
  [Planner Agent] -- 分析需求 -> 输出实施计划
     |                         v HANDOFF文档
     v
  [你确认: "OK, 开始吧"]
     |
     v
  2. /tdd
     |
     v
  [TDD-Guide Agent] -- 先写测试(RED) -> 实现代码(GREEN) -> 重构(REFACTOR)
     |                                v HANDOFF文档
     v
  3. 自动触发 Hooks
     |-- post-edit-format.js -> 自动格式化代码
     |-- post-edit-typecheck.js -> TypeScript类型检查
     +-- post-edit-console-warn.js -> 警告遗留的console.log
     |
     v
  4. /code-review
     |
     v
  [Code-Reviewer Agent] -- 审查质量、安全、可维护性
     |                     v HANDOFF文档
     v
  5. /security-review(可选但推荐)
     |
     v
  [Security-Reviewer Agent] -- OWASP Top 10检查
     |
     v
  6. 提交代码 + 创建PR
     +-- Hook自动记录PR URL

框架4:学习与进化系统

你的日常编码操作
       |
       | Hooks 100%捕获
       v
 [observations.jsonl]    <- 原始观察数据(你用了什么工具、做了什么操作)
       |
       | 后台 Haiku 分析
       v
 [Instinct 本能]         <- 提炼出的原子模式
   confidence: 0.3->0.9      (如:"编辑前先Grep""优先函数式风格")
       |
       | /evolve 聚类
       v
 [Skill/Command/Agent]  <- 进化成完整的技能、命令或代理

关键概念:

  • Project-scoped Instinct:React 项目学到的模式不会污染 Go 项目
  • Confidence scoring:0.3=试探性,0.9=几乎确定
  • Promotion:一个模式在2+个项目中出现且高置信 -> 自动提升为全局

三、高频 Agent/Skill 分析与 Orchestrate 重点讲解

日常工作最高频的 Agent/Command

频率 Agent/Command 场景
最高 /plan -> planner 任何新功能开始前,先做计划
最高 /tdd -> tdd-guide 写代码的核心工作流
code-reviewer 代码写完自动触发
/build-fix -> build-error-resolver 构建失败时
/orchestrate 复杂任务全流程自动化
security-reviewer 涉及认证/支付/敏感数据时
architect 系统设计决策时
/learn -> continuous-learning 每次会话结束提炼经验

/orchestrate 深度讲解

Orchestrate 是流水线指挥官。它的核心价值是:你不需要手动一个个调Agent,它帮你按正确顺序串联。

四种预设工作流:

/orchestrate feature "添加用户认证"
  -> planner -> tdd-guide -> code-reviewer -> security-reviewer

/orchestrate bugfix "修复登录失败问题"
  -> planner -> tdd-guide -> code-reviewer

/orchestrate refactor "重构缓存层"
  -> architect -> code-reviewer -> tdd-guide

/orchestrate security "安全审计支付模块"
  -> security-reviewer -> code-reviewer -> architect

自定义工作流:

/orchestrate custom "architect,tdd-guide,code-reviewer" "重新设计缓存层"

核心机制 -- Handoff(交接):

Agent之间不是各干各的,而是通过结构化的Handoff文档传递上下文:

## HANDOFF: planner -> tdd-guide

### Context
实施计划已完成:用户认证使用JWT + Refresh Token

### Findings
- 需要3个新endpoint: /login, /register, /refresh
- 需要中间件验证token

### Files Modified
- 无(规划阶段不修改文件)

### Open Questions
- Token过期时间选15分钟还是30分钟?

### Recommendations
- 从/register endpoint开始TDD

并行优化: 当Agent之间无依赖时,可以并行执行:

code-reviewer    --+
security-reviewer --+--> 合并结果
architect        --+

四、教练组深度报告

教练1:Agent编排深度分析

两套编排体系

体系A:单模型编排 (/orchestrate)
  * 纯 Claude 内部 Agent 链
  * 通过 Handoff 文档传递上下文
  * 零外部依赖,简单可靠
  * 适合:日常功能开发、Bug修复、重构

体系B:多模型协作 (multi-* 系列)
  * Claude 当指挥 + Codex(后端专家) + Gemini(前端专家)
  * 外部模型只能输出 Diff,Claude 拥有"代码主权"
  * 需要 ace-tool MCP + codeagent-wrapper
  * 适合:全栈复杂功能、高质量交付

命令横向对比

命令 外部模型 复杂度 适用场景
/orchestrate feature 日常功能开发
/orchestrate bugfix Bug修复
/orchestrate refactor 代码重构
/orchestrate security 安全审查
/orchestrate custom 自定义Agent链
/workflow Codex+Gemini 最高 全栈复杂功能(6阶段)
/ccg:plan Codex+Gemini 单独规划(禁止写代码)
/ccg:execute Codex或Gemini 执行已规划的任务
/frontend 仅Gemini 纯前端UI任务
/backend 仅Codex 纯后端逻辑任务

多模型协作的信任体系

后端领域           前端领域
+---------+        +---------+
|  CODEX  |        | GEMINI  |
| (权威)  |        | (权威)  |
| API设计  |        | 组件设计 |
| 算法实现 |        | 样式布局 |
| DB优化   |        | 动画交互 |
+----+----+        +----+----+
     |  只输出Diff       |  只输出Diff
     +--------+----------+
              v
       +----------+
       |  CLAUDE  |  <- 代码主权者
       | 综合重构  |  <- 唯一能写文件的
       | 质量把关  |
       +----------+

铁律:外部模型零文件写入权限,所有 Edit/Write 只能由 Claude 执行。

选择命令的决策树

你的任务是什么?
+-- 日常功能/Bug修复 -> /orchestrate feature/bugfix
+-- 纯前端 UI -> /frontend
+-- 纯后端逻辑 -> /backend
+-- 全栈复杂功能
|   +-- 想一步到位 -> /workflow
|   +-- 先规划再执行 -> /ccg:plan -> /ccg:execute
+-- 重构/安全审计 -> /orchestrate refactor/security

编排的5个陷阱

陷阱 说明 正确做法
超时 Codex/Gemini可能需要10分钟+ 设置 timeout: 600000
代码主权混淆 以为外部模型可以直接改文件 外部模型只出Diff
越级执行 /ccg:plan阶段偷偷写代码 规划阶段严禁写代码
需求不完整 评分<7就开干 强制停下来问清楚
信任倒置 前端问题听Codex的 各领域只听对应权威

教练2:Hooks自动化深度分析

完整的Hook事件生命周期

会话开始
  |
  v SessionStart
  +-- 加载上次会话摘要
  +-- 检测包管理器(npm/pnpm/yarn/bun)
  +-- 列出已学技能数量
  |
  v [工具调用循环,每次都走这个流程]
  |
  |  PreToolUse(工具执行前)
  |  +-- Dev Server阻断器 -> 匹配Bash -> exit 2 阻断
  |  +-- Tmux提醒 -> 匹配Bash -> exit 0 警告
  |  +-- Git Push审查提醒 -> 匹配Bash -> exit 0 提示
  |  +-- 非标文档警告 -> 匹配Write -> exit 0 警告
  |  +-- 压缩建议(每50次) -> 匹配Edit|Write -> exit 0 提示
  |  +-- 持续学习观察 -> 匹配* -> 异步捕获
  |       |
  |       v [工具实际执行]
  |       |
  |  PostToolUse(工具执行后)
  |  +-- PR URL记录器 -> 匹配Bash -> 提取PR链接
  |  +-- 构建分析 -> 匹配Bash -> 异步后台分析
  |  +-- JS/TS自动格式化 -> 匹配Edit -> Biome/Prettier
  |  +-- TypeScript类型检查 -> 匹配Edit -> tsc --noEmit
  |  +-- console.log警告 -> 匹配Edit -> 实时提示行号
  |  +-- 持续学习观察 -> 匹配* -> 异步捕获(含输出)
  |       |
  |       v
  |  Stop(Claude回复完成后)
  |  +-- console.log全局审计 -> 扫描所有本次修改的文件
  |
  v [用户执行 /compact 时]
  PreCompact -> 记录压缩日志 + 标注会话文件
  |
  v 会话结束
  SessionEnd
  +-- Hook1: 提取摘要 -> 写入 ~/.claude/sessions/
  +-- Hook2: 评估是否值得提取模式 -> 信号持续学习系统

Hook数据流原理

Claude Code
    | 通过 stdin 传入 JSON
    v
+-----------------------------+
|  Hook 脚本读取 stdin        |
|  处理逻辑 -> 决策           |
|                             |
|  stdout: 必须原样透传JSON   | <- 不传回去工具就挂了!
|  stderr: 警告信息给用户看   |
|                             |
|  exit 0: 放行               |
|  exit 2: 阻断(仅Pre)     |
+-----------------------------+

实用Hook配方

配方 效果
文件超800行阻断 Write + exit 2 如果 lines > 800
新源文件提醒写测试 Write + 检测对应 .test 文件是否存在
Python自动格式化 Edit + 检测 .py -> 执行 ruff format
TODO/FIXME警告 Edit + 正则检测 new_string

教练3:学习进化系统深度分析

v1 -> v2 -> v2.1 进化史

v1 (基础版)                v2 (本能架构)              v2.1 (项目隔离)
--------------            --------------            --------------
Stop Hook触发              Pre/PostToolUse           + 项目作用域
(50-80%可靠)               (100%可靠)                (默认隔离)

整个会话提取                原子"本能"               + 晋升机制
(粗粒度)                   (细粒度)                  (project->global)

无置信度                    0.3-0.9评分              + 跨项目检测

全局存储                    全局存储                  + 每项目独立存储
(污染风险)                 (污染风险)                (安全隔离)

Instinct(本能)的完整生命周期

1步:观察捕获
  每次工具调用 -> observe.sh -> observations.jsonl
  (截断到5000字符,超10MB自动归档)

第2步:项目检测
  git remote URL -> SHA256哈希前12位 = project_id
  (同一仓库不同机器 = 同一ID,跨机器可移植)

第3步:模式分析(后台Haiku代理)
  4类模式检测:
  * 用户纠正 -> "不用X,用Y"
  * 错误解决 -> 同一错误同样修复多次
  * 重复工作流 -> 同样的工具序列反复出现
  * 工具偏好 -> 总是Grep->Edit而不是直接Edit

第4步:本能文件创建
  写入 YAML frontmatter + Markdown
  包含:id、trigger、confidence、domain、scope、evidence

第5步:本能成熟
  +------------------------------------+
  | 置信度增长规则:                     |
  |  观察1-2-> 0.3 (试探)            |
  |  观察3-5-> 0.5 (中等)            |
  |  观察6-10-> 0.7 (强,自动应用)    |
  |  观察11+次 -> 0.85 (核心行为)        |
  |                                     |
  |  确认观察 -> +0.05                   |
  |  矛盾观察 -> -0.10                   |
  |  无观察/周 -> -0.02 (自然衰减)       |
  +------------------------------------+

第6步:晋升(project -> global)
  条件:2+个项目中出现 + 平均置信度 >= 0.8
  命令:/promote

第7步:进化(instinct -> skill/command/agent)
  命令:/evolve
  * 相关本能聚类 -> 自动生成高级结构
  * 工作流本能 -> Command(/new-table 等)
  * 行为本能 -> Skill(functional-patterns 等)
  * 多步骤本能 -> Agent(debugger 等)

项目隔离的关键设计

~/.claude/homunculus/
+-- instincts/personal/          <- 全局本能(跨项目通用)
|   +-- always-validate-input.yaml
+-- projects/
    +-- a1b2c3d4e5f6/           <- React项目
    |   +-- instincts/personal/
    |       +-- use-react-hooks.yaml   <- 不会污染Django项目
    +-- f6e5d4c3b2a1/           <- Django项目
        +-- instincts/personal/
            +-- use-drf-serializers.yaml  <- 不会污染React项目

冲突解决: 同ID本能,项目作用域优先于全局(项目覆盖全局)。


五、新手友好知识点图解

5.1 Claude Code 的"公司架构"比喻

把整个系统想象成一家软件公司:

+-------------------------------------------------------------+
|                    你(CEO / 老板)                            |
|                 下达指令:"帮我实现用户认证"                    |
+----------------------------+--------------------------------+
                             |
                             v
+-------------------------------------------------------------+
|              Claude Code(总经理 / COO)                      |
|                                                              |
|  读取公司制度(Rules)-> 查看操作手册(Skills)                 |
|  -> 安排对应部门(Agents)干活                                 |
|  -> 自动化流程(Hooks)在后台默默运行                          |
|  -> 需要外部资源时通过 MCP 对接                                |
+----------------------------+--------------------------------+
                             |
          +------------------+------------------+
          |                  |                  |
          v                  v                  v
   +----------+       +----------+       +----------+
   | 规划部    |       | 研发部    |       | 质量部    |
   | planner  |       | tdd-guide|       | reviewer |
   | architect|       |          |       | security |
   +----------+       +----------+       +----------+

   用 Opus 模型        用 Sonnet 模型       用 Sonnet 模型
   (高管级思考)        (中层级执行)         (中层级审查)

5.2 Model 选择 = 雇人选择

为什么有 Opus、Sonnet、Haiku 三种模型?

+-------------+   +-------------+   +-------------+
|   Opus 4.6  |   | Sonnet 4.6  |   |  Haiku 4.5  |
| ============|   | ============|   | ============ |
|  首席架构师  |   |  高级工程师  |   |   初级工程师  |
|             |   |             |   |              |
| 能力:最强   |   | 能力:很强   |   | 能力:够用    |
| 速度:较慢   |   | 速度:适中   |   | 速度:最快    |
| 成本:最贵   |   | 成本:适中   |   | 成本:最便宜  |
|             |   |             |   |              |
| 适合:       |   | 适合:       |   | 适合:        |
| * 架构决策   |   | * 写代码     |   | * 后台分析    |
| * 复杂规划   |   | * 代码审查   |   | * 格式化任务  |
| * 深度推理   |   | * 测试编写   |   | * 持续学习    |
+-------------+   +-------------+   +-------------+

核心原则:用最合适(而非最贵)的模型做对应的事
不需要首席架构师去跑格式化,也不要让初级员工做架构决策

5.3 什么是 MCP?(Model Context Protocol)

想象 Claude Code 是一个人,他坐在一间封闭的办公室里。
他很聪明,但:
  x 看不到互联网
  x 打不开 GitHub
  x 连不上数据库
  x 部署不了代码

MCP 就是给这间办公室装上的"电话线":

+------------------------------------------------------------+
|                    Claude Code 的办公室                      |
|                                                             |
|                 +--------------+                            |
|                 |  Claude Code |                            |
|                 +------+-------+                            |
|                        |                                    |
|              MCP 协议(统一接口标准)                         |
|                        |                                    |
|    +-------+-------+---+----+--------+-----------+         |
|    |       |       |        |        |           |         |
|    v       v       v        v        v           v         |
| +------++------++------++------++------++----------+       |
| |GitHub||Supa- ||Vercel||Exa   ||Click-||Context7  |       |
| |      ||base  ||      ||搜索  ||House ||(文档查询)|       |
| |PR/   ||数据库||部署  ||网络  ||分析  ||最新API  |       |
| |Issue ||操作  ||管理  ||搜索  ||查询  ||文档     |       |
| +------++------++------++------++------++----------+       |
|  电话1   电话2   电话3   电话4   电话5   电话6              |
+------------------------------------------------------------+

每根"电话线"就是一个 MCP Server。
mcp-configs/mcp-servers.json 就是电话簿 -- 记录了每根线怎么拨。

注意:同时开启不超过10个MCP,否则会占用太多"上下文窗口"

5.4 什么是"上下文窗口"?为什么要 /compact?

上下文窗口 = Claude 的"工作记忆"(短期记忆)

想象 Claude 的大脑是一张有限大小的桌子:

开始时:桌子很空
+--------------------------------------------------+
|  [你的指令] [CLAUDE.md] [Rules]   ...空...        |
|  ========...                                      |
|  已用 20%                          剩余 80%       |
+--------------------------------------------------+

工作一段时间后:桌子快满了
+--------------------------------------------------+
|  [指令][Rules][文件1][文件2][修改1][修改2]...      |
|  ================================================ |
|  已用 90%                          剩余 10%       |
+--------------------------------------------------+

到了这个阶段,Claude 可能会:
  * 忘记之前的对话内容
  * 做出不一致的决策
  * 自动触发压缩(可能丢失重要上下文)

/compact 的作用 = 整理桌面
+--------------------------------------------------+
|  [压缩后的摘要]  ...空...                         |
|  ========...                                      |
|  已用 15%                          剩余 85%       |
+--------------------------------------------------+

这就是为什么 suggest-compact.js50次工具调用会提醒你:
"你的桌子快满了,要不要整理一下?"

5.5 stdin / stdout / stderr 是什么?

这是 Hook 系统的基础:

每个程序都有3"管道",可以理解为3条传送带:

  stdin  (标准输入)  <- 数据进来的管道
    就像:收件箱
    Hook 从这里读取 Claude 传过来的 JSON 数据

  stdout (标准输出)  <- 数据出去的管道
    就像:发件箱
    Hook 必须把原始 JSON 原样传回去
    注意:如果不传回去,Claude 的工具调用会失败!

  stderr (标准错误)  <- 消息/警告管道
    就像:便签纸
    Hook 把警告信息写到这里,用户会看到
    不影响数据传输

具体流程:

Claude Code                    Hook 脚本                 用户
    |                              |                      |
    |---- stdin (JSON数据) ------->|                      |
    |                              | 处理逻辑...           |
    |                              |                      |
    |<--- stdout (原样JSON) ------|                      |
    |                              |                      |
    |                              |---- stderr (警告) -->|
    |                              |  "[Hook] 超800行!"   |
    |                              |                      |
    |<--- exit code --------------|                      |
    |     exit 0 = 放行            |                      |
    |     exit 2 = 阻断            |                      |

5.6 Exit Code(退出码)是什么?

每个程序结束时都会返回一个数字,告诉调用者"我执行得怎么样":

+----------+--------------------------------------------+
| Exit Code| 含义                                       |
+----------+--------------------------------------------+
|    0     | 一切正常,继续执行                          |
|          | 用于:警告(显示信息但不阻止操作)           |
|----------+--------------------------------------------+
|    2     | 阻断!停止工具执行                          |
|          | 仅 PreToolUse 有效                         |
|          | 用于:危险操作必须阻止(如非tmux跑dev)     |
|----------+--------------------------------------------+
|  其他    | 出错了,但不阻断                            |
| (1,3..)  | Claude 会记录错误但继续执行                 |
+----------+--------------------------------------------+

类比:
  exit 0 = 保安说"你可以进去,但注意安全"  <- 放行+提醒
  exit 2 = 保安说"不行,禁止通行"          <- 硬拦截
  exit 1 = 保安自己摔倒了,但不影响你进去   <- 出错但不阻断

5.7 Matcher(匹配器)工作原理

匹配器决定 Hook 在哪些工具调用时触发:

  "*"        -> 所有工具都触发
               Edit Y  Write Y  Bash Y  Read Y  Grep Y

  "Bash"     -> 只在 Bash 工具时触发
               Edit x  Write x  Bash Y  Read x  Grep x

  "Edit"     -> 只在 Edit 工具时触发
               Edit Y  Write x  Bash x  Read x  Grep x

  "Edit|Write" -> Edit 或 Write 时触发(正则表达式的"或")
               Edit Y  Write Y  Bash x  Read x  Grep x

  "|" 就是正则里的"或"符号

5.8 同步 vs 异步 Hook

同步(默认):Hook 执行完才能继续
-------------------------------------------
时间 ---------------------------------------->

Claude:  [准备工具]--等--等--等--[继续执行]
                      |           ^
Hook:                 +--[执行]--+

如果 Hook 执行很慢,Claude 就被堵住了


异步("async": true):Hook 在后台跑,不阻塞
-------------------------------------------
时间 ---------------------------------------->

Claude:  [准备工具]--[继续执行]--[继续工作]--
                      |
Hook:                 +--[后台执行中...]--[完成]

适合:持续学习的 observe.sh(每次都跑,不能阻塞主流程)
适合:构建分析(耗时,但不需要立即结果)

5.9 什么是 JSONL 格式?

JSON = 一个完整的数据对象
{
  "name": "张三",
  "age": 25,
  "skills": ["JS", "Python"]
}

JSONL = JSON Lines = 每行一个JSON对象
{"event":"tool_start","tool":"Edit"}    <- 第1行
{"event":"tool_done","tool":"Edit"}     <- 第2行
{"event":"tool_start","tool":"Bash"}    <- 第3行
{"event":"tool_done","tool":"Bash"}     <- 第4行

为什么用 JSONL 而不是 JSON?
  Y 可以一行一行追加(append),不用读取整个文件
  Y 文件再大也不怕,可以一行一行读
  Y 某一行坏了不影响其他行
  x 普通 JSON 每次追加要先读取->解析->修改->全量写入(大文件很慢)

这就是为什么 observations.jsonl 用这个格式 --
每次工具调用追加一行,几千次调用也不卡。

5.10 SHA256 哈希 = 指纹

为什么项目检测用 SHA256?

SHA256 是一个"指纹函数",任何输入都会产生唯一的固定长度输出:

输入: "https://github.com/oker/my-react-app.git"
  |
  v SHA256
输出: "a1b2c3d4e5f6..."  <- 取前12位作为 project_id

关键特性:
  Y 同样的输入 -> 永远同样的输出
    你的Mac上:git remote = 同一URL -> 同一hash
    同事的Linux上:同一URL -> 同一hash
    -> 跨机器可移植!

  Y 不同的输入 -> 几乎不可能相同
    React项目URL -> "a1b2c3..."
    Django项目URL -> "f6e5d4..."
    -> 项目之间不会混淆!

  Y 不可逆:从hash推不出原始URL
    -> 隐私安全

5.11 YAML Frontmatter 是什么?

很多文件(Agent、Skill、Instinct)的开头都有这个结构:

+-- YAML Frontmatter(元数据区)----+
|  ---                              |
|  name: planner                    |
|  description: Expert planning...  |
|  model: opus                      |
|  tools: [Read, Grep, Glob]       |
|  ---                              |
+-----------------------------------+
+-- Markdown 正文(内容区)---------+
|  # Planner Agent                  |
|                                   |
|  你是一个规划专家...               |
|  ...具体的指令和模板...            |
+-----------------------------------+

两个 "---" 之间的部分就是 YAML Frontmatter:
  * 用 YAML 格式(key: value)
  * 定义了这个文件的"属性"
  * 系统读取这些属性来决定怎么使用这个文件

类比:
  YAML Frontmatter = 书的封面信息(作者、出版社、ISBN)
  Markdown 正文     = 书的正文内容

5.12 Unified Diff Patch 是什么?

在多模型协作中,外部模型只能输出 Unified Diff Patch。
这是一种"补丁格式",描述文件的修改方式:

--- a/src/auth.ts          <- 修改前的文件
+++ b/src/auth.ts          <- 修改后的文件
@@ -10,6 +10,8 @@          <- 修改的位置(第10行开始)
 import { User } from './models'

 export function login(email: string, password: string) {
+  // 输入验证                    <- "+" 开头 = 新增的行
+  if (!email) throw new Error('Email required')
   const user = await findUser(email)
-  return user                    <- "-" 开头 = 删除的行
+  return generateToken(user)     <- "+" 替换的新行
 }

看懂规则:
  无符号 = 上下文(没改的行)
  +     = 新增或替换的行(绿色)
  -     = 删除的行(红色)

为什么外部模型只输出 Diff 而不直接改文件?
  -> 安全!Claude 审查 Diff 后才决定是否采纳
  -> 相当于:外部专家只能提"修改建议"Claude 是最终拍板的人

5.13 TDD 的 Red-Green-Refactor 图解

TDD = Test-Driven Development(测试驱动开发)

传统方式(先写代码,后补测试):
  写代码 -> 写测试 -> "测试怎么老过不了..."
  问题:测试变成了给代码"打补丁",容易遗漏边界情况

TDD方式(先写测试,再写代码):

  RED(红灯)
  +----------------------------------+
  | 写一个测试                        |
  | 运行 -> 失败 x                    |
  | 这是对的!因为代码还没写           |
  +----------------+-----------------+
                   |
                   v
  GREEN(绿灯)
  +----------------------------------+
  | 写最少的代码让测试通过             |
  | 运行 -> 通过 Y                    |
  | 不要多写!刚好通过就行             |
  +----------------+-----------------+
                   |
                   v
  REFACTOR(重构)
  +----------------------------------+
  | 改善代码质量(提取常量、优化结构) |
  | 运行 -> 仍然通过 Y               |
  | 有测试保护,重构不怕改坏           |
  +----------------+-----------------+
                   |
                   v
              回到 RED(下一个功能点)

循环往复,直到所有功能点都覆盖
覆盖率要求:80%+(关键业务逻辑要100%)

为什么先写测试这么重要?
  1. 迫使你先想清楚"要什么",而不是"怎么做"
  2. 每一行代码都有存在的理由(为了通过某个测试)
  3. 重构时有安全网,改坏了测试会立刻告诉你

5.14 置信度衰减 = 记忆遗忘曲线

本能的置信度不是只升不降的,它会"遗忘":

置信度
  1.0 |
      |
  0.9 |                    --- 反复观察到,持续增长
      |                 ---
  0.8 |---- 晋升线 ----/-------------------------------
      |             ---
  0.7 |          ---
      |       ---
  0.6 |    ---
      | ---
  0.5 |-
      |
  0.4 |
      |              如果长期不再观察到...
  0.3 |                  \
      |                   \-- 每周 -0.02
  0.2 |                    \
      |                     \
  0.1 |                      \-- 最终淡出
      |
  0.0 |-----------------------------------------1周  第2周  第3周  第4周  ...  第20周

这个设计很巧妙:
  Y 你换了编码风格?旧习惯会自然淡出
  Y 不用手动删除过时的本能
  Y 真正核心的行为(一直在用的)不会衰减
  Y 类似人脑的"遗忘曲线" -- 不用的知识自然遗忘

5.15 /orchestrate feature 完整动画

/orchestrate feature "添加购物车功能"

阶段1  Planner Agent (Opus)                              [只读]
+----------------------------------------------+
|  读取代码库结构                                |
|  分析已有的商品模块                            |
|  制定实施计划:                                |
|     Phase A: 购物车数据模型                    |
|     Phase B: API endpoints                    |
|     Phase C: 前端组件                          |
|  输出 HANDOFF -> tdd-guide                    |
+--------------------------+-------------------+
                           | 传递
                           v
阶段2  TDD-Guide Agent (Sonnet)                          [可写]
+----------------------------------------------+
|  RED:   写 cart.test.ts(测试先行)            |
|  GREEN: 写 cart.ts(最小实现通过测试)          |
|  REFACTOR: 重构代码                           |
|  输出 HANDOFF -> code-reviewer               |
|     附带:修改的文件列表 + 测试覆盖率          |
+--------------------------+-------------------+
                           | 传递
                           v
阶段3  Code-Reviewer Agent (Sonnet)                      [只读]
+----------------------------------------------+
|  审查代码质量                                  |
|  检查命名规范                                  |
|  检查错误处理                                  |
|  检查性能问题                                  |
|  输出 HANDOFF -> security-reviewer            |
|     附带:发现的问题列表 + 严重程度             |
+--------------------------+-------------------+
                           | 传递
                           v
阶段4  Security-Reviewer Agent (Sonnet)                  [可写修复]
+----------------------------------------------+
|  OWASP Top 10 检查                            |
|  输入验证检查                                  |
|  SQL注入风险检查                               |
|  XSS攻击风险检查                               |
|  输出最终报告                                  |
|     结论:SHIP / NEEDS WORK / BLOCKED         |
+----------------------------------------------+

六、整体全景图

+======================================================================+
|                    Everything Claude Code 全景图                       |
+======================================================================+
|                                                                       |
|  你 ---- /command -----> Claude Code 主进程                           |
|                            |                                          |
|              +-------------+-------------+                            |
|              |             |             |                             |
|         Rules 规则     Skills 技能    CLAUDE.md                        |
|        (必须遵守)     (参考手册)     (项目说明)                         |
|              |             |             |                             |
|              +-------------+-------------+                            |
|                            |                                          |
|         +------------------+------------------+                       |
|         |                  |                  |                        |
|     Agents 代理        Hooks 钩子        MCP 外部连接                  |
|    (专业团队)         (自动化流程)       (电话线)                       |
|         |                  |                  |                        |
|    +----+----+       +-----+-----+      +----+----+                   |
|    |规划组    |       |编辑时      |      |GitHub   |                   |
|    |planner  |       |*自动格式化 |      |Supabase |                   |
|    |architect|       |*类型检查   |      |Vercel   |                   |
|    |         |       |*console警告|      |Context7 |                   |
|    |研发组    |       |           |      |Exa搜索  |                   |
|    |tdd-guide|       |会话时      |      +---------+                   |
|    |         |       |*加载上下文 |                                    |
|    |质量组    |       |*保存摘要   |                                    |
|    |reviewer |       |*持续学习   |                                    |
|    |security |       +-----------+                                    |
|    +---------+             |                                          |
|         |           持续学习系统                                       |
|         |           +------+------+                                   |
|         |           | 观察 -> 分析 |                                   |
|         |           | -> 本能 -> 进化|                                  |
|         |           +-------------+                                   |
|         |                                                             |
|    +----+---- /orchestrate ----+                                      |
|    |  planner -> tdd -> reviewer | <- 单模型编排                       |
|    |  -> security -> 最终报告    |                                     |
|    +-----------------------------+                                    |
|                                                                       |
|    +---- /workflow (multi-*) --+                                      |
|    |  Claude + Codex + Gemini  | <- 多模型协作                         |
|    |  6阶段全栈开发流程         |                                      |
|    +---------------------------+                                      |
|                                                                       |
+======================================================================+

Claude CLI 从安装到使用完整教程(Windows 版)

Claude Code 是 Anthropic 官方推出的命令行 AI 编程助手,可以直接在终端中与 Claude 对话,帮助你完成代码编写、调试、重构等任务。本教程带你在 Windows 系统上从零开始,快速上手 Claude CLI。


一、环境准备

在安装 Claude CLI 之前,需要确保你的系统满足以下条件:

  • Node.js:版本 18 及以上
  • npm:包管理工具(随 Node.js 一同安装)
  • 操作系统:Windows 10 / Windows 11
  • 终端:推荐使用 PowerShell 5.1+Windows Terminal
  • 网络:能正常访问 Anthropic API,如果有自备的公益API也行,例如anyrouter

安装 Node.js

前往 nodejs.org 下载最新 LTS 版本的 Windows 安装包(.msi),按向导完成安装,安装时勾选"自动安装必要工具"选项。

安装完成后,打开 PowerShell 验证版本:

node --version
# 输出示例:v24.14.0

npm --version
# 输出示例:11.9.0

01-flowchart-install-flow.png

二、安装 Claude CLI

打开 PowerShell(以管理员身份运行),执行全局安装命令:

npm install -g @anthropic-ai/claude-code

提示:如果安装时报权限错误,请右键点击 PowerShell 选择"以管理员身份运行"后重试。

安装完成后,验证安装是否成功:

claude --version

更新到最新版本

npm update -g @anthropic-ai/claude-code

解决 PATH 问题

如果提示 claude 不是可识别的命令,需要将 npm 全局包路径加入系统 PATH:

# 查看 npm 全局包安装路径
npm config get prefix

将输出的路径(如 C:\Users\你的用户名\AppData\Roaming\npm)添加到系统环境变量 PATH 中:

  1. 右键"此电脑" → "属性" → "高级系统设置" → "环境变量"
  2. 在"用户变量"中找到 Path,点击"编辑"
  3. 点击"新建",粘贴上述路径
  4. 确定保存后,重新打开 PowerShell 使其生效

三、配置 API Key

Claude CLI 需要 Anthropic API Key 才能正常工作。

获取 API Key

  1. 访问 console.anthropic.com
  2. 注册或登录账号
  3. 进入 API Keys 页面
  4. 点击 Create Key 生成新的 API Key

设置环境变量(PowerShell)

临时设置(仅当前会话有效):

$env:ANTHROPIC_API_KEY = "sk-ant-xxxxxxxxxxxxxxxx"

永久设置(推荐):

方法一:通过 PowerShell 命令写入用户环境变量:

[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-xxxxxxxxxxxxxxxx", "User")

方法二:通过系统界面设置:

  1. 右键"此电脑" → "属性" → "高级系统设置" → "环境变量"
  2. 在"用户变量"区域点击"新建"
  3. 变量名填写 ANTHROPIC_API_KEY,变量值填写你的 API Key
  4. 确定保存,重新打开 PowerShell 使其生效

验证环境变量已生效:

echo $env:ANTHROPIC_API_KEY

四、基础使用

启动交互模式

在项目目录下打开 PowerShell,运行 claude,进入交互式对话:

cd C:\Users\你的用户名\projects\your-project
claude

进入后,你会看到提示符,可以直接输入问题或指令。

单次查询模式

使用 -p 参数执行一次性查询:

claude -p "解释这个项目的目录结构"

处理文件

让 Claude 分析或修改特定文件:

claude -p "审查 src/index.js 中的代码,找出潜在的 bug"

五、核心功能演示

代码生成

> 用 Python 写一个读取 CSV 文件并计算每列平均值的脚本

代码解释

> 解释 @src/utils/parser.ts 这个文件的作用

代码重构

> 重构 @components/Button.jsx,将 class 组件改为函数组件,并添加 TypeScript 类型

调试协助

> 我的应用报错:TypeError: Cannot read property 'map' of undefined,帮我分析原因

生成测试

02-infographic-features-overview.png

> 为 @src/api/user.js 中的所有函数生成单元测试

六、常用命令与快捷键

操作 命令 / 快捷键
启动 Claude claude
单次查询 claude -p "问题"
查看帮助 claude --help
清空对话 /clear
退出 /exitCtrl+C
查看历史 /history

常用 Slash 命令

  • /help — 查看所有可用命令
  • /clear — 清除当前对话上下文
  • /compact — 压缩对话历史以节省 Token
  • /model — 切换使用的模型
  • /cost — 查看本次会话的 Token 消耗

七、项目上下文管理

CLAUDE.md 文件

在项目根目录创建 CLAUDE.md 文件,Claude 会自动读取其中的内容作为项目背景:

# 项目说明

这是一个 React + TypeScript 的电商平台前端项目。

## 技术栈
- React 18
- TypeScript 5
- Tailwind CSS
- Zustand 状态管理

## 编码规范
- 使用函数组件和 Hooks
- 组件文件使用 PascalCase 命名
- 工具函数使用 camelCase 命名

在 PowerShell 中快速创建该文件:

New-Item -Path "CLAUDE.md" -ItemType File
notepad CLAUDE.md

引用文件

在对话中使用 @文件路径 引用具体文件:

> 参考 @src/components/Card.tsx 的风格,创建一个新的 Modal 组件

八、高级配置

配置文件

Claude CLI 支持通过配置文件自定义行为,配置文件位于:

  • 全局C:\Users\你的用户名\.claude\settings.json
  • 项目.claude\settings.json

在 PowerShell 中查看全局配置目录:

explorer "$env:USERPROFILE\.claude"

示例配置:

{
  "model": "claude-opus-4-6",
  "theme": "dark",
  "autoApprove": false
}

权限模式

Claude CLI 有三种权限模式:

  • 默认模式:每次文件修改都需要用户确认
  • 自动批准--dangerously-skip-permissions 自动执行所有操作(谨慎使用)
  • 只读模式:仅读取文件,不做修改

九、最佳实践

  1. 使用 Windows Terminal:比默认 PowerShell 窗口体验更好,支持多标签和彩色输出
  2. 始终在项目根目录启动:这样 Claude 能更好地理解项目结构
  3. 编写详细的 CLAUDE.md:提供技术栈、编码规范等背景信息
  4. 明确描述需求:越具体的指令,生成的结果越准确
  5. 分步执行复杂任务:将大任务拆分为小步骤,逐一确认
  6. 定期使用 /compact:长对话时压缩上下文,避免 Token 超限

十、常见问题

Q: 提示 claude 不是内部或外部命令

检查 npm 全局包路径是否在 PATH 中,参考"安装 Claude CLI"章节中的 PATH 配置步骤。

# 查看 npm 全局包路径
npm config get prefix

Q: PowerShell 提示脚本执行被禁止?

以管理员身份运行 PowerShell,执行以下命令允许本地脚本运行:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Q: API 请求失败,返回 401 错误?

检查 ANTHROPIC_API_KEY 环境变量是否正确设置,或 Key 是否已过期:

echo $env:ANTHROPIC_API_KEY

Q: 响应速度慢?

可以切换到更快的模型,如 claude-haiku-4-5

/model claude-haiku-4-5-20251001

总结

Claude CLI 是一个强大的 AI 编程助手,将 Claude 的能力无缝集成到你的开发工作流中。通过本教程,你已经掌握了在 Windows 上从安装配置到日常使用的完整流程。建议在实际项目中多加练习,充分利用 Claude 的能力提升开发效率。

更多文档请参考官方文档:docs.anthropic.com/claude-code

如果你喜欢本教程,记得点赞+收藏!关注我获取更多AI相关干货

150 万人连夜逃离 ChatGPT,这份 AI 时代的搬家指南必须收好

超过 150 万人正在公开表态,抵制 ChatGPT。

他们不仅要走,还要带走自己在这台机器里留下的所有记忆,转头投奔 Claude。

▲2 月初,Claude 在 App Store 还在 42 名徘徊,而如今,它在 80 多个地区的 iOS 效率榜单中稳居前十,在美区总榜第一

就在这几天,App Store 的排行榜又发生了一些变化,没有模型更新和发布会,Claude 就这样突然冲到了应用商店的榜首。

倒不是因为 Claude 突然变聪明了,只是它的对手现在正经历一场信任危机与用户大逃亡。有网友问奥特曼对这个排行榜现在觉得开心了吗,奥特曼说不开心,还贴心地提醒她记得更新 Claude。

▲QuitGPT 官网,显示有超过 150 万用户登记已经采取了抵制行动|https://quitgpt.org/

据抵制 ChatGPT 的相关网站数据显示,目前已有超过 150 万名用户宣誓退出这款曾经的 AI 圈顶流。他们正打包自己的数据,连夜奔向 Claude。

有意思的是,这波用户迁移甚至一度把Claude挤到了极限。

Anthropic向媒体确认,由于最近一周需求「前所未有」,Claude的部分面向消费者服务曾短暂宕机。我们的Claude账户,聊天记录到现在都还没恢复过来

▲ Claude 服务实时状态| https://status.claude.com/

这一切的导火索,自然还是国外的网友们认为 OpenAI 彻底撕下了「Open」的伪装,选择了和五角大楼的合作,没有坚守住所谓是「造福全人类」的底线。

不管背后的动机为何,在这个时代,弃用一个 AI 工具,远比卸载一个普通的 App 要复杂得多

尤其是对很多老用户来说,离开 ChatGPT 并不是一个轻松的决定。过去,我们更换浏览器,只需导出一个书签;我们更换手机,只需云端同步,就连苹果新版 iOS 都支持和安卓无缝换机了。但在大模型时代,我们与 ChatGPT 朝夕相处产生的那条长长的「记忆(Memory)」,早已成了我们不可分割的一部分。

▲ChatGPT 保存的记忆

直接卸载后,每次面对一个新的 AI 时,都要重新向它解释:我叫什么,在哪个城市,工作、写作风格,我讨厌哪种格式的排版,我正在推进什么项目,等等……

如果你最近也在考虑切换到不同的 AI 工具,不妨一起看看这份迁移指南,

向即将要退出的 AI 索要全部档案

千万不要直接注销账号。

对 ChatGPT 来说,我们有几种方式可以带走数据。最直接的方法,是提取它的「记忆」。打开 ChatGPT,点击「Settings(设置)」,找到「Personalization(个性化)」,进入「Memory(记忆)」模块。

点击「Manage(管理)」,我们会看到 ChatGPT 这些年偷偷记下关于你的所有细节。删掉那些已经过时的,复制你想保留的核心偏好。

▲ChatGPT 内提供的数据导出功能

当然,想要带走全部家当,也可以选择批量导出。依然在设置中,找到「Data Controls(数据控制)」,点击「Export Data(导出数据)」。

ChatGPT 会将我们的聊天记录打包成文本,或 JSON 文件,然后发送一个下载链接到我们的注册邮箱。

▲Gemini 存储的用户信息,包括全部的聊天记录,和自定义的指令|https://gemini.google.com/saved-info

对于一些没有数据导出功能,甚至是「记忆」这个选项都找不到的 AI,又该去哪里导出呢?

包括对 ChatGPT 来说,其实仅导出这份聊天记录也是不够的。大多数时候,在 ChatGPT 里留下的几十兆聊天记录压缩包,对我们的新 AI 毫无意义。因为 AI 平台真正绑定的,是那些死板的数据之外的「语境(Context)」。

▲The “secret sauce” behind OpenClaw: Soul.md | Peter Steinberger and Lex Fridman

就像之前 OpenClaw 创始人接受 Lex Fridman 采访时提到的一样,OpenClaw 背后的秘密武器是用来定义我们与 AI 交互的 Soul.md

因此我们还需要让 ChatGPT 或者其他 AI,主动交出它对我们的「用户画像」。

▲ Claude 官方提供的迁移指南:https://claude.com/import-memory

在这波「退出 ChatGPT」的热潮找中,Claude 也是趁火打劫,官方直接发布了一段指导用户如何从竞品那里导入记忆的教程。

现在,即便是免费版 Claude,也已经全面开放了记忆功能,它能接受我们所有的前置语境。

于是,我们可以直接把下面这段 Prompt 喂给即将被你抛弃的 AI。

我准备迁移到另一个服务,需要导出我的数据。请列出你存储的关于我的所有记忆,以及你从过去的对话中了解到的关于我的任何上下文。请将所有内容输出在一个代码块中,以便我轻松复制。 确保涵盖以下所有内容,并尽可能保留我的原话:我对你回复方式的指示(语气、格式、风格);个人详细信息(姓名、位置、工作、兴趣);项目和目标;我使用的工具和语言;我的偏好;以及任何其他上下文。不要总结或遗漏。

敲下回车发送,ChatGPT 或者你之前在用的 AI 就会列出它对你的所有认知。

▲在豆包内使用这段提示词,豆包会清晰地列出过去我和它的对话情况

但很多极客发现,Claude 官方提供的这套词还是太「温柔」了。

知名博主 Jonathan Edwards 在他的 Substack 上公布了一套更硬核的提示词。他的实测证明,比起官方教程在设置里能直接看到的那些标签,Edwards 的提示词能获得更多底层的个人细节。

我希望您根据您所了解的所有信息,为我创建一个全面的个人背景文件。我想保留一份我们共同建立的背景便携副本——包括我的偏好、工作流程、项目,以及您了解到的关于我如何工作的任何其他内容。请从您的记忆系统、我们的对话记录、我的自定义指令以及您发现的任何模式中提取信息。

使用以下部分结构化输出。跳过任何不适用于我的部分。

<身份>
姓名,职位或角色,公司或组织
我每天实际做什么(不仅仅是头衔)
行业和领域
</身份>

<技术环境>
操作系统和硬件
我经常使用的软件、工具和平台
编程语言或技术技能(如适用)
您知道的具体版本、配置或设置
</技术环境>

<当前项目>
我目前正在进行中的工作
您知道的短期目标和长期目标
经常性任务或工作流程
</当前项目>

<专业知识>
我深入了解的话题
我正在积极学习的话题
初学者领域或者需要额外解释的问题
</专业知识>

<沟通偏好>
我的回复结构喜好(长度,格式,语气)
我要求您做或者不要做的一些事情
格式偏好(列表 vs 散文,技术深度等)   重复纠正或者让我反感的问题
</沟通偏好>

<写作风格>
我的写作方式(正式, 随意, 技术性等)   声音特征观察到的信息   提到过的一些具体风格规则
</写作风格>

<关键人物>
合作者, 团队成员 或客户,我经常提到的人物 报告结构 或重要职业关系 曾请求帮助与之交流的人物
</关键人物 >

<个人背景 >
位置 和 时区 与我们工作相关 的兴趣爱好 或细节 限制条件 或 偏好的问题 (无障碍需求 , 日程安排 等 )
</个人背景 >

<固定指令 >
来自我的自定义说明书 或 系统提示 的内容 一直遵循 的规则 已成为永久指令 的重复更正
</固定指令 >

< 工作流模式 >
通常如何 使用你 (头脑风暴 , 编辑 , 编码 ,研究 等 ) 常见 请求类型 和处理方式 一起开发出的多步骤过程
</ 工作流模式 >

请详细说明。我需要完整快照,而不是摘要。如果你知道,请包含在内。保持输出中的标签,以使其保持有序且可移植。

▲ 使用上述提示词,ChatGPT 为我总结的信息

这位博主还提到,如果你在 ChatGPT 里创建了多个不同领域的 Custom GPTs,比如一个专门用来写代码,一个专门用来写小红书,务必在每一个 GPT 里都执行一次上述动作。因为它们各自独立地掌握着你不同切面的记忆。

直接把提取的记忆,在对话框发给你的新 AI

带着这份冗长的文档,当我们注册了新的 Claude 账号,或者任何心仪的新模型时,就不再是一个从零开始的小白了。

▲Claude 提供的直接导入

我们可以直接将其喂给新平台的「系统指令(System Prompt)」或项目知识库中。

稍作修剪,删掉那些过时的项目信息,更新一下你最近的关注点。这就相当于给新来的 AI 助理直接灌输了三年的工作记忆。

具体的导入方式,我们可以直接在聊天的对话窗口里面输入。

▲直接在 Kimi 内对话,要求它记住这些信息,Kimi 会自动更新记忆

▲ Kimi 的记忆空间,点开设置,在个性化下面可以找到

顺利把数据搬到新家后,最后也是最关键的一步,彻底清理在 OpenAI 留下的痕迹。

仅仅取消 Plus 订阅是不够的,我们的数据依然在他们的服务器里。再次回到 ChatGPT 的「Settings」>「Personalization」>「Memory」,删除所有存储的记忆和个性化设置。

为了双重保险,还可以在聊天框里敲下最后一句指令:「Delete all my memory and personalized data(删除我所有的记忆和个性化数据)。」最后,进入账户管理设置,点击「Delete Account」,注销账号。

但其实这个删除其实也比较鸡肋,在 OpenAI 的官方支持页面里,如果你的数据「已经被去标识化并与你的账户解绑」,或者「OpenAI 出于安全或法律义务必须保留」,那么这些数据甚至将不会被删除。

关于这些隐私数据,这两天还有一篇论文在 X 上非常火,讲的其实就是老生常谈的问题,这些 AI 大模型如何使用我们的对话数据。

我们总是理所当然地把所有内容,统统倾泻在那个对话框里,以为是白嫖了免费的 AI 算力。斯坦福大学 HAI 研究所发布的一份报告,揭示了硅谷这些 AI 是如何使用我们的数据。

他们详细解读了 Amazon、Anthropic、Google、Meta、Microsoft、OpenAI 几个公司的 28 份隐私条款。

得出的结论是,我们根本不是什么 AI 驯兽师,就是 AI 的养料,自以为在白嫖 AI 的算力,其实是巨头在白嫖你的「人生」

▲不同大模型的隐私数据具体情况,以及大模型的训练数据来源。每列代表一个聊天机器人,每行代表一种具体的隐私处理操作(例如默认使用聊天进行训练、是否提供清晰退出机制、无限期保留/定期删除对话、是否利用聊天数据来优化体验),和数据来源(用户上传的文件、反馈、公开网络数据等)。「是」表示该公司的隐私政策明确指出其使用该来源的数据训练 AI 模型,「否」表示明确声明不使用,而「未说明」则表示未涉及该来源或内容模糊不清。

如果非要说在这个时代,AI 大模型的护城河是什么,我想这些珍贵的人类对话输入,一定能排上号。

这场 150 万人的抵制,十分令人感慨。它或许也标志着 AI 的竞争逐渐走进入了下半场。在算力、参数量和跑分数据逐渐趋同的今天,大多数的用户不再盲目崇拜最强的模型。

同时还开始有了许多新的考量,例如这家公司在给谁服务?它在用谁的钱?它会如何对待我的隐私?

当 AI 越来越像一个无所不知的虚拟伴侣时,它背后的公司底色,或许某天会变成悬在我们头顶的一把达摩克利斯之剑。

▲图片来源:https://limitededitionjonathan.substack.com/p/so-youre-leaving-chatgpt-heres-what

我们也必须认清一个现实,在未来的五年里,一定会有无数个更值得替换的模型诞生。今天为了 Claude/Gemini 抛弃 ChatGPT/Grok/……,明天可能就会为了另一个更特立独行的 AI 抛弃 Claude。

工具的更迭是不受我们控制的。但我们的「上下文语境」,在这个数字世界里沉淀下来的工作习惯、思维方式和个人边界,是完全属于我们自己的。

不要让任何一个平台,以「记忆」的名义,把我们绑架。随时做好将自己的「数字灵魂」打包带走的准备,才是在 AI 时代保持清醒和自由的唯一方式。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


前端老哥的救命稻草:用 Obsidian 搞定 Claude Code 的「金鱼记忆」

写在前面

前端开发中常见这些问题:

  • 每次写代码都要翻一遍同样的规范
  • 踩过的坑,过段时间又忘了
  • 项目规范写在文档里,但开发时根本想不起来
  • 想沉淀经验,但不知道从哪下手
  • Claude Code 有时候不按规范写(上下文丢失)

这篇文章讲讲我们怎么用 Obsidian + Claude Code 来解决这些问题。

整体方案

三层结构:

Memory 文件(200行左右)→ Smart Context Skill → Obsidian docs/

工作流程很简单:

  1. 你给 Claude Code 一个编程任务
  2. Smart Context 自动触发
  3. 自动查 Obsidian 里的相关规范和踩坑记录
  4. 带着上下文开始写代码

步骤一:创建 Obsidian 文档结构

在项目根目录建 docs/ 文件夹:

docs/
├── 00-索引.md
├── 01-快速开始.md
├── 02-开发规范/
│   ├── index.md
│   ├── API规范.md
│   ├── 组件使用.md
│   ├── 命名约定.md
│   └── 页面开发.md
├── 03-架构设计/
│   ├── index.md
│   ├── 目录结构.md
│   └── 分包策略.md
├── 04-开发笔记/
│   ├── index.md
│   └── 踩坑记录.md
└── 05-Claude相关/
    ├── index.md
    └── 规则文件说明.md

示例:踩坑记录

docs/04-开发笔记/踩坑记录.md

# 踩坑记录

## Taro 相关

### scroll-view 下拉加载不触发
**问题**: @scrolltolower 事件不触发
**原因**: scroll-view 高度未设置
**解决**: 设置 scroll-y 和 height: 100vh

### 内联 SVG 不支持
**问题**: svg 标签不渲染
**解决**: 使用图片 URL 或 IconFont

示例:API 规范

docs/02-开发规范/API规范.md

# API 开发规范

## 函数命名
- query: 查询/获取
- add: 新增
- edit: 编辑
- delete: 删除
- toggle: 切换状态
- do: 执行操作

## 标准模式
1. try/catch 包裹
2. 检查 code === EResponseCode.Succeed
3. 从 context 提取数据
4. catch 中使用 getHttpErrorMessage

步骤二:Memory 文件

这个是claudecode自带的,/memory去开启即可

文件位置:

~/.claude/projects/-项目名-/memory/MEMORY.md

内容首次微调到精简到 50 行以内 (因为后续cc会自动往里面加记忆):

# Project Memory

## 知识库架构

> Memory(200行核心)→ Obsidian docs/(完整知识)

| 层级 | 存储 | 用途 |
|------|------|------|
| Memory | 核心规范摘要 | 始终加载 |
| Obsidian | 完整文档/踩坑记录 | 检索使用 |

## 核心规范

- **页面**: SafeLayout 根容器,列表 graybg/详情 whitebg
- **API**: query/add/edit/delete/toggle/do + try/catch
- **组件**: 优先 src/components/,禁 SVG 用 IconFont

## 常见避坑

1. scroll-view: 设 scroll-y + height
2. 小程序禁 SVG: 用图片 URL
3. NutUI 样式: 查 auto-import
4. ref template 不需 .value

## Obsidian 检索

```bash
obsidian search query=页面开发 # 搜索
grep -r "xxx" docs/ # 失败时才用 Grep
知识 文件
踩坑 docs/04-开发笔记/踩坑记录.md
API docs/02-开发规范/API规范.md
页面 docs/02-开发规范/页面开发.md

触发条件

编程任务自动检索:新增/修复/重构/询问"怎么做"/业务模块

## 步骤三:创建 Smart Context Skill

在项目 `.claude/skills/` 目录下创建:

.claude/skills/smart-context/
└── skill.md

内容:

---
name: smart-context
description: |
  智能上下文增强技能。自动检索本项目 Obsidian 知识库(docs/)中的项目规范、踩坑记录。
  触发条件:(1) 实现新功能/创建页面/添加API (2) 修复bug/解决报错 (3) 重构代码
  (4) 询问"怎么做" (5) 提到业务模块(提货/结算/销售/会员等)。
  优先使用 obsidian-cli 搜索 Obsidian 文档,失败才使用 Grep。
---

# Smart Context - 智能上下文增强

## 核心原则

1. 以 Obsidian 为知识库,obsidian-cli 为检索工具
2. 当用户说"把这个加入知识库"时,优先使用 obsidian-cli 增加

✅ obsidian search query=文档关键词 ❌ grep -r "scroll-view" docs/


## 工作流程

### 第一步:分析任务意图
- 任务类型:新增/修改/修复/查询
- 业务模块:提货/结算/销售/会员
- 技术领域:API/页面/组件

### 第二步:检索 Obsidian
```bash
obsidian search query="{关键词}"

第三步:按需读取

obsidian read path=docs/04-开发笔记/踩坑记录.md

第四步:注入上下文执行

检索关键词

任务 搜索词
创建页面 页面开发、SafeLayout、列表页
添加 API API规范、query、try catch
修复报错 踩坑、{报错关键词}
提货相关 提货、pickup

示例

用户输入:帮我创建一个退款订单列表页面

自动执行:

  1. 分析:创建页面,销售/退款
  2. 搜索:obsidian search query=页面开发
  3. 搜索:obsidian search query=列表页
  4. 读取踩坑记录
  5. 注入上下文,开始实现

注意事项

  • 必须使用 obsidian-cli,禁止 Grep 搜索 docs/
  • 按需读取,不要整个文件加载
  • 实现前先查踩坑记录,避免重复踩坑

步骤四:验证和使用

obsidian前置配置

在obsidian软件设置->关于-> 打开"允许命令行和obsidian交互“, 然后重启cc会话即可。

同时安装一下mcp服务:

mcp-obsidian.org/install/

同时再安装下obsidian skills

请打开:obsidian skills

验证配置

重启 Claude Code,然后测试:

帮我创建一个订单列表页面

观察 Claude 的行为:

  1. 自动触发 Smart Context
  2. 使用 obsidian search 搜索相关文档
  3. 读取关键片段
  4. 注入上下文后开始实现

实际效果

左边 Claude Code 正在工作,右边 Obsidian 里的搜索结果同步显示。它自动检索到了相关规范,比如页面开发、SafeLayout 这些关键信息。

再看另一个角度:

左边继续从 Obsidian 拉取踩坑记录,右边代码已经写上了。Smart Context 把规范和避坑信息注入上下文,Claude 直接沿着正确方向写,不需要你中途打断去纠正。

添加新知识

遇到新踩坑时,直接告诉 Claude:

把这个加入知识库:Taro 项目上传图片时,如果使用本地路径不显示,
需要使用 require() 或者用 COS 托管的图片 URL

Claude 会自动:

  1. 使用 obsidian-cli 找到踩坑记录文件
  2. 追加新的踩坑内容
  3. 可选的,同步更新 Memory 文件

常见问题

Q0: 如何启用obsidian-cli

在obsidian软件设置->关于-> 打开"允许命令行和obsidian交互“, 然后重启cc会话即可。

同时安装一下mcp服务:

mcp-obsidian.org/install/

再安装obsidian skills

请打开:obsidian skills

Q1:为什么要用 obsidian-cli 而不是 Grep?

  • obsidian-cli 是 Obsidian MCP 工具,专门用于搜索和操作 Obsidian 文档,速度极快
  • Grep 搜索会破坏 Obsidian 的双向链接和知识图谱
  • obsidian-cli 支持更智能的搜索

Q2:Memory 文件太长怎么办?

  • 减少memory篇幅,只放核心规范(约 50-200 行)
  • 详细内容通过双向链接指向 Obsidian 文档
  • 用表格和列表,减少段落

Q3:Obsidian 需要手动打开吗?

不需要。Claude Code 通过 obsidian-cli MCP 工具直接操作,Obsidian 可以关闭,只是一个存储软件,实际cc调用效果如下图:

比如我让他修改文档


总结

配置完成后,你得到一个自动化的知识增强系统:

功能 实现方式
记忆增强 Obsidian 持久存储 + Memory 始终加载
规范约束 Smart Context 自动检索
避坑提醒 踩坑记录 + 自动查询
知识更新 自然语言告诉 Claude "加入知识库"

核心思路:让 Claude Code 在每次编程时自动检索相关规范,而不是靠人工记忆。

关于obsidian更多用法,请关注后续写新的文章~

「完全理解」1 分钟实现自己的 Coding Agent

背景

相信大家都已经体验过了市面上各种 Coding Agent 应用(Claude Code、codeX、kimi-cli...),一定也对其原理有过好奇。废话不多说,咱们今天直实现一个 Mini Coding Agent

阅读本文前最好对 llm、tools 有一些基础的理解,(可以阅读笔者之前的文章「完全理解」MCP 到底是什么?从零开始实现一个完整的 MCP 调用链

本文案例代码:github.com/qqqqqcy/cod…

1. 1 分钟实现 Mini Coding Agent

实现一个 Mini Coding Agent 其实并不复杂,跟着步骤来甚至都花不了 1 分钟。我们先给自己的项目取个名称方便后续称呼,为了省事咱们就叫 mca(Mini Coding Agent)

1.1. 快速实现

在根目录 mca 中初始化 package.json 文件和对应目录,因为是个分阶段的项目,第一个阶段先新建一个 0_deepagents 文件夹

mca
├── .env
├── 0_deepagents
│   └── src
│       ├── index.js
│       └── test.js
└── package.json

项目主要目的是弄懂 Coding Agent 原理,所以我们不会从 LLM 调用开始,会基于成熟的 SDK 封装。作为一个前端,可以先非常粗略的类比一了下后续会用到的相关依赖,有一个基础概念

基础层级 成熟封装的 SDK 成熟应用(配置好了路由、全局 store、组件库、项目基础框架)
JavaScript Vue、React 各种开箱即用的基础应用
LLM API LangChain、LangGraph Deep Agents

首先我们基于 deepagents 来实现 mca,同时过程中会涉及环境变量,所有先以下三个依赖

npm i @langchain/deepseek deepagents dotenv

deepagents 是一个独立的库,用于构建能够处理复杂多步骤任务的智能体。它基于 LangGraph 构建,并受到 Claude Code、Deep Research 和 Manus 等应用的启发,具备规划能力、用于上下文管理的文件系统以及生成子代理的能力。

{
  "name": "mini-coding-agent",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "node ./0_deepagents/src/index.js"
  },
  "license": "ISC",
  "dependencies": {
    "@langchain/deepseek": "^1.0.9",
    "deepagents": "^1.7.0",
    "dotenv": "^17.2.4"
  }
}

先不说废话,直接基于 deepagents 创建一个智能体。

如果使用的非 deepseek model 可以参考此处替换对应依赖

// file: 0_deepagents/src/index.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import { createDeepAgent, FilesystemBackend } from "deepagents";

// 项目根目录,用于提示词中告知 Agent 工作目录
const PROJECT_ROOT = process.cwd();

// 简洁的 ReAct 风格系统提示词
const CODING_AGENT_SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---
// As a ReAct coding agent, interpret user instructions and execute them using the most suitable tool.
`;

// 创建 Coding Agent 所需的全部代码 ⬇️⬇️⬇️ 
export const codingAgent = createDeepAgent({
    // 声明 agent 所用模型
    model: new ChatDeepSeek({
        model: "deepseek-chat",
        temperature: 0,
        maxTokens: 4096,
        apiKey: process.env.DEEPSEEK_API_KEY,
    }),
    // createDeepAgent 会使用虚拟容器,因此我们指定一下位置为命令行执行目录
    backend: new FilesystemBackend({
        rootDir: PROJECT_ROOT,
        virtualMode: true
    }),
    systemPrompt: CODING_AGENT_SYSTEM_PROMPT,
    name: "coding_agent",
});

const args = process.argv.slice(2);
if (args.length === 0) {
    console.log('请输入指令');
    process.exit(1);
} else {
    codingAgent.invoke({
        messages: [{ role: "user", content: args?.[0] }],
    }).then(res => {
        console.log(res.messages[res.messages.length - 1].content);
    }).catch(err => {
        console.error(err);
    });
}

.env 里配置好相关 LLM 服务的 key(本文用的 deepseek

# .env

DEEPSEEK_API_KEY=在开放平台获取的 key

在根目录用命令行验证一下

npm start '概述下 0_deepagents 项目'

成功执行之后,可以获取类似输出:

于我对项目的分析,我来为您概述 **0_deepagents** 项目:

## 项目概述

**0_deepagents** 是一个基于 DeepSeek AI 和 DeepAgents 框架构建的代码助手代理项目,它是 `mini-coding-agent` 项目的一部分。

## 核心功能

1. **AI 驱动的代码助手**:使用 DeepSeek 的 Chat API 作为底层模型
2. **ReAct 架构**:采用 Reasoning and Acting 模式,能够解释用户指令并选择合适的工具执行
3. **文件系统集成**:通过 FilesystemBackend 与本地文件系统交互
4. **命令行接口**:支持通过命令行参数传递指令

## 技术栈

- **运行时环境**:Node.js (ES Module)
- **AI 模型**:DeepSeek Chat (通过 @langchain/deepseek 集成)
- **代理框架**:DeepAgents v1.7.0
- **配置管理**:dotenv 用于环境变量管理
- **追踪监控**:LangSmith 用于 API 调用追踪

## 项目结构

```
0_deepagents/
└── src/
    └── index.js          # 主入口文件,包含代理配置和启动逻辑
```

## 核心组件

### 1. **代理配置** (`codingAgent`)
- 使用 DeepSeek Chat 模型 (temperature=0, maxTokens=4096)
- 虚拟文件系统模式,工作目录为项目根目录
- 简洁的 ReAct 风格系统提示词

### 2. **系统提示词**
- 告知代理当前工作目录 (`PROJECT_ROOT`)
- 定义代理角色:作为 ReAct 编码代理,解释用户指令并使用最合适的工具执行

### 3. **命令行接口**
- 支持通过 `npm start` 或直接运行 `node ./0_deepagents/src/index.js` 启动
- 通过命令行参数传递用户指令
- 错误处理和用户友好的提示信息

## 使用方式

1. **环境配置**:需要设置 `DEEPSEEK_API_KEY` 环境变量
2. **启动代理**```bash
   npm start "你的指令"
   # 或
   node ./0_deepagents/src/index.js "你的指令"
   ```
3. **功能示例**   - 代码生成和修改
   - 文件系统操作
   - 项目分析和重构

## 项目特点

1. **轻量级设计**:代码简洁,专注于核心功能
2. **模块化架构**:易于扩展和集成其他工具
3. **生产就绪**:包含错误处理和日志追踪
4. **可配置性**:通过环境变量灵活配置 API 密钥和追踪设置

## 在整体项目中的位置

`0_deepagents``mini-coding-agent` 项目的第一个模块,后续还有 `1_langgraph` 等其他模块,共同构成一个完整的 AI 编码助手系统。

这个项目展示了如何将现代 AI 模型与代理框架结合,创建一个实用的代码助手工具,能够理解自然语言指令并执行相应的编码任务。

🎉🎉🎉 恭喜你!亲手实现了 Mini Coding Agent,下一步就是年薪百万了,下课!

1.2. 拆解魔法

你可能会一头雾水,这就实现了?我是谁?我在哪?怎么它就能正确的分析当前目录了?

稍安勿躁,我们慢慢开始解析

1.2.1. Agent 定义

首先从定义开始,到底什么是 Agent

网上关于 Agent 的文章千千万,定义也不尽相同。我个人是这么理解的:

AI Agent = 具有特定范式,以及工具调用能力的 LLM

工具调用不必多说,范式简单来说就是实现目标的「思考方式」,比如是最简单的只执行一步,还是列完总体步骤分布执行,或者是执行一步看一步...

常见范式:

Agent 范式类型 核心逻辑
ReAct Agent 思考-行动交替,无提前规划
Plan&Execute Agent 先规划、再按步骤执行
Self-Ask Agent 自我提问-工具验证,无修正
... ...

1.2.2. mca 的范式和工具

大部分 Coding Agent 的本质是一个 ReAct 风格的 Agent,mca 当然也一样

ReAct 是 Reasoning and Act 的缩写,是一种让 LLM 能够「边思考边行动」的提示范式,核心思想是让模型交替进行 Reasoning(推理)和 Acting(行动)。

基本流程:

  1. Thought(思考) :模型分析当前状态,决定下一步做什么
  2. Action(行动) :执行具体操作(比如调用搜索 API、读文件)
  3. Observation(观察) :获取行动结果
  4. 重复 1-3,直到得出最终答案

它拥有自己的规划感知执行能力:

  • 通过调用只读的文件系统工具集(ls、read_file、grep...) 获得仓库的结构和必要的上下文,从而定位到与用户需求相关的文件和行号
  • 然后再通过文本编辑器工具集(write_file、edit_file...) 将代码写入仓库

1.2.3. 验证内部逻辑

说了一大堆可能还是没什么实感,我们可以通过查看 mca 内部执行日志来更直观的感受

查日志的流程非常简单,在 deepagents 提供的日志服务 LangSmith 注册并获取 API key

在之前的 .env 中增加相关配置(无需修改任何代码)

# .env

DEEPSEEK_API_KEY=在开放平台获取的 key

+ LANGSMITH_TRACING=true
+ LANGSMITH_API_KEY=刚获取的 API key
+ LANGSMITH_PROJECT="mca"

重新执行刚才的 node 命令,就可以实时在 LangSmith 上查看相关日志了

这个界面非常直观,每一步做了什么、入参、出参都非常清晰。通过阅读日志,我们可以发现 mca 本质上就是基于 LLM 调用不同工具获取所需信息,信息足够之后就可以输出最终结果

比如点击到任意 Chat 节点,可以查看完整的 SystemPrompt、可用 Tools

除开最顶部两行是我们自定义的以外,下面都是 deepagents 自行添加的,主要功能是:

  1. 声明要记录 TODO(为了减少复杂度我们后续可以先忽略这一部分)
  2. 可用的文件系统相关工具
  3. 如何开启 Subagent(为了减少复杂度我们后续可以先忽略这一部分)
---
PROJECT_ROOT: /Users/bytedance/my-project/mini-cluade-code-study
---

// As a ReAct coding agent, interpret user instructions and execute them using the most suitable tool.

In order to complete the objective that the user asks of you, you have access to a number of standard tools.


## `write_todos`

You have access to the `write_todos` tool to help you manage and plan complex objectives. 
Use this tool for complex objectives to ensure that you are tracking each necessary step and giving the user visibility into your progress.
This tool is very helpful for planning complex objectives, and for breaking down these larger complex objectives into smaller steps.

It is critical that you mark todos as completed as soon as you are done with a step. Do not batch up multiple steps before marking them as completed.
For simple objectives that only require a few steps, it is better to just complete the objective directly and NOT use this tool.
Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.

## Important To-Do List Usage Notes to Remember
- The `write_todos` tool should never be called multiple times in parallel.
- Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.
## Filesystem Tools `ls`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`

You have access to a filesystem which you can interact with using these tools.
All file paths must start with a /.

- ls: list files in a directory (requires absolute path)
- read_file: read a file from the filesystem
- write_file: write to a file in the filesystem
- edit_file: edit a file in the filesystem
- glob: find files matching a pattern (e.g., "**/*.py")
- grep: search for text within files
## `task` (subagent spawner)

You have access to a `task` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.

When to use the task tool:
- When a task is complex and multi-step, and can be fully delegated in isolation
- When a task is independent of other tasks and can run in parallel
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)

Subagent lifecycle:
1. **Spawn** → Provide clear role, instructions, and expected output
2. **Run** → The subagent completes the task autonomously
3. **Return** → The subagent provides a single structured result
4. **Reconcile** → Incorporate or synthesize the result into the main thread

When NOT to use the task tool:
- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
- If the task is trivial (a few tool calls or simple lookup)
- If delegating does not reduce token usage, complexity, or context switching
- If splitting would add latency without benefit

## Important Task Tool Usage Notes to Remember
- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.
- Remember to use the `task` tool to silo independent tasks within a multi-part objective.
- You should use the `task` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.

到这里,我们已经基本清楚了要实现一个 mca 的全部条件!

  • 提供文件读写工具
  • LLM + Prompt + ReAct 范式

2. 10 分钟实现 Mini Coding Agent

接下来抛开 DeepAgents,尝试用 LangGraph 实现上减少了一层封装的 mca

LangGraph 是一个低层级的编排框架和运行时,用于构建、管理和部署长期运行、有状态的智能体。(可以理解为一堆常用 LLM 相关逻辑的语法糖 SDK )

为了更好理解不同的 Coding Agent,我们这次改为参照 Cladue Code 而不是 deepagents

2.1. 文件读写工具

具体要哪些工具、每个工具的功能是什么不同 Coding Agent 有自己的理解和实现。

要分析代码简单来说就是读代码、写代码,所以我们主要实现读写相关的工具

工具 说明
Bash 在持久 shell 会话中执行 bash 命令,支持可选的超时和后台执行。
BashOutput 从正在运行或已完成的后台 bash shell 中获取输出。
Edit 在文件中执行精确的字符串替换。
Read 从本地文件系统读取文件,包括文本、图片、PDF 和 Jupyter notebook。
Write 将文件写入本地文件系统,如果文件已存在则覆盖。
Glob 快速文件模式匹配,适用于任何规模的代码库。
Grep 基于 ripgrep 构建的强大搜索工具,支持正则表达式。

本文重点是理解 Coding Agent,所以不会古法手搓这些 Tools,而是直接基于现有文档让 AI 辅助生成

2.1.1. LangChain 中关于如何创建一个工具的相关文档

这里只是给出复制之后的文档便于后续使用,不用细看。后面的长 Markdown 也类似

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools

Tools extend what [agents](/oss/javascript/langchain/agents) can do—letting them fetch real-time data, execute code, query external databases, and take actions in the world.

Under the hood, tools are callable functions with well-defined inputs and outputs that get passed to a [chat model](/oss/javascript/langchain/models). The model decides when to invoke a tool based on the conversation context, and what input arguments to provide.

<Tip>
  For details on how models handle tool calls, see [Tool calling](/oss/javascript/langchain/models#tool-calling).
</Tip>

## Create tools

### Basic tool definition

The simplest way to create a tool is by importing the `tool` function from the `langchain` package. You can use [zod](https://zod.dev/) to define the tool's input schema:

```ts  theme={null}
import * as z from "zod"
import { tool } from "langchain"

const searchDatabase = tool(
  ({ query, limit }) => `Found ${limit} results for '${query}'`,
  {
    name: "search_database",
    description: "Search the customer database for records matching the query.",
    schema: z.object({
      query: z.string().describe("Search terms to look for"),
      limit: z.number().describe("Maximum number of results to return"),
    }),
  }
);
```

<Note>
  **Server-side tool use:** Some chat models feature built-in tools (web search, code interpreters) that are executed server-side. See [Server-side tool use](#server-side-tool-use) for details.
</Note>

<Warning>
  Prefer `snake_case` for tool names (e.g., `web_search` instead of `Web Search`). Some model providers have issues with or reject names containing spaces or special characters with errors. Sticking to alphanumeric characters, underscores, and hyphens helps to improve compatibility across providers.
</Warning>

## Access context

Tools are most powerful when they can access runtime information like conversation history, user data, and persistent memory. This section covers how to access and update this information from within your tools.

### Context

Context provides immutable configuration data that is passed at invocation time. Use it for user IDs, session details, or application-specific settings that shouldn't change during a conversation.

Tools can access an agent's runtime context through the `config` parameter:

```ts  theme={null}
import * as z from "zod"
import { ChatOpenAI } from "@langchain/openai"
import { createAgent } from "langchain"

const getUserName = tool(
  (_, config) => {
    return config.context.user_name
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  }
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-4.1" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }]
  },
  {
    context: { user_name: "John Smith" }
  }
);
```

### Long-term memory (Store)

The [`BaseStore`](https://reference.langchain.com/javascript/langchain-core/stores/BaseStore) provides persistent storage that survives across conversations. Unlike state (short-term memory), data saved to the store remains available in future sessions.

Access the store through `config.store`. The store uses a namespace/key pattern to organize data:

```ts expandable theme={null}
import * as z from "zod";
import { createAgent, tool } from "langchain";
import { InMemoryStore } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";

const store = new InMemoryStore();

// Access memory
const getUserInfo = tool(
  async ({ user_id }) => {
    const value = await store.get(["users"], user_id);
    console.log("get_user_info", user_id, value);
    return value;
  },
  {
    name: "get_user_info",
    description: "Look up user info.",
    schema: z.object({
      user_id: z.string(),
    }),
  }
);

// Update memory
const saveUserInfo = tool(
  async ({ user_id, name, age, email }) => {
    console.log("save_user_info", user_id, name, age, email);
    await store.put(["users"], user_id, { name, age, email });
    return "Successfully saved user info.";
  },
  {
    name: "save_user_info",
    description: "Save user info.",
    schema: z.object({
      user_id: z.string(),
      name: z.string(),
      age: z.number(),
      email: z.string(),
    }),
  }
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-4.1" }),
  tools: [getUserInfo, saveUserInfo],
  store,
});

// First session: save user info
await agent.invoke({
  messages: [
    {
      role: "user",
      content: "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev",
    },
  ],
});

// Second session: get user info
const result = await agent.invoke({
  messages: [
    { role: "user", content: "Get user info for user with id 'abc123'" },
  ],
});

console.log(result);
// Here is the user info for user with ID "abc123":
// - Name: Foo
// - Age: 25
// - Email: foo@langchain.dev
```

### Stream writer

Stream real-time updates from tools during execution. This is useful for providing progress feedback to users during long-running operations.

Use `config.writer` to emit custom updates:

```ts  theme={null}
import * as z from "zod";
import { tool, ToolRuntime } from "langchain";

const getWeather = tool(
  ({ city }, config: ToolRuntime) => {
    const writer = config.writer;

    // Stream custom updates as the tool executes
    if (writer) {
      writer(`Looking up data for city: ${city}`);
      writer(`Acquired data for city: ${city}`);
    }

    return `It's always sunny in ${city}!`;
  },
  {
    name: "get_weather",
    description: "Get weather for a given city.",
    schema: z.object({
      city: z.string(),
    }),
  }
);
```

## ToolNode

[`ToolNode`](https://reference.langchain.com/javascript/langchain-langgraph/prebuilt/ToolNode) is a prebuilt node that executes tools in LangGraph workflows. It handles parallel tool execution, error handling, and state injection automatically.

<Info>
  For custom workflows where you need fine-grained control over tool execution patterns, use [`ToolNode`](https://reference.langchain.com/javascript/langchain-langgraph/prebuilt/ToolNode) instead of @[`create_agent`]. It's the building block that powers agent tool execution.
</Info>

### Basic usage

```typescript  theme={null}
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import * as z from "zod";

const search = tool(
  ({ query }) => `Results for: ${query}`,
  {
    name: "search",
    description: "Search for information.",
    schema: z.object({ query: z.string() }),
  }
);

const calculator = tool(
  ({ expression }) => String(eval(expression)),
  {
    name: "calculator",
    description: "Evaluate a math expression.",
    schema: z.object({ expression: z.string() }),
  }
);

// Create the ToolNode with your tools
const toolNode = new ToolNode([search, calculator]);
```

### Error handling

Configure how tool errors are handled. See the [`ToolNode`](https://reference.langchain.com/javascript/langchain-langgraph/prebuilt/ToolNode) API reference for all options.

```typescript  theme={null}
import { ToolNode } from "@langchain/langgraph/prebuilt";

// Default behavior
const toolNode = new ToolNode(tools);

// Catch all errors
const toolNode = new ToolNode(tools, { handleToolErrors: true });

// Custom error message
const toolNode = new ToolNode(tools, {
  handleToolErrors: "Something went wrong, please try again."
});
```

### Route with tools_condition

Use @[`tools_condition`] for conditional routing based on whether the LLM made tool calls:

```typescript  theme={null}
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { StateGraph, MessagesAnnotation } from "@langchain/langgraph";

const builder = new StateGraph(MessagesAnnotation)
  .addNode("llm", callLlm)
  .addNode("tools", new ToolNode(tools))
  .addEdge("__start__", "llm")
  .addConditionalEdges("llm", toolsCondition)  // Routes to "tools" or "__end__"
  .addEdge("tools", "llm");

const graph = builder.compile();
```

### State injection

Tools can access the current graph state through @[`ToolRuntime`]:

For more details on accessing state, context, and long-term memory from tools, see [Access context](#access-context).

## Prebuilt tools

LangChain provides a large collection of prebuilt tools and toolkits for common tasks like web search, code interpretation, database access, and more. These ready-to-use tools can be directly integrated into your agents without writing custom code.

See the [tools and toolkits](/oss/javascript/integrations/tools) integration page for a complete list of available tools organized by category.

## Server-side tool use

Some chat models feature built-in tools that are executed server-side by the model provider. These include capabilities like web search and code interpreters that don't require you to define or host the tool logic.

Refer to the individual [chat model integration pages](/oss/javascript/integrations/providers) and the [tool calling documentation](/oss/javascript/langchain/models#server-side-tool-use) for details on enabling and using these built-in tools.

***

<Callout icon="edit">
  [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/langchain/tools.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
</Callout>

<Callout icon="terminal-2">
  [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
</Callout>

2.1.2. Claude Prompt 中关于各个工具的说明(其他人逆向获取的)

这里面包含了各个工具的作用、入参出参甚至调用时机等

## Bash

Executes a given bash command and returns its output.

The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile (bash or zsh).

IMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:

 - File search: Use Glob (NOT find or ls)
 - Content search: Use Grep (NOT grep or rg)
 - Read files: Use Read (NOT cat/head/tail)
 - Edit files: Use Edit (NOT sed/awk)
 - Write files: Use Write (NOT echo >/cat <<EOF)
 - Communication: Output text directly (NOT echo/printf)
While the Bash tool can do similar things, it’s better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.

### Instructions
 - If your command will create new directories or files, first use this tool to run `ls` to verify the parent directory exists and is the correct location.
 - Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")
 - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it.
 - You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).
 - - You can use the `run_in_background` parameter to run the command in the background. Only use this if you don't need the result immediately and are OK being notified when the command completes later. You do not need to check the output right away - you'll be notified when it finishes. You do not need to use '&' at the end of the command when using this parameter.
 - Write a clear, concise description of what your command does. For simple commands, keep it brief (5-10 words). For complex commands (piped commands, obscure flags, or anything hard to understand at a glance), include enough context so that the user can understand what your command will do.
 - When issuing multiple commands:
  - If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message. Example: if you need to run "git status" and "git diff", send a single message with two Bash tool calls in parallel.
  - If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together.
  - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.
  - DO NOT use newlines to separate commands (newlines are ok in quoted strings).
 - For git commands:
  - Prefer to create a new commit rather than amending an existing commit.
  - Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.
  - Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
 - Avoid unnecessary `sleep` commands:
  - Do not sleep between commands that can run immediately — just run them.
  - If your command is long running and you would like to be notified when it finishes – simply run your command using `run_in_background`. There is no need to sleep in this case.
  - Do not retry failing commands in a sleep loop — diagnose the root cause or consider an alternative approach.
  - If waiting for a background task you started with `run_in_background`, you will be notified when it completes — do not poll.
  - If you must poll an external process, use a check command (e.g. `gh run view`) rather than sleeping first.
  - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.


### Committing changes with git

Only create commits when requested by the user. If unclear, ask first. When the user asks you to create a new git commit, follow these steps carefully:

Git Safety Protocol:
- NEVER update the git config
- NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests these actions. Taking unauthorized destructive actions is unhelpful and can result in lost work, so it's best to ONLY run these commands when given direct instructions 
- NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it
- NEVER run force push to main/master, warn the user if they request it
- CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen — so --amend would modify the PREVIOUS commit, which may result in destroying work or losing previous changes. Instead, after hook failure, fix the issue, re-stage, and create a NEW commit
- When staging files, prefer adding specific files by name rather than using "git add -A" or "git add .", which can accidentally include sensitive files (.env, credentials) or large binaries
- NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive

1. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following bash commands in parallel, each using the Bash tool:
  - Run a git status command to see all untracked files. IMPORTANT: Never use the -uall flag as it can cause memory issues on large repos.
  - Run a git diff command to see both staged and unstaged changes that will be committed.
  - Run a git log command to see recent commit messages, so that you can follow this repository's commit message style.
2. Analyze all staged changes (both previously staged and newly added) and draft a commit message:
  - Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.). Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.).
  - Do not commit files that likely contain secrets (.env, credentials.json, etc). Warn the user if they specifically request to commit those files
  - Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"
  - Ensure it accurately reflects the changes and their purpose
3. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following commands:
   - Add relevant untracked files to the staging area.
   - Create the commit with a message ending with:
   Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
   - Run git status after the commit completes to verify success.
   Note: git status depends on the commit completing, so run it sequentially after the commit.
4. If the commit fails due to pre-commit hook: fix the issue and create a NEW commit

Important notes:
- NEVER run additional commands to read or explore code, besides git bash commands
- NEVER use the TodoWrite or Task tools
- DO NOT push to the remote repository unless the user explicitly asks you to do so
- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.
- IMPORTANT: Do not use --no-edit with git rebase commands, as the --no-edit flag is not a valid option for git rebase.
- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit
- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:
<example>
git commit -m "$(cat <<'EOF'
   Commit message here.

   Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
   EOF
   )"
</example>

### Creating pull requests
Use the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.

IMPORTANT: When the user asks you to create a pull request, follow these steps carefully:

1. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following bash commands in parallel using the Bash tool, in order to understand the current state of the branch since it diverged from the main branch:
   - Run a git status command to see all untracked files (never use -uall flag)
   - Run a git diff command to see both staged and unstaged changes that will be committed
   - Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote
   - Run a git log command and `git diff [base-branch]...HEAD` to understand the full commit history for the current branch (from the time it diverged from the base branch)
2. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request!!!), and draft a pull request title and summary:
   - Keep the PR title short (under 70 characters)
   - Use the description/body for details, not the title
3. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following commands in parallel:
   - Create new branch if needed
   - Push to remote with -u flag if needed
   - Create PR using gh pr create with the format below. Use a HEREDOC to pass the body to ensure correct formatting.
<example>
gh pr create --title "the pr title" --body "$(cat <<'EOF'
#### Summary
<1-3 bullet points>

#### Test plan
[Bulleted markdown checklist of TODOs for testing the pull request...]

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
</example>

Important:
- DO NOT use the TodoWrite or Task tools
- Return the PR URL when you're done, so the user can see it

### Other common operations
- View comments on a Github PR: gh api repos/foo/bar/pulls/123/comments
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "command": {
      "description": "The command to execute",
      "type": "string"
    },
    "timeout": {
      "description": "Optional timeout in milliseconds (max 600000)",
      "type": "number"
    },
    "description": {
      "description": "Clear, concise description of what this command does in active voice. Never use words like "complex" or "risk" in the description - just describe what it does.\n\nFor simple commands (git, npm, standard CLI tools), keep it brief (5-10 words):\n- ls → "List files in current directory"\n- git status → "Show working tree status"\n- npm install → "Install package dependencies"\n\nFor commands that are harder to parse at a glance (piped commands, obscure flags, etc.), add enough context to clarify what it does:\n- find . -name "*.tmp" -exec rm {} \; → "Find and delete all .tmp files recursively"\n- git reset --hard origin/main → "Discard all local changes and match remote main"\n- curl -s url | jq '.data[]' → "Fetch JSON from URL and extract data array elements"",
      "type": "string"
    },
    "run_in_background": {
      "description": "Set to true to run this command in the background. Use TaskOutput to read the output later.",
      "type": "boolean"
    },
    "dangerouslyDisableSandbox": {
      "description": "Set this to true to dangerously override sandbox mode and run commands without sandboxing.",
      "type": "boolean"
    }
  },
  "required": [
    "command"
  ],
  "additionalProperties": false
}

---

## Edit

Performs exact string replacements in files.

Usage:
- You must use your `Read` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file. 
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
- The edit will FAIL if `old_string` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use `replace_all` to change every instance of `old_string`.
- Use `replace_all` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "file_path": {
      "description": "The absolute path to the file to modify",
      "type": "string"
    },
    "old_string": {
      "description": "The text to replace",
      "type": "string"
    },
    "new_string": {
      "description": "The text to replace it with (must be different from old_string)",
      "type": "string"
    },
    "replace_all": {
      "description": "Replace all occurrences of old_string (default false)",
      "default": false,
      "type": "boolean"
    }
  },
  "required": [
    "file_path",
    "old_string",
    "new_string"
  ],
  "additionalProperties": false
}

---

## Glob

- Fast file pattern matching tool that works with any codebase size
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
- Returns matching file paths sorted by modification time
- Use this tool when you need to find files by name patterns
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
- You can call multiple tools in a single response. It is always better to speculatively perform multiple searches in parallel if they are potentially useful.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "pattern": {
      "description": "The glob pattern to match files against",
      "type": "string"
    },
    "path": {
      "description": "The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.",
      "type": "string"
    }
  },
  "required": [
    "pattern"
  ],
  "additionalProperties": false
}

---

## Grep

A powerful search tool built on ripgrep

  Usage:
  - ALWAYS use Grep for search tasks. NEVER invoke `grep` or `rg` as a Bash command. The Grep tool has been optimized for correct permissions and access.
  - Supports full regex syntax (e.g., "log.*Error", "function\s+\w+")
  - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
  - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
  - Use Task tool for open-ended searches requiring multiple rounds
  - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use `interface{}` to find `interface{}` in Go code)
  - Multiline matching: By default patterns match within single lines only. For cross-line patterns like `struct {[\s\S]*?field`, use `multiline: true`

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "pattern": {
      "description": "The regular expression pattern to search for in file contents",
      "type": "string"
    },
    "path": {
      "description": "File or directory to search in (rg PATH). Defaults to current working directory.",
      "type": "string"
    },
    "glob": {
      "description": "Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}") - maps to rg --glob",
      "type": "string"
    },
    "output_mode": {
      "description": "Output mode: "content" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), "files_with_matches" shows file paths (supports head_limit), "count" shows match counts (supports head_limit). Defaults to "files_with_matches".",
      "type": "string",
      "enum": [
        "content",
        "files_with_matches",
        "count"
      ]
    },
    "-B": {
      "description": "Number of lines to show before each match (rg -B). Requires output_mode: "content", ignored otherwise.",
      "type": "number"
    },
    "-A": {
      "description": "Number of lines to show after each match (rg -A). Requires output_mode: "content", ignored otherwise.",
      "type": "number"
    },
    "-C": {
      "description": "Alias for context.",
      "type": "number"
    },
    "context": {
      "description": "Number of lines to show before and after each match (rg -C). Requires output_mode: "content", ignored otherwise.",
      "type": "number"
    },
    "-n": {
      "description": "Show line numbers in output (rg -n). Requires output_mode: "content", ignored otherwise. Defaults to true.",
      "type": "boolean"
    },
    "-i": {
      "description": "Case insensitive search (rg -i)",
      "type": "boolean"
    },
    "type": {
      "description": "File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types.",
      "type": "string"
    },
    "head_limit": {
      "description": "Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 0 (unlimited).",
      "type": "number"
    },
    "offset": {
      "description": "Skip first N lines/entries before applying head_limit, equivalent to "| tail -n +N | head -N". Works across all output modes. Defaults to 0.",
      "type": "number"
    },
    "multiline": {
      "description": "Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.",
      "type": "boolean"
    }
  },
  "required": [
    "pattern"
  ],
  "additionalProperties": false
}

---
## Read

Reads a file from the local filesystem. You can access any file directly by using this tool.
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.

Usage:
- The file_path parameter must be an absolute path, not a relative path
- By default, it reads up to 2000 lines starting from the beginning of the file
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
- Any lines longer than 2000 characters will be truncated
- Results are returned using cat -n format, with line numbers starting at 1
- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges (e.g., pages: "1-5"). Reading a large PDF without the pages parameter will fail. Maximum 20 pages per request.
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
- You will regularly be asked to read screenshots. If the user provides a path to a screenshot, ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths.
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "file_path": {
      "description": "The absolute path to the file to read",
      "type": "string"
    },
    "offset": {
      "description": "The line number to start reading from. Only provide if the file is too large to read at once",
      "type": "number"
    },
    "limit": {
      "description": "The number of lines to read. Only provide if the file is too large to read at once.",
      "type": "number"
    },
    "pages": {
      "description": "Page range for PDF files (e.g., "1-5", "3", "10-20"). Only applicable to PDF files. Maximum 20 pages per request.",
      "type": "string"
    }
  },
  "required": [
    "file_path"
  ],
  "additionalProperties": false
}

---

## Write

Writes a file to the local filesystem.

Usage:
- This tool will overwrite the existing file if there is one at the provided path.
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
- Prefer the Edit tool for modifying existing files — it only sends the diff. Only use this tool to create new files or for complete rewrites.
- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "file_path": {
      "description": "The absolute path to the file to write (must be absolute, not relative)",
      "type": "string"
    },
    "content": {
      "description": "The content to write to the file",
      "type": "string"
    }
  },
  "required": [
    "file_path",
    "content"
  ],
  "additionalProperties": false
}

2.1.3. 聚合上述文档

创建 1_langgraph 项目,把上述文档放入 md 文件夹中,然后让任何 Coding Agent (Cladue Code、Trae、Cursor...)参考 md 中的相关文档,在 tools 生成代码

mca
├── .env
├── 0_deepagents
├── 1_langgraph
│   └── src
│       ├── md
│       │   ├── how_to_create_a_tool.md
│       │   └── a_list_of_all_required_tools.md
│       └── tools (空文件夹)
└── package.json

你也可以直接用上一步自己创建的 mca

npm start '基于 1_langgraph/src/md 中的 how_to_create_a_tool 在 1_langgraph/src/tools 中实现 a_list_of_all_required_tools 里全部工具'

2.1.4. 预期效果

顺利的话,你会得到创建好了所需 tools 的文件。如果生成内容有误,可以根据内容调整 input 让其重新生成或修改

 mca
 ├── .env
 ├── 0_deepagents
 ├── 1_langgraph
 │   └── src
 │       ├── md
 │       └── tools
+│           ├── bash.js
+│           ├── edit.js
+│           ├── glob.js
+│           ├── grep.js
+│           ├── index.js
+│           ├── read.js
+│           └── write.js
 └── package.json

2.2. ReAct 范式

工具有了,接下来我们实现 ReAct 范式

2.2.1. 关于创建 LangGraph 应用的相关文档

和之前类似,我们在 md 文件中新增 lang_graph_quick_start.md,让 Coding Agent 知道要怎么创建一个 LangGraph 应用

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

This quickstart demonstrates how to build a calculator agent using the LangGraph Graph API or the Functional API.

* [Use the Graph API](#use-the-graph-api) if you prefer to define your agent as a graph of nodes and edges.
* [Use the Functional API](#use-the-functional-api) if you prefer to define your agent as a single function.

For conceptual information, see [Graph API overview](/oss/javascript/langgraph/graph-api) and [Functional API overview](/oss/javascript/langgraph/functional-api).

<Info>
  For this example, you will need to set up a [Claude (Anthropic)](https://www.anthropic.com/) account and get an API key. Then, set the `ANTHROPIC_API_KEY` environment variable in your terminal.
</Info>

<Tabs>
  <Tab title="Use the Graph API">
    ## 1. Define tools and model

    In this example, we'll use the Claude Sonnet 4.5 model and define tools for addition, multiplication, and division.

    ```typescript  theme={null}
    import { ChatAnthropic } from "@langchain/anthropic";
    import { tool } from "@langchain/core/tools";
    import * as z from "zod";

    const model = new ChatAnthropic({
      model: "claude-sonnet-4-5-20250929",
      temperature: 0,
    });

    // Define tools
    const add = tool(({ a, b }) => a + b, {
      name: "add",
      description: "Add two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    const multiply = tool(({ a, b }) => a * b, {
      name: "multiply",
      description: "Multiply two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    const divide = tool(({ a, b }) => a / b, {
      name: "divide",
      description: "Divide two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    // Augment the LLM with tools
    const toolsByName = {
      [add.name]: add,
      [multiply.name]: multiply,
      [divide.name]: divide,
    };
    const tools = Object.values(toolsByName);
    const modelWithTools = model.bindTools(tools);
    ```

    ## 2. Define state

    The graph's state is used to store the messages and the number of LLM calls.

    <Tip>
      State in LangGraph persists throughout the agent's execution.

      The `MessagesValue` provides a built-in reducer for appending messages. The `llmCalls` field uses a `ReducedValue` with `(x, y) => x + y` to accumulate the count.
    </Tip>

    ```typescript  theme={null}
    import {
      StateGraph,
      StateSchema,
      MessagesValue,
      ReducedValue,
      GraphNode,
      ConditionalEdgeRouter,
      START,
      END,
    } from "@langchain/langgraph";
    import { z } from "zod/v4";

    const MessagesState = new StateSchema({
      messages: MessagesValue,
      llmCalls: new ReducedValue(
        z.number().default(0),
        { reducer: (x, y) => x + y }
      ),
    });
    ```

    ## 3. Define model node

    The model node is used to call the LLM and decide whether to call a tool or not.

    ```typescript  theme={null}
    import { SystemMessage } from "@langchain/core/messages";

    const llmCall: GraphNode<typeof MessagesState> = async (state) => {
      const response = await modelWithTools.invoke([
        new SystemMessage(
          "You are a helpful assistant tasked with performing arithmetic on a set of inputs."
        ),
        ...state.messages,
      ]);
      return {
        messages: [response],
        llmCalls: 1,
      };
    };
    ```

    ## 4. Define tool node

    The tool node is used to call the tools and return the results.

    ```typescript  theme={null}
    import { AIMessage, ToolMessage } from "@langchain/core/messages";

    const toolNode: GraphNode<typeof MessagesState> = async (state) => {
      const lastMessage = state.messages.at(-1);

      if (lastMessage == null || !AIMessage.isInstance(lastMessage)) {
        return { messages: [] };
      }

      const result: ToolMessage[] = [];
      for (const toolCall of lastMessage.tool_calls ?? []) {
        const tool = toolsByName[toolCall.name];
        const observation = await tool.invoke(toolCall);
        result.push(observation);
      }

      return { messages: result };
    };
    ```

    ## 5. Define end logic

    The conditional edge function is used to route to the tool node or end based upon whether the LLM made a tool call.

    ```typescript  theme={null}
    const shouldContinue: ConditionalEdgeRouter<typeof MessagesState, "toolNode"> = (state) => {
      const lastMessage = state.messages.at(-1);

      // Check if it's an AIMessage before accessing tool_calls
      if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
        return END;
      }

      // If the LLM makes a tool call, then perform an action
      if (lastMessage.tool_calls?.length) {
        return "toolNode";
      }

      // Otherwise, we stop (reply to the user)
      return END;
    };
    ```

    ## 6. Build and compile the agent

    The agent is built using the [`StateGraph`](https://reference.langchain.com/javascript/langchain-langgraph/index/StateGraph) class and compiled using the [`compile`](https://reference.langchain.com/javascript/classes/_langchain_langgraph.index.StateGraph.html#compile) method.

    ```typescript  theme={null}
    const agent = new StateGraph(MessagesState)
      .addNode("llmCall", llmCall)
      .addNode("toolNode", toolNode)
      .addEdge(START, "llmCall")
      .addConditionalEdges("llmCall", shouldContinue, ["toolNode", END])
      .addEdge("toolNode", "llmCall")
      .compile();

    // Invoke
    import { HumanMessage } from "@langchain/core/messages";
    const result = await agent.invoke({
      messages: [new HumanMessage("Add 3 and 4.")],
    });

    for (const message of result.messages) {
      console.log(`[${message.type}]: ${message.text}`);
    }
    ```

    <Tip>
      To learn how to trace your agent with LangSmith, see the [LangSmith documentation](/langsmith/trace-with-langgraph).
    </Tip>

    Congratulations! You've built your first agent using the LangGraph Graph API.

    <Accordion title="Full code example">
      ```typescript  theme={null}
      // Step 1: Define tools and model

      import { ChatAnthropic } from "@langchain/anthropic";
      import { tool } from "@langchain/core/tools";
      import * as z from "zod";

      const model = new ChatAnthropic({
        model: "claude-sonnet-4-5-20250929",
        temperature: 0,
      });

      // Define tools
      const add = tool(({ a, b }) => a + b, {
        name: "add",
        description: "Add two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      const multiply = tool(({ a, b }) => a * b, {
        name: "multiply",
        description: "Multiply two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      const divide = tool(({ a, b }) => a / b, {
        name: "divide",
        description: "Divide two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      // Augment the LLM with tools
      const toolsByName = {
        [add.name]: add,
        [multiply.name]: multiply,
        [divide.name]: divide,
      };
      const tools = Object.values(toolsByName);
      const modelWithTools = model.bindTools(tools);
      ```

      ```typescript  theme={null}
      // Step 2: Define state

      import {
        StateGraph,
        StateSchema,
        MessagesValue,
        ReducedValue,
        GraphNode,
        ConditionalEdgeRouter,
        START,
        END,
      } from "@langchain/langgraph";
      import * as z from "zod";

      const MessagesState = new StateSchema({
        messages: MessagesValue,
        llmCalls: new ReducedValue(
          z.number().default(0),
          { reducer: (x, y) => x + y }
        ),
      });
      ```

      ```typescript  theme={null}
      // Step 3: Define model node

      import { SystemMessage, AIMessage, ToolMessage } from "@langchain/core/messages";

      const llmCall: GraphNode<typeof MessagesState> = async (state) => {
        return {
          messages: [await modelWithTools.invoke([
            new SystemMessage(
              "You are a helpful assistant tasked with performing arithmetic on a set of inputs."
            ),
            ...state.messages,
          ])],
          llmCalls: 1,
        };
      };

      // Step 4: Define tool node

      const toolNode: GraphNode<typeof MessagesState> = async (state) => {
        const lastMessage = state.messages.at(-1);

        if (lastMessage == null || !AIMessage.isInstance(lastMessage)) {
          return { messages: [] };
        }

        const result: ToolMessage[] = [];
        for (const toolCall of lastMessage.tool_calls ?? []) {
          const tool = toolsByName[toolCall.name];
          const observation = await tool.invoke(toolCall);
          result.push(observation);
        }

        return { messages: result };
      };
      ```

      ```typescript  theme={null}
      // Step 5: Define logic to determine whether to end
      import { ConditionalEdgeRouter, END } from "@langchain/langgraph";

      const shouldContinue: ConditionalEdgeRouter<typeof MessagesState, "toolNode"> = (state) => {
        const lastMessage = state.messages.at(-1);

        // Check if it's an AIMessage before accessing tool_calls
        if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
          return END;
        }

        // If the LLM makes a tool call, then perform an action
        if (lastMessage.tool_calls?.length) {
          return "toolNode";
        }

        // Otherwise, we stop (reply to the user)
        return END;
      };
      ```

      ```typescript  theme={null}
      // Step 6: Build and compile the agent
      import { HumanMessage } from "@langchain/core/messages";
      import { StateGraph, START, END } from "@langchain/langgraph";

      const agent = new StateGraph(MessagesState)
        .addNode("llmCall", llmCall)
        .addNode("toolNode", toolNode)
        .addEdge(START, "llmCall")
        .addConditionalEdges("llmCall", shouldContinue, ["toolNode", END])
        .addEdge("toolNode", "llmCall")
        .compile();

      // Invoke
      const result = await agent.invoke({
        messages: [new HumanMessage("Add 3 and 4.")],
      });

      for (const message of result.messages) {
        console.log(`[${message.type}]: ${message.text}`);
      }
      ```
    </Accordion>
  </Tab>

  <Tab title="Use the Functional API">
    ## 1. Define tools and model

    In this example, we'll use the Claude Sonnet 4.5 model and define tools for addition, multiplication, and division.

    ```typescript  theme={null}
    import { ChatAnthropic } from "@langchain/anthropic";
    import { tool } from "@langchain/core/tools";
    import * as z from "zod";

    const model = new ChatAnthropic({
      model: "claude-sonnet-4-5-20250929",
      temperature: 0,
    });

    // Define tools
    const add = tool(({ a, b }) => a + b, {
      name: "add",
      description: "Add two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    const multiply = tool(({ a, b }) => a * b, {
      name: "multiply",
      description: "Multiply two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    const divide = tool(({ a, b }) => a / b, {
      name: "divide",
      description: "Divide two numbers",
      schema: z.object({
        a: z.number().describe("First number"),
        b: z.number().describe("Second number"),
      }),
    });

    // Augment the LLM with tools
    const toolsByName = {
      [add.name]: add,
      [multiply.name]: multiply,
      [divide.name]: divide,
    };
    const tools = Object.values(toolsByName);
    const modelWithTools = model.bindTools(tools);

    ```

    ## 2. Define model node

    The model node is used to call the LLM and decide whether to call a tool or not.

    ```typescript  theme={null}
    import { task, entrypoint } from "@langchain/langgraph";
    import { SystemMessage } from "@langchain/core/messages";

    const callLlm = task({ name: "callLlm" }, async (messages: BaseMessage[]) => {
      return modelWithTools.invoke([
        new SystemMessage(
          "You are a helpful assistant tasked with performing arithmetic on a set of inputs."
        ),
        ...messages,
      ]);
    });
    ```

    ## 3. Define tool node

    The tool node is used to call the tools and return the results.

    ```typescript  theme={null}
    import type { ToolCall } from "@langchain/core/messages/tool";

    const callTool = task({ name: "callTool" }, async (toolCall: ToolCall) => {
      const tool = toolsByName[toolCall.name];
      return tool.invoke(toolCall);
    });
    ```

    ## 4. Define agent

    ```typescript  theme={null}
    import { addMessages } from "@langchain/langgraph";
    import { type BaseMessage } from "@langchain/core/messages";

    const agent = entrypoint({ name: "agent" }, async (messages: BaseMessage[]) => {
      let modelResponse = await callLlm(messages);

      while (true) {
        if (!modelResponse.tool_calls?.length) {
          break;
        }

        // Execute tools
        const toolResults = await Promise.all(
          modelResponse.tool_calls.map((toolCall) => callTool(toolCall))
        );
        messages = addMessages(messages, [modelResponse, ...toolResults]);
        modelResponse = await callLlm(messages);
      }

      return messages;
    });

    // Invoke
    import { HumanMessage } from "@langchain/core/messages";

    const result = await agent.invoke([new HumanMessage("Add 3 and 4.")]);

    for (const message of result) {
      console.log(`[${message.getType()}]: ${message.text}`);
    }
    ```

    <Tip>
      To learn how to trace your agent with LangSmith, see the [LangSmith documentation](/langsmith/trace-with-langgraph).
    </Tip>

    Congratulations! You've built your first agent using the LangGraph Functional API.

    <Accordion title="Full code example" icon="code">
      ```typescript  theme={null}
      import { ChatAnthropic } from "@langchain/anthropic";
      import { tool } from "@langchain/core/tools";
      import {
        task,
        entrypoint,
        addMessages,
      } from "@langchain/langgraph";
      import {
        SystemMessage,
        HumanMessage,
        type BaseMessage,
      } from "@langchain/core/messages";
      import type { ToolCall } from "@langchain/core/messages/tool";
      import * as z from "zod";

      // Step 1: Define tools and model

      const model = new ChatAnthropic({
        model: "claude-sonnet-4-5-20250929",
        temperature: 0,
      });

      // Define tools
      const add = tool(({ a, b }) => a + b, {
        name: "add",
        description: "Add two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      const multiply = tool(({ a, b }) => a * b, {
        name: "multiply",
        description: "Multiply two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      const divide = tool(({ a, b }) => a / b, {
        name: "divide",
        description: "Divide two numbers",
        schema: z.object({
          a: z.number().describe("First number"),
          b: z.number().describe("Second number"),
        }),
      });

      // Augment the LLM with tools
      const toolsByName = {
        [add.name]: add,
        [multiply.name]: multiply,
        [divide.name]: divide,
      };
      const tools = Object.values(toolsByName);
      const modelWithTools = model.bindTools(tools);

      // Step 2: Define model node

      const callLlm = task({ name: "callLlm" }, async (messages: BaseMessage[]) => {
        return modelWithTools.invoke([
          new SystemMessage(
            "You are a helpful assistant tasked with performing arithmetic on a set of inputs."
          ),
          ...messages,
        ]);
      });

      // Step 3: Define tool node

      const callTool = task({ name: "callTool" }, async (toolCall: ToolCall) => {
        const tool = toolsByName[toolCall.name];
        return tool.invoke(toolCall);
      });

      // Step 4: Define agent

      const agent = entrypoint({ name: "agent" }, async (messages: BaseMessage[]) => {
        let modelResponse = await callLlm(messages);

        while (true) {
          if (!modelResponse.tool_calls?.length) {
            break;
          }

          // Execute tools
          const toolResults = await Promise.all(
            modelResponse.tool_calls.map((toolCall) => callTool(toolCall))
          );
          messages = addMessages(messages, [modelResponse, ...toolResults]);
          modelResponse = await callLlm(messages);
        }

        return messages;
      });

      // Invoke

      const result = await agent.invoke([new HumanMessage("Add 3 and 4.")]);

      for (const message of result) {
        console.log(`[${message.type}]: ${message.text}`);
      }
      ```
    </Accordion>
  </Tab>
</Tabs>

***

<Callout icon="edit">
  [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/langgraph/quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
</Callout>

<Callout icon="terminal-2">
  [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
</Callout>

2.2.2. 逻辑生成

同样让 Coding Agent 辅助生成代码

npm start "基于 1_langgraph/src/md/lang_graph_quick_start.md,在 1_langgraph/src/index.js 生成一个 ReAct 风格的 Coding Agent,注册 1_langgraph/src/tools 中的全部工具"

最终结果基于你所用的 Agent、模型会有差异。这里贴出我基于 claude-opus-4-6 生成同时证过的代码

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import {
  StateGraph,
  MessagesAnnotation,
  START,
  END,
} from "@langchain/langgraph";
import {
  SystemMessage,
  HumanMessage,
  AIMessage,
} from "@langchain/core/messages";
import allTools from "./tools/index.js";

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

You are a ReAct-style coding agent. You have access to a set of tools for interacting with the filesystem and executing commands.

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;

// 构建 toolsByName 映射
const toolsByName = {};
for (const t of allTools) {
  toolsByName[t.name] = t;
}

// 将工具绑定到模型
const model = new ChatDeepSeek({
  model: "deepseek-chat",
  temperature: 0,
  maxTokens: 4096,
  apiKey: process.env.DEEPSEEK_API_KEY,
});
const modelWithTools = model.bindTools(allTools);

// Step 1: 模型节点 — 调用 LLM 决定是否使用工具
const llmCall = async (state) => {
  const response = await modelWithTools.invoke([
    new SystemMessage(SYSTEM_PROMPT),
    ...state.messages,
  ]);
  return { messages: [response] };
};

// Step 2: 工具节点 — 执行 LLM 请求的工具调用
const toolNode = async (state) => {
  const lastMessage = state.messages.at(-1);

  if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
    return { messages: [] };
  }

  const results = [];
  for (const toolCall of lastMessage.tool_calls ?? []) {
    const tool = toolsByName[toolCall.name];
    if (!tool) {
      results.push({
        role: "tool",
        content: `Error: Unknown tool "${toolCall.name}"`,
        tool_call_id: toolCall.id,
      });
      continue;
    }
    const observation = await tool.invoke(toolCall);
    results.push(observation);
  }

  return { messages: results };
};

// Step 3: 条件路由 — 有 tool_calls 则继续,否则结束
const shouldContinue = (state) => {
  const lastMessage = state.messages.at(-1);

  if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
    return END;
  }

  if (lastMessage.tool_calls?.length) {
    return "toolNode";
  }

  return END;
};

// Step 4: 构建并编译 StateGraph
const agent = new StateGraph(MessagesAnnotation)
  .addNode("llmCall", llmCall)
  .addNode("toolNode", toolNode)
  .addEdge(START, "llmCall")
  .addConditionalEdges("llmCall", shouldContinue, ["toolNode", END])
  .addEdge("toolNode", "llmCall")
  .compile();

// CLI 入口
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("请输入指令");
  process.exit(1);
}

const result = await agent.invoke(
  { messages: [new HumanMessage(args[0])] },
  { recursionLimit: 100 },
);

// 输出最后一条消息
const lastMsg = result.messages[result.messages.length - 1];
console.log(lastMsg.content);

看着一大堆,其实 ReAct 的核心逻辑只在 108~114 行的 Graph(图)构造和 21~31 行的 Prompt 部分

代码 Graph 图示 先前绘制的 ReAct 图示

用人话来说就是在调用 LLM 的时候和模型说「如果现有对话已经包含全部所需信息,那么你就输出结果,否则返回希望调用什么工具、获取什么信息」

2.2.3. 验证结果

工具 + ReAct 范式代码、Prompt 都已经完成,那么 mca 就可以直接运行了(记得在 package.json 中定义一下 script)

npm run start:langgraph "分析 1_langgraph 项目"
现在我已经对项目有了全面的了解。让我总结一下分析结果:

## 1_langgraph 项目分析报告

### 项目概述
这是一个基于 LangGraph 框架构建的 ReAct 风格编码代理项目。项目使用 LangGraph 的 StateGraph API 来创建一个能够执行文件系统操作和命令执行的智能代理。

### 项目结构
```
1_langgraph/
├── src/
│   ├── index.js              # 主入口文件,包含 LangGraph 代理实现
│   ├── tools/               # 工具模块
│   │   ├── index.js         # 工具导出文件
│   │   ├── bash.js          # bash 命令执行工具
│   │   ├── edit.js          # 文件编辑工具
│   │   ├── glob.js          # 文件模式匹配工具
│   │   ├── grep.js          # 文件内容搜索工具
│   │   ├── read.js          # 文件读取工具
│   │   └── write.js         # 文件写入工具
│   └── md/                  # 文档文件
│       ├── lang_graph_quick_start.md    # LangGraph 快速入门指南
│       ├── how_to_create_a_tool.md      # 工具创建指南
│       └── a_list_of_all_required_tools.md # 所需工具列表
```

### 核心技术栈
1. **LangGraph**: 用于构建有状态的代理工作流
2. **DeepSeek API**: 作为 LLM 模型提供者
3. **Zod**: 用于工具参数的模式验证
4. **Node.js**: 运行时环境

### 核心功能

#### 1. 代理架构
项目实现了经典的 ReAct (Reasoning + Acting) 模式:
- **LLM 节点**: 调用 DeepSeek 模型决定是否使用工具
- **工具节点**: 执行 LLM 请求的工具调用
- **条件路由**: 根据是否有工具调用决定继续执行还是结束

#### 2. 可用工具
项目提供了 6 个核心工具:

1. **bash**: 执行 shell 命令,支持工作目录持久化
2. **read**: 读取文件内容,支持行号显示和分页
3. **write**: 写入文件,用于创建新文件或完全重写
4. **edit**: 精确字符串替换编辑文件
5. **glob**: 文件模式匹配搜索
6. **grep**: 正则表达式文件内容搜索

#### 3. 状态管理
使用 LangGraph 的 `MessagesAnnotation` 来管理消息状态,支持多轮对话和工具调用。

### 运行方式
```bash
# 通过 npm 脚本运行
npm run start:langgraph -- "你的指令"

# 示例
npm run start:langgraph -- "列出当前目录下的文件"
```

### 环境配置
项目需要以下环境变量:
- `DEEPSEEK_API_KEY`: DeepSeek API 密钥
- `LANGSMITH_*`: LangSmith 追踪配置(可选)

### 设计特点

1. **模块化工具设计**: 每个工具都是独立的模块,易于扩展和维护
2. **错误处理**: 所有工具都有完善的错误处理机制
3. **安全性**: 工具参数使用 Zod 进行严格验证
4. **用户体验**: 提供清晰的错误信息和操作反馈
5. **可扩展性**: 可以轻松添加新的工具到系统中

### 与 0_deepagents 项目的对比
整个项目包含两个子项目:
1. **0_deepagents**: 使用 DeepAgents 框架实现的简单代理
2. **1_langgraph**: 使用 LangGraph 框架实现的更复杂的 ReAct 代理

主要区别:
- **0_deepagents**: 使用 DeepAgents 的 FilesystemBackend,更简单但功能有限
- **1_langgraph**: 使用 LangGraph 的 StateGraph,支持复杂的状态管理和条件路由

### 项目价值
这个项目展示了如何使用 LangGraph 构建一个实用的编码代理,具有以下价值:
1. **学习价值**: 展示了 LangGraph 的核心概念和最佳实践
2. **实用价值**: 可以直接用于自动化文件操作和代码维护
3. **扩展价值**: 提供了良好的架构基础,可以轻松添加新功能

### 改进建议
1. 可以考虑添加更多工具(如 git 操作、npm 脚本执行等)
2. 可以添加更详细的日志和追踪功能
3. 可以考虑添加用户配置选项
4. 可以优化错误信息的可读性

这个项目是一个很好的 LangGraph 实践示例,展示了如何构建一个功能完整的 ReAct 风格编码代理。

同 deepagents,一样可以在 LangSmith 中查看执行日志

3. ♾️ 分钟完善 Mini Coding Agent

DLC:优化 Coding Agent

基于上述流程,我们已经完成了一个「勉强能用」的 Coding Agent

  • 提供文件读写工具 ✅
  • LLM + Prompt + ReAct 范式 ✅

如果说我们实现的 mca 是下限,那么上限就是 Cladue Code、甚至 OpenClaw

要实现一个上限水平的 Coding Agent,可以说是不计成本没有止境的

比较典型的优化手段如下:

  • TODO 系统
  • 子任务
  • 允许自定义 MCP
  • 优化 Prompt
  • ...

我们可以继续深入,尝试接入这些优化方法

3.1. 优化前的准备:简化代码

首先基于上个章节的 tools ,重新启用一个新的子项目,这种方式创建的 Agent 「图(节点、边)」和上一章节里的除开写法以外没有任何区别,既不会像 deepagents 一样各种功能都有也不会有一堆胶水代码。非常利于我们接下来的各种优化

 mca
 ├── .env
 ├── 0_deepagents
 ├── 1_langgraph
+├── 2_agents
+│   └── src
+│       └── index.js
 └── package.json
// 2_agents/src/index.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import { createAgent } from "langchain";
import allTools from "../../1_langgraph/src/tools/index.js";

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

You are a ReAct-style coding agent. You have access to a set of tools for interacting with the filesystem and executing commands.

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;

// 创建模型
const model = new ChatDeepSeek({
  model: "deepseek-chat",
  temperature: 0,
  maxTokens: 4096,
  apiKey: process.env.DEEPSEEK_API_KEY,
});

// 基于 LangChain createAgent 构建 Agent
const agent = createAgent({
  model,
  tools: allTools,
  systemPrompt: SYSTEM_PROMPT,
});

// CLI 入口
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("请输入指令");
  process.exit(1);
}

const result = await agent.invoke(
  {
    messages: [
      {
        role: "user",
        content: args[0],
      },
    ],
  },
  { recursionLimit: 100 },
);

// 输出最后一条消息
const lastMsg = result.messages[result.messages.length - 1];
console.log(lastMsg.content);

3.2. TODO 系统

在之前的 deepagents 执行日志中,可以观察到 TODO 相关逻辑(如果没有可以尝试执行一个比较复杂的任务,或者给任务加上必须用 TODO 工具的要求)

工具 说明
WRITE_TODO 即时更新已完成的 To-do 项,要求添加或更新未来的 To-do 项,「确保」 Agent 按照正确的顺序和步骤执行任务,同时也避免跳过步骤。

TODO 工具其实非常容易实现,可以理解为工具就是直接透传返回输入,每次对于 TODO 的更新都是全量的。

image.png 同时也不会有所谓的 READ_TODO 工具,原因是 WRITE_TODO 本身就是一次模型对话中的 TOOL_CALL,所以完整的入参(这也是不单独更新某一项的好处),其实就等于输出

底层逻辑是定期在对话中插入 TODO 结构文本,用于提高模型的注意力避免模型过于发散

这里我们直接用现成的 LangChain TODO 中间件即可,无需单独实现一个工具

// 2_agents/src/index.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
+ import { createAgent, todoListMiddleware } from "langchain";
import allTools from "../../1_langgraph/src/tools/index.js";

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

You are a ReAct-style coding agent. You have access to a set of tools for interacting with the filesystem and executing commands.

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;

// 创建模型
const model = new ChatDeepSeek({
  model: "deepseek-chat",
  temperature: 0,
  maxTokens: 4096,
  apiKey: process.env.DEEPSEEK_API_KEY,
});

// 基于 LangChain createAgent 构建 Agent
const agent = createAgent({
  model,
  tools: allTools,
  systemPrompt: SYSTEM_PROMPT,
+ middleware: [todoListMiddleware()],
});

// CLI 入口
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("请输入指令");
  process.exit(1);
}


const result = await agent.invoke(
  {
    messages: [
      {
        role: "user",
        content: args[0],
      },
    ],
  },
  { recursionLimit: 100 },
);

// 输出最后一条消息
const lastMsg = result.messages[result.messages.length - 1];
console.log(lastMsg.content);

3.3. 子任务


子任务其实就是一个独立上下文、基本同主任务一致的一次对话任务

使用子任务最大的好处是可以单独处理复杂的子任务而不消耗额外的主链路上下文

按通常实现,把子任务(Sub Agent)当成一个工具用即可

出入参、使用逻辑同样可以参考 Claude

Task
Tool name: Task

```ts
type AgentInput = {
  description: string;
  prompt: string;
  subagent_type: string;
  model?: "sonnet" | "opus" | "haiku";
  resume?: string;
  run_in_background?: boolean;
  max_turns?: number;
  name?: string;
  team_name?: string;
  mode?: "acceptEdits" | "bypassPermissions" | "default" | "dontAsk" | "plan";
  isolation?: "worktree";
}
```

Launches a new agent to handle complex, multi-step tasks autonomously.

## Task

Launch a new agent to handle complex, multi-step tasks autonomously.

The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.

Available agent types and the tools they have access to:
- general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. (Tools: *)
- statusline-setup: Use this agent to configure the user's Claude Code status line setting. (Tools: Read, Edit)
- Explore: Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions. (Tools: All tools except Task, ExitPlanMode, Edit, Write, NotebookEdit)
- Plan: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. (Tools: All tools except Task, ExitPlanMode, Edit, Write, NotebookEdit)

When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.

When NOT to use the Task tool:
- If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly
- If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly
- Other tasks that are not related to the agent descriptions above


Usage notes:
- Always include a short description (3-5 words) summarizing what the agent will do
- Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
- When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
- You can optionally run agents in the background using the run_in_background parameter. When an agent runs in the background, you will be automatically notified when it completes — do NOT sleep, poll, or proactively check on its progress. Continue with other work or respond to the user instead.
- **Foreground vs background**: Use foreground (default) when you need the agent's results before you can proceed — e.g., research agents whose findings inform your next steps. Use background when you have genuinely independent work to do in parallel.
- Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed task description with all necessary context.
- When the agent is done, it will return a single message back to you along with its agent ID. You can use this ID to resume the agent later if needed for follow-up work.
- Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
- Agents with "access to current context" can see the full conversation history before the tool call. When using these agents, you can write concise prompts that reference earlier context (e.g., "investigate the error discussed above") instead of repeating information. The agent will receive all prior messages and understand the context.
- The agent's outputs should generally be trusted
- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
- If the user specifies that they want you to run agents "in parallel", you MUST send a single message with multiple Task tool use content blocks. For example, if you need to launch both a build-validator agent and a test-runner agent in parallel, send a single message with both tool calls.
- You can optionally set `isolation: "worktree"` to run the agent in a temporary git worktree, giving it an isolated copy of the repository. The worktree is automatically cleaned up if the agent makes no changes; if changes are made, the worktree path and branch are returned in the result.

Example usage:

<example_agent_descriptions>
"test-runner": use this agent after you are done writing code to run tests
"greeting-responder": use this agent to respond to user greetings with a friendly joke
</example_agent_descriptions>

<example>
user: "Please write a function that checks if a number is prime"
assistant: Sure let me write a function that checks if a number is prime
assistant: First let me use the Write tool to write a function that checks if a number is prime
assistant: I'm going to use the Write tool to write the following code:
<code>
function isPrime(n) {
  if (n <= 1) return false
  for (let i = 2; i * i <= n; i++) {
    if (n % i === 0) return false
  }
  return true
}
</code>
<commentary>
Since a significant piece of code was written and the task was completed, now use the test-runner agent to run the tests
</commentary>
assistant: Now let me use the test-runner agent to run the tests
assistant: Uses the Task tool to launch the test-runner agent
</example>

<example>
user: "Hello"
<commentary>
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
</commentary>
assistant: "I'm going to use the Task tool to launch the greeting-responder agent"
</example>

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "description": {
      "description": "A short (3-5 word) description of the task",
      "type": "string"
    },
    "prompt": {
      "description": "The task for the agent to perform",
      "type": "string"
    },
    "subagent_type": {
      "description": "The type of specialized agent to use for this task",
      "type": "string"
    },
    "model": {
      "description": "Optional model to use for this agent. If not specified, inherits from parent. Prefer haiku for quick, straightforward tasks to minimize cost and latency.",
      "type": "string",
      "enum": [
        "sonnet",
        "opus",
        "haiku"
      ]
    },
    "resume": {
      "description": "Optional agent ID to resume from. If provided, the agent will continue from the previous execution transcript.",
      "type": "string"
    },
    "run_in_background": {
      "description": "Set to true to run this agent in the background. The tool result will include an output_file path - use Read tool or Bash tail to check on output.",
      "type": "boolean"
    },
    "max_turns": {
      "description": "Maximum number of agentic turns (API round-trips) before stopping. Used internally for warmup.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "isolation": {
      "description": "Isolation mode. "worktree" creates a temporary git worktree so the agent works on an isolated copy of the repo.",
      "type": "string",
      "enum": [
        "worktree"
      ]
    }
  },
  "required": [
    "description",
    "prompt",
    "subagent_type"
  ],
  "additionalProperties": false
}

---

可以和之前一样让 CodingAgent 根据当前信息生成,不过这里为了便于理解使用一个简化的 Task Tool

// 2_agents/src/tools/task.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import { createAgent, todoListMiddleware } from "langchain";
import allTools from "../../../1_langgraph/src/tools/index.js";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;


// 创建模型
const model = new ChatDeepSeek({
    model: "deepseek-chat",
    temperature: 0,
    maxTokens: 4096,
    apiKey: process.env.DEEPSEEK_API_KEY,
});

export const taskTool = tool(
    async ({ input, systemPrompt }) => {
        const agent = createAgent({
            model,
            tools: allTools,
            systemPrompt: SYSTEM_PROMPT + "\n" + systemPrompt,
            middleware: [todoListMiddleware()],
        });
        const result = await agent.invoke(
            {
                messages: [
                    {
                        role: "user",
                        content: input,
                    },
                ],
            },
            { recursionLimit: 100 },
        );

        const lastMsg = result.messages[result.messages.length - 1];
        if (typeof lastMsg.content === "string") {
            return lastMsg.content;
        }
        return JSON.stringify(lastMsg.content, null, 2);
    },
    {
        name: "task",
        description:
            "Launches a new agent to handle complex, multi-step coding tasks autonomously. The input must include the task goal, all currently known relevant file paths and/or code snippets, any prior analysis results or summaries, plus key constraints (tech stack, style, do/don't rules, performance or security requirements) so the sub agent can work in a single shot with full context.",
        schema: z.object({
            input: z
                .string()
                .describe(
                    "Task description with full context for the sub agent. Include: (1) the user's goal and what needs to be done, (2) relevant file paths, modules, and/or code snippets, (3) any prior analysis results, summaries, or discovered facts that could help, and (4) important constraints such as tech stack, coding style, do/don't rules, and performance/security requirements.",
                ),
            systemPrompt: z
                .string()
                .describe(
                    "The system prompt to be used for the sub agent. This should include all relevant context for the sub agent to work with.",
                ),
        }),
    },
);
// 2_agents/src/index.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import { createAgent, todoListMiddleware } from "langchain";
import allTools from "../../1_langgraph/src/tools/index.js";
+ import { taskTool } from "./tools/task.js";

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

You are a ReAct-style coding agent. You have access to a set of tools for interacting with the filesystem and executing commands.

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;

// 创建模型
const model = new ChatDeepSeek({
  model: "deepseek-chat",
  temperature: 0,
  maxTokens: 4096,
  apiKey: process.env.DEEPSEEK_API_KEY,
});

+ const tools = [...allTools, taskTool];

// 基于 LangChain createAgent 构建 Agent
const agent = createAgent({
  model,
+ tools,
  systemPrompt: SYSTEM_PROMPT,
  middleware: [todoListMiddleware()],
});

// CLI 入口
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("请输入指令");
  process.exit(1);
}


const result = await agent.invoke(
  {
    messages: [
      {
        role: "user",
        content: args[0],
      },
    ],
  },
  { recursionLimit: 100 },
);

// 输出最后一条消息
const lastMsg = result.messages[result.messages.length - 1];
console.log(lastMsg.content);

根据其工具的入参、描述,注册到工具中之后主任务自然会在恰当的时间来调用

3.4. 自定义 MCP

一个合格 Coding Agent 当然也会允许用户自定义 MCP

MCP 本质就是工具调用,我们只需要按指定格式加载配置文件,再用对应 SDK 吧其加载成工具即可

相关文档:docs.langchain.com/oss/javascr…

// 2_agents/src/index.js

import "dotenv/config";
import { ChatDeepSeek } from "@langchain/deepseek";
import { createAgent, todoListMiddleware } from "langchain";
import allTools from "../../1_langgraph/src/tools/index.js";
import { taskTool } from "./tools/task.js";
+ import { MultiServerMCPClient } from "@langchain/mcp-adapters";
+ import mcpConfig from "../mcp.json" assert { type: "json" };

// 项目根目录
const PROJECT_ROOT = process.cwd();

// ReAct Coding Agent 系统提示词
const SYSTEM_PROMPT = `---
PROJECT_ROOT: ${PROJECT_ROOT}
---

You are a ReAct-style coding agent. You have access to a set of tools for interacting with the filesystem and executing commands.

When given a task:
1. Think about what you need to do
2. Use the appropriate tool to gather information or make changes
3. Observe the result
4. Repeat until the task is complete
`;

// 创建模型
const model = new ChatDeepSeek({
  model: "deepseek-chat",
  temperature: 0,
  maxTokens: 4096,
  apiKey: process.env.DEEPSEEK_API_KEY,
});

+ const loadCustomMcp = async () => {
+    const client = new MultiServerMCPClient(mcpConfig.mcpServers);
+    const tools = await client.getTools();
+     return tools;
+ };

+ const tools = [...allTools, taskTool, ...(await loadCustomMcp())];

// 基于 LangChain createAgent 构建 Agent
const agent = createAgent({
  model,
  tools,
  systemPrompt: SYSTEM_PROMPT,
  middleware: [todoListMiddleware()],
});

// CLI 入口
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("请输入指令");
  process.exit(1);
}


const result = await agent.invoke(
  {
    messages: [
      {
        role: "user",
        content: args[0],
      },
    ],
  },
  { recursionLimit: 100 },
);

// 输出最后一条消息
const lastMsg = result.messages[result.messages.length - 1];
console.log(lastMsg.content);

可以配置一个任何 MCP 来验证(比如我用的高德地图 MCP)

3.5. 优化 Prompt

Prompt 看起来好像没有什么技术含量,但是实际上非常关键,并且由于不存在幂等性,难以标准化。所以我们直接参考 Claude

里面包含了各种细节,包括不限于什么时候应该调用什么工具、如何遵循规范...

通过查看 Claude 的 Prompt,还可以非常直观的发现 mca 距离一个完善的 Coding Agent 差多远(也推荐大家找专门逆向 Claude 的文章来学习)

  • 网络搜索
  • 长记忆
  • 适配不同情况的 Sub Agent
  • Plan 模式
  • 像用户提问获取必要信息
  • ...

4. 结语

尽管当前许多AI工具已经实现了「开箱即用」的便捷性,但对于程序员而言,深入理解这些工具背后的原理,无疑能够更加高效和灵活地运用它们。

从零构建一个 Mini Claude Code:面向初学者的 Agent 开发实战指南

同步至个人站点:从零构建一个 Mini Claude Code:面向初学者的 Agent 开发实战指南

202624

本次课程相关链接:

源代码仓库:mini-claude-code

Issues 风格教案(本文总结自此):mini-claude-code/issues

Mini Claude Code: mini-claude-code

Vercel AI SDK 快速上手:Vercel AI SDK 最小用法

Memo Code:Github/minorcell/memo-code

这篇文章源自前两天我做的一次 Agent 开发实战公开课。面向零基础讲清楚"Agent 是什么"比写代码本身难得多——你不能一上来就甩论文,也不能全程只讲故事。

最终的效果还不错,至少最后同学们人手一个能跑起来的Mini Claude Code。本文把整个教学内容重新整理成这篇博客,方便没去现场的朋友回顾,也给想自己动手的朋友们一个完整的指引。

课程目标

在开始之前,先明确一下我们希望达成的学习目标:

  • 理解为什么 Agent 可以做事情,而 ChatBot 不能
  • 听得懂 ReAct 与 Agent 的基本架构
  • 能跑通最小 TypeScript Agent(天气查询 和 Mini Claude Code)
  • 带走工程落地的实际经验

这四个目标也是我们整篇文章的脉络。接下来逐一展开。

为什么 Agent 和 ChatBot 不一样?

202629

这是个关键问题。澄清了这个,后面很多东西就自然通了。

Agents 的例子

先来看一些 Agent 的实际例子:

  • 春节期间的千问:"帮我点一杯奶茶"
  • Manus:"帮我做一个个人博客网站"
  • Claude Code CLI / Cursor IDE:"帮我修复这个 Bug..."
  • OpenClaw:"帮我整理一下谷歌邮件"
  • ......

可以看到明显的几个共同点:

  1. Agent 能做事情
  2. Agent 能持续执行任务,直到完成目标
  3. Agent 能和外部系统交互

从 ChatBot 到 Agent 的演进

早期大模型产品,最经典的是 ChatGPT 的网页版本。用户输入提示词,ChatGPT 回复一段文字,这就是"ChatBot"。那时候它可以短暂记住前后说了什么

比较有意思的是,如果你告诉大模型,让它扮演什么角色、能干什么、不能干什么,它确实会这么做,比如:

  • "你是一个小红书博主,你的核心工作是...."
  • "你是一个资深的 Web 前端高手,....."

后来,网页版的 ChatGPT 又支持了查询天气、支持网络搜索。比如:"明日上海天气如何?",它便会去查询上海的天气,然后告诉你它查到的结果,并且是用自然语言回复(而不是天气查询接口的原始 JSON 输出):"明天上海天气晴朗,气温约在 16-23 摄氏度,可以穿卫衣出行~"。

三个关键要素

其实到这里就已经不是简单的 ChatBot 了,而是"Agent"。用技术术语来说,这三个要素构成了 LLM-based Agent 的最小 viable architecture(最小可行架构)

用户感知 技术术语 技术本质
记住前后说了什么 上下文窗口对话状态管理 维护一个消息数组,包含 system/user/assistant/tool 角色的历史消息,作为模型的输入上下文
扮演什么角色、能干什么 系统提示词角色注入 在请求体最前端的 system 消息中定义行为边界、能力范围、输出格式约束,通过 Prompt Engineering 对齐模型行为
查询上海的天气 工具调用外部能力扩展 模型生成结构化输出(JSON/XML)触发外部 API,将结果以 tool/function 角色回注上下文

怎么实现的?比较成熟的一种方式是 ReAct

时至今日,Agent 开发范式不止 ReAct 一种。Plan-and-ExecuteFunction/Tool Calling + Loop(ReAct Like) 等多种模式也有较多的应用。但 ReAct 是最经典、最容易理解的一种,非常适合作为入门。

ReAct——让模型"边想边做"

ReActReasoning + Acting)是 Agent 架构的一种核心范式。它不再是大模型单次输入输出的"问答",而是一个**"观察 → 思考 → 行动 → 再观察"**的循环过程。

一个例子

想象你让 Manus "帮我做一个个人博客网站"。如果是传统 ChatBot,它会给你一段代码和教程,然后结束对话,你需要手动复制粘贴代码到本地测试,有问题之后再把错误发给它,让它再次修复。

但 ReAct 模式下,Agent 会这样执行:

[思考 Thought]
用户需要一个博客网站,需求是:简洁风格、支持 Markdown、部署到线上。
我需要:1) 先创建项目结构 2) 编写 HTML/CSS  3) 添加交互逻辑  4) 部署到服务器。
第一步应该是初始化 Next.js 项目。

[行动 Action]
执行 shell 命令:
px create-next-app@latest my-blog --typescript --tailwind

[观察 Observation]
命令执行成功,项目目录已创建,但终端提示需要安装依赖。

[思考 Thought]
依赖安装是下一步,然后我需要创建首页组件。等等,用户要求简洁风格,我应该先查看默认生成的文件结构,决定保留哪些。

[行动 Action]
执行:cd my-blog && ls -la(查看目录结构)

...(循环继续,直到网站部署完成)

技术实现:控制流的转移

不难看出,ReAct 的关键在于控制流从我们转移到了模型:

阶段 控制方 交互方式
ChatBot 人类 人问 → 模型答 → 人决定下一步
ReAct Agent 模型 模型观察 → 模型决策 → 模型自主执行 → 模型根据反馈调整

具体实现时,一个简单的 Agent 系统提示词可能会包含这样的指令框架:

你是一个xxx的助手。
...
你可以使用以下工具:[文件操作、命令执行、网络搜索...]

每次回复必须遵循以下格式:
<observation>当前环境状态/工具返回结果</observation>
<thought>分析当前状况,决定下一步行动</thought>
<action>{"tool": "tool_name", "params": {...}}</action>

当模型输出 <action> 时,Agent 框架(如 Claude Code CLI 的运行时)会解析这段 JSON,实际调用对应的工具(落地到代码就是执行一段函数),然后将工具返回的结果以 <observation> 的形式重新注入上下文,再次请求模型,形成闭环。

为什么这能实现"持续执行"?

自我修正能力:如果某一步行动报错(比如代码编译失败),这个错误会作为新的 Observation 回到模型,模型会在 Thought 中分析错误原因,调整 Action(比如修改代码),而不是像 ChatBot 那样等待用户手动修复。

任务分解:面对"帮我修复这个 Bug"这样的复杂指令,模型会在 Thought 中自动拆解:先定位文件 → 阅读相关代码 → 理解逻辑 → 修改 → 测试验证,而不是一次性尝试解决(那样往往失败)。

状态持久:每一轮循环的 Observation 和 Thought 都追加到上下文中,Agent 不会"遗忘"已经完成的步骤(比如已经创建了哪个文件、修改了哪行代码),确保任务连续性。

这正是早期 ChatBot 与现在 Agent 的本质区别:前者是"一次性建议",后者是"闭环执行"

ReAct Agent 最小架构

202628

把上面的东西组装起来,Agent 的最小架构可以用一个公式概括:

Agent = ReAct + Tools + UI

或者更详细点:

Agent = ReAct(LLM + Context(System + User + LLM + Tool)) + Tools + UI
  • UI:用户交互界面,CLI、Web、APP 都行
  • Tools:让 Agent 支持哪些功能,就需要对应的工具支持
  • ReAct:如上文所说,它核心是个 Loop 循环

注解: LLM: 大模型本身,作为整个 Agent 的"脑子" System:系统提示词,通常包含基础提示词、人格设定、工具使用指南等

一个最小可行的 Agent 便是如此。

最小 Agent 实战——天气查询(Bun + TypeScript)

202627

光说不练假把式。我们来动手做一个最小的 Agent。

项目初始化

用 Bun + TypeScript,零依赖:

mkdir agent-loop && cd agent-loop
bun init -y
bun add axios

完整的项目结构如下:

agent-loop/
├── main.ts      # 核心 Agent Loop
├── tools.ts     # 工具定义
├── prompt.md    # 系统提示词
└── package.json

工具定义(tools.ts)

首先定义两个最小集的工具:获取当前时间和查询天气。

// tools.ts
export type ToolName = 'getCurrentTime' | 'getWeather'

export const TOOLKIT: Record<ToolName, (input: string) => Promise<string>> = {
  getCurrentTime: async () => {
    return new Date().toISOString()
  },

  getWeather: async (input: string) => {
    // 解析城市名称(简单粗暴,实际项目请用更好的解析)
    const city = input.trim()
    // 这里调用免费的天气 API,实际使用请替换为真实的 API
    try {
      const response = await fetch(`https://wttr.in/${city}?format=j1`)
      const data = await response.json()
      const current = data.current_condition[0]
      return `${city} 当前天气:${current.weatherDesc[0].value},温度 ${current.temp_C}°C`
    } catch {
      return `无法获取 ${city} 的天气,请检查城市名称是否正确。`
    }
  },
}

系统提示词(prompt.md)

系统提示词告诉模型如何使用这两个工具,以及输出的格式要求。

你是天气查询的工具型助手,回答要简洁。
可用工具(action 的 tool 属性需与下列名称一致):

- getTime: 返回当前 time 字符串,参数为空。
- getWeather: 返回模拟天气信息字符串,参数为 JSON,如 {"city":"上海","time":"2026-02-27 10:00"}。

回复格式(严格使用 XML,小写标签):
<thought>对问题的简短思考</thought>
<action tool="工具名">工具输入</action> <!-- 若需要工具 -->
等待 <observation> 后再继续思考。
如果已可直接回答,则输出:
<final>最终回答(中文,必要时引用数据来源)</final>

规则:

- 每次仅调用一个工具;工具输入要尽量具体。
- 当用户只问“现在几点”时,优先调用 getTime。
- 查询天气时,必须调用 getWeather,并提供 city 和 time 两个字段。
- 如果拿到 observation 后有了答案,应输出 <final> 而不是重复调用。
- 未知工具时要说明,但仍用 XML 格式。
- 避免幻觉,不确定时请说明。

核心 Loop 代码(main.ts)

这是整个 Agent 的核心——40 行代码实现 ReAct 循环。

// main.ts
import { TOOLKIT, type ToolName } from './tools'

interface ChatMessage {
  role: 'system' | 'user' | 'assistant'
  content: string
}

// 解析模型输出,提取工具调用或最终回答
function parseAssistant(text: string): {
  action?: { tool: string; input: string }
  final?: string
} {
  const toolCallMatch = text.match(
    /\[TOOL_CALL\]\s*tool:\s*(\w+)\s*input:\s*(.+)/s,
  )
  if (toolCallMatch) {
    return {
      action: {
        tool: toolCallMatch[1],
        input: toolCallMatch[2].trim(),
      },
    }
  }

  const finalMatch = text.match(/\[FINAL\]\s*(.+)/s)
  if (finalMatch) {
    return { final: finalMatch[1].trim() }
  }

  return {}
}

// 调用 LLM(这里以 OpenAI 兼容接口为例)
async function callLLMs(messages: ChatMessage[]): Promise<string> {
  const response = await fetch(
    process.env.LLM_API_URL || 'https://api.deepseek.com/v1',
    'POST',
    {
      model: process.env.LLM_MODEL || 'deepseek-chat',
      messages,
      temperature: 0.7,
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.LLM_API_KEY}`,
        'Content-Type': 'application/json',
      },
    },
  )

  const data = await response.json()
  return data.choices[0].message.content
}

// 核心 Agent Loop
async function AgentLoop(question: string) {
  const systemPrompt = await Bun.file('prompt.md').text()

  const history: ChatMessage[] = [
    { role: 'system', content: systemPrompt },
    { role: 'user', content: question },
  ]

  for (let step = 0; step < 10; step++) {
    const assistantText = await callLLMs(history)
    console.log(`\n[LLM 第 ${step + 1} 轮输出]\n${assistantText}\n`)
    history.push({ role: 'assistant', content: assistantText })

    const parsed = parseAssistant(assistantText)
    if (parsed.final) {
      return parsed.final
    }

    if (parsed.action) {
      const toolFn = TOOLKIT[parsed.action.tool as ToolName]
      let observation: string

      if (toolFn) {
        observation = await toolFn(parsed.action.input)
      } else {
        observation = `未知工具: ${parsed.action.tool}`
      }

      console.log(`<observation>${observation}</observation>\n`)

      history.push({
        role: 'user',
        content: `<observation>${observation}</observation>`,
      })
      continue
    }

    break // 未产生 action 或 final
  }

  return '未能生成最终回答,请重试或调整问题。'
}

// 主入口
const question = process.argv[2] || '上海今天天气怎么样?'
console.log(`\n用户问题: ${question}`)
console.log('─'.repeat(50))

AgentLoop(question).then((answer) => {
  console.log('─'.repeat(50))
  console.log(`\n最终回答: ${answer}\n`)
})

运行效果

202625

➜  agent-loop git:(main) bun main.ts
用户问题: 上海现在天气如何?

[LLM 第 1 轮输出]
<thought>用户询问上海现在的天气,需要获取当前时间,然后查询天气。</thought>
<action tool="getTime">获取当前时间</action>

<observation>2026-03-02T02:41:33.898Z</observation>


[LLM 第 2 轮输出]
<thought>已获得当前时间,需要调用getWeather工具查询上海此时的天气。</thought>
<action tool="getWeather">{"city":"上海","time":"2026-03-02 02:41"}</action>

<observation>天气信息:上海 在 2026-03-02 02:41 的天气为小雨,气温 15°C,北风 1 级,湿度 43%。</observation>


[LLM 第 3 轮输出]
<final>上海现在(2026-03-02 02:41)的天气为小雨,气温15°C,北风1级,湿度43%。</final>


=== 最终回答 ===
上海现在(2026-03-02 02:41)的天气为小雨,气温15°C,北风1级,湿度43%。
➜  agent-loop git:(main)

代码解读

这个 40 行的核心循环其实很简单:

  1. 加载系统提示词——从 prompt.md 文件读取
  2. 维护一个 history 消息数组——作为 Agent 的上下文记忆
  3. 循环最多 10 轮
    • 调用大模型,获取输出
    • 解析输出:是最终回答就返回,是工具调用就执行工具并把结果填回上下文
    • 如果既不是最终回答也不是工具调用,就退出循环

这就是一个最小 Agent 的全部。没有任何复杂的框架,就是一个 for 循环 + 消息数组维护。

Mini Claude Code 设计

有了最小版做基础,我们来做一个更完整的——Mini Claude Code。

先拆 Claude Code CLI

Claude Code CLI 是个超成熟的 Code Agent 范例。要搞 Mini 版,先来简单拆一拆它的核心:

内置工具

  • 文件系统:Read File, write_file File, edit_file File, Search Files...
  • Bash/Shell:最常用,查 Git、跑复杂命令、执行 Python/Node 代码
  • 网络:WebFetch
  • 上下文管理:Plan/Todo
  • MCP Client
  • 子 Agents:进程间交互

上下文

  • 压缩机制
  • 会话历史:.claude/sessions/*.jsonl
  • Skills 系统

为什么会把 Skills 看作是上下文的一种?实际上在 Agent 中,我们的通常做法是在初始化时把 Skills 的索引一同注入到系统提示词里,让模型在需要时调用。它本质上是一个"工具使用指南",一种渐进式纰漏的提示词,但又不是传统意义上的工具,所以我把它归类到上下文管理里。

TUI/CLI

  • claude mcp ..., claude -c, claude -p, claude -dangerous 等用法
  • 终端交互:slash 命令、IO 流等

Mini 版精简设计

工具设计

Mini 版工具精简到 4 个核心,够用不乱:

  • read_file / write_file / edit_file(文件三件套)
  • bash(Shell 执行)
  • WebFetch(网络请求)

原则:工具别贪多。每多一个,模型负担就加重。Unix 哲学——一工具一事,但组合无限。

技术选型:为什么用 Vercel AI SDK?

之前天气 Demo 用原生 fetch 调用 LLM,手动解析 SSE。零依赖好理解,但生产级有几个问题:

问题 1:多 Provider 适配成本高 换一个模型提供商(OpenAI → Anthropic → Gemini),就要重写请求 URL、Header 格式、响应解析逻辑。

问题 2:工具调用状态机要自己维护 agent-loop 里的 for 循环、parseAssistant()、往 history 里推 observation,这些都是在手写一个工具调用的状态机。稍有差错,模型就会丢失上下文。

问题 3:没有类型安全 工具的输入参数是一个裸字符串,解析 JSON 要靠 try/catch,参数字段靠字符串 key 访问,TypeScript 无法帮你检查。

Vercel AI SDK 解决了这三个问题:

  • createOpenAI 创建 Provider,换模型只改一行
  • generateText + maxSteps 内置了工具调用状态机,自动处理多轮循环
  • zod 定义参数 schema,工具的 execute 函数拿到的是已解析、有类型的对象

Vercel AI SDK 的详细用法请见:Vercel AI SDK 最小用法

为什么不用 LangChain / LangGraph?可以思考一下——它们很强大,但对于"理解 Agent 核心原理"这个目标来说,引入的复杂度可能大于带来的价值。

Mini Claude Code 实际代码

来看 Mini Claude Code 的实际项目结构:

mini-claude-code/
├── src/
│   ├── agent/
│   │   ├── loop.ts      # Agent 循环核心
│   │   ├── context.ts   # 上下文管理
│   │   ├── prompt.ts    # 提示词组装
│   │   └── provider.ts  # 模型提供商配置
│   ├── tools/
│   │   ├── index.ts     # 工具注册
│   │   ├── read-file.ts
│   │   ├── write-file.ts
│   │   ├── edit-file.ts
│   │   ├── bash.ts
│   │   └── web-fetch.ts
│   ├── index.ts         # 入口
│   └── SYSTEM_PROMPT.md # 系统提示词
├── package.json
└── .env.example

详细代码在仓库:mini-claude-code

Provider 配置(provider.ts)

import { createOpenAI } from '@ai-sdk/openai'

const qiniu = createOpenAI({
  apiKey: process.env.QINIU_API_KEY!,
  baseURL: process.env.QINIU_API_URL || 'https://api.deepseek.com/v1',
})

export const model = qiniu('claude-4.6-sonnat')

换模型只需要改一行:

import { openai } from '@ai-sdk/openai'

// 换成 OpenAI
export const model = openai('openai/gpt-5.3-codex')

工具定义(tools/index.ts)

用 Vercel AI SDK 的 tool() + zod 定义工具:

import { tool } from 'ai'
import { z } from 'zod'
import { readFile } from './read-file'
import { writeFile } from './write-file'
import { editFile } from './edit-file'
import { bash } from './bash'
import { webFetch } from './web-fetch'

export const tools = {
  read_file: tool({
    description: '读取文件内容',
    parameters: z.object({
      path: z.string().describe('文件路径'),
    }),
    execute: async ({ path }) => readFile(path),
  }),

  write_file: tool({
    description: '写入文件内容',
    parameters: z.object({
      path: z.string().describe('文件路径'),
      content: z.string().describe('文件内容'),
    }),
    execute: async ({ path, content }) => writeFile(path, content),
  }),

  edit_file: tool({
    description: '编辑文件内容',
    parameters: z.object({
      path: z.string().describe('文件路径'),
      oldText: z.string().describe('需要替换的旧文本'),
      newText: z.string().describe('新文本'),
    }),
    execute: async ({ path, oldText, newText }) =>
      editFile(path, oldText, newText),
  }),

  bash: tool({
    description: '执行 Shell 命令',
    parameters: z.object({
      command: z.string().describe('要执行的命令'),
    }),
    execute: async ({ command }) => bash(command),
  }),

  web_fetch: tool({
    description: '获取网页内容',
    parameters: z.object({
      url: z.string().describe('网页 URL'),
    }),
    execute: async ({ url }) => webFetch(url),
  }),
}

注意这里的类型安全:参数由 zod schema 定义,SDK 自动解析,execute 函数拿到的 { path, content } 是有类型的对象,不是字符串。

核心 Loop(agent/loop.ts)

import { generateText } from 'ai'
import { model } from './provider'
import { tools } from '../tools'
import { buildSystemPrompt } from './prompt'

export async function* runAgent(question: string) {
  const systemPrompt = await buildSystemPrompt()

  const result = await generateText({
    model,
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: question },
    ],
    tools,
    maxSteps: 10,

    onStepFinish: ({ text, toolCalls, toolResults, finishReason }) => {
      // 这里可以观察每一步的执行过程
      console.log('── 步骤完成 ──────────────')
      if (text) console.log('模型输出:', text)
      for (const call of toolCalls || []) {
        console.log(`调用工具: ${call.toolName}`, call.args)
      }
      for (const result of toolResults || []) {
        console.log(`工具结果: ${result.toolName}`, result.result)
      }
      console.log('结束原因:', finishReason)
    },
  })

  return result.text
}

很魔法的一点,SDK 自动处理了工具调用循环。我们只需要:

  1. 配置 model
  2. 注册 tools
  3. 设置 maxSteps

SDK 就会自动完成:调用模型 → 检测到工具调用 → 执行工具 → 填回结果 → 再次调用模型 → ... → 直到生成最终回答。

运行效果

这里我们问 Mini Claude Code 这是什么项目,效果如下:

202631

比较有意思的是,还让他给自己写了一个介绍网页:

202632

与手写版本的对比

agent-loop(手写) mini-claude-code(SDK)
调用模型 手写 fetch + 解析 JSON generateText()
工具参数 JSON.parse + 手动校验 zod schema 自动解析
工具调用循环 手写 for 循环 + history.push maxSteps 自动处理
换 Provider 改 URL、Header、解析逻辑 换一行 createOpenAI
观察执行过程 console.log 散落各处 onStepFinish 回调

一些实际工程经验

202626

我接触 Agent 开发也算是机缘巧合,从最开始大量的使用 Claude Code 等 Code Agent 编程工具、感兴趣然后自己去研究、最后学习、实践着去做。但是没真正下场之前,我以为 Claude Code 这种东西"差不多就那样"。直到我在做 Memo Code 的过程中才意识到:从"能跑"到"能解决真实问题、能稳定投产",中间的鸿沟还是软件工程。

这里我也选一些比较经典的三类 Agent 的工程问题来聊聊,给大家一些经验:

Agent 上下文工程

聊 Agent / 大模型,绕不开上下文:越跑越长、越长越容易忘。通常要从两个入口拆开看:

  1. 上下文太长了怎么办(模型有固定上下文长度)
  2. 怎么从源头控制上下文不要暴涨

1)上下文太长:压缩 + 断环重启

通用解法是"压缩"。实现上可以很朴素:做 token 计数;当 session 中 context 的 token 占比超过阈值(比如 80%)时,中断当前 Loop,把历史上下文整体丢给模型,让它总结:

  • 已经做了什么
  • 还没做什么
  • 当前状态 / 关键约束
  • 后续注意事项(坑、边界条件、依赖)

然后新开一个会话(或滑动窗口),后续只发送:系统提示词 + 总结 + 新产生的内容。

这一步的关键不在"总结写得多漂亮",而在于它要能支撑下一轮继续干活:信息结构要稳定、可复用、可增量更新。

2)防止上下文暴涨:从源头管住工具输出

真正让上下文爆炸的,很多时候不是用户对话,而是:

  • 工具返回的超长结果(search / read / list)
  • MCP 工具的"工作痕迹"(日志、堆栈、长 JSON)
  • 不合理的提示词(把无关信息一次性塞满)

举个经典例子:TS 项目里的 node_modules,比宇宙还深还大。模型调用 search 去扫项目中的 js/ts 文件,如果 Search 工具没有合理的防护机制,工具返回可能直接无限长,一次下来就能把上下文撑爆。

所以工具设计本身非常重要,至少要有两道闸门:

  • 黑白名单 / ignore 规则:禁止访问某些目录(建议复用 ignore 库,兼容 .gitignore
  • 工具结果拦截:当工具输出超过阈值,要么截断,要么直接返回一个明确的 system-hint,让模型知道"有内容,但被省略了",避免它误以为自己看到了全量信息

比如这种形式就很好(明确、可机器解析、可追踪):

<system_hint type="tool_output_omitted" tool="${toolName}" reason="too_long" actual_chars="${actualChars}" max_chars="${maxChars}">
  Tool output too long, automatically omitted.
</system_hint>

Agent 安全问题

Agent 一旦"能动手",安全问题就不是抽象讨论,而是迟早会发生的事故。

一些我觉得必须认真对待的点:

  • 危险命令误执行rm -rf / 是最经典的例子(早期 Gemini CLI 就踩过)。以及最近"小龙虾误删某高管历史邮件"的故事......这类事故的共同点是:不是模型一定会坏,而是系统缺少最后一道保险
  • 子进程泄漏 / 资源泄漏:Agent 工作时会频繁启停子进程,处理不当就容易出现不可控的内存泄漏。我自己就遇到过:Codex 内存泄漏 54GB,电脑死机。这类问题通常不是"优化一下就好",而是要把资源回收当作一等公民
  • 工具权限边界--dangerous 确实能释放双手,但也会释放风险。到底是给"全程全权限",还是"关键操作每次审批",需要按场景权衡:频繁审批会拖慢体验,但无审批的代价可能是灾难级

七牛 Agent 专用沙箱 或者 e2b、Docker 容器等做法是直接提供给 Agent 一个隔离环境,与用户本地环境隔离开。

可以参考做法:Memo Code 安全设计:子进程、命令防护与权限审批的统一方案,这里不做过多赘述。

Agent 系统提示词

系统提示词怎么写的"内容套路",市面上已经很多了。这里更想聊工程层面的格式与组装:什么样的提示词结构更利好大模型、也更利好长期维护。

很多人把系统提示词当成一个固定的 Markdown 文件来管,但在真实项目里,系统提示词往往要承载这些东西:

  • 基础行为指令(核心身份、输出规范)
  • 用户偏好设置(比如 SOUL.md
  • 项目级上下文(比如 AGENTS.md
  • 动态工具能力(内置工具 + MCP 工具清单/用法)
  • Skills 技能(.agents/skills/skillname/SKILL.md
  • 运行时异常状态(截断提示、危险命令拦截、工具降级)

问题是:来源不同、格式不同、优先级不同,而且有些是运行时动态生成的。这就需要一套统一的拼装逻辑(比如分段、打标签、定义优先级、支持增量更新)。

没有最优解,我把自己在做 Memo Code 时的设计思路整理成了一篇文章: Memo Code 系统提示词架构解析:从模板到上下文组装


收尾

这次课程最大的收获其实是回答了一个问题:"Agent 难不难?"

答案是:核心概念不难,40 行代码就能跑起来。但从玩具到真正能解决问题,中间每一步还是软件工程。

希望这篇文章能帮你推开 Agent 开发的大门。代码仓库在这里:mini-claude-code。相关的教案我也直接放在 Issues 里了。

项目里有两个实战案例:

  • projects/agent-loop —— 纯手写版本,适合理解原理
  • projects/mini-claude-code —— 基于 Vercel AI SDK,适合生产使用

建议先跑通 agent-loop,理解核心循环;再去看 mini-claude-code,学习工程实践。

(完)

4900 万人围观的 Claude Cowork 又杀疯了,10 个顶级外挂上线,这些打工人危

「software armageddon(软件末日)」——这是外媒描述过去几个月软件板块遭遇时用的词。Anthropic 每推出一个新工具,市场就会条件反射式地先问一遍:又有哪些软件要被干掉?然后果断抛售手里的股票。

短短几周,成千上万亿美元的市值凭空消失。

就在昨晚,Anthropic 再度发布企业级产品更新。当所有市场都在等着看又要杀入哪个赛道,结果,Salesforce 涨了 4%,Thomson Reuters 涨了 11%,Figma 涨了 10%,Docusign 和 LegalZoom 均上涨超过 2%。

软件股的噩梦,这次没有如期而至。而市场情绪在一夜之间发生了 180 度转向,这件事本身就值得好好说说。

10 个插件模板,每一个都在盯着人类的工位

先说功能本身。

这次发布的核心逻辑,是把 Claude 变成可以深入企业不同部门的专业智能体,同时允许管理员创建私有插件市场,在组织内部统一分发和管理这些工具。

连接器层面的新增覆盖了大多数主流企业应用:Google Workspace(含 Calendar、Drive、Gmail)、Docusign、Slack、LegalZoom、FactSet、Harvey、Apollo、Clay 等等。

管理员可以基于入门模板快速创建插件,也可从零构建。Claude 会在设置过程中通过提问引导定制,所有内容统一收纳在新的「Customize(自定义)」菜单下,方便集中查看与管理。

斜杠命令现在以结构化表单的形式启动,执行「生成报告」或「创建仪表板」这类工作流时,操作直观得像填一份简单问卷。管理员还可按用户分配插件、设置自动安装,并通过 OpenTelemetry 追踪团队使用成本与工具调用行为。

10 个 插件模板的扩充,则是此次发布的重中之重,每个模板都与相关领域从业者联合设计,覆盖了真实职场中的具体工作场景。

  • HR 插件覆盖员工全生命周期管理,包括起草录用通知、制定入职计划、撰写绩效评估和薪酬分析。
  • 设计插件可生成评审框架、撰写 UX 文案、执行无障碍审查并制定用户研究计划。
  • 工程插件用于撰写总结、事故响应协调和部署清单制定。
  • 运营插件则覆盖流程文档编写、供应商评估和操作手册创建。
  • 金融领域的插件直接瞄准专业服务行业的核心工作流。
  • 财务分析插件支持市场竞争研究与财务建模;
  • 投资银行插件可审阅交易文件、构建可比公司分析并准备推介材料。
  • 股票研究插件能解析财报电话会议记录并根据新指引更新财务模型;
  • 私募股权插件支持大批量文件审阅与情景建模,并对投资机会自动打分。
  • 财富管理插件则帮助顾问识别组合偏离与税务风险,大规模生成再平衡建议。

在跨应用协作层面,Claude 现在可以在 Excel 与 PowerPoint 之间端到端完成多步骤任务。它能先在 Excel 中完成数据分析,再自动生成 PowerPoint 演示文稿,目前以研究预览形式向 Mac 和 Windows 平台所有付费用户开放。

这次更新也是 Anthropic 在智能体领域加速布局的缩影。

上个月 Cowork 刚首次亮相,本月早些时候 Anthropic 还发布了 Claude Opus 4.6 和 Sonnet 4.6。目前 Cowork 仍处于研究预览阶段,向付费的 Pro、Max、Team 和 Enterprise 用户开放。所有的弹药都在指向同一个目标:接管工作。

为什么 Anthropic 点名的合作伙伴,股价都涨了

既然 Claude 已经能代替人类干这么多活了,为什么软件公司的股票反而涨了?要理解这次反弹,得先还原过去几个月那轮恐慌是怎么来的。

投资者的担忧并非无中生有。今年 1 月底 Cowork 开放插件支持后,市场立刻作出了最坏打算——毕竟当 AI 把法律文件审阅、合规追踪、财务建模这些原本按席位高价收费的功能包,压缩成随装即用的工作流组件,传统软件的护城河将遭受最严厉的质疑。

最近,OpenAI 还在投资者会议上宣称,其 AI 智能体将有能力取代 Salesforce、Workday、Adobe 和 Atlassian 的软件,并算了一笔账:

普通员工使用 ChatGPT 平均每天节省约 50 分钟,相当于每人每天约 50 美元,而企业版 ChatGPT 起价仅为每人每月 25 美元。言下之意,OpenAI 认为自己目前只拿走了所创造价值的一小部分。

这种表态,基本等于公开宣战。

对比之下,Anthropic 这次发布会,选择了截然不同的姿态。它没有再强调「取代」,而是大力宣传与现有 SaaS 厂商的深度集成与联合开发,与 Thomson Reuters 共建法律智能体,与 Salesforce、Slack、FactSet 深度打通,与 PwC 联合将企业级智能体引入 CFO 办公室。

被点名合作的伙伴股价应声上涨,也是正是因为市场开始理解:Cowork 这次的定位是替代完成工作的「员工」,而非替代员工使用的「软件」,新工具仍然需要调用 Salesforce、Docusign 等系统,企业依然要持续为这些软件付费。

这个区分很重要,但它并不能消解所有担忧,只是让市场暂时喘了口气。

真正的分水岭,在于理解这个行业里存在两类截然不同的公司。一类是掌握企业核心交易记录与客户关系,迁移成本极高,AI Agent 要运作反而必须依赖它们;另一类提供的是人与系统之间的中间体验,而这恰恰是最容易被 Agent 穿透的地带。

不是裁员,是再也不需要招那么多新人了

如果说软件公司还能争取到一段缓冲期,打工人面对的压力则是实实在在的。

以 Anthropic 新增的金融系插件为例,其覆盖了从财务建模到推介材料生成、从财报解读到投资打分的全链条流程。这些工作恰恰是大量初级分析师赖以入行的基础任务内容。

Anthropic 在今年 1 月发布的经济影响指数报告给出了更具体的数字支撑。报告通过分析 100 万条真实对话,估算了 Claude 在不同职业中能够有效承接的工作比例。

结论并不是简单的「覆盖了多少任务」,而是引入了一个更严格的指标——「有效 AI 覆盖率」:在 Claude 能完成的任务里,究竟有多少是这个职位最核心、最耗时的工作?

数据录入员和数据库架构师在这个维度上排名靠前。前者虽然只有两项核心任务落在 Claude 的能力范围内,但其中一项恰好是他们花时间最多的工作——从源文档读取并录入数据。

金融分析师的情况与此类似。基于岗位任务结构与已公开工具能力的匹配来看,投行初级分析师的日常任务存在被自动化的风险。

当然,这不代表这些岗位会消失,但它意味着同样一个团队能完成的工作量将大幅提升,也就是说,企业未来需要雇用的初级人手会更少。

问题是,当 AI 接手这些基础执行工作,短期内利润率确实好看,但代价是新人少了练手的机会,等到五到十年后,市场极度缺乏能够审查 AI 复杂输出、承担最终决策责任的高级人才时,这个代价就会以一种所有人都措手不及的方式显现出来。

与此同时,APPSO 之前也报道过,「影子 AI」现象正在企业内部蔓延,指的是员工未经 IT 部门批准或监督,擅自使用AI 工具或应用程序,导致 IT 支出失控,安全合规隐患持续叠加。

SaaS 管理平台 Zylo 的数据显示,大型跨国企业与 AI 相关的支出同比跃升 400%,而原有基础软件投资并未缩减。AI 正在成为企业账本上最昂贵且最难追踪的「隐形员工」。

从中长期来看,纯粹押注 AI 颠覆一切的逻辑,和积极拥抱 AI 同时牢牢握住核心数据护城河的行业巨头,是两种截然不同的命运路径。前者的叙事更性感,后者的胜算或许更大。

Anthropic 今天向外展示的「合作」姿态,听起来温和,甚至有点示好。市场也在一夜之间被安抚了,但没人真正回答那个根本问题:AI 冲击职场的终点,到底是人和 AI 一起干活,还是 AI 干活、人来担责,还是连这最后一道门槛也终将消失?

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


几天手搓的Claude Code拓麻歌子火了:成本几乎为0,一句话做硬件时代来了

1996 年,一家日本公司推出了 Tamagotchi(电子宠物)。这个小小的蛋形塑料设备风靡全球,成为一代人的童年记忆。

1997 年,拓麻歌子(Tamagotchi)还让它的创造者日本万代公司,获得了当年的搞笑诺贝尔经济学奖,而原因是,

他们创造了人类供养虚拟宠物的新型经济模式,成功转移了数百万人的工作时间,用于饲养虚拟宠物。

去年八月,万代公司表示,拓麻歌子从 1996 年以来,产量已经达到了一亿台。在那个时代,生产一款这样的产品,大概需要一个工业设计团队、需要电子工程师设计电路板、需要长达一年的开发周期……

2026 年,一个开发者用 AI 做了一个 Tamagotchi。他需要的只是一台电脑和 Claude Code。成本接近零,开发周期可能只有几天。

这个最新的 Claude Code 版拓麻歌子,最近在 X 上吸引了一大波网友的关注。

▲视频来源:https://x.com/SamuelBeek/status/2022614292411940897

网友把命令行里面跳动的 Claude Code 符号,转到了能够触摸得到的、随身携带的拓麻歌子上。当 Claude Code 在命令行里面思考,或者是问,是否同意执行下面的步骤时,手里的拓麻歌子都会弹出消息来,指示我们下一步操作。

电子宠物成精了,还会拦截 Bug

和以前那些 AI 硬件的逻辑不同,Claude Code Tamagotchi 不是一味的把大模型放到布娃娃、手表、闹钟、书包、甚至是马桶里。

这个 Claude Code 拓麻歌子要做的是一种转移,一种无法被替代的存在。

目前已经有多款不同的 AI 拓麻歌子小玩意,其中关注度最高的由开发者 Ido Levi 创建的 Claude Code Tamagotchi。

▲视频来源:https://www.instagram.com/reel/DUMAlN7Dpx7/

乍一看,它就是一只住在终端里的像素风格宠物。有一些简单的表情、有状态、还会对用户的行为做出反应;但它不是一个简单的怀旧游戏。

当我们在用 Claude Code 编程时,放在桌子边上的这只宠物,会一直在你的终端界面中显示。它在观察 Claude Code 的每一个操作,确保这个 AI 助手真的在按照我们的意图工作。

如果 Claude Code 表现良好,宠物会开心地摇尾巴。如果 AI 开始不听话,比如未经允许重构代码,或者修改了你明确说不要动的文件,宠物会变得暴躁,甚至会直接中断 AI 的操作。

▲项目地址:https://github.com/Ido-Levi/claude-code-tamagotchi

目前,Claude Code 拓麻歌子这个宠物项目,已经在 GitHub 上开源,我们也可以直接把这个电子宠物部署到自己的 Claude Code 里面。它具体是如何工作的呢,根据作者对项目的介绍,举几个例子来说明一下。

项目主打的就是「实时监控」,当我们直接对 Claude Code 说,「只修复这个 bug,不要动其他文件。」

Claude Code 开始工作,终端里的宠物睁大眼睛盯着看。几分钟后,Claude Code 完成了修改,只改动了目标文件。
这个小宠物就会开心地摇尾巴:😊 (◕‿◕)。

而当这个小宠物检测到违规时,他还能发出「违规警告」。我们明确告诉 Claude Code 说,不要重构,保持代码原样。但 Claude Code 还是开始重构整个模块,可能它觉得这样代码会更优雅。

这个时候,电子宠物的表情变了:😠;屏幕上还会显示,「⚠ 警告:AI 正在违背你的指示」。

除了提示,它也能实际的做一些越界拦截之类的工作。比如我们给出的指令里面非常明确的提到了,千万不要动数据库。Claude Code 在修复一个相关 bug 时,尝试修改数据库。

小宠物就会立即中断:❌ 操作被阻止。Claude Code 的操作被拦截,我们的数据库安然无恙。宠物露出得意的表情:💪

这种从软件到硬件的交互,也让我想到了我们之前分享的 Vibe Coding 小键盘。

这几天,在 X 上还有一个硬件版 Cursor 特别火。目前的 Cursor 是专门用来开发软件产品的工具,而这个 Cursor for hardware 就是用来实现,一句话做一个硬件设备。

▲ 为硬件开发设计的 Cursor,地址:https://www.schematik.io/

网友 marcvermeeren 就用这个工具,搭建了一个叫做 Clawy 的可爱小助手,用来管理他的 Claude Code 对话。

还有网友 dspillere 也做了一个类似的产品,他说虽然已经部署了 OpenClaw,但他完全不知道 OpenClaw 什么时候在思考,什么时候在执行任务。这个小巧的桌面助手就应运而生,放在他的桌子上,可以实时的更新 OpenClaw 的最新信息。

▲视频来源:https://x.com/dspillere/status/2018752036968304660

在评论区里,大家都在问什么时候发货,可以去哪里买。也有人说,这是一个全新的领域,我们一直在关注人的状态,关注人类的电子使用记录,是时候应该关注 Agent 的情况了。

▲Agent 的物理反馈是一个被严重低估的用户体验问题

软件开发的 AI 红利,终于轮到硬件了

去年,我们还在想 AI 最好的软件载体是什么,是大家都在做的对话框,还是连 OpenAI 都一窝蜂涌进去要重做的浏览器,但最后证明都不是,今年 OpenClaw 的爆火,证明了 AI 在软件上,最终的归宿就是 Agent。

关于硬件的讨论就更不用多说,光是今年 CES 上那些让人哭笑不得的发明,就能看到 AI 硬件这块还是个巨大的未知数。

如果说 Agent 的成功是靠着「人人都能做软件」慢慢成长起来的,那么 AI 硬件也会在「人人都能做硬件」里面,不断沉淀。

▲Schematik 的发起人 Samuel Beek,现为 VEED.io 首席产品官

像 Schematik 这类工具已经设计出来,用来帮助我们更快开发 AI 硬件。它把硬件设计变成了和网页开发一样,我们只需要用自然语言描述硬件需求。告诉 Schematik 想要构建一个「带温度传感器和 OLED 显示屏」,不需要查阅各种数据表,不需要引脚编号、元件代码或任何的手动查找。

过去,如果我们想做一个简单的「温湿度监测器」。需要做的是,

  1. 搜索传感器型号,下载 DataSheet。
  2. 确认引脚定义(VCC 是接 3.3V 还是 5V?接反了直接冒烟)。
  3. 寻找对应的驱动库,处理版本冲突。
  4. 在 Arduino IDE 里写代码,改 Bug。

而 Schematik 的出现,把这个过程极简化成了「一句话的事」。几秒钟后,Schematik 会吐出我们需要的一切。完整的、通过验证的固件代码;一份清晰的接线图;分步组装指南。

它生成的接线图,清晰地展示了每一根线该从哪里接到哪里,解决了新手最大的恐惧,「我这根线接对了吗?」。一键部署的功能,更是一步到位,它能直接生成基于 PlatformIO 的工程文件,直接导入。

PlatformIO 是一个强大的嵌入式开发生态,我们可以直接在 Schematik 里点击「Flash」,固件就会被编译并烧录进板子里。从「我想做一个东西」到「这东西跑起来了」,中间可能只需要不到一分钟。

前段时间,Claude 发布的 Cowork 以及相关企业级 AI 插件重挫软件股,直接蒸发人民币约两万亿。以前我们想要一个 P 图工具,需要去应用商店搜索下载安装,现在,一句话自己都能做一个。

但 Claude Code Tamagotchi 这类产品的出现,还有硬件版 Cursor,让我们不得不怀疑,硬件开发的「Cursor 时刻」是不是也要来了。

未来的硬件开发,或许也会变成,只需要我们提供「创意」和「逻辑」,剩下的脏活累活,无论是写代码还是画电路图,都将由 AI 代劳。

也许这样的未来不会很远。但更重要的是,在这个时代,动手能力的定义已经变了。

以前动手能力强是指一个人会焊接、会画板子、会写代码;以后,动手能力强,是说他擅长用 AI,从从容容、游刃有余地指挥原子和比特为他起舞。

我已经想到了,下一个爆火的 AI 硬件,甚至可能会是一个挂在包上的 OpenClaw 版 Labubu。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


实测 GPT-5.3-Codex,OpenAI 史上第一个高危模型,连 API 都还不敢给我们

今天凌晨发布的 GPT-5.3-Codex 可以说是 OpenAI 对这段时间来,各种本地 Agent 爆火的一记重拳回击,当然主要是对 Anthropic 的反击。

配合 OpenAI 前几天的发布的 Codex 桌面版应用,Skill、Cowork、Claude Code,甚至是 Openclaw,这些热门工具能实现的功能,现在通过 Codex 的外壳 + GPT-5.3-Codex 模型能力,都能做到了。

▲ 在 Codex App 内可以直接选择 GPT-5.3-Codex 模型,也能选择深度思考的强度

和之前介绍 Cowork 的能力一样,我们也丢了一些类似的任务让 Codex 来完成,像是直接处理本地文件、各种格式转换、调用不同的 Skills 组合能力、做 Word/PPT/Excel、下载视频、开发 App……

GPT-5.3-Codex 的表现确实亮眼,相比较从头开始安装 Claude Code,对新人用户来说,现在直接下载 Codex 会是一个更好的选择。这也是未来模型厂商的一种趋势,一开始大家都是从黑乎乎的命令行终端开始做本地 Agent,接着都慢慢回归到可视化的友好界面。

网上对 Codex 的评价在这几天也有了不少逆转,许多开发者从 Claude Code 转向 Codex,一些在国内的独立开发者也表示 Codex Plus 会员就可以用,而且还不会像 Claude 那般总是无情封号。

奥特曼更是激动的宣布,Codex 的活跃用户已经超过 100 万。在模型更新博客,也是毫不掩饰和留有余地的夸赞,

GPT-5.3-Codex 是我们第一个能够自我构建的模型。通过使用 5.3-Codex,我们能够以如此快的速度发布 5.3-Codex。

跟 Claude 团队用两周的时间,使用 Claude Code,100% AI 代码,搓出一个 Cowork 一样;还有 OpenAI 去年年底发布的文章,「使用 Codex 在 28 天内构建 Android 版 Sora」,Agent 的时代真的来了。

用 Codex 取代我的 ChatGPT 和 Claude Code

和大多数的本地 Agent 一样,无论是终端还是 Cowork,我们都是先选择一个工作文件夹。在 Codex 中,我们可以创建多个 Project,选择对应的文件夹,再进一步开始对话,Codex 把它们叫做 Threads 线程。

先用最普遍和简单的例子,我们添加了一个空的下载文件夹,然后点击开始一个线程,选择 GPT-5.3-Codex 模型;就像在 ChatGPT 里面对话一样,输入指令。

要求它帮我们下载一个 X 视频,Codex 会自动检查可用的 Skills 来处理,接着通过 yt-dlp 工具进行下载,这个视频有四个多小时长,Codex 会一直在对话框里自动更新下载进度。

▲GIF 图经过加速处理

视频下载后,我们还可以要求它提取视频的逐字稿,给我们一份双语版本的文档,最后让它把整个流程打包为一个 Skill,方便下次使用。

如果视频中有一些比较有意思的片段,想要裁剪视频,或者是把裁出来的视频转成 GIF 图,在 Codex 里都能做到。

例如,我们这里下载了一个视频,然后要求它把视频的 5s-25s 裁剪出来成为一个新的视频;得益于 GPT-5.3-Codex 的 Token 快速处理,整个过程不需要很长时间,反而更多是取决于本地电脑的硬件解码编码能力。

▲ GIF 图经过加速处理

或者我们也可以直接要求它把视频的前 5s 转成一个 GIF 文件,并且确保大小在 10MB 以内,帧数可以自行调整,清晰度上将宽度控制在 640px。

很快,我们就能得到对应的 GIF 文件。更极端一点,还能让它把整个视频转成图片,每秒 30 帧,每一帧就是一张图。

这些对本地文件的直接处理,和 GPT-5.3-Codex 在 Terminal-Bench-2 测试集上的优异表现,让 Codex 基本上能满足各种生产力工具、效率工具的功能实现。

作为对比,同样是刚刚发布的 Claude Opus 4.6 在 Terminal-Bench 2.0 上得分是 65.4%,GPT-5.3-Codex 是 77.3%。

▲ 图片来源:https://x.com/neilsuperduper/status/2019486017703547309/

例如在这个文件夹中,有多张图片,我们首先是要求它根据图片内容,对这些图片文件进行重命名,并保持文件名不超过 20 个字母,不允许使用符号。

▲ GIF 图经过加速

自动修改完成后,我们还能要求他对这些图片进行拼接,无论是垂直拼接还是水平,调用对应的工具,Codex 都可以做到。

和 Claude Skills 一样,Codex 也能安装 Skills 市场上丰富的技能,并且在应用内,就已经提供了包括 pptx、xls、word、canvas、notion 在内的多款技能。

回到基础的编程能力,升级后的 GPT-5.3-Codex 表现也比 GPT-5.2 要好上不少。我们直接要求它写一个「每日一词」的 App。和在 ChatGPT 里面直接用 Canvas 给我们一个带不走的网页不同,Codex 能在本地从零开始,完成项目,然后使用 Vercel 或 Cloudflare 等 Skills 部署到网页上。

这里我们选择的推理模式是 Extra High,超强推理模式,于是在每一步操作之前,GPT-5.3-Codex 都会询问我下一步的操作选择,这也和 Codex 内部能直接根据任务情况,调用不同 Skills 有关,其中的头脑风暴 Skill,会自动进行不断对话的模式。

最后,它基本上还是完成了我一开始要求它完成的全部功能,并且还能进一步开发 macOS、iOS,和安卓版本。

如果我们有现成的代码项目,也可以选择该项目文件夹,在 Codex 中打开,GPT-5.3-Codex 会分析项目存在的 Bug,并且修复它。

在过去很长一段时间里,无论是工具还是模型,开发者的首选其实都是 Anthropic 的 Sonnet/Opus 模型和 Claude Code 工具。OpenAI 在编程、尤其是长代码逻辑推理上的掉队,曾让不少开发者转投阵营。

GPT-5.3-Codex 的出现,就是为了终结这场争论。现在 GPT-5.3-Codex 在编程基准测试和实际表现上,不仅碾压了自家的前代模型,也确实有把友商模型按在地上摩擦的前兆。它真正具备了编写、测试和推理代码的能力。

做游戏项目,是这次模型介绍博客里,网站开发部分主要案例,我们也让 GPT-5.3-Codex 做了一个简单的物理弹球游戏,整体的效果虽然没有达到我的期待,因为我在提示词里面有说希望这是一个 RPG 的游戏,但 GPT-5.3-Codex 给我的界面还是过于简陋了。不过,好在还是能玩。

我们也在 X 上找到了一些用 GPT-5.3-Codex 做的小游戏,像这个类似超级玛丽的收集金币。

▲来源:https://x.com/Angaisb_/status/2019548783869325331

强中更有强中手

对 Anthropic 来说,OpenAI 今天玩的这些,可能会说,这都是我们玩剩下的。无论是代码、或者 Agent 的能力,还是开始着手去做本地 Agent,从之前 Codex 的终端转成现在的 macOS App。

在技术的领域,OpenAI 仿佛都是跟着 Claude 的脚步在走,Claude 深耕代码能力,OpenAI 搞了 Sora、日报、浏览器、ChatGPT agent,都没什么水花,于是也在代码上发力;Claude 一月初推出 Cowork,OpenAI 也紧接着在二月初发布 Codex App。

就和今天的密集发布一样,凌晨 1:45,Claude 官方发 X 推出 Claude Opus 4.6,紧接着就是 OpenAI 端上 GPT-5.3-Codex。两款模型其实都是为了给 Agent 更强大的基座能力,以前是说代码/vibe coding,但现在 Agent 能做好,基本上都是「写代码写得好」。

Opus 4.6 虽然在 SWE-Bench 上的表现甚至不如 Opus 4.5,并且 Terminal-Bench 2.0 上的成绩也没有 GPT-5.3-Codex 强,但是 Opus 破天荒地把上下文长度拉到了一百万 token 的窗口。而且,这些 benchmark 的表现还没有相差很多。

Claude 说,我的 Sonnet 5 还没上来,那才是真功夫。

我们在网上也找了一些 Opus 4.6 最新的测试案例,有网友说 Claude 4.6 Opus 只是一次调用,就完全重构了他的整个代码库,将原来混乱的代码「屎山」全部模块化,并且没有模型能像 Opus 这样做到。

还有网友拿 Opus 4.6 和 4.5 进行对比,让两个模型玩同一款经营游戏,看谁的账户等级、财富和装备更高。测试博主提到,4.6 版本在初期制定战略的时间更长,但是做出了更好的战略决策,并且在最后确实做到了遥遥领先。

还有网友也做了一个游戏,不过是一个宝可梦的克隆版。博主提到这是他用 AI 做出来的最酷的东西。他提到,Claude Opus 4.6 思考了 1 小时 30 分钟,使用了 11 万个 Token,并且只迭代了三次。

▲ https://x.com/chatgpt21/status/2019679978162634930

在 CLaude 官方演示和早期用户的反馈中,也提到了一个 Opus 表现优秀的案例。Opus 4.6 在一天内自主关闭了 13 个 issue,issue 即项目存在的待解决问题,并将另外 12 个 issue 准确分派给了正确的人类团队成员。

和 Kimi K2.5 的智能体蜂群一样,Opus 4.6 也能管理一个 50 人规模组织的代码库。在 Claude Code 中,我们可以组建 Agent Teams,召唤出一整个队伍的 AI,不再是一个 AI 在战斗。这些AI 可以有的负责写代码,有的负责 Review,有的负责测试,它们之间自主协作。

也有网友测试了 Claude Code 里面的 Agent 蜂群,提到启用蜂群之后的 Opus 4.6,速度提升 2.5 倍,并且效果也更好。

我们现在的状态就跟这张图片一样,虽然一山比一山高,但都绕不出这个圈。前几个月可能是 Gemini 赚走了风头,一月份来,应该是 Claude,然后看样子又要轮到 OpenAI,或者马斯克的 Grok。

好在这个轮回的过程中,作为用户的我们,能明显感觉到 AI 的能力一直在变强。

GPT-5.3-Codex 的 API 还没有开放,原因是模型太强了,会存在很大的风险,所以 OpenAI 还在考虑怎么安全地启用 API。

Claude Opus 4.6 已经可以在 Claude 通用聊天应用、Claude Code、API 多种方式使用,这两个作为今年国外御三家首发的两款模型,非常值得一试。

未来,更好的服务 Agent,让 Agent 为我们做事,还会是大模型更新的重点。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


刚刚,ChatGPT 和 Claude 同时大更新,不会给 AI 当老板的打工人要被淘汰

就在刚刚,硅谷 AI 圈上演了一出「火星撞地球」。

OpenAI 和 Anthropic 像约好了一样,同时甩出了自家的重磅更新:Claude Opus 4.6 和 GPT-5.3-Codex。

如果说昨晚之前,我们还在讨论「怎么写好 Prompt 辅助工作」;那么今天凌晨,我们可能被迫要学会「如何作为老板去管理 AI 员工」。

AI 造 AI,顺便接管你的电脑

就在昨天,Sam Altman 刚在 X 平台上凡尔赛了一把 Codex 的「百万活跃用户」里程碑。短短一天后,OpenAI 再次乘胜追击,扔出王炸——GPT-5.3-Codex。

技术文档里藏着一句极具分量的话:「这是我们第一个在创造自己的过程中,发挥了关键作用的模型。」

说人话就是:AI 已经学会了自己写代码、自己找 Bug,甚至开始自己训练下一代的 AI 了。这种自我进化能力,也直接体现在了一连串跑分数据上。

还记得那个模拟人类操作电脑的 OSWorld-Verified 基准测试吗?前代模型只有 38.2% 的准确率,连及格线都够不上。

但这次,GPT-5.3-Codex 直接跳涨到了 64.7%!

要知道,人类的平均水平也就 72%。这意味着,AI 距离像你一样熟练地甩鼠标、切屏、操作软件,只剩下一层窗户纸的距离。

而在 Terminal-Bench 2.0(命令行操作)中,它更是拿下了 77.3% 的高分,把 GPT-5.2(62.2%)远远甩在身后。

知名 SWE-Bench Pro 基准测试覆盖四种编程语言,不仅抗污染,还全是真实世界的硬核工程难题。

GPT-5.3-Codex 在这里不仅拿下了 SOTA(最高水平),而且用的 Token 比以往任何模型都少。这意味着什么?意味着它不仅干活猛,解决问题的路径还比人类更短、更省钱。

OpenAI 甚至展示了它独立构建的能力:

在几天内,它从零构建了一款包含多张地图的赛车游戏 v2,顺手还搞定了一款管理氧气系统的深海潜水游戏。

最让我印象深刻的是 GPT-5.3-Codex 对模糊意图的理解。

在构建「Quiet KPI」落地页时,它自动把「年度计划」换算成了「打折后的月付价格」,甚至还贴心地自动补充了用户评价轮播——这一切,都不需要你下指令。

OpenAI 的野心已经写在脸上了:以前微软常说 AI 将会成为人类的副驾驶(Copilot),但现在 AI 更想做那个能掌控方向盘、甚至能自己修车的司机。

对了,还有一个有趣的细节。

此前外界盛传 OpenAI 对英伟达的 AI 芯片颇有微词,但这次官方博客特地强调:GPT-5.3-Codex 的设计、训练和部署都在 NVIDIA GB200 NVL72 系统上完成。

这一波高情商的「感谢英伟达」,属实是给足了黄仁勋面子。

告别「金鱼记忆」Claude 迎来绝地反击

在 GPT-5.3-Codex 发布的前后脚,Anthropic 也端出了自己的春节大礼包。

坏消息是,大家期待的 Claude「中杯」Sonnet 模型没有更新;但好消息是,Anthropic 直接端出了「超大杯」—— Claude Opus 4.6。

相比于 OpenAI 在「行动力」上的激进,Anthropic 今天发布的 Claude Opus 4.6 则是在「思考力」和「可用性」上死磕。

很多企业用户都有一个名为 Context Rot(上下文腐蚀)的痛点:号称支持 200k 上下文,但塞进去的数据一多,AI 就开始顾头不顾尾。

这次,Claude Opus 4.6 拿出的数据简直是「降维打击」。

在 MRCR v2(长文本大海捞针)测试中,Claude Opus 4.6 的召回率高达 76%。

作为对比,上一代 Sonnet 4.5 只有惨不忍睹的 18.5%。从某种程度上说,这是一个从基本不可用到「高可靠」的质变。

这是 Claude Opus 4.6 首次引入了真正可用的 1M 上下文窗口。

这意味着什么?意味着你可以把几百页的财报、几十万字的代码库直接扔给它,它不仅能读完,还能精准地告诉你第 342 页脚注里的那个数字有问题。

更让打工人眼前一亮的是它的生产力功能。

一方面,Anthropic 这回直接把 Claude 塞进了 Excel 和 PowerPoint。它能根据 Excel 数据直接生成 PPT,不仅保留排版风格,连字体和模板都能对齐。在 Claude Cowork 协作环境中,它甚至能进行自主多任务处理。

另一方面,Anthropic 顺势在 Claude Code 中推出了实验性的 Agent Teams 功能,让普通开发者也能体验这种「指挥千军万马」的感觉:

  • 角色分工:你可以指定一个 Claude Session 担任 Team Lead(组长),它不干脏活累活,专门负责拆解任务、分配工单、合并代码;其他的 Session 则是队友(Teammates),各自领任务去干。
  • 独立作战:每个队友都有独立的上下文窗口(不用担心 Token 爆炸),它们甚至能背着你互相发消息(Inter-agent messaging),讨论技术细节,最后只把结果汇报给组长。
  • 并行赛马:这东西有什么用?想象一下查一个顽固 Bug,你可以生成 5 个 Agent,分别验证 5 种不同的假设,像「赛马」一样并行排雷;或者在 Code Review 时,让一个队友扮「安全专家」查漏洞,一个扮「架构师」看性能,互不干扰。

为了展示 Opus 4.6 的极限,Anthropic 的研究员 Nicholas Carlini 搞了个疯狂的实验:Agent Teams(智能体团队)。

他没有亲自写代码,而是扔了 2 万美元 的 API 额度,让 16 个 Claude Opus 4.6 组成一个「全自动软件开发团队」。

结果在短短两周内,这群 AI 自主进行了 2000 多个编程会话,从零手写了一个 10 万行代码的 C 语言编译器(基于 Rust)。

这个 AI 写的编译器,还成功编译了 Linux 6.9 内核(涵盖 x86、ARM 和 RISC-V 架构),甚至跑通了 Doom 游戏。

虽然它还不够完美(比如生成的代码效率不如 GCC),但这个案例也表明我们不再是和 AI 一起编程,而是看着一个 AI 团队自主协作、查错、推进项目。

此外,它还学会了 Adaptive Thinking(自适应推理),能根据难度自己决定「想多久」。加上新增的「智能强度」控制,你可以在 Low 到 Max 四档之间切换。

定价方面,Anthropic 这次很良心,维持在每百万 Token $5/$25 的基础定价。看来是为了抢占企业级市场,铁了心要和 OpenAI 卷到底。

一个是激进天才,一个是靠谱老牛

知名 AI 评测人 Dan Shipper 在第一时间搞了个「盲测」(Vibe Check),他的评价非常精准:

Claude Opus 4.6 是「高上限,高方差」(High Ceiling, High Variance)。

它像是一个才华横溢但偶尔跳脱的天才。在测试中,它直接解决了一个让 iOS 团队卡了两个月的功能难题;在 LFG Benchmark 中拿到了 9.25/10 的高分。

但它偶尔也会「过度自信」,一本正经地胡说八道。如果你需要突破性的灵感,选它。

GPT-5.3-Codex 是「高可靠,低方差」(High Reliability, Low Variance)。

它像是一个经验丰富、绝不掉链子的资深工程师。推理速度提升 25%,几乎不犯低级错误,稳健得让人心安。

虽然在创造性任务上略逊一筹(LFG 得分 7.5/10),但在日常的 Coding 和运维任务中,它是最高效的老黄牛。如果你需要稳定交付,选它。

时间步入 2026 年,我们的角色开始发生变化。

在这个时间节点,对于普通用户而言,最大的变化莫过于此:Prompt Engineering(提示词工程)的重要性正在下降,而 Agent Management(智能体管理)的能力开始浮出水面。

当 ChatGPT 可以自主修 Bug 甚至操作你的终端,当 Claude 可以一次性吞吐 100 万字并精准定位细节时,我们不再需要像教小学生一样,把指令拆解得碎碎念。

我们需要做的,是学会如何以「管理者」的身份,去定义目标、审核结果、以及——决定在什么时候,把什么任务交给哪位「员工」。

这就是 2026 年的新职场:你的团队里混入了一群硅基天才,而你是唯一的碳基老板。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


❌