跳转到内容

消息模型与跨轮记忆

跨轮记忆的真相源是持久化的会话消息,而不是某一轮 Processor 内存里的临时变量。Agent Forge 用 Message(一条用户或助手消息)加 Part(消息内的片段)描述一次完整对话;流式过程中的增量先进入事件总线,再投影到存储,供下一轮调度与 UI 订阅共用。

Message 携带角色、父子关系、模型与结束态等元信息。Part 是消息体内的片段:一条 Message 可含多个 Part。

Part 类型含义
text自然语言(含合成提示)
tool工具调用过程与结果
reasoning推理过程文本
file / agent附件、@agent 引用
compaction / subtask压缩任务、子任务标记
step-start / step-finish单步边界、token、快照
patch本步文件变更摘要

Message 是信封,Part 是信封内的段落与附件。排障时按 Part 类型定位:看不到增量多半是 text/reasoning 的 delta 未到客户端;工具卡住则看 tool Part 的状态迁移。

会话更新走统一写入口:更新 Message 或 Part 后发出同步事件,由投影层写入存储(同一条记录冲突时覆盖更新)。Processor 在模型流过程中持续更新 Part;部分 delta 只进总线、不每次整行落库,完整块在 text-end 等边界落盘。

flowchart LR
Up[updateMessage / updatePart] --> Ev[同步事件]
Ev --> Proj[投影写入]
Proj --> Store[(session / message / part)]
Ev -.-> Bus[Bus]
Bus --> SSE[SSE 客户端]

并非每个流事件都值得写库。Processor 按内容是否已形成完整块分流:

流事件写入方式原因
text-start / reasoning-start新建空 Part,整行落库订阅端要先知道这一段存在
text-delta / reasoning-delta只发总线增量逐字写库带来大量写放大;订阅端自行拼接片段
text-end / reasoning-end整行落库(含去尾空白与插件后处理)边界处定稿,之后读到的正文是完整块
tool 状态迁移(pending → running → completed / error)整行落库参数、输出、错误需可回放
step-start / step-finish整行落库(含快照、用量、成本)计费与排障锚点,必须持久
patch整行落库本步文件变更摘要

工具入参的逐段 delta 不落库也不广播,只保留事件;工具结果仍然整行写入。

这套切分的代价是:如果在两次边界之间进程中断,最后一段未收尾的正文可能只存在于总线里。收尾阶段会闭合未完成的 text / reasoning 与工具状态,把已确认内容补成完整块。复核时如果只轮询数据库,看不到逐字增量;要看逐字效果,需要订阅 SSE 总线。

存储形态:会话元信息、消息行、部件行。默认可用 SQLite;配置 Postgres 时仍是同一消息模型,并带租户隔离。客户端不必区分方言,只要认 Message / Part 语义。

全量历史可通过分页游标读完整个会话(每页有上限,有更多则继续),不是“永远只留最近 50 条”。送入模型前,runLoop 使用 filterCompacted 得到紧凑视图:从新到旧扫描,命中“带成功摘要的助手消息,且其父用户消息带 compaction Part”的边界后截断更老原文,再反转为旧→新顺序。

flowchart TD
All[会话全量消息] --> Page[分页读完]
Page --> Filter[filterCompacted]
Filter --> Bound{命中压缩边界?}
Bound -->|是| Cut[丢弃边界前原文]
Bound -->|否| Keep[保留可见历史]
Cut --> View[送模视图]
Keep --> View

大窗口仍可能很大;压缩边界决定模型“看不见”多老的原文。摘要本身留在边界附近,语义可延续。细节见runLoop 与上下文压缩。

层级生命周期作用
Processor 上下文单轮 process 内当前 tool-call、文本缓冲、是否需要压缩
Message / Part 存储跨轮持久真正的记忆体与排障时间线
runLoop 局部变量单次 runLoop步数、结构化输出暂存等

每一轮重新从存储做 filterCompacted,不把上一轮内存上下文当作跨轮真相。因此进程重启或多副本切换后,只要存储可读,会话仍可续跑。

  • 流式 UI:在 POST 之前建立事件订阅;仅依赖同步 HTTP 响应只会看到整包结果。见HTTP 流式与 SSE。
  • 历史是否“丢了”:先分清是存储里没有,还是被 filterCompacted 从送模视图拿掉。前者查写入与投影;后者查压缩边界与摘要消息。

相关阅读: