- 集成MCP连接管理器,支持MCP服务器连接 - 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、 PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、 TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等 - 实现工具注册和装配功能 - 添加技能选择上下文参数 - 支持思考模式控制参数thinking_enabled feat(coordinator): 重构任务执行计划器参数命名 - 将learning_candidate_enabled重命名为allow_candidate_generation - 更新TeamGraphScheduler中的参数传递 - 修改LocalAgentRunner中的相关参数处理 - 更新README文档中的相应描述 refactor(context): 标准化工具调用参数格式 - 添加_json导入用于参数序列化 - 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷 - 修复工具调用中参数非字符串类型的序列化问题 refactor(session): 优化消息历史记录过滤逻辑 - 修改get_messages_as_conversation为基于运行状态过滤消息 - 排除未完成、失败或错误结束的运行记录 - 改进对话历史的可见性控制机制 fix(store): 修复FTS索引重建逻辑 - 添加异常处理防止FTS索引创建失败 - 实现_rebuild_fts_index方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
2247 lines
72 KiB
Markdown
2247 lines
72 KiB
Markdown
# Beaver Backend 施工指南
|
||
|
||
这份文档不是蓝图,也不是迁移映射,而是“真正开始施工时怎么下手”的执行指南。
|
||
|
||
目标是:**按运行时主链路,一步一步把 `backend-old` 的能力迁进新的 `beaver` 后端,并且始终保证我们先打通主链,再扩外围。**
|
||
|
||
文档分工:
|
||
|
||
1. `flow.md`
|
||
- 只保留树形运行结构
|
||
- 只回答“现在 runtime 怎么接、模块怎么连”
|
||
2. `施工指南.md`
|
||
- 保留施工顺序、阶段目标、完成标准、迁移动作
|
||
3. `change.md`
|
||
- 保留长期蓝图、设计动机、参考项目边界、架构判断
|
||
|
||
---
|
||
|
||
## 0. 当前施工状态(2026-05-07)
|
||
|
||
当前新后端已经完成的不只是最小 `AgentLoop` 主链,而是已经把 Main Agent 自动 Task 化、反馈学习闭环、Agent Team v1 轻量 coordinator,以及 Task mode 内部 team 执行规划链路接入到了内部服务层。
|
||
|
||
已完成:
|
||
|
||
1. `AgentService.process_direct/submit_direct` 前置 Main Agent 路由。
|
||
- `simple`:直接走原有单轮回答,不创建 Task。
|
||
- `task`:内部自动创建或复用 Task。
|
||
2. 内部 Task 子系统已落地。
|
||
- `beaver/tasks/models.py`
|
||
- `beaver/tasks/store.py`
|
||
- `beaver/tasks/service.py`
|
||
- `beaver/tasks/router.py`
|
||
- `beaver/tasks/validation.py`
|
||
3. `AgentLoop.process_direct()` 已支持内部参数:
|
||
- `task_id`
|
||
- `task_mode`
|
||
- `attempt_index`
|
||
- `allow_candidate_generation`
|
||
4. `RunRecord` 已记录:
|
||
- `task_id`
|
||
- `attempt_index`
|
||
- `validation_result`
|
||
5. Task 模式完成后会自动验证。
|
||
- 通过 `ValidationService.validate_task_result(...)` 生成结构化 `ValidationResult`
|
||
- 验证失败自动修订一次
|
||
- 第一次失败尝试会从可见上下文隐藏,避免用户刷新后看到被系统判失败的草稿
|
||
6. 聊天反馈接口已落地。
|
||
- `POST /api/chat/feedback`
|
||
- 通过 `run_id -> task_id` 找到内部 Task
|
||
- `satisfied / revise / abandon` 三种反馈
|
||
- 反馈状态投影回最近 assistant 消息,刷新后保留
|
||
7. 前端已做最小反馈控件。
|
||
- 最新 assistant Task 结果下显示“满意 / 需要修改 / 放弃”
|
||
- REST 和 WebSocket 路径都会携带或刷新 `run_id/task_id/validation_result`
|
||
8. 学习触发已经收紧。
|
||
- Task 模式 run 不再直接生成成功学习候选
|
||
- 只有“自动验证通过 + 用户点击满意”才触发成功学习候选
|
||
- “放弃”只写失败证据,不默认写主 memory,不生成成功 Skill draft
|
||
9. Agent Team v1 已落地为 Beaver 自有轻量 coordinator。
|
||
- 新增 `AgentDescriptor / DelegationEnvelope / ExecutionNode / ExecutionGraph / TeamRunResult`
|
||
- 新增 `TeamService.run_team(...)` 作为内部服务入口
|
||
- 新增 `LocalAgentRunner`,sub-agent 复用主 `AgentLoop.process_direct()` / `submit_direct()`
|
||
- 支持 `sequence / parallel / dag` 三个执行原语
|
||
- `parallel` 和 DAG 同层节点保持真并发
|
||
- sub-agent 使用 per-run memory snapshot,避免并发串记忆
|
||
- 支持 pinned skill 继承,open skills 继续由 `SkillAssembler` 补充
|
||
- 支持 per-node `provider_bundle_factory`
|
||
- 父 `Task` 前置校验,sub-agent run_ids 回填父 Task
|
||
- 节点级异常归一成 `NodeRunResult`,summary 只聚合成功输出并列出失败节点
|
||
10. Agent Team 已接入 Task mode 内部执行链。
|
||
- 新增 `beaver/tasks/planner.py`
|
||
- `TaskExecutionPlanner` 使用 LLM JSON 规划 `single / team`
|
||
- team node 只声明 `skill_query / required_capabilities`,不声明固定 specialist 人设
|
||
- 新增 `beaver/tasks/skill_resolver.py`
|
||
- `TaskSkillResolver` 为 generic sub-agent 选择 published skill;未命中时生成 ephemeral guidance,并作为本次 run 的 pinned guidance 使用
|
||
- 只允许 v1 已实现的 `sequence / parallel / dag`
|
||
- planner 失败或 graph 非法时降级为 `single`
|
||
- team run 先作为 sub-agent 内部执行,输出注入主 Agent synthesis run
|
||
- 用户可见最终回答仍由主 Agent 生成,再进入验证、反馈和学习门控
|
||
- 隐藏事件记录 `task_execution_planned / task_team_run_completed / task_team_run_failed`
|
||
11. Skill Learning 后台 pipeline 已落地为 assisted learning,而不是自动上线。
|
||
- candidate 状态扩展为 `open / queued / synthesizing / draft_ready / safety_failed / eval_failed / review_pending / approved / rejected / published / failed / superseded`
|
||
- `SkillLearningWorker` 支持按配置后台扫描,也支持 `POST /api/skills/learning/run-once`
|
||
- worker 自动到 draft/safety/eval 为止,永不自动 approve/publish
|
||
- 每个 draft 发布前必须有 safety report;critical/safety failed 直接阻断
|
||
- eval failed 阻断 publish;provider 不可用时记录 `skipped_provider_unavailable`
|
||
- 前端 skills 页已提供候选、草稿、安全报告、评估报告、审核、发布、禁用、回滚入口
|
||
|
||
当前仍未完成:
|
||
|
||
1. Agent Team 不暴露产品级聊天路由或显式 Task API;当前只作为 Task 内部 sub-agent 执行策略。
|
||
2. `moa / hierarchy / heavy / group_chat / forest / maker / router` 仍只是预留策略,不是 v1 完整行为。
|
||
3. 自动验证还是 LLM validator,不是 replay sandbox。
|
||
4. Skill Learning 当前是 assisted pipeline,不做低风险自动发布;自动发布/灰度发布仍是未来阶段。
|
||
5. `/api/agents` 和 agent registry 可作为未来外部 agent/A2A 管理面保留,但不参与 Task sub-agent 选择。
|
||
|
||
---
|
||
|
||
## 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”的情况。
|
||
|
||
### 1.5 参考项目怎么用,边界先写死
|
||
|
||
这版施工指南对应的是 `2026-05-06` 已重新核对后的参考口径。我们确认过的公开入口:
|
||
|
||
1. `OpenHarness`
|
||
- <https://github.com/HKUDS/OpenHarness>
|
||
2. `hermes-agent`
|
||
- <https://github.com/NousResearch/hermes-agent>
|
||
3. `swarms`
|
||
- <https://github.com/kyegomez/swarms>
|
||
|
||
后续施工时,这三个项目只按下面的方式使用:
|
||
|
||
1. `OpenHarness`
|
||
- 参考它的 harness 分层和统一 loop 组织方式
|
||
- 用来校正目录边界:`engine / tools / skills / permissions / memory / coordinator / interfaces`
|
||
- 不照搬它的 CLI/TUI、commands、plugin 生态,也不追求目录一模一样
|
||
2. `hermes-agent`
|
||
- 参考它的 memory / session / session_search / skills 关系
|
||
- 重点借鉴:durable memory、frozen snapshot、FTS5 transcript search、显式 skill 注入、session lineage
|
||
- 不把自动 skill 学习闭环、完整渠道网关、全部远端 backend 一次性纳入当前施工范围
|
||
3. `swarms`
|
||
- 只作为后续多智能体 execution backend / strategy 来源
|
||
- 重点借鉴:sequential / hierarchy / rearrange / router 这类编排形态
|
||
- 不允许它定义 Beaver 的主 runtime、session、tool、provider 契约
|
||
|
||
把这条边界写死的原因很简单:
|
||
|
||
1. 当前阶段先把单 agent 主链做稳
|
||
2. 多智能体回迁时只能挂到 Beaver 自己的 coordinator/backend 抽象下面
|
||
3. 不再恢复 `third_party/swarms` 那种由第三方目录反向定义平台结构的做法
|
||
|
||
---
|
||
|
||
## 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`
|
||
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 可以显式声明工具:
|
||
|
||
```yaml
|
||
---
|
||
tools:
|
||
- terminal
|
||
- read_file
|
||
---
|
||
```
|
||
|
||
3. `ToolAssembler` 合并:
|
||
- always tools
|
||
- skill hints
|
||
- task embedding top10 tools
|
||
4. 第一版只信任 frontmatter / metadata 的显式 `tools`,不从正文里猜工具名
|
||
5. 如果 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. 已完成学习闭环的第一层门控:
|
||
- `RunRecord`
|
||
- `SkillActivationReceipt`
|
||
- `SkillEffectRecord`
|
||
- `SkillLearningCandidate`
|
||
- `TaskRecord`
|
||
- `TaskEvent`
|
||
- `ValidationResult`
|
||
- `/api/chat/feedback`
|
||
3. 还没完成长期智能体治理:
|
||
- 智能体定期整理 / 提示记忆
|
||
- 复杂任务完成后自动合成 skill draft 的后台 pipeline
|
||
- 技能在使用过程中自我提升
|
||
- FTS5 + LLM 摘要的跨会话回忆增强
|
||
- Honcho 风格辩证用户建模
|
||
- agentskills.io 开放标准兼容
|
||
|
||
这里要特别说明:这些“还没完成”的点里,**最不应该被误解成可有可无附件**的,就是
|
||
Hermes 的 learning loop,也就是 Beaver 这里预想要落成的 `skills 学习能力`。
|
||
|
||
Hermes 官方公开说明里,明确把这些能力作为它的核心区别:
|
||
|
||
1. built-in learning loop
|
||
2. creates skills from experience
|
||
3. skills self-improve during use
|
||
4. nudges itself to persist knowledge
|
||
5. FTS5 session search for cross-session recall
|
||
|
||
参考:
|
||
|
||
1. <https://github.com/NousResearch/hermes-agent>
|
||
2. <https://github.com/NousResearch/hermes-agent-self-evolution>
|
||
|
||
所以这里不是“我们没打算做”。当前阶段已经把 learning loop 的第一层接回主链:
|
||
|
||
1. 复杂任务自动进入内部 Task。
|
||
2. Task run 必须经过自动验证。
|
||
3. 成功学习候选必须等待用户满意反馈。
|
||
4. 失败/放弃进入 Failure Memory。
|
||
|
||
当前已补齐 assisted learning pipeline:后台 skill draft synthesis、safety report、轻量 eval report、review/publish UI 已接入。它仍不是“全自动自学习系统”,因为自动发布、灰度发布、长期线上效果自动回滚仍保留为未来阶段。
|
||
|
||
### 5.3 skills 生命周期与学习闭环
|
||
|
||
这一步建议明确单列出来,不和 `5.2 skills 最小接入` 混为一谈。
|
||
|
||
`5.2` 解决的是:
|
||
|
||
1. skill 能被加载
|
||
2. skill 能被选择
|
||
3. skill 能注入当前 run
|
||
4. skill frontmatter 能影响工具选择
|
||
|
||
`5.3` 要解决的是:
|
||
|
||
1. skill 如何被创建
|
||
2. skill 如何被修订
|
||
3. skill 如何被审核
|
||
4. skill 如何被发布/禁用/回滚
|
||
5. skill 的效果如何被记录与比较
|
||
6. 哪个 skill 版本参与了哪次运行,如何留痕
|
||
|
||
### 5.3.1 第一批文件清单
|
||
|
||
先不要一上来做“自动改 skill”。第一批先把 skill 作为**可版本化、可审核、可留痕的能力对象**
|
||
落成稳定边界。
|
||
|
||
建议先补这些文件:
|
||
|
||
1. `beaver/skills/specs/models.py`
|
||
- 定义 `SkillSpec`
|
||
- 定义 `SkillVersion`
|
||
- 定义 `SkillReviewState`
|
||
- 定义 `SkillDraft`
|
||
- 定义 `SkillActivationReceipt`
|
||
2. `beaver/skills/specs/serialization.py`
|
||
- skill metadata/frontmatter 规范化
|
||
- dataclass <-> dict/json 转换
|
||
- 摘要哈希、正文哈希、版本指纹
|
||
3. `beaver/skills/specs/storage.py`
|
||
- 负责 `drafts/reviews/published/archive` 目录读写
|
||
- 负责原子写入和版本索引
|
||
4. `beaver/skills/drafts/service.py`
|
||
- 创建 draft
|
||
- 基于已有 skill version 生成修订 draft
|
||
- 列出 / 读取 draft
|
||
5. `beaver/skills/reviews/service.py`
|
||
- 提交审核
|
||
- 审核通过
|
||
- 审核拒绝
|
||
- 记录审核意见
|
||
6. `beaver/skills/publisher/service.py`
|
||
- draft -> published version
|
||
- 禁用 skill
|
||
- 回滚到历史版本
|
||
- 更新“当前生效版本”指针
|
||
7. `beaver/memory/runs/models.py`
|
||
- 定义 `RunRecord`
|
||
- 定义 `RunOutcome`
|
||
- 定义 `SkillEffectRecord`
|
||
8. `beaver/memory/runs/store.py`
|
||
- 持久化 run receipts
|
||
- 支持按 skill/version 查询历史效果
|
||
9. `beaver/memory/skills/models.py`
|
||
- 定义 `SkillPerformanceSnapshot`
|
||
- 定义 `SkillLearningCandidate`
|
||
10. `beaver/memory/skills/store.py`
|
||
- 聚合 skill 版本的效果统计
|
||
- 记录待学习/待修订候选
|
||
|
||
已有目录可直接接住这批文件:
|
||
|
||
1. `beaver/skills/drafts/`
|
||
2. `beaver/skills/reviews/`
|
||
3. `beaver/skills/publisher/`
|
||
4. `beaver/memory/runs/`
|
||
5. `beaver/memory/skills/`
|
||
|
||
建议新增:
|
||
|
||
1. `beaver/skills/specs/`
|
||
|
||
### 5.3.2 建议的磁盘布局
|
||
|
||
第一版先用 workspace 文件存储,不急着上数据库。
|
||
|
||
建议目录:
|
||
|
||
```text
|
||
<workspace>/skills/
|
||
├─ <skill-name>/
|
||
│ ├─ skill.json # SkillSpec 稳定元数据
|
||
│ ├─ current.json # 当前生效版本指针
|
||
│ ├─ versions/
|
||
│ │ ├─ v0001/
|
||
│ │ │ ├─ SKILL.md
|
||
│ │ │ └─ version.json
|
||
│ │ └─ v0002/
|
||
│ ├─ drafts/
|
||
│ │ └─ draft-<id>.json
|
||
│ ├─ reviews/
|
||
│ │ └─ review-<id>.json
|
||
│ └─ archive/
|
||
└─ _index/
|
||
├─ published.json
|
||
├─ drafts.json
|
||
└─ disabled.json
|
||
```
|
||
|
||
`memory/runs/` 这边建议先用:
|
||
|
||
```text
|
||
<workspace>/memory/runs/
|
||
├─ runs.jsonl
|
||
└─ skill-effects.jsonl
|
||
```
|
||
|
||
这样第一版的优点是:
|
||
|
||
1. 容易调试
|
||
2. 容易做 review/publish 流程
|
||
3. 不和 session SQLite 强绑定
|
||
4. 后面真要迁到 SQLite 或对象存储,模型层也不用重写
|
||
|
||
### 5.3.3 第一批核心数据结构
|
||
|
||
第一批数据结构建议严格控制在“运行时必需 + 生命周期必需”,不要先把智能学习策略混进去。
|
||
|
||
1. `SkillSpec`
|
||
- 代表一个稳定的 skill 身份,不代表某个具体正文版本
|
||
- 最少字段:
|
||
- `name`
|
||
- `display_name`
|
||
- `description`
|
||
- `created_at`
|
||
- `updated_at`
|
||
- `current_version`
|
||
- `status`
|
||
- `tags`
|
||
- `owners`
|
||
- `source_kind`
|
||
- `lineage`
|
||
2. `SkillVersion`
|
||
- 代表某个已发布或待发布的具体版本
|
||
- 最少字段:
|
||
- `skill_name`
|
||
- `version`
|
||
- `content_hash`
|
||
- `summary_hash`
|
||
- `created_at`
|
||
- `created_by`
|
||
- `change_reason`
|
||
- `parent_version`
|
||
- `review_state`
|
||
- `frontmatter`
|
||
- `summary`
|
||
- `tool_hints`
|
||
- `provenance`
|
||
3. `SkillDraft`
|
||
- 代表尚未生效的候选修改
|
||
- 最少字段:
|
||
- `draft_id`
|
||
- `skill_name`
|
||
- `base_version`
|
||
- `proposed_content`
|
||
- `proposed_frontmatter`
|
||
- `created_at`
|
||
- `created_by`
|
||
- `trigger_run_id`
|
||
- `trigger_session_id`
|
||
- `reason`
|
||
- `status`
|
||
4. `SkillReviewState`
|
||
- 第一版先用枚举,不急着做复杂状态机
|
||
- 最少值:
|
||
- `draft`
|
||
- `in_review`
|
||
- `approved`
|
||
- `rejected`
|
||
- `published`
|
||
- `disabled`
|
||
- `archived`
|
||
5. `SkillActivationReceipt`
|
||
- 这是 learning loop 的关键 receipt
|
||
- 只要 run 用到了某个 skill,就应落一条 receipt
|
||
- 最少字段:
|
||
- `run_id`
|
||
- `session_id`
|
||
- `skill_name`
|
||
- `skill_version`
|
||
- `content_hash`
|
||
- `activated_at`
|
||
- `activation_reason`
|
||
- `tool_hints`
|
||
6. `RunRecord`
|
||
- 代表一次运行的可学习摘要
|
||
- 最少字段:
|
||
- `run_id`
|
||
- `session_id`
|
||
- `task_id`
|
||
- `attempt_index`
|
||
- `task_text`
|
||
- `started_at`
|
||
- `ended_at`
|
||
- `success`
|
||
- `finish_reason`
|
||
- `validation_result`
|
||
- `feedback`
|
||
- `activated_skills`
|
||
7. `SkillEffectRecord`
|
||
- 连接 `RunRecord` 与 skill version 的效果记录
|
||
- 最少字段:
|
||
- `run_id`
|
||
- `skill_name`
|
||
- `skill_version`
|
||
- `success`
|
||
- `feedback_score`
|
||
- `notes`
|
||
- `created_at`
|
||
8. `SkillPerformanceSnapshot`
|
||
- 是聚合结果,不是原始 receipt
|
||
- 最少字段:
|
||
- `skill_name`
|
||
- `skill_version`
|
||
- `activation_count`
|
||
- `success_count`
|
||
- `failure_count`
|
||
- `latest_used_at`
|
||
- `last_feedback_score`
|
||
9. `SkillLearningCandidate`
|
||
- 描述一个“值得生成 draft”的候选
|
||
- 最少字段:
|
||
- `candidate_id`
|
||
- `kind`
|
||
- `new_skill`
|
||
- `revise_skill`
|
||
- `merge_skills`
|
||
- `retire_skill`
|
||
- `source_run_ids`
|
||
- `source_session_ids`
|
||
- `related_skill_names`
|
||
- `reason`
|
||
- `evidence`
|
||
- `status`
|
||
|
||
### 5.3.4 第一批服务边界
|
||
|
||
第一版服务边界建议保持克制:
|
||
|
||
1. `DraftService`
|
||
- `create_new_skill_draft(...)`
|
||
- `create_revision_draft(...)`
|
||
- `list_drafts(...)`
|
||
- `get_draft(...)`
|
||
2. `ReviewService`
|
||
- `submit_for_review(draft_id, ...)`
|
||
- `approve(draft_id, ...)`
|
||
- `reject(draft_id, ...)`
|
||
3. `SkillPublisher`
|
||
- `publish(draft_id, ...)`
|
||
- `disable(skill_name, ...)`
|
||
- `rollback(skill_name, target_version, ...)`
|
||
4. `RunMemoryStore`
|
||
- `append_run_record(...)`
|
||
- `append_skill_effect(...)`
|
||
- `list_skill_effects(skill_name, version=None, limit=...)`
|
||
5. `SkillLearningStore`
|
||
- `record_learning_candidate(...)`
|
||
- `list_learning_candidates(status=...)`
|
||
- `update_performance_snapshot(...)`
|
||
|
||
### 5.3.5 第一批 runtime 接入点
|
||
|
||
先不要让 learning loop 自己乱改线上 skill。第一批只接这些点:
|
||
|
||
1. `engine/loop.py`
|
||
- run 结束时写 `RunRecord`
|
||
- 对本轮激活 skill 写 `SkillActivationReceipt`
|
||
2. `skills/assembler/task_assembler.py`
|
||
- 输出 skill name 时,尽量能带上当前 version/hash
|
||
3. `skills/catalog/loader.py`
|
||
- 只向 runtime 暴露已发布版本
|
||
- 不默认暴露 draft / rejected / archived
|
||
4. `tools/builtins/skill_view.py`
|
||
- 默认看 published
|
||
- 必要时增加看 draft/review 的管理模式
|
||
|
||
建议把这段 runtime 接入过程明确理解成下面这条树形主链:
|
||
|
||
```text
|
||
用户输入 task
|
||
│
|
||
├─ AgentService._process_with_main_agent(...)
|
||
│ ├─ MainAgentRouter.classify(...)
|
||
│ │ ├─ simple -> 原有单轮回答,不创建 Task
|
||
│ │ └─ task -> 创建或复用内部 Task
|
||
│ └─ TaskService.create_task/get_latest_open_task(...)
|
||
│
|
||
├─ AgentLoop.boot()
|
||
│ └─ EngineLoader.load()
|
||
│ ├─ SessionManager
|
||
│ ├─ MemoryStore
|
||
│ ├─ MemoryService
|
||
│ ├─ RunMemoryStore
|
||
│ ├─ SkillLearningStore
|
||
│ ├─ ToolRegistry
|
||
│ ├─ ToolAssembler
|
||
│ ├─ ToolExecutor
|
||
│ ├─ SkillsLoader
|
||
│ ├─ SkillAssembler
|
||
│ ├─ SkillSpecStore
|
||
│ ├─ DraftService
|
||
│ ├─ ReviewService
|
||
│ ├─ SkillPublisher
|
||
│ ├─ EvidenceSelector
|
||
│ ├─ SkillDraftSynthesizer
|
||
│ ├─ SkillLearningService
|
||
│ ├─ TaskService
|
||
│ ├─ ValidationService
|
||
│ └─ ContextBuilder
|
||
│
|
||
├─ AgentLoop.process_direct(task, task_id, task_mode, attempt_index)
|
||
│ ├─ skill_assembler.assemble(...)
|
||
│ │ └─ 返回带 `skill_name/version/content_hash/tool_hints` 的 activated_skills
|
||
│ │
|
||
│ ├─ 为每个 activated skill 构造 `SkillActivationReceipt`
|
||
│ ├─ sessions.append_message(
|
||
│ │ event_type="skill_activation_snapshotted",
|
||
│ │ hidden,
|
||
│ │ payload={receipts, activation_messages},
|
||
│ │ )
|
||
│ │
|
||
│ ├─ tool_assembler.assemble(...)
|
||
│ ├─ ContextBuilder.build_messages(...)
|
||
│ ├─ provider/chat/tool loop
|
||
│ ├─ sessions.append_message(event_type="run_completed" 或 "run_failed", hidden)
|
||
│ │
|
||
│ └─ AgentLoop._record_run_receipts(...)
|
||
│ ├─ 构造 `RunRecord`
|
||
│ ├─ 构造 `SkillEffectRecord[]`
|
||
│ ├─ 默认只记录 receipts/effects,不生成学习候选
|
||
│ ├─ Task 模式下先只记录 receipts,不立即生成成功学习候选
|
||
│ ├─ 非 Task 模式也只走普通 run receipt 记录
|
||
│ ├─ skill_learning_service.collect_run_receipts(...)
|
||
│ │ ├─ RunMemoryStore.append_run_record(...)
|
||
│ │ ├─ RunMemoryStore.append_skill_effect(...)
|
||
│ │ ├─ SkillLearningService.rescore_skill_versions()
|
||
│ │ │ └─ SkillLearningStore.update_performance_snapshot(...)
|
||
│ │ └─ build_learning_candidates 只在显式门控允许时触发
|
||
│ └─ sessions.append_message(
|
||
│ event_type="skill_effects_snapshotted",
|
||
│ hidden,
|
||
│ payload={run_record, skill_effects, learning_candidates},
|
||
│ )
|
||
│
|
||
├─ ValidationService.validate_task_result(...)
|
||
│ ├─ 生成 `ValidationResult`
|
||
│ ├─ TaskService.record_validation(...)
|
||
│ ├─ RunMemoryStore.update_run_record(validation_result=...)
|
||
│ ├─ sessions.append_message(event_type="task_validation_snapshotted", hidden)
|
||
│ └─ 验证失败时自动重试一次
|
||
│
|
||
└─ /api/chat/feedback
|
||
├─ satisfied + validation accepted -> close Task + build learning candidates
|
||
├─ revise -> needs_revision,下条用户消息复用 Task
|
||
└─ abandon -> abandoned + Failure Memory
|
||
```
|
||
|
||
这里要特别强调:
|
||
|
||
1. `engine/loop.py` 第一版只负责记录 receipts / effects,默认不生成 candidates
|
||
2. 成功学习候选只由 `AgentService.submit_feedback(... satisfied ...)` 在验证通过后触发
|
||
3. `SkillLearningService` 第一版只负责生成候选,不负责自动上线
|
||
4. `SkillDraftSynthesizer` 不应默认跑在 hot path 里,而应由显式后台流程或管理入口触发
|
||
|
||
### 5.3.6 第一批完成标准
|
||
|
||
先不要把“自学习”理解成“自动上线修改”。第一批完成标准只要达到下面这些就够:
|
||
|
||
1. skill 已经不是无版本 Markdown 文件,而是 `SkillSpec + SkillVersion`
|
||
2. runtime 能明确记录“这次 run 用了哪版 skill”
|
||
3. 系统能基于验证通过且用户满意的 Task 结果生成学习候选
|
||
4. draft 必须经过 review/publish 才能进入正式 catalog
|
||
5. rollback/disable 至少有最小实现
|
||
6. published skill catalog 与 draft/review 状态严格隔离
|
||
|
||
最小闭环建议先做成:
|
||
|
||
1. run 结束后记录:
|
||
- 本次激活了哪些 skill
|
||
- skill 版本号/摘要哈希
|
||
- 结果是否成功
|
||
- 自动验证结果
|
||
- 用户反馈
|
||
2. Task 自动验证通过后等待用户点击“满意”
|
||
3. 满意后允许 agent 或后台流程生成 learning candidate / `skill draft`
|
||
4. draft 不直接生效,先进入 review/publish 流程
|
||
5. 只有发布后的 skill version 才进入正式 runtime catalog
|
||
|
||
为什么这一步不能直接排到第一优先级:
|
||
|
||
1. 没有稳定 session / event stream,就没有可靠训练材料
|
||
2. 没有稳定 skill catalog / activation 记录,就不知道“哪版 skill 起了作用”
|
||
3. 没有 review / publish / rollback,就会把自我修改直接变成生产风险
|
||
|
||
为什么这一步又不能被一直拖着不做:
|
||
|
||
1. `skills` 是 Beaver 借 Hermes 的核心目标之一,不只是 prompt 包装
|
||
2. 如果长期只有 `load/select/inject`,那 Beaver 的 `skills` 仍然更像静态文档目录
|
||
3. 后续多 agent、procedure reuse、memory governance 都会反过来依赖 skill 生命周期
|
||
|
||
---
|
||
|
||
## 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_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. `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_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 资源
|
||
|
||
这一阶段也明确了模型配置的归属:
|
||
|
||
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/nanobot`、`backend/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 自己创建 service,则 `await service.start()`
|
||
- Web 层现在已经有最小正式 schema:
|
||
- `WebChatRequest`
|
||
- `WebChatResponse`
|
||
- `WebChatFeedbackRequest`
|
||
- `WebChatFeedbackResponse`
|
||
- `WebStatusResponse`
|
||
- Web 请求处理时:
|
||
- 用结构化 schema 校验输入
|
||
- 只允许走 `await service.submit_direct(...)`
|
||
- 将常见 runtime / config 错误收成明确的 HTTP 层错误
|
||
- 外部注入但尚未进入 running mode 的 service,返回 `503`
|
||
- `/api/chat/feedback`
|
||
- 不暴露 Task 创建/管理 API
|
||
- 只接收 `session_id/run_id/feedback_type/comment`
|
||
- 后端通过 `run_id -> task_id` 找内部 Task
|
||
- 同一 run 的重复同类反馈幂等,不同反馈会被拒绝
|
||
- `/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`
|
||
- 可选接收 `ChannelManager` / channel adapters
|
||
- `ChannelManager` 和 `channels` 参数二选一:
|
||
- 传 `ChannelManager`:外部提前配置好 channel
|
||
- 传 `channels`:gateway 内部创建 `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`
|
||
- old-style 平台字段(如 `chat_id/message_id/thread_id/raw_channel_payload`)只能在 adapter 层映射和保留
|
||
- adapter 负责生成稳定 `session_id`,例如 `telegram:{chat_id}` / `slack:{channel_id}:{thread_ts}`
|
||
- `MemoryChannelAdapter` 只用于本地测试和内嵌接入,不是正式消息 broker
|
||
- WebSocket 是 Web 入口适配层,不是 Gateway channel;真实多渠道仍统一走 `ChannelAdapter -> MessageBus -> AgentService.handle_inbound_message(...)`
|
||
|
||
所以现在已经明确:
|
||
|
||
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`
|
||
|
||
这一阶段的 v1 已完成范围:
|
||
|
||
1. 先支持 local delegation,不引入独立 sub-agent runtime。
|
||
2. `LocalAgentRunner` 调用现有 `AgentLoop.process_direct()` / `submit_direct()`。
|
||
3. sub-agent 通过 `parent_session_id` 建立 session lineage。
|
||
4. sub-agent run 通过父 `task_id` 归入当前主 agent Task。
|
||
5. pinned skills 由主 agent 显式委派,sub-agent 必须注入。
|
||
6. open skills 继续复用现有 `SkillAssembler`。
|
||
|
||
完成标准:
|
||
|
||
1. 主 agent 的当前 Task 可以包住 team run。
|
||
2. 子 agent 与主 agent 复用同一个 `AgentLoop` 主链。
|
||
3. 子 agent 不拥有独立 task store、独立 skill learning store、独立 runtime。
|
||
4. sub-agent run receipt 自然进入主 Task 的学习门控。
|
||
5. 学习候选仍必须等验证通过 + 用户满意,不因 team run 自动生成。
|
||
|
||
---
|
||
|
||
## 8. 第五施工阶段:接回群组讨论和流程化 team
|
||
|
||
这阶段已经先落地 Beaver 自己的 Agent Team v1,不再直接回接旧 `third_party/swarms` runtime。
|
||
|
||
### 8.1 已落地的 team core
|
||
|
||
已实现:
|
||
|
||
1. `beaver/coordinator/models.py`
|
||
- `AgentDescriptor`
|
||
- `DelegationEnvelope`
|
||
- `ExecutionNode`
|
||
- `ExecutionGraph`
|
||
- `NodeRunResult`
|
||
- `TeamRunResult`
|
||
2. `beaver/coordinator/local.py`
|
||
- `LocalAgentRunner`
|
||
- sub-agent 复用主 `AgentLoop.process_direct()` / `submit_direct()`
|
||
- 禁止 `provider_bundle + node model/provider_name` 静默混用
|
||
3. `beaver/coordinator/execution/scheduler.py`
|
||
- `TeamGraphScheduler`
|
||
- 支持 `sequence / parallel / dag`
|
||
- 同层节点保持真并发
|
||
- 节点级异常归一成 `NodeRunResult`
|
||
- summary 只聚合成功输出,并列出 `Failed nodes`
|
||
4. `beaver/services/team_service.py`
|
||
- `TeamService.run_team(...)`
|
||
- 执行前校验 `parent_task_id`
|
||
- 执行后把 sub-agent `run_ids` 回填父 Task
|
||
|
||
### 8.2 当前 v1 策略边界
|
||
|
||
当前只实现三个执行原语:
|
||
|
||
1. `sequence`
|
||
- 前一个成功节点输出进入下一个节点 dependency context。
|
||
2. `parallel`
|
||
- 同层节点并发执行。
|
||
- 每个节点可通过 `provider_bundle_factory(node)` 拿 fresh provider bundle。
|
||
3. `dag`
|
||
- 按依赖拓扑分批执行。
|
||
- 依赖失败节点的后续节点标记为 `blocked`。
|
||
|
||
以下策略只预留枚举,不在 v1 实现完整行为:
|
||
|
||
1. `moa`
|
||
2. `hierarchy`
|
||
3. `heavy`
|
||
4. `group_chat`
|
||
5. `forest`
|
||
6. `maker`
|
||
7. `router`
|
||
|
||
### 8.3 swarms 的新定位
|
||
|
||
注意:
|
||
|
||
1. 不再引入 `third_party/`。
|
||
2. 不再允许旧式 `sys.path` 注入。
|
||
3. v1 不依赖 `swarms` runtime。
|
||
4. swarms 的架构形态只作为策略参考,后续高级 preset 可以生成 Beaver `ExecutionGraph` 或 step loop。
|
||
5. 如果以后确实要接 swarms,也必须作为 adapter/backend,而不是平台内部结构。
|
||
|
||
### 8.4 当前 Task 内部 team 融合状态
|
||
|
||
已经实现:
|
||
|
||
1. `AgentService` 在 Task mode 内部按需调用 `TeamService`。
|
||
2. `TaskExecutionPlanner` 通过 LLM JSON 规划 `single / team`。
|
||
3. team 输出不直接面向用户,而是注入主 Agent synthesis run。
|
||
4. `ValidationService` 可接收 `team_summaries` 辅助验证最终结果。
|
||
5. 最小 observability 已落地为隐藏 session events,但不新增独立 team task store。
|
||
|
||
后续仍要做:
|
||
|
||
1. 将 `moa / hierarchy / heavy / group_chat / forest / maker / router` 作为 strategy preset 编译成 `ExecutionGraph` 或 step loop。
|
||
2. 增加更清晰的 agent registry / target resolver。
|
||
3. 补产品级过程视图,让前端能展示 Task 内部 team 规划和 sub-agent 执行过程。
|
||
|
||
这一阶段完成后,才算真正恢复:
|
||
|
||
1. 群组讨论。
|
||
2. 高级 swarms 风格策略。
|
||
3. skills 约束下的多 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` 接入改动
|
||
|
||
### 提交 6:Main Agent 自动 Task 化与反馈验证闭环
|
||
|
||
文件:
|
||
|
||
1. `beaver/tasks/models.py`
|
||
2. `beaver/tasks/store.py`
|
||
3. `beaver/tasks/service.py`
|
||
4. `beaver/tasks/router.py`
|
||
5. `beaver/tasks/validation.py`
|
||
6. `beaver/services/agent_service.py`
|
||
7. `beaver/engine/loop.py`
|
||
8. `beaver/engine/session/*`
|
||
9. `beaver/interfaces/web/app.py`
|
||
10. `beaver/interfaces/web/schemas/chat.py`
|
||
11. `frontend/app/(app)/page.tsx`
|
||
12. `frontend/components/chat-workbench/MessageList.tsx`
|
||
13. `frontend/lib/api.ts`
|
||
14. `frontend/lib/store.ts`
|
||
15. `frontend/types/index.ts`
|
||
|
||
目标:
|
||
|
||
1. 聊天入口自动判断 simple / task。
|
||
2. 不提供显式 Task 创建 API。
|
||
3. Task 模式自动验证并失败重试一次。
|
||
4. 用户反馈决定 Task close / revise / abandon。
|
||
5. 成功学习候选必须由“验证通过 + 用户满意”触发。
|
||
|
||
### 提交 7:Agent Team v1 轻量 Coordinator
|
||
|
||
文件:
|
||
|
||
1. `beaver/coordinator/models.py`
|
||
2. `beaver/coordinator/local.py`
|
||
3. `beaver/coordinator/execution/scheduler.py`
|
||
4. `beaver/services/team_service.py`
|
||
5. `beaver/engine/loop.py`
|
||
6. `beaver/services/memory_service.py`
|
||
7. `tests/unit/test_agent_team_v1.py`
|
||
|
||
目标:
|
||
|
||
1. 定义 Beaver 自己的 team execution models。
|
||
2. sub-agent 复用主 `AgentLoop.process_direct()` / `submit_direct()`。
|
||
3. 支持 `sequence / parallel / dag`。
|
||
4. `parallel` / DAG 同层节点保持真并发。
|
||
5. 每个 run 使用独立 memory snapshot。
|
||
6. 支持 pinned skill 继承和 open skill assembly。
|
||
7. 支持 per-node provider bundle factory。
|
||
8. parent Task 前置校验,sub-agent run_ids 回填父 Task。
|
||
9. 节点异常归一成 `NodeRunResult`,不炸掉整次 team run。
|
||
10. summary 只聚合成功输出,并清晰列出失败节点。
|
||
|
||
### 提交 8:Agent Team 与 Task mode 执行策略融合
|
||
|
||
文件:
|
||
|
||
1. `beaver/tasks/planner.py`
|
||
2. `beaver/services/agent_service.py`
|
||
3. `beaver/engine/loader.py`
|
||
4. `beaver/tasks/validation.py`
|
||
5. `beaver/coordinator/local.py`
|
||
6. `tests/unit/test_task_execution_planner.py`
|
||
7. `tests/unit/test_task_mode_feedback.py`
|
||
|
||
目标:
|
||
|
||
1. Task mode 每个 attempt 先规划 `single / team`。
|
||
2. planner 只接受 `sequence / parallel / dag`,异常或非法 graph 降级 `single`。
|
||
3. team run 使用 `TeamService.run_team(...)`,并归入父 Task。
|
||
4. team 输出注入主 Agent synthesis run,不直接返回用户。
|
||
5. 最终仍只围绕主 Agent synthesis run 做验证、反馈和学习门控。
|
||
6. running mode 下 sub-agent 通过 `AgentLoop.submit_direct()` 执行,direct mode 下继续用 `process_direct()`。
|
||
7. 隐藏事件记录规划和 team 执行结果。
|
||
|
||
---
|
||
|
||
## 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 条没过,不要进入下一阶段。
|
||
|
||
当前 Main Agent / Task 闭环还应额外验收:
|
||
|
||
1. 简单问题不创建 Task。
|
||
2. 复杂请求自动创建 Task。
|
||
3. 同 session 的修订反馈会复用未关闭 Task。
|
||
4. Task run 完成后必定写 `task_validation_snapshotted`。
|
||
5. 验证失败自动重试一次。
|
||
6. 首次失败草稿不会留在可见上下文。
|
||
7. `/api/chat/feedback` 能通过 `run_id` 找到内部 Task。
|
||
8. 同一 run 的重复同类反馈幂等,冲突反馈拒绝。
|
||
9. `satisfied` 只有在验证通过后触发成功学习候选。
|
||
10. `abandon` 写 Failure Memory,不生成成功 Skill draft。
|
||
11. 前端最新 assistant Task 结果显示反馈按钮。
|
||
12. WebSocket 和 REST 路径都能保留 `run_id/task_id/validation_result`。
|
||
|
||
当前 Agent Team v1 还应额外验收:
|
||
|
||
1. `LocalAgentRunner` 复用主 `AgentLoop.process_direct()` / `submit_direct()`。
|
||
2. pinned skill 能注入 sub-agent context。
|
||
3. `sequence` 能传递上游输出。
|
||
4. `parallel` 多节点能真并发执行。
|
||
5. `dag` 遵守依赖,失败节点阻断下游。
|
||
6. parent Task 不存在或 session 不匹配时,执行前拒绝。
|
||
7. valid parent Task 会回填 sub-agent `run_ids`。
|
||
8. provider factory 节点异常会归一成失败节点,不取消其它节点。
|
||
9. `provider_bundle + node model/provider_name` 不会被静默忽略。
|
||
10. summary 不把失败输出混入成功摘要。
|
||
11. direct run 和 team run 默认只写 receipts/effects,不生成 learning candidates。
|
||
12. Task mode team plan 会先产生 sub-agent runs,再产生主 Agent synthesis run。
|
||
13. 父 Task 的 `run_ids` 同时包含 sub-agent runs 和主 Agent synthesis run。
|
||
14. team summary 进入主 Agent execution context,而不是直接作为用户最终回答。
|
||
15. team 节点失败时仍由主 Agent synthesis 生成最终回答。
|
||
16. 验证失败重试时会重新规划,并隐藏第一次主 Agent synthesis 草稿。
|
||
|
||
当前 Task Skill Resolver / Process / Learning Pipeline 还应额外验收:
|
||
|
||
1. planner team JSON 支持 `skill_query / required_capabilities`,不要求 agent role。
|
||
2. `TaskSkillResolver` 命中 published skill 时,写入 `ExecutionNode.inherited_pinned_skills`。
|
||
3. sub-agent run 的 published pinned skill receipt 记录 `activation_reason=pinned_delegation`。
|
||
4. 未命中 skill 时创建 ephemeral guidance,并写入 `ExecutionNode.inherited_pinned_skill_contexts`。
|
||
5. ephemeral guidance receipt 记录 `activation_reason=ephemeral_guidance`。
|
||
6. ephemeral guidance 不写入 draft store,不自动 approve/publish,不进入 runtime skill catalog。
|
||
7. plan event 写入 `skill_queries / selected_skill_names / ephemeral_guidance_ids / skill_resolution_report`。
|
||
8. `/api/sessions/{session_id}/process` 能把隐藏 Task/team/validation 事件投影成 `processRuns / processEvents`。
|
||
9. ChatWorkbench 桌面端有 `ProcessLane`,移动端有 `Process` tab。
|
||
10. process view 展示 selected skills、ephemeral guidance id、ephemeral skill used,不展示 specialist agent selection。
|
||
11. team 部分失败时,process view 显示失败节点,但最终回答仍来自主 Agent。
|
||
12. `SkillLearningPipelineService` 能串起 candidate -> draft -> safety/eval -> review -> approve/reject -> publish。
|
||
13. rejected draft 不能 publish。
|
||
14. draft 在 publish 前不能进入 runtime skill catalog。
|
||
15. publish 必须要求 approved review + safety passed + eval not failed;high risk 需要显式确认。
|
||
16. rollback / disable 必须通过 publisher 写入 skill spec,而不是直接改 Markdown。
|
||
17. 后端全量单测应通过:`uv run pytest`。
|
||
18. 前端至少通过:`npm run typecheck`、`npm test`、`npm run lint`。
|
||
|
||
---
|
||
|
||
## 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。
|