编程思想要点:Agent 编程不是"用 AI 写代码",而是一种全新的编程范式——你不再编写执行步骤,而是定义能力边界和意图空间。
3.1 工具即接口:传统 API vs Agent 工具
在传统编程中,接口(Interface)是程序员之间的契约。在 Agent 编程中,工具(Tool)是人与 AI 之间的契约。这个看似微小的转变,实际上是一场认知革命。
3.1.1 传统 API 的设计哲学
传统 API 设计遵循几个核心原则:
// 传统 API 设计
interface FileService {
read(path: string): Promise<string>;
write(path: string, content: string): Promise<void>;
edit(path: string, oldText: string, newText: string): Promise<void>;
delete(path: string): Promise<void>;
list(dir: string, pattern?: string): Promise<string[]>;
}核心假设:
- 调用者知道要做什么:程序员预先决定调用哪个方法、传什么参数
- 接口是稳定的:方法签名不应该频繁变化
- 错误是异常的:预期路径上不应该出错
- 粒度是固定的:每个方法做一件事
这是一种命令式接口——你告诉系统确切地做什么。
3.1.2 Agent 工具的设计哲学
现在看看 Claude Code 如何定义同样的文件操作工具:
// Claude Code 的 Agent 工具定义
interface FileEditInput {
/** The absolute path to the file to modify */
file_path: string;
/** The text to replace */
old_string: string;
/** The text to replace it with (must be different from old_string) */
new_string: string;
/** Replace all occurrences of old_string (default false) */
replace_all?: boolean;
}interface FileReadInput {
/** The absolute path to the file to read */
file_path: string;
/** The line number to start reading from */
offset?: number;
/** The number of lines to read */
limit?: number;
/** Page range for PDF files (e.g., "1-5", "3", "10-20") */
pages?: string;
}表面上看,这与传统 API 没有太大区别。但关键的差异在于谁决定使用这些工具:
- 传统 API:程序员在代码中调用
fileService.edit(path, old, new) - Agent 工具:AI 根据用户意图自主决定调用
FileEdit,并自己构造参数
源码透视:工具描述的重要性
在 Agent 编程中,工具的文档描述比类型签名更重要。因为 AI(而不是程序员)是工具的使用者,它通过阅读描述来理解何时、如何使用工具。
从 sdk-tools.d.ts 中可以看到,每个字段都有详细的 JSDoc 注释:
interface BashInput {
/** The command to execute */
command: string;
/**
* Clear, concise description of what this command does in active voice.
*
* For simple commands (git, npm, standard CLI tools), keep it brief:
* - ls → "List files in current directory"
* - git status → "Show working tree status"
*
* For commands that are harder to parse at a glance:
* - find . -name "*.tmp" -exec rm {} \; → "Find and delete all .tmp files"
*/
description?: string;
/** Set to true to run this command in the background */
run_in_background?: boolean;
/**
* Set this to true to dangerously override sandbox mode
* and run commands without sandboxing.
*/
dangerouslyDisableSandbox?: boolean;
}注意 description 字段的注释——它不仅说明了字段的作用,还给出了使用示例和最佳实践。这是因为 AI 需要足够的上下文来正确使用这个工具。
再注意 dangerouslyDisableSandbox 字段的命名——使用了 "dangerously" 前缀。这不是随意的命名,而是对 AI 的一种软约束。当 AI 看到这个名字时,它会倾向于不使用这个选项,除非用户明确要求。
3.1.3 FileEdit 的设计哲学
FileEdit 工具是最能体现 Agent 编程思维的例子。它不是传统的 file.write(path, content)——完全覆盖文件。它是一个差异编辑器:
interface FileEditInput {
file_path: string; // 绝对路径
old_string: string; // 要替换的原始文本
new_string: string; // 替换后的新文本
replace_all?: boolean; // 是否替换所有匹配
}为什么是差异编辑而不是全文件覆盖?三个原因:
- 精确性:AI 只修改它确定需要修改的部分,而不是重写整个文件
- 安全性:如果
old_string不匹配,操作会失败,防止意外覆盖 - 可审计性:每次修改都有明确的 before/after,便于审查
这是一种微创手术式的设计哲学——最小化每次变更的影响范围。对于 AI Agent 来说,这尤为重要,因为它的操作需要人类的信任。
源码透视:子 Agent (AgentInput) 的设计
最令人惊叹的工具设计是 AgentInput:
interface AgentInput {
/** A short (3-5 word) description of the task */
description: string;
/** The task for the agent to perform */
prompt: string;
/** The type of specialized agent to use */
subagent_type?: string;
/** Model override: "sonnet" | "opus" | "haiku" */
model?: string;
/** Run in background */
run_in_background?: boolean;
/** Name for the spawned agent */
name?: string;
/** Team name */
team_name?: string;
/** Permission mode */
mode?: "acceptEdits" | "bypassPermissions" | "default" | "dontAsk" | "plan";
/** Isolation mode */
isolation?: "worktree";
}这是一个嵌套 Agent 的接口——Agent 可以创建子 Agent 来执行子任务。这种递归的设计体现了 Agent 编程的核心思想:分解与委托。
特别注意几个设计亮点:
isolation?: "worktree":子 Agent 在独立的 Git worktree 中工作。这意味着它可以自由修改文件,而不会影响主分支。这是一种沙盒隔离——AI 版本的"影子构建"。mode参数的五种权限级别:"acceptEdits":自动接受文件编辑"bypassPermissions":绕过所有权限检查"default":默认权限模式"dontAsk":不询问用户"plan":只生成计划,不执行
model参数:允许为不同任务选择不同能力的模型。简单任务用 haiku(快且便宜),复杂任务用 opus(慢但能力强)。
3.2 推理即执行:Extended Thinking 与自适应计算
传统程序的计算量是可预测的——排序 O(n log n),搜索 O(n),矩阵乘法 O(n³)。Agent 编程的计算量是自适应的——简单问题快速回答,复杂问题深度思考。
3.2.1 Extended Thinking:让 AI "想一想"
Anthropic 的 Extended Thinking(扩展思考)功能是 Claude Code 的重要基础。它允许模型在生成最终答案之前进行内部推理。
从 Claude Code 的源码中可以看到与 thinking 相关的多个字段:
// Vj7() 状态中的 thinking 相关字段
{
thinkingClearLatched: null, // Thinking 清除状态
systemPromptSectionCache: new Map() // 缓存 thinking 结果
}以及 MessageStream 中对 thinking 事件的处理:
// MessageStream 事件类型
case "thinking_delta":
// 接收增量思考内容
if (block.type === "thinking")
message.content[index] = { ...block,
thinking: block.thinking + delta.thinking };
break;
case "signature_delta":
// 接收思考签名(完整性校验)
if (block.type === "thinking")
message.content[index] = { ...block, signature };
break;Extended Thinking 的工作原理:
用户提问 → Claude 开始"思考"(不可见) →
思考完成(thinking 块) → 生成最终答案 →
调用工具 → 获取结果 → 可能再次"思考" → ...每个 thinking 块都有一个 signature(签名),用于验证思考过程的完整性。这是一种链式完整性保证——确保思考内容没有被篡改。
3.2.2 自适应思考:thinking_budget
Claude Code 支持自适应思考预算——根据任务复杂度动态调整思考深度。
从 Vj7() 状态中可以看到:
{
promptCache1hEligible: null, // 1小时缓存资格
promptCache1hAllowlist: null, // 缓存白名单
afkModeHeaderLatched: null, // AFK 模式标记
fastModeHeaderLatched: null, // 快速模式标记
}fastModeHeaderLatched 字段特别有趣——它指示 Claude Code 是否应该使用"快速模式"。在快速模式下,Claude 可能会减少思考时间、使用更小的模型、或跳过某些验证步骤。
这是一种计算预算管理——类似于游戏引擎中的 LOD(Level of Detail)系统,根据场景复杂度动态调整渲染精度。Claude Code 根据当前模式(快速/正常/深度)来调整计算精度(思考时间/模型大小/验证级别)。
3.2.3 Prompt Cache:1小时窗口
Anthropic 的 Prompt Cache 是一个性能优化特性——它允许缓存 System Prompt 和对话历史,避免每次 API 调用都重新发送。
Claude Code 的实现:
{
promptCache1hEligible: null, // 当前请求是否符合 1h 缓存条件
promptCache1hAllowlist: null, // 允许使用 1h 缓存的内容白名单
}promptCache1hAllowlist 是一个精心维护的列表——只有被列入白名单的 System Prompt 段才能享受 1 小时缓存。这是因为缓存需要内容完全匹配,任何变化都会导致缓存失效。
Claude Code 的 System Prompt 是动态构建的(如我们在第2章所见),但某些部分是稳定的——比如工具定义、代码规范、安全规则。这些稳定部分被列入缓存白名单,而动态部分(如 MCP 服务器指令、项目特定信息)则不缓存。
这种选择性缓存策略是在性能和灵活性之间的精妙平衡:
缓存的内容(稳定) 不缓存的内容(动态)
├── 工具定义 ├── MCP 服务器指令
├── 代码规范 ├── 项目结构信息
├── 安全规则 ├── 用户偏好
└── 基础上下文 └── 会话状态设计思想:推理即执行
Extended Thinking 和 Prompt Cache 共同体现了一个核心设计哲学:推理即执行。
在传统编程中,"推理"和"执行"是分开的:
- 编译器推理类型,运行时执行代码
- 优化器推理性能,CPU 执行指令
- 测试框架推理正确性,部署系统执行发布
在 Agent 编程中,"推理"和"执行"融为一体:
- AI 的思考过程就是它的执行过程
- 工具调用是思考的延续,而不是独立步骤
- 上下文管理既是推理策略,也是执行优化
这意味着你不能将推理和执行分开优化。提高思考质量会直接提高执行质量,提高执行效率会释放更多计算资源给推理。
3.3 反馈即控制:权限模式与交互设计
在传统编程中,控制流由代码决定。在 Agent 编程中,控制流由人机交互决定。Claude Code 提供了一套精密的权限和交互系统。
3.3.1 六种权限模式
从 AgentInput 的 mode 字段中,我们已经看到了五种权限级别。加上默认的交互模式,Cla Code 实际上支持六种权限模式:
| 模式 | 描述 | 适用场景 |
|---|---|---|
default | 默认交互模式,每步询问用户 | 首次使用、敏感操作 |
acceptEdits | 自动接受文件编辑 | 信任度高的批量重构 |
bypassPermissions | 绕过所有权限检查 | CI/CD、自动化流水线 |
dontAsk | 不询问用户,自动执行 | 非交互式(SDK 模式) |
plan | 只生成计划,不执行 | 需要预审的场景 |
auto | 自动模式,平衡安全与效率 | 日常开发 |
从 Vj7() 状态中可以看到相关的控制字段:
{
sessionBypassPermissionsMode: false, // 会话级权限绕过
hasExitedPlanMode: false, // 是否已退出计划模式
needsPlanModeExitAttachment: false, // 是否需要计划模式退出附件
needsAutoModeExitAttachment: false, // 是否需要自动模式退出附件
}源码透视:模式切换的状态机
模式之间的切换不是简单的标志位翻转,而是一个状态机:
// 计划模式转换处理
function handlePlanModeTransition(currentMode, newMode) {
if (newMode === "plan" && currentMode !== "plan") {
G8.needsPlanModeExitAttachment = false;
}
if (currentMode === "plan" && newMode !== "plan") {
G8.needsPlanModeExitAttachment = true;
}
}
// 自动模式转换处理
function handleAutoModeTransition(currentMode, newMode) {
if (currentMode === "auto" && newMode === "plan" ||
currentMode === "plan" && newMode === "auto") {
return; // plan ↔ auto 是直接切换
}
let wasAuto = currentMode === "auto";
let isAuto = newMode === "auto";
if (isAuto && !wasAuto) {
G8.needsAutoModeExitAttachment = false;
}
if (wasAuto && !isAuto) {
G8.needsAutoModeExitAttachment = true;
}
}这些状态转换确保了:
- 从计划模式退出时,计划内容会被正确附加
- 从自动模式退出时,操作历史会被保留
- 模式切换的"附件"(attachment)机制确保信息不丢失
3.3.2 计划模式(Plan Mode)
计划模式是 Claude Code 最独特的设计之一。在这种模式下,AI 只生成计划而不执行——它分析问题、分解任务、规划步骤,但不会触碰任何文件或运行任何命令。
计划模式的价值:
- 信任建立:在执行前让用户审查 AI 的思路
- 成本控制:规划比执行便宜(不需要工具调用)
- 并行规划:可以同时让 AI 规划多个方案
从 ExitPlanModeInput 的定义中可以看到计划模式的退出机制:
interface ExitPlanModeInput {
/**
* Prompt-based permissions for plan execution.
* These describe categories of actions rather than specific commands.
*/
allowedPrompts?: {
/** The tool this prompt applies to */
tool: "Bash";
/** Semantic description, e.g. "run tests", "install dependencies" */
prompt: string;
}[];
}注意 allowedPrompts 的设计——它不是列出具体的命令(如 npm test),而是语义描述(如 "run tests")。这是 Agent 编程特有的设计:你描述意图,AI 理解意图。
3.3.3 用户交互工具:AskUserQuestion
Claude Code 提供了一个专门的工具来与用户交互:
interface AskUserQuestionInput {
questions: [
{
question: string; // 问题文本
header: string; // 短标签(最多12字符)
options: [ // 2-4个选项
{
label: string; // 选项标签
description: string; // 选项描述
preview?: string; // 可选的预览内容
}
];
multiSelect: boolean; // 是否多选
}
];
}这个工具的设计有几个值得注意的细节:
结构化输入:不是自由文本问答,而是结构化的选择题。这确保了 AI 能正确解析用户的回答。
preview 字段:选项可以附带预览内容。例如,当选择不同的重构方案时,可以预览重构后的代码。
multiSelect 支持:允许多选,适用于"你想要启用哪些功能?"这类问题。
1-4 个问题限制:一次最多问 4 个问题,避免信息过载。
2-4 个选项限制:每个问题 2-4 个选项,加上自动提供的"Other"选项。
设计思想:人机协同的控制论
Claude Code 的权限系统体现了人机协同控制论的核心理念:
- 控制不是二元的:不是"人类控制一切"或"AI 自主一切",而是连续的权限光谱
- 信任是渐进的:从
plan(不执行)到default(逐步确认)到auto(自动执行) - 干预是精确的:通过
allowedPrompts可以精确控制 AI 在执行阶段能做什么 - 可审计性:每个决策都有记录(modelUsage, totalCostUSD 等)
3.4 编写 Agent 代码的思维模型
理解了 Claude Code 的架构和工具设计之后,我们最后来探讨一个更深层的问题:编写 Agent 系统需要什么样的思维模型?
3.4.1 从"写步骤"到"描述意图"
传统程序员习惯于写步骤:
# 传统思维:我需要告诉计算机每一步做什么
def deploy_service():
# 1. 检查环境
if not os.path.exists("Dockerfile"):
raise FileNotFoundError("Dockerfile not found")
# 2. 构建镜像
subprocess.run(["docker", "build", "-t", "myapp", "."])
# 3. 停止旧容器
subprocess.run(["docker", "stop", "myapp-container"])
# 4. 启动新容器
subprocess.run(["docker", "run", "-d", "--name", "myapp-container", "myapp"])
# 5. 健康检查
for i in range(30):
response = requests.get("http://localhost:8080/health")
if response.status_code == 200:
break
time.sleep(1)
else:
raise TimeoutError("Health check failed")Agent 编程者写意图:
# Agent 思维:我需要告诉 AI 我想要什么结果
claude "帮我部署这个应用到 Docker,确保新容器启动后健康检查通过再停止旧容器"这个转变不只是在语言层面的——从 Python 到自然语言。更根本的是思维方式的转变:
| 维度 | 步骤思维 | 意图思维 |
|---|---|---|
| 关注点 | 怎么做 | 做什么 |
| 错误处理 | 预定义所有错误路径 | 让 AI 自适应处理 |
| 边界条件 | 显式检查 | 上下文推断 |
| 抽象层级 | 固定(一个函数做一件事) | 动态(AI 根据任务调整) |
| 验证 | 单元测试 | 人工审查 + AI 自检 |
3.4.2 从"防御性编程"到"信任性编程"
传统编程强调防御性编程——假设一切都会出错:
# 防御性编程
def parse_config(path):
if not isinstance(path, str):
raise TypeError("path must be a string")
if not os.path.exists(path):
raise FileNotFoundError(f"Config file not found: {path}")
if not os.access(path, os.R_OK):
raise PermissionError(f"Cannot read config file: {path}")
content = open(path).read()
if not content.strip():
raise ValueError("Config file is empty")
config = json.loads(content)
if "version" not in config:
raise KeyError("Config missing 'version' field")
# ...Agent 编程更接近"信任性编程"——假设 Agent 有基本的判断能力:
# 信任性编程
claude "读取 config.json 并根据配置初始化应用,如果配置有问题就告诉我"这不是放弃验证,而是转移验证的责任——从程序代码转移到 Agent 的推理过程。Agent 会检查文件是否存在、内容是否合法、配置是否完整,因为它被训练为这样做。
3.4.3 从"确定性"到"概率性"思维
也许这是最难适应的转变:传统程序员期望确定性——相同的输入总是产生相同的输出。Agent 编程本质上是概率性的——即使相同的输入,AI 也可能做出不同的决策。
这并不意味着 Agent 编程是不可控的。Claude Code 通过多个机制来管理不确定性:
- 工具约束:工具的输入输出类型是确定的,AI 只能在工具提供的接口内操作
- 权限模式:通过权限级别控制 AI 的自主程度
- 成本追踪:通过 token 和费用追踪来监控 AI 的行为
- 会话审计:所有操作都有日志,可以事后审查
源码透视:不确定性管理
从 Vj7() 的状态设计中可以看到不确定性管理的多个层次:
{
// 第一层:操作审计
totalLinesAdded: 0, // 追踪所有变更
totalLinesRemoved: 0,
totalToolDuration: 0,
// 第二层:成本控制
totalCostUSD: 0, // 花费上限
modelUsage: {}, // 按模型追踪
// 第三层:行为监控
inMemoryErrorLog: [], // 错误日志
slowOperations: [], // 慢操作检测
lastAPIRequest: null, // 最后一次 API 请求
// 第四层:用户控制
isInteractive: false, // 交互/非交互模式
sessionBypassPermissionsMode: false, // 权限绕过
hasExitedPlanMode: false, // 计划模式控制
}每一层都是对不确定性的一个约束——操作审计告诉你"发生了什么",成本控制告诉你"花了多少",行为监控告诉你"是否异常",用户控制让你"可以干预"。
3.4.4 一个完整的思维模型转换案例
让我们通过一个完整的案例来感受思维模型的转变。
任务:为一个 Express.js 项目添加 rate limiting 中间件。
传统思维(步骤化):
// 1. 安装依赖
// npm install express-rate-limit
// 2. 创建中间件
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个 IP 限制 100 次请求
message: 'Too many requests from this IP'
});
// 3. 应用到路由
app.use('/api/', limiter);
// 4. 添加错误处理
app.use((err, req, res, next) => {
if (err.type === 'entity.too.large') {
return res.status(413).json({ error: 'Request body too large' });
}
next(err);
});
// 5. 编写测试
// test/rate-limit.test.js
// ...Agent 思维(意图化):
claude "为这个 Express.js API 项目添加 rate limiting,要求:
- 每个 IP 每15分钟最多100次请求
- 对 /api/ 路径生效
- 添加适当的错误处理
- 更新 README 说明这个新功能"注意区别:
传统思维需要知道:express-rate-limit 的 API、中间件的顺序、错误处理的最佳实践
Agent 思维只需要知道:业务需求(100次/15分钟)、适用范围(/api/)、期望输出(README 更新)
传统思维的验证方式:编写单元测试
Agent 思维的验证方式:AI 自动测试 + 人工审查代码变更
传统思维的修改方式:找到相关代码,手动修改
Agent 思维的修改方式:告诉 AI "把限制改成 200 次",AI 自动找到并修改相关代码
3.4.5 Agent 编程的"四象限"思维
最后,我提出一个 Agent 编程的"四象限"思维模型:
明确意图
│
┌───────────┼───────────┐
│ 象限 I │ 象限 II │
│ 脚本化 │ 委托化 │
│ │ │
│ 传统编程 │ Agent编程 │
│ 的最佳区 │ 的最佳区 │
│ 域 │ 域 │
隐 │ │ │
含 │───────────┼───────────│
知 │ 象限 III │ 象限 IV │
识 │ 探索化 │ 协同化 │
│ │ │
│ Agent │ 人类+Agent
│ 独立探索 │ 深度协作 │
│ │ │
└───────────┼───────────┘
│
模糊意图象限 I(明确+隐性):传统编程的最佳领域。明确的步骤,隐含的细节。比如实现一个排序算法。
象限 II(明确+显性):Agent 编程的最佳领域。明确的意图,显式的约束。比如"重构认证模块为 JWT"。
象限 III(模糊+隐性):Agent 独立探索的领域。模糊的目标,隐含的需求。比如"优化这个项目的性能"。
象限 IV(模糊+显性):人类+Agent 深度协作的领域。模糊的愿景,但需要精确执行。比如"设计一个新功能"——愿景模糊,但最终代码需要精确。
优秀的 Agent 编程者知道什么时候用哪个象限的思维:
- 确定性逻辑 → 象限 I(直接写代码)
- 明确需求 → 象限 II(委托给 Agent)
- 探索性问题 → 象限 III(让 Agent 先探索,再审查)
- 复杂设计 → 象限 IV(与 Agent 协同完成)
小结
本章我们从思维模型的视角理解了 Agent 编程:
工具即接口:传统 API 是"命令",Agent 工具是"能力描述"。工具的文档描述比类型签名更重要,因为 AI(而非程序员)是使用者。FileEdit 的差异编辑设计和 AgentInput 的嵌套 Agent 设计是最佳范例。
推理即执行:Extended Thinking 将推理过程嵌入执行流程,thinking_budget 实现自适应计算,Prompt Cache 的选择性缓存平衡性能与灵活性。
反馈即控制:六种权限模式形成连续的控制光谱,计划模式支持"先规划后执行"的工作流,结构化的用户交互工具(AskUserQuestion)确保人机沟通的精确性。
意图思维:从"写步骤"到"描述意图",从"防御性编程"到"信任性编程",从"确定性"到"概率性"思维。四象限模型帮助判断何时用传统编程,何时委托给 Agent。
核心认知转变:
- 你不是在写代码,你是在定义能力边界和意图空间
- 最好的 Agent 代码是你不写的代码
- 控制不是限制,而是精确度调节
卷一总结:认知篇的三层递进
让我们回顾这三章的递进关系:
第1章(历史层):编程思想的三次浪潮——指令式→对象式→Agent式。理解历史,是为了理解我们正站在什么位置。
第2章(架构层):Claude Code 的全景架构——12MB 单文件中如何组织一个完整的 Agent 系统。理解架构,是为了理解 Agent 的工程实现。
第3章(思维层):Agent 编程的思维模型——工具即接口、推理即执行、反馈即控制。理解思维,是为了改变你"编写软件"的方式。
这三层构成了一个认知金字塔:
思维模型
╱ ╲
╱ ╲
╱ ╲
╱ ╲
架构实现 ─────
╱ ╲
╱ ╲
历史演进 ─────历史是基础,架构是支撑,思维是目标。没有对历史的理解,你无法欣赏架构的精妙;没有对架构的理解,你无法掌握新的思维方式。
在接下来的卷二中,我们将从"认知"走向"实现"——深入 Claude Code 的工具系统、会话管理、MCP 协议和权限模型,看看这些设计理念是如何在代码中落地的。