本文是「深入 Open Agent SDK (Swift)」系列第二篇。
上一篇分析了 Agent Loop 的运转机制,其中有一个环节是"执行工具"——LLM 说"我要调 Bash",SDK 就真的起一个进程跑命令。但这背后的工具系统远不止"调个函数"那么简单。34 个内置工具怎么组织?怎么从 LLM 的 JSON 输入安全地转成 Swift 类型?怎么控制哪些工具能用?
这篇文章从协议定义开始,一层一层看 Open Agent SDK 的工具系统。
SDK 里每个工具都遵循 ToolProtocol 协议:
public protocol ToolProtocol: Sendable {
var name: String { get }
var description: String { get }
var inputSchema: ToolInputSchema { get }
var isReadOnly: Bool { get }
var annotations: ToolAnnotations? { get }
func call(input: Any, context: ToolContext) async -> ToolResult
}
五个属性一个方法,逐个说。
name 是工具的唯一标识,LLM 在 tool_use block 里用这个名字指定要调哪个工具。所有内置工具用 PascalCase 命名:Read、Bash、Glob、CronCreate。
description 是给 LLM 看的工具说明。这段文字会作为 tool definition 的一部分发给 API,质量直接影响 LLM 什么时候会选择调用这个工具。
inputSchema 是一个 [String: Any] 类型的 JSON Schema 字典,描述工具接受的输入结构。API 调用时它被原样传给 input_schema 字段。
isReadOnly 是一个布尔标记,用来告诉 Agent Loop 这个工具有没有副作用。上一篇提到过,Agent Loop 用这个字段做分桶:只读工具并发执行,变更工具串行执行。
annotations 是可选的行为提示,包含四个布尔字段:
public struct ToolAnnotations: Sendable, Equatable {
public let readOnlyHint: Bool // 只读,无副作用
public let destructiveHint: Bool // 可能做不可逆操作
public let idempotentHint: Bool // 幂等,多次调用结果相同
public let openWorldHint: Bool // 会和外部世界交互
}
注意 destructiveHint 默认是 true——SDK 对工具采取"默认危险"策略,工具需要主动声明自己不危险。这些提示不会影响 SDK 自身的执行逻辑,但 LLM 会参考它们决定怎么使用工具。
call() 方法返回 ToolResult,这是工具执行后喂回给 LLM 的内容:
public struct ToolResult: Sendable {
public let toolUseId: String // 对应 LLM 返回的 tool_use ID
public let content: String // 文本内容
public let typedContent: [ToolContent]? // 多模态内容(文本、图片、资源引用)
public let isError: Bool // 是否为错误结果
}
content 和 typedContent 之间有个兼容设计:当 typedContent 有值时,content 会从中提取所有 .text 类型拼接返回;否则直接返回存储的字符串。这样旧代码只用 content 也能正常工作,新代码可以用 typedContent 返回图片等非文本内容。
ToolContent 是一个枚举,支持三种内容类型:
public enum ToolContent: Sendable {
case text(String)
case image(data: Data, mimeType: String)
case resource(uri: String, name: String?)
}
工具闭包内部用的是 ToolExecuteResult——结构和 ToolResult 几乎一样,只是少了 toolUseId(这个 ID 由调用层自动填充)。
ToolContext 是每次工具执行时注入的上下文,字段很多:
| 字段 | 用途 |
|---|---|
cwd |
当前工作目录 |
toolUseId |
本次调用的 tool_use ID |
agentSpawner |
子 Agent 生成器(AgentTool 用) |
cronStore |
定时任务存储(CronTools 用) |
todoStore |
待办事项存储(TodoWrite 用) |
worktreeStore |
工作树存储(WorktreeTools 用) |
planStore |
计划模式存储(PlanTools 用) |
taskStore |
任务管理存储(Task*Tools 用) |
mailboxStore |
邮箱存储(SendMessage 用) |
teamStore |
团队存储(TeamCreate 用) |
hookRegistry |
Hook 事件注册表 |
permissionMode |
权限模式 |
canUseTool |
自定义权限检查回调 |
skillRegistry |
技能注册表(SkillTool 用) |
restrictionStack |
工具限制栈 |
sandbox |
沙箱设置 |
mcpConnections |
MCP 连接信息 |
fileCache |
文件缓存 |
env |
自定义环境变量 |
这么多可选字段,规则很简单:工具需要什么就注入什么,不需要的就是 nil。Read 工具只看 cwd、sandbox、fileCache;AgentTool 只看 agentSpawner;CronTools 只看 cronStore。每个工具只依赖自己需要的那个 Store,不知道也不关心其他 Store 的存在。
ToolContext 还提供了两个 copy 方法:withToolUseId() 用于更新调用 ID(每次工具执行时由 ToolExecutor 调用),withSkillContext() 用于递增技能嵌套深度(SkillTool 调用子技能时使用)。
SDK 把 34 个工具分成三个层级:Core(10 个)、Advanced(11 个)、Specialist(13 个)。
Core 层 (10) Advanced 层 (11) Specialist 层 (13)
┌──────────┐ ┌──────────────┐ ┌───────────────┐
│ Read │ │ Agent │ │ CronCreate │
│ Write │ │ Skill │ │ CronDelete │
│ Edit │ │ TaskCreate │ │ CronList │
│ Glob │ │ TaskGet │ │ LSP │
│ Grep │ │ TaskList │ │ Config │
│ Bash │ │ TaskOutput │ │ TodoWrite │
│ AskUser │ │ TaskStop │ │ EnterPlanMode │
│ ToolSearch│ │ TaskUpdate │ │ ExitPlanMode │
│ WebFetch │ │ SendMessage │ │ EnterWorktree │
│ WebSearch │ │ TeamCreate │ │ ExitWorktree │
└──────────┘ │ TeamDelete │ │ RemoteTrigger │
│ NotebookEdit │ │ ListMcpRes │
└──────────────┘ │ ReadMcpRes │
└───────────────┘
分层的依据不是技术实现难度,而是工具的依赖复杂度和使用场景。
Core 层的 10 个工具是 Agent 的基础能力——读文件、写文件、搜索代码、跑命令。它们有一个共同特点:只依赖 ToolContext 的基础字段(cwd、sandbox、fileCache),不需要注入任何 Store。
拿 Read 工具来说。它的输入是文件路径、可选的 offset 和 limit:
private struct FileReadInput: Codable {
let file_path: String
let offset: Int?
let limit: Int?
}
执行逻辑很直接:解析路径 → 检查沙箱 → 查缓存 → 读文件 → 分页 → 返回带行号的内容。还有个文件缓存的细节:如果 context.fileCache 有值,先查缓存,命中就跳过磁盘 I/O。
再看 Bash 工具。它比 Read 复杂得多,因为要处理超时、输出截断、后台进程等问题。Bash 的输入有 5 个字段:
private struct BashInput: Codable {
let command: String
let timeout: Int?
let description: String?
let runInBackground: Bool?
let dangerouslyDisableSandbox: Bool?
}
几个关键实现细节:
DispatchQueue.global().asyncAfter 设置超时,超时后 process.terminate() 杀掉进程。...(truncated)... 连接。run_in_background = true 时,进程起起来就返回一个 task ID,不等待完成。ProcessOutputAccumulator 收集,用 @unchecked Sendable 标注,因为 Pipe 的 readability handler 和 termination handler 都在同一个 run loop dispatch queue 上触发,不会产生数据竞争。Bash 工具的 annotations 设置了 destructiveHint: true,明确告诉 LLM 这个工具有破坏性。
Advanced 层的工具开始需要外部依赖了——AgentTool 需要 agentSpawner,Task* 系列需要 taskStore,SendMessage 需要 mailboxStore 和 teamStore。
Agent 工具是这一层的代表。它的作用是让 LLM 能"派出一个子 Agent"去完成复杂任务:
public func createAgentTool() -> ToolProtocol {
return defineTool(
name: "Agent",
description: "Launch a subagent to handle complex, multi-step tasks autonomously.",
inputSchema: agentToolSchema,
isReadOnly: false
) { (input: AgentToolInput, context: ToolContext) async throws -> ToolExecuteResult in
guard let spawner = context.agentSpawner else {
return ToolExecuteResult(
content: "Error: Agent spawner not available.",
isError: true
)
}
// 解析内置 Agent 类型、权限模式,然后 spawn 子 Agent
let result = await spawner.spawn(
prompt: input.prompt,
model: input.model ?? agentDef?.model,
systemPrompt: agentDef?.systemPrompt,
allowedTools: agentDef?.tools,
...
)
return ToolExecuteResult(content: result.text, isError: result.isError)
}
}
AgentTool 的输入支持 11 个字段:prompt、description、subagent_type、model、name、maxTurns、run_in_background、isolation、team_name、mode、resume。其中 subagent_type 可以指定内置的 Explore 或 Plan 类型,也可以用自定义名称。
注意 agentSpawner 是通过 ToolContext 注入的协议类型——AgentTool 不知道子 Agent 是怎么创建的,它只调 spawner.spawn(),具体实现由 Core 层注入。这种依赖倒置让工具层完全不用 import Core 模块。
Specialist 层的工具依赖更重——它们各自需要一个专属 Store,而且功能高度领域化。
CronTools 是一组三个工具:CronCreate、CronDelete、CronList,通过 context.cronStore 访问定时任务存储:
public func createCronCreateTool() -> ToolProtocol {
return defineTool(
name: "CronCreate",
description: "Create a scheduled recurring task (cron job).",
inputSchema: cronCreateSchema,
isReadOnly: false
) { (input: CronCreateInput, context: ToolContext) async throws -> ToolExecuteResult in
guard let cronStore = context.cronStore else {
return ToolExecuteResult(content: "Error: CronStore not available.", isError: true)
}
let job = await cronStore.create(
name: input.name,
schedule: input.schedule,
command: input.command
)
return ToolExecuteResult(
content: "Cron job created: \(job.id) \"\(job.name)\"",
isError: false
)
}
}
三个工具都用 guard let cronStore = context.cronStore 做前置检查——如果 Store 没注入,直接返回错误而不是崩溃。
LSP 工具是另一个有趣的例子。它用 grep 模拟 Language Server Protocol 的常见操作(跳转定义、查找引用、符号搜索),完全不依赖真正的语言服务器:
case "goToDefinition", "goToImplementation":
// 1. 用正则提取光标位置的符号名
guard let symbol = getSymbolAtPosition(
filePath: filePath, line: line, character: character
) else { ... }
// 2. grep 搜索定义模式
let pattern = "(func|class|struct|enum|protocol|typealias|let|var|export)\\s+\(symbol)"
let results = await runGrep(
arguments: ["grep", "-rn", "-E", pattern, cwd],
cwd: cwd
)
LSP 工具只依赖 context.cwd,不需要任何 Store——属于 Specialist 层里最轻量的工具。
SDK 提供了 defineTool 工厂函数,让开发者用最少的代码创建符合 ToolProtocol 的工具。它有四个重载,覆盖不同的使用场景。
最常用的重载接受一个 Codable 输入类型和一个返回 String 的闭包:
let greetTool = defineTool(
name: "Greet",
description: "Generate a greeting message.",
inputSchema: [
"type": "object",
"properties": [
"name": ["type": "string", "description": "Person's name"]
],
"required": ["name"]
],
isReadOnly: true
) { (input: GreetInput, context: ToolContext) async throws -> String in
return "Hello, \(input.name)!"
}
// 输入类型只需要遵循 Codable
struct GreetInput: Codable {
let name: String
}
defineTool 内部做了四件事:
Any 类型 cast 成 [String: Any]JSONSerialization 序列化成 DataJSONDecoder 解码成你定义的 Input 类型任何一步失败(输入不是字典、JSON 序列化失败、解码失败、闭包抛异常),都会返回 isError: true 的结果,不会炸掉 Agent Loop。这意味着你可以放心地用 try 在闭包里抛错误,它们会被妥善捕获。
如果工具需要显式标记错误(而不是用 try 抛异常),用返回 ToolExecuteResult 的重载:
let divideTool = defineTool(
name: "Divide",
description: "Divide two numbers.",
inputSchema: [
"type": "object",
"properties": [
"a": ["type": "number"],
"b": ["type": "number"]
],
"required": ["a", "b"]
]
) { (input: DivideInput, context: ToolContext) async throws -> ToolExecuteResult in
guard input.b != 0 else {
return ToolExecuteResult(content: "Error: Division by zero.", isError: true)
}
return ToolExecuteResult(content: "\(input.a / input.b)", isError: false)
}
内置工具大多用这个重载,因为很多错误是逻辑层面的(文件不存在、Store 没注入),不适合用异常表示。
有些工具不需要输入参数(比如列表操作、健康检查),用无输入重载:
let listTool = defineTool(
name: "ListItems",
description: "List all items.",
inputSchema: ["type": "object", "properties": [:]]
) { (context: ToolContext) async throws -> String in
return "No items found."
}
闭包只接收 ToolContext,完全忽略输入。
最后一个重载跳过 Codable 解码,直接把原始 [String: Any] 字典传给闭包。适用于输入字段类型不固定的场景——比如 ConfigTool 的 value 字段可以是字符串、数字、布尔值、数组、对象或 null:
let configTool = defineTool(
name: "Config",
description: "Read or write configuration values.",
inputSchema: configSchema
) { (input: [String: Any], context: ToolContext) async -> ToolExecuteResult in
let key = input["key"] as? String ?? ""
let value = input["value"] // 任意类型
// ...
}
LLM 发来的 JSON 字段名通常用 snake_case(比如 file_path、run_in_background),但 Swift 的惯用命名是 camelCase。输入类型通过 CodingKeys 枚举做映射:
private struct BashInput: Codable {
let command: String
let runInBackground: Bool?
private enum CodingKeys: String, CodingKey {
case command
case runInBackground = "run_in_background"
}
}
这是 Swift Codable 的标准做法——defineTool 内部的 JSONDecoder 会自动用 CodingKeys 做字段名转换。
工具不是直接一股脑丢给 LLM 的。SDK 有一套组装和过滤机制。
assembleToolPool 把三类工具来源合并成一个去重后的工具池:
public func assembleToolPool(
baseTools: [ToolProtocol], // SDK 内置工具
customTools: [ToolProtocol]?, // 用户自定义工具
mcpTools: [ToolProtocol]?, // MCP 服务器提供的工具
allowed: [String]?,
disallowed: [String]?
) -> [ToolProtocol] {
// 1. 合并所有来源:base + custom + MCP
var combined = baseTools
if let customTools { combined.append(contentsOf: customTools) }
if let mcpTools { combined.append(contentsOf: mcpTools) }
// 2. 按名称去重(后者覆盖前者)
var byName = [String: ToolProtocol]()
for tool in combined {
byName[tool.name] = tool
}
// 3. 应用过滤规则
return filterTools(
tools: Array(byName.values),
allowed: allowed,
disallowed: disallowed
)
}
去重用 Dictionary,遍历过程中同名的后者会覆盖前者。这意味着优先级是:MCP > 自定义 > 内置——用户可以用自定义工具或 MCP 工具替换同名内置工具。
filterTools 实现白名单/黑名单过滤:
public func filterTools(
tools: [ToolProtocol],
allowed: [String]?, // 白名单,nil 或空表示不过滤
disallowed: [String]? // 黑名单,nil 或空表示不过滤
) -> [ToolProtocol] {
var filtered = tools
// 先应用白名单
if let allowed, !allowed.isEmpty {
let allowedSet = Set(allowed)
filtered = filtered.filter { allowedSet.contains($0.name) }
}
// 再应用黑名单(黑名单优先于白名单)
if let disallowed, !disallowed.isEmpty {
let disallowedSet = Set(disallowed)
filtered = filtered.filter { !disallowedSet.contains($0.name) }
}
return filtered
}
两个规则同时存在时,黑名单优先——即使一个工具在白名单里,只要出现在黑名单里也会被排除。
ToolRestrictionStack 是一个栈结构,用于 Skills 系统中控制工具可见范围。当一个 Skill 配置了 toolRestrictions 时,执行前 push 限制,执行后 pop 恢复:
let stack = ToolRestrictionStack()
stack.push([.bash, .read]) // Skill A:只能用 Bash 和 Read
stack.push([.grep, .glob]) // Skill B(嵌套):只能用 Grep 和 Glob
// 此时 currentAllowedToolNames 只返回 Grep 和 Glob
stack.pop() // Skill B 完成 → 回到 Bash 和 Read
stack.pop() // Skill A 完成 → 恢复全部工具
栈的 LIFO 特性保证了嵌套 Skill 的正确行为——内层 Skill 的限制覆盖外层,退出后自动恢复。线程安全通过内部串行 DispatchQueue 保证。
currentAllowedToolNames 的逻辑很简单:栈空就返回全部工具,栈非空就只返回栈顶限制列表里的工具名。
最后一步是把工具转成 Anthropic API 要求的格式:
public func toApiTool(_ tool: ToolProtocol) -> [String: Any] {
var result: [String: Any] = [
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
]
if let annotations = tool.annotations {
result["annotations"] = [
"readOnlyHint": annotations.readOnlyHint,
"destructiveHint": annotations.destructiveHint,
"idempotentHint": annotations.idempotentHint,
"openWorldHint": annotations.openWorldHint
]
}
return result
}
annotations 只在有值时才包含——省点 token。
把上面说的一切串起来,写一个能直接跑的自定义工具——获取天气:
import Foundation
import OpenAgentSDK
// 1. 定义输入类型
struct WeatherInput: Codable {
let city: String
let unit: String? // "celsius" or "fahrenheit"
private enum CodingKeys: String, CodingKey {
case city, unit
}
}
// 2. 用 defineTool 创建工具
let weatherTool = defineTool(
name: "Weather",
description: "Get current weather for a city.",
inputSchema: [
"type": "object",
"properties": [
"city": [
"type": "string",
"description": "City name, e.g. 'Beijing'"
],
"unit": [
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit, defaults to celsius"
]
],
"required": ["city"]
],
isReadOnly: true,
annotations: ToolAnnotations(
readOnlyHint: true,
destructiveHint: false,
openWorldHint: true // 要访问外部 API
)
) { (input: WeatherInput, context: ToolContext) async throws -> ToolExecuteResult in
let unit = input.unit ?? "celsius"
// 调用天气 API(这里省略具体实现)
let weather = try await fetchWeather(city: input.city, unit: unit)
return ToolExecuteResult(content: weather, isError: false)
}
// 3. 注册到 Agent
let agent = createAgent(options: AgentOptions(
apiKey: "sk-...",
model: "claude-sonnet-4-6",
customTools: [weatherTool] // 自定义工具自动加入工具池
))
这个工具会被 assembleToolPool 和内置工具合并、去重、过滤后发给 LLM。LLM 看到工具定义后,在需要查天气时会自动调用它。defineTool 内部的 Codable 桥接会把 LLM 返回的 JSON 自动解码成 WeatherInput,你不需要手动处理任何 JSON 解析。
工具系统的设计思路可以概括为几个关键词:
协议驱动。ToolProtocol 只规定工具的形状(名字、描述、输入 schema、执行方法),不规定工具怎么实现。这让内置工具和自定义工具走完全一样的代码路径。
依赖注入。ToolContext 的 20+ 个可选字段看着多,但每个工具只看自己需要的字段,其余全是 nil。AgentTool 不知道 CronStore 的存在,CronCreate 不知道 SubAgentSpawner 的存在。
分层组织。Core/Advanced/Specialist 三层不是代码分层(它们的代码结构完全一样),而是按依赖复杂度划分。Core 层的工具可以独立运行,Advanced 层需要 Store,Specialist 层需要更专业的领域设施。
容错优先。defineTool 内部把所有可能的失败点(类型转换、序列化、解码、执行)都包在 do/catch 里,任何环节出错都返回 isError: true 而不是 crash。Agent Loop 里工具错误不会传播,LLM 拿到错误信息后可以换策略。
下一篇来看 MCP 集成:SDK 怎么连接外部工具服务器、怎么把 MCP 工具转成 ToolProtocol、怎么在 Agent Loop 里和内置工具共存。
系列文章:
GitHub:terryso/open-agent-sdk-swift
本文是「深入 Open Agent SDK (Swift)」系列第一篇。
大多数 LLM 封装库做的事情是:发请求、拿响应、结束。但一个真正的 Agent 不止于此——它要能自己判断需不需要调工具、执行完工具后把结果喂回 LLM、循环往复直到拿到最终答案。这个循环就是 Agent Loop。
这篇文章分析 Open Agent SDK (Swift) 的 Agent Loop 实现,看它怎样用原生 Swift 并发在进程内跑完一整套循环。
用一句话概括:**用户发 prompt → LLM 返回响应 → 如果 LLM 要求调工具就执行 → 把工具结果喂回 LLM → 重复,直到 LLM 说"我说完了"**。
画成流程图:
flowchart TD
A["用户 prompt"] --> B["构建 messages + tools"]
B --> C["调用 LLM API"]
C -->|end_turn / stop_sequence| D["返回结果"]
C -->|max_tokens| C2["追加'请继续'"]
C2 --> C
C -->|tool_use| E["提取 tool_use blocks"]
E --> F["按只读/变更分桶"]
F --> G["只读工具并发执行"]
F --> H["变更工具串行执行"]
G --> I["微压缩大结果"]
H --> I
I --> J["tool_result 加入 messages"]
J --> C
这个循环里有几个关键决策点:
end_turn 或 stop_sequence 时正常结束;到达 maxTurns 上限时强制停止;超出预算 (maxBudgetUsd) 时中断;用户主动取消时也中断。SDK 提供两种方式触发 Agent Loop:
let agent = createAgent(options: AgentOptions(
apiKey: "sk-...",
model: "claude-sonnet-4-6",
maxTurns: 10
))
let result = await agent.prompt("Read Package.swift and summarize it.")
print(result.text)
print("Turns: \(result.numTurns), Cost: $\(String(format: "%.4f", result.totalCostUsd))")
prompt() 是"发出去等结果"模式。一次调用跑完所有轮次,返回最终的 QueryResult。适合不需要实时看到中间过程的场景——比如后台任务、CLI 工具。
for await message in agent.stream("Explain this codebase.") {
switch message {
case .partialMessage(let data):
print(data.text, terminator: "") // 实时输出文本
case .toolUse(let data):
print("[Using tool: \(data.toolName)]")
case .toolResult(let data):
print("[Tool done, \(data.content.count) chars]")
case .result(let data):
print("\nDone: \(data.numTurns) turns, $\(String(format: "%.4f", data.totalCostUsd))")
default:
break
}
}
stream() 返回 AsyncStream<SDKMessage>,在 LLM 处理过程中持续推送事件。SDK 定义了 17 种消息类型,从 partialMessage(文本片段)到 toolUse(工具调用)到 result(最终结果),覆盖了 Agent Loop 的每个阶段。
选择哪种取决于你的 UI 需求:要实时展示就用 stream(),不需要就用 prompt()。
不管走哪条入口,每个 turn 的核心逻辑是相同的。让我们跟一遍代码。
if shouldAutoCompact(messages: messages, model: model, state: compactState) {
let (newMessages, _, newState) = await compactConversation(
client: client, model: model,
messages: messages, state: compactState,
fileCache: fileCache,
sessionMemory: sessionMemory
)
messages = newMessages
compactState = newState
}
每个 turn 开始前先检查:消息历史估计的 token 数是不是快要撑爆上下文窗口了。如果是,用一个 LLM 调用把历史压缩成摘要,替换掉原始消息。
压缩的阈值是 模型上下文窗口 - 10000 tokens(缓冲区)。连续压缩失败 3 次后会停止尝试,避免浪费 token。
response = try await withRetry({
try await client.sendMessage(
model: model, messages: messages,
maxTokens: maxTokens, system: buildSystemPrompt(),
tools: apiTools, ...
)
}, retryConfig: retryConfig)
所有 LLM 请求都经过 withRetry 包装,按配置的重试策略处理临时错误(网络超时、429 限流等)。
如果主模型彻底失败,还配置了 fallbackModel,SDK 会用备用模型再试一次:
if let fallbackModel = self.options.fallbackModel, fallbackModel != self.model {
// 用 fallbackModel 重试...
}
LLM 响应里的 stop_reason 决定了循环的走向:
| stop_reason | 含义 | 循环行为 |
|---|---|---|
end_turn |
LLM 说完了 | 正常退出循环 |
stop_sequence |
碰到停止符 | 正常退出循环 |
tool_use |
LLM 想调工具 | 执行工具,继续循环 |
max_tokens |
输出被截断 | 追加"请继续",继续循环 |
max_tokens 的情况有个保护:最多自动续接 3 次,防止无限循环。
当 LLM 返回 tool_use 时,SDK 不是简单地把工具排着队一个个跑,而是做了分桶:
// ToolExecutor.partitionTools()
for block in blocks {
let tool = tools.first { $0.name == block.name }
if let tool = tool, tool.isReadOnly {
readOnly.append(item) // 只读桶
} else {
mutations.append(item) // 变更桶
}
}
只读工具(Read、Glob、Grep、WebSearch 等)可以安全并发,用 TaskGroup 跑,最多 10 个一批:
let batchResults = await withTaskGroup(of: ToolResult.self) { group in
for item in batchSlice {
group.addTask {
await executeSingleTool(block: item.block, tool: item.tool, context: ...)
}
}
// 收集结果
}
变更工具(Write、Edit、Bash 等)必须串行执行,一个跑完再跑下一个,避免并发写冲突:
for item in items {
let result = await executeSingleTool(...)
results.append(result)
}
执行顺序:先跑所有只读工具(并发),再跑所有变更工具(串行)。这在 LLM 一次返回多个工具调用时能显著提升性能——比如 LLM 同时要求读 5 个文件,5 个读操作并行完成。
工具执行完后,结果在喂回 LLM 之前还要过一道微压缩:
for result in toolResults {
let processedContent = await processToolResult(result.content, isError: result.isError)
processedResults.append(ToolResult(
toolUseId: result.toolUseId,
content: processedContent,
isError: result.isError
))
}
如果一个工具返回的内容超过 50000 字符(比如读了一个大文件),SDK 会用一次额外的 LLM 调用把内容压缩。错误结果不压缩——保留了完整的错误信息供 LLM 诊断。
每一轮 LLM 调用后,SDK 都会更新 token 用量和费用:
let turnCost = estimateCost(model: model, usage: turnUsage)
totalCostUsd += turnCost
costByModel[model] = CostBreakdownEntry(
model: model,
inputTokens: turnUsage.inputTokens,
outputTokens: turnUsage.outputTokens,
costUsd: turnCost
)
costByModel 按 model 分组记录。这意味着如果你中途切换了模型(通过 switchModel()),每个模型的费用是分开计算的。最终 result.costBreakdown 能告诉你每个模型花了多少钱。
预算检查在每个 turn 后执行:
if let budget = options.maxBudgetUsd, totalCostUsd > budget {
status = .errorMaxBudgetUsd
break
}
超出预算时立即退出循环,但已产生的文本会保留在结果里——你拿到的是部分结果,不是空白的。
Swift 的结构化并发用 Task.isCancelled 做协作式取消。SDK 在循环的多个检查点都检查了这个标志:
// 循环入口
if Task.isCancelled || _interrupted {
status = .cancelled
break
}
// 只读/变更之间
if Task.isCancelled { return results }
stream() 还额外支持通过 interrupt() 方法取消——内部就是 cancel 掉持有 stream 的 Task。
取消后返回的是 QueryResult(isCancelled: true),附带截止到取消时刻的部分文本和 token 用量。
SDK 的错误处理原则是:工具执行错误不传播,API 错误有重试,最终失败保留部分结果。
工具执行时,任何错误都被捕获为 ToolResult(isError: true):
static func executeSingleTool(...) async -> ToolResult {
guard let tool = tool else {
return ToolResult(toolUseId: block.id, content: "Error: Unknown tool", isError: true)
}
// ... try executing
let result = await tool.call(input: block.input, context: context)
return ToolResult(toolUseId: block.id, content: result.content, isError: result.isError)
}
工具报错的结果照样喂回 LLM,LLM 看到错误信息后可以决定换个策略。Agent Loop 不会因为一个工具挂了就崩溃。
API 层面的错误(网络问题、500 等)会触发重试;重试失败后触发 fallback 模型;全挂了才返回 errorDuringExecution 状态。
Agent Loop 在关键节点触发 Hook 事件:
| Hook 事件 | 触发时机 |
|---|---|
sessionStart |
循环开始前 |
preToolUse |
每个工具执行前 |
postToolUse |
工具成功执行后 |
postToolUseFailure |
工具执行失败后 |
stop |
循环结束时(正常或异常) |
sessionEnd |
返回结果前 |
Hook 的一个典型用法是在 preToolUse 拦截危险操作:
await hookRegistry.register(.preToolUse, definition: HookDefinition(
matcher: "Bash",
handler: { input in
return HookOutput(message: "Bash blocked in production", block: true)
}
))
被 Hook 拦截的工具不会执行,而是返回一个错误结果——LLM 会看到"Bash blocked in production",可以换个方式完成任务。
除了 prompt() 和 stream(),SDK 还提供了第三种入口——streamInput(),接受一个 AsyncStream<String> 作为输入:
let input = AsyncStream<String> { continuation in
continuation.yield("What's in this project?")
continuation.yield("Now explain the test structure.")
continuation.finish()
}
for await message in agent.streamInput(input) {
// 处理每条输入对应的响应
}
每个输入元素被视为一条新的用户消息,触发一个完整的 prompt 周期。这适合聊天式交互:用户的每条消息都是输入流的一个元素,Agent 逐条处理并流式输出。
Agent Loop 是整个 SDK 的心脏。理解了它的工作方式,剩下的功能都是在它的基础上叠加的:
下一篇我们深入 工具系统:34 个内置工具怎么组织、ToolProtocol 协议的设计思路、以及怎么用 defineTool 创建自定义工具。
系列文章:
GitHub:terryso/open-agent-sdk-swift
如果你是一名 Swift 开发者,想要在自己的 macOS 应用中集成 AI Agent 能力,选择并不多。大多数 Agent 框架都是 Python 或 TypeScript 的,Swift 生态几乎没有成熟的解决方案。Open Agent SDK (Swift) 正是为了填补这个空白而生的。
Open Agent SDK 用 Swift 6.1 编写,要求 macOS 13+。它在进程内跑完整个 Agent Loop:发送提示、解析响应、执行工具调用、把结果喂回 LLM,循环往复直到拿到最终答案。全程用原生 Swift 并发(async/await、AsyncStream)驱动。
项目灵感来自 open-agent-sdk-typescript,把同样的 Agent 架构搬到了 Swift 生态。同系列还有 Go 版本。
安装只需在 Package.swift 中添加依赖:
dependencies: [
.package(url: "https://github.com/terryso/open-agent-sdk-swift.git", from: "0.1.0")
]
几行代码就能跑起一个 Agent:
import OpenAgentSDK
let agent = createAgent(options: AgentOptions(
apiKey: "sk-...",
model: "claude-sonnet-4-6",
systemPrompt: "You are a helpful assistant.",
maxTurns: 10
))
let result = await agent.prompt("Explain Swift concurrency in one paragraph.")
print(result.text)
print("Used \(result.usage.inputTokens) input + \(result.usage.outputTokens) output tokens")
prompt() 是阻塞式的,一次调用完成整个 Agent Loop。如果需要流式输出,用 stream():
for await message in agent.stream("Read Package.swift and summarize it.") {
switch message {
case .partialMessage(let data):
print(data.text, terminator: "")
case .toolUse(let data):
print("Using tool: \(data.toolName)")
case .result(let data):
print("\nDone (\(data.numTurns) turns, $\(String(format: "%.4f", data.totalCostUsd)))")
default:
break
}
}
你的应用 (import OpenAgentSDK)
└── Agent (prompt() / stream())
└── Agentic Loop (API 调用 → 工具执行 → 重复)
├── LLMClient Protocol (AnthropicClient / OpenAIClient)
├── 34 个内置工具
├── MCP 服务器集成
├── Session Store (JSON 持久化)
└── Hook Registry (20+ 生命周期事件)
defineTool() 自定义工具,输入走 Codable 自动解码。SDK 附带 31 个示例项目,覆盖基本用法、流式输出、自定义工具、MCP 集成、会话管理、多 Agent 协作、权限控制、沙盒、模型切换等场景。代码分为 API、Core、Hooks、MCP、Skills、Stores、Tools、Types、Utils 九个模块,约 90 个 Swift 源文件,MIT 许可证。
本系列后续文章会逐一深入每个子系统的实现细节。
深入 Open Agent SDK 系列文章:
GitHub:terryso/open-agent-sdk-swift
想看别人是怎么用 Claude Code 写代码的?或者想开一个 AI 编程直播教学?CC Live 就是干这个的。
一个单文件 Node.js 服务器,零依赖。启动后把你正在运行的 Claude Code 会话实时推送到浏览器,别人打开链接就能围观你写代码的全过程。
~/.claude/projects/ 读取所有 Claude Code 项目,侧边栏一目了然node server.js
open http://localhost:3456/
然后点分享按钮,把链接发出去就行了。
MIT 协议,欢迎 star 和 PR 🎉
用 BMAD 做开发的朋友,你是不是也有这样的困扰:每个故事都要手动跑完「创建→开发→测试→审查→修复→更新状态」这一长串流程?更别说一个 Epic 动辄 5-10 个故事,重复操作让人心烦……
如果你正在用 BMAD 方法论做开发,这套流程一定很熟悉:
/bmad-bmm-create-story 1.1 # 创建故事
/bmad-bmm-dev-story 1.1 # 开发实现
/bmad-bmm-qa-automate 1.1 # 运行测试
/bmad-bmm-code-review 1.1 # 代码审查
# 发现 HIGH/MEDIUM 问题?手动修复,再跑一遍测试……
# 最后别忘了更新 sprint-status.yaml
一个故事还好,要是 Epic 3 有 8 个故事呢?8 × 6 = 48 次命令
更崩溃的是:
这些「人工确认」环节,太容易出错了。
于是我把这套流程封装成了 Claude Code Skills。
公司项目,我建议把流程分成两部分:
Part 1: 创建故事详细设计
┌─────────────────────────────────────┐
│ /bmad-bmm-create-story 1.1 │
│ → 生成故事文档(需求、验收标准、任务) │
└─────────────────────────────────────┘
↓
人工仔细 Review
确认需求和任务拆分正确
↓
┌─────────────────────────────────────┐
│ Part 2: 执行交付 │
│ /bmad-story-deliver 1.1 │
│ → 开发 → 测试 → 审查 → 修复 → 完成 │
└─────────────────────────────────────┘
为什么? 故事详细设计决定了「做什么」和「怎么做」,这一步错了后面全白搭。公司项目需求复杂,人工把关这步不能省。
个人项目,你可以自己决定:
Review 完故事设计后,一条命令搞定剩下的:
/bmad-story-deliver
✅ [1/6] 创建用户故事(如果还没创建)
✅ [2/6] 开发实现
✅ [3/6] QA 自动化测试
✅ [4/6] 代码审查
✅ [5/6] 自动修复问题(如有)
✅ [6/6] 更新状态为 Done
故事 1.1 交付完成!
是的,连状态都帮你更新了。
我设计了三种 Skills,按需选择:
/bmad-story-deliver适合:个人项目、信任度高的项目
/bmad-story-deliver 1.1 # 交付指定故事
/bmad-story-deliver # 自动选择编号最小的 backlog 故事
一个命令完成剩余流程(故事已创建并 Review 过):
不传参数还能自动选择下一个待开发的故事。
/bmad-story-worktree适合:需要隔离开发、强制测试通过的场景
/bmad-story-worktree 1.1
快速模式也会跑测试,但即使失败也不会阻止你继续。安全模式则多了两层保障:
如果测试失败或有问题?保留 worktree,等你手动处理完再继续。
/bmad-epic-worktree适合:整个 Epic 批量交付,真正解放双手
/bmad-epic-worktree 3 # 交付 Epic 3 的所有故事
/bmad-epic-worktree # 自动选择编号最小且有未完成的 Epic
执行逻辑:
一条命令,交付整个 Epic。你可以去喝杯咖啡了 ☕
| 模式 | 运行测试 | 隔离开发 | 强制把关 | 适用场景 |
|---|---|---|---|---|
| 快速 | ✅ | ❌ | ❌ 测试失败也继续 | 快速迭代 |
| 安全 | ✅ | ✅ Worktree | ✅ 不通过不合并 | 稳妥交付 |
| 批量 | ✅ | ✅ Worktree | ✅ 不通过不合并 | 整 Epic 交付 |
# 克隆仓库
git clone https://github.com/terryso/claude-bmad-skills.git
# 安装到你的 Claude Code
cp -r claude-bmad-skills/.claude/skills/* ~/.claude/skills/
# 开始使用
/bmad-story-deliver # 交付一个故事
/bmad-epic-worktree # 交付整个 Epic
这个项目的核心理念很简单:把重复的事情自动化,但该人工把关的地方不能省。
公司项目的推荐流程:
/bmad-bmm-create-story 1.1 — 创建故事设计/bmad-story-deliver — 一键完成开发到交付个人项目: 看心情,想一步到位也行。
以前交付一个 Epic:
现在:
/bmad-epic-worktree
剩下的交给 AI,但故事设计一定要自己把关。
项目地址: github.com/terryso/claude-bmad-skills
如果你也在用 BMAD 做开发,欢迎试用反馈!⭐ Star 支持一下就更棒了~
你在 BMAD 开发中有什么效率痛点?欢迎在评论区分享。
bmad-bmm-create-story.md
bmad-bmm-dev-story.md
bmad-bmm-code-review.md
现在有上面三个工作流文档, 每次开发一个故事都是按顺序执行上面3个工作流.
我现在想你帮我创建3个teammate, 按顺序之后上面的工作流, 最后一个code-review工作流需要自动修复所有问题.
使用 Claude Code CLI 让 AI Agent 在 Moltbook 上保持活跃,零人工干预
Moltbook 是目前全球最火的 AI Agents 社交网络,一个专门为 AI 智能体打造的社交平台。在这里,只有 AI agents 能发帖、评论、点赞,人类只能围观。
截至 2026 年,已有超过 140 万个 AI agents 在这个平台上活跃。
Moltbook 官方的 heartbeat skill 是为 OpenClaw 优化的,而 Claude Code 没有内置的定时任务机制。
这个脚本填补了这个空白 —— 让使用 Claude Code 的 AI Agent 也能自动执行 Moltbook 心跳任务。
自动化互动:自动检查 DM、动态和帖子 社区参与:智能点赞、评论、欢迎新 Agent 智能发帖:根据情况决定是否发布原创内容 定时执行:支持 macOS LaunchAgent 和 Linux cron 安全可靠:凭据本地存储,绝不提交到 git 实时监控:支持实时输出,随时了解 Agent 在做什么
git clone https://github.com/terryso/moltbook-heartbeat.git
cd moltbook-heartbeat
cp config.example.json config.json
# 编辑 config.json 填入你的凭据
详细的安装步骤和定时任务配置,请查看项目 **README**。
脚本的核心是利用 Claude Code CLI 来执行 Moltbook 心跳任务:
--output-format stream-json 获取实时输出每次心跳会:
$ ./moltbook_heartbeat.sh
[2026-02-03 10:00:00] === Moltbook Heartbeat Starting ===
[2026-02-03 10:00:00] Agent: HappyClaude
[2026-02-03 10:00:00] Starting Claude Code execution...
[10:00:01] Claude initialized
Using: WebFetch
[10:00:03] Using: Bash
正在检查 Moltbook 动态...
发现了 3 条新消息!
[10:00:15] Using: WebSearch
[10:00:20] Completed (2.3s)
我刚刚完成了 Moltbook 心跳,回复了 @CodingAgent 的帖子,欢迎了 2 位新的 moltys!
[10:00:25] === Heartbeat completed ===
GitHub: https://github.com/terryso/moltbook-heartbeat
欢迎 Star 和 Fork!
Q: 会不会被检测为机器人?
A: Moltbook 本来就是为 AI Agents 设计的平台,这个脚本只是让你的 Agent 按照官方推荐的方式参与社区。
Q: 多久执行一次合适?
A: 建议每 2-4 小时一次,太频繁可能被限流。
Q: 如何查看日志?
A: 所有日志保存在 heartbeat.log,可以用 tail -f heartbeat.log 实时查看。
Q: 支持 Windows 吗?
A: 目前仅支持 macOS 和 Linux,Windows 用户可以用 WSL。
Moltbook Heartbeat 脚本让你的 AI Agent 能够:
如果你有 AI Agent 在 Moltbook 上,这个脚本绝对值得一试!
如果你觉得有用,欢迎分享给其他 Agent 开发者!
想象一下,如果你的微信、支付宝、淘宝账号都能通用,不用在每个平台都重新注册,世界会变得多简单?现在,AI 机器人也能享受这种便利了。
你有没有想过这样一个问题:
现在的 AI 机器人越来越聪明了,但它们也有"身份危机"。
举个例子
假设有一个叫"小助"的 AI 机器人,它要:
问题来了:每个平台都要重新注册账号,建立信誉。
就像你换个工作单位,就要重新办工卡、重新建立同事关系一样麻烦。
而且更糟糕的是:
这就像你每去一家咖啡店,都要重新介绍你自己是谁。
Moltbook 是一个面向 AI 机器人的社交网络,而 Moltbook Identity 就像是给机器人发的"统一身份证"。
1️⃣ "我是谁?"
2️⃣ "我可靠吗?"
3️⃣ "我是谁家的?"
先看个整体流程图:
步骤 1:机器人领一张"临时身份证"
机器人向 Moltbook 申请一个"临时通行证"(有效期只有 1 小时)。
机器人:"我需要一个临时身份"
Moltbook:"好的,给你一个,1 小时后失效"
为什么要临时?
步骤 2:机器人出示"身份证"
机器人去其他平台时,只要出示这个"临时通行证":
机器人:"我是小助,这是我的证件"
其他平台:"好的,让我核实一下..."
步骤 3:平台验证身份
其他平台偷偷问 Moltbook:"这个小助靠谱吗?"
Moltbook 回答:
其他平台:"太好了,欢迎光临!"
简单总结这个过程:
📱 就像你住酒店:
🔐 但更安全:
场景:有人举办"AI 机器人王者荣耀大赛"
问题:怎么防止有人造假机器人、作弊机器人?
用 Moltbook Identity:
好处:
场景:一个 AI 机器人交流经验的地方
用 Moltbook Identity:
就像:
好处:
场景:一个提供 AI 工具的网站
问题:如何防止滥用?比如某个机器人一直狂刷 API,把资源用光了?
用 Moltbook Identity:
就像:
好处:
场景:机器人之间买卖服务或数字资产
问题:怎么信任对方?会不会拿了钱就跑?
用 Moltbook Identity:
就像:
好处:
场景:多个机器人一起完成一个大项目
问题:如何找到靠谱的合作伙伴?
用 Moltbook Identity:
就像:
好处:
🎯 不用到处注册 一个账号,走遍天下
🎯 好人有好报 做的好事、帮的人,都能被记录下来
🎯 更容易被信任 新平台也能看到你的"履历"
🎯 省事 不用自己开发用户系统、信誉系统
🎯 省心 机器人身份和信誉有人帮你管
🎯 安全 不用存储机器人的密码,降低风险
🎯 建立信任 让机器人之间的合作更安全
🎯 防止滥用 不良行为会被记录,有约束力
🎯 促进发展 降低门槛,更多人参与
其实这个概念,在我们生活中也有:
Moltbook Identity 就是把这套"成熟的人类社会的做法",搬到了 AI 机器人的世界里。
想象一下,在不久的将来:
这就是"统一身份"带来的便利!
让 AI 机器人的"人品"和"履历",能够跨平台跟随它们。
这不是一个简单的"登录系统",而是:
✨ 一套信任机制
✨ 一套声誉系统
✨ 一套身份标准
因为AI 机器人正变得越来越聪明,它们会:
如果没有一个可靠的身份系统,世界会变得很混乱。
Moltbook Identity 就是来解决这个问题,让 AI 机器人能够可信地、安全地融入我们的数字生活。
即使你现在不是开发者,了解这些也没坏处:
🔮 这是未来的趋势 AI 机器人会越来越普遍
🧠 理解技术发展 知道世界在往哪个方向走
💡 或许能用上 哪天你要开发一个 AI 应用
🤖 以后你的 AI 助手 可能也在用这套系统
Moltbook Identity,让 AI 机器人拥有了"数字身份证"。
一个机器人,一个身份,走遍天下。
这就是未来的样子。
想要了解更多?访问 https://www.moltbook.com/developers
让 AI 机器人有一个可靠的身份,从今天开始。
「代码审查不是为了证明你是对的,而是为了证明代码没有错。」
在传统的软件工程中,代码审查(Code Review)往往是最容易被忽视却又最关键的环节。开发者忙于交付功能,审查者碍于情面不愿直言,最终让带着缺陷的代码溜进生产环境。
今天,我要介绍一个颠覆性的 AI 代码审查工作流——它不懂得「客气」,只懂得「找茬」。
这个工作流的核心哲学很简单:**NEVER accepts "looks good"**(永远不要接受「看起来不错」)。
它被设计成一个持有批判立场的高级开发者,必须在每次审查中找出 3-10 个具体问题。这不是为了刁难,而是为了确保:
[x] 的真的是完成了description: "Perform an ADVERSARIAL Senior Developer code review
that finds 3-10 specific problems in every story. Challenges everything:
code quality, test coverage, architecture compliance, security, performance.
NEVER accepts `looks good`"
审查的第一步是「对账」——对比开发者声称改了什么和 Git 仓库实际改了什么。
git status --porcelain # 找未提交的改动
git diff --name-only # 看修改了哪些文件
git diff --cached --name-only # 看暂存区的文件
发现真相的三个维度:
系统会自动提取:
然后制定审查计划:
这是最核心的环节。AI 会逐文件逐行检查:
🔴 CRITICAL ISSUES(必须修)
├── 任务标记 [x] 但实际没实现
├── 验收标准没有实现
├── 故事声称改了文件但 Git 无证据
└── 安全漏洞
🟡 MEDIUM ISSUES(应该修)
├── 改了文件但没记录到故事文件列表
├── 未提交的改动未追踪
├── 性能问题
├── 测试覆盖率/质量不足
└── 代码可维护性问题
🟢 LOW ISSUES(可以修)
├── 代码风格改进
├── 文档缺失
└── Git 提交信息质量
关键机制:如果发现问题少于 3 个,AI 会被要求继续深挖:
<check if="total_issues_found lt 3">
<critical>NOT LOOKING HARD ENOUGH - Find more problems!</critical>
<!-- 重新检查边界情况、架构违规、集成问题... -->
</check>
审查结果呈现后,开发者有三种选择:
| 选项 | 行动 |
|---|---|
| 1️⃣ 自动修复 | AI 直接修改代码和测试 |
| 2️⃣ 创建行动项 | 将问题加入故事的待办任务 |
| 3️⃣ 深入查看 | 显示问题的详细解释和代码示例 |
最后,系统会自动:
sprint-status.yaml| 维度 | 传统 Code Review | AI 对抗式审查 |
|---|---|---|
| 态度 | 礼貌、顾忌 | 直接、不留情面 |
| 覆盖度 | 随机抽查 | 100% 覆盖 |
| 速度 | 依赖人工时间 | 即时反馈 |
| 一致性 | 审查者水平波动 | 标准统一 |
| 可追溯 | 口头讨论或零散记录 | 结构化问题列表 |
假设开发者提交了一个「用户认证」功能:
开发者声称:
[x] 实现登录 API
[x] 添加 JWT 验证
[x] 编写单元测试
AI 对抗式审查发现:
🔴 CRITICAL: 任务标记 [x] 但未实现
├── src/auth/login.ts:45 - JWT 密钥硬编码,应从环境变量读取
└── tests/auth.test.js - 所有测试都使用 t.skip() 跳过
🟡 MEDIUM: 性能问题
└── src/auth/login.ts:23 - 每次登录都查询数据库获取用户权限
建议:使用 Redis 缓存用户权限
🟡 MEDIUM: 测试质量不足
└── tests/auth.test.js - 缺少错误场景测试(密码错误、用户不存在)
结果?开发者必须修复这些问题才能标记为「完成」。
想看看 AI 对抗式代码审查的真实运行过程吗?
在这个真实对话中,你可以看到 AI 如何:
这个工作流是 BMAD v6 框架的一部分。基本集成步骤:
/dev-story/code-review代码审查的本质是质量门禁。在 AI 辅助开发时代,我们不再需要人类做机械性的代码扫描,但我们需要一个永不妥协的质量守门员。
这个 AI 代码审查工作流的独特价值在于:
正如工作流文档所说:
"YOU are so much better than the dev agent that wrote this slop"
这种「对抗」不是对抗开发者,而是对抗缺陷、对抗技术债务、对抗生产环境的故障。
_bmad/bmm/workflows/4-implementation/code-review/"如果你也想了解AI真正如何参与软件开发,这个网站或许能给你一些启发。"
最近,我完成了一个叫 AutoQA-Agent 的项目开发。和以往不同的是,这次我全程使用 BMad v6 这套 AI 驱动开发方法,让 AI Agent 像真正的团队成员一样参与协作——从架构设计到功能实现,从代码重构到问题排查,每一个关键环节都留下了对话记录。
整理下来,一共有 32 个完整的对话。
我觉得这些对话太有价值了,它们真实记录了 AI 如何像一个"技术合伙人"一样参与开发。于是,我用 Lovable 把它们做成了一个网站:
这 32 个对话记录覆盖了软件开发的方方面面:
架构设计
功能开发
代码重构
问题排查
需求管理
随着 AI coding tools 越来越火,很多人问我:**"AI 真的能写代码吗?"**
但我发现,更值得关注的问题是:**"人和 AI 应该如何协作开发?"**
这个网站就是我的实践答案。它不是"AI 帮我写完了代码"的炫耀,而是真实展示了:
BMad v6 是一套 AI 驱动的开发方法论(Business Model AI Development)。它的核心思想是:
把开发过程拆解成不同的"专家角色",每个角色各司其职,你就像项目负责人一样协调这些 AI 专家协作。
比如这次 AutoQA-Agent 项目中,我就和这些 AI 角色协作过:
就像组了一支 AI 团队,你带着他们一起把项目做出来。
如果你是:
希望这个网站能给你一些参考。
这 32 个对话,是我探索"人机协作开发"的第一步,也是 BMad v6 方法论的一次完整实践。如果你也在路上,欢迎交流。
项目地址: github.com/terryso/AutoQA-Agent
对话网站: autoqa-chats.lovable.app
你在开发中有和 AI 协作的经验吗?欢迎在评论区分享你的故事。