Files
beaver_project/app-instance/backend/施工指南.md
steven_li 30ab74ffb2 feat(engine): 添加MCP连接管理和工具集成功能
- 集成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方法重新构建全文搜索索引
- 优化索引触发器和表的维护流程
2026-05-14 09:43:48 +08:00

2247 lines
72 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Beaver Backend 施工指南
这份文档不是蓝图,也不是迁移映射,而是“真正开始施工时怎么下手”的执行指南。
目标是:**按运行时主链路,一步一步把 `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 reportcritical/safety failed 直接阻断
- eval failed 阻断 publishprovider 不可用时记录 `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. 不先定 registryfactory 就会出现 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. 第一批真正建议开工的文件
如果现在立刻开始干,建议按下面顺序提交,不要跳。
### 提交 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`
3. `beaver/engine/providers/base.py`
4. `beaver/engine/providers/registry.py`
5. `beaver/engine/providers/factory.py`
6. 至少一个真实 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` 接入改动
### 提交 6Main 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. 成功学习候选必须由“验证通过 + 用户满意”触发。
### 提交 7Agent 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 只聚合成功输出,并清晰列出失败节点。
### 提交 8Agent 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 failedhigh 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 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。