---
type: study
status: verified
created: 2026-08-11
updated: 2026-08-11
sensitivity: standard
tags:
  - langchain
  - deep-agents
  - python
  - streaming
  - frontend
  - human-in-the-loop
sources:
  - "https://docs.langchain.com/oss/python/deepagents/event-streaming"
  - "https://docs.langchain.com/oss/python/deepagents/streaming"
  - "https://docs.langchain.com/oss/python/deepagents/human-in-the-loop"
  - "https://docs.langchain.com/oss/python/deepagents/frontend/overview"
  - "https://docs.langchain.com/oss/python/deepagents/frontend/subagent-streaming"
  - "https://docs.langchain.com/oss/python/deepagents/frontend/todo-list"
  - "https://docs.langchain.com/oss/python/deepagents/frontend/sandbox"
last_verified: 2026-08-11
---

# Deep Agents Python：流式前端、HITL 与状态协议

## 流式输出的两代接口

Deep Agents 基于 LangGraph 流式机制，并增加对子代理的第一等投影。

- 新应用优先 event streaming（Deep Agents 0.6+）：按 messages、tool_calls、values、subagents、output 分成独立迭代器。
- 旧式 streaming 以 stream_mode 分支处理 updates、messages、custom 等 chunk；仍可用，但 UI 代码更容易混杂。
- LangGraph 1.1+ 的 v2 格式统一为 type、ns、data，避免单模式/多模式/子图之间出现不同的 tuple 解包。

产品 UI 应优先 stream.subagents，而不是 stream.subgraphs：前者表达“用户可理解的委派任务”，后者暴露内部图执行结构。

## Event streaming 模型

根 stream 表示协调者。每个 task 委派生成一个轻量 subagent handle；只有访问该 handle 的 messages、tool_calls 或 values 时才真正打开对应投影，适合按需加载 UI。

| 投影/字段 | 含义 |
| --- | --- |
| stream.messages | 主协调者消息和最终综合 |
| stream.tool_calls | 主协调者工具调用 |
| stream.values | 主图状态，如 todos、自定义状态 |
| stream.subagents | 发现子代理及生命周期 |
| subagent.name | 配置中的 subagent_type 名称 |
| subagent.path | 该子代理命名空间 |
| subagent.status | started、completed、failed、interrupted 等 |
| subagent.messages | 子代理消息 |
| subagent.tool_calls | 子代理内部工具调用 |
| subagent.subagents | 更深层委派 |
| subagent.output | 最终子代理状态/完成信号 |

协调者和子代理事件会交错。异步服务端用 astream_events + asyncio.gather 并发消费；同步代码可使用 interleave。若必须保持跨所有层级的精确到达顺序，消费原始协议事件并根据 namespace 路由。

旧式子图 streaming 中：

- 空 namespace 表示主 Agent。
- tools:任务ID 等 namespace 表示 task 启动的子代理。
- 更深 path 表示子代理内部节点。

LLM token、工具调用块和 custom updates 都可按 namespace/lc_agent_name 归属。自动摘要步骤也可能产生 token；面向用户展示时应过滤摘要中间件节点。

## 前端总体架构

前端 SDK 的 useStream 把 Deep Agents 展示为“协调者 + 可发现工作者 + 共享状态 + 中断”，而不是单一聊天气泡。

| 前端投影 | UI 用途 |
| --- | --- |
| messages | 主对话与最终综合 |
| subagents | 专家卡片、状态、委派树 |
| values | 待办、计划、报告章节、沙箱元数据 |
| tool-call state | 文件、搜索、浏览器、业务动作卡片 |
| interrupts | 审批、补充输入、恢复 |

React、Vue、Svelte、Angular 使用同一 v1 前端 SDK 思路；应传 Agent 类型参数以获得状态推断。

## 子代理 UI

selector-based 设计让根消息保持干净：

- stream.messages 只渲染协调者。
- stream.subagents 只提供发现快照。
- useMessages(stream, subagent) 和 useToolCalls(stream, subagent) 在卡片挂载时订阅该命名空间。
- 可按启动它的 tool_call_id 把子代理卡片插回对应协调者消息下。

SubagentDiscoverySnapshot 只有身份、namespace、status 和任务元数据，不内嵌完整消息。这允许完成的卡片折叠、按需展开，而不让 5 个以上并行专家把页面和内存同时撑大。

建议：

1. 主区域展示协调者高层计划和综合。
2. 子代理卡片默认显示名称、状态、当前工具和简短输出。
3. 进行中的保持展开，完成的自动折叠；侧栏可显示全局鸟瞰。
4. 单个子代理失败只在该卡片显示，不应使其他卡片或根 UI 崩溃。
5. selector 只在可见组件挂载，避免无意义订阅。
6. 仅对异常深的自定义工作流提高 recursion_limit；Deep Agents 默认已经很高。

## Todo UI

任务计划从 0.7 起默认关闭。只有 Agent 加入 TodoListMiddleware，才会有 write_todos 和 stream.values.todos。

状态流程：

pending → in_progress → completed

UI 直接由 todos state 派生完成数、总数和百分比，不轮询、不维护第二份业务状态。初始 todos 为空时不显示占位面板；长列表降低已完成项视觉权重；通常只突出第一个 in_progress 项。

Todo 是 Agent 自报的执行计划，不等于后端任务队列的权威状态。它适合解释进展，不应被用作结算、权限或幂等依据。

## 沙箱 IDE UI

编码 Agent 的典型三栏：

1. 文件树。
2. 代码/差异查看器。
3. Agent 聊天与工具进度。

后端有三部分：

- Deep Agent 以 sandbox backend 获得 read/write/edit/delete/execute。
- FastAPI 自定义路由供前端直接浏览沙箱文件。
- Agent graph 与 API 路由共用同一个“按 thread 解析 sandbox”函数，sandbox_id 存在线程 metadata，不能依赖进程内 cache 作为唯一事实源。

### Thread 与页面恢复

- 默认 thread-scoped sandbox。
- 页面首次加载创建 thread，把 thread_id 存 sessionStorage。
- 刷新页面继续同一 thread 和 sandbox。
- “新会话”清除旧 thread_id，下一次创建新环境。
- 多租户改用经过认证的用户/assistant scope，并在服务端验证 thread 所有权。

### 实时文件同步

每次运行前保存原始文件快照。流中看到以下 ToolMessage 后更新：

- write_file、edit_file、delete：刷新目标文件/目录。
- execute：命令可能修改任意文件，刷新整个文件树。

当前内容与运行前快照比较得到 changed files；用户打开改动文件时默认展示 diff，并显示增加/删除行统计。只读工具在聊天里显示紧凑摘要，避免把 read_file 全文复制一遍。

过滤 node_modules 等大目录；自定义 http.app 路由会优先于默认 LangGraph 路由，命名时避免意外遮蔽 /threads、/runs。

## Human-in-the-loop

interrupt_on 为工具名配置审批：

- True：默认允许 approve、edit、reject、respond。
- False：不拦截。
- InterruptOnConfig：限定 allowed_decisions，可加 when 条件谓词。

| 决策 | 语义 | 注意 |
| --- | --- | --- |
| approve | 原参数执行 | 适合已确认动作 |
| edit | 修改参数再执行 | 大幅修改可能使模型重新规划或重复动作 |
| reject | 不执行，返回拒绝反馈 | 副作用工具的正确拒绝方式 |
| respond | 人直接作为工具结果 | 仅适合 ask_user 类工具，不应用于拒绝副作用 |

reject 消息应明确“工具未执行”和下一步：放弃、补充信息或选择更安全方案。respond 会被模型当作工具成功结果，错误使用会造成状态误判。

### 条件审批

when 谓词接收 ToolCallRequest，可根据参数只拦截高金额、外部地址或敏感路径。未命中条件的调用不会进入审批 batch。条件中断需要 langchain 1.3.3+。

文件权限 mode=interrupt 会对匹配的 write_file、edit_file、delete 产生同类中断，并与 interrupt_on 合并。

### 恢复协议

HITL 的硬性条件：

1. Agent 必须有 checkpointer。
2. 首次调用与恢复必须用相同 thread_id。
3. 读取 result 中的 interrupts/action_requests。
4. 多个被拦截调用会批量返回；decisions 必须与 action_requests 同序。
5. 用 Command(resume=...) 恢复，而不是重新发送原消息。

如果运行在工具返回前取消或中断，PatchToolCallsMiddleware 会修复消息历史中的悬空工具调用，减少恢复后的协议错误。

### 子代理中断

声明式子代理可有自己的 interrupt_on；也可以在子代理工具内部直接调用 interrupt。父/子恢复都依赖同一持久状态语义。

必须特别注意两条旁路：

- PTC 从解释器桥接工具，不走常规工具调用路径，不会逐次触发父 Agent interrupt_on。
- 动态子代理从 eval 内 task() 调度，也不会逐次走父 task 审批。

需要覆盖这两种能力时，对 eval 本身做审批，并缩小解释器 allowlist。

## 状态一致性原则

前端看到的是运行投影，不应自创第二个事实源：

- 对话连续性：thread_id。
- 沙箱归属：服务端 thread metadata。
- Agent 共享状态：stream.values / checkpoint。
- 子代理身份：namespace + lc_agent_name + tool_call_id。
- 异步后台任务：专用 async_tasks channel，不靠历史消息。
- 审批恢复：interrupt action 顺序 + 同一 thread。

UI 可缓存用于体验，但任何刷新、重连和授权判断都要回到服务端 thread/run 状态。

## 上线检查

- 断网重连后能继续同一 run/thread，不丢进度。
- 同一协调者消息下能正确关联多个并行 subagent 卡片。
- 子代理失败、取消、中断均有独立状态。
- 自动摘要 token 不作为用户最终回复展示。
- Todo 未启用时 UI 不假定 todos 存在。
- 审批 batch 的顺序、拒绝反馈、同 thread 恢复均有测试。
- execute 后全量文件刷新；delete 后文件树同步。
- thread_id 不能由用户越权访问；所有自定义文件 API 做服务端授权。
- 差异与沙箱产物按不可信内容渲染，防止 HTML/script 注入。

## 延伸阅读

- [[01-概览与定制]]
- [[02-工具-后端-权限-沙箱与协议]]
- [[03-上下文-记忆-检索与子代理]]
- [[05-生产-容错与应用模式]]
