日期:2026-09-13 | 性质:技术分享文档(供公开发布)
来源:MFS(Memory Flow System,单用户本地 AI 记忆系统)上下文压缩架构的完整设计与实测记录,对照业界公开方案整理而成。
姊妹篇:《贵的不是上下文长,是前缀形状变了几次:MFS 的 KV-cache 命中优化与注入分层》——本文回答"发什么"(压缩),它回答"按什么顺序发"(缓存命中的完整设计:损失公式、注入分层、缓存经济学)。
阅读提示:本文呈现的是一套完整的压缩架构——为什么这么设计、业界怎么做、MFS 怎么做、代价是什么。所有数字均出自实测或公开资料,诚实标注已知缺陷与取舍。
长对话 AI 的"失忆"不是玄学,而是压缩机制的结构性缺陷。本文从根因出发,逐层拆解一套以"分层段块 + 双逐字轨"为核心的压缩架构,并与 Claude Code 等业界方案正面对比。
0. 摘要
一句话结论:在"上下文压缩"这一单项上,MFS 的设计深度超过业界所有公开方案——"分层段块 + 双逐字轨 + 经济闸 + 终态链 + 确定性降级"的完整结构,业界没有任何一家有公开对等物。但 CC(Claude Code)的 20:1 摘要是数百万用户验证过的服役下限;MFS 用约 4 倍机制复杂度换精细度,在单用户本地场景值得,多租户产品场景未必划算。
压缩哲学三句话:
- 摘要只配当导读,逐字才是主轨——前沿模型摘要 ≈ 原文 1/7 信号量(arXiv:2607.09691),确定性选择 + 逐字保留原文片段 ≈ 全上下文效果、token 只要 1/3;
- 压缩必须付缓存税,所以要么不动、要动就一刀动到底——KV-Cache 让旧字节按 0.1 折续费,但每次改前缀形状 = 改写点后全部全额重付("过路费");频繁小压 = 每次按全额付费,越频繁越贵;
- 保真不靠"压得少",靠四层兜底各管一段——事实轨管数字/路径/命令,原话轨管用户原话,尾部原文管新近上下文,
recall_history管定点取回;散文压到 20:1 也不丢关键信息。
实测基线(详见正文):真 LLM 段生成 4.2s、压缩比 5.4%;全链路 77,900 → 38,360 token(44.1%);缓存命中 99.3%;覆盖完整性检查 3,000 段 3.6ms;写回体量 128k 档 miss −84%、1M 档 −99%;用户真话预算存活率 30% → 90%。
| 章节 | 内容 |
|---|---|
| §一 | 问题定义:七种旧机制、根因双层剖析 |
| §二 | 业界基线:四家方案 + 九条外部证据 |
| §三 | 总体架构:S0–S3 四层流水线 |
| §四 | 核心机制:分层段摘要五步循环、触发档、两道闸、终态链 |
| §五 | 保真结构:六不变量、四道防线、双轨内容 |
| §六 | 缓存协作要点(三条纪律,完整缓存设计见姊妹篇) |
| §七 | 用户消息处理:来源判定、分类处置、快照制 |
| §八 | 正面对比:MFS vs Claude Code 八维度 |
| §九 | 工程化:端点降级、透明度、门禁测试 |
| §十 | 技术决策精选(D1–D16) |
| §十一 | 明确不做的事 + 已知缺陷 |
| §十二 | 结语:五条机制定则 |
一、问题定义
1.1 现象与根因
现象:AI 助手"失忆"——旧摘要越压越短、早期细节消失。
根因有两层(实测确认,第二层比第一层更严重):
- 单份覆盖重压:旧实现每轮把"已压缩的旧摘要"与"新增内容"一起重压成一份——最早的信息每压一次掉一层。业界证据一致:一次压缩留 53%、五次只剩 10%(Compaction Cliff);递归合并放大幻觉(ACL 2025)。
- 口径错位(真因):
cut_idx用_messages的下标计算,却拿去切_full_messages。第 2 次裁剪起,真正被裁掉的轮次从未进过压缩器(被静默丢弃),压缩器反复压的是最老的、已压过的 1–2 轮;同时_full_messages因长度比较同步已冻结,永不更新。
1.2 改造前的七种并存机制
MFS 原有 7 种压缩/裁剪机制并存(C1–C7),全部锚在写死的阈值上(20 轮 / 150k 字符 / 200k 硬顶),不随模型窗口走,且多路径直接改写历史原文:
| # | 机制 | 触发 | 处置(新架构) |
|---|---|---|---|
| C1 | 主对话裁 20 轮前原文 | 每轮,>20 轮 | 删除(改全量发送) |
| C2 | 旧消息 LLM 摘要 | C1 后 | 停用,职责并入段摘要 |
| C3 | 自主 turn 压缩 | turns>150k 字符且>3 | 改读统一 profile,作为 L0 生产者 |
| C4 | 自主 413 紧急压缩 | LLM 报超长 | 保留为 hard guard 确定性降级 |
| C5 | 重工具结果 handoff | 每轮后台 | 归 S2 零 LLM 清理 |
| C6 | 工具大输出截断 | 入历史时 | 归 S2(升级为头+尾) |
| C7 | 历史工具结果就地替换 | 每轮请求前 | 删除(与全量发送直接矛盾) |
1.3 新设计必须满足的四约束
- 同一条原文只压一次,且"被压缩的输入 == 被移除的轮次"可断言(否则口径错位复发);
- 历史原文不可变——一切裁剪/清理/摘要只作用于发送副本(护前缀缓存、护写盘原料、护向量提炼原料);
- 阈值必须随模型窗口走(比例制),不写死;
- 工具结果与对话记录手法分开:工具结果用零模型处理,对话记录才用 LLM 摘要。
1.4 效果目标(G1–G8)
| # | 目标 | 可测量判据 |
|---|---|---|
| G1 | 日常对话不裁剪 | 20 轮以上历史仍全量在上下文中,无"最近 N 轮"窗口 |
| G2 | 只在逼近窗口时压缩 | 触发压缩时真实占用 ∈ window × [0.85, 0.95) |
| G3 | 关键事实永不丢失 | 数字/路径/命令/报错串压缩后逐字保留 100% |
| G4 | 原文只压一次 | 同一条原文消息被送入 LLM 压缩的次数 ≤ 1 |
| G5 | 统计可信 | 整请求估算与 usage.prompt_tokens 偏差 < 10% |
| G6 | 不撞 413 | 三档窗口(64k/128k/1M)均有界;hard guard 发送前先降级 |
| G7 | 全程透明 | 每次清理/压缩留痕可查,绝不静默 |
| G8 | 换模型零代码改动 | 只改 window_tokens 档位 + max_output_tokens 两项配置 |
二、业界基线(2026-09 查证)
2.1 四家方案对照
| 维度 | OpenAI Codex | Claude Code | DeepSeek-Harness (dsh) | Pydantic AI |
|---|---|---|---|---|
| 触发 | 90%×raw 窗口(硬顶 95%) | 85% 警告 / 95% 强制 | 80%×窗口 | 按策略 |
| 每轮发送 | 完整会话(含全部工具结果) | — | 完整会话 | — |
| 压缩后保留 | 一份 checkpoint 交接摘要 | ~10 条 + 1 摘要 | 尾部 16% 逐字 + 摘要 | 锚定增量更新(默认) |
| 是否分层 | 无 | 无 | 无 | 无(单级锚定) |
| 工具结果 | 重写为短形式 | microcompact 清空为占位符 | pruner 掐头尾(8192→4096+1024) | ClearToolResults 策略 |
| 统计口径 | 真实 usage | 真实 usage(仅输入) | 估算 + usage 锚点 | 锚定 provider 真实 usage |
| 召回通道 | 有(模型侧私有工具) | 无(明说不可逆) | 无 | pinning 豁免 |
六条结论性方向(实施依据):
- 每轮全量发送 + 窗口比例兜底压缩(三家共性)——大窗口 + prompt cache 才是"记忆好"的真正原因;
- 触发与占用判断锚在真实 usage,不是字符估算;
- 工具结果是清理重点(清它省最多、几乎不损决策信息);
- 压缩时保留最近少量消息原文 + 一条摘要;
- "重压旧摘要"已被证实衰减(Anthropic 官方、ACL 2025),"不重压摘要"已有生产实现(Pydantic AI 锚定增量更新);
- 压缩必须可见:CC 的静默 microcompact 曾致有效上下文被削到 40–80k,社区强烈反弹。
2.2 九条关键外部证据(支撑设计转向)
| # | 证据 | 对设计的含义 |
|---|---|---|
| E1 | 整体重写累积上下文会"崩溃"(ACE, ICLR 2026):实测第 60 步 18,282 token / 准确率 66.7 → 下一步塌成 122 token / 57.1,低于"完全不压缩"的 63.7;作者称之为 "a fundamental risk of end-to-end context rewriting" | 整体收拢不采用 monolithic rewriting,改为锚定增量更新(旧核当锚、只并入新增层)+ 收拢次数硬上限 + 冻结核 |
| E2 | 逐字保留是主轨(arXiv:2607.09691):前沿模型摘要 ≈ 原文 1/7 信号量,23-vs-0 无一题摘要胜出;确定性选择 + 逐字保留原文片段 ≈ 全上下文效果、token 只要 1/3 | 逐字事实清单是主轨、LLM 散文只是导读;"结构化压缩替代原文"被证伪(p=0.75) |
| E3 | 递归合并放大幻觉(ACL 2025 Findings):纯层次合并(只喂上一层摘要)事实召回最差,用原文做证据 refine 最好 | 收拢时锚定旧核 + 只压未固化增量;纯层级合并档① 默认关 |
| E4 | 反复压缩衰减曲线(Compaction Cliff):一次压缩留 53%、五次只剩 10% | max_consolidations=3 硬上限 + 到顶改零 LLM 确定性下沉 |
| E5 | 失败归因:遗漏占 ~90%(Slipstream);"sufficiency 取决于未来行为,源侧校验看不见" | 长度闸/回滚闸测不到遗漏 → 探针问题式校验列为待办 |
| E6 | "原文可取回"是多级方案的必要条件(RAPTOR 叶节点保留原始块 + 5 层硬上限;Generative Agents 观察原文不删且反思带证据指针) | 逐字事实清单从原文抽取、永不重生成;层级硬上限同 RAPTOR |
| E7 | 缓存与压缩对冲(OpenAI/Claude 官方:"context engineering and prompt caching are inherently at odds") | 每次改段块 = 其后全部 miss,必须显式计价(回本闸 + rebuild 账目);策略是少动、动到底 |
| E8 | 本机生产数据(4,440 次请求、输入 289.7M token):命中 81.1%;低命中请求占 29.6%,却贡献 84.9% 的 miss token;单次形状变化实测连续 1.75 次低命中、每次 miss ≈26–27k | 花钱的是"前缀形状变了几次",不是"上下文太长"——写回体量定则与"少动"策略的直接依据 |
| E9 | 压缩比例的业界分野取决于兜底结构:有逐字/记忆兜底的系统敢深压——CC 全量 compact 约 20:1(摘要长期服役,靠尾部原文 + CLAUDE.md 兜底,关键词保留 85–90%)、确定性裁剪 22:1;无兜底的裸摘要在 3:1~8:1(3.2:1 时保留 94%) | MFS 散文有四层兜底,只剩导读职责,按有兜底一档定标:三档比例 warn 10:1 / compress 15:1 / hard 20:1 |
2.3 延伸:记忆系统层面的对比
| 方案 | 定位 | LoCoMo | 记忆形态 |
|---|---|---|---|
| Zep/Graphiti | 企业级时序知识图谱(只失效不删除) | 94.7% | 事实卡片 + 双时序 |
| Mem0 | LLM 两段式抽取 + 冲突判定(增/改/删/跳过) | 92.5% | 事实卡片(+可选图) |
| Letta/MemGPT | agent 自编辑记忆(OS 式分层 + MemFS) | 74.0% | 记忆即文件 |
| MFS | 单用户个人记忆系统(活体书结构) | 未跑分 | 书:句子→段落→章节→大纲,因果链、情感极性、溶解骨架、检索四标记 |
AgentMemBench(2026-08)给出压缩策略(CBS)实测定位:长程回忆 Recall@5 = 0.556,仅次于稠密向量检索(0.573),远超滑窗/图谱/联网(≤0.005)——压缩+检索双轨是有效路线,MFS 正是"压缩后的段散文留在 payload + 逐字事实仍可检索"的混合形态。
一句话定位:业界在做"事实卡片库"(查得快、答得准、可治理),MFS 在写"一本有人格的书"(有因果、有情感、有厚度、知道自己在重复什么经历)。
三、总体架构:S0–S3 四层流水线
3.1 分层总览
(provider usage.prompt_tokens)"] A2["分类估算
(CJK÷1.5 拉丁÷4 代码÷3.2 数字÷3)"] A3["EMA 自动校准
(α=0.3)"] A1 & A2 & A3 --> A4["pressure = anchor + delta"] end S0 --> S1["S1 全量发送(内置不可配置)
warn 0.85 / compress 0.90 / hard 0.95"] S1 -->|"对话记录压缩形态"| B1["分层段摘要(LLM,低频)
+ S3 保真清单约束"] S1 -->|"工具结果清理"| B2["S2 零 LLM 清理(最省 token)"] B1 --> V["发送视图:
system + 知识槽 + [L1][L2][L0…] + 最近 K 轮原文"] B2 --> V V --> OUT["hard guard:
发送前 anchor + 增量估算 ≥ 0.95W → 先降级防 413"]
3.2 S0|token 统计地基
按窗口百分比触发,若建立在 chars÷2 粗估上,百分比就是虚的。三层结构:
① 真实 usage 锚点(主):每次 LLM 响应后从 provider usage 取总输入 token 记为 anchor;拿不到 usage 的轮次(流式中断、异常)只估算、不更新锚点——防把坏锚点当真;口径统一为仅输入;同时记录"结构指纹"(消息条数 / 末条消息 id / 槽内容哈希),tools 集合不计入(按轮变化,计入则锚点每轮作废)。锚点随会话 dump 持久化、重启恢复(修复前锚点只在内存,重启即丢 → 预热静默失效)。
② 增量估算(只算新增):pressure_next = anchor + estimate(本轮新增)。分类估算按字符类别加权:CJK ÷1.5、拉丁单词 ÷4、代码/JSON ÷3.2、数字/标点 ÷3;连续串数字必须按串建模(实测 32 位数字 = 12 token,逐字符估差一倍)。固定开销不估算——anchor 已包含。
③ 结构变化时重置:压缩/裁剪/槽重写 → 指纹变化 → 退回全量估算,用下一次真实 usage 重新锚定。误差不累积。
④ EMA 自动校准:k ← (1−α)·k + α·(真实增量/估算增量),α=0.3;偏差持续 >15% 告警一次,不打断对话。
⑤ 双保险触发:
| 判据 | 阈值 | 行为 |
|---|---|---|
| soft warn | 0.85 × window | 前端提示 + 启用 S2 清理 + 排后台预压 |
| soft compress | 0.90 × window | 触发压缩 |
| hard guard | anchor + 发送前新增估算 ≥ 0.95 × window | 发送前立即降级(防 413 必须靠发送前判定) |
| 兜底 | provider 报 413 | reactive 压缩 + 重试 |
实测基线:离线 fixture MAE 8.3%;整请求估算 29,310 vs 真实 27,485,偏差 +6.6%。
3.3 S1|全量发送(内置,不可配置)
- 每轮请求 = 当前会话全部消息(system + 知识槽 + 完整历史 + 全部工具结果);无"最近 N 轮"裁剪;
- 代价(如实):单轮输入随会话长度增长 → S0 统计、hard guard、413 兜底、成本监控是必需项,不是可选项;
- 固定开销必须扣掉:system+tools+槽实测每请求 26k+ token → 32k 档不可用、64k 档余量很小;
- 三处判据同一把尺:主对话 + 自主循环 + 第三套硬编码阈值,统一收敛到
context.profile; - 换模型 = 改
window_tokens档位字段,代码零改动。
| 档位 | 警告(85%) | 压缩(90%) | 保留尾部(16%) |
|---|---|---|---|
| 64k | ~54k | ~58k | ~10k |
| 128k | ~109k | ~115k | ~20k |
| 256k | ~218k | ~230k | ~41k |
| 1M | ~850k | ~900k | ~160k |
3.4 S2|旧工具结果清理(零 LLM,最省 token)
纯函数(入参发送副本、返回新列表,绝不碰历史原文),三档处理顺序执行:
| 档 | 范围 | 处理 |
|---|---|---|
| ① 原文保留 | 最近 3 轮(压力达压缩线降到 2) | 一个字不动 |
| ② 占位符清空 | 更早的工具结果 | 替换为 [<tool> 结果已清理 · 结论: <头一行>],消息与 tool_call_id 保留(配对不破) |
| ③ 单条超长剪枝 | 任何 >8192 字符 | 头 4096 + 中间标记 + 尾 1024(尾部常有结论/报错) |
启用时机:压力 ≥ warn 线即开(零成本)。硬约束:不能出现"有 tool_calls 没有结果"的孤立对。实测(40 轮合成会话):清理 72 条 + 剪枝 8 条,省 167,552 字符;清理后压力低于压缩线 → 跳过本轮 LLM 压缩(省一次调用)。
3.5 S3|摘要保真清单
保留: 完成任务与结果 / 重要决策与理由 / 进行中任务状态 /
关键文件与变更(文件名+摘要+关键行号) / 用户重要偏好与约束 / 路径·命令·报错串·数字(逐字)
丢弃: 工具详细输出(只留结果) / 中间步骤细节 / 已解决错误的细节
输出: 条目式要点清单 + 可累加(旧摘要的关键项并入, 不整段重写) + 数字/路径/原话照抄(硬要求)
两个"不采用":
- 不采用四栏结构化模板:模板不产生信息、只产生格式税,还诱导模型为凑栏位写"无";
- 不采用语义线索表(关键词抽取"目标/要求"类语义行):实测挤占数字轨预算(数字命中 8/9 → 4/9),且属于"系统替用户判断什么重要"——重要性判断由 AI 助手自己的台账(record_value / set_user_profile)当场落库承载。
3.6 发送视图构成
发送视图 = system(静态) + 知识槽 + [ L1 ][ L2 ][ L0-a ][ L0-b ] + 最近 K 轮原文
└── 固定开销,锚定在 anchor 里 ──┘ │ ↑ 已固化层:只在收拢时变
│ ↑ 未固化增量:新段永远追加在末尾
└── 段块 = 固化层 + 增量(由旧到新,时间正序)
3.7 存储三层与清理
段(压缩产物)的三层存储:
| 层 | 存什么 | 机制 |
|---|---|---|
| payload(内存) | 段的"现在":[L1][L2][L0…] 就是几条相邻普通消息 |
收拢 = 取现有段文本 → 压 → 删旧段 → 追加新段(原位替换,零临时存储) |
| session persist(PostgreSQL) | 段的"跨窗口/跨重启":_messages + 段块 + 压力锚点整体 JSON 落库 |
恢复时段块形状不丢、锚点同步恢复——压缩成果跨重启有效,无需重压 |
| 会话 dump(JSONL/md) | 原文的"永远" | recall_history 按 uid 区间定点取回 |
压缩进度四处留痕:段结构(随 persist 落库,"压到哪了"的主体)、压缩摘要 sidecar、token_usage_log 账本(source='compact',每次压缩器调用的时间/token/miss)、会话 dump(一切可重放的兜底)。
清理设计逻辑(有存储就有清理,不接受死数据):快照类有 TTL(persist 6 个月 / 垃圾桶 7 天);真源类不自动清(dump/md 只随用户删对话连带删);账本类按期滚删(token_usage_log 180 天,最远先删走索引)。
四、核心机制:分层段摘要
为什么不用"单份覆盖摘要":重压旧摘要会逐层衰减(外部多方证据一致),且原实现存在口径错位(§1.1)。"不重压摘要"已有生产实现背书(Pydantic AI
incremental=True:旧摘要作为锚定块原地更新)。MFS 的差异化 = 多级分层 + 整体收拢 + 逐字事实轨。
4.1 五步循环与块形状演化
压成 1 个新 L0,追加末尾
(原文只被压这一次)"] --> B{"② L0 一组攒够?
(档①,默认关)"} B -->|"是"| C["只把这组 L0 合成 1 个 L1
(原文不再重压)"] B -->|"否"| D{"④ 整体到窗口 80%?
(档② 整体收拢)"} C --> E{"③ L1 攒够?
(档①,默认关)"} E -->|"是"| F["L1→L2, L2→L3
(层级递归)"] E -->|"否"| D F --> D D -->|"是"| G["已固化层 → 锚定增量更新
压成 1 个新 L1 (冷数据)"] G --> H["未固化 L0 原样保留
回到 [L1][L0-a][L0-b] + 最近 K 轮"] H -->|"⑤ 继续循环"| A
块形状演化(实测轨迹):
| 轮次 | 触发 | 动作 | 块形状 | 覆盖 uid(由旧到新) |
|---|---|---|---|---|
| 第 1 轮 | 达线预压 | 最冷区间 → 1 个新 L0 | [L0][L0][L0] |
#11-40 / 41-60 / 61-70 |
| 第 3 轮 | 档① | L0×3 成组 → L1 | [L1][L0][L0][L0] |
#11-70 / 71-80 / 81-90 / 91-100 |
| 第 11 轮 | 档① | L1 组→L2、L2 组→L3 | [L1][L2][L3] |
#11-160 / 161-280 / 281-340 |
| 第 12 轮 | 档② 收拢 | L1+L2+L3 → 1 个新 L1 | [L1][L0][L0][L0] |
#11-340 / 341-350 / 351-360 / 361-370 |
| 第 16 轮 | 档② 收拢 | 同上 | [L1][L0][L0][L0] |
#11-460 / 461-470 / 471-480 / 481-490 |
全程 uid 区间单调递增、覆盖零丢失;收拢后块形状回到 [L1][L0][L0][L0]——递增、反复、形状稳定。
标签约定:渲染标签按位置编号(从最老固化核起 L1、L2、L3…,未固化增量统一 L0);内部 level = 压实深度,只用于合并判据。两者有意分离——若判据也用位置编号,"同层成组"永远找不到成对成员,分层递归失效。
4.2 触发档(谁先成立谁触发)
| 档 | 条件 | 动作 |
|---|---|---|
| 档⓪ 安全阀 | 段块自身 ≥ 0.25 × 窗口 | 立刻收拢,不等整体到线,不受冷却约束——没有它块会先被应急丢段或涨到 413 |
| 档② 整体收拢(唯一常开的合并机制) | payload ≥ 0.80 × 窗口 且段数 ≥ 2 | 已固化层用锚定增量更新压成 1 个新 L1;未固化 L0 原样保留 |
| ~~档① 分层成组~~(默认关) | 某层攒够且过冷却 | 同层连续成组合成上一层 |
档① 为什么默认关:它是"摘要的摘要"(递归合并放大幻觉,E3),且每次改块 = 一次前缀 miss——128k/600 轮产生 597 次、64k/800 轮 732 次(生产冷却下约 60 次),是档②(3 次)的 20 倍;有界性实测完全不依赖它(三档零丢弃)。
两道经济闸("整体到 80%"是收拢的必要条件、不是充分条件——不满足就整单放弃保留原段):
- 回本闸
payback = miss ÷ freed ≤ 40 轮:miss ≈ 段块之后的全部 token(改块即其后全部重新 miss),freed = 块前 − 块后。已知结构性限制(如实):合并输入被_SCOPE_CAP_CHARS(120k)钉死 ⇒ freed 有常数上界,而 miss ∝ 窗口 ⇒ 大窗口下回本闸结构性难通过(1M 档实测 213 连败)。定位:大窗口由档⓪ 应急豁免兜底,档⓪ 稳态主导; - 变小闸
freed ≥ 0.35 × 块前——防"合出来跟输入一样大"。档⓪ 应急时放宽为"只要真的变小"(应急时唯一替代是 413 或丢段,任何真实收缩都更优)。
一次合并的固定代价与块大小无关、收益 ∝ 块大小——晚合并永远优于早合并(实测小层块被拒 no_shrink(1456→343);大层块成功 13,032 → 5,472)。
4.3 终态链(深度到顶后的降级序列)
(LLM, 保导读)"] -->|"max_consolidations=3 到顶"| B["确定性下沉
(零 LLM, 保地图)"] B -->|"仍逼近有界性上限"| C["整段淘汰
(最后手段, 保命)"] C -.->|"被丢段仅可
recall_history 取回"| D[(会话 dump 原文)]
- 确定性下沉(零 LLM):事实行并集 + 散文每段截 120 字符,并把最冷的 L0 一并吸入;
- 整段淘汰:丢导读地图保命,免经济闸——它是保命终态,不做比值判据,只留效果检查"淘汰后 payload ≤ 0.9W";
- 深度硬上限
max_consolidations=3:一次压缩留 53%、五次只剩 10%(E4)。
4.4 fit 切点轮对齐(生产事故修出的规则)
合并/压缩范围必须先裁到装得进输入预算(segment_input_cap_chars=120000),否则渲染文本超 prompt 上限时中段没进过摘要而 uid 声明已覆盖 ⇒ 内容静默消失。
v2.5 生产实测 missing=23 现形后补的三件套:切点按字符预算从最老端裁,可落在轮中间(含 u_k 不含 a_k),被劈掉的轮尾 assistant 永远无人认领 ⇒ 覆盖不变量违规。修复:
- fit 后逐条回退到最近轮起点(下限与 min_messages 一致);
- 对齐不回去的由
make_segment内部截断闸(truncated>0 ⇒ 降级段)零丢失兜底; - 挑食门槛——冷区间 token 不足 → 记
range_too_small跳过:区间留在 payload、不漏不压,宁可不压也不产出低质量小段。
4.5 双轨内容(治"数字被改写")
| 轨 | 生成 | 内容 | 合并时 |
|---|---|---|---|
| 段散文 | LLM(输出 ≤ 目标 token) | 条目式要点清单(含"数字/路径/原话照抄"硬要求) | 重新归纳 |
| 逐字事实清单 | 零 LLM:按 _FACT_PATTERNS 抽含数字、路径、命令、报错、URL、ID 的行,一字不改 |
附在段后 | 字符串多重集并集(规范化键去重、保首次逐字版本),永不重新生成 |
- 清单必须有独立预算:实测
run_shell日志与grep的"路径:行号"命中率 100%,不给预算等于原文(压缩率 0);纯中文语义对话命中率 0%(语义内容靠散文 + 台账承载); - 预算:2400 tok / 40 行 / 单行 200 字符(头 150 + 中略 + 尾 50),超预算按优先级(报错/命令/路径 > 纯数字)截断并标注;
- 主次定调(E2):逐字事实清单是主轨、段散文只是导读——清单为准,散文可能写错数字,清单不会。
4.6 异步预压生命周期
- 压力达 warn 线排后台任务,把已冷却历史压成 L0(只读原文、只写新段);
- 快照必须在进入线程前做完(线程内禁止读
_messages,防竞态); - 任务显式持引用;每会话并发 1;取消 = 放弃结果;
- 原子提交:提交前校验 uid 区间仍连续存在且逐条哈希一致,否则丢弃记
commit_rejected; - 达线时段未就绪 → 用"已有段 + 最近 16% 原文"先发,后台继续压;
- 最小区间门槛
max(2000, 2×段目标):小输入压了白压(实测 271 token 输入只能压到 86%); - 重启预热(preheat):重启恢复会话后立即预检一次压力,达档即提前排预压,不等首轮发送撞墙;四级日志出口(warn/compress/hard → 排预压;ok → 留痕跳过;异常 → 静默)。
4.7 失败降级
| 情形 | 处理 |
|---|---|
| 超时 / HTTP 错 / 返回空 | 不提交段;记账;改用确定性降级段 |
| 返回比输入长 / 压缩后 > 0.8×输入(原文回声) | 拒绝,走降级段 |
| 合并后未见显著下降 | 放弃合并,保留原段 |
| 同一区间连续失败 ≥2 次 | 停止重试,永久降级段并告警 |
确定性降级段(零 LLM):区间内每条消息保留头 120 / 尾 60 字符 + 逐字事实清单;标 degraded=true;允许一次"追赶升级"(仅限段块末尾 1 个段,升级后长度必须严格下降)。
4.8 段边界六条硬约束
- 切点落在完整 tool 组之外(不切 tool_calls↔tool 配对);
- 落在登记的 turn 起点——"轮"不由 role=="user" 界定:四个入口(user / 自主任务 / 定时 / 感知)统一登记并打单调 uid,纯自主窗口同样能分段;
- 覆盖区间不得包含 system[0] / 知识槽 / 段块自身 / 当前轮起点之后;
- 必须整条包含
<summary>与轮内注入的临时 system; - 尾部保留与 min_keep_messages 同时成立:retain 按轮累计,最后一轮自身超 retain 则整轮保留;
- 提交前校验 uid 区间仍连续存在且未被覆盖,否则丢弃本次段。
4.9 压缩器输入裁剪与写回体量
_tool_digest()(零 LLM 确定性裁剪):工具正文进压缩器时只给首 400 字符 + 尾 200 字符 + 前 12 条事实行——数字/路径已由事实轨逐字抽走,让压缩器逐字读日志是白烧输入。实测输入 59,994 → 15,010 字符(省 75%)。发送视图完全不受影响。
写回体量定则:段块位于 payload 前部 ⇒ 任何一次改块都让它之后的全部内容重新 miss ⇒ "每次只压一点点、却频繁发生" = 每次按全额付费(抖动)。因此:
- 预压区间先裁到输入预算内再压,且切点对齐回轮起点;
- 输出目标按区间比例给(
clamp(区间 × 0.50, 1000, 16000)),不钉死固定值——旧默认钉死 1,000 token 导致 34:1 压缩比 + 抖动。
触发时机的设计依据(为什么"攒满一刀 + 0.85 提前排"而不是定期压):定期压缩产生次满事件——每刀吃不到预算,事件次数翻倍,而每次改段块的过路费 ≈ P 几乎不变 ⇒ 总过路费 ∝ 1/Δ,越频繁越贵。三档触发实际是提前压:warn 0.85 排后台预压 + S2 零 LLM 清理先手(能白嫖清掉的先清掉),只有 hard 0.95 是"马上死才压"的救命档。频繁压不提升保真——保真由四层兜底保证、与频率无关,只会把段切得更碎、加速收拢与衰减。
五、保真结构:六不变量与四层兜底
5.1 六条不变量
| # | 不变量 | 为什么 |
|---|---|---|
| I① | 覆盖完整性:payload 的 uid ∪ 所有段覆盖的 uid = 全部历史 uid,且段覆盖两两不相交 | 任何消息既不在 payload 又不被段覆盖 = 静默挖洞;必须记账并告警 |
| I② | 单次压缩:同一条原文被送入 LLM 压缩的次数 ≤1;段散文只在合并时被再压,深度上界 3 | 一次压缩留 53%、五次只剩 10% |
| I③ | 历史不可变:历史消息在发送路径上逐字不变;一切裁剪/清理/摘要只作用于发送副本 | 前缀缓存 + 写盘原料 + 向量提炼原料 |
| I④ | 段 append-only:段生成后不再修改;新段追加末尾;替换一律按身份下标拼接(按 level 过滤或按长度切都会丢段,两种错法各踩过一次) | 段块前缀稳定 |
| I⑤ | 有界性:段块 ≤ 0.35 × 窗口、payload ≤ 0.95 × 窗口,64k/128k/1M 三档都成立 | 段块是受保护头部,无界增长必然 413(实测出厂默认曾涨到窗口的 13.5 倍) |
| I⑥ | uid 覆盖 ≠ 内容进了摘要:压缩输入必须装得进输入预算,记录 input_truncated_chars(必须为 0) | I① 只比 uid,看不见内容洞(实测 1M 首次合并静默丢 44/72 段的教训) |
5.2 I① 覆盖完整性的全链四道防线
洞既可能被制造(压缩选不出起点)、也可能被静音(出口白名单误吞)⇒ 防线必须分层设防,不靠单点:
select_cold_range:
起点判据三层退化 + 存量早洞并入候选
+ 合并消息补登记 turn_start"] --> B["② 视图站零丢失
组装视图时查 I①:
违规 ERROR; 未覆盖消息原样拼回视图
(带时间线提示, uid 层面永不静默丢)"] B --> C["③ 出口站归因
出网 payload 复查:
有裁剪/降级标记 → INFO 预期结果
无标记却缺 uid → ERROR 静默挖洞"] C --> D["④ 白名单同步
日志文案必须自解释;
白名单静音前必查视图报告
(本轮有降级 与 视图本来就缺 可同时为真)"]
教训实录:白名单曾漏认降级标记,把设计行为误报成生产 ERROR;后又发现"视图洞叠加降级标记时,白名单会把真洞静音成预期结果"——日志文案必须自解释,否则读者会把设计行为读成漏洞。
5.3 四层保真兜底(各管一段)
数字/路径/命令/报错逐字
(零 LLM, 永不重生成)"] T2["原话轨 min(0.15W, 20k)
用户原话逐字
(永不进压缩器)"] T3["尾部原文 min(16%W, 40k)
新近上下文逐字"] T4["recall_history
按 uid 区间定点取回全文"] T1 & T2 & T3 & T4 --> S["段散文只承担导读职责
→ 深压 20:1 不丢关键信息"]
5.4 用户原话轨(v1.4)
为什么必须有:事实轨只认含数字/路径/命令的行,用户说过的"以后所有脚本一律用 LF 换行"这类纯语义需求恰好落在夹缝里——事实轨不要它(无技术串)、散文轨会把它压薄、台账靠模型当场自觉(不保证)。改为按来源保底:role == "user" 的原话一律逐字保留,不进摘要、不进压缩器。
| 项 | 定稿 | 说明 |
|---|---|---|
| 预算 | min(0.15 × W, 20000) 双约束 |
64k 档自动缩到 9.8k(防固定预算吃掉 72% 窗口),1M 档封顶 20k |
| 取材范围 | 只装已被段覆盖区间的 user 消息 | 尾部原文里的 user 消息不进本轨——去重,同一条原话只计费一次 |
| 选取顺序 | 从最近往前累计,装满为止 | 需求类信息天然靠后 |
| 单条超限 | >8192 token:头 60% + 中略 + 尾 40% | 超长 user 消息多为粘贴的日志/代码 |
| 重建时机 | 只在压缩事件发生时重建 | 平时不动,否则前缀每轮全断(台阶式淡出,见姊妹篇 §4.3) |
| 溢出处理 | 被挤出的最老原话:原文仅可 recall_history 取回;语义要点由 AI 当场 record_value 落库 |
有损但不丢,不违反 append-only |
硬约束:不做"最近 20k 的滚动窗口"——那样它每轮都变,把"只追加"的尾部变成每轮重写区,省的钱全部赔回去。
六、缓存协作要点
本文与姊妹篇《贵的不是上下文长,是前缀形状变了几次:MFS 的 KV-cache 命中优化与注入分层》分工:本篇回答"发什么"(压缩),缓存侧的完整设计——损失公式与 E8 实证、payload 七区结构、注入分层 L0–L3、0.1 折续费账本、过路费模型、台阶式淡出、编号即寻址、"宁陈旧不抖动"、双路径同构与量化验收——全部收录于姊妹篇。这里只保留与压缩器直接相关的三条协作纪律:
- 一次压缩事件 = 段块改动点起全部 miss(128k 窗 ≈85k token 单次)——所以回本闸双门槛(freed ≥ max(0.35 × 压缩前体量, miss ÷ 40 轮))必须过,不过不压;
- 原话轨重建只发生在压缩事件当轮——与段块改写绑同一列车,同一次全价、两份变动(搭便车);
- 合并、收拢、旧 summary 剥离同一窗完成——"少动、动到底",绝不连续多轮各动一点。
七、用户消息处理:分清谁在说话
7.1 三类错收与危害
原话轨曾按 role == "user" 一刀切整条逐字收轨,三类消息被错收:
| 消息类型 | 实际遭遇 | 危害 |
|---|---|---|
| 粘贴文档(大段文档/日志/代码) | 逐字收轨 | ① 一条 8k 文档吃掉预算 42%,用户真话被挤出;② 永不进压缩器 → 文档无摘要层 |
| 引用 AI 陈述(复制 AI 说的话发回) | 逐字收轨 | 语义污染:AI 的输出被标成用户立场——模型的输出经用户引用回到上下文,被当作更高权重的用户输入,形成自我强化回路 |
| 引用 AI + 加工几句 | 逐字收轨 | 同上(混合体),且加工部分是用户真话,不能不收 |
危害排序:语义污染(最隐蔽)> 预算挤占 > 文档无摘要层。
7.2 方案论证与裁决
三方案对比(来源标记 A / 文档指针化 B / LLM 判定 C)后的两个关键发现:
- B 单独不成立——它只治文档,不治引用 AI(引用 AI + 用户批注必须收全文,否则批注丢失),只能作为 A 框架下"文档条目"的处置策略;
- C 违反"能向量不 LLM"——判定"是不是用户说的"本质是相似度问题,正是向量的主场,用 LLM 是杀鸡用牛刀还引入新失败模式。
裁决:A 框架 + B 作为文档子处置 = "来源判定 + 分类处置"——判定层零 LLM,处置层按来源分类施策。
7.3 判定层(三级优先级,零 LLM)
【引用 AI 发言】/【文档引用】"} P1 -->|"【引用 AI 发言】"| Q["quote_ai / quote_mixed
(用户亲手指定, 零推断)"] P1 -->|"【文档引用】"| D["document"] P1 -->|"无前缀"| P2{"② BGE 推断:
与最近 M=5 条 assistant 的
max cos_sim"} P2 -->|"≥ 0.85"| Q P2 -->|"≥ 0.70"| QM["quote_mixed"] P2 -->|"< 0.70 或空参考窗"| P3{"③ 长度兜底:
tokens ≥ 1500?"} P3 -->|"是"| D P3 -->|"否"| O["owner (默认用户)"] Q2["质量闸: 向量数齐 + dim=1024
+ 非 sha256 伪向量
(确定性重算精确比对)"] Q2 -->|"不达标"| O2["整体降级 owner
(宁收全文不错杀批注)"]
- 质量闸(防伪向量):embedding 服务的真实故障模式是静默返回垃圾而非抛异常——失败时可能返回 sha256 伪向量。利用伪向量的确定性:同文本必然算出同向量,闸门内逐条用
_embed_fallback重算精确比对,命中即伪向量 → 整体降级 owner; - 降级完整口径:任何环节失败 → 该消息按 owner 处理,原话轨退回现行行为,压缩管线永不阻塞;
- 判定顺序的理由:显式标记是用户亲手指定的来源,比任何推断都准;引用判定优先于长度——用户贴很长的 AI 输出时"引用 AI"语义更准。
7.4 处置层(四类条目,四样施策)
| kind | 收轨策略 | 标记 | 理由 |
|---|---|---|---|
owner |
全文收 | 无 | 保真轨的核心职责 |
quote_ai |
全文收 | [引用AI] |
引用的 AI 原文对理解批注有上下文价值,但必须标明非用户立场 |
quote_mixed |
全文收 | [引用AI+批注] |
批注是用户真话,不能不收;标记让模型区分立场归属 |
document(快照型) |
整条收 | [文档] |
用户的真批注必须逐字保真;快照路径本身就是取回通道 |
document(裸长文) |
首行 + 指针 | [文档] |
仅对消息体即全文的裸长文成立;语义要点由 AI 当场 record_value 落库 |
渲染示例:
[#102] 帮我把连接池参数改成 30
[#109] [引用AI] (用户复述 AI 原话全文)
[#130] [引用AI+批注] (引用内容…用户的批注…)
[#188] [文档] 部署日志第 3 节的性能问题帮我分析下
【文档引用】workspace/attachments/a3f2b1c8d4e5f607-部署日志.md
(导入时快照:128,402 字符 | 首行:部署日志摘要——9月12日灰度…)
7.5 前端三通道(治本第一环)
根因是前端没有让用户表达"我发的是什么"的通道——一切只能靠后端推断。业界教训:自动注入来源不可靠(Cursor "最近浏览文件"自动附带导致大量 stale context 事故),显式引用是唯一可靠形态:
| 通道 | 交互 | 协议 |
|---|---|---|
| A. 选中引用 | 选中 AI 消息文本 → 浮动"引用"按钮 → 发送框生成引用块 | 【引用 AI 发言】 前缀 + 选中文本(500 字符硬上限,完整原文在原位消息 uid 可寻址)+ 用户批注 |
| B. 文档导入 | 附件按钮 → 快照接口 | 【文档引用】 前缀 + 快照路径 + 首行 + 字符数 |
| C. 粘贴快照化 | 粘贴大段文本自动走快照通道 | AI 发言预检(前 200 字符精确子串匹配,命中转引用通道——防引用场景被劫持成 document)→ ≥1500 字符走快照,否则原文进输入框 |
粘贴阈值 1500 字符的论证:低于后端 1500 token 判定阈值(前端快照化先行,指针永远先于判定到位);ChatGPT web 是唯一"粘贴自动文件化"先例(5,000 字符自动转附件),MFS 与之同构但阈值更激进——因为 MFS 有更硬的理由:粘贴文本逐字进原话轨(预算 ~19k token),5000 中文字符一条吃掉 18% 预算。
7.6 快照制(git 对象风格)
裸路径(活引用)有三个致命问题:变动问题(文档改了路径不变 → 非确定性,ChatGPT Project Files 官方立场即静态快照)、缓存破坏(每轮按路径重读最新 → 文件一变全额 miss)、审计缺失(三个月后回看"当时发的是什么版本"无据可查)。
裁决:纯内容寻址快照——workspace/attachments/{hash16}-{原文件名}.md:
| 规则 | 说明 |
|---|---|
| 同 hash 幂等 | 同内容文件已存在 → 直接返回既有路径——同一文档重复导入 N 次只有一个副本 |
| 防半写 | 写临时文件 → 校验完整 sha256 → rename 落位 |
| 文件名净化 | 去路径穿越 + 去控制字符 + 截 80 字符;扩展名白名单外落 .txt |
| 尺寸上限 | ≤ 2MB(超出 422) |
路径内嵌内容哈希,指向不可变副本:改内容 = 重新导入 = 新哈希 = 新路径;路径字符串写进消息后字节恒定 → 前缀稳定;与 append-only 哲学完全同构。
7.7 快照清理联动(生有来源,灭有依据)
裁决:快照寿命 = 三处引用痕迹全灭 + 48h 宽限,两条清理线共用三处并集查证 _snapshot_referenced(DB dump ILIKE ∪ broker 活跃会话 ∪ chat_*.md):
先读 dump 取路径集合
不被引用才删"| DEL["删除"] R -->|"② 滞后线: 跑批兜底(~300s)
mtime>48h 预筛 → 单条合并 ILIKE 查询
三处全无命中 → 删除"| DEL
关键设计点:restore 会把 dump 从 DB 删掉(活跃会话原始对话只在内存)——若只查 DB 会误删活跃会话正在引用的快照,故必须三处并集;记忆指针不保快照(切片里的文档指针在快照退役后指向"原文已回收",但精炼内容 + 因果链骨架仍在——原文可退役,记忆不可)。
7.8 落地效果(v1.4,测试 72/72 + 两轮独立审查通过)
场景:一轮压缩事件里,用户贴 3 篇 5000 字文档 + 引用 2 段 AI 长发言并批注 + 说了 5 句真话:
| 消息类型 | 优化前(一刀切) | 优化后 | 节省 |
|---|---|---|---|
| 3 篇贴文(各 3500 tok) | 10.5k tok 全文进轨,且被当作用户立场 | 3 × ~50 tok(指针+首行+批注) | -98.6% |
| 2 段引用 AI(各 700 tok) | 1.4k 全文进轨(AI 的话重复计费) | 2 × ~350 tok,标记声明非原创立场 | -50% + 语义正名 |
| 5 句用户真话 | 被挤出轨道(预算被文档吃光) | 全文逐字收(预算余量充足) | 零回归 |
同样 9.8k 预算(64k 窗)下,用户真话的存活空间从可能不足 30% 提升到 ~90%,91% 预算留给用户原话。四条硬收益:语义污染清零、缓存纪律零破坏(渲染逐字节稳定)、故障全兜底(BGE 挂 → 整体降级 owner,不会错收只会少判)、快照生灭有据。
BGE 止损规则(防无限调参):探针显示 owner 误标率 > 5% 或两档分布重叠不可分 → 整体裁撤 BGE 推断层,判定退化为"显式协议 + 长度兜底",止损点写死。
八、正面对比:MFS vs Claude Code
前文各章已拆解 MFS 的机制细节,本章回到最初的对比问题:与 Claude Code(CC)的滚动窗口单段摘要相比,这套"台阶式 + 双轨 + 台账"的复杂度到底换来了什么?先用一张机制对照图看清两者的根本形态差异,再逐维度对照,最后给出三点不回避的保留意见。
8.1 机制形态对照
CC 的形态是滚动窗口:窗口逼近上限时,把全部历史一次性交给 LLM 压成一段摘要,摘要替换原文成为新的"历史",尾部保留最近若干轮对话。MFS 的形态是台阶式:多次小步压缩,每次只动一小段,且事实、原文、摘要各有归宿。
(~150k token)"] --> A2["全部历史 → LLM 一次性摘要
(约 20:1 大幅压缩)"] A2 --> A3["摘要 + 最近若干轮对话
= 新窗口的全部历史"] A3 -->|对话继续增长| A1 A2 -.->|"原文丢弃,摘要即终点
细节不可逆"| A4["无独立事实存储
无原话保留"] end subgraph MFS["MFS:台阶式(多次小步)"] direction TB B1["估算达 0.90
(在硬限前提前触发)"] --> B2["零 LLM 三档清理
(占位符/超长工具输出/头尾保留)"] B2 --> B3["S3 保真清单摘出
事实与决策"] B3 --> B4["分层段摘要:逐段 3:1~8:1
双轨写回(散文导读+事实清单)"] B4 -->|仍放不进| B3 B4 --> B5["四层保真兜底:
事实台账 · 原话轨 · 尾部原文 · recall_history"] end
一句话概括形态差异:CC 把历史压成一团"记忆摘要",MFS 把历史拆成"事实账本 + 原话档案 + 摘要导读"三层各有职责的存储。前者的风险集中——一次摘要失败或失真,全部历史受损;后者的风险分散——任何一层出错,其余层仍可兜底。
8.2 八维度对比表
| 维度 | Claude Code | MFS | 差异本质 |
|---|---|---|---|
| 压缩形态 | 滚动窗口:全文→单段摘要,约 20:1 一次性大步 | 台阶式:多次触发,每次小块 3:1~8:1,逐级加深 | 大步 vs 小步;一步到位 vs 逐级可回收 |
| 保真结构 | 摘要即终点,原文不可取回 | 四层兜底:事实轨(record_value)→ 原话轨 → 尾部原文 → recall_history,层间可交叉验证 | 单点存储 vs 多层冗余 |
| 成本意识 | 压缩后继续对话即全量重算(长提示计费),无 KV 复用设计公开 | 台阶式使前缀字节稳定,KV-Cache 命中部分按 0.1 折计费;实测 4,440 请求 81.1% 命中 | 视缓存为第一公民 vs 不依赖缓存 |
| 失败路径 | 摘要失败重试,多次失败影响会话连续性 | 显式失败降级表:重试 → 降级档 → 确定性降级段(无 LLM 也能收尾),压缩事件永不阻塞对话主流程 | 隐式依赖 vs 显式降级路径 |
| 增量性与缓存 | 滚动窗口必然 miss:每次压缩窗口整体换血 | [#uid] 编号即寻址 + 台阶式切点:压缩只改被压段,未被波及的 token 前缀稳定 |
必然全 miss vs 结构性保命中 |
| 验证背书 | 无公开的压缩质量量化指标 | S0 估算 MAE 8.3%;门禁 C21–C117 共 116 项 + uv_origin 72 项;负面指标(1M 档回本闸 213 连败)如实记录 | 黑盒信任 vs 可审计账本 |
| 复杂度 | 机制简单:约一个摘要循环 + 触发阈值 | S0–S3 四层流水线 + 双轨内容 + 快照制 + 端点降级 + 用户消息判定(约 4 倍复杂度) | 简单可维护 vs 精细可调控 |
| 延迟代价 | 增量摘要延迟可控(云端模型) | 台阶式多次触发累计延迟更高;深压场景本地模型 prefill 需 2.7~5.5 分钟 | 换精细度的真实代价 |
8.3 结论与三点诚实保留
结论:两种形态是不同约束下的产物,而非简单的优劣关系。CC 服务于云端多租户——机制必须极简、延迟必须低、单用户成本必须可摊薄,单段摘要是合理的工程选择;MFS 服务于单用户本地——KV-Cache 经济学成立、延迟可容忍、追求极限保真,台阶式 + 多层兜底才有施展空间。§二 的外部证据(E3 递归合并放大幻觉、E6 缓存与压缩对冲)说明 CC 形态存在已知的结构性风险,MFS 的复杂度正是花在规避这些风险上。
但有三点必须诚实说清:
- 验证规模不可比。CC 的 20:1 是数百万用户日常使用中"够用"的服役下限;MFS 的指标(MAE 8.3%、81.1% 命中、116 项门禁)来自单环境实测与构造测试,未经过同等规模的分布外考验。"设计上更保真"与"大规模下更保真"之间还隔着距离。
- 4 倍复杂度换精细度,只在本环境成立。S0–S3 四层 + 双轨 + 快照制的每一层都有其理由,但也意味着每一层都是潜在的维护点与故障面。单用户本地场景中这份复杂度由设计者本人消化;若迁移到多租户云端,运维与调试成本很可能反噬精细度收益——届时 CC 式极简反而是对的。
- 深压延迟是真实代价。台阶式的"多次小步"在极端场景(1M 档、深压)下意味着本地模型 prefill 2.7~5.5 分钟的等待。CC 用户永远不会遇到这个量级的压缩延迟。MFS 用异步预压与 preheat 预热缓解,但无法消除——这是"结构保真"向"响应速度"收取的费用。
九、工程化:降级、透明与门禁
机制设计只是纸面方案,能否在生产环境存活取决于三件事:依赖故障时怎么办(降级)、用户能否看见发生了什么(透明)、改动是否被持续验证(门禁)。本章记录 MFS 在这三件事上的做法——其中审查修复部分特意保留原始缺陷描述,因为"如何发现自己错了"与"如何设计"同样值得分享。
9.1 LLM 端点自动降级(状态机 + 运行时切换)
压缩器复用会话模型端点——主端点宕机意味着对话链与压缩链同时失血:不仅无法对话,上下文也无法压缩,恢复全靠重启。为此在 openai_compat.py 模块级驻留一个降级状态机,覆盖两个咽喉点(对话流式调用与压缩链共用的非流式调用):
本次直接换备端点重试(对话无感) 单败重试 --> 正常: 重试成功(不广播) 单败重试 --> 降级态: fail_count≥2
广播 llm_degraded 降级态 --> 降级态: 直接走备端点(主端点零调用) 降级态 --> 正常: 距上次探活>120s 试主端点一次
成功→广播 llm_recovered 降级态 --> 降级态: 备端点也失败
广播 llm_fallback_failed(60s冷却)→提示手动切换
三条判定规则值得注意:
| 规则 | 内容 | 理由 |
|---|---|---|
| 溢出不切 | 413 / 上下文溢出 / 请求过大 → 不降级,走溢出处理链路 | 溢出是"这轮请求太长",切端点解决不了;400/401 配置错同理(切了也是同错) |
| 流式安全窗 | 仅"零 token 失败"(连接/HTTP 检查阶段)可换端点重试;流中途断(已吐 token)不重试 | 防前端重复输出;非流式无此约束(整次重试无副作用) |
| 运行时生效 | 函数体内每调用重读模块属性,setattr 即切换,不需要重启;手动保存配置时清状态机并广播恢复 |
dsh 已证伪"切配置需重启"的体验 |
另有一条朴素防御:fallback 三件套(url/key/model)齐全才启用,缺任一项视为未配置(单端点原样抛)——半套降级配置比没有更危险。
9.2 审查修复实录(双子代理逐项审查,5 项)
这轮审查最有价值的产出不是修复本身,而是暴露的缺陷形态——每一项都是"看起来在工作"的隐性断路:
- P0 · 流式 5xx/429 绕过降级:异常归一化函数只认四类自定义异常 + 超时/传输错误,漏了
httpx.HTTPStatusError(且它不是TransportError子类)——流式主端点 500/429 原样抛出、状态机零感知,而非流式路径行为正常。两条路径行为不对称,本身就是缺陷证据。修复:补 isinstance 分支,≥500/429 映射为连接错误进降级。 - P0 · WS 广播链路断裂:事件处理白名单只有 5 种事件类型,
llm_*与ctx_*全落 else 丢弃——连此前增补的压缩状态条事件也一直是断的(推送了、没人收)。修复:else 前补前缀透传分支,一处救两族通知。 - P1 · .env 毒化:保存坏温度值(如 "abc")先写盘再校验,接口返回成功——下次重启时
float()直接抛错、服务起不来,保存当下毫无报错的延时炸弹。修复:写盘前先验,坏值拒绝保存。 - P1 · 线程安全:压缩链跑在工作线程,
asyncio.Queue.put_nowait非线程安全、状态机读-改-写有竞态。修复:广播经call_soon_threadsafe转发主事件循环;状态机函数加锁,广播调用移到锁外(防持锁做 I/O)。 - P1 · 测试真实性:测试桩只记 model 不记 URL——门禁"证明请求打到了新端点"实为空断言。修复:桩改记
(url, model)元组 + URL 级断言;新增回归用例(流式 500 零 token → 自动切备)直接验证第 1 条修复。
9.3 前端透明度(六条硬要求)
Claude Code 的静默 microcompact 教训(用户不知道上下文被动了)被列为反面教材,透明度是硬要求而非可选项:
used_percentage实时状态(仅输入口径);- 一次性提示:
已清理 N 条旧工具结果/上下文已压缩:Xk → Yk; - 压缩账目:时间 / 压掉 token / 摘要长度 / 当时档位;
- 绝不静默;
- 「工具轨迹」+「上下文分层」两个标签页:轨迹从后端唯一真源(只读接口)还原,分层页肉眼核对段机制(层序、新段在尾、收拢形状、覆盖缺口);
- 压缩进行中状态条:压缩后台跑几分钟、聊天流零提示时,"下一轮变慢"不可解释(本地压缩器 prefill 与主推理抢带宽)。两个 WS 事件驱动悬浮条两态:"正在整理上下文…" → "已整理(下一轮响应稍慢)",完成后 3 秒淡出。明确不做的:进度百分比(压缩是单次 LLM 调用,无中间进度);低频修复路径的推送(账目面板可见即可)。
9.4 门禁体系(机制在 ≠ 机制会被走到)
门禁是这套系统的第三根支柱,共 C21–C117 合计 116 项(含原话轨 4 项、成本文档生效链 3 项、端点降级 14 项、fit 截断轮对齐 1 项),另有用户消息处理 uv_origin 系 72 项。全部为假 provider + 合成长对话仿真(零真 LLM 依赖),每步改动必过。
门禁体系里最重要的单条经验来自"砖机事件"教训:38 道门禁全部用 merge_leveled_enabled=True 去验证机制,而出厂默认路径从未被走过——机制在配置里存在,不等于默认配置下它会被调用。此后立规:凡改默认值者,必须先过只读默认值的门禁。验收清单中对应的硬指标包括:200 轮每一轮被移出 payload 的消息 100% 被某段覆盖(覆盖完整性)、普通轮前缀命中 ≥99%、同输入跑两遍得到相同覆盖集与相同段文本(确定性)。
十、技术决策精选(D1–D16)
16 项决策完整记录于设计文档,此处收录全部 16 项的一览表——每项都标注了放弃的方案,因为被放弃的理由往往比被选择的理由更有信息量:
| # | 决策点 | 选择 | 放弃 | 一句话理由 |
|---|---|---|---|---|
| D1 | 发送模式 | 全量发送,内置不可配置 | 按轮数裁剪 | 固定轮次不看 token,早期细节静默丢失;大厂均为比例兜底 |
| D2 | token 统计 | anchor + 分类估算 + EMA 校准 | chars÷2 全量估算 | 百分比判据建立在粗估上是虚的;实测 MAE 8.3% |
| D3 | 压缩形态 | 分层段摘要(append-only + 锚定收拢) | 单份覆盖摘要 | 重压逐层衰减 + 口径错位双重根因 |
| D4 | 合并机制 | 只留锚定收拢(默认开) | 分层成组(默认关) | 后者是 20 倍缓存破坏事件 + "摘要的摘要";有界性不依赖它 |
| D5 | 工具结果处理 | 零 LLM 三档清理 | LLM 摘要工具结果 | 工具结果是上下文大头,零成本且几乎不损决策信息 |
| D6 | 事实保真 | 零 LLM 逐字抽取 + 独立预算 | 让压缩器"多写一句" | 确定性抽取扛得住压缩,走 LLM 的那层扛不住 |
| D7 | 摘要模板 | 条目式要点清单 | 四栏结构化模板 + 语义线索表 | 模板产生格式税;线索表挤占数字轨且属"系统替用户判断" |
| D8 | 工具正文进压缩器 | digest 裁剪后进(省 75%) | 整段日志进 | 白烧输入;摘要变日志复述 |
| D9 | 写回体量 | 一次压满输入预算(按区间比例给输出) | 每次压固定 1,000 token | 段块在前部,碎事件 = 每次全额 miss |
| D10 | 召回通道 | uid 定点取回(按段头 uid 区间) | 盲目检索式召回 | 不依赖"模型主动查自己忘了什么"(业界实测失配 26.5–54%) |
| D11 | 轨迹存储 | 后端唯一真源 + 只读接口 | 前端 localStorage | 双宿主(桌面壳/浏览器)物理隔离必然分裂 |
| D12 | 段块角色 | system 消息(开头连续串) | user 消息式 checkpoint | 受保头部规则保护,天然抗裁剪 |
| D13 | 压缩模型 | 独立 provider + 精简 system | 会话主模型 + 人格 system | 省钱;避免每次压缩白烧约 1.7 万 token |
| D14 | 触发判据 | anchor + delta 主判据 | 双重扣减并用 | 双重扣减导致提前 26k token 触发 |
| D15 | 语义要点承载 | AI 助手台账落库 | 压缩器语义线索表 | 实测挤占数字轨(8/9→4/9);重要性的判断权归用户与 AI 自己 |
| D16 | 压缩比三档 | warn 10:1 / compress 15:1 / hard 20:1 | 维持 6/9/12 | 散文只承担导读职责,四层兜底决定比例下限;落地捆绑四项耦合参数(事实轨 1200→2400、原话轨预算、保留上限、总段 token 窗口化)——只改比例不改它们 = 保真前提塌掉 |
D16 的捆绑项值得单独强调:改一个参数必须连同它的耦合项一起改。压缩比从 6/9/12 放宽到 10/15/20,前提是事实轨预算翻倍(否则深压后单段覆盖扩大,逐字 100% 不保)、总段 token 上限窗口化(否则 1M 档收拢等效 70:1、需 24 刀)。设计文档为此专门列出"落地必须捆绑的耦合项"清单——参数不是旋钮,是齿轮组。
十一、边界与已知缺陷
11.1 明确不做的六条(边界即设计)
- 不做盲目检索式召回(全文搜索/归档检索)——uid 定点取回已覆盖需求(D10);
- 不做聊天记录自动进记忆切片(向量化 auto_save)、工具细节不进向量库——记忆入库是有意识的动作,不是流水线副产品;
- 不做 knowledge 槽分层重构——只保留 system[0] 静态化,槽位大改的收益配不上风险;
- 不学 dsh 的"无自动跨会话记忆"——MFS 的记忆库/事件/话题/联想能力更强,不倒退;
- 机制参数开关不进设置面板——只留配置文件 + 门禁把关,防止用户误关保真机制;
- 轨迹不写入聊天记录文件——会污染记忆切片原料的内容哈希检测。
11.2 已知缺陷(如实记录,不粉饰)
| 缺陷 | 现状 | 影响 |
|---|---|---|
| 估算分支漏算 tools 字段 | 某分支未计入 tools 定义(约 13.7k token) | 触发点偏晚,靠 hard guard(真实 usage)双兜底 |
| 1M 档回本闸结构性难通过 | 回本闸 miss÷freed≤40 在 1M 档 213 连败 | 由档⓪ 安全阀(2% 放宽)+ 档② 应急豁免兜底;如实记录,不假装达标 |
| 64k 档事件频率偏高 | 块约 4 轮填满,确定性下沉每 2–3 轮一次 | 待调"触发线高度 vs 余量"权衡;1M 档无此问题 |
| 413 reactive 路径曾是死代码 | 异常映射缺失 → 溢出后无回复、下轮同错、重启仍卡 | 批次前置修复项(映射为溢出异常 + 确定性降级不依赖压缩器) |
| 压缩段散文仍是"二手压二手" | 收拢时散文再压缩不可避免 | 输入有界 + 频率低频缓解;数字/路径/报错由逐字事实轨兜底 |
十二、结语:五条机制定则
全文压缩到最后,是五条可以迁移到任何上下文压缩系统的定则:
- 保真由"输入:输出比"决定,不由"能看到多少"决定。任何一次压缩保持小比例(≤4:1)。抬高输入不抬输出,只会把比例推向 75:1,更糟。
- 块有界 + 历史无限 ⇒ 压缩比必然无限增长。能无限增长的只能是有界结构——逐字事实是主轨,散文允许越来越薄。
- 只留一条合并机制。合并路径多于一条,意味着"摘要的摘要"有多条演化分支,每一分支都需要单独验证。
- 语义要点由台账承载。目标、方向、用户要求、偏好由 AI 助手自己落库(
record_value等),压缩器不替它判断"什么重要"——重要性的判断权归用户与 AI 自己。 - 诚实标注。每个压缩段的段头有一行刻意不含任何数字的常量声明:"以下每段都是压缩产物,细节原文已不在上下文中;涉及具体数值/路径/原话时,用
recall_history按段头 uid 区间取回原文核对,不要把这里的概述当原文引用。"——不含数字 ⇒ 逐字节恒定 ⇒ 不成为新的前缀失效源;诚实声明 ⇒ 模型知道自己记的是"二手信息"。
回头看这套系统的本质:它不是一个更聪明的摘要器,而是一套让"遗忘"变得可控、可审计、可追责的记账制度。摘要只承担导读,事实走台账,原话有归档,取回靠寻址,每一层压缩都有账目,每一个参数都有门禁。业界主流把上下文压缩当作模型的内部机制,MFS 把它当作系统的核心账务——这或许是本文最值得带走的一句话。
本文基于 MFS 内部设计文档《上下文压缩架构设计 v2.9》与《用户消息处理设计方案 v1.4》整理,面向公开读者重新组织。文中所有实测数字(MAE 8.3%、81.1% 命中、-98.6% 等)均来自单环境实测与构造测试;业界数据引用自 2026-09 查证的公开资料,已在正文对应位置标注。
举手提问