:多 Agent 协作——子代理、团队与任务编排)
子 AgentSubAgentSpawner 协议与 AgentToolSubAgentSpawner 协议子 Agent 的生成不是 AgentTool 直接 new 一个 Agent 出来——中间隔了一层协议。SubAgentSpawner定义在Types/AgentTypes.swift里public protocol SubAgentSpawner: Sendable { func spawn( prompt: String, model: String?, systemPrompt: String?, allowedTools: [String]?, maxTurns: Int? ) async - SubAgentResult func spawn( prompt: String, model: String?, systemPrompt: String?, allowedTools: [String]?, maxTurns: Int?, disallowedTools: [String]?, mcpServers: [AgentMcpServerSpec]?, skills: [String]?, runInBackground: Bool?, isolation: String?, name: String?, teamName: String?, mode: PermissionMode?, resume: String? ) async - SubAgentResult }两个方法一个基础版5 个参数一个增强版13 个参数。协议还提供了默认实现增强版直接调用基础版这样已有的实现类不用改代码就能兼容。为什么要把 spawner 放在Types/而不是Core/因为Tools/Advanced/AgentTool.swift需要用它但Tools/不应该导入Core/。把协议定义在Types/具体实现放在Core/通过ToolContext.agentSpawner注入——这是 SDK 里常见的依赖倒置。DefaultSubAgentSpawner 实现DefaultSubAgentSpawner在Core/DefaultSubAgentSpawner.swift里做了这几件事final class DefaultSubAgentSpawner: SubAgentSpawner, unchecked Sendable { private let apiKey: String private let baseURL: String? private let parentModel: String private let parentTools: [ToolProtocol] private let provider: LLMProvider private let client: (any LLMClient)? func spawn(...) async - SubAgentResult { // 1. 过滤掉 AgentTool防止无限递归 var subTools parentTools.filter { $0.name ! Agent } // 2. 如果指定了 allowedTools进一步过滤 if let allowed allowedTools, !allowed.isEmpty { let allowedSet Set(allowed) subTools subTools.filter { allowedSet.contains($0.name) } } // 3. disallowedTools 再过一遍优先级高于 allowedTools if let disallowed disallowedTools, !disallowed.isEmpty { let disallowedSet Set(disallowed) subTools subTools.filter { !disallowedSet.contains($0.name) } } // 4. 创建子 Agent 并执行 let options AgentOptions( apiKey: apiKey, model: model ?? parentModel, systemPrompt: systemPrompt, maxTurns: maxTurns ?? 10, tools: subTools ) let agent Agent(options: options) let result await agent.prompt(prompt) return SubAgentResult( text: result.text.isEmpty ? (Subagent completed with no text output) : result.text, toolCalls: [], isError: result.status ! .success ) } }几个关键点防递归子 Agent 不会再拿到 AgentTool所以不会出现 Agent 套 Agent 套 Agent 的情况工具继承子 Agent 默认继承父 Agent 的所有工具除了 AgentTool但可以通过allowedTools/disallowedTools限制阻塞式执行父 Agent 调用spawn()后会 await等子 Agent 跑完才继续AgentToolLLM 眼里的子 Agent 工具AgentTool是暴露给 LLM 的工具。LLM 调用Agent工具时传入 prompt 和参数AgentTool 负责调用 spawner 生成子 Agent。它内置了两种预定义的子 Agent 类型private let BUILTIN_AGENTS: [String: AgentDefinition] [ Explore: AgentDefinition( name: Explore, description: Fast agent specialized for exploring codebases..., systemPrompt: You are a codebase exploration agent. Search through files and code to answer questions..., tools: [Read, Glob, Grep, Bash], maxTurns: 10 ), Plan: AgentDefinition( name: Plan, description: Software architect agent for designing implementation plans..., systemPrompt: You are a software architect. Design implementation plans..., tools: [Read, Glob, Grep, Bash], maxTurns: 10 ), ]Explore代码库探索用 Glob 找文件、Grep 搜内容、Read 读文件Plan软件架构师理解代码库后输出实施方案LLM 调用 AgentTool 时通过subagent_type字段指定用哪种{ prompt: Explore the project structure and find all Swift source files, description: Explore codebase, subagent_type: Explore }AgentTool 还支持一堆可选参数model指定模型、maxTurns覆盖轮次上限、run_in_background后台运行、isolation隔离模式比如 worktree、team_name关联团队、mode权限模式。这些参数直接透传给 spawner。一个完整的示例SDK 自带了一个 SubagentExample演示了主 Agent 作为协调者通过 AgentTool 委派 Explore 子 Agent 的完整流程// 主 Agent 的系统提示 let systemPrompt You are a coordinator agent. When given a task, you should delegate it to a sub-agent \ using the Agent tool. The Agent tool will spawn a specialized agent (e.g., Explore type) \ that can use Read, Glob, Grep, and Bash tools to investigate the codebase. \ After the sub-agent returns its findings, summarize the results for the user. // 注册工具核心工具 AgentTool let agent createAgent(options: AgentOptions( apiKey: apiKey, model: defaultModel, systemPrompt: systemPrompt, maxTurns: 10, tools: getAllBaseTools(tier: .core) [createAgentTool()] )) // 发任务——主 Agent 会调用 AgentTool 委派给 Explore 子 Agent for await message in agent.stream( Explore the current project directory. Find all Swift source files, \ examine the project structure, and provide a summary. \ Use the Agent tool to delegate this task to an Explore sub-agent. ) { switch message { case .toolUse(let data): if data.toolName Agent { print([Sub-agent Delegation: \(data.toolName)]) } case .toolResult(let data): print([Result: \(data.content.prefix(200))]) case .result(let data): print(Turns: \(data.numTurns), Cost: $\(data.totalCostUsd)) default: break } }执行流程用户发 prompt - 主 Agent 判断需要探索代码库 - 调用 AgentTool - AgentTool 通过 spawner 生成 Explore 子 Agent - 子 Agent 用 Glob/Grep/Read 执行探索 - 结果返回给主 Agent - 主 Agent 汇总后回复用户。二、Task 系统任务追踪与状态机子 Agent 解决了谁干活的问题Task 系统解决的是活干了多少、谁在干、结果是什么的问题。TaskStore线程安全的 ActorTaskStore是一个 Swift Actor保证并发安全public actor TaskStore { private var tasks: [String: Task] [:] private var taskCounter: Int 0 public func create( subject: String, description: String? nil, owner: String? nil, status: TaskStatus .pending ) - Task { taskCounter 1 let id task_\(taskCounter) let now dateFormatter.string(from: Date()) let task Task( id: id, subject: subject, description: description, status: status, owner: owner, createdAt: now, updatedAt: now ) tasks[id] task return task } }用 Actor 而不是普通类意味着所有方法都是隐式串行化的——不需要自己加锁。多个 Agent 同时创建任务不会出现竞态条件。Task 的状态机Task 有 5 种状态流转规则很明确public enum TaskStatus: String, Sendable, Equatable, Codable { case pending // 等待开始 case inProgress // 进行中 case completed // 已完成 case failed // 失败 case cancelled // 已取消 }状态转换有约束pending和inProgress可以转到任何状态但completed、failed、cancelled是终态不可再变private func isValidTransition(from: TaskStatus, to: TaskStatus) - Bool { switch from { case .pending, .inProgress: return true case .completed, .failed, .cancelled: return false // 终态不能再转 } }画成状态图pending ──→ inProgress ──→ completed │ │ │ ├──→ failed │ │ └──→ cancelled ←──┘TaskStatus还有个贴心的parse()方法同时支持 camelCaseinProgress和 snake_casein_progress因为 LLM 返回的 JSON 格式不一定统一public static func parse(_ string: String) - TaskStatus? { if let direct TaskStatus(rawValue: string) { return direct } // snake_case → camelCase let camel string .split(separator: _) .enumerated() .map { $0.offset 0 ? String($0.element) : String($0.element).capitalized } .joined() return TaskStatus(rawValue: camel) }Task 结构体一个 Task 实例除了基本的状态追踪还预留了依赖关系和元数据public struct Task: Sendable, Equatable, Codable { public let id: String public var subject: String public var description: String? public var status: TaskStatus public var owner: String? // 谁在干 public let createdAt: String public var updatedAt: String public var output: String? // 结果 public var blockedBy: [String]? // 被哪些任务阻塞 public var blocks: [String]? // 阻塞了哪些任务 public var metadata: [String: String]? }blockedBy和blocks字段说明 Task 系统预留了任务依赖的能力——任务 A 可以声明我需要等任务 B 和 C 完成才能开始。三个 Task 工具SDK 提供了三个工具让 LLM 操作 Task 系统TaskCreate-- 创建任务public func createTaskCreateTool() - ToolProtocol { return defineTool( name: TaskCreate, description: Create a new task for tracking work progress., inputSchema: taskCreateSchema, isReadOnly: false ) { (input: TaskCreateInput, context: ToolContext) in guard let taskStore context.taskStore else { return ToolExecuteResult(content: Error: TaskStore not available., isError: true) } let initialStatus: TaskStatus input.status.flatMap { TaskStatus.parse($0) } ?? .pending let task await taskStore.create( subject: input.subject, description: input.description, owner: input.owner, status: initialStatus ) return ToolExecuteResult( content: Task created: \(task.id) - \\(task.subject)\ (\(task.status.rawValue)), isError: false ) } }TaskList-- 列出任务支持按 status 和 owner 过滤// LLM 可以查 列出所有 pending 状态的任务 或 列出分配给 agent-1 的任务 let tasks await taskStore.list(status: status, owner: input.owner)TaskUpdate-- 更新任务状态、描述、负责人、输出do { let task try await taskStore.update( id: input.id, status: status, description: input.description, owner: input.owner, output: input.output ) return ToolExecuteResult( content: Task updated: \(task.id) - \(task.status.rawValue) - \\(task.subject)\, isError: false ) } catch let error as TaskStoreError { return ToolExecuteResult(content: Error: \(error.localizedDescription), isError: true) }注意 TaskUpdate 会抛出invalidStatusTransition错误——比如试图把一个completed的任务改成inProgressLLM 会收到错误提示可以据此调整策略。三、Team 系统团队组建与管理