夜雨聆风学习资料网

ARTICLE · 1334293

dsh-superagent 文档|003 执行

dsh-superagent 文档|003 执行

本文档描述一张已受理的工单(Ticket)如何经历执行、交付与恢复。它建立在 001 领域模型 与 002 授权 之上。

工单阶段

每张处于打开状态的工单都有一个阶段,由它正在等待的内容决定。请自上而下阅读下表,第一条匹配的规则生效。

阶段
条件
面板标签
person / delivery
向 Executor Session 投递一条 Input 失败;它会在 2 秒后重试,随后按每次翻倍、最长到一分钟的延迟重试;而一次成功、一次新的 Assignment,或工单的结束都会清除该状态
Input undelivered(Input 未投递)
person / answer
它带有一个 Attention
Awaiting answer(等待答复)
person / review
最新一条 Report 处于 ready
Awaiting review(等待审阅)
person / blocked, failed
最新一条 Report 处于 blocked 或 failed,且此后没有任何来自人的消息到达该工单
Blocked · Failed(受阻 · 失败)
working / queued
没有 Assignment,或有一个处于 pending 状态
Queued(排队中)
idle
最新一条 Report 处于 waiting,或 Assignment 已结束
Waiting for a Trigger(等待触发)
working
其他所有情况
Working(执行中)

person 阶段意味着该工单在没有人类输入的情况下无法继续。一条新的人类消息会把一张 blocked 或 failed 的工单释放回它的 Executor,于是它进入下一条匹配的阶段,通常是 working。

一张 idle 的工单已经完成它的这一轮,且没有遗留待答的问题。订阅 Thread 上的一个 Trigger 可以让它继续。

Executor

每张工单都会获得一个名为 worker-<ticket id> 的 Executor Session,以及一个以该工单短名命名的目录。目录创建于所配置的执行根目录下,该根目录默认为 <stateDir>/work。

Executor Session 以运维用户(operator)的操作系统账户身份运行命令,无需审批。它们的 dsh-superagent-worker 预设(preset)包含了工作所需的工具,并会在 5 秒后将前台 shell 命令转为后台任务。这样,worker 就能在下一步接收新消息,而不必等待一条长命令执行完毕。

主 Session 使用 dsh-superagent-router,带有相同的 5 秒 shell 与任务等待限制,以及一套用于路由的更小的工具集。Session 会保留其创建时所选定的预设。Worker 子代理(subagent)使用 DeepSeek Harness 的 standard 预设。

后台工作与 worker 槽位

当一轮结束时若仍有 shell 任务或子代理在运行,该工单会保有自己的 worker 槽位并保持 working 状态。它不会被上报为 waiting,主 Session 也不会收到任何 waiting 报告。一个已完成的任务或子代理会带着它的结果,在同一个槽位上唤醒 Executor。一旦某一轮在无后台工作的情况下结束,Executor 就会照常上报 waiting。

每一轮结束后,后台工作最多有 30 分钟完成时间。到达该上限时,shell 任务会被杀死,工单被上报为 waiting。报告中会包含 Executor 最后留下的话,以及哪些任务被杀死、哪些子代理仍在活动。子代理被允许继续运行。Executor 会在它的下一轮读取被杀死任务的结果。

一次向人的交接(handoff)会立即释放槽位,并让 Report 保持生效。这适用于:一次 ready Report 或一个问题之后(除非此后又有一条 Input 到达),以及任何一次 blocked 或 failed Report 之后。

在交接时,子代理会停止。shell 任务——例如一台为供审阅而提供的服务器——可以继续运行直到 Assignment 结束。它们不占用槽位,并且免于 30 分钟限制,包括在其后的各轮中也是如此。它们的完成不会唤醒 Executor;Executor 会在下一轮读取其结果。

人也可以通过直接在 Executor Session 中书写来发起一轮。它的后台工作遵循相同规则。

当一张 Assignment 结束时,它的 shell 任务会被杀死而不唤醒该 Session。它的子代理会被释放,任何由它们唤醒的完成轮都会被丢弃。替代它的 Assignment 及其处于等待中的 Inputs 会在该关停完成之后启动。

在 DeepSeek Harness 进程内停止或重启该插件使用同一条关停路径。尚未被纳入 Session 历史的 Inputs 会被保留。关停会为每个 worker 的后台工作最多等待 10 秒;一次进程重启则会直接终止那些工作。一张在忙碌中被中断的 Assignment 会带着一条恢复说明重新启动。

Worker 子代理——包括嵌套的子代理——使用 DeepSeek Harness 的 standard 预设,并带有该 worker 的权限。它们只保留该 worker 所允许的工作工具,并在工单结束时失去这些工具。在 standard 下,一条 shell 命令会在 60 秒后转入后台。子代理的任务会在它结束时被杀死。

准备工单目录

在第一次 Assignment 之前,如果工单目录尚不存在,prepare 命令会在执行根目录下用 sh 运行一次。它有十分钟完成时间。若没有 prepare 命令,worker 就在一个空目录中启动。

一条失败的命令会使工单失败并记录其输出。一次重试,或一条出现在该工单某条 Thread 上的人类消息,都会再次尝试准备。一条典型的命令使用 git worktree 来创建一个检出(checkout)。当某个 pull request 其分支的最后一个路径段与工单的短名相符时,该 pull request 便归属于这张工单。

并发

一张工单从一次启动(launch)——包括准备工作——的开始,或从唤醒其 idle Executor 的那次投递起,就持有 worker 槽位。它在整个活动轮期间也一直持有该槽位。

所配置的并发上限适用于这些槽位,另外为一条人类消息或一次状态请求额外提供一个槽位。当常规槽位已满时,这类唤醒可以使用这个额外槽位,并且会在新启动之前抢占下一个空闲槽位。因此,新工作无法阻止一个人继续一张已有的工单。

其他 Inputs——例如 CI 结果——要等待一个常规槽位。一次启动会停留在 working / queued,直到有槽位空闲。启动之间并发运行,因此一条缓慢的 prepare 命令只会拖延它自己的工单。

执行变量

prepare 命令与 Executor 指令会收到相同的执行变量。命令通过环境变量读取它们;Executor 在它的提示词(prompt)中读取这些变量的值与说明。其中都不包含凭据。

变量
由谁注册
值
DSH_SUPERAGENT_DIR
, DSH_SUPERAGENT_NAME, DSH_SUPERAGENT_TICKET
Executor
该工单的目录、短名与 id
DSH_SUPERAGENT_BLOCKED_COMMANDS
Executor
Executor 不得运行的命令
DSH_SUPERAGENT_GITHUB_REPOSITORY
GitHub
该 Source 所关注的仓库,形如 owner/name
DSH_SUPERAGENT_GITHUB_ACCOUNT
, DSH_SUPERAGENT_GITHUB_COMMIT_EMAIL
GitHub
代理账号及其提交邮箱
DSH_SUPERAGENT_GITHUB_NUMBER
, DSH_SUPERAGENT_GITHUB_PR_HEAD
GitHub
发起该工单的 Issue 或 pull request,以及当它是 pull request 时的 head ref
DSH_SUPERAGENT_FEISHU_BOT
, DSH_SUPERAGENT_FEISHU_CHAT
Feishu
该机器人,以及发起该工单的会话(chat)

工单专有的值来自它的目录及其来源 Trigger。其他值来自当前的部署设置。只有面板能够更改执行环境设置;发送给主 Session 的文本无法更改所配置的 shell 命令。

Worker 通过代理账号的 gh 访问 GitHub,通过机器人的 lark-cli 访问 Feishu。被阻止的命令在按名称调用时会失败。

这些包装器(wrapper)与目录确立的是默认值,而非安全边界。worker 仍然可以通过绝对路径执行某个程序。

合并

自动合并受 Delivery requirements(交付要求) 提示词的约束。宿主(host)没有专门的合并决策逻辑。

默认情况下,Executor 从不以代理账号的身份启用自动合并:GitHub 会把合并归属于启用它的那个人。交付要求必须指名另一个账号用于合并的 CLI。若没有指定,则由人来合并该 pull request。

默认指令只允许对一张为该工单打开、且处于 ready 状态、所需批准已覆盖 head commit、并且新增行数加删除行数总计少于 200 行的 pull request 启用自动合并。在推送另一个修订版本之前,Executor 会撤回自动合并。

没有正文的审阅——例如批准——也会作为 Triggers 到达,并能够唤醒一个正在等待审阅的 Executor。这些指令约束的是代理,而非 GitHub 的合并规则。要强制要求批准,就在分支保护(branch protection)中要求它们,并撤销失效的旧批准(dismiss stale approvals)。省略合并 CLI,或删掉自动合并那一段,即可要求手动合并。

工单如何结束

  • Ready 且没有 pull request:
     一条 ready Report 会以 completed 结束该工单。
  • 无事可做:
     一个发现没有需要人来处理的工作的 Executor——例如一个无法复现或已被取代的请求——会上报 dropped。该工单会在不发送消息的情况下结束,除非有一条匹配的规则要求发送消息;而人后来在其 Threads 上的一条消息会恢复它。

Pull requests

一旦其 Executor 上报了它所发起的 pull request,该工单就会跟随这个 pull request。它也会跟随每一个从其分支打开的、一旦由 Executor 上报或由 GitHub Source 发现的 pull request。被重新打开的 pull request 会被再次跟随。

当它所打开的 pull request 中没有任何一个仍处于打开状态时,该工单即完成。如果它没有打开任何 pull request,那么它所跟随的所有 pull request 都必须已关闭。随后它以最近一次相关关闭(close)的结局结束,并保留其 Subscriptions,从而允许后来的一条人类评论恢复它。

只要被恢复(resumed)、被重试(retried)或被重启恢复(recovered)的工作尚未完成,一次关闭(close)就不会结束该工单。如果 Executor 已确认或答复了它所拥有的一条后续跟进(follow-up),但此后尚未作出一次非进展性的 Report,或尚未在读取那条消息之后结束一轮,那么它也会等待。一条后续的 ready Report 会在同一条 pull-request 完成规则得到满足时结束该工单。

Chat Channels 会收到一条关于 pull-request 结局的最终通知。GitHub 已经在 pull request 本身上显示该结局。移除某工单对一个 pull request 的 Subscription,也会停止它跟随该 pull request。

因此,"Awaiting review(等待审阅)"意味着一条带 pull request 的 ready Report。

后续跟进与上下文 Threads

Executor 在其工单自身的 Threads 上决定人类后续跟进。它处理范围内的请求,并把超出其范围或属于另一张工单的请求交还回去。当没有任何 Executor 拥有一条消息时,它就成为主 Session 的一个候选。否则主 Session 只能为了应人之请求停止工作而对后续跟进采取行动。

一条 Input 在 Session 把它纳入某一步骤、随后上报或结束一轮之后,即被视为已读。当一张工单结束时,那些既未被确认、未被答复、也未被交还的未读消息会返回给 Eval,如 002 授权 中所述。那些已读但未被采取行动的遗留消息——例如致谢——则不会返回。

仅作为上下文被跟随的 Threads 仍然会投递消息,但不会把人类后续跟进的所有权赋予该工单,也不会恢复已结束的工单。那些消息可以被独立评估。

Source 在有一张活动工单订阅期间会轮询所订阅的 Threads,并在最后一次 Trigger 与发起订阅的各工单结束二者中较晚者之后的两周内继续轮询。此后,消息只会通过常规接收到达:GitHub 通知或 Feishu 实时事件。

失败与恢复

Executor 受阻或失败

当某个 Executor Session 上报 blocked 或 failed,或启动失败或出错(这会记录 failed)时,该工单不会接收任何自动 Input。

一次模型服务商或网络的短暂故障——一个 HTTP 408、429 或 5xx,或一次被拒绝、被重置或超时的连接——会先被再次尝试,共三次并带递增的回退(growing backoff):一次启动会重新运行,一个其轮次失败的 worker 会被告知继续;而一条失败的 prepare 命令不会被再次尝试。

一旦失败,CI 和机器人消息会被记录在它上面,但它们不会被投递,也不会唤醒该 Session。

它的工具保持可用,因此人可以直接在 Session 中继续。

一次失败的启动不会自行再次尝试。

一条人类消息、一条其后既非 blocked 也非 failed 的 Report,或一次重试,都会结束这次搁置(hold)。

它是从已存储的 Reports 和 Inputs 推算出来的,因此一次重启会保留它。

重试

在一张打开的工单处于 person / delivery、person / blocked 或 person / failed 期间可用:对于每一种情况,补救措施都是一次新的 Assignment,它会重新启动其 worker 并接管那些处于等待中的 Inputs。

一旦一条人类消息到达了一张受阻或失败的工单,Executor 就已拥有该消息,于是该工单显示为 working,或者在一次失败的启动重新开始时显示为 queued,并且不提供重试选项,除非投递那条消息失败了。

一次重试会结束当前的 Assignment,并在同一个 Executor Session、目录、分支和 pull request 中启动一个新的。

Executor 模型只在创建 Session 时生效;一个已有的 Session 会保留它的模型。

重启

正在运行的 Assignments 会以 "host restarted"(宿主已重启)结束,新的会以一条恢复说明启动,该说明告诉 Executor:在重复任何外部动作之前先检查其目录、git 历史和 GitHub,并按最旧的工单优先处理。

处于 waiting、ready、blocked 或 failed 的 Assignments 会保持原样,除非在该 Report 之后有一条 Input 到达了它们,在这种情况下它们也会重新启动。

Sessions、目录、Subscriptions、待处理消息以及下述已存储的工作都能在重启中存活;每个 Executor Session 把它哪些消息纳入了历史的记录同样如此——一个步骤在其认领(claim)它们之后、并在其模型调用之前完成这一步:一条被某个被重启截断的轮次所认领的消息,在随后出现一次 Report 或一轮结束之后即被视为已读;而一条被某步骤认领、但在纳入历史之前被取消或错误截断的消息,则被视为未读。

一次重试或重启会把它所替代的那次 Assignment 已投递、但其 Executor Session 未纳入的消息再次交给新的 Assignment——这些消息可能因一次取消或一个被截断的步骤而丢失;一份仍留在 Session 中等待的副本,若被重启保留或跨崩溃持久化下来,会在新 Assignment 的第一条消息之前被取出,从而让新投递成为唯一的副本。

已存储的工作

dsh-superagent 在某个决定之后所做的事——例如把一个候选、一次后续跟进或一份报告转达给主 Session,启动一个 Assignment,或投递一条 Input——都是先存储、后执行。

一次失败的转达或投递——例如其 Session 无法访问时——会在 2 秒后重试,随后按每次翻倍、最长到一分钟的延迟重试;一次失败的启动会使其工单失败,除非该失败如上所述是一次短暂故障。

每一类工作只有在某一项连续失败三次之后,才会在总览(overview)的系统问题中报告一个错误;一次失败的 Input 投递反而显示在它自己的工单上。

向主 Session 的转达保持其顺序,通知同样如此;一个失败者会阻住同类的后续各项。

Source 失败

GitHub Source 会独立读取每一个 Issue、pull request 和 Thread:一次失败的读取会在其 Thread 上保留其错误,在下一次轮询时重新读取,并计入该 Source 的一个错误——默认情况下,它在连续三次轮询失败后报告该错误;任何一次成功的轮询都会重置计数。

当代理失去了对一个私有仓库的访问权时,GitHub 同样回答 404,因此一个 404 会被重试。

一个被通知的 Issue 或 pull request,若其读取持续失败,会在其首次失败读取一天后被丢弃,并在 GitHub 报告它已被删除时立即丢弃;此后它不再使轮询失败,其 Thread 保留该错误,而该 Source 的状态会点名它。

它的读取位置停留在原处,因此一次后续通知或对其 Thread 的一次轮询会读取那些失败读取所遗漏的内容。

Feishu 事件消费者会报告每一次失败,而对已订阅主题的轮询会把每一次失败保留在其 Thread 上。

GitHub 列表或 Feishu 分页中一个格式错误的条目会被跳过,而该 Source 的状态会统计它。

Channel 失败

外发消息作为已存储的工作被存储并发送,带回退重试,从 5 秒起,翻倍至 300。

一张工单发往某一个会话的消息按它们入队的顺序发出,因此它的确认(acknowledgement)会出现在它后续的答复或结果之前:一条消息会等待,直到同一工单发往同一会话的某条较早消息已经发出——除非那条较早的消息已连续失败三次并显示为一个问题。

其他工单的消息,以及不属于任何工单的消息,不会为它等待。

一条带有失败尝试的消息,在它发出或被撤回之前,是一个系统问题。

一条消息只在其 Source 不再监听该会话时,或在其 Source 回答该会话永远无法接收它时,才会被丢弃;而只要其 Source 处于关闭状态,它就等待。

对于 GitHub 上一个已删除的 Issue 或一个被锁定的会话,以及对于 Feishu 上一条已撤回的消息或一条 Feishu 不再能找到的消息,其 Source 会作出这样的回答。

一条回复给某个已不存在的审阅评论的消息,会被改发到该 pull request 的会话(conversation)中。

一条被丢弃的消息会保留其记录,连同何时、为何被丢弃以及它是为哪张工单而发,因此它绝不会再被入队;一条被丢弃的确认会被读作"已丢弃",而非"已发送"。

暂停处理

会停止分发、新的 Eval、Eval 超时、Input 投递和回复;一个已在进行的启动会等待,直到处理恢复。

Source 会继续记录 Triggers,而一个正在运行的 Executor 轮次不会被中断。

一条发给已结束工单的人类消息会等待:暂停恢复后的第一次处理会恢复该工单——前提是其 Mandate 在当时依据 Policy 仍然成立。

一个进行中的 Eval 不会在暂停期间过期;它的截止时间会按暂停的时长向后顺延。

无效配置

一个其类型所拒绝的已存储 Source 配置,会禁用该 Source 实例。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料