消息模型与跨轮记忆
跨轮记忆的真相源是持久化的会话消息,而不是某一轮 Processor 内存里的临时变量。Agent Forge 用 Message(一条用户或助手消息)加 Part(消息内的片段)描述一次完整对话;流式过程中的增量先进入事件总线,再投影到存储,供下一轮调度与 UI 订阅共用。
Message 与 Part
Section titled “Message 与 Part”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 的状态迁移。
写入:事件驱动投影
Section titled “写入:事件驱动投影”会话更新走统一写入口:更新 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 客户端]哪些增量只进总线
Section titled “哪些增量只进总线”并非每个流事件都值得写库。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 语义。
读取与 filterCompacted
Section titled “读取与 filterCompacted”全量历史可通过分页游标读完整个会话(每页有上限,有更多则继续),不是“永远只留最近 50 条”。送入模型前,runLoop 使用 filterCompacted 得到紧凑视图:从新到旧扫描,命中“带成功摘要的助手消息,且其父用户消息带 compaction Part”的边界后截断更老原文,再反转为旧→新顺序。
flowchart TD All[会话全量消息] --> Page[分页读完] Page --> Filter[filterCompacted] Filter --> Bound{命中压缩边界?} Bound -->|是| Cut[丢弃边界前原文] Bound -->|否| Keep[保留可见历史] Cut --> View[送模视图] Keep --> View大窗口仍可能很大;压缩边界决定模型“看不见”多老的原文。摘要本身留在边界附近,语义可延续。细节见runLoop 与上下文压缩。
跨轮记忆分层
Section titled “跨轮记忆分层”| 层级 | 生命周期 | 作用 |
|---|---|---|
| Processor 上下文 | 单轮 process 内 | 当前 tool-call、文本缓冲、是否需要压缩 |
| Message / Part 存储 | 跨轮持久 | 真正的记忆体与排障时间线 |
| runLoop 局部变量 | 单次 runLoop | 步数、结构化输出暂存等 |
每一轮重新从存储做 filterCompacted,不把上一轮内存上下文当作跨轮真相。因此进程重启或多副本切换后,只要存储可读,会话仍可续跑。
- 流式 UI:在 POST 之前建立事件订阅;仅依赖同步 HTTP 响应只会看到整包结果。见HTTP 流式与 SSE。
- 历史是否“丢了”:先分清是存储里没有,还是被
filterCompacted从送模视图拿掉。前者查写入与投影;后者查压缩边界与摘要消息。
相关阅读: