docs: Add comprehensive agentic design documentation (2.5-hour learning session)

新增 docs/agentic-design/ 教育文档,全中文+英文技术术语,覆盖:

- README.md: 总索引,整体架构图,阅读指南
- 00-codebase-tour.md: 代码库全景,递归覆盖所有重要目录
- 01-agent-loop.md: Query loop 状态机,token budget,compaction
- 02-tool-system.md: Tool interface,权限检查,并发执行,sibling abort
- 03-multi-agent-coordination.md: Agent 类型,coordinator 4 阶段,XML 通信
- 04-permission-system.md: Permission mode,rule system,YOLO 分类器,denial tracking
- 05-context-and-memory.md: System prompt 分层,compaction,auto-dream 后台巩固
- 06-feature-gating.md: Compile-time feature,runtime flags,Bun dead-code elimination

总计 ~2.5 小时阅读,每篇含 Design Decision 专栏和常见误解纠正

https://claude.ai/code/session_017Vdqo9B8eTXiEDcqnv9DxM
This commit is contained in:
Claude 2026-03-31 16:07:29 +00:00
parent 0cf2fa2edb
commit 808d5a61b3
No known key found for this signature in database
8 changed files with 2697 additions and 0 deletions

View file

@ -0,0 +1,486 @@
# 代码库全景:从顶层到最深处
> 这篇文档是一个**递归的导游**。我们从顶层文件开始,逐层深入每个重要的目录,直到粒度太细而没有学习价值为止。每个章节都注明关键文件,你可以随时打开源代码对照。
---
## 顶层:核心文件
`/home/user/claude-code/` 的根目录下,以下文件是系统的脊梁:
| 文件 | 行数 | 用途 |
|------|------|------|
| `main.tsx` | ~4700 | CLI 入口React + Ink 终端渲染器 |
| `query.ts` | ~1700 | Query loop 状态机的完整实现 |
| `QueryEngine.ts` | 核心 | Query 执行的编排引擎 |
| `Tool.ts` | 核心 | Tool 的类型定义和接口规范 |
| `tools.ts` | 核心 | Tool 注册与初始化 |
| `context.ts` | 核心 | System prompt 和 context 的组装 |
| `commands.ts` | ~700 | 所有 slash command 的中央路由 |
**学习路线**`main.tsx``query.ts``Tool.ts` 可以了解 agent loop 的完整流程。
---
## `entrypoints/` — 入口层
系统支持多种启动方式:
| 子目录/文件 | 用途 |
|-----------|------|
| `cli.tsx` | 标准 CLI 入口React 组件树的根feature detection |
| `sdk/` | Anthropic SDK 集成(给程序员用) |
| `mcp.ts` | MCP (Model Context Protocol) 服务器 |
| `init.ts` | 初始化逻辑,首次运行配置 |
| `sandboxTypes.ts` | 沙箱相关的类型定义 |
**关键点**`cli.tsx` 做 feature flag 检测,决定如何初始化全局状态。
---
## `query/` — Query Loop 配置和支撑
这个目录下的文件都是 `query.ts` 的辅助组件:
| 文件 | 用途 |
|------|------|
| `config.ts` | Query 配置构建器,参数组合 |
| `tokenBudget.ts` | Token budget 追踪和强制执行 |
| `stopHooks.ts` | Stop/interrupt 处理 |
| `deps.ts` | Query 依赖注入 |
**最重要的**`tokenBudget.ts` 控制着"超限时触发 compaction"的逻辑。
---
## `tools/` — 所有 Tool 实现40+ 个)
这个目录有 40 多个子目录,每个代表一个 Tool。核心的几个
### 必读:
| Tool | 文件 | 用途 |
|------|------|------|
| `AgentTool/` | 子 agent 的产卵和管理 | 核心,递归往下 |
| `FileReadTool/` | 读文件 | 基础 |
| `FileEditTool/` | 编辑文件 | 基础 |
| `FileWriteTool/` | 写文件 | 基础 |
| `BashTool/` | 执行 shell 命令 | 基础 |
| `GlobTool/` | 文件匹配 | 基础 |
| `GrepTool/` | 内容搜索 | 基础 |
| `WebFetchTool/` | 获取网页 | 基础 |
| `AskUserQuestionTool/` | 询问用户 | 权限边界 |
| `TodoWriteTool/` | 写 todo list | 后台任务 |
### `AgentTool/` — 深递归
这是最复杂的 Tool用来 spawn 子 agent
```
AgentTool/
├── AgentTool.tsx [233 KB] 主逻辑agent 生命周期
├── runAgent.ts 执行 agent 的主函数
├── forkSubagent.ts fork 一个新 agent
├── resumeAgent.ts 恢复之前的 agent
├── loadAgentsDir.ts 从 ~/.agents/ 加载 agent 定义
├── built-in/ 内置 agent
│ ├── generalPurposeAgent.ts
│ ├── planAgent.ts
│ ├── exploreAgent.ts
│ └── ... (其他 5 个)
├── agentMemory.ts Agent 记忆快照
├── agentColorManager.ts Agent 的颜色分配(便于输出区分)
└── ... (其他支撑文件)
```
**关键概念**Agent 可以是 local (in-process) 或 remote (CCR),使用 `AsyncLocalStorage` 隔离上下文。
---
## `services/` — 业务逻辑服务
这个目录是 Claude Code 的"心脏"
### `tools/` — Tool 执行编排
```
services/tools/
├── StreamingToolExecutor.ts [核心] 并发 tool 执行,结果缓冲
├── toolOrchestration.ts Tool 调度和权限检查
├── toolExecution.ts 单个 tool 调用的执行
├── toolHooks.ts Tool 执行前/后的 hook
└── ...
```
**最重要**`StreamingToolExecutor.ts` 实现了"并发执行但顺序输出结果"的逻辑。
### `autoDream/` — 自动记忆压缩
```
services/autoDream/
├── autoDream.ts [11 KB] Dream 流程3 个触发门 + 4 个阶段
├── consolidationPrompt.ts [核心] 告诉 Claude 怎么压缩 memory
├── consolidationLock.ts 防并发的 lock 机制
└── config.ts Dream 参数配置
```
**关键点**Dream 有三个触发条件门24h 时间 + 5+ 个 session + 拿到 lock。
### 其他重要服务
| 目录 | 用途 |
|------|------|
| `api/` | Claude API 调用token 计算usage 追踪 |
| `mcp/` | MCP 协议服务器的实现 |
| `analytics/` | 事件上报、GrowthBook feature flags |
| `plugins/` | 插件系统 |
---
## `utils/permissions/` — 权限系统(深递归)
这是整个 agent sandbox 的防线,共 20+ 个文件,总计 300+ KB
```
utils/permissions/
├── permissions.ts [52 KB] 核心决策逻辑
├── yoloClassifier.ts [52 KB] ML 自动审批分类器
├── filesystem.ts [62 KB] 文件系统安全规则
├── PermissionMode.ts Permission mode 枚举
├── denialTracking.ts 追踪拒绝次数circuit breaker
├── pathValidation.ts 路径安全Unicode normalization 等)
├── permissionRuleParser.ts 解析 "Bash(git *)" 这样的规则
├── classifierDecision.ts 分类器决策包装
├── bashClassifier.ts Bash 命令的特殊规则
├── permissionExplainer.ts 用 LLM 解释权限决定
└── ... (其他辅助)
```
**核心 pipeline**mode → rules → YOLO classifier → prompt user
---
## `utils/` — 其他公用工具(部分列举)
```
utils/
├── permissions/ [见上面的深递归]
├── messages/
│ ├── mappers.ts SDK 消息格式转换
│ ├── systemInit.ts 系统初始化消息构建
│ └── ...
├── settings/ 设置加载和应用
├── queryHelpers.ts Query 辅助函数
├── undercover.ts 隐藏内部代号Capybara, Tengu 等)
├── abortController.ts Abort signal 管理
├── agentContext.ts Agent 上下文 getter
├── api.ts API 工具函数
├── hooks/ React hooks (不是本目录)
├── ... (数十个其他)
```
**学习重点**`permissions/` 最复杂,其次是 `messages/``settings/`
---
## `coordinator/` — 多 Agent 协调
```
coordinator/
└── coordinatorMode.ts [19 KB] 唯一的文件,包含完整 coordinator 系统提示
功能:启用 CLAUDE_CODE_COORDINATOR_MODE=1 时激活,引入 4 个阶段:
research → synthesis → implementation → verification
```
**关键点**Worker 通过 `<task-notification>` XML 向 coordinator 报告进度。
---
## `tasks/` — 后台任务管理
```
tasks/
├── LocalAgentTask/ 本地 agent 任务追踪
├── RemoteAgentTask/ 远程 agent 任务追踪CCR
├── LocalMainSessionTask.ts 主 session 的任务
├── LocalShellTask/ Shell 任务tmux/iTerm2
├── InProcessTeammateTask/ In-process teammate 任务
├── DreamTask/ Memory dream 任务
├── stopTask.ts 停止任务的逻辑
├── types.ts Task 的类型定义
└── pillLabel.ts 任务标签UI 展示)
```
**核心概念**:每个后台任务都有生命周期追踪。
---
## `state/` — React 状态管理
```
state/
├── AppState.tsx React 状态类型定义
├── AppStateStore.ts [21 KB] Zustand-like 状态存储
├── onChangeAppState.ts 状态变化的 handler
├── selectors.ts 状态选择器
├── store.ts 存储初始化
└── teammateViewHelpers.ts Teammate 视图辅助
```
**模式**:这是 Claude Code 的全局状态树UI 所有数据都来自这里。
---
## `hooks/` — 80+ 个 React Hook
重要的:
| Hook | 用途 |
|------|------|
| `useCanUseTool.tsx` | [40 KB] 权限检查 Hook频繁调用 |
| `useGlobalKeybindings.tsx` | [31 KB] 全局键盘快捷键 |
| `useTypeahead.tsx` | [212 KB] 自动补全建议 |
| `useVoice.ts` | [45 KB] 语音输入集成 |
| `useInboxPoller.ts` | [34 KB] 后台轮询消息 |
| `useArrowKeyHistory.tsx` | [34 KB] 历史导航(上/下箭头) |
| `useReplBridge.ts` | [115 KB] REPL 集成 |
**学习策略**:跳过大部分 hook只深入 `useCanUseTool``useTypeahead`
---
## `bootstrap/state.ts` — 全局启动状态
```
bootstrap/
└── state.ts [56 KB]
```
内容:
- 当前 session ID 和持久化 flag
- 用户类型ant/public
- Feature flag 缓存
- KAIROS mode 检测
- 全局配置状态
**作用**:在 App 启动的最早时刻初始化所有全局状态。
---
## `cli/` — 终端输出和格式化
```
cli/
├── print.ts [212 KB] 终端渲染引擎
├── structuredIO.ts NDJSON 和结构化输出
├── handlers/ 命令特定的输出 handler
├── transports/ 多种输出后端stdout, HTTP 等)
├── remoteIO.ts 远程 IO
└── ... (其他)
```
**关键**`print.ts` 是整个 Claude Code 的"输出黑洞",所有文本最终都通过这里。
---
## `context/` — React Context Provider
```
context/
├── QueuedMessageContext.tsx 消息队列
├── mailbox.tsx Agent 邮箱inter-agent 消息)
├── modalContext.tsx Modal 对话框
├── notifications.tsx 通知系统
├── overlayContext.tsx 浮层上下文
├── promptOverlayContext.tsx 提示浮层
├── stats.tsx 统计数据
└── voice.tsx 语音上下文
```
**作用**:提供 React component tree 范围内的全局上下文。
---
## `memdir/` — 用户 Memory 管理
```
memdir/
├── memdir.ts [21 KB] 核心 memory 目录管理
├── memoryScan.ts 扫描 memory 文件
├── memoryTypes.ts Memory 文件的类型
├── memoryAge.ts Memory 的年龄计算
├── paths.ts Memory 文件路径管理
├── teamMemPaths.ts Team memory 路径multi-agent
└── findRelevantMemories.ts 查找相关 memory
```
**用途**:管理 `~/.claude/memory/` 目录,存储用户的持久化知识。
---
## `constant/` — 常量和系统提示
```
constant/ (也被称为 constants/)
├── system.ts Base system prompt
├── systemPromptSections.ts System prompt 的可组装部分
├── cyberRiskInstruction.ts 安全指导Safeguards team 维护)
├── betas.ts API beta features 列表
├── messages.ts 常见消息文本
├── prompts.ts 各种 prompt 模板
└── ... (其他常量)
```
**关键**System prompt 是模块化的,不同 feature 会添加不同的部分。
---
## `bridge/` — 与 claude.ai 的 JWT 连接
```
bridge/
├── bridgeApi.ts 与 claude.ai 的通信
├── bridgeMessaging.ts 消息协议
├── remoteBridgeCore.ts 远程 bridge 核心
├── createSession.ts 创建远程 session
├── jwtUtils.ts JWT 令牌工具
├── trustedDevice.ts 受信设备管理
└── ... (10+ 个其他)
```
**用途**BRIDGE_MODE 时CLI 可以通过 claude.ai 的 WebUI 远程控制。
---
## `remote/` — 远程 Session 管理
```
remote/
├── RemoteSessionManager.ts 远程 session 生命周期
├── SessionsWebSocket.ts WebSocket 连接管理
├── remotePermissionBridge.ts 权限同步
├── sdkMessageAdapter.ts SDK 消息适配
└── ... (其他)
```
**用途**:支持多个远程客户端同时连接到同一个服务。
---
## `entrypoints/sdk/` — SDK 集成
```
entrypoints/sdk/
├── agentSdkTypes.ts 给外部程序使用的类型
└── (其他 SDK 特定代码)
```
**用途**:让程序员可以用代码调用 Claude Code而不只是 CLI。
---
## `components/` — React UI 组件
```
components/
├── App.tsx 顶层 app 组件
├── AgentProgressLine.tsx Agent 进度显示
├── BaseTextInput.tsx 文本输入基础
├── ... (100+ 个其他组件)
```
**特点**:使用 Ink 库在终端中渲染 React 组件。
---
## `commands/` — Slash 命令88 个)
```
commands/
├── commit.ts / commit-push-pr.ts Git 相关命令
├── agent.ts Agent 管理命令
├── task.ts, tasks.ts 任务管理
├── config.ts 配置命令
├── memory.ts, memory-dream.ts Memory 管理
├── session.ts Session 管理
├── autofix-pr.ts PR 自动修复
├── ... (80+ 个其他)
```
**特点**:每个 command 都可以定制输出格式,支持 structured output (NDJSON)。
---
## 递归粒度停止条件
以下类型的文件不再递归深入:
- **单功能工具函数** (<100 做一件明确的事) `array.ts`, `api.ts` 的某些导出
- **简单 enum/type 定义**:如 `types.ts`, `*.d.ts`
- **单个小的 React Hook**:如 `useAfterFirstRender.ts`
- **Utility 映射和适配器**:如 message format converter
这些文件的学习价值在于"知道它存在",而不是理解其内部细节。
---
## 学习路线建议
1. **快速扫描**:从 README 开始,看一眼这个全景。
2. **焦点学习**:根据你的兴趣选择深入:
- 想理解 agent loop? → `query.ts`, `QueryEngine.ts`
- 想理解 tool 系统? → `Tool.ts`, `tools/`, `services/tools/`
- 想理解权限? → `utils/permissions/`
- 想理解多 agent? → `tools/AgentTool/`, `coordinator/`
3. **对照原文**:打开相应的源文件,参照本文的路线图逐个浏览。
---
## 核心数据流
```
User Input
entrypoints/cli.tsx (React 初始化)
query.ts (Query Loop: QUERY → TOOL_USE → RESULT)
Tool.ts & services/tools/ (Tool 执行)
utils/permissions/ (权限检查) + BashTool/FileEditTool/... (实际执行)
queryHelpers + messages (结果处理)
state/ (状态更新)
components/ + cli/print.ts (UI 渲染)
User sees output
```
---
## 关键文件大小排名
```
Top 15 largest files (最值得读):
1. query.ts ~1700 行
2. services/tools/StreamingToolExecutor.ts
3. utils/permissions/permissions.ts ~52 KB
4. utils/permissions/yoloClassifier.ts ~52 KB
5. utils/permissions/filesystem.ts ~62 KB
6. hooks/useTypeahead.tsx ~212 KB
7. cli/print.ts ~212 KB
8. tools/AgentTool/AgentTool.tsx ~233 KB
9. bootstrap/state.ts ~56 KB
10. state/AppStateStore.ts ~21 KB
11. services/autoDream/autoDream.ts ~11 KB
12. coordinator/coordinatorMode.ts ~19 KB
13. hooks/useCanUseTool.tsx ~40 KB
14. hooks/useGlobalKeybindings.tsx ~31 KB
15. hooks/useInboxPoller.ts ~34 KB
```
先读前 5 个,会 cover 80% 的系统复杂性。

View file

@ -0,0 +1,279 @@
# 核心 Query LoopAgent 的心脏
## 为什么需要 Loop?
想象一个简单的"一问一答"的 AI用户问 → API 回复 → 完成。但 agent 不一样:
- 用户问:"帮我修复 bug"
- Agent 读取文件 → 分析问题 → 尝试修复 → 运行测试 → 测试失败 → 再次修改 → 再次测试 → 成功
这个过程需要**多轮往返**,每一轮中 Agent 决定"下一步应该执行哪个 tool"。这就是 **Query Loop**
---
## 核心概念:状态机
Query loop 本质上是一个**状态机**,在以下几个状态间循环:
```
START
QUERY (提交 prompt 给 Claude API)
TOOL_USE (API 返回 tool_use block包含 tool 名 + 输入)
执行 Tool (BashTool, FileEditTool, 等等)
RESULT (Tool 返回结果)
提交结果给 Claude (作为 user 消息)
QUERY (继续循环)
END (API 返回 stop_reason="end_turn" 或其他终止条件)
```
---
## 代码位置
关键文件:`query.ts` (~1700 行)
这个文件包含:
- Loop 的完整实现 (使用 `AsyncGenerator`)
- 状态跟踪
- Tool 调用的编排
- Streaming 支持
- Compaction 触发
- 错误处理和恢复
你应该从 `query.ts` 的函数签名开始:
```typescript
export function* query(...): AsyncGenerator<QueryMessage | QueryEvent, ...>
```
它返回一个 `AsyncGenerator`,每次 `yield` 都是一个"事件"API 消息、tool 调用、错误等)。
---
## Loop 的生命周期
### 阶段 1: 初始化
当你调用 `query()` 时:
- `messages` 数组初始化(包含 system prompt + 历史消息)
- `token_budget` 初始化(计算剩余 token
- Compaction 检查(如果历史太长,自动压缩)
- Abort controller 初始化(支持中途停止)
### 阶段 2: 发送 Prompt
```typescript
// 构建消息数组
let messages = [system_prompt, ...history];
// 调用 Claude API使用缓存
let response = await claude.messages.create({
messages,
system: system_prompt,
max_tokens: remaining_budget,
stream: true, // 重要:流式输出
...config
});
```
Streaming 很关键——API 会逐步返回:
1. First chunk: 可能包含 `message.start`
2. Content blocks: `text_delta`, `tool_use` block 开始
3. Stop reason: 表示本轮结束
### 阶段 3: 处理 Streaming
当 API 返回 `tool_use` block 时loop 收集完整的参数,然后:
```
接收 tool_use block
验证 tool 名称是否存在
检查权限 (useCanUseTool hook)
执行 tool (StreamingToolExecutor)
Yield tool 结果给消费者
添加到 messages 作为 user 消息
```
### 阶段 4: 工具执行
Tool 执行由 `services/tools/StreamingToolExecutor.ts` 处理:
- **并发**:最多 5 个 tool 同时运行
- **顺序输出**:结果按调用顺序返回(即使执行顺序不同)
- **Sibling abort**:一个失败会 cancel 兄弟 tool
### 阶段 5: 检查终止条件
每一轮后loop 检查是否应该继续:
```
stop_reason == "end_turn"? → 正常结束
stop_reason == "max_tokens"? → Token 超限,触发 compaction
consecutive_errors >= 3? → 连续错误,放弃
max_turns_reached? → 超过最大轮数
abort_signal? → 用户中断
```
---
## Token BudgetContext Window 的有限性
Context window 是有限的(比如 200K tokens。这里的逻辑
```
available_tokens = context_window_size - system_prompt_tokens - reserved_buffer
每一轮后:
usage = response.usage (API 返回的实际使用)
remaining = available_tokens - usage.input_tokens - usage.output_tokens
if remaining < threshold:
触发 compaction (压缩历史)
if remaining < min_required:
拒绝继续,返回错误
```
**Threshold** 通常设得比较激进,比如还剩 20% 时就开始压缩,以避免在真的耗尽时措手不及。
相关文件:`query/tokenBudget.ts`
---
## Compaction自动历史压缩
当 token 预算吃紧时loop 会自动压缩历史:
1. **Analyzer phase**: 扫描历史消息,找出"长输出消息"(比如代码块)
2. **Prioritize**: 对话越早,压缩优先级越高
3. **Compress**: 使用 Claude API 总结这段历史,生成 200 行以内的 summary
4. **Replace**: 把原始的 20 条消息替换为 1 条 summary 消息
这保证了 loop 可以持续运行,即使初始历史很长。
---
## Streaming 与 Tool Call 的交织
这是一个复杂的地方。API 可能返回:
```
message {
content: [
{ type: "text", text: "让我先读文件..." },
{ type: "tool_use", id: "...", name: "FileReadTool", ... },
{ type: "text", text: "现在我看到问题了..." },
{ type: "tool_use", id: "...", name: "FileEditTool", ... },
]
}
```
所以一条消息中可能**混合了文本和 tool call**。Loop 需要:
- 收集每个 tool_use block 的完整输入参数(可能分段到达)
- 在 tool_use 结束时立即执行(不等待消息全部返回)
- 继续接收后续的文本或 tool call
- 当消息完全接收后,将所有 tool 结果作为一条 user 消息发回
这使得 **interleaved thinking** 成为可能:模型可以一边思考一边调用工具。
---
## Design Decision 专栏
### 为什么用 `AsyncGenerator` 而不是 callback?
不好的设计callback
```typescript
query(messages, {
onMessage: (msg) => {...},
onToolCall: (tool, input) => {...},
onError: (err) => {...},
onProgress: (progress) => {...},
})
```
问题:
- 4 种不同的 callback难以理解执行顺序
- 消费者需要管理状态机来追踪"当前在哪个阶段"
- 测试困难
**更好的设计**Generator
```typescript
for await (const event of query(messages, config)) {
if (event.type === 'message') {...}
else if (event.type === 'tool_use') {...}
else if (event.type === 'error') {...}
}
```
优点:
- 单一的 `for await...of` 循环,清晰的顺序
- 每个 event 都是一个联合类型,编译器可以帮你检查
- 可以在 loop 中添加复杂的条件逻辑abort, timeout 等)
- 易于测试(可以模拟一个生成事件的 generator
Generator 让"一系列事件"变得一级公民。
### 为什么需要 Compaction?
不做 compaction
- 100 轮 agent 工作后messages 数组有几千条消息
- 提交给 API 时prompt cache hit 率下降(因为前缀不再是"静态部分"
- Token 浪费在重复的对话历史上
做 compaction
- 定期将"已经达成共识的历史部分"总结成一条消息
- 保持 prompt cache 的"静态前缀"有效
- 节省 token延长 agent 能工作的轮数
---
## 常见误解
**误解 1**"Query loop 就是不断问 Claude"
实际Loop 实现的是一个**两层状态机**
- 外层Claude API 的请求/响应循环
- 内层Tool 的执行和结果收集
tool 执行完全不涉及 Claude API 的额外调用(除非 tool 本身调用 API
**误解 2**"Compaction 会丢失信息?"
实际Compaction 使用 Claude 自己来总结历史,所以关键信息被保留。丢失的只是"冗余的解释"或"中间尝试",这些对后续工作没用。
**误解 3**"Token budget 追踪是精确的?"
实际API 返回的 `usage` 是经过舍入的(某些模型),所以我们的估计可能有 ±5% 的误差。这就是为什么我们用激进的阈值20% 时就开始压缩)而不是等到 100% 才反应。
---
## 关键要点
1. **Query loop 是异步生成器**yield 事件流而不是回调
2. **循环的关键状态转移**QUERY → TOOL_USE → RESULT → QUERY
3. **Token budget 必须主动管理**compaction 是自动压缩的关键
4. **Streaming 支持 interleaved thinking**,一条消息中可以混合文本和 tool call
5. **Compaction 会定期触发**,保持 history 可控,并维护 prompt cache 的有效性
---
## 深入阅读
- `query.ts`:完整 loop 实现(必读)
- `query/tokenBudget.ts`Token 追踪逻辑
- `services/tools/StreamingToolExecutor.ts`Tool 执行编排
- `query/stopHooks.ts`:终止条件检查
下一步:去了解 **Tool 系统**是如何设计的,使得这个 loop 可以灵活地执行任意 tool。

View file

@ -0,0 +1,378 @@
# Tool 系统Agent 影响世界的手段
## 为什么需要 Tool?
如果 Claude 只能回复文字,它就只是个聊天机器人。要让 Claude **执行动作**(读文件、改代码、运行命令),需要一个 **Tool 接口**
Tool 系统解决的问题:
1. 如何告诉 Claude "有哪些动作可用"
2. 如何验证 Claude 的请求是否合法(权限检查)?
3. 如何让多个 tool 并发执行,但结果保持有序?
4. 如何在 tool 执行失败时恢复?
---
## 核心概念Tool Interface
一个 `Tool` 必须满足这个接口(`Tool.ts`
```typescript
interface Tool {
// 1. 模型识别 tool 的名称
name: string // 如 "FileReadTool"
// 2. 模型理解 tool 的用途(出现在 system prompt 中)
description: string
// 3. 模型生成的参数必须符合这个 schema
inputSchema: JSONSchema // 如 { type: "object", properties: { path: ... } }
// 4. 执行 tool 的函数
call(input: ToolInput): AsyncGenerator<ToolResult>
// 5. 指示这个 tool 是否"安全"(后文详述)
isSafe?: boolean
// 6. 内部使用,不会发给模型
internalMetadata?: { ... }
}
```
每个字段的意义:
### `name``description`
这两个被送到 Claude API出现在 system prompt 的 tool 列表中:
```
Available tools:
1. FileReadTool
Read the contents of a file from the filesystem
Parameters: { path: string }
2. BashTool
Execute a bash command in the user's shell
Parameters: { command: string }
```
Claude 看到这个列表,学会了"我可以用 FileReadTool 来读文件"。
### `inputSchema`
这是 JSON Schema 格式,例如:
```typescript
{
type: "object",
properties: {
path: {
type: "string",
description: "Absolute file path"
}
},
required: ["path"]
}
```
Claude 会**严格遵守这个 schema**(模型已训练)。例如,如果你要求 `path` 必须是字符串Claude 就不会发来 `{path: 123}`
### `call(input)`
这是 tool 的实现。它返回一个 `AsyncGenerator`,每次 `yield` 都是一个结果。例如:
```typescript
async *call(input: { path: string }) {
// 逐步产生结果(支持 streaming
yield { type: "start", message: "Opening file..." }
yield { type: "content", data: file_content }
yield { type: "done", lines_read: 100 }
}
```
为什么是 generator因为某些 tool如 BashTool的输出可能很长需要**流式返回**。
### `isSafe` 和权限
某些 tool 被标记为 `unsafe`
- `BashTool` ← 可以执行任意命令,危险
- `FileEditTool` ← 可以修改文件,需要权限检查
- `FileReadTool` ← 只读,相对安全
Unsafe tool 在执行前**必须通过权限检查**。
---
## Tool 注册与过滤
`tools.ts` 中,所有 tool 被注册到一个中央注册表:
```typescript
const ALL_TOOLS: Tool[] = [
AgentTool,
BashTool,
FileReadTool,
FileEditTool,
...,
]
```
然后根据用户权限和配置进行**过滤**
```typescript
// 仅公开 tool
const PUBLIC_TOOLS = ALL_TOOLS.filter(t => !t.internalOnly)
// 仅 ant 用户可用
const ANT_ONLY_TOOLS = [REPL_TOOL, CONFIGTOOL, ...]
```
这样,模型只会看到当前环境允许的 tool 清单。
---
## Tool 执行 Pipeline
从"模型调用 tool"到"返回结果"的完整流程:
```
Query Loop 收到 tool_use block
├─ Tool 名: "FileEditTool", Input: {path: "...", newContent: "..."}
├─ 1. 验证 Tool 存在 ✓
├─ 2. 权限检查 (utils/permissions/)
│ - Mode: auto 还是 default?
│ - 规则匹配? (如 "FileEdit(/src/*.ts)")
│ - YOLO 分类器? (是否 auto-approve)
│ - 如果拒绝 → 返回 permission_denied error
├─ 3. 执行 Tool
│ - 调用 tool.call(input)
│ - 得到 AsyncGenerator
│ - 逐个 yield 结果
├─ 4. 收集结果
│ - 内容可能很长,缓冲到内存
│ - 或流式返回给 UI
└─ 5. 发送给 Claude
- 组合成 user 消息: "Tool result: ..."
- 继续 Query Loop
```
相关文件:
- 权限检查:`utils/permissions/permissions.ts`
- 执行编排:`services/tools/toolOrchestration.ts`
- 流式执行:`services/tools/StreamingToolExecutor.ts`
---
## 并发执行:多个 Tool 同时运行
一条 Claude 消息中可能包含多个 `tool_use` block
```
Claude:
1. 读文件 A (tool_use id=1)
2. 读文件 B (tool_use id=2)
3. 读文件 C (tool_use id=3)
```
这三个 tool 可以**并发执行**,但有几个复杂性:
### Concurrency Gate
同时最多 5 个 tool 运行(可配置):
```typescript
const CONCURRENCY_LIMIT = 5
// 队列管理
const queue = [tool_1, tool_2, tool_3, ...]
while (queue.length > 0) {
const batch = queue.splice(0, CONCURRENCY_LIMIT)
const results = await Promise.all(
batch.map(tool => tool.call(...))
)
// 处理结果
}
```
### In-Order Emission
虽然执行可能乱序,但**结果必须按调用顺序返回**
```
调用顺序: tool_1, tool_2, tool_3
执行时序: tool_2 ✓ (快速)
tool_1 ✓ (较慢)
tool_3 ✓
返回顺序: tool_1 结果 → tool_2 结果 → tool_3 结果
为什么? 因为 Claude 期望看到它调用的顺序被保留
```
实现方式:使用**结果缓冲**
```typescript
const results = new Map() // id → result
let nextIdToEmit = 0
for each completed tool {
results.set(tool.id, tool.result)
// 检查是否可以按顺序 emit
while (results.has(nextIdToEmit)) {
yield results.get(nextIdToEmit)
nextIdToEmit++
}
}
```
### Sibling Abort
如果一个 tool 失败了,它的**兄弟 tool 会被立即取消**
```
tool_1 → 运行中
tool_2 → 运行中
tool_3 → 运行中
tool_2 抛异常 ✗
立即 abort tool_1 和 tool_3
返回错误,整个批次失败
```
这是一个**快速失败**策略:不要继续浪费时间在其他 tool 上,直接告诉 Claude 出了问题。
相关文件:`services/tools/StreamingToolExecutor.ts`
---
## Safe vs Unsafe Tool Classification
Tool 分两类:
### Safe Tool自动执行
```
FileReadTool ← 只读,没有副作用
WebFetchTool ← 只是下载网页
GlobTool ← 只是列文件
GrepTool ← 只是搜索内容
```
这些可以在 `auto` permission mode 下自动执行,不需要用户确认。
### Unsafe Tool需要权限检查
```
BashTool ← 可能执行任意命令
FileEditTool ← 可能修改重要文件
FileWriteTool ← 可能覆盖数据
AgentTool ← 可能 spawn 新 agent
TaskCreateTool ← 可能创建后台任务
```
这些在执行前必须通过 `utils/permissions/` 的检查。
---
## Design Decision 专栏
### 为什么 Tool 返回 `AsyncGenerator` 而不是 `Promise<string>`?
不好的设计:
```typescript
call(input): Promise<string> {
// 必须等待整个操作完成后才返回
const result = await readFile(input.path)
return result // 10 MB 的代码文件?等吧
}
```
问题:
- 大文件时UI 一直卡住,看不到进度
- Claude 看不到中间步骤
更好的设计:
```typescript
call(input): AsyncGenerator<ToolResult> {
yield { type: "progress", message: "Reading..." }
const content = await readFile(input.path)
yield { type: "content", data: content } // 分块返回
yield { type: "complete", lines: 100 }
}
```
优点:
- UI 可以**立即显示**进度消息
- 长操作时用户看得到"不是卡住了,是在处理"
- 便于 debug中间消息可以被记录
Generator 把**长时间操作的透明度**提升为一级特性。
### 为什么要保持结果顺序?
如果乱序返回:
```
Claude 说:读 A, 读 B, 读 C
我们返回B 的结果, C 的结果, A 的结果
```
Claude 会困惑:"我要的 A 呢?"
所以必须:
```
Claude 说:读 A, 读 B, 读 C
我们返回A 的结果, B 的结果, C 的结果
```
这样 Claude 可以正确关联"我的第一个请求的响应是这个"。
---
## 常见误解
**误解 1**"Tool 就是函数调用?"
实际Tool 是一个**完整的接口**,包括:
- 模型可见的名称和描述
- 输入规范schema
- 权限检查
- 执行编排
- Streaming 支持
函数调用只是内部实现的一部分。
**误解 2**"Tool 是 OpenAI function calling 的翻版?"
实际Claude Code 的 Tool 设计更深层:
- 对权限的原生支持OpenAI 需要自己实现)
- 对并发执行的编排
- 对长时间操作的 streaming 支持
- 更细粒度的安全分类
**误解 3**"所有 tool 都必须是同步的?"
实际Tool 是异步的(`AsyncGenerator`),可以支持任意长的操作(包括网络请求、文件 I/O
---
## 关键要点
1. **Tool interface 是客户端与 Agent 的合约**包含名称、描述、schema、执行函数
2. **Permission 检查是 tool 执行前的网关**,决定是否允许
3. **并发执行最多 5 个 tool**,但结果必须按调用顺序返回
4. **Sibling abort 是快速失败**,一个错误就停止兄弟 tool
5. **AsyncGenerator 使 tool 支持 streaming**UI 可见进度model 可见中间步骤
---
## 深入阅读
- `Tool.ts`Tool interface 定义
- `tools.ts`Tool 注册和过滤
- `services/tools/StreamingToolExecutor.ts`:并发执行编排
- `utils/permissions/permissions.ts`:权限检查
下一步:去了解**多 Agent 协调**,看看当 Claude 想要 spawn 一个子 agent 时(通过 `AgentTool`),系统如何支持这个。

View file

@ -0,0 +1,351 @@
# 多 Agent 协调:并行与分解
## 为什么需要多个 Agent?
有些任务太复杂,一个 agent 力不从心:
- 用户说:"帮我把代码库从 TypeScript 迁到 Rust"
- 一个 agent 从第一个文件开始,改 5 个小时后context 满了
更好的方式:**分解任务**
- Coordinator agent 解析任务、制定计划
- Worker agent 1 并行处理"转换 utils 模块"
- Worker agent 2 并行处理"转换 API 层"
- Worker agent 3 并行处理"转换 UI 组件"
- Coordinator 总结结果、验证一致性
这就是 **多 agent 协调**
---
## 核心概念Agent 类型
Claude Code 支持多种 agent
### 1. Local Agent进程内
```typescript
// 在当前进程中 spawn 一个子 agent
const subAgent = await spawnAgent({
type: "local",
agentDef: generalPurposeAgent,
initialMessage: "帮我测试这个函数"
})
```
特点:
- 共享内存(同一个 Node.js 进程)
- 上下文隔离(通过 `AsyncLocalStorage`
- 无网络开销
- 最快的交互
### 2. Remote Agent云容器
```typescript
const remoteAgent = await spawnAgent({
type: "remote",
agentDef: exploreAgent,
initialMessage: "搜索 codebase 中的 bug"
})
```
特点:
- 在 CCRCloud Container Runtime中运行
- 独立的资源池
- 支持长时间运行(最多 30 分钟)
- 网络传输延迟,但隔离度最高
### 3. Forked Agent进程派生
```typescript
const forkedAgent = spawnForkedAgent({
agentScript: "./my-agent.ts",
args: ["--mode", "debug"]
})
```
特点:
- 创建新的 Node.js 子进程
- 完全隔离的 V8 引擎
- 支持 CPU 密集任务(不会阻塞主线程)
### 4. In-Process Teammate进程内同伴
```typescript
const teammate = await createTeam({
name: "research-squad",
agents: [agent1, agent2, agent3],
strategy: "parallel"
})
```
特点:
- 多个 agent 同时运行
- 共享邮箱(互相发消息)
- 同步协调,不需要网络
---
## Agent 的 Spawning 和隔离
### 怎样 Spawn 一个 Agent?
通过 `AgentTool` (文件:`tools/AgentTool/AgentTool.tsx`)
```typescript
// Claude 调用 AgentTool
Agent: "让我 spawn 一个 explore agent 来搜索代码库"
Tool Call:
name: "AgentTool"
input: {
type: "spawn",
agentDef: "exploreAgent",
prompt: "搜索 utils/ 下的性能问题",
isolationMode: "local"
}
返回:
agentId: "abc123"
status: "running"
```
### 上下文隔离:`AsyncLocalStorage`
为什么需要隔离?子 agent 的全局状态不应该污染父 agent
```typescript
// 主 agent 的全局状态
const sessionId = "parent-session-id"
const permissions = "default"
// 在子 agent 中,这些应该被**覆盖**
asyncLocalStorage.run({
sessionId: "child-agent-id",
permissions: "bubble", // 特殊权限给 worker
memoryDir: "/tmp/child-memory"
}, () => {
runChildAgent() // 子 agent 看到的是覆盖后的值
})
```
实现:`AsyncLocalStorage` 是 Node.js 内置的上下文隔离机制。
---
## Coordinator 模式4 个阶段
启用 `CLAUDE_CODE_COORDINATOR_MODE=1`agent 进入 coordinator 模式。
### 阶段结构
```
用户请求: "帮我重构这个 monorepo"
[1] RESEARCH PHASE
Coordinator 说:"我需要理解项目结构"
Worker 1 探索 package.json + tsconfig
Worker 2 探索 src/ 目录结构
Worker 3 探索 dependencies 图
→ 输出: 项目现状报告
[2] SYNTHESIS PHASE
Coordinator 读 3 个 worker 的报告
Coordinator 说:"基于这些发现,我的计划是..."
→ 输出: 详细重构计划
[3] IMPLEMENTATION PHASE
Coordinator 把计划分解为任务
Worker A 修改 package.json
Worker B 更新 tsconfig
Worker C 调整 src/ 组织
→ 并行执行
[4] VERIFICATION PHASE
Coordinator 说:"让我验证改动"
Worker 运行测试
Worker 检查类型错误
→ 输出: 验证报告
完成Coordinator 总结全流程,返回最终结果
```
### 通信机制:`<task-notification>`
Worker 通过 XML 消息向 Coordinator 报告进度:
```xml
<task-notification>
<task-id>research-1</task-id>
<status>complete</status>
<summary>Found 3 monorepo packages</summary>
<details>
- Package A: React components
- Package B: Utils library
- Package C: CLI tool
</details>
</task-notification>
```
Coordinator 看到这个消息,就知道:
- Task "research-1" 完成了
- 发现了 3 个包
相关文件:`coordinator/coordinatorMode.ts`(包含完整的 system prompt大约 330 行)
---
## 共享 Scratchpad
Worker 可以在共享空间写临时数据feature gate: `tengu_scratch`
```typescript
// Worker 1 写入
await writeToScratchpad("project_structure.md", """
# Monorepo Structure
- packages/ui/
- components/
- styles/
""")
// Worker 2 读取
const structure = await readFromScratchpad("project_structure.md")
// Coordinator 读取
const allNotes = await listScratchpad()
```
优点:
- Worker 不需要把所有信息放在 user 消息中
- 大文件可以直接读取,不占用 context window
- 自然的"工作台"抽象
---
## Agent 内存和记忆
每个 agent 可以有自己的 memory
```
~/.claude/memory/
├── team-memory/ # 团队共享
│ ├── shared-findings.md
│ └── progress.md
├── agent-abc123/ # Agent 特定
│ ├── MEMORY.md # Agent 的长期知识库
│ └── session-transcript.log
```
**Team Memory Sync** (`services/teamMemorySync/`) 确保:
- 所有 agent 可以读取共享的 findings
- 不会产生数据竞争(使用 file lock
---
## Design Decision 专栏
### 为什么用 `<task-notification>` XML 而不是结构化 IPC?
不好的设计(二进制 IPC
```
Worker 发送: MessageType::RESEARCH_COMPLETE {
task_id: 0x123,
status: 0x02,
payload: [0xAB, 0xCD, ...]
}
```
问题:
- Claude 看不懂二进制协议
- 如果需要调试,你必须手动反序列化
- 扩展协议时需要修改版本号
更好的设计XML
```
<task-notification>
<task-id>abc123</task-id>
<status>complete</status>
<summary>Found 100 bugs</summary>
</task-notification>
```
优点:
- Claude 可以**直接阅读和生成**这种格式
- 调试时在日志中直接看到可读的消息
- 自文档化XML 标签说明意义)
- 易于扩展(添加新字段不破坏旧系统)
**Text is the protocol** 的又一例证。
### 为什么 Coordinator 要等 Research 完全结束才进行 Synthesis?
如果不等(流式处理):
```
Worker 1 报告 → Coordinator 开始 synthesis
Worker 2 报告(晚到)→ Coordinator 需要重新 synthesis
Worker 3 报告(更晚)→ 又得重新来一遍
```
问题:浪费 token 和时间在重复的 synthesis。
如果等待(阶段隔离):
```
所有 Worker 完成 Research
Coordinator 一次性读完所有报告
一次 Synthesis不需要改
```
权衡:
- 阶段隔离时间线性4 个阶段顺序执行)
- 流式处理:时间可能更短(如果 worker 速度差异大),但 prompt 可能更长
Claude Code 选择阶段隔离,因为系统提示清晰度更重要。
---
## 常见误解
**误解 1**"Coordinator 也会执行 tool?"
实际Coordinator **只** spawn worker 和管理任务。所有的 tool 执行(读文件、改代码、运行命令)都在 worker 中进行。Coordinator 只是看消息、做决策、发指令。
**误解 2**"Worker 之间可以直接通信?"
实际Worker 通过 **Coordinator 中转**。这样 Coordinator 可以:
- 理解全局状态
- 决定优先级
- 在冲突时仲裁
直接 worker-to-worker 通信会导致难以追踪的依赖和死锁。
**误解 3**"多个 agent 肯定比单个快?"
实际:取决于任务:
- **可并行化**(如搜索 3 个不同目录):并行快
- **高度依赖**后续任务需要前序结果并行反而慢overhead
Claude Code 的 Coordinator 会选择合适的分解策略。
---
## 关键要点
1. **Agent 类型**local (in-process), remote (CCR 30min), forked, teammate
2. **上下文隔离**:通过 `AsyncLocalStorage` 分离全局状态
3. **Coordinator 模式**4 个阶段research → synthesis → implementation → verification
4. **Worker 通信**:通过 `<task-notification>` XMLCoordinator 中转
5. **Shared scratchpad**:大文件存储,不占 context window
---
## 深入阅读
- `tools/AgentTool/AgentTool.tsx`Agent spawning 逻辑
- `tools/AgentTool/forkSubagent.ts`Fork 实现
- `coordinator/coordinatorMode.ts`Coordinator system prompt 和阶段逻辑
- `services/teamMemorySync/`Team memory 同步
下一步:了解**权限系统**,看看 Agent 怎么知道"我可以执行什么 tool"。

View file

@ -0,0 +1,317 @@
# Permission 系统Agent 的沙箱
## 为什么需要权限管控?
想象 Claude 可以执行任意 bash 命令。用户说"帮我看看网站"Claude 可能:
- 合法地执行 `curl https://example.com`
- 也可能执行 `rm -rf /` ← 灾难
**权限系统的目标**
1. 阻止意外的危险操作
2. 提示用户"你确定吗?"
3. 在保留便利的同时维护安全
Claude Code 的 permission 系统是一个**多层防线**。
---
## 核心概念Permission Mode
系统支持多种权限模式,用户可以选择:
| Mode | 特点 | 场景 |
|------|------|------|
| `default` | 每次都问用户 | 不信任 agent想完全控制 |
| `auto` | ML 分类器自动审批 | 信任 agent想要流畅体验 |
| `acceptEdits` | 自动接受文件编辑(但仍然问危险操作) | 开发工作流(改代码 OK执行脚本要问 |
| `bypass` | 完全自动,不问(谨慎使用)| 内部测试、自动化流程 |
| `dontAsk` (aka `yolo`) | 拒绝所有非低风险操作 | 沙箱环境,最安全 |
| `plan` | 特殊模式:进入计划编辑阶段 | 用户审核计划后再执行 |
用户在启动时选择(环境变量或配置文件):
```bash
# 每次都问
claude-code --permission-mode default
# 自动审批
claude-code --permission-mode auto
# 最安全
claude-code --permission-mode dontAsk
```
相关文件:`utils/permissions/PermissionMode.ts`
---
## Permission 决策 Pipeline
当 agent 试图执行一个 tool 时,系统会问:"我应该允许吗?"
```
Agent 调用: BashTool { command: "npm test" }
├─ [1] Mode 检查
│ if mode == "bypass" → ✓ 允许,返回
│ if mode == "dontAsk" → ✗ 拒绝 Bash返回
├─ [2] 规则匹配
│ 检查用户配置的规则列表
│ 如: "Bash(git *)" → 允许 git 命令
│ "Bash(npm *)" → 允许 npm 命令
│ "Bash(*)" → 允许任意 bash (太宽)
│ 我们的命令 "npm test" 匹配 "npm *" → ✓
├─ [3] 风险分类
│ rule match → LOW 风险
│ 如果没匹配到规则,分类器决定风险等级
├─ [4] YOLO 分类器
│ 对于 MEDIUM 风险的操作:
│ - 如果被模型认为"明显安全",自动批准
│ - 否则需要用户确认
└─ [5] 最后手段:提示用户
用户选择: ✓ 允许 / ✗ 拒绝 / ⚠️ 改规则
```
相关文件:`utils/permissions/permissions.ts` (52 KB核心决策逻辑)
---
## 规则系统
用户可以配置规则,告诉 agent 什么是自动允许的。规则语法:
```
Bash(git *) # 允许任何 git 命令
Bash(npm install) # 只允许 npm install不允许 npm test
FileEdit(src/*.ts) # 允许编辑 src/ 下的 .ts 文件
FileRead(/*) # 允许读任何文件
```
规则支持 **glob 模式**`*` 通配符)。
### 规则分层
规则可以来自多个地方(优先级从高到低):
1. **CLI 参数**`--approve-file "FileEdit(src/*.ts)"`
2. **项目配置**`.claude/settings.json` 中的 `approvals`
3. **用户设置**`~/.claude/settings.json`
4. **系统默认**Claude Code 内置的基础规则
下层规则会被上层规则覆盖。
相关文件:`utils/permissions/permissionRuleParser.ts`, `utils/permissions/permissionsLoader.ts`
---
## YOLO 分类器ML 自动审批
对于**规则未覆盖的操作**system 用 YOLO 分类器("You Only Live Once")决定:
```
输入:
tool_name: "BashTool"
command: "npm test"
context: 当前任务是"修复 bug"
分类器说: "这个操作很像是开发者会做的正常事,批准"
结论: 自动允许,不问用户
```
分类器基于 ML 训练,考虑:
- Tool 类型BashTool 比 FileReadTool 高风险)
- 具体命令(`npm test``rm -rf` 安全)
- 当前上下文("修复 bug" vs "浏览网页"
相关文件:`utils/permissions/yoloClassifier.ts` (52 KB)
### 分类器的风险等级
```
LOW
→ 自动允许(不问用户)
→ 例FileReadTool, GlobTool, WebFetchTool
MEDIUM
→ 取决于 modeauto mode 用分类器default mode 问用户)
→ 例FileEditTool, BashTool (git 命令)
HIGH
→ 一般不自动允许(需要用户确认)
→ 例BashTool (rm -rf), FileWriteTool (覆盖重要文件)
```
---
## Denial TrackingCircuit Breaker
如果用户连续拒绝 agent 多次system 会自动**降级权限模式**
```
用户拒绝:
1. "BashTool" ← Denial #1
2. "FileEditTool" ← Denial #2
3. "BashTool" ← Denial #3
连续 3 次拒绝触发!
系统切换到 "default" mode后续所有操作都要问用户
这样防止:
- Agent 持续尝试被拒的操作
- 用户一直看到提示烦不胜烦
```
同时,系统还追踪**总拒绝数**
```
同一 session 中总共拒绝了 20 次
系统记录日志:"这个 session 决策质量差,建议用户审视任务"
```
相关文件:`utils/permissions/denialTracking.ts`
---
## 路径安全:防止路径遍历攻击
即使规则写得再好,攻击者也可能用**特殊编码**绕过:
```
规则: FileEdit(src/*.ts) ← 只允许 src/ 目录
攻击尝试:
path: "src/../../../etc/passwd" ← 试图逃逸
path: "src%2f..%2f..%2fetc%2fpasswd" ← URL 编码
path: "src/\u202e../../../etc/passwd" ← Unicode 隐藏字符
```
系统有多层防御:
1. **Path Normalization**
```
"src/../../../etc/passwd"
→ 规范化
→ "/etc/passwd"
→ 检查是否在 src/ 内? 否 → 拒绝
```
2. **URL Decoding**
```
"src%2f..%2f..%2fetc"
→ 解码
→ "src/../../etc"
→ 规范化 → 拒绝
```
3. **Unicode Normalization**
```
Unicode 隐藏字符也会被清理
```
相关文件:`utils/permissions/pathValidation.ts` (62 KB)
---
## Protected Files黑名单
某些文件**永远不会被自动编辑**
```
.gitconfig ← Git 配置,修改可能破坏工作流
.bashrc, .zshrc ← Shell 配置,修改可能卡住终端
.mcp.json ← MCP 配置,影响工具可用性
.claude.json ← Claude Code 自己的配置
```
这些文件即使通过了权限检查,也会再次提示用户确认。
---
## Design Decision 专栏
### 为什么需要 ML 分类器?
如果只用规则:
```
允许所有 Bash 命令?
→ 太宽,不安全(用户可能 rm -rf
只允许 git/npm 命令?
→ 太窄不便利sudo docker pull 被拒)
```
ML 分类器的优势:
```
可以说"这个命令看起来是在开发工作流的一部分,
而不是破坏性操作,所以允许"
如果出错classify 有假阴性),系统还有:
- Denial tracking连续拒绝降级
- Protected files黑名单
- Circuit breaker
```
所以 ML 不是唯一防线,而是**多层防御中的一层**。
### 为什么规则支持 Glob 而不是正则表达式?
Glob`src/*.ts` → 简单直观
正则:`^src/[^/]+\.ts$` → 复杂,容易写错
目标用户是**工程师,但不是安全专家**。Glob 的错误率更低。
---
## 常见误解
**误解 1**"Permission mode 是全局的,改一次就生效?"
实际:可以在 CLI 参数中针对单个命令覆盖:
```bash
claude-code --permission-mode auto /commit # 这个 /commit 用 auto mode
claude-code --permission-mode default /config # 这个 /config 用 default mode
```
**误解 2**"YOLO 分类器是完美的?"
实际:分类器是 **best-effort**,有假阳性和假阴性。假阴性(该拒的通过了)会被 denial tracking 捕捉到。
**误解 3**"Protected files 名单是硬编码的?"
实际:可以通过配置扩展:
```json
{
"protectedFiles": [".gitconfig", ".bashrc", ".my-precious-file"]
}
```
---
## 关键要点
1. **Permission mode**`default`(每次问), `auto`ML 自动), `acceptEdits`, `bypass`, `dontAsk`
2. **决策 pipeline**Mode → Rules → Risk Classification → YOLO → User Prompt
3. **规则系统**Glob 模式分层来源CLI > project > user > default
4. **YOLO 分类器**ML 自动批准 MEDIUM 风险操作,不需要用户每次确认
5. **Denial tracking**:连续 3 次拒绝自动降级权限,防止烦人的重复提示
6. **路径安全**多层防御normalization, URL decode, Unicode clean
7. **Protected files**:黑名单,永远要再次确认
---
## 深入阅读
- `utils/permissions/permissions.ts`:完整决策逻辑
- `utils/permissions/yoloClassifier.ts`:分类器实现
- `utils/permissions/pathValidation.ts`:路径安全
- `utils/permissions/denialTracking.ts`Denial tracking
- `utils/permissions/PermissionMode.ts`Mode 定义
下一步:了解**Context 和 Memory 系统**,看看当 context window 快满时会发生什么。

View file

@ -0,0 +1,416 @@
# Context 和 Memory有限的脑容量
## 为什么这很重要?
Claude 的 context window比如 200K tokens是**有限的**。一个长时间运行的 agent task
```
第 1 轮用户输入200 tokens+ 响应1000 tokens
第 2 轮新输入300 tokens+ 响应2000 tokens
...
第 100 轮:?
总 tokens: 200 + 300 + 400 + ... + 整个历史
在第 50 轮时messages 数组有 100 条消息,超过 context window 的 80%
```
一旦超限系统必须做些什么。Claude Code 的答案是:
1. **System prompt 优化**:区分静态和动态部分,利用 prompt cache
2. **主动 compaction**:在超限前压缩历史
3. **长期 memory**:用户的持久化知识库
---
## System Prompt 架构
不是简单的一个大 string而是**模块化的**
```typescript
const systemPrompt = [
// 第 1 部分:静态,会被缓存
SYSTEM_PROMPT_PREFIX, // 关于 Claude Code 的基础指导
TOOL_DESCRIPTIONS, // 所有 tool 的描述
SYSTEM_PROMPT_SAFETY, // 安全指导
// 缓存边界 ← 这之后的部分会变化,不会被缓存
"SYSTEM_PROMPT_DYNAMIC_BOUNDARY",
// 第 2 部分:动态,每个 session 不同
USER_MEMORY, // 用户的个人知识库
RECENT_CONTEXT, // 最近的几轮对话(总结)
CURRENT_TASK, // 当前任务的上下文
]
```
**Cache boundary marker** 的妙处:
```
初始化时: │首个请求:
cache_key = MD5(prefix) │所有文本
缓存大小10 KB │2000 tokens (40 KB)
│缓存命中 10 KB
│处理 30 KB 新文本
│总成本40 KB 请求
同一 session 中再次使用: │再次请求:
cache 仍有效 │新文本 1000 tokens (20 KB)
直接跳过了 10 KB 的重复传输 │缓存 hit10 KB
成本20 KB │总成本20 KB 请求(节省 10 KB
```
所以,通过把 system prompt 分为静态和动态两部分,我们:
- 减少了重复 token 消耗
- 加快了推理速度(缓存的部分直接使用)
- 提高了吞吐量(多个请求共享缓存)
相关文件:`utils/messages/systemInit.ts`, `context.ts`
---
## Token Budget 生命周期
### 阶段 1初始化
```typescript
const CONTEXT_WINDOW = 200_000 // 比如 200K tokens
const SYSTEM_PROMPT_TOKENS = 5_000
const RESERVED_BUFFER = 10_000 // 总是留出来,防止意外超限
available = CONTEXT_WINDOW - SYSTEM_PROMPT_TOKENS - RESERVED_BUFFER
= 185_000 tokens // 工作预算
```
### 阶段 2追踪使用
每一轮 API 调用后,系统获得实际使用数据:
```typescript
response = await claude.messages.create({...})
remaining = available - response.usage.input_tokens - response.usage.output_tokens
if (remaining < available * 0.2) { // 还剩 20% 以下
console.warn("Token budget 即将耗尽,触发 compaction")
}
if (remaining < 10_000) { // 紧急状态
console.error("无法继续context 严重超限")
return ERROR_OUT_OF_TOKENS
}
```
### 阶段 3警告和恢复
```
Threshold 180% 已用):显示警告,继续
Agent 执行更多 tool ← 看不到警告,继续工作
Threshold 290% 已用):触发 compaction
← 在后台自动压缩,不中断 agent 工作
Threshold 399% 已用):停止接受新请求
← 返回错误,等待用户决定
```
相关文件:`query/tokenBudget.ts`
---
## Compaction自动历史压缩
当 token 预算吃紧时,系统自动压缩历史。这是一个 4 步流程:
### 第 1 步:分析历史
```typescript
// 扫描所有消息,找出"能压缩的部分"
messages = [
system_prompt, // 不能压缩
message_1, // 用户说:"帮我读个文件" → 不压缩(用户消息)
message_2, // Claude 说:"我读了,代码如下..." → 能压缩(输出很长)
message_3, // 用户说:"现在修复 bug" → 不压缩
message_4, // Claude 说:"修复完,改了 5 处..." → 能压缩
...
]
优先级:越早的消息优先级越高(假设最早的对话不再相关)
```
### 第 2 步:优先排序
```
对能压缩的消息排序:
1. 最早的长消息4 messages ago
2. 次早的长消息6 messages ago
3. ...
实际压缩的可能是:
messages 1-10最早的 10 条)被替换为 1 条 summary
```
### 第 3 步:调用 Claude 做总结
```typescript
const summary = await claude.messages.create({
messages: [
{ role: "user", content: "Summarize this conversation in 200 lines max:\n" + messagesText }
],
system: "You are a memory consolidation assistant..."
})
// 返回:
// "用户让我读了 package.json 和 tsconfig.json发现项目是 TypeScript monorepo。"
```
### 第 4 步:替换
```typescript
// 替换前:
messages = [msg_1, msg_2, ..., msg_100] // 100 条消息
// 替换后:
messages = [msg_summary, msg_51, msg_52, ..., msg_100]
// ^被压缩成1条 ^保留后面的消息(近期的)
```
结果:从 100 条消息减少到 51 条token 预算恢复。
相关文件:`query.ts` 中的 `compactMessages()` 函数,大约 200 行
---
## Memory 系统:长期知识库
Compaction 是**临时的**(压缩 session 内的历史)。但如果用户有**多个 session**,同一信息会被重复压缩。
解决方案:**持久化 memory**。
### Memory 文件结构
```
~/.claude/memory/
├── MEMORY.md ← 用户的长期知识库(最多 200 行)
├── projects/
│ └── claude-code.md ← 项目特定的知识
├── patterns/
│ └── async-patterns.md ← 学到的模式
└── people/
└── team.md ← 关于人的信息
```
**MEMORY.md** 的内容示例:
```markdown
# 我的知识库
## 工作偏好
- 不喜欢在 main 分支上直接提交
- 总是用 TypeScript避免 JavaScript
## 项目信息
- Claude Code 仓库: ~/projects/claude-code
- 技术栈: TypeScript + React + Ink
## 编码习惯
- 函数 <100 行为最佳
- 避免 deep nesting最多 3 层
- 总是加 error handling
```
### Auto-Dream后台内存巩固
不是用户手动维护 MEMORY.md而是系统**自动**从 session 中提取和更新。
过程("dream"
```
触发条件(三门齐全):
1. 时间门:距离上次 dream ≥ 24 小时?
2. 会话门:距离上次 dream产生了 ≥ 5 个新 session
3. Lock 门:能获得 consolidation lock防并发
都满足 → 执行 dream
[1] ORIENT
读取用户的 MEMORY.md
扫描最近 N 个 session 的 transcripts
[2] GATHER
提取新的学习点:
- "用户在这个 session 中学到了异步 Rust"
- "发现项目从 4.1 升级到了 4.2"
- "优化了 CI/CD速度提升 30%"
[3] CONSOLIDATE
合并到 MEMORY.md
- 如果已经有同类笔记,合并
- 如果是新的,添加
- 保持在 200 行以内(做优先级选择,删除过时的)
[4] PRUNE & INDEX
- 删除 24 小时内不用的 session transcript
- 更新搜索索引(便于后续 memory 查询)
```
相关文件:
- `services/autoDream/autoDream.ts`:主逻辑(~11 KB
- `services/autoDream/consolidationPrompt.ts`:告诉 Claude 怎么总结
- `memdir/memdir.ts`Memory 目录管理
### 为什么自动化很关键?
```
手动维护:
用户要记得更新 MEMORY.md
→ 大部分人会忘记
→ Memory 逐渐变陈旧
自动化:
后台每 24 小时自动 consolidate
→ 用户无感
→ Memory 总是最新的
```
---
## Context 使用的优化层级
当 context 变紧时,系统依次采取行动:
```
Tier 1还有 70% token
✓ 正常工作
✗ 触发自动 compaction
Tier 2还有 20% token
✓ 继续工作(使用压缩后的历史)
✗ 不接受新的大型工具输出(如读 10 MB 文件)
Tier 3还有 10% token
✓ 只允许低 token 成本的操作
✗ 禁止高 token 成本的操作
Tier 4还有 < 5% token
✗ 停止工作,返回错误
→ 用户可以:
a) 启动新 session重置 token 预算)
b) 手动编辑 MEMORY.md删除过时信息
```
---
## Design Decision 专栏
### 为什么区分静态和动态 system prompt?
不区分(一大块):
```
发第 1 个请求:发 5000 tokens 的 system prompt + 输入
发第 2 个请求:又发 5000 tokens 的 system prompt + 输入
...
浪费了大量 token 在重复发送
```
区分cache boundary
```
发第 1 个请求:发 5000 tokens静态+ 1000 tokens动态
API 缓存这 5000 tokens
发第 2 个请求:直接用缓存的 5000 tokens + 1000 tokens新的动态
节省了 5000 tokens
```
如果 session 很长100 次请求),节省 = 5000 * 99 = 495K tokens
### 为什么 Compaction 触发阈值是 20%,而不是 1%
等到 1% 时才 compact
```
已用 99%,只有 1% 剩余(~2000 tokens
这时启动 compaction自己就需要消耗大量 tokens调用 Claude 做总结)
可能没有足够 tokens 来完成 compaction
→ 失败,系统崩溃
```
提前到 20% 时 compact
```
已用 80%,还有 20% 剩余(~40K tokens
这时启动 compaction有充足 tokens 来做总结
完成后token 预算恢复到 50%
→ 继续工作,很多时间都有富足的 token 可用
```
权衡:早点 compact 意味着多付出一些 token压缩的成本但换来系统稳定性。
### 为什么用后台 dream 而不是 inline compaction?
Inline同步
```
在 query loop 中调用 compaction
等待 compaction 完成5-10 秒)
用户看到卡顿
```
后台 dream
```
query loop 继续运行
在空闲时或定时触发 dream24 小时一次)
用户感觉不到延迟
```
代价:增加系统复杂性(需要处理并发)、需要文件锁(防同时 dream。但值得因为用户体验好得多。
---
## 常见误解
**误解 1**"Context window 满了就完蛋?"
实际:有多层缓冲和恢复机制。系统会逐步:
1. 显示警告
2. 触发 compaction
3. 拒绝新操作
4. 要求用户决定
完全"卡住"很少发生。
**误解 2**"Compaction 会丢失所有细节?"
实际Compaction 由 Claude 做,所以**关键信息被保留**。丢失的是:
- 冗余的解释
- 尝试失败的细节(对后续不相关)
- 已解决的问题的讨论过程
**误解 3**"MEMORY.md 会无限增长?"
实际:维持在 ~200 行(可配置),通过 dream 的 prune 阶段定期清理过时信息。
---
## 关键要点
1. **System prompt 分两部分**:静态(可缓存)+ 动态(每次变化)
2. **Token budget 主动追踪**:到达 80% 时触发 compaction
3. **Compaction 自动压缩历史**:用 Claude 总结 N 条消息为 1 条
4. **Memory 系统持久化知识**MEMORY.md 最多 200 行,用户的长期知识库
5. **Auto-dream 后台巩固**:每 24 小时自动更新 MEMORY.md无需用户介入
6. **优化层级**70% → 20% → 10% → 5% → 停止,每个阈值有对应策略
---
## 深入阅读
- `query/tokenBudget.ts`Token 追踪逻辑
- `services/autoDream/autoDream.ts`Dream 实现
- `memdir/memdir.ts`Memory 目录管理
- `utils/messages/systemInit.ts`System prompt 组装
下一步:了解**Feature Gating 系统**,看看 Claude Code 怎么区分内部功能和公开功能。

View file

@ -0,0 +1,357 @@
# Feature Gating内部功能和公开功能的隔离
## 为什么需要 Feature Gating?
Claude Code 是一个复杂系统,有很多功能:
- 一些已发布、稳定、所有用户可用
- 一些还在测试、只给内部用户("ant" 用户)
- 一些只给特定高级用户Max/Pro)
- 一些通过实验 A/B 测试
如果不做隔离,代码会变得一团糟:
```typescript
// 不好的做法
if (user.isInternal) {
// KAIROS 模式逻辑
} else if (user.isMax) {
// 高级功能
} else if (featureFlags.has('experimental_search')) {
// A/B 测试
} else {
// 默认行为
}
// ... 这样嵌套太深,难以维护
```
Claude Code 的解决方案:**编译期和运行期双层 gating**。
---
## 编译期 GatingBun 的 `feature()` API
BunJavaScript 运行时)提供了一个编译期特性检测 API
```typescript
if (feature("KAIROS")) {
// 这段代码只在 KAIROS 构建中出现
// 其他构建完全不包含这代码
} else {
// 默认行为
}
```
### 工作原理
```
源代码:
if (feature("KAIROS")) {
launchKAIROS()
} else {
launchDefault()
}
↓ Bun 编译(--feature KAIROS
if (true) {
launchKAIROS() // 只保留这段
} else {
// 被 dead code elimination 删除
}
↓ 输出的二进制
launchKAIROS() // 这是最终的可执行代码
```
### 优势
1. **二进制体积小**:内部功能完全不包含在公开二进制中
2. **安全**:用户不能"解锁"内部功能(不存在于二进制)
3. **性能**:不需要运行时检查这些条件
相关代码散布在整个 codebase 中,比如 `entrypoints/cli.tsx`, `bootstrap/state.ts`
---
## Compile-Time Feature 列表
Claude Code 在构建时支持这些 feature flag
| Flag | 启用时 | 作用 |
|------|--------|------|
| `KAIROS` | 启动 KAIROS modealways-on Claude | 全天候 assistant |
| `PROACTIVE` | 同上(别名) | 同上 |
| `BRIDGE_MODE` | 启用 claude.ai 远程控制 | 在 Web UI 中控制 CLI |
| `VOICE_MODE` | 启用语音输入 | 用话筒说指令 |
| `DAEMON` | 启用 daemon 模式 | 后台运行的 Claude |
| `COORDINATOR_MODE` | 启用 coordinator 多 agent | 复杂任务分解 |
| `BUDDY` | 启用 Tamagotchi 伴侣系统 | 养一个宠物 AI |
| `WORKFLOW_SCRIPTS` | 启用工作流脚本 | 自动化重复任务 |
| `NATIVE_CLIENT_ATTESTATION` | 启用本地证明 | 设备信任 |
| `TRANSCRIPT_CLASSIFIER` | 启用 AFK 自动模式 | 离开电脑时自动工作 |
| `HISTORY_SNIP` | 启用历史压缩优化 | Context 更高效 |
| `EXPERIMENTAL_SKILL_SEARCH` | 启用技能搜索(实验) | 发现新 skill 命令 |
| `ABLATION_BASELINE` | 科学研究 baseline | 论文用 |
默认只编译:`KAIROS=0, BRIDGE_MODE=0, ...`(全是 0最小二进制
内部构建时:编译脚本设置 `--feature KAIROS=1 --feature BRIDGE_MODE=1 ...`
相关文件:构建脚本(如 `scripts/build.sh`,如果存在),或 `bunfig.toml`
---
## 运行期 GatingGrowthBook Feature Flags
编译期 feature 决定"可能性",运行期 flags 决定"激活"。
系统启动时,从 GrowthBook特性管理平台获取动态配置
```typescript
// 启动时
const flags = await growthbook.getFeatures({
userId: currentUser.id,
organization: currentUser.org
})
// 现在可以做运行期决策
if (flags.has('tengu_scratch')) {
// 启用共享 scratchpadcoordinator 模式)
enableScratchpad()
}
if (flags.has('tengu_amber_flint')) {
// 启用 in-process teammate swarms
enableTeammates()
}
if (flags.has('tengu_penguins_off')) {
// 禁用某个实验
disableExperiment()
}
```
### Flag 命名约定
Runtime flags 以 `tengu_` 开头("tengu" 是 Claude Code 的内部代号):
```
tengu_scratch # 共享 scratchpad为 coordinator
tengu_amber_flint # In-process teammate
tengu_onyx_plover # 某个实验
tengu_penguins_off # 禁用什么功能
...
```
这样容易区分:
- `KAIROS`编译期vs `tengu_kairos_v2`(运行期)
相关文件:`bootstrap/state.ts`(初始化),`services/analytics/`GrowthBook 集成)
---
## 两层系统的协作
### 场景 1Compile-Time Only最常见
```
新功能 KAIROSalways-on Claude
↓ 编译
├─ 构建 ant内部--feature KAIROS=1
│ → KAIROS 代码被编译进去
└─ 构建 public公开--feature KAIROS=0
→ KAIROS 代码被 dead-code-elimination 删除
→ 二进制不含任何痕迹
```
### 场景 2Compile-Time + Runtime用于 A/B 测试)
```
新的 memory 系统(已编译进去)
↓ 运行时
├─ 用户在 tengu_new_memory=true 组50%
│ → 使用新系统
└─ 用户在 tengu_new_memory=false 组50%
→ 使用旧系统
→ 收集对比数据
```
### 场景 3运行期特定用户
```
高级功能(编译期编译进去)
↓ 运行时
├─ Max 订阅用户 → 启用计算机使用
├─ Pro 订阅用户 → 启用高级 memory
└─ 免费用户 → 基础功能
```
相关文件:`services/policyLimits/`(根据订阅级别应用 policy
---
## 对 System Prompt 的影响
Feature gating 会改变 system prompt 的内容:
```typescript
const systemPrompt = [
BASE_PROMPT,
// 只有在编译时启用 BRIDGE_MODE 时才包含
feature("BRIDGE_MODE") ? BRIDGE_MODE_INSTRUCTIONS : "",
// 运行时检查:如果用户有 tengu_coordinator flag
flags.get("tengu_coordinator") ? COORDINATOR_INSTRUCTIONS : "",
// 运行时 API beta 特性
BETAS_NEGOTIATED_WITH_API, // 如 "interleaved_thinking"
]
```
这也影响 **prompt cache**
```
缓存键 = MD5(system_prompt_prefix)
如果 flag 变化cache key 变化,旧 cache 失效
```
所以,高频变化的 runtime flag 应该放在 cache boundary 之后(动态部分),而不是之前(静态部分)。
---
## Betas 协商
Claude API 定期发布新特性betaClaude Code 需要:
1. 请求启用这些 beta
2. 如果启用,在 system prompt 中告诉 Claude这个 API 支持什么新功能)
例子:
```typescript
// constants/betas.ts
const BETAS_REQUESTED = [
"interleaved-thinking", // 支持在回复中穿插思考
"structured-outputs", // 支持返回结构化 JSON
"context-1m", // 100 万 token context
"web-search", // 网络搜索能力
]
// API 返回确认
const BETAS_ENABLED = [
"interleaved-thinking", // ✓ 已启用
// "structured-outputs", // ✗ 暂无权限
"context-1m", // ✓ 已启用
"web-search", // ✓ 已启用
]
// System prompt 中加入这些信息
You have access to the following beta features:
- Interleaved thinking: You can output <thinking> blocks
- Context 1M: You can use up to 1,000,000 tokens
- Web search: You can call the WebSearchTool
```
相关文件:`constants/betas.ts`
---
## Design Decision 专栏
### 为什么同时用编译期和运行期?
只用编译期:
```
缺点:无法做快速的 A/B 测试(需要重新编译)
无法根据用户身份动态启用功能
```
只用运行期:
```
缺点:内部功能可能被反编译/逆向工程
内部代码暴露在公开二进制中
启动时需要网络请求拿 flags
```
两者结合:
```
编译期决定"可能性"(物理隔离内部代码)
运行期决定"激活"(灵活的 A/B 测试)
最安全、最灵活
```
### 为什么 Bun 的 feature() 而不是其他工具?
Bun 的 feature() 是编译期指令,会进行 dead code elimination
```
其他工具(如 rollup 的条件编译):
需要额外的 webpack 插件
编译配置复杂
Bun:
原生支持
编译快Bun 本身就快)
输出体积最小
```
---
## 常见误解
**误解 1**"Runtime flag 可以改变编译期行为?"
实际不能。Runtime flag 只能在编译期已包含的代码中做选择。如果代码在编译时被 dead-code-elimination 删除了,运行时再想启用也没办法。
```typescript
// 如果编译时 --feature KAIROS=0这段代码被删除
if (feature("KAIROS")) {
launchKAIROS()
}
// 运行时即使 growthbook.flags.get("enable_kairos") == true也没用
// 代码不存在于二进制中
```
**误解 2**"Feature flag 对性能有开销?"
实际:编译期 feature flag 没有开销(代码级别选择)。运行期 flag 有极小开销map lookup可忽略不计。
**误解 3**"所有用户都能看到内部代码?"
实际:不能。公开二进制中编译时被删除的代码,用户看不到。只有 ant内部用户的二进制才包含这些代码。
---
## 关键要点
1. **编译期 feature**Bun 的 `feature()` APIdead code elimination对二进制体积和安全性的保障
2. **运行期 flags**GrowthBook动态启用特性支持 A/B 测试
3. **两层协作**:编译决定可能性,运行时决定激活
4. **System prompt 影响**:不同 flag 组合 → 不同 system prompt → 不同 cache key
5. **API Beta**:协商启用新的 Claude API 特性,在 system prompt 中告诉 Claude
---
## 深入阅读
- `bootstrap/state.ts`:运行时 flag 初始化56 KB
- `constants/betas.ts`Beta feature 列表
- `constants/system.ts``constants/systemPromptSections.ts`System prompt 组装
- `entrypoints/cli.tsx`:编译期条件编译示例
---
## 后记
这 6 篇文档覆盖了 Claude Code 最核心的 agentic 设计决策。还有很多其他话题Bridge 模式、Voice 输入、Plugin 系统等),但这些是最重要的基础。
如果你想进一步了解,建议:
1. 读完这 6 篇
2. 打开 `query.ts``Tool.ts`,对照源代码
3. 运行 Claude Code`--verbose` flag 看内部日志
4. 探索 `tools/AgentTool/` 的源代码
祝学习愉快!

View file

@ -0,0 +1,113 @@
# Claude Code Agentic 设计文档
> **目标读者**:有一定软件工程背景、希望通过真实项目理解 agentic 系统设计的学习者。
> **建议用时**:约 2.5 小时
> **阅读语言**:中文正文 + 英文技术术语
---
## 这个项目是什么?
[Claude Code](https://claude.ai/code) 是 Anthropic 开发的 AI 辅助编程 CLI 工具。它不是一个简单的"问答机器人",而是一个完整的 **agentic system**
- 它可以自主执行多轮工具调用(读文件、改代码、运行命令)
- 它可以 spawn 子 agent 并行处理复杂任务
- 它有 permission 管控、memory 管理、context 压缩等完整的 agent 基础设施
这套文档通过分析 Claude Code 的源代码,提炼出其中最有价值的 agentic 设计选择,帮助你建立对真实 agent 系统的直觉。
---
## 整体架构
```
┌─────────────────────────────────────────────────────────────────┐
│ 用户 / IDE / SDK │
└────────────────────────────┬────────────────────────────────────┘
│ 输入:用户消息
┌─────────────────────────────────────────────────────────────────┐
│ CLI / Entrypoint Layer │
│ entrypoints/cli.tsx │ entrypoints/sdk/ │ entrypoints/mcp/ │
└────────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Query Loop Engine │
│ QueryEngine.ts ←→ query.ts ←→ query/ │
│ (状态机: QUERY → TOOL_USE → RESULT → QUERY → ...) │
└──────────────┬────────────────────────────────┬─────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌───────────────────────────────┐
│ Claude API (Streaming) │ │ Tool Execution Layer │
│ services/api/ │ │ services/tools/ │
│ • token budget 追踪 │ │ • StreamingToolExecutor │
│ • compaction 触发 │ │ • 并发控制 + sibling abort │
└──────────────────────────┘ └──────────────┬────────────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────────┐
│ File │ │ Bash / │ │ AgentTool │
│ Tools │ │ Shell │ │ (子 agent) │
└─────────┘ └──────────┘ └──────┬───────┘
┌────────────────────────┘
│ spawn
┌────────────────────────┐
│ 子 Agent / Coordinator│
│ coordinator/ │
│ tasks/Local|Remote │
└────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 横切关注点Cross-cutting
│ Permission System │ Context/Memory │ Feature Gating │
│ utils/permissions/ │ memdir/ autoDream│ bootstrap/state.ts │
└─────────────────────────────────────────────────────────────────┘
```
---
## 核心设计哲学
**1. Tool 是 Agent 触碰世界的唯一手段**
Agent 的所有副作用(写文件、执行命令、发消息)都必须经过 Tool 接口,这使得权限控制、审计日志、测试 mock 都可以在一个地方统一处理。
**2. Text is the protocol**
Agent 之间通过 `<task-notification>` XML 消息通信memory 以 Markdown 文件存储permission rules 是 `Bash(git *)` 这样的字符串模式。用文本作为协议,意味着 AI 模型自己也可以读懂并调试这些通信内容。
**3. Failure modes are first-class**
每个长时间运行的操作都有明确的 abort 路径,例如:连续 3 次 permission denial 就回退到手动提示;并行 tool 中一个失败会触发 sibling abortcompaction 失败有 rollback 保护。
**4. Context window 是最稀缺的资源**
整个系统的大量设计决策system prompt 的静态/动态分割、token budget 追踪、auto-dream 后台压缩)都围绕着"如何最大化利用有限的 context window"展开。
**5. 编译期与运行期特性隔离**
内部功能通过 Bun 的 `feature()` API 在编译时消除,不出现在公开二进制文件中;运行时行为通过 GrowthBook runtime flags 动态控制。
---
## 建议阅读顺序
| 编号 | 文档 | 用时 | 核心问题 |
|------|------|------|---------|
| [00](./00-codebase-tour.md) | 代码库目录全景 | 30 min | "这个 repo 里有什么?" |
| [01](./01-agent-loop.md) | 核心 Query Loop | 20 min | "Agent 是如何循环运作的?" |
| [02](./02-tool-system.md) | Tool 系统 | 20 min | "Tool 是怎么被定义和执行的?" |
| [03](./03-multi-agent-coordination.md) | 多 Agent 协调 | 25 min | "多个 agent 怎么协作?" |
| [04](./04-permission-system.md) | Permission 系统 | 20 min | "Agent 怎么知道自己能做什么?" |
| [05](./05-context-and-memory.md) | Context 与 Memory | 20 min | "Context window 满了怎么办?" |
| [06](./06-feature-gating.md) | Feature Flag 系统 | 10 min | "内部功能是怎么隐藏的?" |
---
## 如何使用这套文档
- **按顺序读**00 → 01 → 02 → 03 → 04 → 05 → 06每篇都假设你已读过前面的内容
- **跳读**:如果你已熟悉某个概念,可以直接跳到感兴趣的章节
- **对照代码**:每篇文档都标注了关键源文件路径,随时可以打开对照阅读
- **关注 Design Decision 专栏**:这些是最有学习价值的地方,解释了"为什么这样设计"而不只是"是什么"