Files
deer-flow/backend/packages/harness/deerflow/runtime/README_zh.md
T
rayhpeng 9d0a42c1fb refactor(runtime): restructure runs module with new execution architecture
Major refactoring of deerflow/runtime/:
- runs/callbacks/ - new callback system (builder, events, title, tokens)
- runs/internal/ - execution internals (executor, supervisor, stream_logic, registry)
- runs/internal/execution/ - execution artifacts and events handling
- runs/facade.py - high-level run facade
- runs/observer.py - run observation protocol
- runs/types.py - type definitions
- runs/store/ - simplified store interfaces (create, delete, query, event)

Refactor stream_bridge/:
- Replace old providers with contract.py and exceptions.py
- Remove async_provider.py, base.py, memory.py

Add documentation:
- README.md and README_zh.md for runtime module

Remove deprecated:
- manager.py moved to internal/
- worker.py, schemas.py
- user_context.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 11:28:01 +08:00

585 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# deerflow.runtime 设计说明
本文基于当前代码实现,说明 `backend/packages/harness/deerflow/runtime` 的总体设计、约束边界、`stream_bridge``runs` 的协作方式、与外部基础设施和 `app` 层的交互方式,以及 `actor_context` 如何通过动态注入实现用户隔离。
## 1. 总体定位
`deerflow.runtime` 是 DeerFlow 的运行时内核层。它位于 agent / tool / middleware 之下、app / gateway / infra 之上,主要负责定义“运行时语义”和“基础边界契约”,而不直接拥有 Web 接口、数据库模型或具体基础设施实现。
当前 `runtime` 的公开表面由 [`__init__.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/__init__.py) 统一导出,主要包括四类能力:
1. `runs`
- run 领域类型、执行 façade、生命周期观察者、store 协议
2. `stream_bridge`
- 流式事件桥接契约与公共类型
3. `actor_context`
- 请求/任务级的 actor 上下文与用户隔离桥
4. `serialization`
- 运行时对外事件与 LangChain / LangGraph 数据的序列化能力
从结构上看,可以把当前 `runtime` 理解成:
```text
runtime
├─ runs
│ ├─ facade / types / observer / store
│ ├─ internal/*
│ └─ callbacks/*
├─ stream_bridge
│ ├─ contract
│ └─ exceptions
├─ actor_context
└─ serialization / converters
```
## 2. 总体设计与约束范式
### 2.1 设计目标
`runtime` 当前最核心的设计目标是把“运行时控制面”和“基础设施实现”解耦。
它自己只关心:
1. run 是什么、状态如何变化
2. 执行时会产出哪些生命周期事件和流式事件
3. 哪些能力必须由外部注入,例如 checkpointer、event store、stream bridge、durable store
4. 当前 actor 是谁,以及下游如何据此做隔离
它刻意不关心:
1. 事件是落到内存、Redis 还是别的消息介质
2. run / thread / feedback 是怎么持久化的
3. HTTP / SSE / FastAPI 细节
4. 认证插件如何识别 request user
### 2.2 约束边界
当前 `runtime` 的边界约束比较明确:
1. `runs` 负责运行编排,不直接写 ORM 或 SQL。
2. `stream_bridge` 只定义流语义,不提供 app 级基础设施装配。
3. `actor_context` 只定义运行时上下文,不依赖 auth plugin。
4. durable 数据只能通过协议边界接入:
- `RunCreateStore`
- `RunQueryStore`
- `RunDeleteStore`
- `RunEventStore`
5. 生命周期副作用只能通过 `RunObserver` 接入。
6. 用户隔离不是散落在每个模块里做,而是通过 actor context 自上而下传递。
这套范式可以概括成一句话:
`runtime` 定义语义和边界,`app.infra` 提供实现和装配。
## 3. runs 子系统的设计
### 3.1 作用
`runtime/runs` 是运行编排域。它负责:
1. 定义 run 的领域对象与状态机
2. 组织 create / stream / wait / join / cancel / delete 等操作
3. 维护进程内运行控制面
4. 在执行期间发出流式事件与生命周期事件
5. 通过 callbacks 收集 trace、token、title、message 等运行数据
### 3.2 核心对象
见 [`runs/types.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/types.py)。
关键对象有:
1. `RunSpec`
- 由 app 输入层构建,是执行器输入
2. `RunRecord`
- 运行中的记录对象,由 `RunRegistry` 管理
3. `RunStatus`
- `pending` / `starting` / `running` / `success` / `error` / `interrupted` / `timeout`
4. `RunScope`
- 区分 stateful / stateless 与临时 thread
### 3.3 当前约束
当前 `runs` 明确限制了一些能力范围:
1. `multitask_strategy` 当前主路径只支持 `reject``interrupt`
2. `enqueue``after_seconds`、批量执行等尚未进入当前主路径
3. `RunRegistry` 是进程内状态,不是 durable source of truth
4. 外部查询可以走 durable store,但控制面仍然以内存 registry 为中心
### 3.4 façade 与内部组件
`RunsFacade` 在 [`runs/facade.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/facade.py) 中暴露统一入口:
1. `create_background`
2. `create_and_stream`
3. `create_and_wait`
4. `join_stream`
5. `join_wait`
6. `cancel`
7. `get_run`
8. `list_runs`
9. `delete_run`
它底层组合了:
1. `RunRegistry`
2. `ExecutionPlanner`
3. `RunSupervisor`
4. `RunStreamService`
5. `RunWaitService`
6. `RunCreateStore` / `RunQueryStore` / `RunDeleteStore`
7. `RunObserver`
也就是说,`RunsFacade` 是 public entry point,而真正的执行和状态推进拆散在内部组件中。
## 4. stream_bridge 的设计和实现思路
### 4.1 为什么单独抽象
`StreamBridge` 在 [`stream_bridge/contract.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/stream_bridge/contract.py) 中定义。
把它单独抽象出来的原因是:run 执行期间需要一个“可订阅、可回放、可终止、可恢复”的事件通道,而这件事不能直接绑定到 HTTP SSE、in-memory queue 或 Redis 细节。
所以:
1. harness 负责定义流语义
2. app 层负责选择和实现流后端
### 4.2 契约内容
`StreamBridge` 当前提供这些关键方法:
1. `publish(run_id, event, data)`
2. `publish_end(run_id)`
3. `publish_terminal(run_id, kind, data)`
4. `subscribe(run_id, last_event_id, heartbeat_interval)`
5. `cleanup(run_id, delay=0)`
6. `cancel(run_id)`
7. `mark_awaiting_input(run_id)`
8. `start()`
9. `close()`
公共类型包括:
1. `StreamEvent`
2. `StreamStatus`
3. `ResumeResult`
4. `HEARTBEAT_SENTINEL`
5. `END_SENTINEL`
6. `CANCELLED_SENTINEL`
### 4.3 语义边界
当前契约显式区分了两类终止语义:
1. `end` / `cancel` / `error`
- 是 run 级别的真实业务终止事件
2. `close()`
- 是 bridge 自身关闭
- 不应被当作 run 被取消
### 4.4 当前实现方式
当前实际使用的实现是 app 层的 [`MemoryStreamBridge`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/stream_bridge/adapters/memory.py)。
它的设计是“每个 run 一条内存事件日志”:
1. `_RunStream` 保存事件列表、offset 映射、状态、subscriber 计数和 awaiting-input 标记
2. `publish()` 生成递增事件 ID 并追加到 per-run log
3. `subscribe()` 支持 replay、heartbeat、resume、terminal 退出
4. `cleanup_loop()` 处理:
- 过老 stream
- 长时间无 publish 的 active stream
- orphan terminal stream
- TTL 过期 stream
5. `mark_awaiting_input()` 为 HITL 场景延长超时
Redis 版本当前仍在 [`RedisStreamBridge`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/stream_bridge/adapters/redis.py) 中作为占位。
### 4.5 调用链路
stream bridge 在运行链路中的作用可以概括为:
```text
RunsFacade
-> RunStreamService
-> StreamBridge
-> app route converts events to SSE
```
更具体地说:
1. `_RunExecution._start()` 会发布 `metadata`
2. `_RunExecution._stream()` 会把 agent 的 `astream()` 输出统一转成 bridge 事件
3. `_RunExecution._finish_success()` / `_finish_failed()` / `_finish_aborted()` 会发布 terminal 事件
4. `RunWaitService` 通过 `subscribe()` 等待 `values` / `error` / terminal
5. app 路由层再把这些事件转换为对外 SSE
### 4.6 后续扩展
后续可以沿几个方向扩展:
1. Redis 真正落地,支持跨进程 / 多实例流桥接
2. 更完整的 Last-Event-ID gap recovery
3. 更细粒度的 HITL 状态管理
4. 跨节点运行协调和 dead-letter 策略
## 5. 如何与外部通信,store 如何读写数据
### 5.1 两条主要外部边界
`runtime` 自身不直接发 HTTP 请求,也不直接写 ORM,但通过两条主边界与外界交互:
1. `StreamBridge`
- 对外输出流式运行事件
2. `store` / `observer`
- 对外输出 durable 数据与生命周期副作用
### 5.2 store 边界协议
在 [`runs/store`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/store) 中定义了四个协议:
1. `RunCreateStore`
2. `RunQueryStore`
3. `RunDeleteStore`
4. `RunEventStore`
这些协议不是 harness 内部的数据层,而是 harness 对 app 层的依赖声明。
### 5.3 app 层如何提供 store 实现
当前 app 层提供了这些实现:
1. [`AppRunCreateStore`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/gateway/services/runs/store/create_store.py)
2. [`AppRunQueryStore`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/gateway/services/runs/store/query_store.py)
3. [`AppRunDeleteStore`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/gateway/services/runs/store/delete_store.py)
4. [`AppRunEventStore`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/storage/run_events.py)
5. [`JsonlRunEventStore`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/run_events/jsonl_store.py)
这里的统一模式是:
1. harness 只看协议
2. app 层自己决定 session、commit、访问控制和后端选型
3. durable 数据最终通过 `store.repositories.*` 落数据库,或者通过 JSONL 落盘
### 5.4 runs 生命周期数据是怎么写出去的
单次执行器 [`_RunExecution`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/internal/execution/executor.py) 不直接写数据库。
它把数据写出去的方式有三条:
1. bridge 事件
- 流式发布给订阅者
2. callback -> `RunEventStore`
- 执行 trace / message / tool / custom event 以批次方式落地
3. lifecycle event -> `RunObserver`
- 把 run 开始、完成、失败、取消、thread status 更新发给 app 层观察者
### 5.5 `RunEventStore` 的后端
`RunEventStore` 当前由 app 层工厂 [`app/infra/run_events/factory.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/run_events/factory.py) 统一构造:
1. `run_events.backend == "db"`
-`AppRunEventStore`
2. `run_events.backend == "jsonl"`
-`JsonlRunEventStore`
因此,`runtime` 不关心事件最终是数据库还是文件,它只要求支持 `put_batch()` 和相关读取方法。
## 6. runs 生命周期数据、callback 和查询回写
### 6.1 单次 run 的主流程
`_RunExecution.run()` 的主流程是:
1. `_start()`
2. `_prepare()`
3. `_stream()`
4. `_finish_after_stream()`
5. `finally`
- `_emit_final_thread_status()`
- `callbacks.flush()`
- `bridge.cleanup(run_id)`
### 6.2 start 阶段记录什么
`_start()` 会:
1. 把 run 状态置为 `running`
2. 发出 `RUN_STARTED`
3. 抽取首条 human message,并发出 `HUMAN_MESSAGE`
4. 捕获 pre-run checkpoint id
5. 发布 `metadata` 流事件
### 6.3 callbacks 收集什么
当前 callbacks 位于 [`runs/callbacks`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/callbacks)。
主要有三类:
1. `RunEventCallback`
- 记录 run_start / run_end / llm_request / llm_response / tool_start / tool_end / tool_result / custom_event 等
- 按批 flush 到 `RunEventStore`
2. `RunTokenCallback`
- 聚合 token 使用、LLM 调用次数、lead/subagent/middleware token、message_count、首条 human message、最后一条 AI message
3. `RunTitleCallback`
- 从 title middleware 响应或 custom event 中提取 thread title
### 6.4 completion_data 如何形成
`RunTokenCallback.completion_data()` 会得到 `RunCompletionData`,包括:
1. `total_input_tokens`
2. `total_output_tokens`
3. `total_tokens`
4. `llm_call_count`
5. `lead_agent_tokens`
6. `subagent_tokens`
7. `middleware_tokens`
8. `message_count`
9. `last_ai_message`
10. `first_human_message`
执行器在完成 / 失败 / 取消时都会把这份数据带入 lifecycle payload。
### 6.5 app 层如何回写
执行器通过 [`RunEventEmitter`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/runs/internal/execution/events.py) 发出 `RunLifecycleEvent`
app 层 [`StorageRunObserver`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/storage/runs.py) 再根据事件类型回写 durable 状态:
1. `RUN_STARTED`
- 更新 run 状态为 `running`
2. `RUN_COMPLETED`
- 写 completion_data
- 同步 title 到 thread metadata
3. `RUN_FAILED`
- 写 error 和 completion_data
4. `RUN_CANCELLED`
-`interrupted` 状态与 completion_data
5. `THREAD_STATUS_UPDATED`
- 同步 thread status
### 6.6 查询路径
`RunsFacade.get_run()` / `list_runs()` 有两条路径:
1. 注入了 `RunQueryStore` 时,优先查 durable store
2. 否则回退到 `RunRegistry`
这意味着:
1. 内存 registry 负责控制面
2. durable store 负责对外查询面
## 7. actor_context 如何动态注入并实现用户隔离
### 7.1 设计目标
`actor_context` 在 [`actor_context.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/packages/harness/deerflow/runtime/actor_context.py) 中定义。
它的目标是让 runtime 和下游基础模块可以依赖“当前 actor 是谁”这个运行时事实,但不直接依赖 auth plugin、FastAPI request 或具体用户模型。
### 7.2 当前实现方式
当前实现是一个基于 `ContextVar` 的请求/任务级上下文:
1. `ActorContext`
- 当前只有 `user_id`
2. `_current_actor`
- `ContextVar[ActorContext | None]`
3. `bind_actor_context(actor)`
- 绑定当前 actor
4. `reset_actor_context(token)`
- 恢复之前上下文
5. `get_actor_context()`
- 获取当前 actor
6. `get_effective_user_id()`
- 取当前 user_id,如果没有则返回 `DEFAULT_USER_ID`
7. `resolve_user_id(value=AUTO | explicit | None)`
- 在 repository / storage 边界统一解析 user_id
### 7.3 app 如何动态注入
动态注入链路当前在 auth plugin 侧完成。
HTTP 请求路径:
1. [`app.plugins.auth.security.middleware`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/plugins/auth/security/middleware.py)
- 从认证后的 request user 构造 `ActorContext(user_id=...)`
- 在请求处理期间绑定 / 重置 runtime actor context
2. [`app.plugins.auth.security.actor_context`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/plugins/auth/security/actor_context.py)
- 提供 `bind_request_actor_context(request)``bind_user_actor_context(user_id)`
- 在路由或非 HTTP 入口中显式绑定 runtime actor
非 HTTP / 外部通道路径:
1. [`app/channels/manager.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/channels/manager.py)
2. [`app/channels/feishu.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/channels/feishu.py)
这些入口在把外部消息转入 runtime 前,也会用 `bind_user_actor_context(user_id)` 包住执行过程。这样做的意义是:
1. runtime 不区分请求来自 HTTP、飞书还是别的 channel
2. 只要入口能解析出 user_id,就能把同一套隔离语义注入进去
3. 同一份 runtime/store/path/memory 代码不需要知道上层协议来源
因此 runtime 自己不知道 request 是什么,也不知道 auth plugin 的 user model 长什么样;它只知道当前 `ContextVar` 中是否绑定了 `ActorContext`
### 7.4 注入后的传播语义
这里的“动态注入”本质上不是把 `user_id` 一层层作为函数参数硬传下去,而是在 app 边界把 actor 绑定进 `ContextVar`,让当前请求/任务上下文中的 runtime 代码按需读取。
当前语义可以理解为:
1. 入口边界先 `bind_actor_context(...)`
2. 在该上下文内创建的异步调用链共享同一个 actor 视图
3. 请求结束或任务退出后用 `reset_actor_context(token)` 恢复
这有两个直接效果:
1. 运行链路中的大部分接口不需要把 `user_id` 塞进每一层函数签名
2. 真正需要 durable 隔离或路径隔离的边界,仍然可以通过 `resolve_user_id()` / `get_effective_user_id()` 显式取值
### 7.5 用户隔离如何生效
用户隔离当前是通过“动态注入 + 下游统一读取”实现的。
几条关键链路如下:
1. path / uploads / sandbox / memory
- 通过 `get_effective_user_id()` 把 user_id 带入路径解析和目录隔离
2. app storage adapter
- 通过 `resolve_user_id(AUTO)``RunStoreAdapter``ThreadMetaStorage` 等处做查询和写入隔离
3. run event store
- `AppRunEventStore` 会读取 `get_actor_context()`,判断当前 actor 是否可见指定 thread
也就是说,用户隔离并不是靠单一中间件“一次性做完”,而是:
1. app 边界把 actor 动态绑定进 runtime context
2. runtime 及其下游模块在需要时读取该 context
3. 每个边界按自己的职责决定如何使用 user_id
### 7.6 这种方式的优点
当前设计有几个明显优点:
1. runtime 不依赖具体 auth 实现
2. HTTP 和非 HTTP 入口都能复用同一套隔离机制
3. user_id 可以自然传递到路径、memory、store、事件可见性等不同边界
4. 需要强约束时可通过 `AUTO` + `resolve_user_id()` 强制要求 actor context 存在
### 7.7 后续如何扩展
`ActorContext` 文件里已经预留了扩展点注释,后续完全可以在不破坏当前模式的前提下继续扩展:
1. `tenant_id`
- 用于多租户隔离
2. `subject_id`
- 用于更稳定的主体标识
3. `scopes`
- 用于更细粒度授权
4. `auth_source`
- 用于记录来源渠道
扩展方式建议保持现有模式不变:
1. 继续由 app/auth 边界负责绑定 richer `ActorContext`
2. runtime 只依赖抽象上下文字段,不依赖 request/user 对象
3. 下游基础模块按需读取必要字段
4. 在 store / path / sandbox / stream / memory 等边界逐步引入 tenant-aware 或 scope-aware 行为
更具体地说,后续如果要做多租户和更强隔离,推荐按边界渐进式扩展:
1. store 边界
-`RunStoreAdapter``ThreadMetaStorage`、feedback/event store 中引入 `tenant_id` 过滤
2. 路径与沙箱边界
- 把目录分片从 `user_id` 扩展成 `tenant_id/user_id`
3. 事件可见性边界
- 在 run event 查询和 thread 查询时叠加 `scopes``subject_id`
4. 外部通道边界
- 为不同来源填充 `auth_source`,区分 API / channel / internal job
这样 runtime 仍然只依赖“当前 actor 上下文”这个抽象,不会重新耦合回 FastAPI request 或某个认证实现。
## 8. 与 app 层的交互
### 8.1 app 如何装配 runtime
当前 app 层会在 [`app/gateway/services/runs/facade_factory.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/gateway/services/runs/facade_factory.py) 装配 `RunsFacade`
它会组装:
1. `RunRegistry`
2. `ExecutionPlanner`
3. `RunSupervisor`
4. `RunStreamService`
5. `RunWaitService`
6. `RunsRuntime`
- `bridge`
- `checkpointer`
- `store`
- `event_store`
- `agent_factory_resolver`
7. `StorageRunObserver`
8. `AppRunCreateStore`
9. `AppRunQueryStore`
10. `AppRunDeleteStore`
### 8.2 app.state 如何提供基础设施
在 [`app/gateway/registrar.py`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/gateway/registrar.py)
1. `init_persistence()` 创建:
- `persistence`
- `checkpointer`
- `run_store`
- `thread_meta_storage`
- `run_event_store`
2. `init_runtime()` 创建:
- `stream_bridge`
然后这些对象挂在 `app.state`,供依赖注入和 façade 构造使用。
### 8.3 `stream_bridge` 的 app 边界
当前具体 stream bridge 的装配已经完全属于 app 层:
1. harness 只导出 `StreamBridge` 契约
2. 具体实现由 [`app.infra.stream_bridge.build_stream_bridge`](/Users/rayhpeng/workspace/open-source/deer-flow/backend/app/infra/stream_bridge/factory.py) 构造
这条边界非常清晰:
1. harness 定义运行语义和接口
2. app 选择和构造基础设施实现
## 9. 设计总结
可以把当前 `deerflow.runtime` 总结为一句话:
它是一个“以 run orchestration 为核心、以 stream bridge 为流式边界、以 actor context 为动态隔离桥、以 store / observer 为 durable 与副作用边界”的运行时内核层。
更具体地说:
1. `runs` 负责编排和生命周期推进
2. `stream_bridge` 负责流语义
3. `actor_context` 负责运行时用户上下文和隔离桥
4. `serialization` / `converters` 负责对外事件与消息格式转换
5. app 层通过 infra 负责真正的持久化、流式基础设施和 auth 注入
这套结构的优势是:
1. 运行语义与基础设施实现解耦
2. 请求身份与 runtime 逻辑解耦
3. HTTP、CLI、channel worker 等多种入口都可以复用同一套 runtime 边界
4. 后续可平滑扩展到多租户、跨进程 stream bridge、更多 durable backend
当前的主要限制也同样清楚:
1. `RunRegistry` 仍然是进程内控制面
2. Redis bridge 仍未落地
3. 一些多任务策略和批量能力仍未进入主路径
4. `actor_context` 目前只携带 `user_id`,还没有 tenant / scopes / auth_source 等 richer context
因此,当前最准确的理解方式不是“最终态平台”,而是“已经具备清晰语义和扩展边界的 runtime kernel”。