WeaveAgentDesign
WeaveAgent 编排架构设计
本文是
Agent/一种理想的智能体编排架构.md的工程化落地设计,对应骨架代码Agent/WeaveAgent.py。经 Q1–Q22 的逐项确认汇总而成。
1. 核心概念与层级关系
- Frame:重量级思考 / 工作单元。每个 Frame 拥有独立的 Session,是思维树的节点。
- Task:Frame 内的轻量 TODO 项,不是 Frame 的特例,是两个独立概念。
- 根 Frame:
parent=None的普通 Frame,结构上与其他 Frame 完全一致。其"根性"只体现在 parent 字段上,type照常取EXPLORE / FIXED / THINK之一。 - 思维树:所有 Frame 按父子关系构成的树,根 Frame 是唯一根。
Task 与 SubFrame 的区别
| 维度 | Task(轻量) | SubFrame(重量) |
|---|---|---|
| 上下文 | 共享所在 Frame 的 Session | 自己独立的 Session |
| 场景 | 顺手做、顺序推进的步骤 | 需要独立思考上下文的工作 |
| 字段 | describe, status, result, method, origin |
完整的 Frame 六块字段 |
| 创建 | LLM 输出 add_task 动作 |
LLM 输出 spawn_subframe 动作 |
| 转化 | 不会升级成 SubFrame,两条路径平行 | — |
Task 发现实际过复杂、需要独立上下文时的约定:不做升格机制。LLM 在同一次 Next() 里连续发两个动作:(1) 把该 Task 状态置
SKIP并在result写下转移原因;(2)spawn_subframe新建子 Frame 承接这件事。两个动作没有数据依赖,但顺序保留 LLM 意图痕迹。
Task 的 status:PENDING / RUNNING / DONE / SKIP。
2. Frame 的字段六块
- 身份 —
id, type, status, parent - 配置 / 人格层 Prompt(创建时基本静态)
system_msg, next_guide, summary_guide, reflect_guide - 意图上下文 —
origin, goal, background, planorigin:本 Frame 创建原因(并入原pre_msg的"创建原因")background:逻辑关系 / 依赖前置信息(并入原pre_msg的"逻辑关系")goal:预期目标 / 预期效果(并入原pre_msg的"预期效果")
- 执行状态 —
log, result, summary, logic_trace - 子结构 —
task_list, sub_frame_list - 本地资源 —
session、临时记忆、长时记忆引用、公共存储引用、ToolRegistry 引用 (Frame 不持有本地 tools,tools 全局)
额外:父→子通道 inbox(Message 队列)。
3. 思维树的组织
父子交互
- 子 → 父:被动式
子 Frame 结束时只更新自身
status + result,不直接调用父的方法。父 Frame 的唤醒由调度器通过SUBFRAME_DONE事件触发(见 §7),父被唤醒后在Next()里轮询sub_frame_list获取子的最终状态。换言之,"被动式"指的是代码层无直接回调,事件总线替子做了通知。 - 父 → 子:inbox 激励
父 Frame 调
child.Stimulate(msg),消息入子 Frame 的inbox队列;子 Frame 被唤醒后下一次Next()从 inbox 取出合并处理。- Stimulate 推入的
Message类型为USER——MessageType.USER语义扩展为"来自本 Frame 外部的驱动性输入",同时覆盖外部用户推入根 Frame 与父 Frame 推入子 Frame 两种情况。
- Stimulate 推入的
- 父子松耦合,通过事件记录器联动(见 §7)。
临时记忆可见性
- 每个 Frame 独占一份短时记忆。
- 父 Frame 能读取直接子 Frame 的短时记忆快照(通过
GetChildMemorySnapshot)。 - 子 Frame 看不到父 Frame 的短时记忆。子 Frame 创建时通过
origin / background / goal拿到静态"简报",运行期与父完全隔离。
4. Frame 状态机
PENDING → RUNNING ↔ BLOCKED → REFLECTING → DONE / FAILED
状态集:PENDING / RUNNING / BLOCKED / REFLECTING / DONE / FAILED。
REFLECTING 是终结前的必经阶段:成功与失败都经过它,DONE 与 FAILED 只能从 REFLECTING 进入,不允许从 RUNNING / BLOCKED 直接跳到终结态。
- 成功路径:
RUNNING → REFLECTING(提取方法学 / 总结收获)→ DONE - 失败路径:
RUNNING → REFLECTING(提取教训 / 错误原因)→ FAILED
REFLECTING 严格单向:只能去 DONE / FAILED,不允许回 RUNNING。若反思后发现方向错误要继续干,按"失败 + 父 spawn 新 Frame"模式处理——走 REFLECTING → FAILED,在 summary / result 里写清教训,父 Frame 被 SUBFRAME_DONE 唤醒后读到子的产出,决定新方向并 spawn 新 SubFrame。
REFLECTING 阶段的 Guide 段会根据 LLM 声明的最终 status(DONE 或 FAILED)注入不同的自洽要求:成功强调"忠于历史、可泛化成 skill",失败强调"错误根因清晰、可作为下次的先验"。
转移驱动:LLM 在 Next() 的结构化输出里显式声明 next_status,本地代码(_handle_declare_status)校验合法性后 apply。
- 被动类(BLOCKED 等待子/等待 inbox)也可由本地代码根据外部条件直接判断。
- LLM 声明类(REFLECTING / DONE / FAILED)一定经过本地校验,非法转移(如 RUNNING→DONE 越过 REFLECTING)会被拒绝。
5. Frame.Next() 的职责
每次被调度器唤醒时执行:
- 读状态:status / task_list / sub_frame_list / logic_trace / plan / inbox …
- 本地动作筛选:按当前状态挑出本轮允许 LLM 选择的动作子集 (不是把全部 20+ 动作盲选交给 LLM)。
- 组装分层 prompt → 调 LLM → 拿结构化输出
- 派发动作:
- Frame 自处理:仅改 Frame 自身的结构性骨架(
add_task / update_task / spawn_subframe / update_plan / append_log / declare_status) - Tool 处理:一切记忆类与外部资源类操作,包括短时记忆、长时记忆、公共存储、文件、skill 存取等(
memory/short/*,memory/long/*,public/*,file/*,skill/*…)
- Frame 自处理:仅改 Frame 自身的结构性骨架(
动作边界原则:Frame 自处理动作只修改 Frame 的结构性字段(状态、任务、子 Frame、计划、日志、逻辑链);所有记忆读写统一经 Tool(包括短时记忆),以便 LLM 只学一套记忆接口风格。
logic_trace由本地自动从每轮reasoning追加,不需要显式动作。
关于 Rollback:没有独立的 rollback 动作。思维树上"回退 / 走错方向"通过 Frame 终结机制实现:当前 Frame 的 LLM 判定方向错误 → 在
summary / result里写下错误原因(作为教训)→ 声明next_status=FAILED→ Frame 结束 → 父 Frame 下次 Next() 轮询到子 FAILED + 错误记录 → 基于教训 spawn 新 SubFrame 走不同方向。Frame 内无真正的状态回滚。
5.1 LLM 输出 Schema
每次 Frame.Next() 调 LLM 的结构化返回:
{
"reasoning": "当前子任务已完成,我应该先把结果总结到 summary,再进入 REFLECTING。",
"actions": [
{"type": "append_log", "args": {"text": "..."}},
{"type": "update_plan", "args": {"plan": "..."}}
],
"next_status": "REFLECTING"
}
字段语义:
reasoning(必填):自然语言思维链,本地自动附加到logic_trace(带时间戳)。没有独立的record_logic动作——logic_trace 的唯一来源就是每轮 reasoning。actions(必填,可为空):有序,动作间无互相依赖(不支持引用前序返回值);任一失败即中断,错误写回 logic_trace,下一轮 Next() 时 LLM 可见并纠正。next_status(可选):省略或 null 则保持当前;否则经_handle_declare_status校验转移合法性。
6. 分层 Prompt 组装
[System 段]
1. system_msg — 人格(由 Persona 渲染)+ 自我 + 通用自洽
2. 本轮动作集说明 + 可用 Tool 列表 + 结构化输出 schema
[Context 段]
3. 意图上下文 — origin(创建原因)/ background(逻辑关系/依赖)/ goal(预期效果)/ plan
4. 状态快照 — status / task_list 简表 / sub_frame_list 简表
5. logic_trace 摘要 — 思维链路(可压缩)
6. 父 inbox 新消息 — 如有
7. 命中记忆 / skill — 最近检索到的相关长时记忆条目
[Working 段]
8. 本 Frame 的短时记忆(经 memory/short Tool 快照展开)
9. Session 历史消息
[Guide 段]
10. next_guide / summary_guide / reflect_guide 之一或组合
+ 按动作类型定制的自洽要求片段
+ REFLECTING 阶段按最终 status(DONE / FAILED)切换不同片段
自洽没有独立模块,按动作类型在 Guide 段注入定制片段(总结 / 创建子任务 / 回退 等各有各的自洽标准)。
7. 事件驱动调度
不采用周期性 tick。改为事件记录器 + 就绪队列。
事件类型(6 类)
| # | 事件 | 触发时机 |
|---|---|---|
| 1 | INBOX_NEW |
父 Frame 调用 child.Stimulate(msg) |
| 2 | SUBFRAME_DONE |
子 Frame 进入 DONE / FAILED,标脏父 Frame |
| 3 | TASK_CHANGE |
异步 Tool 完成后更新某个 task 状态并唤醒其所属 Frame(只有异步路径触发;Frame 在 Next() 内同步完成的 task 推进不发此事件) |
| 4 | EXTERNAL_INPUT |
用户输入推到根 Frame 的 inbox |
| 5 | TIMER |
Frame 主动请求 N 秒后再唤醒(ScheduleTimer) |
| 6 | CREATED |
Frame 创建后首次入队自检 |
调度流程
事件发生 → EventLog.Log(event)
→ Scheduler.OnEvent(event) # 标脏目标 Frame
→ ready queue 入队
→ Scheduler.Tick() # 主循环取一个 Frame
→ frame.Next()
所有跨 Frame 的状态变化都统一成事件,可回溯可追踪。
7.1 并发模型
并发隔离完全依赖树结构,不做资源声明、不做子树互斥标记。
天然隔离点:
- 兄弟 Frame 彼此不可见(短时记忆天然隔离),跨兄弟协调必须经父 Frame 中转。
- 父读子的短时记忆快照(只读 snapshot),父通过
Stimulate推消息到子的 inbox 队列——无实时共享内存。 - 子 Frame 的生命周期由父 Frame 控制:创建 / Stimulate / 感知 SUBFRAME_DONE 都是父 Frame 的行为。
- 全局存储(长时记忆 / 公共存储)的写并发由存储层内部加锁,不由调度器介入。
调度器只做两件事:
- per-Frame 锁:同一 Frame 不允许并发
Next()。就绪队列用OrderedDict[frame_id, None]实现——同一 Frame 被多事件标脏时仅入队一次(O(1) 去重 + 保持首次入队的 FIFO 顺序)。 - 全局并发上限 N:worker 池,
N个 Frame 可同时在Next()中。
Tick 时的 Frame 上下文注入:调度器从 ready 取 frame_id 后、调 frame.Next() 前,设置一个 async 上下文变量 current_frame = frame。Tool Call 在执行期可从该上下文拿到当前 Frame 引用,以定位 frame.short_memory 等 Frame 级资源——这是"短时记忆物理独占 + 接口统一经 Tool"组合的关键衔接点。
不需要:资源声明、子树互斥标记、Frame 预知自己写哪些 key。
7.2 定时器实现
调度器跑在 asyncio event loop 中:
ScheduleTimer(frame_id, delay)≈loop.call_later(delay, lambda: self.OnEvent(TIMER_event))- worker 池用
asyncio.Semaphore控制并发上限 N - LLM 调用使用 async HTTP 客户端
- 整个系统 async 化,零额外依赖
8. 记忆与存储
| 存储 | 归属 | 实现 | 访问路径 |
|---|---|---|---|
| 短时记忆 | 每个 Frame 独占(Frame.short_memory 字段) |
通用 Memory 接口(CRUD) | 统一经 Tool(memory/short/*)。Tool 执行时从 async 上下文变量 current_frame 定位到本 Frame 的字段。父可读直接子的 Snapshot(GetChildMemorySnapshot(child_id)) |
| 长时记忆 | Agent 全局单例 | 基于关键词树的知识库(见 Agent/基于关键词的知识树系统设计方案.md) |
通过 Tool(memory/long/search, memory/long/create_keyword, memory/long/create_info, memory/long/link_info…)访问 |
| 公共存储 | Agent 全局单例 | 简单 KV | 通过 Tool(public/get, public/set)访问 |
长时记忆不是通用 KV,是"关键词树节点 + Info + 多对多关联"的结构化知识库。LongTermMemory 类是该库的薄封装。主要 API:Search, GetInfosOfKeyword, CreateKeyword, CreateInfo, LinkInfo, MoveKeyword, MergeKeywords, DeleteKeyword 等(完整见库设计文档)。无"热度排序"概念,组织靠关键词树本身。
9. Tool 与 Skill 的组织
Tool
- 全局
ToolRegistry,挂在 Agent 上,分层路径组织(memory/*,frame/*,file/*,skill/*…)。 - Frame 不持有本地 tools 实例,但持有全局 Registry 引用。
Frame.Next()在 prompt 组装阶段按当前状态 / 动作集按路径前缀拉取可见工具子集,注入 System 段。
Skill
- 不独立建模。Skill 就是长时记忆(关键词树知识库)里的一类
Info。 - 挂载约定:所有 Skill Info 挂在固定关键词子树下(如
root/methodology/skill),按领域可细分到skill/memory,skill/coding等子节点。 - 存:
abstract_to_skill动作调用LongTermMemory.SaveSkill(content, category),内部CreateInfo + LinkInfo到对应 skill 关键词节点。 - 取:
LongTermMemory.FindSkills(category)或GetInfosOfKeyword("skill/<category>")检索。 - 用:LLM 在动作里给出
skill_info_id,本地代码把该 skill 的 prompt 模板拼进下一轮 prompt,或把其内容作为新 SubFrame 的background简报。 - 演化:调用
UpdateInfo改对应 Info 条目。
10. 人格与自洽
- 人格:静态
Persona,由Persona.RenderSystemPrompt()渲染为文本,作为所有 Frame 的system_msg的一部分注入。人格不在运行期动态调度。 - 自洽:没有独立检查模块;按当前动作类型在 prompt 的 Guide 段注入定制化的自洽要求(总结任务要求忠于历史、创建子任务要求目标可落地、回退要求原因清晰等)。
11. Agent 角色
Agent 是全局容器:
- 持有
Persona/ 根 Frame / 三大全局存储(LongTermMemory、PublicStore、ToolRegistry) - 持有
EventLog+Scheduler - 持有全局 Frame id 计数器与
id → Frame注册表;所有 Frame 创建统一通过Agent.CreateFrame(...)入口注入自增 id 并注册,Frame.__init__不接受 id 参数 - 构造签名:
Agent(persona, root_goal="", root_origin="", root_background="")。Agent.__init__内部调用self.CreateFrame(parent=None, goal=root_goal, origin=root_origin, background=root_background)生成根 Frame——根 Frame 走同一构造路径,无特例。root_goal / root_origin / root_background允许在构造 Agent 时指定根 Frame 的初始意图(如"做个通用助手、维持自我意识",对应理念稿 §4.5.1)。 - 对外入口:
SubmitExternalInput(content)(推到根 Frame inbox 并发EXTERNAL_INPUT事件)、CreateFrame(...)(Frame 构造唯一入口)、Run()(启动调度)、FindFrame(id)
Frame 生命周期 / 资源清理策略:Frame 进入 DONE / FAILED 后不自动清理,其 session / short_memory / inbox / logic_trace 等资源保留至 Agent 停机。理由:父 Frame 可能在任意时刻回读子的 summary / result / 短时记忆快照作为决策输入;思维树作为可回溯的历史本身也有价值。长时运行场景下的压缩策略留待实现阶段按需引入,本设计不做预优化。
12. 对应代码结构
骨架文件:Agent/WeaveAgent.py。
主要类:
Message / MessageType / SessionMemory / ShortTermMemory / LongTermMemory / PublicStoreTool / ToolRegistryTask / TaskStatusFrame / FrameType / FrameStatusPersonaEvent / EventType / EventLog / SchedulerAgent
文件目前只含字段与方法签名,所有方法体为 ...;实现阶段按本文档逐项落地。
13. 已关闭的历史议题
以下项曾作为待定列出,已在后续讨论中收敛为明确决策(汇总记录):
| 议题 | 结论 | 参考 |
|---|---|---|
| LLM 输出 schema | reasoning + actions[] + next_status 三字段,动作间无依赖 |
§5.1 |
| Rollback 语义 | 不做状态回滚;改为"Frame 终结 + 父重试" | §5 末尾提示 |
| 并发模型 | 隔离靠树结构;调度器只做 per-Frame 锁 + 全局并发上限 N | §7.1 |
| 定时器实现 | asyncio event loop + loop.call_later |
§7.2 |
| 短时记忆遗忘 | Frame 生命期内不自动遗忘,REFLECTING 阶段筛选写长时 | §8 |
| 长时记忆热度 | 不适用,关键词树库不使用热度概念 | §8 |
| Task 动态升格 | 不做升格机制;约定 SKIP + spawn_subframe 双动作组合 | §1 末尾提示 |
| REFLECTING 必经 | DONE / FAILED 只能从 REFLECTING 进入,成败都反思 | §4 |
| REFLECTING 回退 | 严格单向,禁止 REFLECTING→RUNNING;要继续干走 FAILED + 父 spawn 新 Frame | §4 |
| TASK_CHANGE 范围 | 仅异步 Tool 完成时触发,同步路径不发 | §7 |
| FrameType.ROOT | 去除;根 Frame 由 parent is None 判定 |
§1 |
| pre_msg 字段 | 并入 origin / background / goal,不再单列 |
§2 |
| 短时记忆操作 | 走 Tool(memory/short/*),不作为 Frame 自处理动作;Tool 经 async 上下文 current_frame 定位 |
§5 / §7.1 / §8 |
| record_logic 动作 | 去除;logic_trace 由每轮 reasoning 自动追加 | §5 / §5.1 |
| Frame id 分配 | Agent 全局自增 + 统一 Agent.CreateFrame(...) 工厂 |
§11 |
| Message 类型 | 父→子 Stimulate 用 USER,语义扩展为"外部驱动输入" |
§3 |
| 就绪队列去重 | OrderedDict[frame_id, None],FIFO + O(1) 去重 |
§7.1 |
| 根 Frame 构造 | 经 Agent.__init__(persona, root_goal, root_origin, root_background) → CreateFrame(parent=None, ...),无特例 |
§11 |
| Frame 终结清理 | 不自动清理;资源保留至 Agent 停机 | §11 |
| result vs summary | result 对外事实产出(父可读),summary 对内反思抽象(面向长时记忆) |
§2 |
| Tool 字段命名 | Tool.describe 更名为 Tool.description |
— |
| 长时记忆 LLM 参数 | use_agent → llm_expand_query,use_agent_for_parent → llm_auto_place |
— |
No comments to display
No comments to display