中文译文 · 原文发布于 2026-11-13
构建常驻 agent
常驻 agent 无限期运行,在许多会话之间自己决定何时行动、怎样行动。这篇指南讲它的核心原语,以及这些原语怎样组合在一起。
先说结论
常驻 agent 大部分时间在睡眠,被事件唤醒后做一次短暂的 run,再回去睡。让它显得“一直在线”的是 run 外面的五个原语:事件收件箱、定时器、记忆、权限、发件箱。原文给出的落地做法以数据库表加后台服务为主:每个用户一个收件箱、同一时间只有一个 worker、记忆只存查不回来的东西、发件箱的消息先少后多。
过去几个月,常驻 agent(always-on agent)重新热了起来。
常驻 agent 有状态,也主动:它跨会话管理上下文,在没有直接监督时也会采取行动。底层上,它和其他 agent 跑的是同一个核心循环。区别在外围架构。外围架构由一组原语构成,这些原语决定 agent 何时醒来、哪些东西在两次 run 之间保留、它的工作成果怎样送到用户手里。
这次回潮主要来自最近的模型进步,模型进步让常驻 agent 更实用。第一,最新模型判断力更好:它们更可靠地理解用户意图,也更清楚什么时候该向用户确认。第二,智能在变便宜,长周期任务受益最大。例如在一项长周期编程基准测试上,Opus 5.5 每解决一个任务的成本比 Opus 5 低 78%;在常规工作负载上,这个数字是 40%。
客户开始问我们,怎样把这类体验集成进自己的产品。有些公司想让现有的 agent 更主动,另一些公司想从零构建。
本文拆解常驻 agent 背后的原语,并说明它们怎样组合在一起。
常驻 agent 背后的循环
常驻 agent 其实并不“常驻”。这个词描述的只是用户体验。
实际上,agent 大部分时间在睡眠,定期醒来做事。每一段工作叫一次 run:agent 收到一个事件,完成任务,然后保存这次发生了什么。run 在一个会话(session)里进行,会话就是消息和工具调用的记录。run 结束后,agent 回到睡眠,这个循环无限重复。
核心原语
常驻 agent 的外围架构有五个核心原语:事件收件箱、定时器、记忆、权限和发件箱。下面逐个看。
事件收件箱
多数 AI agent 只在用户发消息时运行。常驻 agent 不同,它可以被多种事件触发,包括 webhook、定时任务和后台作业。
一个诱人的做法是让每个事件源直接调用 agent,但这有几种失败方式。例如,两个事件同时到达,会启动两个互相竞争的 run;一次崩溃会让某个事件彻底丢失。
事件收件箱就是为此准备的。事件源不再直接调用 agent,而是把每个事件发到收件箱,再由一个 worker 取走处理。
最简单的版本有三个组件:
- 一个持久化的事件列表,通常是一张数据库表;
- 一个 handler,通常是一个 HTTP 端点,负责把事件写进列表;
- 一个 worker,通常是一个后台服务,负责从列表读取事件。
收件箱应记录每个事件的状态,并可以按事件源提供的唯一标识去重。关系型数据库很适合做收件箱,每个事件追加为新的一行。我们建议从每个用户一个收件箱开始;如果需要更高的并行度,可以再拆分。
新事件到来时,handler 把它写进收件箱并唤醒 worker。worker 读取所有待处理事件,启动一次 run。延续之前工作的事件(例如用户的回复)恢复已有会话,其他事件都开启新会话。
接着,worker 把事件交给 agent 循环,等 run 结束,再把对应事件标为完成。实现细节可能比较棘手,下面是几条最佳实践:
- handler 应快速确认事件。有些事件源(例如 webhook 提供方)会把响应慢当作失败。handler 仍可以做轻量检查,但更深入的校验(例如检查提示词注入)应放在 run 里做。
- 收件箱应是唯一可信来源。worker 被唤醒时,应先从收件箱拉取最新事件。这样,如果 worker 在 run 中途挂掉,新的 worker 可以接手同一个事件并重试。
- 一个收件箱同一时间只应有一个活跃 worker。这样可以防止两个 run 重复工作或互相覆盖。run 进行中来了新事件,可以推到下一次 run,可以并入当前 run,也可以中断后重启。
- worker 应把待处理事件合批。例如用户连续发了几条消息,worker 可以把它们合并成一次 run,agent 只需回复一次。
定时器
定时器和事件收件箱一起,让常驻 agent 能够主动行动。定时器一般分两类:跟进(follow-up)和定期任务(schedule)。跟进是 agent 给自己定的截止时间,例如过一阵去看有没有回复。定期任务是按已知节奏重复发生的任务,例如每日简报。
定时器放在自己的持久化存储里,通常是另一张数据库表,和收件箱分开。收件箱只应存放可以立即处理的事件。跟进和定期任务可以用相似的表结构,但定期任务还需要记录重复规则。
定时器本身不会启动 run。一个调度器(通常是后台服务)按固定间隔轮询存储,触发所有到期的定时器。每轮轮询,调度器会:
- 扫描存储,找出到期的定时器。
- 按类别更新每个定时器:
- 跟进标为完成。
- 定期任务把到期时间更新为下一次发生的时间。
- 向收件箱添加一个事件,并唤醒 worker。
调度器可以自己写一个服务,也可以用持久化执行引擎(durable execution engine)。
记忆
常驻 agent 在许多互不相连的 run 之间工作,靠记忆保持连续性。
和事件收件箱、定时器一样,记忆也应有自己的存储。在我们的产品里,例如 Claude Code 的自动记忆和 Messages API 的记忆工具,记忆的实现是一个文件目录。对常驻 agent,这些目录应映射到持久化存储,通常是数据库或对象存储。这样记忆的寿命比 worker 长。
经验法则:记忆只存 agent 无法再查到的东西。如果某样东西已经在别的系统里(例如工单系统),就只存一个指针。常驻 agent 应存的记忆有以下几类:
- 用户指令:用户关于 agent 行为的所有指令,应单独存一个文件,每次 run 开始时加载。用户不会一直在场引导 agent,所以这些指令必须在 run 之间延续。
- 用户笔记:agent 应保存它对用户的了解,例如用户的角色、偏好、给过的纠正。可以先用一个文件,笔记变多后再拆成几个小文件。拆分后,每次 run 开始时只加载一个索引,agent 需要细节时再把单个文件拉进上下文。
- 历史消息:agent 应记录它已经告诉过用户什么,避免重复。可以存在一个单独文件里,每次 run 开始时加载;也可以让 worker 加载 agent 最近几条发给用户的消息。
- 任务进度:每个进行中的任务,agent 都应维护一个进度文件,写清楚做完了什么、还剩什么。任何新的 run 读这个文件,就能接着上一次 run 停下的地方继续。和用户笔记一样,每次 run 开始时只加载一个索引,agent 需要细节时再拉取单个文件。
下图是一种示例配置。
instructions.md、index.md、reported.md 每次都加载,notes/ 和 progress/ 按需读。以上说的都是 run 期间保存的记忆。你还可以加上“做梦”(dreaming):回看过去的会话来整理记忆。做梦可以替换过时的条目,也能发现只有跨许多次 run 才显现的规律。想了解更多,可以看我们关于 agent 记忆的文章。
上下文窗口内部
开始干活前,agent 先加载系统提示词、工具定义和记忆文件。这部分初始上下文“开销”每次 run 都有,所以我们建议保持精简,并用提示词缓存降低成本。
新会话开始时,只有这部分开销和触发本次 run 的事件。已有会话则会带回它之前各次 run 的消息和工具调用。
会话太长时,可以压缩较早的消息,也可以开一个新会话。两种做法都会丢失细节,所以需要保留的东西应在过程中随时存进记忆。
权限
权限是常驻 agent 风险最大的地方。一次 run 持有用户的访问权限,而且按设计无人值守。对每个工具调用,agent 都要决定:自己继续、拦下,还是等待批准。
权限用到的组件和其他 agent 一样:
- 凭据存储:常驻 agent 要发挥作用,常常需要密码、访问令牌这类敏感的用户凭据。推荐的模式是在 agent 环境之外放一个代理,由它给出站请求加上凭据。agent 发出的请求不带凭据,代理在转发前注入真实凭据。
- 自动模式:常驻 agent 应能在没有用户持续输入的情况下工作。我们在 Claude Code 的自动模式里已经处理过这个问题的一个版本。自动模式用分类器审查较高风险的操作,拦下它们判定为危险的操作,让 Claude 另找更安全的做法。不过,最重要的决定仍需要用户审核。
- 延迟工具调用:在 Claude Code 里,hook 可以延迟一个工具调用,等用户补充输入。同样的模式适用于常驻 agent,它在执行某些操作前可能需要澄清或批准。调用被延迟时,run 结束,会话保存在调用中途。请求展示给用户(例如通过用 MCP Apps 做的交互卡片),用户的回答作为事件回到收件箱。这个事件启动新的 run,用用户的输入恢复保存的会话。延迟调用等待回复期间,worker 可以处理其他事件,agent 不会被卡住。
发件箱
对多数 AI agent,回复在生成时就流式传给用户,用户可以看着 Claude 推理问题、调用工具、写出答案。
常驻 agent 则大部分工作在后台完成。用户拿到的是结果和少量细节,而不是完整的分步过程。
一次 run 可以以三种方式之一结束:
- agent 向发件箱发一条消息;
- agent 判断不需要发任何东西;
- agent 用延迟工具调用请求许可或补充输入。
多数常驻 agent 有一个直接把消息送到用户的“send”工具就够用。只有想控制 agent 发消息的节奏,或想让 agent 能从错误中恢复时,才需要正式的发件箱。
发件箱是一张单独的待发消息表,和事件收件箱相对应。还有一个分发器(dispatcher),通常是后台服务,和 worker 相对应。分发器读取待发消息,决定送哪些,再发给用户。延迟工具调用产生的请求可以走发件箱,也可以直接在界面里展示。
除了投递,调好 agent 发多少消息也很重要。发太多,agent 就成了噪音;发太少,会漏掉用户需要知道的事。我们建议构建评测(eval)来找到平衡点。实践中,agent 最好先保持安静,再随时间赢得用户更多的注意力。
各部分如何组合
五个原语这样组合在一起:
每次 run 都从收件箱里的一个事件开始,可能是用户消息、webhook,也可能是定时器。worker 取到事件后启动一次 run,在新会话或已有会话里唤醒 agent。agent 读取记忆获得基础信息,在权限范围内采取行动,再把学到的东西写回记忆。run 结束后,agent 往发件箱推一行,分发器把消息送到用户。
这些部分合起来构成一个循环。一次 run 可以设一个定时器,让 agent 稍后再醒;用户回复消息时,回答作为事件回到收件箱。每次 run 都很短,但循环无限持续,正是这一点让 agent 感觉一直在线。
致谢
作者 William Hu。特别感谢 Rodrigo Olivares 和 Michael Segner 的贡献。
方法与来源
本页是全文翻译,没有删减或增补原文内容。“先说结论”一块是译者加的摘要,不属于原文。原文抓取于 2026-11-14。七张图来自原文,以原尺寸内嵌;点图可打开原站上的原图。图中文字保留英文,图下说明和替代文本是译文。
术语译法:always-on agent 译作“常驻 agent”;run、worker、handler、webhook 保留英文;session 译作“会话”,event inbox 译作“事件收件箱”,outbox 译作“发件箱”,timer 译作“定时器”,scheduler 译作“调度器”,dispatcher 译作“分发器”,deferred tool call 译作“延迟工具调用”,dreaming 译作“做梦”。
- claude.com,William Hu,《Building always-on agents》,2026-11-13,claude.com/resources/articles/building-always-on-agents