# Beaver Backend 施工指南 这份文档不是蓝图,也不是迁移映射,而是“真正开始施工时怎么下手”的执行指南。 目标是:**按运行时主链路,一步一步把 `backend-old` 的能力迁进新的 `beaver` 后端,并且始终保证我们先打通主链,再扩外围。** --- ## 1. 施工总原则 先把几条原则定死,否则很容易又回到旧项目那种“边写边散”的状态。 ### 1.1 先打通主链,再补外围 不要一上来拆 Web,不要先做页面,不要先做渠道接入。 施工顺序必须是: 1. 运行时主链 2. memory / skills / tools 3. delegation / team 4. CLI / Web / channels / gateway ### 1.2 先做最小可运行链路 第一阶段目标不是“全部功能迁完”,而是先让新 `beaver` 后端具备一条最小闭环: `input -> session -> context -> provider -> tool loop -> save turn -> output` 只要这条链没通,后面的多 agent、Web、MCP、cron 都不应该大规模开工。 ### 1.3 一次只收口一层边界 每一阶段必须回答三个问题: 1. 本阶段新增了什么文件 2. 本阶段替换了旧项目哪几个函数 3. 本阶段结束后,能跑通什么能力 ### 1.4 统一以 `beaver.engine.AgentLoop` 为唯一运行内核实现 这里说的不是“系统里只会存在一个 loop 实例”,而是: 1. 整个后端只维护一套 `AgentLoop` 实现代码 2. 可以同时存在多个 agent 运行实例 3. 但这些实例都必须复用同一个 `beaver.engine.AgentLoop` 4. 它们的差异只能来自 profile、context、toolset、skills、permissions,而不是来自不同执行栈 后续所有 agent 角色都必须围绕这同一套内核装配: - CLI agent instance - delegated local agent instance - team member agent instance - future A2A local specialist instance 不允许再出现“CLI 一套 loop、delegation 一套 loop、team 一套 loop”的情况。 --- ## 2. 从运行时视角看,系统到底怎么工作 我们先把最终运行时主链画出来。后续所有施工都围绕这条链拆。 ### 2.1 目标主链 一次标准请求应该按下面顺序流动: 1. `interfaces/*` - CLI / Web / Gateway 收到输入 2. `services/agent_service.py` - 创建或复用 `AgentLoop` 3. `engine/session/*` - 恢复 session 与历史消息 4. `memory/curated/*` - 读取 frozen snapshot 5. `skills/resolver/runtime.py` - 决定本轮注入哪些 skills 6. `engine/context/builder.py` - 拼装 system prompt + messages 7. `engine/providers/*` - 调用模型 8. `engine/loop.py` - 解析模型输出、执行 tool、迭代下一轮 9. `tools/*` - 执行文件、shell、web、memory、session_search、spawn 等工具 10. `engine/loop.py` - 汇总最终 assistant 输出 11. `engine/session/*` - 写回本轮消息 12. `engine/session/store.py` + `engine/session/search.py` - 记录 transcript,供 `session_search`、resume、history 使用 13. `interfaces/*` - 把结果回传给用户 ### 2.2 第一条必须先打通的链 真正施工时,不要直接从 message bus + async background task + WebSocket 开始。 第一条应该先打通的是: `process_direct(task) -> build context -> call provider -> execute tools -> save session -> return text` 也就是说: 1. 先做 direct run 2. 再做 bus run 3. 再做 Web / gateway 这样复杂度最低,也最容易验证。 --- ## 3. 施工顺序总览 后续建议严格按下面顺序施工。 ### 阶段 0:运行时前置件 目标:把主链运行所需的基础模型和公共组件先补齐。 先做这些文件: 1. `beaver/foundation/config/schema.py` 2. `beaver/foundation/config/loader.py` 3. `beaver/foundation/config/paths.py` 4. `beaver/foundation/events/messages.py` 5. `beaver/foundation/events/message_bus.py` 6. `beaver/foundation/events/process.py` 7. `beaver/foundation/models/run_result.py` 8. `beaver/foundation/utils/helpers.py` 9. `beaver/foundation/utils/llm_audit.py` 主要迁移来源: 1. `backend-old/nanobot/config/schema.py` 2. `backend-old/nanobot/config/loader.py` 3. `backend-old/nanobot/config/paths.py` 4. `backend-old/nanobot/bus/events.py` 5. `backend-old/nanobot/bus/queue.py` 6. `backend-old/nanobot/agent/process_events.py` 7. `backend-old/nanobot/agent/run_result.py` 8. `backend-old/nanobot/utils/helpers.py` 9. `backend-old/nanobot/llm_audit.py` 完成标准: 1. 新后端已经有稳定的 config / bus / event / result 基础件 2. 后续 engine、tools、services 不再依赖旧 `nanobot.*` --- ## 4. 第一施工阶段:先把单 agent 主链做出来 这是最关键的一阶段,也是整个项目的起点。 ### 4.1 先做 session 层 先实现: 1. `beaver/engine/session/models.py` 2. `beaver/engine/session/manager.py` 3. `beaver/engine/session/store.py` 4. `beaver/engine/session/search.py` 直接参考旧文件: - `backend-old/nanobot/session/manager.py` 额外参考: - `hermes-agent` 的 `hermes_state.py` - `OpenHarness` 的 harness 分层思路 这里的目标不是简单把旧 `SessionManager` 搬过来,而是把 session 拆成 4 层: 1. `models.py` - 放 `SessionRecord`、`MessageRecord`、`SessionUsage` - 只放数据结构,不放数据库逻辑 2. `store.py` - 放 SQLite 实现 - 负责 `sessions/messages` 表、WAL、FTS5、写入与查询 3. `search.py` - 放 `list_sessions_rich()`、`search_messages()`、`resolve_session_id()` 这类检索逻辑 - 明确这是 session 子系统的一部分,不再挂到 memory 下面 4. `manager.py` - 作为运行时门面 - `AgentLoop`、`services`、`interfaces` 只优先依赖它,而不是直接操作 SQLite 这四层的职责必须分开: 1. `session` 保存完整会话过程与历史恢复 2. `memory` 只保存 durable facts 3. `session_search` 只是 session transcript 的检索能力 4. `skills` 保存稳定方法论 现有的: - `beaver/memory/search/transcript_store.py` 要明确视为**临时过渡实现**。它是为了先让 MCP `session_search` tool 可用而建的,不是最终归宿。 真正开工 session 层时,应把它的能力并回: 1. `beaver/engine/session/store.py` 2. `beaver/engine/session/search.py` 然后让: - `beaver/tools/builtins/session_search.py` 改为直接依赖 `engine/session` 的 store / manager,而不是继续单独维护一套“memory search store”。 优先迁移的类和函数: 1. 旧 `Session` -> 拆成 `SessionRecord` + conversation replay 相关模型 2. 旧 `SessionManager` -> 改成 `SessionManager` 门面层 3. `get_or_create` 4. `_load` 5. `save` 6. `get_history` 但新 session 层必须新增这些 Hermes 风格能力: 1. `ensure_session(session_id, source, model, parent_session_id=None)` 2. `append_message(session_id, role, content, tool_name=None, tool_calls=None, ...)` 3. `get_messages_as_conversation(session_id)` 4. `update_system_prompt(session_id, system_prompt)` 5. `update_usage(session_id, input_tokens, output_tokens, ...)` 6. `end_session(session_id, end_reason)` 7. `reopen_session(session_id)` 8. `get_session(session_id)` 9. `list_sessions_rich(limit, include_children=False)` 10. `search_messages(query, role_filter=None, limit=...)` 11. `resolve_session_id(session_id_or_prefix)` session schema 也要从第一天就按 Hermes 思路建好,而不是后补: 1. `sessions` - `id` - `source` - `model` - `system_prompt` - `parent_session_id` - `started_at` - `ended_at` - `message_count` - `token / cost / usage` 相关字段 2. `messages` - `session_id` - `role` - `content` - `tool_name` - `tool_calls` - `timestamp` 3. `messages_fts` - 用 FTS5 做全文搜索 这一步要支持 lineage: 1. 正常 direct run 可以只有一个 root session 2. 后续 compression / resume / delegation / team member session 都通过 `parent_session_id` 挂到同一条会话链 3. `session_search` 和 session browser 都要按 lineage 理解,而不是只看单个碎片 session ### 4.1.1 session 层第一批施工顺序 按下面顺序落文件,不要反过来: 1. `models.py` 2. `store.py` 3. `search.py` 4. `manager.py` 原因: 1. 先把数据结构和 SQLite 能力定住 2. 再把搜索能力挂上 3. 最后才让 `manager` 做统一门面 ### 4.1.2 session 层第一批必须先跑通的函数 第一批不要贪多,先跑通这 8 个: 1. `ensure_session` 2. `append_message` 3. `get_session` 4. `get_messages_as_conversation` 5. `update_system_prompt` 6. `list_sessions_rich` 7. `search_messages` 8. `close` 只要这 8 个通了,`process_direct()`、history replay、`session_search` 就都有基础了。 ### 4.1.3 session 层与 runtime 的接点 后续 `AgentLoop` 必须按下面方式使用 session 层: 1. 请求进入时: - `ensure_session(...)` 2. context 组装完成后: - `update_system_prompt(...)` 3. 读取历史消息时: - `get_messages_as_conversation(...)` 4. 每次 user / assistant / tool 产出后: - `append_message(...)` 5. 会话结束或取消时: - `end_session(...)` ### 4.1.4 第一批 session 层不要做的事 不要一上来就做这些: 1. 复杂 session compaction 2. team transcript 合并策略 3. Web session API 4. gateway 专属优化 5. 跨数据库抽象层 先把单机 SQLite + WAL + FTS5 跑通即可。 ### 4.1.5 session 层如何一步步做 code review 每次完成 `session` 子系统的一轮改动,不要直接看“能不能跑”,而是按下面顺序 review。 #### 第一步:先看职责是否串层 按文件检查: 1. `models.py` - 只能有数据结构与序列化/反序列化辅助 - 不能出现 SQL - 不能出现 runtime orchestration 2. `store.py` - 只能负责 SQLite schema、写入、读取、事务 - 不能写 `session_search` 的上层业务流程 3. `search.py` - 只能负责 browse / FTS / resolve 逻辑 - 不能负责写入 4. `manager.py` - 只能做 facade - 不要把复杂 SQL 又塞回 manager 只要出现“某层开始顺手做别层的事”,这轮 review 就先不过。 #### 第二步:看 schema 是否支撑后续 runtime 逐项检查 `sessions` / `messages` 表字段是否够用: 1. `sessions` 是否有: - `id` - `source` - `model` - `system_prompt` - `parent_session_id` - `started_at` - `last_active` - `ended_at` - `message_count` 2. `messages` 是否有: - `session_id` - `role` - `content` - `tool_name` - `tool_calls` - `timestamp` 3. 是否已经有 FTS5 4. 是否已经有支持 lineage 的 `parent_session_id` 这一步的核心问题是: “等后面接 `AgentLoop`、resume、delegation、session_search` 时,会不会缺字段?” #### 第三步:看写路径是否完整 按运行时顺序 review: 1. `ensure_session()` - 新 session 能不能被创建 2. `append_message()` - user / assistant / tool message 能不能都写入 - tool_calls 是否正确序列化 3. `update_system_prompt()` - assembled prompt snapshot 能不能落盘 4. `update_usage()` - usage 是增量还是绝对覆盖,语义是否清楚 5. `end_session()` / `reopen_session()` - 生命周期状态是否闭环 这里只问一件事: “如果一轮对话完整跑完,session 数据是否真的闭环写全了?” #### 第四步:看读路径是否能支撑 prompt 组装 重点检查: 1. `get_session()` 2. `get_messages_as_conversation()` 3. `get_history()` 要问: 1. provider replay 需要的字段有没有漏 2. tool_calls 有没有被正确还原 3. leading non-user trimming 是否合理 4. 会不会把本地存储字段脏带进 prompt #### 第五步:看 search 是否真的能服务 `session_search` 重点检查: 1. `list_sessions_rich()` 2. `search_messages()` 3. `resolve_session_id()` 要问: 1. FTS query sanitization 是否足够 2. recent mode 返回的信息够不够 UI / tool 用 3. prefix resolve 是否会误命中多个 session 4. exclude_sources / role_filter 是否真的生效 #### 第六步:看并发与持久化风险 这里不要只看“代码风格”,要看数据安全: 1. SQLite 是否开启了 WAL 2. 写事务是否明确 3. `BEGIN IMMEDIATE` 是否正确 4. `check_same_thread=False` 后有没有线程锁保护 5. schema 初始化是否幂等 这一步问的是: “两个入口同时写 session 时,会不会炸?” #### 第七步:看兼容路径是不是临时且可收敛 现在的: - `beaver/memory/search/transcript_store.py` 是兼容层。 review 时要明确: 1. 它有没有新长逻辑 2. 它是不是只是薄封装 3. 后续是否可以安全删掉 兼容层一旦开始长业务逻辑,就说明架构又开始回退了。 #### 第八步:最后才看命名、注释、可读性 这一步最后看: 1. 命名是否统一用 `session` 而不是混成 `memory/transcript/history` 2. 中文注释是否解释了设计意图,而不是重复代码 3. manager / store / search 的边界是否一眼能看懂 #### 第九步:做最小行为验证 每轮 review 最后至少手跑这几条: 1. 创建 session 2. 写入 user message 3. 写入 assistant message 4. 更新 system prompt 5. 读取 history 6. FTS 搜索关键词 7. recent mode 浏览 session 如果这 7 条没过,这轮 review 不算通过。 为什么先做它: 因为没有 session,就没有: 1. 历史消息窗口 2. transcript 持久化 3. session_search 的真实后端 4. resume / history / lineage loop 无法闭环。 完成标准: 1. 能创建 session 2. 能读取历史 3. 能写回消息 4. 能记录 assembled system prompt snapshot 5. 能用 FTS5 搜历史消息 6. `session_search` 可以直接复用 session store ### 4.2 再做 provider 契约层 先实现: 1. `beaver/engine/providers/base.py` 2. `beaver/engine/providers/registry.py` 3. `beaver/engine/providers/factory.py` 4. `beaver/engine/providers/runtime.py` 然后再迁最常用 provider: 5. `beaver/engine/providers/custom.py` 6. `beaver/engine/providers/codex.py` 7. `beaver/engine/providers/litellm.py` 8. `beaver/engine/providers/anthropic.py` 对应旧来源: 1. `backend-old/nanobot/providers/base.py` 2. `backend-old/nanobot/providers/registry.py` 3. `backend-old/nanobot/providers/openai_codex_provider.py` 4. `backend-old/nanobot/providers/litellm_provider.py` 5. `backend-old/nanobot/providers/custom_provider.py` 额外参考: 1. `Hermes-agent` 的 provider runtime resolution 2. `Hermes-agent` 的 auxiliary routing 3. `Hermes-agent` 的 fallback model/provider 4. `OpenHarness` 的模块化边界 provider 层不要再做成“一个厂商一个世界”,而是要拆成 4 层: 1. `base.py` - 统一 provider 契约 - 统一 `LLMResponse` / `ToolCallRequest` 2. `registry.py` - 只放 provider 元数据与匹配规则 - 不放网络请求逻辑 3. `runtime.py` - 做 Hermes 风格 runtime resolution - 决定最终 `provider / model / api_base / api_mode / auth path` 4. `factory.py` - 作为对 engine 暴露的唯一装配入口 - 统一产出 `main / fallback / auxiliary` provider 组合 ### 4.2.1 provider 层的目标结构 最终 provider 子系统应该是: 1. `beaver/engine/providers/base.py` 2. `beaver/engine/providers/registry.py` 3. `beaver/engine/providers/runtime.py` 4. `beaver/engine/providers/factory.py` 5. `beaver/engine/providers/chain.py` 6. `beaver/engine/providers/custom.py` 7. `beaver/engine/providers/codex.py` 8. `beaver/engine/providers/litellm.py` 9. `beaver/engine/providers/anthropic.py` ### 4.2.2 provider 不按厂商数扩类,而按 API path 收敛 实现原则: 1. 大部分 OpenAI-compatible / LiteLLM-compatible provider 走 `litellm.py` 2. Anthropic 走 `anthropic.py` 的 native messages path 3. OpenAI Codex 走 `codex.py` 的 Responses path 4. 自定义 OpenAI-compatible endpoint 走 `custom.py` 5. embedding runtime 作为独立配置线存在,不再默认继承主聊天 provider 的 provider 语义 6. 当前 embedding 只支持 OpenAI-compatible `/v1/embeddings` 也就是说: 1. provider registry 可以很多 2. 但真正的执行路径只有少数几条 ### 4.2.3 第一批必须先支持的 provider 第一批先把这些 provider 的 runtime path 定住: 1. `openai` 2. `anthropic` 3. `openrouter` 4. `openai_codex` 5. `custom` 6. `github_copilot` 7. `deepseek` 8. `gemini` 第二批再补这些旧后端已有 provider: 1. `aihubmix` 2. `siliconflow` 3. `volcengine` 4. `dashscope` 5. `zhipu` 6. `moonshot` 7. `minimax` 8. `vllm` 9. `groq` ### 4.2.4 provider 层必须照 Hermes 做的能力 这几个能力要从第一天就在设计里留位置: 1. `api_mode` - `chat_completions` - `anthropic_messages` - `codex_responses` 2. `fallback_model` - 主 provider/model 失败后切换备用 - 由 `FallbackProviderChain` 统一执行 failover 3. `auxiliary routing` - 主对话与辅助任务可用不同 provider/model - 由 `resolve_auxiliary_runtime()` 做独立解析 4. `OpenRouter provider routing` - `sort` - `only` - `ignore` - `order` - `require_parameters` - `data_collection` ### 4.2.5 第一批 provider 施工顺序 按下面顺序落代码: 1. `base.py` 2. `registry.py` 3. `runtime.py` 4. `factory.py` 5. `chain.py` 6. `custom.py` 7. `codex.py` 8. `litellm.py` 9. `anthropic.py` 原因: 1. 不先定 runtime resolution,后面 provider 实现会继续散 2. 不先定 registry,factory 就会出现 if/else 污染 3. `chain` 先把 fallback 行为从 `AgentLoop` 里拿走 4. `custom` 和 `codex` 最容易先单独落地 5. `litellm` 收口大多数 provider 6. `anthropic` 作为 native path 独立出来 ### 4.2.6 第一批 provider 层必须先跑通的函数 第一批先跑通这些: 1. `find_by_name` 2. `find_by_model` 3. `find_gateway` 4. `resolve_provider_runtime` 5. `resolve_fallback_runtime` 6. `resolve_auxiliary_runtime` 7. `make_provider_from_runtime` 8. `make_main_provider` 9. `make_fallback_provider` 10. `make_aux_provider` 11. `make_provider_bundle` 12. `FallbackProviderChain.chat()` 13. 至少一个 provider 的 `chat()` 第一阶段不要求把所有 provider 全迁完,只要求先有一个能跑通主链的 provider。 建议先选: 1. `OpenAICodexProvider` 2. 或 `CustomProvider` 完成标准: 1. `AgentLoop` 能拿到 provider 2. 能发出一次最小模型请求 3. provider 解析不再散落在 CLI / Web / gateway 4. registry / runtime / factory 三层边界清楚 ### 4.3 再做 context builder 实现: 1. `beaver/engine/context/builder.py` 参考旧文件: - `backend-old/nanobot/agent/context.py` 优先实现的函数: 1. `build_system_prompt` 2. `build_messages` 3. `add_tool_result` 4. `add_assistant_message` 这一版必须改掉的地方: 1. 不再直接读取 live memory 2. 只注入 frozen snapshot 3. 给 skills 预留注入点 4. 给 current session / channel metadata 预留注入点 完成标准: 1. 能从 session history + frozen memory + skills 拼出 prompt 2. 输出结构稳定,后续便于测试 ### 4.4 再做 tools 基础设施 实现: 1. `beaver/tools/base.py` 2. `beaver/tools/registry/tool_registry.py` 参考旧文件: 1. `backend-old/nanobot/agent/tools/base.py` 2. `backend-old/nanobot/agent/tools/registry.py` 然后先迁最小工具集: 1. `beaver/tools/builtins/filesystem.py` 2. `beaver/tools/builtins/shell.py` 3. `beaver/tools/builtins/web.py` 4. `beaver/tools/builtins/message.py` 5. `beaver/tools/builtins/memory.py` 6. `beaver/tools/builtins/session_search.py` 第一阶段可以暂时不迁: 1. `spawn` 2. `cron` 3. `mcp wrapper` 因为先要保证单 agent tool loop 可运行。 完成标准: 1. registry 可以注册工具 2. provider 返回 tool call 时可以找到并执行工具 3. memory / session_search 已纳入统一工具集合 ### 4.5 最后实现第一版 `AgentLoop` 这是第一施工阶段的收口点。 实现: 1. `beaver/engine/loader.py` 2. `beaver/engine/loop.py` 参考旧文件: - `backend-old/nanobot/agent/loop.py` 第一版必须先实现这些函数: 1. `AgentLoop.__init__` 2. `boot` 3. `_set_tool_context` 4. `_process_message` 5. `_run_agent_loop` 6. `_save_turn` 7. `process_direct` 8. `run` 9. `stop` 第一版暂时不要迁的逻辑: 1. `_connect_mcp` 2. `reload_mcp_servers` 3. 复杂 background consolidation 4. team delegation 因为现在 memory 体系已经不是旧的 `consolidate_memory()` 了,这些旧逻辑不能硬搬。 第一阶段验收方式: 1. 用 CLI 或脚本创建一个 loop 2. 调 `process_direct("hello")` 3. 能返回模型回复 4. 工具调用能生效 5. session 能写回 只要这一条通了,新的 `beaver` runtime 就算正式开工成功。 --- ## 5. 第二施工阶段:把 memory / skills 接进主链 第一阶段打通的是“能跑”;第二阶段打通的是“跑得像 Beaver”。 ### 5.1 memory 接入主链 当前已经有: 1. `beaver/memory/curated/store.py` 2. `beaver/memory/curated/snapshot.py` 3. `beaver/memory/search/transcript_store.py`(临时过渡实现) 4. `beaver/tools/builtins/memory.py` 5. `beaver/tools/builtins/session_search.py` 现在要做的是把它们真正装进运行时: 需要改的地方: 1. `beaver/engine/loader.py` - 初始化 `MemoryStore` - `load_from_disk()` - capture snapshot 2. `beaver/engine/context/builder.py` - 注入 frozen snapshot 3. `beaver/engine/loop.py` - 在 `_save_turn` 后把消息写进 session store 4. `beaver/tools/builtins/session_search.py` - 改为依赖 `beaver/engine/session/search.py` 5. `beaver/memory/search/transcript_store.py` - 在 session 层稳定后删除或保留为兼容薄封装,不再作为主实现 完成标准: 1. `memory` tool 真正能写持久记忆 2. 新 session 能读到上次写入的 frozen snapshot 3. `session_search` 能直接搜 session transcript ### 5.2 skills 接入主链 实现: 1. `beaver/skills/catalog/loader.py` 2. `beaver/skills/catalog/utils.py` 3. `beaver/skills/resolver/runtime.py` 参考旧文件: - `backend-old/nanobot/agent/skills.py` 先迁这些函数: 1. `list_skills` 2. `get_skill_metadata` 3. `load_skill` 4. `load_skills_for_context` 5. `build_skills_summary` 6. `get_always_skills` 接入点: 1. `engine/loader.py` 2. `engine/context/builder.py` 3. `engine/loop.py` 第二阶段要求做到: 1. 主 agent 能按上下文注入 skills 2. skills 不只是文档目录,而是运行时上下文的一部分 3. 激活后的 skill 正文按 Hermes 风格走显式消息注入,而不是长期塞进 system prompt 4. skill 的选择不再由 AgentLoop 内部硬编码完成,而是交给外置 `SkillAssembler` 5. `SkillAssembler` 采用最直接的 LLM 选择器: - 输入 task description - 输入候选 skill 摘要 - 先用 embedding 做语义召回 - 输出应该激活的 skills 6. embedding 配置通过 provider bundle 的独立 `embedding runtime` 传入;若没有显式 embedding 配置,则只有主链本身是 OpenAI-compatible 时才允许继承 `api_base/api_key` --- ## 6. 第三施工阶段:把 direct run 扩成标准 runtime 当 direct run 已经稳定后,再把它扩成“完整的运行时内核”。 这一阶段的核心思想先明确为两句: 1. `Session = Durable Memory + Event Source` 2. `Harness = Stateless Orchestrator` 也就是说,后面的 Beaver 不应再把任务进度、运行中间态、恢复点藏在进程内对象里,而应尽量写回外部 Session。 ### 6.0 这一阶段的目标架构 这一阶段要把当前的“最小单 agent 主链”推进成下面这种结构: 1. `Session` - 是外部持久化记忆 - 是唯一事实来源 - 本质上是 append-only event stream 2. `Harness` - 只负责编排 - 自身不持有不可恢复状态 - 崩溃后可由新实例读取 Session 接管 3. `ContextBuilder` - 不再直接依赖进程内状态 - 只从 Session / curated memory / skills 中提取当前需要的上下文 这一步不是要一口气做完 fork / rewind / checkpoint 全套系统,而是先把“Session-first, Stateless Harness”这条主线立住。 ### 6.1 第一步:先把 Session 升级为事件源模型 这是第六阶段真正的第一步,也是后续 runtime 生命周期的基础。 目标: 1. 让 `Session` 不只是聊天记录表,而是运行事件源 2. 让 `AgentLoop` 不再依赖进程内隐式状态来判断“任务做到哪了” 3. 让新的 Harness 实例理论上可以只靠 Session 恢复运行现场 第一步先做这些文件: 1. `beaver/engine/session/models.py` 2. `beaver/engine/session/store.py` 3. `beaver/engine/session/manager.py` 4. `beaver/engine/loop.py` 5. `beaver/engine/context/builder.py` 6. `beaver/engine/loader.py` #### 6.1.1 具体怎么做 先在 session 层引入“事件优先”的视角: 1. 保留现有 `sessions` 表 - 它继续承担 projection / summary row 的角色 - 例如 `last_active`、`message_count`、`preview`、累计 usage 2. 强化 `messages` 表的事件语义 - 它不只是聊天记录 - 而是当前阶段的主事件流 3. 给事件加清晰类型边界 - `user` - `assistant` - `tool` - 后续可扩 `system_event` / `checkpoint_event` / `run_event` 然后在 `AgentLoop.process_direct()` 中,明确把运行过程拆成“事件追加”: 1. `session_started/ensured` 2. `run_started` 3. `system_prompt_snapshotted` 4. `user_message_added` 5. `assistant_message_added` 6. `tool_call_requested` 7. `tool_result_recorded` 8. `run_failed` 或 `run_completed` 并且每次 run 都要带独立 `run_id`,这样同一个 session 内的多次运行才能被切开。 注意: 第一步不一定要真的新建一张 `session_events` 表,先把现有 `messages` 作为主事件流用起来也可以。 关键不是表名,而是: 1. 运行进度要能从外部事件重建 2. 不依赖进程内变量才能知道“上一步发生了什么” #### 6.1.2 这一小步里具体要改哪些函数 优先改这些函数: 1. `SessionStore.append_message()` - 明确它承担事件追加语义 2. `SessionManager.get_history()` - 不只是“取最近聊天记录” - 而是“从事件流里切一段 provider 需要的上下文” 3. `SessionManager.get_run_event_records()` - 能按 `run_id` 读取某一次运行的事件片段 4. `SessionManager.list_run_ids()` - 能发现当前 session 内有哪些 run 5. `AgentLoop.process_direct()` - 继续保留 direct run 入口 - 但内部按事件阶段组织代码 6. `ContextBuilder.build_messages()` - 明确消费的是“上游裁剪后的事件片段” - 而不是默认依赖进程内连续状态 #### 6.1.3 第一步完成后的结果 这一小步做完后,应达到: 1. Session 已经是当前运行事实的主要来源 2. AgentLoop 即使重建实例,也能读出: - 当前 session 里有哪些 run - 某一次 run 的起点和结束点 - 当前历史消息 - 上一次 system prompt snapshot - 工具执行痕迹 - 失败点 3. 后续继续做: - `run()` - `stop()` - `close()/shutdown()` - fork / rewind / checkpoint 才不会回到“全靠进程内状态续命” ### 6.2 第二步:把 runtime 生命周期协议补齐 当前已完成的最小骨架: 1. `EngineLoader` 返回的 `EngineLoadResult` 已经具备 runtime 容器语义 2. `EngineLoadResult.close()` 已能统一关闭已登记的 closeables 3. `AgentLoop.boot()/close()` 已建立成对协议 4. `AgentService.close()/shutdown()` 已可作为接口层统一释放入口 5. `AgentLoop.run()/stop()/submit_direct()` 已形成最小运行循环 6. `AgentService.start()/stop()/submit_direct()` 已可包装该运行循环 只有在 6.1 稳住后,才开始补统一生命周期: 1. 扩 `closeables / shutdown hooks` 2. 明确 provider/client 等更多资源的释放协议 3. 再补更复杂的 bus / worker / 调度语义 这一步的目标: 1. 明确谁创建 runtime 2. 明确谁拥有 runtime 3. 明确谁负责释放 session/provider/client 等资源 这一阶段的 lifecycle 语义也已经定死: 1. `start()`:让一个 `AgentLoop` 实例进入运行模式 2. 运行模式下:所有外部任务只能走 `submit_direct()` 3. 运行模式下:不允许外部再直接调用 `process_direct()` 4. `stop()`:instance-scoped,只停止当前这个 `AgentLoop` 实例 5. `stop()` 不是 session-scoped,也不是 platform-scoped 6. `stop()` 调用后拒绝新任务,已入队任务收尾退出 7. `stop()` / `shutdown()` 应支持 graceful timeout,必要时允许 force cancel 8. `close()`:只有在实例已停止后才能释放 runtime 资源 ### 6.2.1 Web / Gateway 现在如何接这套 lifecycle 这一层现在已经开始落成真正的宿主层,而不是只停留在文档占位: 1. `beaver/interfaces/web/app.py` - FastAPI lifespan 启动时: - 创建或接收 `AgentService` - 如果 Web 自己创建 service,则 `await service.start()` - Web 层现在已经有最小正式 schema: - `WebChatRequest` - `WebChatResponse` - `WebStatusResponse` - Web 请求处理时: - 用结构化 schema 校验输入 - 只允许走 `await service.submit_direct(...)` - 将常见 runtime / config 错误收成明确的 HTTP 层错误 - 外部注入但尚未进入 running mode 的 service,返回 `503` - `/api/ping` - 返回 `status/running/mode` - 不会为了 health check 额外 boot runtime - app 关闭时: - 如果 Web 自己创建 service,则 `await service.shutdown(timeout_seconds=5.0, force=True)` - 如果 Web 自己接管 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` 但这一阶段还没做: 1. channels adapter 2. realtime streaming 3. platform-level supervisor 4. 更复杂的 bus 语义(retry / routing / persistence) ### 6.3 第三步:回填 bus 模式 实现: 1. `beaver/foundation/events/message_bus.py` 2. `beaver/engine/loop.py::run` 参考旧逻辑: - `backend-old/nanobot/agent/loop.py` 需要补的函数: 1. 从 inbound 读取消息 2. 调用 `_process_message` 3. 发布 outbound 注意: 只有在 `process_direct()` 稳定,并且 6.1 / 6.2 已经把 Session-first + lifecycle 骨架立住后,才做 `run()` 的长循环版本。 ### 6.4 单 agent lifecycle 如何扩展到 team 这里也先把关系写清楚,避免后面 team 层重走弯路: 1. team 不会共用一个 `AgentLoop` 来跑所有成员 2. 每个 team member 都应该是一个独立的 `AgentService / AgentLoop` 实例 3. 每个 member 自己有: - `start()` - `submit_direct()` - `stop()` - `close()` 4. team coordinator 在上层管理这些 member 实例,而不是绕开它们的 lifecycle 5. 因此当前这套 lifecycle 首先是 member-level lifecycle,后面才能往 team-level / platform-level 扩 --- ## 7. 第四施工阶段:加入 delegation 和单机 subagent 现在才开始动 multi-agent。 ### 7.1 先做 registry 实现: 1. `beaver/coordinator/registry/models.py` 2. `beaver/coordinator/registry/workspace_store.py` 3. `beaver/coordinator/registry/agent_registry.py` 4. `beaver/coordinator/registry/local_subagent_store.py` 来源: 1. `backend-old/nanobot/agent/agent_registry.py` 2. `backend-old/nanobot/agent/subagents.py` ### 7.2 再做本地单机 delegation 实现: 1. `beaver/engine/runtime/local_runner.py` 2. `beaver/coordinator/delegation/manager.py` 3. `beaver/coordinator/delegation/events.py` 4. `beaver/coordinator/delegation/announcement.py` 来源: 1. `backend-old/nanobot/agent/subagent.py` 2. `backend-old/nanobot/agent/delegation.py` 这一阶段的范围: 1. 先支持 `spawn_subagent` 2. 先支持 local delegation 3. 暂不急着接 swarms team 完成标准: 1. 主 agent 可以调用子 agent 2. 子 agent 与主 agent 复用同一个 `AgentLoop` 3. 只是 profile / toolset / prompt context 不同 --- ## 8. 第五施工阶段:接回群组讨论和流程化 team 这阶段才开始回收旧 `agent_team` 和 `swarms bridge` 的成果。 ### 8.1 先做 team types / planner / policy 实现: 1. `beaver/coordinator/team/types.py` 2. `beaver/coordinator/planner/swarms.py` 3. `beaver/coordinator/backends/swarms/policy.py` ### 8.2 再做 bridge / adapter 实现: 1. `beaver/coordinator/backends/swarms/bridge.py` 2. `beaver/coordinator/backends/swarms/adapter.py` 3. `beaver/coordinator/backends/swarms/runtime.py` 注意: 1. 不再引入 `third_party/` 2. 不再允许旧式 `sys.path` 注入 3. `swarms` 必须作为 adapter/backend,而不是平台内部结构 ### 8.3 最后做 orchestrator 实现: 1. `beaver/coordinator/team/orchestrator.py` 2. `beaver/coordinator/team/target_resolver.py` 3. `beaver/coordinator/team/provisioning.py` 这一阶段完成后,才算真正恢复: 1. 群组讨论 2. 流程化 team 3. skills 约束下的 multi-agent 执行 --- ## 9. 第六施工阶段:最后才拆入口层 这时候再拆 CLI / Web,成本最低,也最稳。 ### 9.1 CLI 从: - `backend-old/nanobot/cli/commands.py` 拆到: 1. `beaver/interfaces/cli/main.py` 2. `beaver/interfaces/cli/commands/agent.py` 3. `beaver/interfaces/cli/commands/web.py` 4. `beaver/interfaces/cli/commands/cron.py` 5. `beaver/interfaces/cli/commands/providers.py` 6. `beaver/interfaces/cli/tty.py` ### 9.2 Web 从: - `backend-old/nanobot/web/server.py` 拆到: 1. `beaver/interfaces/web/app.py` 2. `beaver/interfaces/web/deps.py` 3. `beaver/interfaces/web/realtime.py` 4. `beaver/interfaces/web/auth.py` 5. `beaver/interfaces/web/routes/*.py` 6. `beaver/interfaces/web/schemas/*.py` 只有在 engine / services 已稳定后,Web 才值得拆。 --- ## 10. 第一批真正建议开工的文件 如果现在立刻开始干,建议按下面顺序提交,不要跳。 ### 提交 1:foundation 前置件 文件: 1. `beaver/foundation/config/schema.py` 2. `beaver/foundation/config/loader.py` 3. `beaver/foundation/config/paths.py` 4. `beaver/foundation/events/messages.py` 5. `beaver/foundation/events/message_bus.py` 6. `beaver/foundation/events/process.py` 7. `beaver/foundation/models/run_result.py` 8. `beaver/foundation/utils/helpers.py` ### 提交 2:session + provider 基础 文件: 1. `beaver/engine/session/models.py` 2. `beaver/engine/session/manager.py` 3. `beaver/engine/session/store.py` 4. `beaver/engine/session/search.py` 3. `beaver/engine/providers/base.py` 4. `beaver/engine/providers/registry.py` 5. `beaver/engine/providers/factory.py` 6. 至少一个真实 provider ### 提交 3:context + tool registry 文件: 1. `beaver/engine/context/builder.py` 2. `beaver/tools/base.py` 3. `beaver/tools/registry/tool_registry.py` 4. 最小 builtins ### 提交 4:第一版 AgentLoop 文件: 1. `beaver/engine/loader.py` 2. `beaver/engine/loop.py` 3. `beaver/services/agent_service.py` 目标: 1. 跑通 `process_direct` ### 提交 5:memory / skills 正式接入 文件: 1. `beaver/memory/*` 2. `beaver/skills/catalog/*` 3. `beaver/skills/resolver/runtime.py` 4. `engine` 接入改动 --- ## 11. 第一阶段验收清单 在开始 Web / delegation 之前,必须满足以下条件: 1. `beaver.interfaces.cli.main` 能启动一个最小 loop 2. `AgentLoop.process_direct()` 可用 3. session 历史能读写 4. provider 能完成一次普通回复 5. provider 能触发工具调用 6. `memory` tool 可写 7. 新 session 能读到 frozen snapshot 8. `session_search` 能直接搜 session transcript 9. skills 能注入到 system prompt 如果这 9 条没过,不要进入下一阶段。 --- ## 12. 施工时要避免的错误 ### 12.1 不要先拆 Web `web/server.py` 很大,但它不是第一施工点。 先拆它,只会让你在 engine 还没稳的时候同时维护两套未完成装配。 ### 12.2 不要先做 team orchestration multi-agent 很吸引人,但没有稳定的单 agent runtime,team 层只会把问题放大。 ### 12.3 不要把旧 memory consolidation 直接搬过来 新 memory 基线已经确定是 Hermes 风格: 1. CRUD memory tool 2. frozen snapshot 3. session_search 所以旧的 `_consolidate_memory()` 路径不能原样迁。 ### 12.4 不要在新 backend 中继续扩散 `nanobot` 命名 允许在迁移说明文档里引用旧路径,但新代码文件、类名、导出都必须收敛为 `beaver`。 --- ## 13. 一句话施工结论 **从 `engine/session -> providers -> context -> tools -> AgentLoop.process_direct()` 这条最小运行时主链开始施工。** 先把单 agent 运行内核打通,再把 memory / skills 接进去,再做 delegation / team,最后才拆 CLI / Web。