Skip to content

把"等人"做成一等公民状态:
Agent 的人机协同怎么设计才不卡死 ​

一句话结论:Agent 出错很少是因为"它干了什么",更多是因为"它卡在等人类时整系统僵住了"。把"等人"当成异常去 try-catch,是最常见也最致命的设计错误。正确做法是把"等人"做成显式的、可恢复的状态——这一点上,DeepSeek 的 dsh-superagent 和 Lyncore 走到了同一个答案。

「等人」不是异常:一条工单的状态表与源码依据

"等人"为什么会被误当成错误 ​

设想一个 Agent 干到一半,需要你拍板:这个 PR 能不能合并?这个外部 API 密钥要不要填?它把问题发出来,然后……呢?

很多实现的本能反应是:把"等待"塞进一个 status=blocked 的终态,或者干脆让主循环阻塞在那一步。后果是:

  • 阻塞态意味着"这件事结束了",可它明明没结束——人一回复,系统不知道该往哪接;
  • 一个 Ticket 卡住,调度器可能把它当成失败去重试,结果重复发起、重复问人;
  • 更严重的是,所有挂在它身上的并发上下文一起冻结,因为"等"被当成了"死"。

根因是:工程师把"需要人"和"出错了"用了同一套语义。

dsh-superagent:把"等人"列进状态表 ​

dsh-superagent 的做法很直白——在 Ticket 的阶段表里,把"等人"的几个情形显式列为一等公民(见 docs/003-execution.md):

阶段含义
person/delivery投递输入给执行会话失败,重试中
person/answer有一个 Attention(在等某人回答)
person/review最新报告是 ready,等复核
person/blocked / person/failed卡住或失败,且此后没有人的消息到达

文档写得很清楚:person 阶段意味着 Ticket 在没有人工输入前无法继续。而且它给出了一条关键规则——人的一条新消息,会把 blocked / failed 的 Ticket 释放回执行器,让它进入下一个匹配的阶段(通常是 working)。

换句话说:等人类不是一个错误分支,而是一个会自己解开的挂起态。Attention 是结构化字段,不是从自由文本里"猜"出来的——文档特意强调,不能用报告的自由文本去推断"是否在等人",要用结构化事实。

Lyncore:waiting_approval 挂起态 ​

Lyncore 在任务生命周期里给了同一个答案,名字叫 waiting_approval(T3 挂起态):

  • 它是一个非终态的挂起——代码里常量就是 ApprovalSuspendPhaseWaiting = "waiting_approval"(approval_guard.go),和终态(succeeded / failed / cancelled)平级但不同类;
  • 审批通过后,任务经 resumed 阶段原位续走——不是重开一个新任务,而是在原任务上接着跑;
  • 它不设超时:等的是人,不是定时器;
  • 重启时 waiting_approval 一并纳入扫描面,但一律豁免不回收——不会因为"挂太久"就被判定失败。

这条线和 dsh 的 person/* 阶段是同构的:都拒绝把人工介入算作失败,都把"等"做成可观测、可恢复、可原位接续的状态。差别只在触发语义——dsh 的"等人"更多来自执行中的 Attention 与 review,Lyncore 的 waiting_approval 更多来自需要人拍板的高危操作(T3 挂起域)。

对照阅读:《主会话 + 工作会话》 讲分层之后人怎么插进执行会话;《任务生命周期》 与 《三种终止》 讲 Lyncore 的终态与挂起态边界。

设计这种状态时,三件事不能省 ​

  1. 结构化,不要靠猜。 用显式字段(dsh 的 Attention、Lyncore 的 waiting_approval 状态位)标记"在等人",而不是解析 Agent 输出的自然语言。
  2. 可原位续走,不要重开。 人回复后,应在原任务/原 Ticket 上接着跑,保留它的上下文、目录、分支。dsh 用 resumed,Lyncore 用 resumed 阶段,都不是新建。
  3. 可独立恢复,不要连坐。 一个 Ticket 在等,不该冻结调度器或别的并发任务。Lyncore 把 waiting_approval 纳入可观测但豁免回收,正是这个意思。

一个反例提醒 ​

dsh 的文档里专门列了一条反向设计:不让 Executor 自己宣布"人被它叫停了"——只有 Eval 能决定"停"的强度。因为"停"是个有后果的判定,不能让执行器自说自话。这条也适用于任何 Agent 系统:把"是否终止/挂起"的裁决权,留在调度层,而不是下放给干活的那个会话。

本系列 ​

本文以 DeepSeek 开源的 dsh-superagent(MIT)为例子。Lyncore 的 waiting_approval 挂起态与任务生命周期见官网 lyncore.top 与技术文档。