Files
beaver_project/app-instance/backend/flow.md

611 lines
16 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.

# Beaver Backend Flow
这份文档只记录两件事:
1. 我们**为什么这么实现**
2. 当前代码里**真实已经实现了什么**
它不是蓝图,也不是未来设计草稿。以后只要主链、装配逻辑、运行时边界发生变化,就必须同步更新它。
---
## 1. 参考项目各自借什么
当前 Beaver 的实现思路,主要借了三个参考项目,但借的点是分开的。
### 1.1 `OpenHarness`
借的是**模块边界和 Harness 形态**
1. `Harness / Runtime` 应该和 Web、Gateway、产品接入分开
2. `skills / memory / tools / session / orchestration` 都属于平台层
3. 运行时最好是可装配的,而不是所有逻辑都塞进一个大 agent 类
所以 Beaver 现在一直在做的事情,是把:
- `EngineLoader`
- `AgentLoop`
- `ContextBuilder`
- `Session`
- `Tools`
- `Skills`
收成一个清晰的运行内核。
### 1.2 `hermes-agent`
借的是**memory、skills、session 的运行时风格**
1. memory 用 curated CRUD + frozen snapshot
2. `session_search` 查历史细节,不把所有历史都塞进 memory
3. skills 用:
- 显式 skill loading path
- 激活后的 skill 正文显式注入
所以 Beaver 现在这些点都明显受 Hermes 影响:
1. `MemoryService` + frozen snapshot
2. `session_search`
3. `skill_view`
4. activated skill messages
### 1.3 `swarms`
借的是**后面多智能体 orchestration 的方向**
1. team orchestration
2. swarm strategy
3. multi-agent execution backend
但要注意:它现在**还不是当前主链的核心**。
当前我们主要先把单 agent runtime 打稳,多智能体还没正式接回主链。
---
## 2. 当前我们到底做到哪了
当前已经不是“搭骨架”阶段了,而是:
**最小单 agent runtime 已经跑通。**
现在已经完成的核心段落是:
1. `4.1 session`
2. `4.2 provider`
3. `4.3 context`
4. `4.4 tools`
5. `4.5 最小主链`
6. `5.1 memory 最小接入`
7. `5.2 skills 最小接入`
8. `6.1 session-first / event-source 第一阶段`
更准确地说,当前 Beaver 已经有:
1. 一个可运行的 `AgentService -> AgentLoop` 主链
2. 一个外部化的 Session 子系统
3. 一个可工作的 tool loop
4. Hermes 风格的 memory / skills 接入
5. LLM-driven 的 `SkillAssembler`
但还没有:
1. 更完整的 shutdown hooks
2. Web / Gateway 的 bus / channels / realtime 全量接入
3. delegation / swarm / team runtime
4. 权限系统
5. MCP 全量工具接回 runtime
---
## 3. 当前真实主链
当前主入口已经不是 CLI 逻辑,而是:
```python
service = AgentService()
await service.process_direct("你好")
```
同时,第 6 阶段的最小运行循环已经有了:
```python
service = AgentService()
await service.start()
result = await service.submit_direct("你好")
await service.stop()
service.close()
```
宿主层现在也已经开始接到这条 lifecycle 上:
```python
app = create_app() # FastAPI lifespan 内部托管 AgentService.start()/shutdown()
await run_gateway() # Gateway 常驻进程托管 AgentService.start()/shutdown()
```
这套 lifecycle 当前明确是:
1. `start()` 进入一个 `AgentLoop` 实例的运行模式
2. 运行模式下,外部任务只能走 `submit_direct()`
3. 运行模式下,不允许再直接调用 `process_direct()`
4. `stop()`**instance-scoped**
- 只针对当前这个 `AgentLoop` 实例
- 不是 session-scoped
- 也不是 platform-scoped
5. `stop()` 调用后会拒绝新任务,已入队任务正常收尾
6. `stop()` / `shutdown()` 支持 graceful timeout必要时可 force cancel
7. `close()` 只能在该实例已停止后调用
### 3.1 Web / Gateway 当前怎么接
这一层现在已经不是纯占位了,而是最小宿主层:
1. `beaver/interfaces/web/app.py`
- FastAPI lifespan 启动时:
- 创建或接收 `AgentService`
- 如果 app 自己创建 service`await service.start()`
- Web 接口现在有最小正式 schema
- `WebChatRequest`
- `WebChatResponse`
- `WebStatusResponse`
- `/api/chat` 请求:
- 用结构化 request schema 校验输入
- `await service.submit_direct(...)`
- 把常见 runtime / config 错误收成 HTTP 错误
- 外部注入但尚未进入 running mode 的 service会返回 `503`
- `/api/ping`
- 返回 `status/running/mode`
- 不会为了 health check 额外 boot runtime
- app 关闭时:
- 如果 app 自己创建 service`await service.shutdown(timeout_seconds=5.0, force=True)`
- app 自己接管 lifecycle 时:
-`start()` 失败,会立即 `close()` 做 startup cleanup
2. `beaver/interfaces/gateway/main.py`
- `run_gateway()` 启动时:
- 如果 gateway 自己创建 service`await service.start()`
- 持有最小 `MessageBus`
- 常驻消费 `bus.inbound`
-`await service.submit_direct(...)`
- 把结果写回 `bus.outbound`
- 同时等待 `stop_event`
- 退出时:
- 先尝试 `await service.shutdown(timeout_seconds=5.0, force=True)`
- 再等待 bridge 协程收尾;必要时取消 bridge
- 如果 gateway 自己接管 lifecycle 且 `start()` 失败:
- 会立即 `close()` 做 startup cleanup
- 未处理完的 inbound
- 不再静默丢下
- 会被冲刷成结构化 outbound error
3. `beaver/foundation/events/message_bus.py`
- 已有最小:
- `MessageBus`
- `InboundMessage`
- `OutboundMessage`
- 当前只做双队列桥接:
- `inbound`
- `outbound`
- 还没有 broker / topic routing / retry / persistence
所以现在已经明确:
1. Web / Gateway 属于宿主层
2. 它们不直接 new `AgentLoop` 或绕过运行模式
3. 它们复用:
- `start()`
- `submit_direct()`
- `stop()`
- `shutdown()`
4. ownership 语义:
- 自己创建的 `AgentService`:自己负责 lifecycle
- 外部注入的 `AgentService`:默认不自动 start/shutdown除非显式要求接管
5. gateway 已经从“只会常驻等待”推进到“最小消息桥接层”
- external inbound message
- `MessageBus.inbound`
- `service.submit_direct(...)`
- `MessageBus.outbound`
### 3.2 总体链路
当前代码里的主链可以概括成:
```text
AgentService
-> AgentLoop
-> Session
-> Memory
-> SkillAssembler
-> ContextBuilder
-> Provider
-> ToolExecutor
-> Session writeback
```
### 3.3 详细顺序
```text
用户输入 task
├─ AgentService.create_loop()
│ ├─ 创建 AgentLoop(profile, loader)
│ └─ loop.boot()
├─ AgentLoop.boot()
│ └─ EngineLoader.load()
│ ├─ SessionManager
│ ├─ MemoryStore
│ ├─ MemoryService
│ ├─ ToolRegistry
│ ├─ ToolExecutor
│ ├─ SkillsLoader
│ ├─ SkillAssembler
│ └─ ContextBuilder
├─ AgentLoop.process_direct(task)
│ │
│ ├─ 生成 `session_id` / `run_id`
│ │
│ ├─ memory_service.reload_for_new_run()
│ │ └─ 建立本轮 frozen memory snapshot
│ │
│ ├─ sessions.ensure_session(session_id)
│ ├─ sessions.append_message(event_type="run_started", hidden)
│ │
│ ├─ make_provider_bundle()
│ │ ├─ main provider
│ │ ├─ fallback provider
│ │ ├─ auxiliary provider 可用于 skill 选择
│ │ └─ embedding runtime 提供 embeddings 的 model/api_key/api_base
│ │ 说明:它是独立配置线,只支持 OpenAI-compatible embeddings endpoint
│ │
│ ├─ skill_assembler.assemble(task_description=task, provider=selector_provider, embedding_runtime=..., ...)
│ │ ├─ 读取全量可用 skill 候选摘要
│ │ ├─ 用 `text-embedding-v4` 对全量候选做相似度召回
│ │ ├─ 把召回结果交给 LLM 做最终选择
│ │ └─ 返回 activated_skills
│ │
│ ├─ ContextBuilder.build_skill_activation_messages(...)
│ ├─ 如果 activated_skills 非空:
│ │ └─ sessions.append_message(event_type="skill_activation_snapshotted", hidden)
│ │
│ ├─ ContextBuilder.build_messages()
│ │ ├─ system prompt 包含:
│ │ │ ├─ base system prompt
│ │ │ ├─ session metadata
│ │ │ ├─ execution context
│ │ │ └─ frozen memory snapshot
│ │ ├─ messages 里显式插入 activated skill messages
│ │ ├─ 再拼 visible history
│ │ └─ 最后追加当前 user input
│ │
│ ├─ sessions.update_system_prompt()
│ ├─ sessions.append_message(event_type="system_prompt_snapshotted", hidden)
│ ├─ sessions.append_message(event_type="user_message_added")
│ │
│ ├─ 进入最小 tool loop
│ │ ├─ provider.chat(messages, tools=schemas)
│ │ ├─ sessions.update_usage()
│ │ ├─ sessions.append_message(event_type="assistant_message_added")
│ │ ├─ ContextBuilder.add_assistant_message(...)
│ │ ├─ 如果没有 tool calls
│ │ │ └─ 结束
│ │ └─ 如果有 tool calls
│ │ ├─ ToolExecutor.execute_tool_call(...)
│ │ ├─ sessions.append_message(event_type="tool_result_recorded")
│ │ ├─ ContextBuilder.add_tool_result(...)
│ │ └─ 再回 provider.chat(...)
│ │
│ ├─ 成功结束:
│ │ └─ sessions.append_message(event_type="run_completed", hidden)
│ │
│ ├─ 异常结束:
│ │ ├─ 补 assistant error message
│ │ └─ sessions.append_message(event_type="run_failed", hidden)
│ │
│ └─ return AgentRunResult
│ ├─ session_id
│ ├─ run_id
│ ├─ output_text
│ ├─ finish_reason
│ ├─ tool_iterations
│ ├─ provider_name
│ ├─ model
│ └─ usage
```
---
## 4. 当前模块边界
### 4.1 `EngineLoader`
职责:装配运行时依赖。
当前已经装配:
1. `SessionManager`
2. `MemoryStore`
3. `MemoryService`
4. `ToolRegistry`
5. `ToolExecutor`
6. `SkillsLoader`
7. `SkillAssembler`
8. `ContextBuilder`
### 4.2 `AgentLoop`
职责:执行单次 run。
当前已经负责:
1. direct run 主链
2. provider 调用
3. 最小 tool loop
4. session 事件写回
5. usage 汇总
当前还没负责:
1. 更复杂的 message bus mode
2. 多 worker / 并发调度
3. 更完整的 runtime lifecycle
4. multi-agent orchestration
### 4.3 `Session`
职责:外部化的运行事实存储。
当前实现重点:
1. `sessions`
- projection / summary row
2. `messages`
- 当前主事件流
3. `run_id`
- 把同一个 session 里的多次 run 切开
当前主要读取接口:
1. `get_event_records(session_id)`
- 整个 session 的完整事件流
2. `get_run_event_records(session_id, run_id)`
- 某一次 run 的事件片段
3. `list_run_ids(session_id)`
- 发现当前 session 中有哪些 run
4. `get_visible_history(session_id)`
- 给 ContextBuilder 用的可见历史切片
5. `session_search`
- 只检索可见 transcript
- 不把 hidden prompt / skill snapshot 当成搜索候选
当前关键 hidden 事件:
1. `run_started`
2. `skill_activation_snapshotted`
3. `system_prompt_snapshotted`
4. `run_completed`
5. `run_failed`
### 4.4 `Memory`
职责durable facts不是 transcript。
当前实现重点:
1. curated CRUD
2. frozen snapshot
3. 每次新 run 开始时刷新 snapshot
4. 当前 run 中途写 memory 不反向污染本轮 prompt
### 4.5 `Skills`
职责:外置 skill 装配与按需查看。
当前实现重点:
1. `SkillsLoader`
- 扫描 `workspace/skills/*/SKILL.md`
- 扫描 builtin skills
2. `SkillAssembler`
- 输入 task description + 候选 skill 摘要
- 先用 embedding 做语义召回
- 再调一次 LLM 直接选择 skills
- 没有匹配时返回空 skills
3. `skill_view`
- 显式加载 skill 正文或支持文件
4. activated skills
- 按 Hermes 风格作为显式消息注入
当前 skill 语义已经定成:
1. **run-scoped**
- skill 激活只对当前 run 生效
2. **不是 session-scoped**
- 不默认跨 run 持久化为 session 状态
3. **explicit loading path**
- `skill_view`
4. **no-match means no skill injection**
- 如果 assembler 没选出 skill
- 当前 run 不拼接 skill messages
- 也不会写 `skill_activation_snapshotted`
### 4.6 `Tools`
当前内建工具:
1. `echo`
2. `memory`
3. `skill_view`
4. `session_search`
当前工具基础设施:
1. `ToolSpec`
2. `ObjectBackedTool`
3. `ToolRegistry`
4. `ToolExecutor`
### 4.7 `Providers`
当前已经实现:
1. provider registry
2. runtime resolution
3. main provider
4. fallback provider
当前状态:
1. fallback 已经是“每次调用都先 main再 fallback”
2. auxiliary provider 已经可用于 skill 选择
3. auxiliary provider 还没有进入主对话 tool loop
---
## 5. 当前最重要的设计决定
这几条是现在已经定下来的,不应该再反复漂:
### 5.1 `Session-first`
当前 Beaver 明确在往这个方向走:
1. 运行事实优先写回 Session
2. Session 是 replay / audit / resume 的基础
3. prompt 不是状态源Session 才是
### 5.2 `Harness != Product Interface`
当前主入口已经是:
- `AgentService`
- `AgentLoop`
而不是 CLI 本身。
CLI、Web、Gateway 后面都应该只是接口层。
### 5.3 `Skill selection` 外置
已经不再让 `AgentLoop` 自己“决定该选哪个 skill”而是
```text
task description
-> SkillAssembler
-> AgentLoop
```
### 5.4 `Skills` 采用 Hermes 风格
不是:
- skill 正文长期塞进 system prompt
- summary 让模型自己猜怎么展开
而是:
1. activated skill messages
2. `skill_view`
---
## 6. 当前还没完成什么
这部分是接下来继续施工的重点。
### 6.1 运行时生命周期
已做第一步:
1. `EngineLoadResult.close()`
2. `AgentLoop.close()`
3. `AgentService.close()`
4. `AgentService.shutdown()`
已做第二步的最小版本:
1. `AgentLoop.run()`
2. `AgentLoop.stop()`
3. `AgentLoop.submit_direct()`
还没做:
1. 统一 shutdown hooks
2. 更完整的 provider/client 资源释放协议
3. 多 worker / bus / 调度策略
### 6.2 Web / Gateway 接主链
现在主链已经能跑,但还没正式变成:
1. Web 真正调用 `AgentService.process_direct()`
2. Gateway 真正调用 `AgentService.process_direct()`
### 6.3 Session 更完整的 event-source 能力
还没做:
1. checkpoint
2. rewind
3. fork session
4. crash-resume protocol
### 6.4 Multi-agent / swarms
还没正式接回主链:
1. delegation
2. team runtime
3. swarms orchestration backend
但 lifecycle 关系已经先定下来了:
1. team 不会共享一个大 `AgentLoop` 跑所有成员
2. 每个 team member 都应有自己独立的 `AgentService / AgentLoop`
3. team coordinator 在上层调度多个 member 实例
4. 因此当前这套 `start()/submit_direct()/stop()/close()` 首先是 member-level lifecycle
2. team runtime
3. swarms backend
4. group discussion / workflow orchestration
### 6.5 权限与治理
还没做:
1. permission gates
2. tool policy
3. MCP 工具治理
---
## 7. 下一步从哪开始最合理
如果现在继续施工,最合理的顺序是:
1. 先把 `flow.md` 作为当前基线固定下来
2. 再继续第 6 阶段:
- runtime lifecycle
- `boot / close / run / stop`
3. 然后再接:
- Web / Gateway
4. 最后才是:
- multi-agent / swarms
一句话总结:
**当前 Beaver 已经有一个可运行的单 agent runtime接下来不是继续堆局部能力而是把它升级成有完整生命周期的标准 harness。**
---
## 8. 文档维护要求
以后只要发生以下任一变动,必须同步更新本文件:
1. `EngineLoader` 装配项变化
2. `AgentLoop` 主链变化
3. `Session` 事件流结构变化
4. `Memory` 接入方式变化
5. `Skills` 装配方式变化
6. `Tools` 默认集合变化
7. Web / Gateway / multi-agent 真正接入主链