Files
dsh-superagent/docs/002-授权.md
T

13 KiB
Raw Blame History

002 授权

授权决定谁可以开始工作、以及那项工作可以包含什么。本文建立在 001 领域模型 之上。

host 强制执行允许名单、规则与 Mandates。消息、评论与被引用的文档只是决策的输入;它们不能改变那些权限。

一条 Trigger 的两条路径

Eval:决定是否开始工作

Source 记录消息是否点名了 agent:提到 bot 或 agent 账号的记为 mention,私聊记为 direct。gate 检查发送者与该 Source 的 Policy:

  • 名单内的人点名 agent,构成显式请求。
  • 人在一个已有 Tickets 的 Thread 上的跟进,遵循下面的规则。
  • 其他人类消息按它们的接收范围走准入表。
  • Source bot 名单上的 bot 可以产生 inferred 候选,永远不能构成显式请求。其他 bots 不予准入。

Subscriptions:继续已有的工作

被订阅 Thread 上的每一条消息都会作为上下文到达订阅它的 Tickets,包括来自 bots 与 CI 的消息。这条投递路径绕过 gate,不授予任何新的授权。每个 Ticket 仍受它的 Mandate 约束。

Ticket 自己的 Thread 上的人的消息,归该 Ticket 的 Executor 决定。main Session 把一条消息 attach 给某个 Ticket 时同理。消息仍由 Executor 认领期间,不进入 Eval、不产生 Decision。每个 Executor 要么在自己 Ticket 的 scope 内处理这个请求,要么把它交还给别处路由。

认领在 Ticket 存活期间持续,即使有人移除了它的 Subscription。认领也覆盖这样一个 ended Ticket:它不是被人停掉的,并且有消息正在等待让它恢复。

main Session 会收到一条跟进通知。消息仍由 Executor 认领时,main Session 只能应人的要求停止工作;accept、attach、ignore 与 requeue 都会被拒绝。一次停止仍然要过 gate,带着下面所述的跟进强度。

交还与释放跟进

Executor 只在一条消息要求的工作超出 Ticket 的 scope、或属于另一个 Ticket 时,把它交还一次。Ticket 保留它的 Subscription。如果一个被 attach 上来的 Thread 无关,Executor 交还它在其中认领的那些消息。

一条消息只有在没有任何 Ticket 仍认领它之后,才进入一次全新的 Eval。它的候选里带着各个 Executors 的理由。gate 按该 Thread 上的一条普通消息来判它,main Session 可以接单、把它 attach 到别处、或忽略。交还一条 Ticket 并未认领的消息——例如一条 bot 消息、或一条人已经送进 Eval 的消息——什么都不会排队。

一个 Ticket 结束时,会释放它的 Executor 没有读过或没有认领的跟进。其中包括从未投递给 Executor 的消息,以及在其最后一次 Report 或回合结束之后才送达的消息,只要它既没有认领、答复过、也没有交还。每条被释放的消息在没有其他 Ticket 认领时进入 Eval。一次 stop 只结束被停那个 Ticket 的工作。

被停掉的 Ticket 不会把它的 stop 消息再送一次 Eval,除非有另一个 Ticket 交还过它。停掉它的 main Session 可以把这条消息的其余部分接成一项新工作。

人可以从面板把一个被认领的跟进送进 Eval。若 main Session 随后又把它 attach 回一个活着的 Ticket,Executor 会再次收到它。当没有任何有效的判断覆盖这条消息时,面板还提供重新评估。

仅上下文的订阅

Executor 可以为了上下文关注一个相关的 Thread,例如另一个 Issue 或 pull request。它的消息能到达 Ticket,但不授予授权或跟进强度。那里的人类消息不会恢复一个 ended Ticket,除非 main Session 把来自那个 Thread 的一条消息 attach 过来。main Session 关于其他工作的备注同样只是上下文。

没有到达任何 Ticket 的人类消息——包括 Tickets 都被人停掉的 Threads——走 Eval 路径。bot 消息走哪条路径,与它们向订阅 Tickets 的投递互不相干。

强度

强度 情形
explicit 名单上的人点名 agent,或在一个已经有 Ticket 的 Thread 上跟进;准入表中设为 explicit request 的单元格
inferred 不在名单上的人在一个有 Ticket 的 Thread 上跟进,除非他的准入表单元格是 explicit;准入表中设为 inferred candidate 的单元格;Source bot 名单上某个 bot 的消息点名了 agent,或落在 Source 全量接收消息之处
not admitted 其他任何 bot 的消息、准入表忽略的一切、以及 accept mode 为 off 时所有 inferred 的情形

对一个未列入名单的人,explicit 准入单元格有优先权——即使是在有 Tickets 的 Thread 上,并且在任何 accept mode 下都成立。否则他的跟进按 inferred 处理,在 off 模式下不予准入。只为上下文而关注的 Threads 上的消息走准入表,而不是跟进强度。

准入

每个 Source 的 Policy 都有一张准入表。它的行是接收范围:

  • Feishu: 每个已配置的 chat、bot 收到过 @ 的其他 chats、以及私聊。其他 chats 只有当 Feishu 卡片上的 其他群 设为 仅 @ 时才接收。
  • GitHub: 新的 Issues、新的 pull requests、评论、以及默认分支的 CI。

一行至多三个单元格。每个单元格决定一条消息变成显式请求、inferred 候选、还是什么都不是:

单元格 选项 默认
名单内、未点名 agent inferred、ignore inferred
名单外、点名 agent explicit、inferred、ignore ignore
名单外、未点名 agent inferred、ignore ignore

未点名消息的单元格,只在 Source 全量接收消息的地方可用,例如设为 全部消息 的 Feishu chat、或开了全量接收开关的 GitHub scope。CI 结果没有发送者,所以它的行只提供最后一个单元格。bot 消息用 bot 名单判断,不看这些单元格。

把"名单外、点名 agent"单元格设为 explicit,等于允许那个范围内的任何人通过 @ agent 请求工作。这对一个专门的支持群可能有用。inferred 候选仍然取决于 accept mode。跟进通常绕过这张表,唯一的例外是 explicit 单元格仍会给出 explicit 强度。

准入表是 Policy 版本的一部分。削弱一个单元格不会立刻停止按它接单的那些 open Tickets。在恢复前的重新校验中,它可以吊销它们的 Mandates。

Bots 与自动化评审者

host 把 GitHub Apps、以 [bot] 结尾的 logins、以及 Feishu bots 认作 bots。Source 可以列出 dependabot[bot] 这样的 bots,它们的消息可以成为 inferred 候选。它们要么点名 agent,要么发在一个全量接收消息的范围里。它们不能构成显式请求,也不能停止 Tickets。

用普通账号的自动化评审者,在 host 看来是人。它的评论会到达订阅的 Tickets 并产生跟进通知。团队的提示词文本必须把这类账号指认为自动化评审者。

路由候选

main Session 用 orchestrator_route 路由显式候选。一次调用可以包含多个 accept、attach、stop 或 ignore 决定。每一项独立处理:某一项被拒或字段缺失,不妨碍其余各项执行。

inferred 候选通常需要一次完整的评估。有一个捷径:如果它的卡片明显不触及任何启用的规则——例如纯闲聊或无关讨论——main Session 可以直接忽略它。Decision 会记录 ignoredFromCard,以区别于一次规则评估。main Session 在它的 Policy 提示词节里读取启用的规则。

接一个 inferred 候选永远需要评估。直接 accept 会被拒绝——如果 gate 并没有把它准入为 explicit。

Accept mode 与规则

accept mode 是 Policy 的一部分:

模式 效果
off 任何 inferred 的东西都不成为候选,例如未点名的消息、bot 的消息、名单外的人的跟进
observe(默认) inferred 候选会被评估;一次正向的评估记为 ignore 并标上 "would accept",什么都不接单
auto 对 inferred 候选的一次正向评估即接单

显式请求在所有模式下都有效。从 observe 起步,评审过记录的 Decisions 之后再切到 auto。每次评估对每条启用的规则恰好判断一次,无论命中与否。一次主动接单至少要求一条命中的规则。

每条规则描述一类主动工作。Ticket 的 Mandate 记录命中的规则,它的 Executor 会收到这些规则的名字与指令。

对那个 Ticket 而言,规则的流程与沟通要求会覆盖默认值。一条静默规则可以压掉认领回执;一条评审规则可以指定合并前所需的评审人与批准。适用于每个 Ticket 的要求应当放进团队提示词分节。

Policy 版本

规则、accept mode 与各 Source 的 Policy 共同构成 Policy 版本。每个 Mandate 都记录它接单时所在的版本。

一次版本变更会丢弃正在运行的 Evals,并把它们的 Triggers 重新排队。它还会给每个受影响的活 Ticket 发一条 policy-changed Input。这条 Input 说明是哪种情况:

  • 一条命中的规则变了(包括它的新文本),或不再适用;
  • 发起人不再可以开始工作;或
  • 主动工作不再被接受,例如模式从 auto 改走了。

未受影响的 Ticket 收不到通知。这次变更不会立刻吊销活着的 Tickets 的 Mandates。

Source 配置与 Policy 是分开的。它控制什么能进入系统:读哪些 chats、某个 scope 是接收全部消息还是只收 @。一个 Mandate 记录它的 Source 与接收范围。恢复要求 Source 仍然监听这个 Thread,但不要求它仍然全量接收那里的一切。

答复在它们的 Source 关闭时等待。若 Source 不再监听它们的目的地,答复被丢弃。

关于 Executor 可以拿自己的结果做什么的条件——例如它何时可以合并——不决定谁可以授权工作。它是 agent 指令的一部分,不是 Policy 的一部分。

恢复与停止

一个 ended Ticket 保留它的 Mandate。有人在它自己的某个 Thread 上发一条消息,无需新的 Eval 或 Decision 就能恢复同一份工作,无论发送者是谁。Bots 与 CI 不会唤醒 ended Tickets。由 Executor 决定这个请求是否合 scope,不合就结束这一轮。

只有三个操作会改变一个 Mandate:accept 创建它,人的 stop 撤回它,重新校验失败吊销它。被人停掉的 Ticket 保持 ended,之后什么都不收。那里要请求的工作需要一次新的接单。

恢复前的重新校验

Source 必须仍然监听原始的 Thread。其余的检查取决于 Mandate 的依据:

  • 一条 explicit 跟进类的 Mandate 直接通过。
  • 一条 inferred Mandate 需要 auto 模式、并且它命中的规则仍然启用。
  • 由准入表单元格准入的 Mandate,按今天的准入表和发送者的成员身份,必须至少仍能收到它原来的强度。因此把发送者移出名单可以吊销一个经"名单内发送者"单元格接下的 Mandate。
  • bot 的 Mandate 要求该 bot 仍在 Source 的 bot 名单上。inferred 的表类与 bot 类 Mandates 还需要 auto 模式与仍然启用的命中规则。
  • 其余 Mandates 要求发送者仍在该 Source 的允许名单上。

若重新校验失败,Ticket 保持 ended,一条等待中的消息都不取。每条消息按"没有到达任何 Ticket"处理;若没有其他 Ticket 认领它,它进入 Eval。main Session 不会被告知它到达过 worker。处理被暂停之后也是如此。attach 到该 Ticket 会被拒绝,继续工作需要一次新的接单。

  • 一个人在 Thread 里说"停"是一条普通 Trigger, 由 Eval 决定 stop。Executor 不能声称有人要求它停止。
  • 面板里的 Stop 是一条命令。 它直接作用于 Ticket,并被记录为一次手动停止。
  • 等待类条件属于 Sources。 一个像"N 小时无人 assign"这样的条件,应当是一个会自己等待并再次检查的 Source;Eval 从不等待。

Eval 没有完成时

时限从候选到达 main Session 时开始计。等待投递的时间不计入,一个从未送达的候选永不过期。

一次超时的 Eval 把它的 Triggers 重新排队再试。连续两次重试之后,第三次超时记为 unavailable。一次评估失败也记 unavailable;后续的一个 Trigger 可以发起新的 Eval。一次 Policy 变更、或依据过期 Policy 做出的判断,记为 unavailable 并立刻按当前版本把该批次重新排队。

evals 健康条目报告最近一小时内未解决的 unavailable Decisions。scripts/dsh-superagent status 会包含那个错误。

面板只在以下条件全部满足时才请求人工注意:这条 unavailable Decision 距今不足 24 小时、仍是它所在 Thread 上最新的 Decision、没有被 dismiss、且该 Thread 既不处于 queued 也不处于 evaluating。人可以再次评估这个 Trigger,或 dismiss 这个问题。