Agent Modes — 编程 Agent 的交互模式设计 / Designing Interaction Modes for Coding Agents
📅 创建时间:2026-07-29 🏷️ 标签:#AgentModes #PlanMode #EditMode #ShellMode #ClaudeCode #Codex #AgentEngineering 📚 前置知识:[[01-function-calling]] [[05-agent-workflow]] [[21-tools-design]] [[24-harness-loop-skills]]
📋 本章目标
- 理解为什么 Agent 需要"模式"——从单一对话界面到多维操作状态的演进逻辑
- 掌握 Plan Mode 的设计原理:为什么"先想再做"能解决复杂任务的迷航问题
- 掌握 Edit Mode 的三种编辑策略(Full-file / String Replacement / AST)及其取舍
- 理解 Shell/Bash Mode 作为 Agent 终端的核心地位和四层安全设计
- 对比 Claude Code、Codex、Grok Build、Pi 四种 Agent 的模式实现差异
- 理解 Anthropic 2026 Plan 重设计方案中的 6 个新 Tool 及其架构意图
- 能够绘制模式组合的数据流图,理解真实任务中模式的动态切换路径
第0部分:为什么 Agent 需要"模式"
0.1 回顾——Agent Loop 的基本形态
在 [[01-function-calling]] 中,我们建立了 Agent Loop 的核心模型:
while True:
response = POST /v1/chat/completions (把 messages + tools 发过去)
if response.finish_reason == "stop":
return response.message.content ← 结束
if response.finish_reason == "tool_calls":
执行工具(response.tool_calls)
把执行结果追加到 messages 里
继续循环这个循环看起来简洁优雅,但它隐含了一个危险的假设:Agent 可以同时做所有事情。它能读文件(Read)、改代码(Edit)、执行命令(Bash)、安装依赖——所有工具平等地摆在面前,LLM 自己决定何时调用哪个。
┌─────────────────────────────────────────────────────────────┐
│ 无模式 Agent 的困境 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户:"给这个项目加一个用户认证系统" │
│ ↓ │
│ 第一轮:Read 了几个文件,直接在 package.json 里加了依赖 │
│ 第二轮:npm install 自动跑了(因为 LLM 觉得"需要") │
│ 第三轮:改了 3 个文件,但逻辑有 bug │
│ 第四轮:尝试修复 bug,又改了 2 个文件 │
│ 第五轮:发现最初的设计思路就是错的,全部推翻 │
│ ↓ │
│ 结果:浪费了 5 轮 token + 改了不该改的文件 + 装了不需要的包 │
│ │
│ 根本原因:LLM 在"探索"和"修改"之间没有边界 │
│ │
└─────────────────────────────────────────────────────────────┘0.2 "模式"的核心思想
"模式"把 Agent 的能力分解为不同的操作状态,每种状态有不同的工具可用、不同的安全约束、不同的用户交互方式。
用一个生活化的类比:
┌─────────────────────────────────────────────────────────────┐
│ 计算机系统的"模式"类比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 你的操作系统有: │
│ │
│ 👁 只读模式(浏览文件) │
│ → 可以看任何东西,但不能改 │
│ → 即使你双击了一个 .exe 也不会运行 │
│ │
│ ✏️ 编辑模式(修改文件) │
│ → 可以创建、修改、删除文件 │
│ → 但需要明确的"保存"动作才算数 │
│ │
│ 🔧 管理员模式(安装软件) │
│ → 可以做任何事,但需要 UAC 弹窗确认 │
│ → 每次敏感操作都需要人类批准 │
│ │
│ 没有人会设计一个操作系统让所有程序都以管理员权限运行。 │
│ 但早期的 Agent 设计恰恰就是这样——所有工具平等可用。 │
│ │
└─────────────────────────────────────────────────────────────┘0.3 三种核心模式的诞生
随着 Coding Agent 在实践中大规模部署,三种模式自然地浮现出来:
┌─────────────────────────────────────────────────────────────┐
│ Coding Agent 的三种核心模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔍 Plan Mode(规划模式) │ │
│ │ · 工具:只读(Read, Grep, Glob) │ │
│ │ · 输出:结构化计划文档 │ │
│ │ · 安全:零修改风险 │ │
│ │ · 时机:复杂任务的第一步 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ✏️ Edit Mode(编辑模式) │ │
│ │ · 工具:读写(Read, Write, Edit) │ │
│ │ · 输出:修改后的文件 │ │
│ │ · 安全:每次修改可审查 / 可撤销 │ │
│ │ · 时机:按计划逐文件修改 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 💻 Shell Mode(终端模式) │ │
│ │ · 工具:Bash / Shell 执行 │ │
│ │ · 输出:命令执行结果 │ │
│ │ · 安全:Permission 系统 + Sandbox + Hooks │ │
│ │ · 时机:编译、测试、安装依赖、部署 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘这三个模式不是人为设计的分类,而是从真实使用场景的摩擦中自然生长出来的。下面逐个深挖每个模式的工程细节。
第1部分:Plan Mode——先想再做
1.1 核心问题:为什么"边想边做"会失败
考虑一个典型场景:给一个已有 50 个文件的 Express 项目加上 JWT 认证中间件。
┌─────────────────────────────────────────────────────────────┐
│ 无 Plan 模式的 Agent 执行路径(失败案例) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Read package.json → 发现已有 express,没有 jsonwebtoken
│ Step 2: npm install jsonwebtoken bcryptjs(改了依赖)
│ Step 3: Read server.js → 发现用的是 Koa 不是 Express(错了!)
│ Step 4: npm uninstall jsonwebtoken → npm install koa-jwt
│ Step 5: Read 3 个路由文件 → 发现项目已经有自定义 auth 中间件
│ Step 6: 在已有的 auth 中间件旁边又写了一个新的(冲突了)
│ Step 7: npm test → 7 个测试失败
│ Step 8: 尝试修复测试...进入打地鼠模式
│ ↓
│ 问题根源:Agent 在理解项目全貌之前就开始修改了
│ │
└─────────────────────────────────────────────────────────────┘Plan Mode 的解决方案很简单但有效:强制执行一个"只读探索→生成计划→人类审批→执行"的顺序。
┌─────────────────────────────────────────────────────────────┐
│ Plan Mode 的 Agent 执行路径(理想路径) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 阶段 1: 探索(只读) │ │
│ │ · Read 所有入口文件、package.json、目录结构 │ │
│ │ · Grep 搜索 "auth" "jwt" "token" 等关键词 │ │
│ │ · Glob 找到所有路由文件、中间件文件 │ │
│ │ → 发现:Koa 框架 + 已有自定义 auth + 3 个路由模块 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 阶段 2: 制定计划 │ │
│ │ · 条目 1: 安装 koa-jwt + bcryptjs │ │
│ │ · 条目 2: 重构现有 auth 中间件,集成 JWT 验证 │ │
│ │ · 条目 3: 添加 /login 路由(生成 token) │ │
│ │ · 条目 4: 修改 3 个受保护路由,使用新中间件 │ │
│ │ · 条目 5: 更新测试,覆盖 JWT 场景 │ │
│ │ → 用户审批:同意 / 修改 / 拒绝 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 阶段 3: 执行 │ │
│ │ · 按计划逐条执行,每步完成后验证 │ │
│ │ · 如果中途发现计划需要调整 → 回到 Plan Mode 更新计划 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.2 各 Agent 的 Plan 实现对比
不同 Agent 对 Plan Mode 的实现方式反映了不同的设计哲学:
┌─────────────────────────────────────────────────────────────┐
│ 四种 Agent 的 Plan Mode 实现对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Claude Code(工具驱动) │ │
│ │ · 触发:EnterPlanMode tool 或 /plan 命令 │ │
│ │ · 状态:permission 切换为只读,保存 prePlanMode │ │
│ │ · 工具集:Read / Grep / Glob / AskUserQuestion │ │
│ │ · 输出:Markdown 计划文件 → .claude/plans/ │ │
│ │ · 退出:ExitPlanMode → 恢复原 permission │ │
│ │ · 特点:工具即状态——进入/退出 Plan Mode 本身就是 │ │
│ │ tool call,不需要特殊的协议层 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Codex CLI(命令行驱动) │ │
│ │ · 触发:--plan flag 或 Shift+Tab 快捷键 │ │
│ │ · 分析阶段:至少一次非破坏性探索后才向用户提问 │ │
│ │ · TL;DR checkpoint:先给 3-5 条摘要,确认后展开 │ │
│ │ · 计划存储:.codex/plans/ 目录 │ │
│ │ · 特点:强制"先看再说"——在没有读取任何文件之前 │ │
│ │ 不可以向用户提需要信息的问题 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Grok Build(多 Agent 并行) │ │
│ │ · 触发:Plan → Search → Build 三阶段自动流转 │ │
│ │ · Plan 阶段:8 个并行 agent 各自探索不同维度 │ │
│ │ · 每个 agent:独立制定自己领域的计划 │ │
│ │ · 特点:不是"一个 Agent 做计划",而是"8 个 Agent │ │
│ │ 从不同角度同时做计划然后汇总" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Pi(文件约定驱动) │ │
│ │ · 触发:无内置 Plan Mode │ │
│ │ · 替代方案:PLAN.md 文件约定 │ │
│ │ · 用户创建 PLAN.md → Pi 读取 → 按计划执行 │ │
│ │ · 特点:极简哲学——不需要特殊的 Plan Mode 工具, │ │
│ │ 一个 .md 文件就是计划 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.3 Claude Code Plan Mode 深度剖析
Claude Code 的 Plan Mode 实现是"工具即状态"哲学的典型代表。它不依赖特殊的协议或 API,而是把"进入只读状态"本身实现为一个 tool。
触发与状态切换流程:
┌─────────────────────────────────────────────────────────────┐
│ Claude Code Plan Mode 状态流转 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户输入 /plan 或 LLM 调用 EnterPlanMode tool │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ EnterPlanMode 执行: │ │
│ │ 1. 保存当前 permission 状态到 prePlanMode │ │
│ │ 2. 切换 permission 为只读模式 │ │
│ │ · deny: Edit, Write, Bash(所有写操作) │ │
│ │ · allow: Read, Grep, Glob, AskUserQuestion │ │
│ │ 3. 向 LLM 返回:"你现在处于 Plan Mode" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Plan Mode Loop(只读工具循环) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ LLM: Read → Grep → Glob → Read → AskUser │ │ │
│ │ │ (探索项目结构,向用户确认需求) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ 任何 Edit/Write/Bash 的 tool call 都会被拒绝 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 调用 ExitPlanMode: │ │
│ │ 1. 生成 Markdown 计划文件 │ │
│ │ 2. 保存到 .claude/plans/<plan-name>.md │ │
│ │ 3. 恢复 permission 为 prePlanMode 中的状态 │ │
│ │ 4. 向 LLM 返回:"已退出 Plan Mode,可以执行了" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 正常 Agent Loop(所有工具可用,按计划执行) │
│ │
└─────────────────────────────────────────────────────────────┘Plan Mode 中的工具白名单:
// Claude Code Plan Mode 中的可用工具
const PLAN_MODE_TOOLS = [
"Read", // 读取文件
"Grep", // 文本搜索
"Glob", // 文件名匹配
"AskUserQuestion", // 向用户提问(确认需求、澄清歧义)
"EnterPlanMode", // 已在 Plan Mode 中,但可以在子计划中递归
"ExitPlanMode", // 退出 Plan Mode,提交计划
];
// 关键:所有写入工具(Edit, Write, Bash, NotebookEdit)被 deny计划文件的结构:
Claude Code 输出的计划文件是一个标准的 Markdown 文件,通常包含:
# Plan: 给项目添加 JWT 认证系统
## 探索发现
- 项目使用 Koa 框架(非 Express)
- 已有自定义 auth 中间件在 `src/middleware/auth.js`
- 3 个路由模块:`routes/users.js`, `routes/posts.js`, `routes/admin.js`
- 依赖管理:npm,无 lock file 冲突
## 执行计划
### Step 1: 安装依赖
- `npm install koa-jwt bcryptjs`
- 验证:`npm ls koa-jwt`
### Step 2: 重构认证中间件
- 修改 `src/middleware/auth.js`
- 保留现有 token 解析逻辑
- 集成 `koa-jwt` 的 `verify()` 方法
- 添加 token 过期处理
### Step 3: 添加登录路由
- 新建 `src/routes/auth.js`
- POST `/login`:验证凭据 → 生成 JWT
- POST `/refresh`:刷新过期 token
### Step 4: 保护现有路由
- `routes/users.js`:添加 JWT 中间件
- `routes/posts.js`:添加 JWT 中间件
- `routes/admin.js`:添加 JWT + role check
### Step 5: 更新测试
- 更新 `tests/auth.test.js`
- 添加 token 过期场景测试
- 添加未授权访问场景测试
## 风险点
- 现有 token 可能与 JWT 格式冲突
- 前端需要同步更新 token 存储方式1.4 Codex Plan Mode 的 TL;DR Checkpoint 设计
Codex CLI 的 Plan Mode 有一个独特的设计决策:TL;DR checkpoint。
┌─────────────────────────────────────────────────────────────┐
│ Codex Plan Mode 的 TL;DR Checkpoint │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户:codex --plan "add JWT auth to this project" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 分析阶段(只读) │ │
│ │ · 探索项目结构 │ │
│ │ · 发现关键文件和模式 │ │
│ │ · 至少一次非破坏性探索 │ │
│ │ → 不展示完整计划,只展示 TL;DR │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TL;DR Checkpoint: │ │
│ │ │ │
│ │ 📋 计划摘要: │ │
│ │ 1. 安装 koa-jwt + bcryptjs │ │
│ │ 2. 重构 src/middleware/auth.js(集成 JWT 验证) │ │
│ │ 3. 新建 src/routes/auth.js(登录 + 刷新) │ │
│ │ 4. 修改 3 个路由文件(添加 JWT 中间件) │ │
│ │ 5. 更新测试覆盖 │ │
│ │ │ │
│ │ ⚠️ 风险:现有 auth 中间件可能与 JWT 格式冲突 │ │
│ │ │ │
│ │ [确认执行] [查看详情] [修改计划] [取消] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (用户点击"查看详情") │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 展开完整计划(包含每个步骤的具体代码变更预览) │ │
│ │ · 每个文件的具体修改位置 │ │
│ │ · 新增代码和删除代码的 diff │ │
│ │ · 每个步骤的预计 token 消耗 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 设计原理: │
│ · 大多数用户不想看完整计划,3-5 条摘要足够决策 │
│ · 需要深入了解的用户可以展开详情 │
│ · 避免了在用户不关心时浪费 token 输出冗长计划 │
│ │
└─────────────────────────────────────────────────────────────┘1.5 为什么 Plan Mode 会失败——真实评测数据
Plan Mode 的概念很好,但在实践中面临严峻挑战。Codex 团队在真实代码库上的评测数据揭示了核心瓶颈:
┌─────────────────────────────────────────────────────────────┐
│ Plan Mode 的实战评分与瓶颈分析 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 评测环境:真实开源代码库(非合成 benchmark) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景 A:Cold Start(无预建知识) │ │
│ │ · Codex Plan Mode 评分:41% │ │
│ │ · 主要失败原因: │ │
│ │ 1. Cold-start retrieval:每次都要重新发现项目结构 │ │
│ │ 2. 无运维记忆:不知道历史故障点和常见陷阱 │ │
│ │ 3. Context rot:上下文压缩后关键约束丢失 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景 B:预构建知识图谱(Pre-built Knowledge Graph) │ │
│ │ · Codex Plan Mode 评分:87% ⬆ │ │
│ │ · 提升来源: │ │
│ │ 1. 知识图谱提供了即时的项目结构理解 │ │
│ │ 2. 历史运维记录标注了高风险区域 │ │
│ │ 3. 预计算的依赖关系图减少了探索轮次 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论:Plan Mode 的核心瓶颈不是 LLM 的推理能力, │
│ 而是"信息获取效率"。Plan 的质量取决于 Agent │
│ 能在多短时间内建立对代码库的准确心智模型。 │
│ │
│ 这直接催生了 Anthropic 2026 的 Plan 重设计方案。 │
│ │
└─────────────────────────────────────────────────────────────┘1.6 Anthropic 2026 Plan 重设计方案:6 个新 Tool
基于实战教训,Anthropic 在 2026 年提出了 Plan 的重新设计方案——不再把 Plan Mode 当作"进入一个特殊状态",而是把计划本身变成一等公民,用 6 个专用 Tool 管理其完整生命周期:
┌─────────────────────────────────────────────────────────────┐
│ Anthropic 2026 Plan 重设计:计划作为一等公民 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanCreate │ │
│ │ · 创建新计划 │ │
│ │ · 参数:title, description, steps[], metadata │ │
│ │ · 返回:plan_id, plan 对象 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanList │ │
│ │ · 列出所有计划(支持按状态过滤) │ │
│ │ · 参数:status (active/archived/all), limit │ │
│ │ · 返回:plan 摘要列表 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanUpdate │ │
│ │ · 更新计划的步骤、状态或元数据 │ │
│ │ · 参数:plan_id, steps[], status, metadata │ │
│ │ · 特点:支持增量更新,不需要重新生成完整计划 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanExecute │ │
│ │ · 执行计划中的指定步骤 │ │
│ │ · 参数:plan_id, step_ids[], mode (sequential/parallel)│
│ │ · 返回:步骤执行结果 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanStatus │ │
│ │ · 查询计划执行状态 │ │
│ │ · 参数:plan_id │ │
│ │ · 返回:完成百分比、当前步骤、阻塞项 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PlanArchive │ │
│ │ · 归档已完成或已放弃的计划 │ │
│ │ · 参数:plan_id, reason │ │
│ │ · 特点:保留计划历史用于未来参考(解决"无运维记忆") │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 设计转变: │
│ · 旧方案:EnterPlanMode(进入状态)→ ExitPlanMode(退出) │
│ · 新方案:PlanCreate(创建实体)→ PlanExecute(操作实体) │
│ · 本质:从"模式切换"转向"对象操作" │
│ · 优势:计划可持久化、可查询、可增量更新、可归档复用 │
│ │
└─────────────────────────────────────────────────────────────┘新旧方案对比:
┌─────────────────────────────────────────────────────────────┐
│ Plan Mode 新旧设计对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 维度 │ 旧方案(Plan Mode) │ 新方案(Plan Tools) │
│ ─────────────┼─────────────────────┼───────────────────── │
│ 抽象层级 │ 操作状态 │ 操作对象 │
│ 计划持久化 │ 文件系统(.md) │ 结构化存储 + API │
│ 增量更新 │ 需要人工判断 │ PlanUpdate 原生支持 │
│ 历史复用 │ 无 │ PlanArchive 归档复用 │
│ 多计划并行 │ 不支持 │ 支持(plan_id 隔离) │
│ 中断恢复 │ 依赖文件内容解析 │ PlanStatus 状态查询 │
│ 与执行耦合 │ 退出即结束 │ 持续关联,可回溯 │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:Edit Mode——精准修改代码
2.1 核心问题:LLM 如何"修改"一个文件?
这是 Coding Agent 最核心的工程问题之一。当一个 LLM 需要修改 server.js 的第 47 行时,它该怎么做?三种策略各有取舍:
┌─────────────────────────────────────────────────────────────┐
│ 三种代码编辑策略对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 1: Full-file Rewrite(全文件重写) │ │
│ │ │ │
│ │ Tool: Write(file_path, content) │ │
│ │ │ │
│ │ 流程: │ │
│ │ Read 整个文件 → LLM 输出修改后的完整内容 │ │
│ │ → Write 覆盖原文件 │ │
│ │ │ │
│ │ ✅ 优点: │ │
│ │ · 不会出现格式错误(完整内容由 LLM 生成) │ │
│ │ · 实现简单(一个 Write tool 就够了) │ │
│ │ · 适合:小文件(< 200 行)、新建文件 │ │
│ │ │ │
│ │ ❌ 缺点: │ │
│ │ · Token 爆炸:1000 行文件改 1 行也要输出 1000 行 │ │
│ │ · 上下文浪费:大量 token 花在"不变的部分"上 │ │
│ │ · 大文件改不动:超过上下文窗口就无法处理 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 2: String Replacement(字符串替换) │ │
│ │ │ │
│ │ Tool: Edit(file_path, old_string, new_string) │ │
│ │ │ │
│ │ 流程: │ │
│ │ Read 相关区域 → LLM 输出 old_string + new_string │ │
│ │ → 工具在文件中精确匹配 old_string → 替换 │ │
│ │ │ │
│ │ ✅ 优点: │ │
│ │ · Token 极省:只输出变化的部分 │ │
│ │ · 大文件友好:10000 行文件改 3 行只需要几十 token │ │
│ │ · 精确控制:每一处修改都明确指定位置 │ │
│ │ │ │
│ │ ❌ 缺点: │ │
│ │ · old_string 必须精确匹配(空格、缩进、换行) │ │
│ │ · 匹配失败 → LLM 需要重新 Read 文件 → 再试 │ │
│ │ · 多文件协同修改时需要 LLM 记住每个文件的当前状态 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 3: AST/Semantic Edit(语法树编辑) │ │
│ │ │ │
│ │ Tool: ASTEdit(file, operation, target, content) │ │
│ │ │ │
│ │ 流程: │ │
│ │ 解析文件 AST → LLM 指定操作 + 目标节点 │ │
│ │ → 工具在 AST 上执行操作 → 重新序列化 │ │
│ │ │ │
│ │ ✅ 优点: │ │
│ │ · 语义精确:不受空格/格式化影响 │ │
│ │ · 安全:不可能无意中破坏代码结构 │ │
│ │ │ │
│ │ ❌ 缺点: │ │
│ │ · 语言依赖:每种语言需要不同的 AST 解析器 │ │
│ │ · LLM 不够理解 AST:目前 LLM 更擅长操作文本 │ │
│ │ · 序列化损失:AST → 文本可能导致格式变化 │ │
│ │ · 目前状态:没有 Agent 大规模使用此策略 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 Claude Code 的 Edit 工具详解
Claude Code 的 Edit 工具(内部名称 FileEditTool)是 String Replacement 策略的典型实现,背后有一个关键的设计哲学。
Edit 工具的工作流程:
┌─────────────────────────────────────────────────────────────┐
│ Claude Code Edit 工具的执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ LLM 调用 Edit(file_path, old_string, new_string) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 读取目标文件 │ │
│ │ · 确保文件存在且可写 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 精确匹配 old_string │ │
│ │ · 严格字符串匹配(包括空格、缩进、换行) │ │
│ │ · 如果 old_string 不唯一 → 返回错误 │ │
│ │ · 如果 old_string 找不到 → 返回错误 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ├── 匹配成功 ──────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ ┌─────────────┐ │
│ │ 3. 执行替换 │ │ 3. 返回错误 │ │
│ │ · 计算替换后的文件内容 │ │ · 精确 │ │
│ │ · 写入文件 │ │ 描述 │ │
│ │ · 返回成功 │ │ 失败 │ │
│ │ │ │ 原因 │ │
│ └─────────────────────────────────────┘ │ · LLM 需 │ │
│ │ │ 要重新 │ │
│ ▼ │ Read 文 │ │
│ ┌─────────────────────────────────────┐ │ 件获取 │ │
│ │ 4. 文件内容变更通知 │ │ 最新内 │ │
│ │ · 触发 file watcher(如果开启) │ │ 容后再 │ │
│ │ · 更新 Agent 的文件状态缓存 │ │ 试 │ │
│ └─────────────────────────────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Edit 工具的匹配失败——最常见的问题:
# 典型的匹配失败场景
# 文件中有(注意行尾空格):
# def get_user(id):
# return db.query(User).filter(User.id == id).first()
# LLM 输出了 old_string(没有行尾空格):
old_string = """def get_user(id):
return db.query(User).filter(User.id == id).first()"""
# 匹配失败!因为原文件第一行末尾有两个空格
# LLM 的 old_string 没有这两个空格 → 精确字符串匹配失败这就是为什么 Claude Code 的设计中,Edit 的失败处理如此重要——它不是 bug,而是一个设计特性:让 LLM 自己处理匹配失败,迫使它重新读取文件以获取准确的最新状态。这比"容错匹配"更好,因为容错匹配可能匹配到错误的位置。
设计原则:"Seeing like an agent":
来自 Anthropic 工程师的 blog 文章 "Seeing like an agent":设计工具时,要站在 Agent 的视角,而不是开发者的视角。开发者会想"让我做一个模糊匹配算法来解决 LLM 的输出不精确",但 Agent 需要的是"如果我的 old_string 不精确,请告诉我具体哪里不对,让我可以修正"。模糊匹配会掩盖 Agent 对文件状态的理解错误,导致更隐蔽的 bug。
2.3 Codex 的三种编辑模式——渐进式自动化
Codex CLI 将编辑模式进一步细分为三个自动化等级:
┌─────────────────────────────────────────────────────────────┐
│ Codex CLI 的三种编辑模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 1: Suggest(建议模式)——默认 │ │
│ │ │ │
│ │ 每次编辑都需要用户批准: │ │
│ │ │ │
│ │ Codex: 将修改 src/auth.js 第 15-23 行: │ │
│ │ - const jwt = require('jsonwebtoken') │ │
│ │ + import jwt from 'jsonwebtoken' │ │
│ │ - const secret = process.env.SECRET │ │
│ │ + const secret = process.env.JWT_SECRET │ │
│ │ │ │
│ │ [接受] [拒绝] [查看上下文] │ │
│ │ │ │
│ │ 适用场景:不熟悉项目 / 高风险修改 / 学习阶段 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 2: Auto Edit(自动编辑模式) │ │
│ │ │ │
│ │ 文件修改自动应用,不需逐次批准: │ │
│ │ │ │
│ │ 自动执行连续的 Edit 操作 │ │
│ │ → 但遇到风险操作时仍会暂停询问 │ │
│ │ → 风险操作:删除 > 50 行、修改核心配置、改动 > 5 文件│ │
│ │ │ │
│ │ 适用场景:熟悉的项目 / 低风险批量修改 / 效率优先 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 3: Full Auto(全自动模式) │ │
│ │ │ │
│ │ 文件 + Shell 全自动,只在任务完成时报告: │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 编辑 5 个文件 │ │ │
│ │ │ npm install 3 个包 │ │ │
│ │ │ npm test(全部通过) │ │ │
│ │ │ git commit │ │ │
│ │ │ → 完成。 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 适用场景:高度信任 / CI/CD 集成 / 自动修复 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 设计洞察: │
│ 自动化不是二元的(开/关),而是渐进式的(信任谱系)。 │
│ Codex 的三级模式允许用户在"安全"和"效率"之间滑动。 │
│ │
└─────────────────────────────────────────────────────────────┘2.4 Pi 的极端设计——只有 4 个工具
Pi 采取了一条截然不同的路径。它的 system prompt 约 1000 token,总共只有 4 个工具:
┌─────────────────────────────────────────────────────────────┐
│ Pi 的极简工具设计 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 工具清单(全部 4 个): │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ read(path) — 读取文件内容 │ │
│ │ write(path, content) — 写入文件(全量覆盖) │ │
│ │ edit(path, old, new) — 字符串替换 │ │
│ │ bash(command) — 执行 shell 命令 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 没有的东西: │
│ ❌ 无 MCP(Model Context Protocol) │
│ ❌ 无 sub-agent(子代理) │
│ ❌ 无 Plan Mode 专用 tool │
│ ❌ 无 Glob / Grep(read 读取目录即可) │
│ ❌ 无 NotebookEdit(不需要) │
│ ❌ 无 WebFetch / WebSearch │
│ │
│ 设计哲学: │
│ · System prompt ~1000 token(Claude Code 约 70000 token) │
│ · "工具少 → 决策简单 → LLM 更少犯工具选择错误" │
│ · edit 就是 old_string → new_string,没有花哨的功能 │
│ · 计划通过 PLAN.md 文件约定而非内置工具 │
│ · 不提供 MCP——如果用户需要新能力,自己写脚本用 bash 调 │
│ │
│ 优势: │
│ · Token 预算:极少的 system prompt token 留给上下文 │
│ · 可预测性:LLM 只有 4 种操作可选,行为高度可预测 │
│ · 透明度:用户一眼就能看懂 Agent 能做什么 │
│ │
│ 劣势: │
│ · 能力天花板:无法连接外部 MCP 服务 │
│ · 复杂任务:缺少并行工具和专用搜索工具,效率较低 │
│ · 扩展性:新能力需要新工具时只能等框架更新 │
│ │
└─────────────────────────────────────────────────────────────┘Claude Code vs Pi 的工具数量对比:
┌─────────────────────────────────────────────────────────────┐
│ 工具注册数 vs 活跃使用数 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ Claude Code │ Pi │
│ ───────────────┼──────────────────┼────────────────────── │
│ 注册工具数 │ ~48 │ 4 │
│ 活跃使用数* │ ~14-20 │ 4 │
│ System Prompt │ ~70000 token │ ~1000 token │
│ 工具选择难度 │ 高(48 选 1) │ 低(4 选 1) │
│ │
│ *活跃使用数 = 在实际任务中被调用过的工具 │
│ │
│ Claude Code 的策略: │
│ · 注册 48 个工具,但通过 Deferred Tool Loading │
│ 标记 shouldDefer: true 的工具只在被请求时才加载完整 schema │
│ · 从 77000 token 降到 8700 token(节省 ~88%) │
│ · 在功能丰富和 token 效率之间取得平衡 │
│ │
│ Pi 的策略: │
│ · 从一开始就只有 4 个工具,不需要 deferred loading │
│ · System prompt 用极少的 token 描述工具 │
│ · 用"少即是多"的哲学解决 token 和选择困难的问题 │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:Shell/Bash Mode——Agent 的"终端"
3.1 核心问题:Agent 如何"做事"?
读文件和改代码解决的是"理解和修改代码"的问题。但现代软件开发中,代码修改只是工作流的一部分。Agent 还需要:
┌─────────────────────────────────────────────────────────────┐
│ Agent 必须执行的 Shell 操作 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 依赖管理 │ │
│ │ · npm install / pip install / cargo add │ │
│ │ · 安装新的依赖、升级现有依赖、解决版本冲突 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 构建与编译 │ │
│ │ · npm run build / make / cargo build │ │
│ │ · 验证代码能编译通过 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 测试 │ │
│ │ · npm test / pytest / cargo test │ │
│ │ · 验证修改没有破坏现有功能 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 版本控制 │ │
│ │ · git status / git diff / git add / git commit │ │
│ │ · 查看变更、提交代码 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 环境操作 │ │
│ │ · 创建目录、移动文件、设置环境变量 │ │
│ │ · 启动开发服务器、查看日志 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 如果 Agent 不能执行这些操作, │ │
│ 用户就必须在 Agent 和终端之间反复切换, │ │
│ 这违背了 Agent 设计的初衷。 │ │
│ │
└─────────────────────────────────────────────────────────────┘Claude Code 团队的核心设计哲学:
"What's the simplest answer to 'where do you run commands?' It's locally."
Shell = 人类工程师能做的所有事,Agent 也能做。零桥接。
不需要为"运行测试"设计一个专用的 TestTool,不需要为"安装依赖"设计一个 PackageManagerTool。Bash 就是通用接口——所有命令行操作都可以通过它完成。
3.2 Shell 工具的安全层设计
正因为 Shell 能做"一切",它的安全设计至关重要。现代 Coding Agent 通常有四层安全防护:
┌─────────────────────────────────────────────────────────────┐
│ Shell 工具的四层安全架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 第 1 层: Permission 系统 │ │
│ │ │ │
│ │ · 基于 glob 模式的 allow / deny / ask 规则 │ │
│ │ · 例:allow: "npm test" / deny: "rm -rf /*" │ │
│ │ · 例:ask: "git push"(危险操作需要确认) │ │
│ │ · 细粒度参数匹配:Bash(npm:install) vs Bash(npm:*) │ │
│ │ │ │
│ │ 规则生效顺序: │ │
│ │ 1. 精确匹配(最高优先级) │ │
│ │ 2. glob 模式匹配 │ │
│ │ 3. 默认策略(通常为 ask) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 第 2 层: Sandbox 沙箱 │ │
│ │ │ │
│ │ · macOS: Apple Seatbelt (sandbox-exec) │ │
│ │ · Linux: bubblewrap (bwrap) │ │
│ │ · Windows: Windows Sandbox / AppContainers │ │
│ │ │ │
│ │ 沙箱限制: │ │
│ │ · 文件系统:只能访问指定目录 │ │
│ │ · 网络:可限制出站连接到特定域名 │ │
│ │ · 进程:隔离进程空间,防止横向移动 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 第 3 层: Hooks 生命周期钩子 │ │
│ │ │ │
│ │ Claude Code 提供 ~30 个生命周期事件: │ │
│ │ │ │
│ │ · PreToolUse:工具调用前(可阻止、可修改参数) │ │
│ │ · PostToolUse:工具调用后(可检查结果) │ │
│ │ · PermissionDenied:权限被拒绝时(可记录、报警) │ │
│ │ · Notification:各类通知事件 │ │
│ │ · Stop:Agent 停止时 │ │
│ │ │ │
│ │ 钩子的能力: │ │
│ │ · 在 Bash 执行前检查命令是否安全 │ │
│ │ · 在执行后扫描输出中的敏感信息 │ │
│ │ · 记录所有 shell 操作到审计日志 │ │
│ │ · 自定义安全策略(如"禁止访问 /etc 目录") │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 第 4 层: 参数匹配规则(细粒度控制) │ │
│ │ │ │
│ │ 格式:Tool(param:value) │ │
│ │ │ │
│ │ 示例: │ │
│ │ · Bash(npm:install) → allow(允许安装依赖) │ │
│ │ · Bash(npm:run dev) → allow(允许启动开发服务器) │ │
│ │ · Bash(git:push) → ask(推送前需要确认) │ │
│ │ · Bash(rm:*) → deny(禁止删除操作) │ │
│ │ · Bash(*) → ask(其他所有命令需确认) │ │
│ │ │ │
│ │ 注:param 值由 Agent 从命令字符串中解析, │ │
│ │ 第一个词为主命令,后续为子命令/参数 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.3 Claude Code 的 Bash 工具实现细节
工具执行策略——并行读,串行写:
┌─────────────────────────────────────────────────────────────┐
│ Claude Code 工具执行策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 只读工具(Read, Grep, Glob, Bash 只读命令) │ │
│ │ · 并行执行:最多 10 个同时运行 │ │
│ │ · 原因:只读操作没有副作用,并行可以大幅加速探索 │ │
│ │ · 示例:同时 Grep 5 个关键词 + Glob 3 个模式 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 写入工具(Edit, Write, Bash 写入命令) │ │
│ │ · 串行执行:一次一个 │ │
│ │ · 原因:写入有副作用,并行可能导致竞态条件和冲突 │ │
│ │ · 顺序:按 LLM 的输出顺序依次执行 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 判断公式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ if tool.isReadOnly: │ │
│ │ execute_in_parallel(max_concurrency=10) │ │
│ │ else: │ │
│ │ execute_sequentially() │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Deferred Tool Loading——节省 88% Token:
┌─────────────────────────────────────────────────────────────┐
│ Deferred Tool Loading 机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题: │
│ Claude Code 注册了 ~48 个工具,每个工具的 JSON schema │
│ 被序列化后嵌入 system prompt。如果不加处理,所有 48 个 │
│ 工具的完整定义会占用约 77000 token 的上下文窗口。 │
│ │
│ 解决方案:Deferred Tool Loading │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 初始状态: │ │
│ │ · 只加载高频工具的完整 schema(~14 个) │ │
│ │ · 低频工具只注册名称 + 一句话描述(~34 个) │ │
│ │ · 低频工具标记 shouldDefer: true │ │
│ │ · 初始 token 消耗:~8700 token │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (LLM 想调用某个低频工具) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM: "我需要用 PlanCreate 工具" │ │
│ │ → Harness 检测到 shouldDefer: true 的工具被请求 │ │
│ │ → 动态加载 PlanCreate 的完整 JSON schema │ │
│ │ → 注入到下一轮请求的 tools 列表中 │ │
│ │ → LLM 现在可以看到 PlanCreate 的完整参数定义 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 效果: │
│ · 77000 token → 8700 token(节省 ~88%) │
│ · 功能不减少:所有 48 个工具最终都可以被使用 │
│ · 延迟加载的代价:首次调用某个低频工具时多一轮对话 │
│ │
└─────────────────────────────────────────────────────────────┘3.4 Bash 输出处理——长输出的挑战
Shell 命令的输出可能极其庞大(如 find / 的输出),直接把所有输出塞进 context window 会瞬间用完 token 预算。
┌─────────────────────────────────────────────────────────────┐
│ Bash 输出的截断与摘要策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 1: 长度截断(最基础) │ │
│ │ │ │
│ │ · 设置最大输出字节数(如 30KB) │ │
│ │ · 超出部分截断,附带警告: │ │
│ │ "[output truncated after 30000 bytes]" │ │
│ │ · 适用:大部分命令 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 2: 智能分段(保留头尾) │ │
│ │ │ │
│ │ · 保留输出的前 10KB + 后 10KB │ │
│ │ · 中间部分替换为 "[... 中间省略 50000 行 ...]" │ │
│ │ · 适用:日志文件、构建输出 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 3: LLM 摘要(最智能) │ │
│ │ │ │
│ │ · 将长输出发送给一个更快的模型做摘要 │ │
│ │ · 摘要结果返回给主 Agent │ │
│ │ · 适用:需要理解但不需要逐行查看的输出 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 4: 错误优先过滤 │ │
│ │ │ │
│ │ · 优先保留 stderr 和包含 "error"/"fail" 的行 │ │
│ │ · stdout 中正常的行被压缩或省略 │ │
│ │ · 适用:编译输出(用户只关心哪些文件编译失败) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.5 Bash 与 Edit 的交互——"修改→验证"循环
Shell Mode 和 Edit Mode 不是孤立的。它们形成了一个核心循环:
┌─────────────────────────────────────────────────────────────┐
│ 修改→验证循环(Edit ↔ Shell) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ │ │
│ │ Edit Mode │ │ │
│ │ · 修改代码 │ │ │
│ │ · old→new 替换 │ │ │
│ └─────────┬───────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ │ │
│ │ Shell Mode │ │ │
│ │ · npm test │ │ │
│ │ · npm run build │ │ │
│ │ · npm run lint │ │ │
│ └─────────┬───────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────────────────┐ │ │
│ │ 通过? │ │ │
│ │ · ✅ 通过 → 继续下一个修改 │ │ │
│ │ · ❌ 失败 → 分析错误 → 回去修改 ──┘ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 这正是人类工程师的工作流: │
│ 改代码 → 跑测试 → 看结果 → 再改 → 再测 → 提交 │
│ │
│ Agent 不过是将这个循环自动化了。 │
│ Shell Mode 的价值不在于"能执行命令", │
│ 而在于"能在修改后自动验证修改是否正确"。 │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:模式组合——一个真实任务的数据流
4.1 完整任务跟踪:从需求到提交
下面用一个具体任务展示 Plan / Edit / Shell 三种模式如何协同工作:
┌─────────────────────────────────────────────────────────────┐
│ 完整任务数据流:给项目添加用户认证系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户输入:"给这个 Express 项目加上 JWT 用户认证" │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════╗ │
│ ║ 阶段 1: Plan Mode ║ │
│ ╠═══════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ EnterPlanMode ║ │
│ ║ │ ║ │
│ ║ ├── Read package.json(发现:express, mongoose) ║ │
│ ║ ├── Read server.js(发现:入口 + 中间件链) ║ │
│ ║ ├── Grep "auth|token|jwt|session" ║ │
│ ║ │ → 结果:没有现有认证逻辑 ║ │
│ ║ ├── Glob "routes/**/*.js" → 3 个路由文件 ║ │
│ ║ ├── Read routes/users.js(发现:无认证中间件) ║ │
│ ║ ├── Glob "models/**/*.js" → 1 个 User model ║ │
│ ║ ├── Read models/User.js(发现:有 password 字段) ║ │
│ ║ │ ║ │
│ ║ AskUserQuestion: ║ │
│ ║ "User model 已有 password 字段但未哈希。 ║ │
│ ║ 是否需要添加密码哈希?[是] [否]" ║ │
│ ║ → 用户回答:是 ║ │
│ ║ │ ║ │
│ ║ AskUserQuestion: ║ │
│ ║ "是否使用 refresh token?[是] [否]" ║ │
│ ║ → 用户回答:是 ║ │
│ ║ │ ║ │
│ ║ ExitPlanMode → 输出计划文件到 .claude/plans/ ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ (用户审批通过计划) │
│ │ │
│ ╔═══════════════════════════════════════════════════════╗ │
│ ║ 阶段 2: Edit Mode(逐步骤执行) ║ │
│ ╠═══════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ 步骤 1: 安装依赖(Shell Mode) ║ │
│ ║ ├── Bash: npm install jsonwebtoken bcryptjs ║ │
│ ║ └── ✅ 安装成功 ║ │
│ ║ ║ │
│ ║ 步骤 2: 修改 User model(Edit Mode) ║ │
│ ║ ├── Read models/User.js ║ │
│ ║ ├── Edit: 添加 pre-save hook(密码哈希) ║ │
│ ║ │ old_string: "userSchema.pre('save', ...)" ║ │
│ ║ │ new_string: "userSchema.pre('save', async ...)" ║ │
│ ║ └── Edit: 添加 comparePassword 实例方法 ║ │
│ ║ ║ │
│ ║ 步骤 3: 创建认证中间件(Edit Mode) ║ │
│ ║ ├── Write middleware/auth.js(全新文件) ║ │
│ ║ │ content: JWT 验证中间件完整代码 ║ │
│ ║ └── ✅ 文件创建成功 ║ │
│ ║ ║ │
│ ║ 步骤 4: 创建登录路由(Edit Mode) ║ │
│ ║ ├── Write routes/auth.js(全新文件) ║ │
│ ║ │ content: POST /login + POST /refresh 路由 ║ │
│ ║ └── ✅ 文件创建成功 ║ │
│ ║ ║ │
│ ║ 步骤 5: 修改 server.js(Edit Mode) ║ │
│ ║ ├── Read server.js ║ │
│ ║ ├── Edit: 注册 auth 路由 ║ │
│ ║ │ old_string: "app.use('/api', routes)" ║ │
│ ║ │ new_string: "app.use('/api/auth', authRoutes)..." ║ │
│ ║ └── ✅ 修改成功 ║ │
│ ║ ║ │
│ ║ 步骤 6: 保护现有路由(Edit Mode) ║ │
│ ║ ├── Edit routes/users.js: 添加 auth 中间件 ║ │
│ ║ ├── Edit routes/posts.js: 添加 auth 中间件 ║ │
│ ║ └── ✅ 3 个路由文件修改完成 ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════╗ │
│ ║ 阶段 3: Shell Mode(验证) ║ │
│ ╠═══════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ Bash: npm test ║ │
│ ║ → ❌ 3 个测试失败:auth 中间件未处理过期 token ║ │
│ ║ ║ │
│ ║ ┌─────────────────────────────────────────────────┐ ║ │
│ ║ │ 回到 Edit Mode: │ ║ │
│ ║ │ Read 错误日志 → 找出 TokenExpiredError │ ║ │
│ ║ │ Edit middleware/auth.js: 添加过期处理 │ ║ │
│ ║ └─────────────────────────────────────────────────┘ ║ │
│ ║ ║ │
│ ║ Bash: npm test ║ │
│ ║ → ✅ 全部通过 ║ │
│ ║ ║ │
│ ║ Bash: npm run build ║ │
│ ║ → ✅ 构建成功 ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════╗ │
│ ║ 阶段 4: Plan Mode(复查) ║ │
│ ╠═══════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ 检查计划完成度: ║ │
│ ║ ✅ 步骤 1: 安装依赖 ║ │
│ ║ ✅ 步骤 2: 密码哈希 ║ │
│ ║ ✅ 步骤 3: JWT 认证中间件 ║ │
│ ║ ✅ 步骤 4: 登录/刷新路由 ║ │
│ ║ ✅ 步骤 5: 保护现有路由 ║ │
│ ║ ✅ 步骤 6: 测试全部通过 ║ │
│ ║ ║ │
│ ║ 未完成项:无 ║ │
│ ║ → 输出完成报告 ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 最终输出: │ │
│ │ · 修改了 6 个文件(1 个新建,5 个修改) │ │
│ │ · 安装了 2 个依赖 │ │
│ │ · 所有测试通过 │ │
│ │ · 构建成功 │ │
│ │ │ │
│ │ 模式切换统计: │ │
│ │ Plan → Edit → Shell → Edit → Shell → Plan │ │
│ │ 共 6 次模式切换 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 模式切换的状态机
从这个例子中,我们可以抽象出模式之间的一般切换规律:
┌─────────────────────────────────────────────────────────────┐
│ 模式切换状态机(Mode Transition FSM) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ START │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Plan Mode │ │
│ │ (只读探索) │ │
│ └────────┬────────┘ │
│ │ │
│ 用户批准计划│ │
│ ▼ │
│ ┌─────────────────┐ │
│ ┌───→│ Edit Mode │←──────────┐ │
│ │ │ (修改代码) │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Shell Mode │ │ │
│ │ │ (验证/构建) │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌────────┴────────┐ │ │
│ │ │ 结果判断? │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌────────┴────────┐ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────┐ ┌──────────┐ │ │
│ │ │ 通过 │ │ 失败 │ │ │
│ │ └──┬───┘ └────┬─────┘ │ │
│ │ │ │ │ │
│ │ │ └─────────────┘ │
│ │ │ (回去修改) │
│ │ │ │
│ │ ▼ │
│ │ ┌────────────────────┐ │
│ │ │ 还有未完成的步骤? │ │
│ │ └────────┬───────────┘ │
│ │ │ │
│ │ ┌─────┴─────┐ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌─────┐ ┌──────────┐ │
│ │ │ 是 │ │ 否 │ │
│ │ └──┬──┘ └────┬─────┘ │
│ │ │ │ │
│ └──────┘ ▼ │
│ ┌─────────────────┐ │
│ │ Plan Mode │ │
│ │ (复查/收尾) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 完成 │ │
│ └─────────────────┘ │
│ │
│ 核心规律: │
│ · Plan → Edit:计划批准后进入编辑 │
│ · Edit → Shell:每轮修改后进入验证 │
│ · Shell → Edit:验证失败时回到修改 │
│ · Shell → Edit(下一轮):验证通过,继续后续修改 │
│ · Edit → Plan:所有步骤完成,复查计划 │
│ │
└─────────────────────────────────────────────────────────────┘4.3 模式设计的核心取舍
不同 Agent 对"模式"的实现反映了三种根本不同的设计哲学:
┌─────────────────────────────────────────────────────────────┐
│ 三种模式设计哲学 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 哲学 A: 工具即模式(Claude Code) │ │
│ │ │ │
│ │ 模式 = 工具可用性约束 │ │
│ │ · EnterPlanMode → 只读工具白名单 │ │
│ │ · ExitPlanMode → 全部工具恢复 │ │
│ │ · 不需要框架级别的"模式"概念 │ │
│ │ · Agent 自己决定何时切换模式(通过 tool call) │ │
│ │ │ │
│ │ 优势:灵活,Agent 可根据任务动态调整 │ │
│ │ 劣势:依赖 LLM 的判断力,可能不切换或切换太晚 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 哲学 B: 命令行驱动(Codex) │ │
│ │ │ │
│ │ 模式 = 命令行 flag + 自动化等级 │ │
│ │ · --plan → Plan Mode │ │
│ │ · Suggest / Auto Edit / Full Auto → 自动化等级 │ │
│ │ · 用户通过 flag 明确指定当前模式 │ │
│ │ · Agent 不自行决定升级自动化等级 │ │
│ │ │ │
│ │ 优势:用户完全掌控,可预测 │ │
│ │ 劣势:需要用户理解何时该用什么模式 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 哲学 C: 极简无模式(Pi) │ │
│ │ │ │
│ │ 模式 = 不需要模式 │ │
│ │ · 只有 4 个工具,不需要"模式"来区分 │ │
│ │ · 计划通过 PLAN.md 文件约定 │ │
│ │ · 安全通过工具本身的约束实现(而非模式切换) │ │
│ │ · System prompt ~1000 token,没有复杂的规则 │ │
│ │ │ │
│ │ 优势:极简,token 效率极高,行为可预测 │ │
│ │ 劣势:复杂任务缺少保护,容易"边想边做"出问题 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 没有"正确"的设计——每种哲学服务不同的使用场景。 │
│ │
└─────────────────────────────────────────────────────────────┘4.4 未来趋势:模式融合与自适应
从 Anthropic 2026 的 Plan 重设计方案中可以窥见一个趋势——模式正在从"状态切换"进化到"对象操作":
┌─────────────────────────────────────────────────────────────┐
│ 模式设计的演进方向 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 过去(2024): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户手动切换模式 │ │
│ │ 例:/plan → 手动切换到 Plan Mode │ │
│ │ 问题:用户需要知道何时该切换、切换代价高 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 现在(2025-2026): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具驱动模式切换 │ │
│ │ 例:EnterPlanMode tool → Agent 自己决定进入 Plan │ │
│ │ 优势:Agent 自主判断时机,减少用户干预 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 未来(2026+): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 计划作为一等公民 + 自适应模式 │ │
│ │ · PlanCreate/Update/Execute/Status/Archive │ │
│ │ · Agent 根据任务复杂度自动选择模式 │ │
│ │ · 模式不再是"开关",而是"策略"(strategy pattern) │ │
│ │ · 简单任务 → 隐式 Plan(单轮探索即开工) │ │
│ │ · 复杂任务 → 显式 Plan(完整探索 + 多层审批) │ │
│ │ · 高风险任务 → 强制 Plan + Sandbox + Double-check │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:为什么需要模式
Agent 可以同时读文件、改代码、执行 shell 命令时,"模式"把能力分解为不同的操作状态——Plan(只读探索)、Edit(精准修改)、Shell(执行验证)。每种模式有不同的工具可用、不同的安全约束、不同的用户交互方式。没有模式,LLM 在"探索"和"修改"之间没有边界,容易走进死胡同。
总结2:Plan Mode 的四种实现
| Agent | Plan 方式 | 核心特点 |
|---|---|---|
| Claude Code | EnterPlanMode / ExitPlanMode tool | 工具即状态——进入 Plan 就是调用一个 tool |
| Codex CLI | --plan flag + TL;DR checkpoint | 强制先看再说,3-5 条摘要快速审批 |
| Grok Build | 8 个并行 agent 各自 plan | 多 Agent 从不同角度同时探索 |
| Pi | PLAN.md 文件约定 | 极简——不需要特殊的 Plan Mode 工具 |
总结3:Edit Mode 的三种策略
Full-file Rewrite(Write) → Token 消耗大,但不会格式错误
String Replacement(Edit) → Token 极省,但 old_string 匹配可能失败
AST/Semantic Edit(未来) → 语义精确,但目前没有 Agent 大规模使用Claude Code 采用 String Replacement 策略,设计原则来自 "Seeing like an agent":站在 Agent 的视角设计工具,匹配失败不是 bug 而是让 Agent 重新确认文件状态的设计特性。
总结4:Shell Mode 的四层安全
- Permission 系统:基于 glob 的 allow/deny/ask 规则,支持
Tool(param:value)细粒度 - Sandbox 沙箱:Apple Seatbelt / bubblewrap / Windows Sandbox
- Hooks 生命周期:~30 个事件的 PreToolUse / PostToolUse / PermissionDenied
- 参数匹配规则:
Bash(npm:install)allow /Bash(rm:*)deny
总结5:模式组合的核心循环
Plan(探索+出计划) → Edit(修改代码) → Shell(验证)
↑ │
└──── 失败 ←─────────┘
(验证通过 → 下一轮修改)Deferred Tool Loading 将这个循环的 token 消耗从 77000 降到 8700(~88% 节省),证明了一个重要原则:工具设计的"丰富性"和"效率"不是零和博弈,良好的工程架构可以兼得。
总结6:三种设计哲学
| 哲学 | 代表 | 核心理念 |
|---|---|---|
| 工具即模式 | Claude Code | 模式 = 工具可用性约束,Agent 自主切换 |
| 命令行驱动 | Codex | 模式 = flag + 自动化等级,用户掌控 |
| 极简无模式 | Pi | 4 个工具,不需要模式切换,文件约定替代 |
章节测试
测试1:概念理解
为什么无模式的 Agent(所有工具平等可用)会在复杂任务中失败?请用"探索"和"修改"之间的边界问题解释。
测试2:Plan Mode 实现对比
Claude Code 的 Plan Mode 使用 EnterPlanMode 和 ExitPlanMode 两个 tool 来实现状态切换。这种"工具即状态"的设计与 Codex 的 --plan flag 方式有什么本质区别?
测试3:编辑策略分析
String Replacement(Edit 工具)的 old_string 匹配失败时,为什么 Claude Code 选择返回错误让 LLM 重新读文件,而不是使用模糊匹配?这种设计的依据是什么?
测试4:安全架构
请列出 Shell 工具的四层安全防护,并简要说明每层的作用。
测试5:Deferred Tool Loading
Claude Code 如何通过 Deferred Tool Loading 将 tool schema 的 token 消耗从 77000 降到 8700?这种设计有什么代价?
测试6:Anthropic 2026 Plan 重设计
Anthropic 2026 的 Plan 重设计方案引入了哪 6 个新 Tool?这种从"状态切换"到"对象操作"的转变解决了旧方案的哪些核心问题?
测试7:设计哲学
Pi 只有 4 个工具(read, write, edit, bash),没有 Plan Mode,没有 MCP。这种极简设计的优势和劣势分别是什么?在什么场景下这种设计优于 Claude Code?
参考答案
测试1答案
答案:无模式 Agent 在探索(读文件、理解结构)和修改(改代码、装依赖)之间没有边界,导致 LLM 可能在还没理解项目全貌时就开始修改,或者在不该修改的地方修改。这种"边想边做"的模式可能导致:走错方向后需要大量回滚、安装不必要的依赖、修改不相关的文件、在理解错误的假设上累积更多错误。Plan Mode 通过强制"先只读探索→制定计划→人类审批→再执行"来解决这个问题。
测试2答案
答案:
- Claude Code(工具即状态):Plan Mode 的进入和退出是 tool call,Agent 自己决定何时进入/退出。不需要框架层面的特殊协议。状态切换对用户透明——Agent 在 Plan Mode 时自动只能使用只读工具。
- Codex(命令行驱动):Plan Mode 是用户通过 flag 明确指定的。用户决定何时进入 Plan Mode,Agent 不自行切换。这给了用户更多控制权,但要求用户理解何时该用 Plan。
本质区别:谁来决定何时切换模式——Agent(Claude Code)还是用户(Codex)。
测试3答案
答案:设计依据来自 Anthropic 的 "Seeing like an agent" 原则——站在 Agent 的视角设计工具。模糊匹配会掩盖 Agent 对文件状态的理解错误:如果 Agent 以为文件是 A 状态但实际是 B 状态,而模糊匹配"容错"了这种差异,那 Agent 永远不会纠正自己对文件的理解偏差。返回精确的错误迫使 Agent 重新 Read 文件获取最新内容,这是一种"让 Agent 自己发现和修正理解错误"的设计,比"工具替 Agent 猜"更可靠。
测试4答案
答案:
- Permission 系统:基于 glob 模式的 allow/deny/ask 规则,支持
Tool(param:value)细粒度参数匹配,是第一道防线 - Sandbox 沙箱:操作系统级隔离(Apple Seatbelt / bubblewrap / Windows Sandbox),限制文件系统访问、网络访问和进程空间
- Hooks 生命周期钩子:~30 个事件(PreToolUse / PostToolUse / PermissionDenied 等),允许在工具调用前后进行自定义安全检查
- 参数匹配规则:
Bash(npm:install)vsBash(rm:*)级别的细粒度控制,精确到命令+子命令的组合
测试5答案
答案:
- 机制:初始只加载高频工具(~14 个)的完整 schema,低频工具(~34 个)只注册名称和一句话描述并标记
shouldDefer: true。当 LLM 首次请求某个低频工具时,Harness 检测到后动态加载其完整 schema 注入下一轮请求。 - 代价:首次调用某个低频工具时需要额外一轮对话(第一轮 LLM 只知道工具名和简介,第二轮才看到完整参数定义),增加了延迟。但对于绝大多数不调用的低频工具,节省了大量 token。
测试6答案
答案: 6 个新 Tool:PlanCreate、PlanList、PlanUpdate、PlanExecute、PlanStatus、PlanArchive
解决的旧方案问题:
- 计划持久化:旧方案把计划当文件存(.md),新方案结构化存储
- 增量更新:旧方案需要人工判断计划是否需要更新,新方案 PlanUpdate 原生支持
- 历史复用:旧方案无历史记录,新方案 PlanArchive 归档复用
- 多计划并行:旧方案不支持,新方案通过 plan_id 隔离支持
- 中断恢复:旧方案依赖解析文件内容恢复状态,新方案 PlanStatus 查询
- 与执行耦合:旧方案 ExitPlanMode 后计划与执行断开,新方案通过 PlanExecute 持续关联
测试7答案
答案:
- 优势:极少的 system prompt token(~1000 vs ~70000),LLM 只有 4 种操作可选因此行为高度可预测,用户能完全理解 Agent 的能力边界,token 效率极高
- 劣势:能力天花板低(无法连接 MCP),复杂任务缺少并行工具和专用搜索工具导致效率较低,扩展性差,复杂任务容易因无 Plan 模式保护而"边想边做"出问题
- 适用场景:简单到中等复杂度的任务、token 预算紧张的场景、用户完全信任 Agent 且希望最小化等待时间、对可预测性要求高于能力上限的场景
相关笔记
- [[01-function-calling]] - Function Calling 基础与 Agent Loop
- [[05-agent-workflow]] - Agent 工作流设计模式
- [[21-tools-design]] - 工具设计的工程原则
- [[24-harness-loop-skills]] - Harness Engineering 与 Loop Engineering
- [[22-agent-architecture-patterns]] - Agent 架构模式
- [[09-安全沙箱]] - Shell 安全沙箱详解
- [[10-权限与门卫]] - Permission 系统设计
下一步学习
- [ ] 阅读 18 - 安全与权限设计深入
- [ ] 阅读 Anthropic 2026 官方 Plan Mode RFC(如已公开)
- [ ] 动手实践:在 Claude Code 中使用 /plan 完整完成一个多文件修改任务,观察模式切换
学习状态:🟡 开始学习