构建Agent Native 软件的完整技术指南_
当代码终结之后,软件该怎么写?
Dan Shipper(Every 的联合创始人)和 Claude 联手写了一份技术指南,试图给出答案。
这是一份构建 Agent 原生软件的完整技术指南。
它系统性地总结了在这个 AI Agent 能够可靠工作的时代,我们应该如何重新思考软件架构:不是把 Agent 当作一个附加功能,而是让它成为软件的一等公民。
指南的价值在于:它并非空谈理论,而是来自实战。
Dan 和团队已经用这套方法论构建了 Reader、Anecdote 等产品,里面的模式都经过了验证。
当然,也有一些是 Claude 在构建过程中贡献的想法,尚需更多验证,文中都做了标注。
为什么是现在?
软件 Agent 现在能可靠地工作了。
Claude Code 证明了一件事:一个大语言模型,只要给它 bash 和文件工具,让它在循环中运行直到目标达成,就能自主完成复杂的多步骤任务。
这里有一个令人惊讶的发现:一个真正优秀的编程 Agent,其实就是一个真正优秀的通用 Agent。 让 Claude Code 能重构代码库的那套架构,同样能让 Agent 整理你的文件、管理你的阅读清单、或者自动化你的工作流程。
Claude Code SDK 让这一切变得触手可及。你可以构建这样的应用:功能不是你写的代码,而是你描述的结果。由一个拥有工具的 Agent,在循环中运行,直到结果达成。
这开辟了一个新领域:像 Claude Code 那样工作的软件,应用到编程之外的无数场景中。
五大核心原则
1. 对等性(Parity)
用户通过 UI 能做的任何事,Agent 都应该能通过工具实现。
这是基础性原则。没有它,其他一切都免谈。确保 Agent 拥有能够完成 UI 所能做的一切的工具。
测试方法: 随便选一个 UI 操作,Agent 能完成吗?
想象一个笔记应用,用户可以创建、整理、标记笔记。用户说:「创建一个会议总结笔记,标记为紧急。」如果 UI 能做但 Agent 做不到,Agent 就卡住了。
解决方案: 确保 Agent 拥有能够实现 UI 所能达成的一切结果的工具(或工具组合)。这不是 UI 按钮和工具的一对一映射,而是实现相同的结果。
write_filecreate_note 工具 | |
update_filetag_note 工具 | |
search_filessearch_notes 工具 | |
delete_filedelete_note 工具 |
2. 原子性(Granularity)
工具应该是原子化的基础能力。功能是 Agent 在循环中运行直到达成的结果。
关键转变在于:Agent 是在用判断力追求一个结果,而不是执行一个编排好的序列。 它可以遇到意外情况、调整方法、或者提出澄清性问题,循环持续运行直到结果达成。
工具越原子化,Agent 使用它们的灵活性就越高。如果你把决策逻辑打包进工具,你就把判断力从 Agent 那里移回了代码里。
粒度较粗:
工具: classify_and_organize_files(files)→ 你写了决策逻辑→ Agent 执行你的代码→ 要改变行为,你得重构
粒度较细:
工具: read_file, write_file, move_file, bash提示词: "整理下载文件夹..."→ Agent 做决策→ 要改变行为,编辑提示词
测试方法: 要改变行为,你是编辑提示词还是重构代码?
3. 可组合性(Composability)
有了原子化的工具和对等性,你只需要写新的提示词就能创建新功能。
想要一个「每周回顾」功能?那就是一个提示词:
"回顾本周修改的文件。总结关键变更。根据未完成的事项和即将到来的截止日期,建议下周的三个优先事项。"
Agent 使用 list_files、read_file 和它的判断力。你描述了一个结果;Agent 循环运行直到达成它。
4. 涌现能力(Emergent Capability)
Agent 可以完成你没有明确设计的事情。
飞轮效应:
用原子化工具和对等性构建 用户提出你没预料到的需求 Agent 组合工具来完成它们(或者失败,暴露出缺口) 你观察被请求的模式 添加领域工具或提示词,让常见模式更高效 重复
例子:「把我的会议笔记和任务列表交叉对照,告诉我哪些是我承诺了但还没安排的。」你没有构建承诺追踪器,但如果 Agent 能读取笔记和任务,它就能完成这件事。
这揭示了潜在需求。你不是猜测用户想要什么功能,而是观察他们让 Agent 做什么。当模式出现时,你可以用专门的工具或提示词来优化它们。但你不需要预料到它们,你是发现它们的。
5. 持续改进(Improvement Over Time)
Agent 原生应用通过积累的上下文和提示词优化变得更好。
与传统软件不同,Agent 原生应用可以不发布代码就改进。
积累的上下文: 状态通过上下文文件跨会话持久化 开发者级别的优化: 为所有用户发布更新的提示词 用户级别的定制: 用户为自己的工作流修改提示词
从基础能力到领域工具
从纯基础能力开始: bash、文件操作、基础存储。这证明架构可行,并揭示 Agent 真正需要什么。
随着模式出现,有目的地添加领域工具:
create_note | |
领域工具的规则: 它们应该代表从用户角度看的一个概念性动作。可以包含机械性验证,但关于做什么或是否做的判断属于提示词。
保持基础能力可用。 领域工具是捷径,不是门禁。除非有特定原因需要限制访问(安全、数据完整性),Agent 仍应该能够使用底层基础能力处理边缘情况。
升级到代码
有些操作需要从 Agent 编排迁移到优化代码,以提高性能或可靠性。
Agent 在循环中使用基础能力 — 灵活,验证概念 为常见操作添加领域工具 — 更快,仍由 Agent 编排 对热路径实现优化代码 — 快速,确定性
注意: 即使操作升级到代码,Agent 也应该能够自己触发优化操作,并在优化路径无法处理边缘情况时回退到基础能力。升级是关于效率的,对等性仍然成立。
文件作为通用接口
Agent 天生擅长处理文件。 Claude Code 之所以有效,是因为 bash + 文件系统是最久经考验的 Agent 接口。
cat、grep、mv、mkdir。文件操作是它们最熟练的基础能力 | |
/projects/acme/notes/SELECT * FROM notes WHERE project_id = 123 不是 |
Agent 原生设计的一般原则: 为 Agent 能推理的东西设计。最好的代理是对人类有意义的东西。如果人类能看着你的文件结构并理解发生了什么,Agent 大概也能。
实体范围目录
{entity_type}/{entity_id}/├── 主要内容├── 元数据└── 相关材料
例子: Research/books/{bookId}/ 包含全文、笔记、来源和 Agent 日志。
context.md 模式
# 上下文## 我是谁Every 应用的阅读助手。## 我对这个用户的了解- 对军事历史和俄罗斯文学感兴趣- 偏好简洁的分析- 目前在读《战争与和平》## 存在什么- /notes 中有 12 个笔记- 三个活跃项目- 用户偏好在 /preferences.md## 最近活动- 用户创建了「项目启动」(两小时前)- 分析了关于奥斯特里茨的段落(昨天)## 我的指南- 不要剧透他们正在读的书- 用他们的兴趣来个性化见解## 当前状态- 没有待处理任务- 最后同步:10 分钟前
Agent 在每次会话开始时读取这个文件,并在状态变化时更新它:可移植的工作记忆,无需代码变更。
文件 vs 数据库
原则: 文件用于可读性,数据库用于结构。有疑问时,用文件,它们更透明,用户随时可以检查。
冲突模型
如果 Agent 和用户写入同一文件,你需要一个冲突模型。
实用指南: 日志和状态文件很少冲突。对于用户编辑的内容,考虑显式处理或将 Agent 输出分开。iCloud 通过创建冲突副本增加复杂性。
Agent 执行模式
完成信号
Agent 需要一个明确的方式说「我完成了」。 不要通过启发式方法检测完成。
struct ToolResult {let success: Boollet output: Stringlet shouldContinue: Bool}.success("Result") // 继续.error("Message") // 继续(重试).complete("Done") // 停止循环
完成与成功/失败是分开的:一个工具可以成功并停止循环,或者失败并发出继续信号以便恢复。
模型层级选择
不是所有 Agent 操作都需要相同的智能水平。
部分完成
struct AgentTask {var status: TaskStatus // pending, in_progress, completed, failed, skippedvar notes: String? // 为什么失败,做了什么}var isComplete: Bool {tasks.allSatisfy { $0.status == .completed || $0.status == .skipped }}
对于多步骤任务,在任务级别追踪进度。UI 显示:
进度:3/5 任务完成(60%)✓[1] 查找源材料✓[2] 下载全文✓[3] 提取关键段落✗[4] 生成摘要 - 错误:上下文限制○[5] 创建大纲
上下文限制
Agent 会话可以无限延伸,但上下文窗口不行。为有界上下文设计:
工具应该支持迭代细化(摘要 → 细节 → 完整),而不是全有或全无 给 Agent 一种在会话中途整合学习的方式(「总结我学到的东西并继续」) 假设上下文最终会填满:从一开始就为此设计
实现模式
共享工作空间
Agent 和用户应该在同一个数据空间工作,而不是分离的沙箱。
UserData/├── notes/ ← Agent 和用户都在这里读/写├── projects/ ← Agent 可以整理,用户可以覆盖└── preferences.md ← Agent 读取,用户可以编辑
好处:
用户可以检查和修改 Agent 的工作 Agent 可以基于用户创建的内容构建 不需要同步层 完全透明
这应该是默认的。只有在有特定需要时才沙箱化(安全、防止关键数据损坏)。
上下文注入
Agent 需要知道它在处理什么。系统提示词应该包括:
可用资源:
## 可用数据- /notes 中有 12 个笔记- 最新的:「项目启动」- /projects 中有三个项目- 偏好在 /preferences.md
能力:
## 你能做什么- 创建、编辑、标记、删除笔记- 将文件整理到项目中- 跨所有内容搜索- 设置提醒(write_file)
最近活动:
## 最近上下文- 用户创建了「项目启动」笔记(两小时前)- 用户昨天问了关于 Q3 截止日期的问题
Agent 到 UI 的通信
当 Agent 行动时,UI 应该立即反映。聊天集成的事件类型:
enum AgentEvent {case thinking(String) // → 显示为思考指示器case toolCall(String, String) // → 显示正在使用的工具case toolResult(String) // → 显示结果(可选)case textResponse(String) // → 流式传输到聊天case statusChange(Status) // → 更新状态栏}
关键:没有静默操作。 Agent 的变更应该立即可见。
静默的 Agent 感觉坏了。 可见的进度建立信任。
产品影响
渐进式披露
开始简单但无限强大。 基本请求立即工作。高级用户可以向意想不到的方向推进。
Excel 是典型例子:购物清单或财务模型,同一个工具。Claude Code 也有这个特质。界面保持简单;能力随请求扩展。
简单入口:基本请求无需学习曲线 可发现的深度:用户在探索中发现新能力 没有天花板:高级用户超越你预期的方向
Agent 在用户所在的地方与他们相遇。
潜在需求发现
传统产品开发: 想象用户想要什么,构建它,看你是否正确。
Agent 原生产品开发: 构建一个有能力的基础,观察用户让 Agent 做什么,形式化出现的模式。
当用户让 Agent 做某事并且成功了,那是信号。当他们问了但失败了,那也是信号:它揭示了你的工具或对等性的缺口。
随着时间推移,你可以:
为常见模式添加领域工具(使它们更快更可靠) 为频繁请求创建专门的提示词(使它们更容易发现) 移除没有被使用的工具(简化系统)
Agent 成为理解用户真正需要什么的研究工具。
审批和用户代理权
当 Agent 采取主动行动(自己做事而不是响应明确请求)你需要决定给予多少自主权。考虑风险和可逆性:
注意:这适用于主动的 Agent 行动。如果用户明确要求 Agent 做某事(「发送那封邮件」),那已经是审批了——Agent 直接做。
自我修改应该是可读的: 当 Agent 可以修改自己的行为时(更改提示词、更新偏好、调整工作流)目标是:
对变更的可见性 理解效果 能够回滚
审批流程是实现这一点的一种方式。带有简单回滚的审计日志可能是另一种。原则是:使其可读。
移动端
移动端是 Agent 原生应用的一等平台。 它有独特的约束和机会。
挑战
Agent 是长时间运行的。移动应用不是。
一个 Agent 可能需要 30 秒、5 分钟或一小时来完成任务。但 iOS 会在几秒不活动后将你的应用放到后台,可能会完全杀死它来回收内存。用户可能在任务进行中切换应用、接电话或锁屏。
这意味着移动 Agent 应用需要深思熟虑的方法来处理:
检查点: 保存状态以免工作丢失 恢复: 中断后从上次停止的地方继续 后台执行: 明智地使用 iOS 给你的有限时间 设备端 vs 云端: 决定什么在本地运行 vs 什么需要服务器
iOS 存储架构
一种方法:iCloud 优先,本地回退:
1. iCloud 容器(首选)iCloud.com.{bundleId}/Documents/├── Library/├── Research/books/├── Chats/└── Profile/2. 本地文档(回退)~/Documents/3. 迁移层自动迁移 本地 → iCloud
// iCloud 优先,本地回退if let url = fileManager.url(forUbiquityContainerIdentifier: nil) {return url.appendingPathComponent("Documents")}return fileManager.urls(for: .documentDirectory,in: .userDomainMask)[0]
这给你带来:
跨设备自动同步,无需构建基础设施 无需用户操作的备份 iCloud 不可用时的优雅降级 用户可以在应用外访问他们的数据
检查点和恢复
移动应用会被中断。Agent 需要能承受这一点。
检查点什么: Agent 类型、消息、迭代计数、任务列表、自定义状态、时间戳
何时检查点: 应用进入后台时、每个工具结果后、长操作期间定期
恢复流程: 加载中断的会话 → 按有效性过滤(默认一小时) → 显示恢复提示 → 恢复消息并继续
struct AgentCheckpoint: Codable {let agentType: Stringlet messages: [[String: Any]]let iterationCount: Intlet taskListJSON: String?let customState: [String: String]let timestamp: Date}func isValid(maxAge: TimeInterval = 3600)-> Bool {Date().timeIntervalSince(timestamp)< maxAge}
缺口: 如果系统杀死应用,恢复取决于检查点频率。每个工具结果后检查点以获得最大健壮性。
后台执行
iOS 给你有限的后台时间:
func prepareForBackground() {backgroundTaskId = UIApplication.shared.beginBackgroundTask(withName: "AgentProcessing") {handleBackgroundTimeExpired()}}func handleBackgroundTimeExpired() {for session in sessions where session.status == .running {session.status = .backgroundedTask { await saveSession(session) }}}func handleForeground() {for session in sessions where session.status == .backgrounded {Task { await resumeSession(session) }}}
你大约有 30 秒。用它来:
如果可能,完成当前工具调用 检查点会话状态 优雅地过渡到后台状态
对于真正长时间运行的 Agent: 考虑一个可以运行数小时的服务器端编排器,移动应用作为查看器和输入机制。
设备端 vs 云端
应用需要网络进行推理,但可以离线访问数据。设计工具在网络不可用时优雅降级。
高级模式
动态能力发现
构建每个外部 API 端点的工具的替代方案:构建让 Agent 在运行时发现可用内容的工具。
静态映射的问题:
// 你为 50 个数据类型构建了 50 个工具read_steps()read_heart_rate()read_sleep()// 当添加新指标时... 需要代码变更// Agent 只能访问你预料到的
动态能力发现:
// 两个工具处理一切list_available_types() → 返回 ["steps", "heart_rate", "sleep", ...]read_data(type) → 读取任何发现的类型// 当添加新指标时... Agent 自动发现它// Agent 可以访问你没预料到的东西
这是原子性推到其逻辑结论。你的工具变得如此原子化,以至于它们可以处理你构建时不知道存在的类型。
何时使用:
你希望 Agent 拥有完整用户级访问的外部 API(HealthKit、HomeKit、GraphQL 端点) 随时间添加新能力的系统 当你希望 Agent 能够做 API 支持的任何事情
何时静态映射可行:
有限范围的故意受限 Agent 当你需要紧密控制 Agent 可以访问什么时 具有稳定、众所周知端点的简单 API
CRUD 完整性
对于系统中的每个实体,验证 Agent 有完整的创建、读取、更新、删除(CRUD)能力:
常见失败: 你构建了 create_note 和 read_notes 但忘了 update_note 和 delete_note。用户让 Agent「修正我会议笔记中的那个错别字」,Agent 无法帮忙。
反模式
不完全是 Agent 原生的常见方法
这些不一定是错的:它们可能适合你的用例。但值得认识到它们与本文档描述的架构不同。
Agent 作为路由器: Agent 搞清楚用户想要什么,然后调用正确的函数。Agent 的智能用于路由,而不是行动。这可以工作,但你只用了 Agent 能力的一小部分。
先构建应用,然后添加 Agent: 你用传统方式构建功能(作为代码),然后将它们暴露给 Agent。Agent 只能做你的功能已经做的事情。你不会获得涌现能力。
请求/响应思维: Agent 获得输入,做一件事,返回输出。这忽略了循环:Agent 获得一个要实现的结果,运行直到完成,沿途处理意外情况。
防御性工具设计: 你过度约束工具输入,因为你习惯了防御性编程。严格的枚举,每层验证。这是安全的,但它阻止 Agent 做你没预料到的事情。
快乐路径在代码中,Agent 只是执行: 传统软件在代码中处理边缘情况:你写了当 X 出错时会发生什么的逻辑。Agent 原生让 Agent 用判断力处理边缘情况。如果你的代码处理了所有边缘情况,Agent 只是一个调用者。
具体反模式
Agent 执行你的工作流而不是追求结果:
# 错误 - 你写了工作流def process_request(input):category = categorize(input) # 你的代码决定priority = score_priority(input) # 你的代码决定store(input, category, priority)if priority > 3: notify() # 你的代码决定# 正确 - Agent 在循环中追求结果工具: store_item, send_notification提示词: "评估紧急程度 1-5,用你的评估存储,如果 >= 4 通知"
工作流形状的工具: analyze_and_organize 把判断力打包进工具。把它分解成基础能力,让 Agent 组合它们。
孤儿 UI 操作: 用户可以通过 UI 做某事,但 Agent 无法实现。修复:保持对等性。
上下文饥饿: Agent 不知道存在什么。用户说「整理我的笔记」,Agent 不知道有笔记。修复:将可用资源和能力注入系统提示词。
无理由的门禁: 领域工具是做某事的唯一方式,而你没打算限制访问。修复:默认开放。保持基础能力可用,除非有特定理由设置门禁。
人为的能力限制: 出于模糊的安全顾虑而不是特定风险限制 Agent 能做什么。Agent 通常应该能够做用户能做的事情。对破坏性操作使用审批流程,而不是完全移除能力。
动态更好时的静态映射: 当发现 + 访问模式会给出更多灵活性并使系统面向未来时,为 50 个 API 端点构建 50 个工具。
启发式完成检测: 通过启发式方法检测 Agent 完成(连续迭代没有工具调用、检查预期输出文件)是脆弱的。修复:要求 Agent 通过完成工具明确发出完成信号。
成功标准
架构
[ ] Agent 可以实现用户通过 UI 能实现的任何事情(对等性) [ ] 工具是原子化的基础能力;领域工具是捷径,不是门禁(原子性) [ ] 可以通过写新提示词添加新功能(可组合性) [ ] Agent 可以完成你没有明确设计的任务(涌现能力) [ ] 改变行为意味着编辑提示词,而不是重构代码
实现
[ ] 系统提示词包括可用资源和能力 [ ] Agent 和用户在同一数据空间工作 [ ] Agent 操作立即反映在 UI 中 [ ] 每个实体有完整的 CRUD 能力 [ ] 外部 API 在适当时使用动态能力发现 [ ] Agent 明确发出完成信号(没有启发式检测)
产品
[ ] 简单请求无需学习曲线立即工作 [ ] 高级用户可以向意想不到的方向推进系统 [ ] 你通过观察用户让 Agent 做什么来了解用户想要什么 [ ] 审批要求匹配风险和可逆性
移动端
[ ] 检查点/恢复处理应用中断 [ ] iCloud 优先存储,本地回退 [ ] 后台执行明智地使用可用时间
终极测试
向 Agent 描述一个在你应用领域内但你没有为其构建特定功能的结果。
它能搞清楚如何实现它,在循环中运行直到成功吗?
如果是:你构建了 Agent 原生的东西。
如果不是:你的架构太受限了。
原文地址: https://every.to/guides/agent-native