上一篇在 accept 时留下了一张工单、一条 Mandate 和一次 pending 的派工。这篇跟着它走完。文档是 docs/003-execution.md 和 docs/005-panel.md,执行器在 src/executor/dsh.ts。

一、两个 Session

主 Session 的预设是 dsh-superagent-router。工具少,负责评估和派工。它不读仓库的 AGENTS.md,也不做技术决定。

每张工单得到一个名为 worker-<id> 的 Executor Session,目录在执行根下,默认是状态目录里的 work/<短名>。Worker 预设是 dsh-superagent-worker。Worker 再拉起的子代理用 Harness 的 standard 预设,只保留这份工作允许的工具,工单结束就收回。

Worker 以操作者的操作系统用户跑命令,不逐条审批。前台 shell 超过 5 秒就转后台,这样下一步还能收到新消息。子代理的 shell 是 60 秒后转后台。Executor 的工具集里拿掉了会向人提问、向人展示界面或修改 profile 的工具,例如 ask_user_question、present、plugin_manager。要问人,就在 Report 里放一个结构化问题,由主 Session 原样转发。

环境变量只描述仓库、账号、目录、工单 id、禁止的命令名,不含凭据。prepare 命令从环境变量读,Executor 在提示里读同样的值。Worker 通过代理账号的 gh 和机器人的 lark-cli 访问外部。按名字拦截的命令挡得住。文档写明这些包装和目录是默认约定:Worker 仍可以用绝对路径执行程序。只有面板能改执行环境;发给主 Session 的文字改不了配置好的 shell 命令。

和 PocketPilot 对照着记:那边有副作用的工具要停下来等批准;这边的产品论题是团队仓库上的少打扰,命令在操作者的用户下直接跑。你要收紧,就收白名单、接受模式、禁止命令和 prepare,而不是指望逐条审批卡片。

主 Session 与 Worker 槽

二、阶段:打开时它在等谁

打开的工单在任一时刻只有一个阶段。表从上到下,第一条命中的算数。person 表示没有人就继续不了。

阶段条件面板
等人 / 投递Input 没送到 Session,按 2 秒起、加倍到一分钟重试Input undelivered
等人 / 回答有一条 AttentionAwaiting answer
等人 / 评审最新 Report 是 readyAwaiting review
等人 / 卡住最新 Report 是 blocked 或 failed,之后没有人的消息Blocked · Failed
工作 / 排队没有派工,或派工还是 pendingQueued
空闲最新 Report 是 waiting,或这次派工已结束Waiting for a Trigger
工作其余Working

人的新消息会把 blocked 或 failed 交回 Executor,通常回到 working。idle 表示这一轮做完了,没有悬着的问题;订阅线程上的新 Trigger 可以把它叫醒。

Report 的取值是:progress、waiting、ready、blocked、failed、dropped。回合结束时如果还有 shell 任务或子代理在跑,工单继续占着槽,不报 waiting,主 Session 也收不到一份等待报告。完成后台任务会在同一槽里把结果送回。

后台工作最多 30 分钟。到点杀掉 shell,报告 waiting,并写明杀掉了哪些、哪些子代理还活着。子代理可以继续,Executor 下一回合再读被杀掉的任务的结果。交给人的时候(ready、提问、blocked、failed)立刻释放槽。交接时子代理停掉。为了给人看而留着的服务器可以继续,不占槽,也不受 30 分钟限制;它完成时不叫醒 Executor。

三、槽和准备目录

工单从启动(含 prepare)或从把 idle Executor 叫醒的那次投递开始占槽,整段活跃回合都占着。配置的并发上限管这些槽。人的消息或状态查询多一个额外槽,槽满时也能叫醒已有工单,并且比新开工优先。CI 之类的其他 Input 只能等普通槽。新活挤不掉人正在跟的那张工单。启动可以并行,一条慢的 prepare 只耽误自己。

第一次派工前,如果目录还不存在,就在执行根用 sh 跑一次 prepare,限时 10 分钟。常见写法是 git worktree,所以机器上要留一份能加 worktree 的本地克隆。没有 prepare 就在空目录里开工。prepare 失败则工单失败,并记下输出;人再说话或重试会再准备一次。PR 的分支路径最后一段等于工单短名时,这张 PR 属于这张工单。

WorkerSlots 在启动或叫醒之前占位。短暂的模型或网络失败(HTTP 408、429、5xx,连接被拒、重置或超时)先重试三次。启动走副作用队列的退避;失败的回合会在 15、30、60 秒后收到「继续」的消息。prepare 失败不走这三次。一旦 failed,CI 和 bot 消息只记录,不投递,也不叫醒 Session。工具仍可用,人可以直接在这个 Session 里接着做。

四、合并与结束

自动合并不由宿主做判断,写在 Delivery requirements 这段 prompt 里。默认 Executor 不以代理账号打开 auto-merge,因为 GitHub 会把合并算在打开 auto-merge 的那个人头上。要合并,prompt 里得指定另一个账号的 CLI。默认只允许:这张工单自己开的 PR、已经 ready、所需批准覆盖当前 head、增删合计少于 200 行。再推一版之前先撤掉 auto-merge。没有评审正文的批准也会作为 Trigger 到达,可以叫醒正在等评审的 Executor。

这些句子约束的是代理,约束不了 GitHub。要强制批准,就在分支保护里要求批准,并让过期的批准失效。拿掉合并 CLI 或删掉 auto-merge 那段,就回到人工合并。

工单结束有三条路:

  • 没有 PR,Report 为 ready:完成并结束。
  • 发现没什么要人做的(复现不了、已被取代):dropped。默认不发消息,除非命中的规则要求发。人之后再说话可以恢复。
  • 跟了 PR:它开出的 PR 都不再处于打开状态时结束。若它一张都没开,则它跟随的 PR 都关闭时结束。结果取最后一次相关关闭。结束后仍保留订阅,人再评论可以在同一张 Mandate 下恢复。

一条关闭不会在这些情况里结束工单:还有未完成的恢复、重试或收尾;Executor 已经确认或回答了自己负责的跟进,但之后还没有一份非 progress 的 Report,也还没有在读完那条消息之后结束回合。聊天渠道发一条最终通知。GitHub 上 PR 自己已经显示结果,所以不再多说一遍。去掉对某张 PR 的订阅,也就不再跟随它。

因此面板上的「Awaiting review」就是:一份 ready,并且还有 PR。

只为了上下文而跟随的线程仍然投递消息,但不把人的跟进所有权交给这张工单,也不能恢复已结束的工单。那些消息可以单独评估。来源在有活工单订阅期间轮询这些线程,并在「最后一条 Trigger」和「订阅工单结束」里较晚的那个时刻之后再轮询两周。之后只走普通入口:GitHub 通知或飞书实时事件。

五、失败时先落库

Effects 里和执行有关的种类:

种类谁处理做什么
assessmentMasterAssessor把候选卡片送进主 Session,送达后开始评估的截止时间
launchDshExecutorprepare、建 Executor Session、派工消息
deliveryDshExecutor按顺序把等待中的 Input 交给正在跑的派工
relaySessions把 Worker 的报告、问题或人的跟进送进主 Session,保持顺序
noticeSessions注入通知,不叫醒主 Session,保持顺序
replyOutbox经来源把渠道消息发出去

某一类连续失败三次,才出现在总览的系统问题里。delivery 例外,失败显示在工单上。relay 和 notice 各保持自己的顺序,前面一条失败会挡住后面的。

出站消息先存储再发送。同一张工单、同一个对话里按队列顺序发,确认一定在后面的答复之前。退避从 5 秒加倍到 300 秒。其他工单的消息不跟它一起等。来源不再听这个对话,或对方永远收不到(删除的 Issue、锁住的对话、飞书已撤回或找不到的消息),才丢弃,并留下时间和原因,不再重排。回复一条已经不存在的评审评论时,改发到 PR 的主对话。

Retry 在投递失败、或卡在 blocked / failed 时可用。它结束当前派工,在同一个 Session、目录、分支、PR 上开一次新的。模型只在创建 Session 时选定,已有 Session 不换模型。人的消息已经送到之后,面板不再提供 Retry,除非这条消息本身没送到。

进程重启:正在跑的派工以「host restarted」结束,新的带着恢复说明再开始,要求先看目录、git 历史和 GitHub,不要重复外部动作,旧工单优先。waiting、ready、blocked、failed 保持原样,除非那份 Report 之后又来了 Input。Session、目录、订阅、待发送消息都留下。一次 step 在模型调用前已经认领、但被取消或错误打断、还没进历史的消息,仍算未读,会重新投递;重启前已经写进历史的,算读过。

插件在 Harness 进程里停止或重启,走同一条关闭路径。还没进 Session 历史的 Input 保留。关闭时每个 Worker 的后台工作最多等 10 秒;进程重启会直接结束这些工作。

暂停处理会停掉派工、新 Eval、评估超时、Input 投递和回复。已经在跑的回合不打断。来源继续记 Trigger。暂停期间评估不超时,截止时间往后推。暂停恢复后,人在已结束工单上的消息会先按当时的策略尝试恢复。

来源读失败时,GitHub 对每个 Issue、PR、线程单独记错误,下一轮再读。私有仓库失去权限时 GitHub 也会 404,所以 404 会重试。连续失败默认三次后,来源报错;任何一次成功就把计数清零。通知过、却一直读失败的 Issue 或 PR,在首次失败一天后丢掉,GitHub 报已删除则立刻丢掉。读位置留在原地,后面补得上漏掉的内容。列表里格式坏掉的一条会被跳过并计数。

六、面板只回答三问

打开面板的顺序写在 docs/005-panel.md:

  1. 现在有没有事需要我?
  2. 系统是否健康,做的是不是对的事?
  3. 这一张工单从 Trigger 到 Eval 到 Ticket 的历史是什么?

页签按这个领域排:Overview、Source、Trigger、Eval、Execution、Settings。仓库和账号还没配对时,Overview 只显示缺什么,并链到对应设置。Overview 汇总过去 24 小时的活动和 Executor 负载,链到详情,自己不再变成第二份详情。

计数必须等于它链到的列表长度。健康状态不占额外空间。一次网络抖动不会升格成问题。需要人的那一行直接给出动作,破坏性操作要确认。页内说明保持一句,其余放在问号提示里。

Settings 按概念分组:来源、评估(策略版本、规则、允许名单、准入表、接受模式、超时、评估模型)、执行(Worker 模型、并发、目录根、prepare、禁止命令)、提示词、部署。每一组有自己的保存条,只在有改动、保存中或保存失败时出现。DeepSeek Harness 的插件管理器打开的是同一页。

搜索能按文本、id、飞书消息 id 或存下来的链接找 Trigger,按 #12 或 GitHub URL 找线程,按标题找工单。一条仍由 Executor 拥有的人的跟进,标成 Delivered to Ticket,旁边没有 Eval。

七、把 #12 走完

Alice 的 accept 已经落库。

  1. 派工为 pending,阶段是 Queued。槽空出来之后 launch 运行。prepare 用 git worktree 做出目录。worker-<id> 开始干活,阶段变成 Working。Issue 里只有一句确认,过程中保持安静。
  2. 她做完,开出 PR,Report 为 ready。阶段变成 Awaiting review,槽释放。给评审看的预览服务可以留着,完成时不叫醒 Worker。
  3. 评审评论经订阅扇出,不再评估,不新增权限。Worker 被叫醒,仍在原来的 Mandate 里改。再推一版之前,若开过 auto-merge,先撤掉。
  4. PR 合并。工单结束,订阅保留。聊天渠道有一条最终通知。GitHub 上不再重复解释合并结果。
  5. Alice 之后再在这条线程里说话。来源还在听、她仍在名单上,同一张 Mandate 可以把工单叫醒。恢复说明会要求 Executor 先看目录和 git 历史。
  6. 若是名单外的人、模式仍是 observe:第三篇的「本来会接」留在 Eval 页,Execution 页不会出现工单。

八、源码从哪一页翻进去

docs/007-architecture.md 是索引,不必从头读。按你卡住的问题跳:

你想看文件
谁组装进程src/app/runtime.ts、src/app/passes.ts
谁写入工单src/core/engine.ts、src/core/ports.ts
强度和再校验src/policy.ts
模型研判src/app/assessment.ts
槽、prepare、Sessionsrc/executor/dsh.ts
先落库的队列src/app/effects.ts、src/app/outbox.ts
SQLitesrc/app/store.ts
加一种来源docs/008-cookbook.md、src/sources/kind.ts

九、小结

  • 一张工单一个 Worker Session、一个目录。主 Session 只路由。
  • 阶段回答「在等谁」。第一条命中的行算数。自由文本改变不了阶段。
  • 人的消息有额外的槽,并且比新开工优先。前台命令 5 秒转后台,后台最多 30 分钟。
  • 合并条件写在 prompt 里,分支保护写在 GitHub 上。包装目录不是安全边界。
  • 发送、投递、评估都是提交之后的副作用。连续失败三次才升格成系统问题。

四篇到这里可以收束。Harness 三篇讲底座怎么换插件,这四篇讲底座上的团队包怎么把「少占注意力」写成 Gate、Mandate、工单阶段和面板。若要看另一种副作用故事,回到 PocketPilot 的策略与审批。若要从概念课重新串,从 学习路线图 进。