ARTICLE · 1334265
dsh-superagent 文档|002 授权

授权决定谁可以启动工作,以及该工作可以包含什么。本文档建立在 001 领域模型 之上。
主机(host)负责强制执行允许列表、规则(rules)和任务授权(Mandates)。消息、评论和引用的文档都只是做出某个决定时的输入;它们无法改变这些权限。
Trigger 的两条路径
Eval:决定是否启动工作
来源(Source)会记录一条消息是否在寻址(address)智能体:对机器人或智能体账号的 @提及记为 mention,直接消息记为 direct。门控(gate)检查发送者以及该 Source 的策略(Policy):
一个在名单上、且寻址智能体的人,做出的是一个显式请求。 某人在一条带有工单(Tickets)的线程(Thread)上的跟进,遵循下文的规则。 其他人类消息使用其接收范围(receiving scope)所对应的准入表(admission table)。 Source 的 bot 列表上的一个机器人(bot)可以产生一个推断候选(inferred candidate),但绝不会是显式请求。其他 bots 不予准入。
Subscriptions:继续已有的工作
每一条出现在被订阅 Thread 上的消息,都会作为上下文送达其被订阅的 Tickets,其中也包括来自 bots 和 CI 的消息。这种投递会绕过门控,也不授予任何新的权限。每个 Ticket 仍然受其 Mandate 的限制。
某人在一个 Ticket 自身的 Thread 上发送的消息,归该 Ticket 的执行器(Executor)来决定如何处理。当主会话(main Session)把一条消息附加到某个 Ticket 上时,也同样如此。在某个 Executor 拥有该消息期间,它不会进入 Eval,也不会得到任何决定(Decision)。每个 Executor 要么在其 Ticket 的范围内处理该请求,要么把它交回(hands back)以便路由到别处。
只要该 Ticket 处于活跃状态,所有权就一直持续,即使某人撤销了它的订阅(Subscription)。所有权同样覆盖这样一个已结束的 Ticket:它并非被人叫停,且有消息正等待着恢复它。
主会话会收到一条跟进通知。在某个 Executor 拥有该消息期间,主会话只能应某人的请求去停止工作;accept、attach、ignore 和 requeue 都会被拒绝。一次停止(stop)仍然要通过门控,并带有下文所述的跟进强度。
交回与释放跟进
当某条消息请求的工作超出该 Ticket 的范围,或该消息属于另一个 Ticket 时,Executor 会把这条消息交回一次。该 Ticket 保留它的 Subscription。如果一条被附加的 Thread 并不相关,那么 Executor 会把它在那里所拥有的消息交回。
只有在没有任何 Ticket 仍然拥有某条消息之后,这条消息才会进入一次全新的 Eval。它的候选会包含各个 Executor 给出的理由。门控会把它当作该 Thread 上的一条普通消息来评判,而主会话可以接受它、把它附加到别处、或忽略它。交回一条该 Ticket 原本并不拥有的消息——比如一条 bot 消息,或一条某人已经发往 Eval 的消息——不会排入任何队列。
当一个 Ticket 结束时,它会释放那些其 Executor 未曾阅读或未曾接手的跟进消息。这些包括:从未投递给该 Executor 的消息,以及在其最后一次报告(Report)或回合结束(turn end)之后投递的消息——只要其 Executor 既未确认、未回复、也未把它们交回。每一条被释放的消息,在没有其他 Ticket 拥有它时进入 Eval。一次停止只会结束被停止的那个 Ticket 的工作。
被停止的 Ticket 不会把它的停止消息再次送往 Eval,除非有另一个 Ticket 把它交了回来。叫停它的那个主会话,可以把这条消息的剩余部分接受为新工作。
某人可以从面板(panel)把一条被拥有的跟进消息发往 Eval。如果主会话随后又把它附加回一个活跃 Ticket,那么 Executor 会再次收到它。当没有任何有效评判覆盖该消息时,面板也会提供重新评估。
仅上下文订阅
某个 Executor 可以为了获取上下文而关注一条相关的 Thread,比如另一个 Issue 或 pull request。该 Thread 的消息会送达该 Ticket,但不授予任何权限或跟进强度。在那里,人类的消息不会唤醒一个已结束的 Ticket,除非主会话把来自该 Thread 的一条消息附加上来。主会话关于其他工作的备注,同样也只是上下文。
没有到达任何 Ticket 的人类消息——包括那些其 Tickets 已被人叫停的 Threads——走的是 Eval 路径。bot 消息无论是否被投递给了被订阅的 Tickets,都独立地走这条路径。
强度(Strength)
off 时的每一个推断情形 |
对于未在名单上的人,一个显式准入单元格具有优先权——即使在其上带有 Tickets 的 Thread 上也是如此,并且在每一种接受模式下都生效。否则,他们的跟进被归为推断,且在 off 模式下不予准入。仅为了获取上下文而关注的 Threads 上的消息,使用准入表,而不是跟进强度。
准入(Admission)
每个 Source 的 Policy 都有一张准入表。它的行是各个接收范围:
- 飞书(Feishu):
每一个已配置的聊天、机器人被 @提及而从中接收消息的其他聊天,以及直接消息。仅当飞书卡片上的 其他群 被设为 仅 @ 时,才会接收其他聊天。 - GitHub:
新的 Issues、新的 pull requests、评论,以及默认分支的 CI。
一行最多有三个单元格。每个单元格决定一条消息成为显式请求、推断候选,还是什么都不成为:
针对未寻址消息的那些单元格,只有在该 Source 接收所有消息时才可用,比如被设为 全部消息 的飞书聊天,或启用了"接收所有消息"开关的某个 GitHub 范围。CI 结果没有发送者,因此它所在的行只提供最后一个单元格。bot 消息使用 bot 列表,而不是这些单元格。
把"未在名单上、且寻址智能体"的单元格设为显式,可让该范围内的任何人通过 @智能体来请求工作。这对一个专门的支持聊天可能很有用。推断候选仍然取决于接受模式。跟进通常会绕过这张表,只是"显式"单元格仍然授予显式强度。
准入表是 Policy 版本的一部分。削弱某个单元格,不会立即停止那些依据它而被接受的、处于开放状态的 Tickets;但这一削弱可能会在恢复前的重新验证中撤销它们的 Mandates。
Bots 与自动化审阅者
主机把 GitHub Apps、以 [bot] 结尾的登录名,以及飞书机器人识别为 bots。一个 Source 可以列出某些 bots(例如 dependabot[bot]),它们的消息可以成为推断候选。它们必须要么寻址智能体,要么在某个接收所有消息的范围内发布。它们不能做出显式请求,也不能停止 Tickets。
一个使用普通账号的自动化审阅者,在主机看来被计为一个人。它的评论会到达被订阅的 Tickets,并生成跟进通知。团队提示词文本必须把此类账号指明为自动化审阅者。
路由候选
主会话使用 orchestrator_route 来路由显式候选。一次调用可以包含多个 accept、attach、stop 或 ignore 决定。每个条目都独立处理:一个被拒绝的条目,或一个缺失的字段,都不会阻止其他条目运行。
一个推断候选通常需要一次完整评估。有一个捷径:如果它的卡片明显不触及任何启用的规则——比如普通闲聊或不相关的讨论——那么主会话可以直接忽略它。该 Decision 会记录 ignoredFromCard,以区别于一次规则评估。主会话在其 Policy 提示词部分读取那些启用的规则。
接受一个推断候选,总是需要一次评估。直接 accept 会拒绝一个门控未作为显式准入的 Trigger。
接受模式与规则
接受模式是 Policy 的一部分:
off | |
observe | |
auto |
显式请求在每一种模式下都有效。从 observe 开始,并在审查过被记录的 Decisions 之后切换到 auto。每次评估都会对每一条已启用的规则恰好评判一次,无论其是否匹配。一次主动接受,至少需要一个匹配的规则。
每条规则都描述一类主动工作。该 Ticket 的 Mandate 记录所匹配的规则,而它的 Executor 接收这些规则的名称和指令。
对于该 Ticket,某条规则的流程与沟通要求会覆盖默认设置。一条沉默(silence)规则可以抑制一次确认;一条评审(review)规则可以指定所要求的评审人,以及合并前所需的批准。适用于每个 Ticket 的那些要求,应当放在团队提示词部分里。
Policy 版本
规则、接受模式,以及每个 Source 的 Policy,共同构成 Policy 版本。每个 Mandate 都记录它是在哪个版本下被接受的。
一次版本变更会丢弃正在运行的 Evals,并把它们的 Triggers 重新排队。它还会向每个受影响的活跃 Ticket 发送一个 policy-changed Input。该 Input 会解释是否:
某条所匹配的规则发生了变化(包括其新文本),或者不再适用; 该请求者可能不再可以启动工作;或者 主动工作不再被接受,例如因为模式从 auto发生了变更。
未受影响的 Tickets 不会收到任何通知。这次变更也不会立即撤销那些活跃 Tickets 的 Mandates。
Source 配置与 Policy 是分开的。它控制什么进入系统:读哪些聊天,以及某个范围是接收所有消息、还是只接收 @提及。一个 Mandate 记录它的 Source 和接收范围。恢复需要该 Source 仍然在监听这条 Thread,但并不要求它仍然接收那里的每一条消息。
当相应的 Source 处于关闭状态时,回复会等待。如果该 Source 不再监听其目的地,这些回复会被丢弃。
关于 Executor 可以如何处置其自身结果的一个条件——比如它何时可以合并——并不决定谁可以授权工作。它是智能体指令的一部分,而不是 Policy 的一部分。
恢复与停止
一个已结束的 Ticket 会保留它的 Mandate。某人在其自身某条 Thread 上发送的一条消息,可以在没有新的 Eval 或 Decision 的情况下恢复相同的工作,无论是由谁发送的。Bots 和 CI 不会唤醒已结束的 Tickets。由 Executor 决定该请求是否符合范围;如果不符合,它就结束这一轮。
只有三种操作会改变一个 Mandate:accept 创建它、某人的 stop 撤回它、以及一次失败的重新验证(revalidation)撤销它。被某人停止的 Ticket 保持已结束状态,不再接收任何东西。在那里请求的工作需要一次新的 accept。
恢复前的重新验证
该 Source 必须仍然在监听原来的 Thread。其余检查取决于该 Mandate 的依据:
一个显式跟进 Mandate 直接通过。 一个推断 Mandate 需要 auto模式,且其所匹配的规则仍处于启用状态。一个由表格单元格准入的 Mandate,按照今天的表格和发送者成员资格,必须仍然至少接收其原始强度。因此,从名单上移除一个发送者,可能撤销一个通过"在名单上的发送者"单元格而被接受的 Mandate。 一个 bot 的 Mandate,要求该 bot 保持在 Source 的 bot 列表上。经由表格或 bot 列表以推断方式准入的 Mandates,还需要 auto模式和仍然启用的匹配规则。其他 Mandates 要求发送者保持在 Source 的允许列表上。
如果重新验证失败,该 Ticket 保持已结束状态,且不接收其任何等待中的消息。每条消息都被视为没有到达任何 Ticket;如果没有其他 Ticket 拥有它,它就进入 Eval。主会话不会被告知它到达了某个 worker。这也适用于一次处理暂停之后。附加到该 Ticket 会被拒绝,而进一步的工作需要一次新的 accept。
- 某人在一条 Thread 上说"stop",是一个普通的 Trigger,
由 Eval 来决定 stop。某个 Executor 不能宣称某人要求它停止。 - 面板中的 Stop 是一条命令。
它直接作用于该 Ticket,并被记录为一次手动停止。 - 等待条件归属于各个 Sources。
像"N 小时内没有指派人"这样的条件,将是一个自行等待并重新检查的 Source;Eval 从不等待。
当 Eval 未完成时
时限从候选到达主会话时开始计算。等待投递所花的时间不计入,而一个从未投递的候选永不过期。
一次过期的 Eval 会将其 Triggers 重新排队以再试一次。经过两次连续重试后,第三次过期会记录 unavailable。一次评估失败也会记录 unavailable;一个后续的 Trigger 可以启动一次新的 Eval。一次 Policy 变更,或针对一个过时 Policy 做出的决定,会记录 unavailable,并立即在当前版本下将该批次重新排队。
evals 健康条目会报告过去一小时内未解决的 unavailable Decisions。scripts/dsh-superagent status 会包含那个错误。
面板只有在满足以下全部条件时才请求人工关注:该 unavailable 的 Decision 距今不足 24 小时、仍是其所在 Thread 上最新的 Decision、尚未被消除(dismissed),且该 Thread 既不处于排队(queued)也不处于评估(evaluating)状态。某人可以再次评估该 Trigger,或消除该问题。















































































