1. 工单的一生
一个人在群里说:这个接口昨天开始偶尔返回 502,帮我看一下。
接下来发生的事按时间排开,每一步都由某个具体对象负责。
来源收事件。 群消息进入系统,被翻译成一个触发。来源先做两件事:丢掉机器人自己的消息,丢掉不在监听范围内的消息。
触发进线程。 这条触发归属到某个线程——如果它是对之前某条讨论的回复,就归到那个线程;如果是新话题,新开一个。
评估研判。 线程上积攒的触发按批进入评估。评估看三样:这批触发、线程快照(含该线程上所有工单,包括已结束的)、当前策略。它输出一个决定。
接受并建工单。 决定是接受时,创建一个工单,同时给出一份授权——记录这次工作凭什么可以做、能走多远。
派工给执行会话。 工单绑定一个执行器实例。系统为它准备一个工作目录,起一个会话,把工单内容与授权范围交给它。
执行器报进展。 它查日志、定位、准备改动,中间可能提问,可能报告就绪。
人跟进一句。 有人在群里补了一句"顺便看下是不是昨天上线的那个改动"。这条消息不需要重新评估——工单还活着,且它订阅了这个线程,所以消息直接扇出到执行器。
关闭工单。 修完、验证通过、报告关闭。
三天后有人再说一句。 同一个线程上有人问"那个修复上线了吗"。已结束的工单保留着订阅,所以这条消息到达了它。系统在恢复之前做一次重新校验:这个来源还在监听这个线程吗?当初的授权依据现在还成立吗?都通过,工单恢复,沿用同一份授权,不需要新的评估与决定。
这条链路里有两件事值得先记住。
执行会话不等于任务。 第三步到第五步之间可能换过执行器——如果某个会话崩了,工单不结束,重新派工继续。任务的生命周期长于任何一个会话。
授权不等于可见性。 第七步那条跟进消息之所以能到执行器,是因为工单订阅了线程,不是因为发送人有权限。授权在第四步已经判过,之后的讨论只是补充信息。
2. 它解决的问题
多数多 Agent 框架处理的是「父 Agent 如何派生子 Agent 并收拢结果」。deepseek-ai/dsh-superagent-prototype 处理的是另一个问题:Agent 会话崩了、超时了、被压缩了,任务不能丢。
这个定位差异决定整套设计。如果任务是「派生并收拢」,核心是通信协议与结果合并;如果任务是「会话可替换」,核心是状态存在哪、谁能改它、改了之后如何被下一任执行者读到。
它的答案是:把状态放进一组领域对象,让 Agent 会话成为可替换的资源。长驻主会话只做路由,每个工单配一个执行会话,中间隔一层完整的领域模型。
接下来先看这层领域模型由哪些对象构成,再看它如何支撑上面那条链路,最后给批判。
3. 领域对象与各自保持的规则
要让会话可替换,状态必须离开会话。这一步的直接产物是领域层:十个概念,每个都带一组不可违反的规则。
核心是五个:来源、线程、评估、授权、订阅。其余支撑。
来源(Source) 是一串事件流。种类是可复用的实现,实例是种类的一次配置(是否运行、监听什么)。它保持的规则:持有自己的读取位置与自己的一套机器人身份;定义什么算一个事件,包括只在时间上成立的条件;在事件进入领域之前就丢掉机器人自己的消息与监听范围外的触发;把每个触发翻译成它所属的线程与可回复的渠道地址。
「重启不得重放事件」这条规则是它存在的理由。读取位置存在领域层,不在内存里。
触发(Trigger) 是一个不可变事件,带来源自己的元数据(例如发起人)。规则:属于一个或多个线程;同一触发被接受两次只创建一个工单。
这条规则给出幂等性。没有它,网络重试会变成重复开工。
线程(Thread) 是按时间排列的触发序列,由来源定义哪些触发构成一个线程。规则:同时最多一个评估在它上面运行;可以关联多个工单;持有自己的订阅、正在运行的评估与决定历史。
这条规则给出「一致视图」。评估看到的线程快照不会被并发修改。
关联(Correlation) 是线程之间的关系:一个触发可以属于多个线程。规则:只增加上下文,永不参与路由。
这个区分很精明:关联让评估能看到相关证据,但不改变「这条消息该归谁」。两件事分开,避免「因为看到了别的线程,所以把工作派错地方」。
渠道(Channel) 是线程的写侧,地址由来源定义。规则:一个线程可以有多个渠道;一个工单持有一组渠道。
确认、答复、拉取请求、已解决的评审,四者都是写操作,用同一个抽象。
策略(Policy) 是版本化的评估规则加上每个来源的配置:谁能通过该来源发起工作、在该来源自己的身份体系里、名单如何同步、哪些机器人可以产生候选。规则:版本覆盖规则、接受模式与来源配置;影响活工单的变更(例如它被接受时依据的规则变了)投递给它的派工,不撤销授权;不同来源上的身份永不互相映射。
最后一条规则来自一个具体教训:同一个真人在两个来源上有两个账号,系统不尝试合并它们,身份映射交给提示词层(团队可以写一个说明谁是谁的 Skill)。领域层不猜。
评估(Eval) 是对一批触发的判断,分两层:准入与研判。规则:输入是触发批次、线程快照、该线程上所有工单(含已结束)、当前策略;输出是一个决定;不持有状态。
「不持有状态」让它可重放。评估失败可以重新评估,不需要恢复中间态。
授权(Mandate) 是「接受」这个动作给出的东西:策略版本、范围、强度、匹配的规则、发送人在该来源上的身份、来源如何收到这个触发。规则:不可变;范围变宽意味着新工单;工单结束不结束授权,只有人的停止能撤回它,或者一个不再持有它的策略能撤销它;已结束的工单恢复前要按当前策略重新校验。
工单(Ticket) 是一个被授权的工作单元。规则:只有评估能创建;引用它的授权;持有渠道与输入;走 open → ended,当人在它的某个线程上发消息时可以回到 open,除非是被叫停的;定义自己的状态,不跟随执行器的阶段。
最后一句是整套设计的支点。
派工(Assignment) 是工单与一个执行器实例的绑定。规则:一个工单在时间上可以有多次派工;走 pending → running → ended,独立于工单;只有它的最终报告才能在执行器一侧结束工单。
「执行器在领域之外,但谁在执行必须在领域之内」——这句话解释了为什么需要派工这一层。
订阅(Subscription) 是工单与线程的多对多关系,由线程持有。规则里信息量最大:匹配的触发会扇出到每个活着的已订阅工单,不检查是谁发的;以这种方式到达工单的人的消息不进入评估;执行器为上下文跟踪的线程同样到达工单,但不授予任何权限,也不恢复它;已结束的工单保留订阅(包括已关闭或已合并的拉取请求的订阅),人的消息可以在同一份授权下恢复它,不需要评估或决定,机器人与 CI 的消息不恢复它;人叫停的工单什么都不接收;主会话只在消息明确指向某工单时才把线程附加到该工单。
4. 两条独立的状态线
工单与派工各有一条状态线,这是「会话可替换」的技术前提。
| 对象 | 状态 | 迁移条件 |
|---|---|---|
| 工单 | open → ended | 人叫停、工作完成、执行器报告 dropped |
| 工单 | ended → open | 人在它自己的线程上发消息,且未被叫停 |
| 派工 | pending → running → ended | 执行器启动、执行器报告、执行器崩溃 |
两条线独立带来的能力:执行器崩了,工单不结束,换一次派工继续;执行器完成了,工单可能还在等后续的评审意见。
这个分离解决了单 Agent 设计里一个常见问题:会话结束就等于任务结束。实际上任务的生命周期通常长于任何一个会话——一个拉取请求可能开三天,期间执行器换过两次。
5. 授权与上下文的分离
这是整套设计里最值得学的一处。
授权决定能做什么。 它记录策略版本、范围、强度、匹配规则、发送人身份、来源接收方式。
订阅决定能看到什么。 每个活着的已订阅工单收到的线程消息,作为上下文扇出。
关键规则:扇出绕过准入检查,也不授予任何新权限。工单始终受限于它的授权范围。
这个分离带来的好处很直接:讨论可以自由流入执行器,不需要每次重新判断「这个人有没有资格说话」。资格在授权时已经判断过,后续消息只是补充信息。
它也带来一个需要正视的风险。规则明写扇出「不检查是谁发的」——包括机器人消息与 CI 消息。这意味着一条来自任何来源的消息都会进入执行器上下文,而执行器可能据此行动。
设计者的防护是:扇出只给上下文,不给权限,执行范围仍受授权约束。这个防护在「授权范围写得很窄」时成立;如果授权范围很宽(例如「可以修改本仓库任意文件」),扇出就成了一个软通道——外部内容能影响一次被授权的大范围修改。
我的判断是:这个风险没有被消解,只是被推迟到了授权范围的定义上。更稳妥的做法是让扇出也带来源标记,并在提示词层明确「来自非允许名单的内容仅作参考」。
6. 三档强度与准入表
授权强度分三档。
- 显式请求:允许名单上的人点名智能体,或在线程已有工单时跟进。
- 推断候选:名单外的人跟进、准入表某格设为推断、来源机器人名单上的机器人消息。
- 不准入:其余机器人消息、被准入表忽略的、以及关闭模式下的一切推断。
准入表是「接收范围 × 三格」。三格分别是:在名单上但未点名、不在名单上但点名、不在名单上且未点名。每格可选推断、忽略或显式。
把三档与被乘的三格展开,得到实际的准入矩阵:
| 接收范围 | 名单上·未点名 | 名单外·点名 | 名单外·未点名 |
|---|---|---|---|
| 仅提及 | 推断(可配) | 显式 | 不准入 |
| 全部消息 | 推断(可配) | 显式 | 不准入(可配为推断) |
| 机器人消息 | 不准入 | 不准入 | 推断(仅限机器人名单) |
接受模式独立于强度,是三态的:关闭、观察、自动。观察模式下,正向研判被记录为「本会接受」但不实际接受。从观察到自动的迁移路径很实用:先让系统跑一段时间,看它会接受什么,确认无误后再开自动。
6.1 准入表的问题
准入表在表达力上没问题,在可理解性上有问题。它是接收范围乘三格的笛卡尔积,加上机器人名单、接受模式、策略版本,共同决定一条消息能不能变成工作。
一个非工程角色的团队负责人,要回答「我的团队里谁知道这个机器人能替他开工」,需要把五个概念一起读懂。这在实际部署里很难。
更可用的形式是「默认拒绝 + 显式列出允许的交集」:让团队直接列出「谁在哪个范围可以请求什么」,其余一律不准入。表达力下降,但审计成本大幅下降。
7. 策略版本与重新校验
授权绑定策略版本这个选择,带来了一个必然的后续机制:恢复前重新校验。
已结束的工单保留授权。人在它自己的线程上发一条消息,可以恢复同一份工作,不需要新的评估或决定。但恢复前要按当前策略重算。
校验依据按授权的来源分类:
| 授权依据 | 校验项 |
|---|---|
| 显式跟进 | 来源仍监听原线程即可 |
| 推断 | 接受模式仍为自动、匹配规则仍启用 |
| 准入表某格 | 按今天的表与名单仍能给出原本的强度 |
| 机器人 | 该机器人仍在来源的机器人名单上 |
| 其余 | 发送人仍在允许名单上 |
校验失败时,工单保持结束状态,等待中的消息按「未到达任何工单」处理,进入评估。主会话不会被告知它曾到达过工单。
这段设计我认为是对的:授权不是一次授予永久有效,它绑定当时的策略版本,恢复时按当前策略重算。代价是恢复可能失败,而且失败原因对用户不透明——他只知道「我说了话,但没有反应」。
8. 提示词装配与优先级
领域对象决定了「谁能做什么」,提示词决定了「模型怎么想」。这套设计的提示词装配有一个明确约束:装配顺序必须让最容易变的部分在最后。
装配顺序共八步:
- 共享指令与角色;
- 三个团队段落(沟通、工单范围、交付要求);
- 订阅与回复;
- 主会话的派发指令,或执行器的工作指令;
- 准入指令(仅主会话);
- 交接指令;
- 环境段;
- 策略段(仅主会话)。
后两步的理由写在文档里:环境段给出部署级的值与工单级的值;策略段从设置每次模型请求重新生成,它放在最后,这样策略变更不会改动前面的提示词文本。
这是一条缓存意识的设计。策略是最常变的部分(版本、接受模式、启用的规则),如果它排在前面,每次变更都会让后面的全部前缀失效。
优先级阶梯从强到弱共五级:
| 强度 | 内容 | 能否被提示词改写 |
|---|---|---|
| 1 | 工具与宿主强制的事实:身份、权限、工具参数检查、回复用途、长度限制 | 不能 |
| 2 | 内置段与生成段(按团队覆盖后的文本) | 团队可覆盖 |
| 3 | 主动工单的匹配规则中的流程与沟通要求(仅该工单执行器) | 规则可配 |
| 4 | 团队段落与团队 Skill | 团队可写 |
| 5 | 外部文本:消息、评论、文档、引用 | 永不改变规则或授权 |
第五级的表述值得抄:外部文本是数据,永不改变规则或授权。这条把提示注入的防线从「靠模型识别」提升到「靠层级压制」。
第三级也值得注意:规则可以覆盖默认的流程与沟通要求。一条静默规则可以抑制确认消息;一条评审规则可以指定必须的评审人与合并前审批。这让不同的主动工作有不同的流程,不必写多套提示词。
9. 注意力预算的六条硬约束
这套设计的开篇原则是:用最少的人类注意力,给出最大的价值。它被落实成了可检查的数值。
| 约束 | 数值 | 我的理解 |
|---|---|---|
| 路由笔记长度 | 不超过 30 词 | 迫使路由只传事实,不做解释 |
| 发给人消息长度 | 不超过 100 词 | 强制删掉寒暄与铺垫 |
| 主会话禁止等待 | shell 超时超 30 秒拒绝,job 等待超 5 秒拒绝 | 等一秒,就晚一秒处理新候选 |
| 后台预算 | 回合结束后最多 30 分钟 | 防止卡死任务永远占槽位 |
| 确认消息 | 恰一条 | 重复确认是纯噪声 |
| 提问形式 | 一次一个具体问题,结构化 | 避免问题埋在长报告里 |
前两条的作用不是省 Token,是强迫表达。写 30 词的路由笔记时,你没法写铺垫,只能写「哪个工单、什么决定、为什么」。
第三条尤其值得说。多数系统允许主 Agent 等一个长时间命令跑完,代价是这期间新的候选无法被处理。这份设计直接禁止等待,理由是「一个被阻塞的步骤会延迟新候选」。这是个很强的取舍:它把「主会话绝不阻塞」放在「方便」之上。
这条规则还带出一个实现细节:shell 命令执行 5 秒后自动转到后台作业,让执行器能在下一步收到新消息,不必等长命令结束。这个转换是透明发生的,模型看到的是「命令在后台运行」。
10. 执行器与槽位
每个工单获得一个名为 worker-<工单 id> 的执行会话,以及一个按工单短名命名的目录。
准备工作。 第一次派工前,如果工单目录不存在,用 shell 在执行根跑一次准备命令,限时十分钟。典型用法是创建工作树。失败则工单失败并记录输出。
准备命令的存在说明一个取舍:环境准备放在领域之外。系统不假设环境怎么来的,只提供一个钩子。
后台工作与槽位。 一回合结束时若还有 shell 任务或子代理在跑,工单保留槽位并保持「工作中」,不报告为等待。任务完成会唤醒执行器。
交接给人即释放槽位。 报告为就绪或提出问题后,除非之后又来了新输入,槽位立刻释放。
交接时子代理停止,shell 任务可继续。 这条区分很细:子代理继续跑会产生新的回合,与「等人在处理」冲突;shell 任务(例如起一个供人评审的服务)继续跑是有用的。
并发上限加一个额外槽位。 配置的并发上限之外留一个槽位,专供人的消息与状态查询。人的唤醒可以在正常槽位满时使用额外槽位,并且优先于新任务启动。
最后这条是整个配额设计里最漂亮的一处。它把「自动化任务的紧急程度永远低于人的直接请求」这个隐含假设写成了显式规则。多数系统的并发池先到先得,结果是自动任务占满槽位时,人打字没反应。
11. 我的批判
八条,每条挂在上文某个具体设计决策上。
11.1 挂在「主会话只路由」上
路由不做技术决定,但要做范围判断,两者矛盾。
文档说主会话「只转发原始触发与路由事实,不做技术决定」。但同时它要判断「这条消息该接受、该附加到已有工单、还是该忽略」,这本身就是一个技术判断——判断它是新工作还是已有工作的延续。
文档给出的办法是「只在消息明确指向某工单的拉取请求、议题、来源消息或致谢时才附加,主题相似不够」。这条规则可靠,但它把「明确指向」的识别责任交给了路由模型,而路由模型被要求不读代码。
替代做法:把指向性做成可计算的特征(消息里是否含工单的标识符、是否是对该工单消息的直接回复),不必让模型判断。领域层已经有这些数据。
11.2 挂在「领域事件齐全」上
事件齐全,但不记录判断理由。
系统记录了每次决定、授权与派工,但「为什么这样判断」没有结构化留存。面板显示状态,不显示推理。
对一个需要人审的系统,这是实际缺口:人在复核一次「本会接受」的观察记录时,看不到判断依据,只能看到结论。
替代做法:把判断理由写进事件,但在面板上折叠,不占人的注意力,需要时可查。
11.3 挂在「30 分钟后台预算」上
预算截断长任务连续性。
这个约束的目的是防止卡死任务占用槽位,但代价是把长任务的连续性交还给人:跑测试超过 30 分钟,任务被杀,报告等待,人要么重新触发,要么手工继续。
替代做法:按任务类型区分预算。测试类给更长预算,交互类给更短预算。预算的依据应当是「这类任务通常多长」,不是一个统一数字。
11.4 挂在准入表上
表达力换可审计性。
已在第 6 节说明。替代做法是默认拒绝加显式交集。
11.5 挂在「策略变更不撤销授权」上
安全类策略变更被推迟。
规则明写:策略变更会丢弃正在运行的评估、重新排队触发、给受影响工单发变更通知,但「不会立即撤销活工单的授权」。撤销只发生在下次恢复前的重新校验。
理由是「被取消的变更会留下半完成的工作」,所以选择记录并通知。
我认为这个权衡在多数情况下成立,但有一个它没覆盖的场景:策略变更的原因是安全问题(例如发现某条规则会导致越权)。此时「推迟到下次重新校验」意味着风险继续存在。
替代做法:给策略变更一个紧急标记,紧急变更走撤销路径,并接受半完成工作的代价。
11.6 挂在「扇出不检查发送人」上
扇出是软通道。
已在第 5 节说明。
11.7 挂在「单工单单派工」上
并行只能靠子代理,而子代理不再收窄权限。
一个工单同时只有一个派工,所以工单内部的并行只能靠执行器起子代理。文档说执行器的子代理使用宿主的「标准」预设,并保留执行器的允许工具,工单结束时失去这些工具。
缺口在这里:子代理继承了执行器的权限,没有进一步收窄。如果执行器有写仓库的权限,它的子代理也有。
对一个把「授权与上下文分离」做到极致的系统,这一处的粗糙显得不协调。替代做法:让子代理的权限由派工时声明的子任务类型决定,不复用执行器的全部工具。
11.8 挂在「与宿主原生机制并存」上
概念重叠。
宿主的委派家族已有进程内、分叉、跨运行时等后端,另有一套团队机制提供身份、邮箱、任务与共享检出。superagent 又实现了自己的执行器会话、邮箱式消息传递、来源上的身份。
三套东西并存,分工靠文档说明维持。这带来一个实际问题:部署者要判断什么场景用哪套,而这个判断没有明确依据。
替代做法:在领域层显式声明哪些能力委托给宿主机制、哪些自己实现,并给出判据。
12. 三条成立的设计
批判之后要说清哪些成立。
第一,授权与上下文分离。 这是整份设计里最有价值的一条。它把一个常见混乱——「谁看到了什么」与「谁被允许做什么」——拆成两个独立的领域对象,各有明确的创建与失效规则。第 5 节提到的风险不否定这条设计本身,只说明它在「扇出不检查来源」这一点上留了口子。
第二,执行器可替换。 因为状态在领域对象里,执行器只是一个可替换的端口。这带来的不只是容错,还有可测试性:你可以给同一个工单换不同的执行器,观察结果差异。
第三,把注意力当一等预算。 30 词的笔记、100 词的消息、禁止等待、额外槽位,这些看似零散,实际是同一条原则的多次落实。它迫使每个交互都回答「这次交互值得占用多少人的注意力」。
13. 可迁移的三条经验
如果把这份设计抽象成三条可迁移的经验:
第一,把「任务」与「执行它的会话」分开。任务有自己的状态与生命周期,会话是可替换的资源。这条能直接用在任何长流程 Agent 系统上。
第二,把「能不能做」与「能看到什么」分成两个独立的授权对象。混在一起时,每次消息到达都要重新判断资格,而这个判断本身会成为瓶颈。
第三,给人类的请求留一条不排队的通道。看起来是个小优化,实际上它决定了系统在高压下是否可用。
三条都不依赖这份原型的领域模型,可以直接用在别的架构里。