mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
docs: 审校 Agent 文档术语与架构描述准确性(docs/agent) (#231)
基于源码验证和官方文档对照,修正三份 Agent 文档的内容准确性: coordinator-and-swarm.mdx: - 对齐官方术语:Leader→Team Lead, teammate→Teammate, 引入 Mailbox 消息系统 - 修正 Swarm 门控为 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1(原文"默认启用"不准确) - 拓扑描述改为"星型+P2P混合"(官方有 Team Lead 角色,非纯网状) - 新增架构组件表、Mailbox 消息系统、Hook 事件(TeammateIdle/TaskCreated/TaskCompleted)、限制说明 - 新增持久化存储路径(~/.claude/teams/, ~/.claude/tasks/) - 移除虚构的"高水位标记"(claimTask 仅使用文件锁) - 修正 Scratchpad 描述(Workers 获得,非 Coordinator 拥有) - 修复引号不匹配 sub-agents.mdx: - 修正为三路径路由(命名 Agent / Fork / GP 回退),原文仅两种 - 补充 isForkSubagentEnabled() 三前提(feature flag + 非Coordinator + 非非交互式) - 新增模型解析优先级(4级链 + inherit 运行时解析) - 新增内置 Agent 表(Explore/Plan/GP/statusline-setup/claude-code-guide) - 新增 Hook 事件表(frontmatter + settings.json 双级别) - 补充异步生命周期完整链路(finalizeAgentTool → getWorktreeResult) - 补充 transcript 存储路径和结果格式细节 worktree-isolation.mdx: - 区分 createWorktreeForSession vs createAgentWorktree 两种函数 - 修正清理机制为 cleanupWorktreeIfNeeded(非 ExitWorktreeTool 手动操作) - 补充 usedSparsePaths 字段
This commit is contained in:
@@ -10,13 +10,13 @@ keywords: ["协调者模式", "蜂群模式", "Agent Swarm", "多 Agent 协作",
|
|||||||
|
|
||||||
| 维度 | Coordinator Mode | Agent Swarms |
|
| 维度 | Coordinator Mode | Agent Swarms |
|
||||||
|------|-----------------|--------------|
|
|------|-----------------|--------------|
|
||||||
| **门控** | `feature('COORDINATOR_MODE')` + `CLAUDE_CODE_COORDINATOR_MODE=1` | 任务系统 V2(默认启用) |
|
| **门控** | `feature('COORDINATOR_MODE')` + `CLAUDE_CODE_COORDINATOR_MODE=1` | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` 环境变量 |
|
||||||
| **拓扑** | 星型:Coordinator 居中,Worker 外围 | 网状:对等 Agent 共享任务列表 |
|
| **拓扑** | 星型:Coordinator 居中,Worker 外围 | 星型+P2P 混合:Team Lead 协调,Teammate 间可直接通信 |
|
||||||
| **角色** | 明确分工:Coordinator 编排、Worker 执行 | 模糊:每个 Agent 自主认领任务 |
|
| **角色** | 明确分工:Coordinator 编排、Worker 执行 | Team Lead 协调 + Teammate 自主认领任务 |
|
||||||
| **通信** | `SendMessage` 定向通信 + `<task-notification>` | 任务文件系统 + 邮箱广播 |
|
| **通信** | `SendMessage` 定向通信 + `<task-notification>` | Mailbox 消息系统(message / broadcast) |
|
||||||
| **适用** | 需要集中决策的复杂任务 | 并行度高的独立子任务 |
|
| **适用** | 需要集中决策的复杂任务 | 并行度高、需要 Teammate 间直接协作的任务 |
|
||||||
|
|
||||||
两者不是互斥的——Coordinator Mode 可以在 Swarm 架构之上运行,将 Coordinator 作为特殊的 Leader Agent。
|
两者不是互斥的——理论上 Coordinator Mode 可以在 Agent Teams 架构之上运行(概念层叠加,非嵌套团队),将 Coordinator 作为特殊的 Team Lead,但这部分集成(`workerAgent.ts` 中的 `getCoordinatorAgents`)目前为 stub 实现,尚未完整落地。
|
||||||
|
|
||||||
## Coordinator Mode:星型编排架构
|
## Coordinator Mode:星型编排架构
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ Coordinator 被剥夺了所有"动手"工具,只保留编排能力:
|
|||||||
| **TaskStop** | 中途停止走错方向的 Worker |
|
| **TaskStop** | 中途停止走错方向的 Worker |
|
||||||
| **subscribe_pr_activity** | 订阅 GitHub PR 事件(review comments、CI 结果) |
|
| **subscribe_pr_activity** | 订阅 GitHub PR 事件(review comments、CI 结果) |
|
||||||
|
|
||||||
Coordinator **不写代码、不读文件、不执行命令**——它只做三件事:理解需求、分配任务、综合结果。
|
Coordinator **不写代码、不读文件、不执行命令**——它的核心职责是:理解需求、分配任务、综合结果,以及在无需工具时直接回答用户问题。
|
||||||
|
|
||||||
### Worker 的工具权限
|
### Worker 的工具权限
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Worker 的可用工具由 `getCoordinatorUserContext()`(`coordinatorMode.ts:80
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 简化模式下:只有 Bash + Read + Edit
|
// 简化模式下:只有 Bash + Read + Edit
|
||||||
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE')
|
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)
|
||||||
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]
|
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]
|
||||||
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
|
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
|
||||||
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
|
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
|
||||||
@@ -63,7 +63,7 @@ const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE')
|
|||||||
|
|
||||||
### Scratchpad:跨 Worker 的共享知识库
|
### Scratchpad:跨 Worker 的共享知识库
|
||||||
|
|
||||||
当 `tengu_scratch` feature flag 启用时,Coordinator 拥有一个 Scratchpad 目录:
|
当 `isScratchpadGateEnabled()`(内部检查 `tengu_scratch` feature gate)启用时,Workers 获得一个 Scratchpad 目录,Coordinator 通过其系统上下文知晓该目录的存在:
|
||||||
|
|
||||||
```
|
```
|
||||||
Scratchpad 目录:
|
Scratchpad 目录:
|
||||||
@@ -113,32 +113,84 @@ Coordinator System Prompt(`coordinatorMode.ts:111-369`,约 260 行)明确
|
|||||||
|
|
||||||
这是 Coordinator Mode 最核心的设计约束:Coordinator 必须先理解,再分配。
|
这是 Coordinator Mode 最核心的设计约束:Coordinator 必须先理解,再分配。
|
||||||
|
|
||||||
## Agent Swarms:蜂群式协作
|
## Agent Teams (Swarm):蜂群式协作
|
||||||
|
|
||||||
Swarm 模式基于任务系统 V2(详见[任务管理](../tools/task-management.mdx)),核心机制是**共享任务列表 + 竞争认领**:
|
Swarm 模式基于任务系统 V2(详见[任务管理](../tools/task-management.mdx)),核心机制是**共享任务列表 + 竞争认领 + Mailbox 消息系统**:
|
||||||
|
|
||||||
### 团队初始化
|
### 团队初始化
|
||||||
|
|
||||||
```
|
```
|
||||||
Leader 创建团队(TeamCreateTool)
|
Team Lead 创建团队(TeamCreateTool)
|
||||||
↓
|
↓
|
||||||
设置 teamName → setLeaderTeamName()
|
设置 teamName → setLeaderTeamName()
|
||||||
↓
|
↓
|
||||||
所有 teammate 自动获得相同的 taskListId
|
所有 Teammate 自动获得相同的 taskListId
|
||||||
↓
|
↓
|
||||||
teammate 启动时:
|
Teammate 启动时:
|
||||||
1. CLAUDE_CODE_TASK_LIST_ID 环境变量(显式覆盖)
|
1. CLAUDE_CODE_TASK_LIST_ID 环境变量(显式覆盖)
|
||||||
2. teammate 上下文的 teamName(共享 leader 的任务列表)
|
2. Teammate 上下文的 teamName(共享 Lead 的任务列表)
|
||||||
3. CLAUDE_CODE_TEAM_NAME 环境变量
|
3. CLAUDE_CODE_TEAM_NAME 环境变量
|
||||||
4. leader 设置的 teamName
|
4. Lead 设置的 teamName
|
||||||
5. getSessionId()(兜底)
|
5. getSessionId()(兜底)
|
||||||
```
|
```
|
||||||
|
|
||||||
多级优先级确保了 Leader 和所有 Teammate 指向同一个任务列表,无需额外协调。
|
多级优先级确保了 Team Lead 和所有 Teammate 指向同一个任务列表,无需额外协调。
|
||||||
|
|
||||||
|
### 架构组件
|
||||||
|
|
||||||
|
官方 Agent Teams 架构定义了四个核心组件:
|
||||||
|
|
||||||
|
| 组件 | 角色 |
|
||||||
|
|------|------|
|
||||||
|
| **Team Lead** | 创建团队、分配任务、综合结果的主 Claude Code 会话 |
|
||||||
|
| **Teammate** | 独立的 Claude Code 实例,各自拥有独立的上下文窗口 |
|
||||||
|
| **Task List** | 共享的任务列表,Teammate 竞争认领和完成 |
|
||||||
|
| **Mailbox** | 消息系统,支持 Teammate 间直接通信 |
|
||||||
|
|
||||||
|
### Mailbox 消息系统
|
||||||
|
|
||||||
|
官方架构中的 Mailbox 是 Teammate 间通信的核心原语,支持两种消息模式(`broadcast` 模式来自源码推断,官方文档未明确细分):
|
||||||
|
|
||||||
|
| 模式 | 作用 | 场景 |
|
||||||
|
|------|------|------|
|
||||||
|
| **message** | 定向发送给指定 Teammate | 传递具体指令、请求协作 |
|
||||||
|
| **broadcast** | 广播给所有 Teammate | 全局通知、状态同步 |
|
||||||
|
|
||||||
|
Mailbox 的关键特性:
|
||||||
|
- **自动投递**:消息自动送达目标 Teammate 的对话上下文
|
||||||
|
- **空闲通知**(TeammateIdle):Teammate 完成当前任务进入空闲时,自动通过 Mailbox 通知 Team Lead
|
||||||
|
- **直接通信**:与 Coordinator Mode 不同,Teammate 之间可以直接通信,无需经过 Lead 中转
|
||||||
|
|
||||||
|
### Hook 事件
|
||||||
|
|
||||||
|
Agent Teams 提供三个关键 Hook 事件,用于在团队生命周期中注入自定义逻辑:
|
||||||
|
|
||||||
|
| Hook | 触发时机 | 典型用途 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| **TaskCreated** | 新任务添加到任务列表时 | 自动分配、优先级排序 |
|
||||||
|
| **TaskCompleted** | 任务标记为完成时 | 结果通知、依赖解锁 |
|
||||||
|
| **TeammateIdle** | Teammate 完成所有任务进入空闲时 | Lead 重新分配、动态扩缩容 |
|
||||||
|
|
||||||
|
### 限制
|
||||||
|
|
||||||
|
当前 Agent Teams 实现的限制:
|
||||||
|
- **不支持嵌套团队**:Teammate 不能再创建子团队
|
||||||
|
- **每 session 一个团队**:一个会话只能属于一个团队
|
||||||
|
- **Lead 固定**:Team Lead 创建后不可更换
|
||||||
|
- **不支持 in-process Teammate 的会话恢复**:进程重启后 in-process 类型 Teammate 的状态丢失
|
||||||
|
|
||||||
|
### 持久化存储
|
||||||
|
|
||||||
|
团队状态通过文件系统持久化,确保进程重启后可恢复:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.claude/teams/{team-name}/config.json ← 团队配置
|
||||||
|
~/.claude/tasks/{team-name}/ ← 共享任务列表(文件锁保护)
|
||||||
|
```
|
||||||
|
|
||||||
### 任务认领与竞争
|
### 任务认领与竞争
|
||||||
|
|
||||||
`claimTask()` 是 Swarm 的核心并发原语:
|
`claimTask()` 是 Agent Teams 的核心并发原语:
|
||||||
|
|
||||||
```
|
```
|
||||||
Teammate A 调用 TaskList → 发现 task #3 是 pending
|
Teammate A 调用 TaskList → 发现 task #3 是 pending
|
||||||
@@ -146,7 +198,7 @@ Teammate B 同时发现 task #3 是 pending
|
|||||||
↓
|
↓
|
||||||
两者同时尝试 TaskUpdate(task #3, {status: "in_progress"})
|
两者同时尝试 TaskUpdate(task #3, {status: "in_progress"})
|
||||||
↓
|
↓
|
||||||
文件锁 + 高水位标记保证原子性:
|
文件锁保证原子性:
|
||||||
- 第一个写入者获得 owner 锁定
|
- 第一个写入者获得 owner 锁定
|
||||||
- 第二个写入者收到 already_claimed 错误
|
- 第二个写入者收到 already_claimed 错误
|
||||||
↓
|
↓
|
||||||
@@ -166,8 +218,11 @@ unassignTeammateTasks()
|
|||||||
→ 扫描任务列表,找到 owner === teammateName 的未完成任务
|
→ 扫描任务列表,找到 owner === teammateName 的未完成任务
|
||||||
→ 重置为 pending + owner=undefined
|
→ 重置为 pending + owner=undefined
|
||||||
↓
|
↓
|
||||||
Leader 通过 mailbox 收到通知
|
Team Lead 感知途径:
|
||||||
→ 重新分配或创建新 Teammate
|
1. 任务状态变化(pending 重置)—— 通过共享任务列表
|
||||||
|
2. Mailbox 空闲通知(TeammateIdle hook)—— Teammate 停止时自动通知 Lead
|
||||||
|
↓
|
||||||
|
Team Lead 重新分配任务或创建新 Teammate
|
||||||
```
|
```
|
||||||
|
|
||||||
## 任务类型全景
|
## 任务类型全景
|
||||||
@@ -186,11 +241,11 @@ Leader 通过 mailbox 收到通知
|
|||||||
|
|
||||||
`InProcessTeammateTask` 与 `LocalAgentTask` 的关键差异:前者共享进程的内存空间和基础设施状态(如 MCP 连接池),但有独立的对话上下文和工具权限;后者是完全隔离的子进程,启动开销更大但更安全。
|
`InProcessTeammateTask` 与 `LocalAgentTask` 的关键差异:前者共享进程的内存空间和基础设施状态(如 MCP 连接池),但有独立的对话上下文和工具权限;后者是完全隔离的子进程,启动开销更大但更安全。
|
||||||
|
|
||||||
## Coordinator vs Swarm 的选择
|
## Coordinator vs Agent Teams 的选择
|
||||||
|
|
||||||
| 场景 | 推荐模式 | 原因 |
|
| 场景 | 推荐模式 | 原因 |
|
||||||
|------|---------|------|
|
|------|---------|------|
|
||||||
| "重构认证系统,需要多模块协调" | Coordinator | 需要集中决策,Worker 间有依赖 |
|
| "重构认证系统,需要多模块协调" | Coordinator | 需要集中决策,Worker 间有依赖 |
|
||||||
| "修复 10 个独立的 lint 警告" | Swarm | 任务独立,可完全并行 |
|
| "修复 10 个独立的 lint 警告" | Agent Teams | 任务独立,Teammate 可完全并行 |
|
||||||
| "研究方案 A 和方案 B,然后选一个实现" | Coordinator | 先并行研究,再集中决策 |
|
| "研究方案 A 和方案 B,然后选一个实现" | Coordinator | 先并行研究,再集中决策 |
|
||||||
| "在大仓库中搜索所有 TODO 并分类" | Swarm | 无依赖,各自领任务即可 |
|
| "在大仓库中搜索所有 TODO 并分类" | Agent Teams | 无依赖,各自领任务即可 |
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ keywords: ["子 Agent", "AgentTool", "任务委派", "forkSubagent", "子进程
|
|||||||
AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" }
|
AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" }
|
||||||
↓
|
↓
|
||||||
AgentTool.call() ← 入口(AgentTool.tsx:239)
|
AgentTool.call() ← 入口(AgentTool.tsx:239)
|
||||||
├── 解析 effectiveType(fork vs 命名 agent)
|
├── 解析 effectiveType(fork vs 命名 agent vs GP 回退)
|
||||||
├── filterDeniedAgents() ← 权限过滤
|
├── filterDeniedAgents() ← 仅命名 Agent 路径执行:权限过滤
|
||||||
├── 检查 requiredMcpServers ← MCP 依赖验证(最长等 30s)
|
├── 检查 requiredMcpServers ← MCP 依赖验证(最长等 30s)
|
||||||
├── assembleToolPool(workerPermissionContext) ← 独立组装工具池
|
├── assembleToolPool(workerPermissionContext) ← 独立组装工具池
|
||||||
├── createAgentWorktree() ← 可选 worktree 隔离
|
├── createAgentWorktree() ← 可选 worktree 隔离
|
||||||
@@ -26,26 +26,30 @@ runAgent() ← 核心执行(runAgent.ts:248
|
|||||||
├── executeSubagentStartHooks() ← Hook 注入
|
├── executeSubagentStartHooks() ← Hook 注入
|
||||||
├── query() ← 进入标准 agentic loop
|
├── query() ← 进入标准 agentic loop
|
||||||
│ ├── 消息流逐条 yield
|
│ ├── 消息流逐条 yield
|
||||||
│ └── recordSidechainTranscript() ← JSONL 持久化
|
│ └── recordSidechainTranscript() ← JSONL 持久化(~/.claude/projects/{project}/{session}/subagents/)
|
||||||
↓
|
↓
|
||||||
finalizeAgentTool() ← 结果汇总
|
finalizeAgentTool() ← 结果汇总
|
||||||
├── 提取文本内容 + usage 统计
|
├── 提取文本内容 + usage 统计
|
||||||
└── mapToolResultToToolResultBlockParam() ← 格式化为 tool_result
|
└── mapToolResultToToolResultBlockParam() ← 格式化为 tool_result
|
||||||
```
|
```
|
||||||
|
|
||||||
## 两种子 Agent 路径:命名 Agent vs Fork
|
## 子 Agent 的三种路径
|
||||||
|
|
||||||
`AgentTool.call()` 根据是否提供 `subagent_type` 走两条完全不同的路径(`AgentTool.tsx:322-356`):
|
`AgentTool.call()` 根据 `subagent_type` 参数和 Fork 实验开关,走三条不同的路径:
|
||||||
|
|
||||||
| 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(`subagent_type` 省略) |
|
| 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(Fork 启用 + 类型省略) | General-purpose 回退(Fork 关闭 + 类型省略) |
|
||||||
|------|-------------------------------------|--------------------------------------|
|
|------|-------------------------------------|--------------------------------------|---------------------------------------------|
|
||||||
| **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled()` && 未指定类型 |
|
| **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled() === true` 且未指定类型 | `isForkSubagentEnabled() === false` 且未指定类型 |
|
||||||
| **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt |
|
| **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt | General-purpose Agent 的 `getSystemPrompt()` |
|
||||||
| **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) |
|
| **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) | `assembleToolPool()` 独立组装 |
|
||||||
| **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) |
|
| **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) | 仅任务描述 |
|
||||||
| **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) |
|
| **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) | 可独立指定 |
|
||||||
| **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) |
|
| **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) | Agent 定义的 `permissionMode` |
|
||||||
| **目的** | 专业任务委派 | Prompt Cache 命中率优化 |
|
| **目的** | 专业任务委派 | Prompt Cache 命中率优化 | 通用任务处理 |
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Fork 实验的门控函数 `isForkSubagentEnabled()` 需要同时满足三个前提:`FORK_SUBAGENT` feature flag 已启用、当前不在 Coordinator 模式中、且不是非交互式会话。任一条件不满足时,省略 `subagent_type` 会静默降级为 General-purpose Agent,而非触发 Fork。
|
||||||
|
</Note>
|
||||||
|
|
||||||
Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
|
Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
|
||||||
|
|
||||||
@@ -64,9 +68,41 @@ Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通
|
|||||||
1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
|
1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
|
||||||
2. **消息扫描**(降级兜底):检测 `<fork-boilerplate>` 标签
|
2. **消息扫描**(降级兜底):检测 `<fork-boilerplate>` 标签
|
||||||
|
|
||||||
## 工具池的独立组装
|
### 模型解析优先级
|
||||||
|
|
||||||
子 Agent 不继承父 Agent 的工具限制——它的工具池完全独立组装(`AgentTool.tsx:573-577`):
|
子 Agent 的模型选择遵循严格的优先级链(`src/utils/model/agent.ts`):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. CLAUDE_CODE_SUBAGENT_MODEL 环境变量 ← 全局覆盖
|
||||||
|
↓(未设置时)
|
||||||
|
2. 每次调用的 model 参数 ← AgentTool 入参
|
||||||
|
↓(未指定时)
|
||||||
|
3. Agent 定义的 model frontmatter ← 如 "sonnet", "haiku", "inherit"
|
||||||
|
↓(未定义时)
|
||||||
|
4. 继承父对话模型(conversation model) ← getDefaultSubagentModel() 返回 "inherit"
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `inherit` 不是简单的模型传递——它经过 `getRuntimeMainLoopModel()` 解析,确保 plan mode 下的 `opusplan→Opus` 等运行时映射正确生效。当 Agent 指定的模型族(如 `haiku`)与父模型同族时,直接复用父模型的精确 ID,避免跨 provider 降级。
|
||||||
|
|
||||||
|
## 命名 Agent 的工具池独立组装
|
||||||
|
|
||||||
|
### 内置 Agent
|
||||||
|
|
||||||
|
系统预定义了几个内置 Agent(`src/tools/AgentTool/builtinAgents.ts`),各有明确的职责和模型配置:
|
||||||
|
|
||||||
|
| Agent | 模型 | 权限 | 用途 |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| **Explore** | Haiku(轻量快速) | 只读(Read/Grep/Glob) | 代码库搜索与探索 |
|
||||||
|
| **Plan** | 继承父模型 | 只读 | 为 Plan Mode 收集研究信息 |
|
||||||
|
| **General-purpose** | 继承父模型 | 全部工具 | 复杂的通用任务处理 |
|
||||||
|
| **statusline-setup** | 继承父模型 | 受限 | 配置状态栏设置 |
|
||||||
|
| **claude-code-guide** | 继承父模型 | 受限 | 解答 Claude Code 使用问题 |
|
||||||
|
|
||||||
|
用户还可通过 `.claude/agents/` 目录或 settings 定义自定义 Agent,作用域优先级为:managed settings > CLI `--agents` > 项目级 `.claude/agents/` > 用户级 `~/.claude/agents/` > plugin。
|
||||||
|
|
||||||
|
命名 Agent(包括 General-purpose 回退)不继承父 Agent 的工具限制——它的工具池完全独立组装。Fork 子进程则通过 `useExactTools: true` 直接继承父 Agent 的原始工具池,以保持 Prompt Cache 中工具定义的字节一致性。
|
||||||
|
|
||||||
|
命名 Agent 的工具池组装逻辑:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const workerPermissionContext = {
|
const workerPermissionContext = {
|
||||||
@@ -93,6 +129,17 @@ const resolvedTools = useExactTools
|
|||||||
|
|
||||||
`resolveAgentTools()` 会根据 Agent 定义中的 `tools` 字段过滤可用工具,将 `['*']` 映射为全量工具。
|
`resolveAgentTools()` 会根据 Agent 定义中的 `tools` 字段过滤可用工具,将 `['*']` 映射为全量工具。
|
||||||
|
|
||||||
|
### Hook 事件
|
||||||
|
|
||||||
|
子 Agent 支持 Agent 定义 frontmatter 和全局 settings.json 两种级别的 Hook:
|
||||||
|
|
||||||
|
| 来源 | 事件 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Agent frontmatter `hooks` | `PreToolUse` / `PostToolUse` | 工具调用前后拦截 |
|
||||||
|
| Agent frontmatter `hooks` | `Stop` | 自动转换为 `SubagentStop`(`registerFrontmatterHooks` 传入 `isAgent=true`) |
|
||||||
|
| settings.json | `SubagentStart` | 子 Agent 启动时触发(`executeSubagentStartHooks()`) |
|
||||||
|
| settings.json | `SubagentStop` | 子 Agent 停止时触发 |
|
||||||
|
|
||||||
## Worktree 隔离机制
|
## Worktree 隔离机制
|
||||||
|
|
||||||
`isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:590-593`):
|
`isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:590-593`):
|
||||||
@@ -115,19 +162,23 @@ Worktree 生命周期:
|
|||||||
|
|
||||||
### 异步 Agent(后台运行)
|
### 异步 Agent(后台运行)
|
||||||
|
|
||||||
当 `run_in_background=true` 或 `selectedAgent.background=true` 时,Agent 立即返回 `async_launched` 状态(`AgentTool.tsx:686-764`):
|
当 `run_in_background=true`、`selectedAgent.background=true`、或系统判定应强制异步(如 `assistantForceAsync`、`proactiveModule` 激活)时,Agent 立即返回 `async_launched` 状态:
|
||||||
|
|
||||||
```
|
```
|
||||||
registerAsyncAgent(agentId, ...) ← 注册到 AppState.tasks
|
registerAsyncAgent(agentId, ...) ← 注册到 AppState.tasks
|
||||||
↓ (void — 火后不管)
|
↓ (void — 火后不管)
|
||||||
runAsyncAgentLifecycle() ← 后台执行
|
runAsyncAgentLifecycle() ← 后台执行(agentToolUtils.ts)
|
||||||
├── runAgent().onCacheSafeParams ← 进度摘要初始化
|
├── runAgent().onCacheSafeParams ← 进度摘要初始化
|
||||||
├── 消息流迭代
|
├── 消息流迭代
|
||||||
├── completeAsyncAgent() ← 标记完成
|
├── finalizeAgentTool() ← 结果汇总(提取文本 + usage 统计)
|
||||||
├── classifyHandoffIfNeeded() ← 安全检查
|
├── completeAsyncAgent() ← 标记完成(先于通知,确保 TaskOutput 尽快解除阻塞)
|
||||||
|
├── classifyHandoffIfNeeded() ← 安全分类(需 TRANSCRIPT_CLASSIFIER feature)
|
||||||
|
├── getWorktreeResult() ← Worktree 清理(如有隔离)
|
||||||
└── enqueueAgentNotification() ← 通知主 Agent
|
└── enqueueAgentNotification() ← 通知主 Agent
|
||||||
```
|
```
|
||||||
|
|
||||||
|
如果异步 Agent 提供了 `name` 参数,还会注册到 `agentNameRegistry`,支撑 `SendMessage` 工具通过名称路由到该 Agent。
|
||||||
|
|
||||||
异步 Agent 获得独立的 `AbortController`,不与父 Agent 共享——用户按 ESC 取消主线程不会杀掉后台 Agent。
|
异步 Agent 获得独立的 `AbortController`,不与父 Agent 共享——用户按 ESC 取消主线程不会杀掉后台 Agent。
|
||||||
|
|
||||||
### 同步 Agent(前台运行)
|
### 同步 Agent(前台运行)
|
||||||
@@ -154,16 +205,16 @@ const raceResult = await Promise.race([
|
|||||||
|
|
||||||
## 结果回传格式
|
## 结果回传格式
|
||||||
|
|
||||||
`mapToolResultToToolResultBlockParam()` 根据状态返回不同格式(`AgentTool.tsx:1298-1375`):
|
`mapToolResultToToolResultBlockParam()` 根据状态返回不同格式:
|
||||||
|
|
||||||
| 状态 | 返回内容 |
|
| 状态 | 返回内容 |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `completed` | 内容 + `<usage>` 块(token/tool_calls/duration) |
|
| `completed` | 内容 + `<usage>` 块(token/tool_calls/duration);无内容时插入占位文本 `"(Subagent completed but returned no output.)"` 防止模型误判为空 |
|
||||||
| `async_launched` | agentId + outputFile 路径 + 操作指引 |
|
| `async_launched` | agentId + outputFile 路径 + 操作指引(指引内容取决于 `canReadOutputFile`:有读取权限时提示通过 Read/Bash 查看进度,否则仅告知已启动) |
|
||||||
| `teammate_spawned` | agent_id + name + team_name |
|
| `teammate_spawned` | agent_id + name + team_name |
|
||||||
| `remote_launched` | taskId + sessionUrl + outputFile |
|
| `remote_launched` | taskId + sessionUrl + outputFile |
|
||||||
|
|
||||||
对于一次性内置 Agent(Explore、Plan),`<usage>` 块被省略——每周节省约 1-2 Gtok 的上下文窗口。
|
对于一次性内置 Agent(Explore、Plan),当**不存在** worktree 隔离时,`<usage>` 块和 agentId 尾部被省略——每周节省约 1-2 Gtok 的上下文窗口。存在 worktree 时仍需返回 `worktreePath` 和 `worktreeBranch` 信息。
|
||||||
|
|
||||||
## MCP 依赖的等待机制
|
## MCP 依赖的等待机制
|
||||||
|
|
||||||
@@ -174,7 +225,7 @@ const MAX_WAIT_MS = 30_000 // 最长等 30 秒
|
|||||||
const POLL_INTERVAL_MS = 500 // 每 500ms 轮询
|
const POLL_INTERVAL_MS = 500 // 每 500ms 轮询
|
||||||
```
|
```
|
||||||
|
|
||||||
早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。
|
早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。等待结束后如果仍有必需服务器未就绪,`call()` 会抛出错误并明确列出缺失的服务器名称。
|
||||||
|
|
||||||
## 适用场景
|
## 适用场景
|
||||||
|
|
||||||
|
|||||||
@@ -143,14 +143,18 @@ call() — 实际执行
|
|||||||
|
|
||||||
## 与 Agent 工具的联动
|
## 与 Agent 工具的联动
|
||||||
|
|
||||||
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行:
|
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行。注意 Agent 工具使用**专用的** `createAgentWorktree()`(`src/utils/worktree.ts`),而非用户会话用的 `createWorktreeForSession()`,两者有关键差异:
|
||||||
|
|
||||||
- `isolation: "worktree"` → 调用 `createWorktreeForSession()`,子 Agent 在独立 worktree 中执行
|
| 维度 | `createWorktreeForSession`(用户会话) | `createAgentWorktree`(子 Agent) |
|
||||||
- 无 isolation → 子 Agent 共享主工作目录
|
|------|---------------------------------------|----------------------------------|
|
||||||
|
| 调用者 | EnterWorktreeTool | AgentTool |
|
||||||
|
| Session 管理 | 设置 `currentWorktreeSession` | **不设置** `currentWorktreeSession` |
|
||||||
|
| 恢复已有 worktree | 直接复用 | 复用并 bump mtime(防止被周期性清理误删) |
|
||||||
|
|
||||||
子 Agent 结束时的处理:
|
子 Agent 结束时的处理由 `cleanupWorktreeIfNeeded()` 自动完成——它不走 `ExitWorktreeTool`(因为 Agent worktree 没有会话状态,`ExitWorktreeTool` 的 `validateInput` 会拒绝):
|
||||||
- **成功**:主 Agent 通过 `ExitWorktreeTool(action: "keep")` 保留 worktree,然后手动合并
|
- **有变更** → 保留 worktree,返回 `worktreePath` 供主 Agent 后续合并
|
||||||
- **失败/放弃**:主 Agent 通过 `ExitWorktreeTool(action: "remove", discard_changes: true)` 清理
|
- **无变更** → 自动删除
|
||||||
|
- **Hook-based** → 始终保留
|
||||||
|
|
||||||
## Session 状态持久化
|
## Session 状态持久化
|
||||||
|
|
||||||
@@ -168,6 +172,7 @@ Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 wor
|
|||||||
tmuxSessionName?: string, // 关联的 tmux session
|
tmuxSessionName?: string, // 关联的 tmux session
|
||||||
hookBased?: boolean, // 是否由 hook 创建
|
hookBased?: boolean, // 是否由 hook 创建
|
||||||
creationDurationMs?: number, // 创建耗时(分析用)
|
creationDurationMs?: number, // 创建耗时(分析用)
|
||||||
|
usedSparsePaths?: boolean, // 是否使用了 sparse checkout
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user