Files
beaver_project/app-instance/backend/施工指南.md
steven_li 5ba5c7e4c1 feat(app-instance): 集成Beaver后端并更新配置管理
集成新的Beaver后端服务到应用实例中,替换原有的nanobot实现。

主要变更包括:
- 在Dockerfile和环境配置中添加Beaver相关路径和配置变量
- 更新工作目录结构从.nanobot到.beaver
- 实现Beaver引擎加载器,支持配置文件加载和工具组装
- 添加内置工具如ListDirectoryTool、ReadFileTool、SearchFilesTool
- 更新消息处理流程,支持通道适配器和网关模式
- 重构技能系统,支持显式工具提示和嵌入式检索
- 改进错误处理和生命周期管理

此变更使应用实例能够使用统一的Beaver后端进行AI代理运行时管理。
2026-04-27 17:37:40 +08:00

42 KiB
Raw Blame History

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 输出
  1. engine/session/*
  • 写回本轮消息
  1. engine/session/store.py + engine/session/search.py
  • 记录 transcriptsession_search、resume、history 使用
  1. 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-agenthermes_state.py
  • OpenHarness 的 harness 分层思路

这里的目标不是简单把旧 SessionManager 搬过来,而是把 session 拆成 4 层:

  1. models.py
    • SessionRecordMessageRecordSessionUsage
    • 只放数据结构,不放数据库逻辑
  2. store.py
    • 放 SQLite 实现
    • 负责 sessions/messages 表、WAL、FTS5、写入与查询
  3. search.py
    • list_sessions_rich()search_messages()resolve_session_id() 这类检索逻辑
    • 明确这是 session 子系统的一部分,不再挂到 memory 下面
  4. manager.py
    • 作为运行时门面
    • AgentLoopservicesinterfaces 只优先依赖它,而不是直接操作 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

重点检查:

  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

  1. beaver/engine/providers/custom.py
  2. beaver/engine/providers/codex.py
  3. beaver/engine/providers/litellm.py
  4. 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. 不先定 registryfactory 就会出现 if/else 污染
  3. chain 先把 fallback 行为从 AgentLoop 里拿走
  4. customcodex 最容易先单独落地
  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
  3. beaver/tools/assembler/task_assembler.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. 本地工具描述采用 MCP-style name/description/inputSchema
  5. ToolAssembler 能按 task description 用 embedding 召回本轮 top10 工具
  6. activated skill frontmatter 里的 tools 能参与本轮工具选择
  7. 只读 filesystem tools 已接入:
    • list_directory
    • read_file
    • search_files
  8. filesystem tools 强制限制在 ToolContext.workspace
  9. 相对路径逃逸、绝对路径逃逸、符号链接逃逸都会拒绝
  10. 二进制文件读取会拒绝,搜索会跳过二进制 / 大文件

当前工具选择规则已经定为:

  1. always tools 每轮默认可用
    • memory
    • session_search
    • skill_view
    • list_directory
    • read_file
    • search_files
  2. activated skill 可以显式声明工具:
---
tools:
  - terminal
  - read_file
---
  1. ToolAssembler 合并:
    • always tools
    • skill hints
    • task embedding top10 tools
  2. 第一版只信任 frontmatter / metadata 的显式 tools,不从正文里猜工具名
  3. 如果 skill 声明了尚未注册的工具,先忽略,不阻断 run

filesystem 这一版只做只读,不做写文件 / shell

  1. 先让 agent 能看见 workspace 结构、读源码、搜文本
  2. 写文件和 shell 属于高风险工具,必须等 permission gates 明确后再接
  3. 当前 workspace 边界只保证路径隔离,不等价于完整权限系统

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
  7. skill frontmatter 可声明本 skill 推荐工具;这些 tool hints 会交给 ToolAssembler

当前和长期目标的关系:

  1. 已完成基础入口:
    • curated memory CRUD
    • session_search
    • skill_view
    • SkillAssembler
    • ToolAssembler
  2. 还没完成长期智能体治理:
    • 智能体定期整理 / 提示记忆
    • 复杂任务完成后自主创建技能
    • 技能在使用过程中自我提升
    • FTS5 + LLM 摘要的跨会话回忆增强
    • Honcho 风格辩证用户建模
    • agentskills.io 开放标准兼容

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 中提取当前需要的上下文
  4. ToolAssembler
    • 不把所有工具无脑暴露给模型
    • 按 task description / activated skill hints 选择本轮工具
    • 输出 provider 可消费的 tool schema

这一步不是要一口气做完 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_activemessage_countpreview、累计 usage
  2. 强化 messages 表的事件语义
    • 它不只是聊天记录
    • 而是当前阶段的主事件流
  3. 给事件加清晰类型边界
    • user
    • assistant
    • tool
    • 后续可扩 system_event / checkpoint_event / run_event

然后在 AgentLoop.process_direct() 中,明确把运行过程拆成“事件追加”:

  1. session_started/ensured
  2. run_started
  3. skill_activation_snapshotted
  4. tool_selection_snapshotted
  5. system_prompt_snapshotted
  6. user_message_added
  7. assistant_message_added
  8. tool_result_recorded
  9. run_failedrun_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 资源

这一阶段也明确了模型配置的归属:

  1. 大模型 provider / api_key / api_base 属于 backend runtime config
  2. Web / Gateway / Channel 不单独保存模型密钥
  3. 前端请求不传 API Key只传 message/session_id/user_id 等业务输入
  4. Docker 单实例部署时,每个用户 sandbox 读取自己的实例配置
  5. 默认使用新 Beaver 实例目录:
    • /root/.beaver/config.json
    • /root/.beaver/workspace
  6. 新 Beaver 命名优先使用:
    • BEAVER_CONFIG_PATH
    • BEAVER_HOME/config.json
  7. 兼容迁移期旧命名:
    • NANOBOT_CONFIG_PATH
    • NANOBOT_HOME/config.json

app-instance 镜像也已经切到新 Beaver 后端:

  1. Dockerfile 只安装 backend/beaver
  2. 不再复制旧 backend/nanobotbackend/bridge、vendored swarms
  3. entrypoint 通过 python -m uvicorn beaver.interfaces.web.app:create_app --factory 启动 Web
  4. 容器内默认配置与 workspace 使用:
    • /root/.beaver/config.json
    • /root/.beaver/workspace

6.2.1 Web / Gateway 现在如何接这套 lifecycle

这一层现在已经开始落成真正的宿主层,而不是只停留在文档占位:

  1. beaver/interfaces/web/app.py

    • FastAPI lifespan 启动时:
      • 创建或接收 AgentService
      • 如果 Web 自己创建 serviceawait 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 自己创建 serviceawait service.shutdown(timeout_seconds=5.0, force=True)
    • 如果 Web 自己接管 lifecycle 且 start() 失败:
      • 立即 close() 做 startup cleanup
  2. beaver/interfaces/gateway/main.py

    • run_gateway() 启动时:
      • 如果 gateway 自己创建 serviceawait service.start()
    • 持有最小 MessageBus
    • 可选接收 ChannelManager / channel adapters
    • ChannelManagerchannels 参数二选一:
      • ChannelManager:外部提前配置好 channel
      • channelsgateway 内部创建 ChannelManager 并注册这些 channel
    • inbound 流向:
      • channel adapter 发布 InboundMessage
      • MessageBus.inbound
      • gateway bridge 常驻消费
      • await service.handle_inbound_message(...)
    • outbound 流向:
      • AgentService 内部完成 InboundMessage -> OutboundMessage 映射
      • gateway bridge 将结果写回 MessageBus.outbound
      • 如果启用了 ChannelManager,则分发给对应 channel adapter
      • 未启用 ChannelManager 时,保留直接消费 bus.outbound 的最小测试能力
    • 同时等待 stop_event
    • 停机时:
      • 先尝试 await service.shutdown(timeout_seconds=5.0, force=True)
      • 再等待 bridge 协程收尾;必要时取消 bridge
      • 再等待 outbound dispatch 协程收尾;必要时取消 dispatch
    • 如果 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
  4. beaver/interfaces/channels/*

    • 已经补了最小 channel adapter 层:
      • ChannelAdapter
      • ChannelManager
      • MemoryChannelAdapter
    • 当前 channel 职责很窄:
      • 把外部输入发布成 InboundMessage
      • 接收并投递 OutboundMessage
    • MemoryChannelAdapter 只用于本地测试和内嵌接入,不是正式消息 broker

所以现在已经明确:

  1. Web / Gateway 属于宿主层
  2. 它们不直接 new AgentLoop 或绕过运行模式
  3. 它们复用:
    • start()
    • submit_direct()
    • stop()
    • shutdown()
  4. ownership 语义:
    • 自己创建的 AgentService:自己负责 lifecycle
    • 外部注入的 AgentService:默认不自动 start/shutdown除非显式要求接管
  5. gateway 已经从“只会常驻等待”推进到“最小消息桥接层”
    • external inbound message
    • channel adapter
    • MessageBus.inbound
    • service.handle_inbound_message(...)
    • MessageBus.outbound
    • channel adapter outbound delivery

但这一阶段还没做:

  1. realtime streaming
  2. platform-level supervisor
  3. 更复杂的 bus 语义retry / routing / persistence
  4. 外部真实 channel adapter

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. 通过 AgentService.handle_inbound_message(...) 映射到 runtime 调用
  3. 发布 outbound

当前这一步的最小收口方式已经确定为:

  1. MessageBus 只负责协议和队列
  2. gateway 只负责宿主、常驻消费和 channel 分发
  3. InboundMessage -> AgentRunResult -> OutboundMessage 的映射收口在 AgentService
  4. AgentLoop 继续只关心 agent 执行内核,不直接感知 bus 协议

注意:

只有在 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_teamswarms 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. 第一批真正建议开工的文件

如果现在立刻开始干,建议按下面顺序提交,不要跳。

提交 1foundation 前置件

文件:

  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

提交 2session + 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
  5. beaver/engine/providers/base.py
  6. beaver/engine/providers/registry.py
  7. beaver/engine/providers/factory.py
  8. 至少一个真实 provider

提交 3context + 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

提交 5memory / 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 runtimeteam 层只会把问题放大。

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。