我来在代码里查 HITL 的实际用法,而不是凭定义回答。
有专门的docs/dd/supervisor/HITL.md。我读这个,外加 models.py 里的 HITL 字段和 3_agent_design 里的相关段。
HITL 在这个项目里有一份非常完整的设计文档(docs/dd/supervisor/HITL.md)。我给你讲透。
一句话:HITL 是什么
HITL = Human-in-the-Loop(人在回路中)。在这个项目里,它解决一个具体问题:
写操作(订会议室、提工单、提交食堂反馈)在执行前,必须让用户点一下"确认",不能直接动手。
用户的指令"提交反馈"一旦发出,如果 Agent 直接执行,就等于没经过你同意就帮你发了——HITL 就是那个"暂停等你确认"的闸口。
在这个项目里的应用场景
核心例子(来自文档 §4):
用户:"二食堂红烧肉太难吃,提个反馈" │ ▼ supervisor 识别意图 → 调食堂 Agent 的 submit_feedback(写操作) │ ▼ 食堂 Agent 判断"这是写操作,得确认" 子 Agent 自己 interrupt() 暂停,生成 confirmation_id=c1 返回 input-required + 确认卡片 │ ▼ supervisor 也 interrupt() 暂停,把确认卡推给前端 前端渲染:"确认提交这条反馈吗?[确认] [取消]" │ 用户点"确认提交" │ ▼ 复用同一 thread,带 approved=true + c1 调回去 食堂 Agent 从 Redis 恢复 → 真正执行 INSERT本质:一个写操作被拆成两个阶段
- prepare(阶段1):解析、校验、生成确认卡,但不执行
- execute(阶段3):用户确认后,带着
approved=true才真正执行
三个关键设计点(讲给没看过代码的人)
1. 两层 interrupt(最容易混)
整条链路上有两个独立的图,各自会暂停:
- 子 Agent 的 interrupt:食堂 Agent 自己判断"要确认"→ 暂停自己的图
- supervisor 的 interrupt:dispatcher 发现"有子 Agent 要确认"→ 暂停 supervisor 的图,把卡推给用户
两层都靠Redis 共享存储(不在 Pod 内存),保证多实例下任意 Pod 都能恢复。
2. confirmation_id(暂停点身份证)
由子 Agent 生成,prepare 时返回、execute 时透传回去。TTL 30 分钟,过期就CONFIRMATION_EXPIRED(不可重试)。
3. idempotency_key(防重复执行)
execute 阶段复用同一幂等键 → 即使网络重试,下游也不会重复下单。
和你前面那份并行设计的关联
你之前那份 dispatcher 设计里把HITL 明确列在"不做什么"——这是对的。原因在这份文档 §11.3 第 7 条不变量:
并行 HITL 必须三段式:
asyncio.gather内不调interrupt()
也就是说,你那套 group 并行(asyncio.gather跑 step)和 HITL 的 interrupt 机制是冲突的——gather 里不能暂停等人点确认。所以 HITL 要走独立路径,不混进你那套并行 dispatch。
重要现实:这份设计还没落地
文档 §9 明确写了:§11 整套设计只在文档里,代码未落地。
| 项 | 状态 |
|---|---|
PreparedCall模型 | src/models.py未实现 |
| dispatcher HITL 逻辑 | src/nodes/未实现 |
batch_write_confirmation常量 | 只有文档字符串 |
validate_a2a_contract.py校验脚本 | 不存在 |
所以现在代码里models.py搜不到任何HITL/confirmation_id/PreparedCall字段(我刚搜过,0 匹配)。
总结给你的一句话
HITL 在本项目 = “写操作执行前的用户确认闸口”,用两阶段 A2A(prepare 不执行 / execute 才执行)+ 双层 Redis interrupt 实现,目前是完整文档、零代码落地,且与你的并行 dispatch 设计刻意解耦。
需要我把这份 HITL 设计里**"代码未落地"的部分**,对照你之前那套并行 retry 设计,理出一份"实现时谁先谁后、哪些会互相打架"的依赖图吗?