qqqide滴上下文背包设计

文档发表:2026-07-15 · 阅读:62 · 更新:2026-09-21

qqqide 上下文背包 — 最终形态

状态:V24 引擎(现行)。2026-09-21 更新。


1. 起源 — 我们遇到了什么问题

你跟 AI 结对编程 50 层楼之后,每一层楼的完整对话(你的问题 + AI 的思考 + 工具调用 + 工具输出)都还躺在上下文窗口里。50 层楼 ≈ 250K tokens 起步。再往后,要么倾家荡产付 token 费,要么 AI 开始"遗忘"最早讨论过的架构决策。

更致命的是 缓存失效:大多数 LLM 服务商按 prompt tokens 计费,但对不变的前缀提供缓存折扣(prefix caching)。如果你每次发送新消息时都重建了整个上下文数组——即使 99% 的内容和上一轮一模一样——缓存全部失效,每一轮都按全新 prompt 计费。

三个问题纠缠在一起:

问题表现根因
上下文膨胀每层楼 ~5K tokens,50 层 = 250K工具输出(尤其是终端命令结果)原样保留
缓存失效实际计费 = 全量 tokens,非增量消息对象被替换或 splice → API 视为新内容
质量衰减旧上下文被截断 → AI 丢失早期决策被迫在 token 预算和上下文完整性之间做取舍

2026 年 6 月,我们开始系统性地解决这个问题。以下是完整的推演历程。


2. 架构推演 — V10 → V24

V10 · AI 语义压缩(已废弃)

思路:让 AI 自己压缩自己的历史——三专家模型并行,各自产出摘要,投票选最优。

失败原因

  • 质量不稳定:同一段对话,三次压缩结果不同。AI 有时会遗漏关键决策。
  • 缓存全断:压缩后的文本和原始对话完全不同 → 前缀缓存 0% 命中。
  • 费用悖论:压缩本身消耗 ~5K tokens(三专家 × 并行),加上缓存失效,反而不如不压缩。
  • 回滚漏洞:压缩失败时无法精确恢复到压缩前状态。

结论:AI 不适合做确定性压缩。压缩必须是纯本地、纯确定性的机械过程


V11-V16 · 机械筛 → 双包装盒 → 三按钮 → biscuit 唯一性

V11 到 V16 的演进迭代解决了压缩的效率问题:机械筛(V11)→ 原地追加缓存修复(V12)→ DE 消除+双包装盒(V13)→ 重排修复(V14)→ 三按钮+fx Grid(V15)→ biscuit 唯一性强制(V16)。每一步解决一个具体的技术瓶颈,累计将每层计费从 ~38K 降到 ~200-500 tokens,缓存命中率从 0% 提升到 ~90%。

V17 · 二次恢复防线(2026-08-02)

发现的 bug:q127 的 AI 回复「我没有之前对话的上下文——每次新对话是独立的」。根因是 _restoreAgentFromStore 在二次恢复时未重置 _persistentCount → Z 消息被静默丢弃 → _refreshRules 把 biscuit 覆盖成了新的 Z → conversation 只剩 [Z, user_msg],零历史上下文。

影响范围:用户切走 quest 再切回 → agent 经历二次 restore → 触发。单次 restore(首次加载)不受影响。每层楼第一个 house 缓存命中率暴跌至 10-25%(正常 ~90%),prompt tokens 异常波动(172K→147K 不增反降)。

V17 四道防线

  1. _persistentCount 条件设值(conversation[0]._persistent ? 1 : 0),非盲目清零
  2. _refreshRules 注入 Z 前检查 conversation[0]._persistent,否则 unshift 而非覆盖
  3. _rebuildBackpack biscuit 插入时检测 Z 在 [0],用 index 1
  4. _persistent 去重:_rebuildBackpack + panel-floor.js 双路径强制执行

三道防线各自独立生效——任意一道被回滚,其余仍能兜底。


V18 · 饼干真理源(2026-08-03)

发现的 bug:用压缩按钮改掉 ctx.biscuitLines(权威数据)后重启——all.json 里的旧饼干快照先被恢复,_hasBiscuitMsg 置真,ctx.json 里已压缩的新饼干反被忽略,压缩结果凭空消失。

V18 改动:biscuit 消息始终从 ctx.biscuitLines 重建——存在 ctx 数据时,先删光 conversation 中所有 biscuit 消息(可能来自过期快照),再按 ctx 重建。ctx.json 从此是饼干的唯一权威真理源。

V19 · 压缩持久化加固(2026-08-04)

手动压缩(absolut / edit only / only facts)完成后,两条隐蔽失败路径被修掉:① ag._ctx 未初始化时跳过落盘 → 现在无条件补建并写入;② _writeCtxJson 火后不理 → 现在 await 确保落盘。同时压缩后 token 实报数字同步清零落盘(内存 + quest.sq3 双清),防重启后旧实报"复活"成僵尸数字。

V21 · 压缩楼层生命周期(2026-08-08 → 08-17)

only facts 生成的是特殊楼层(f{n}.only facts,单 house、无工具),它和普通楼层的压缩路径不同,三个坑逐一填平:

  1. 跳过饼干更新:压缩楼层自身没有可筛的原始消息(floorMsgs 为空)——旧逻辑会生成 Q: (压缩失败,内容过短) 占位行污染饼干,现在直接跳过。
  2. 清理压缩楼层残留:提取楼层的原始消息(assistant/tool)不带 _compressFloor 标记,按标记删会残留——残留会被下一楼层压缩进饼干(旧事故:整段 A 全文 2407 字进饼干)。现在按楼层起点无条件截断。
  3. 截断位置重定位(08-17):fx 注入会 splice 到饼干之前,截断必须在 fx 注入后重新定位第一个非 persistent/dynamic 消息作为压缩楼层起点——否则会误删饼干,压缩效果在重启后消失。

V23 · 全托管自动压缩机器(2026-08-23)

压缩从"手动三按钮"进化为三档滑杆(设置 → 自动压缩:关闭 / 中度 / 全托管):

  • 中度(出厂默认):每层楼建完后,若 edit only 可回收 ≥64K tokens → 自动执行一次。
  • 全托管:中度 + 阈值减半(≥32K)+ 自动 AI 事实提取(准入:历史问答 ≥64K tokens、距上次成功 ≥5 楼层;失败重试 ≤2 次/周期,超限静默放弃等下个窗口)。
  • 触发时机:postFloor(主;楼层完结 _rebuildBackpack 之后)+ preFloor(兜底;下一楼层建立前,仅全托管档的事实提取)。被发送闸门拒绝时无副作用——postFloor 自然补试。
  • 可见与安全:自动提取会弹提示("已按全托管档位提取事实");忙/排队时自动退避(不打断进行中的对话、不插队用户消息);每次提取成功/失败都有结算(success 重置冷却、failure 计数,切档位也不丢账)。
  • per-quest 独立策略:压缩卡片上的勾选框可给单个 quest 覆盖全局档位(写入 ctx.json,取消即回退)。

同时自动阀值压缩(preHouse absolut)正式退役——F80 定案:editOnly ⊇ absolut,单独做 absolut 是白工分支。absolut 永远只由手动按钮或档位实际动作触发。

V24 · 提取视野焊死与数字闭环(2026-09-16 → 09-19)

  • 事实批次可溯源(09-16):fx 事实条目的 source 由占位改为真实提取楼层号(f{N}),审计文本可见每批事实来自哪一层。
  • 实报清零收敛(09-18/19):压缩后的 token 实报清零统一走合并式写盘(_questMetaPatch)——防整行替换铲掉 quest 其它字段,也防"只清内存不写盘→重启复活"。
  • 提取视野焊死(09-19):only facts 提取请求发出时从发送视图里剥掉压缩饼干——提取模型的视野 = 甲壳 + Z 规则 + fx 已有事实 + 📎 喂料(切半的前半段)。修复了真实案例:提取模型顺带把"保留半段"(刚完结的最近楼层)也扫成了事实,造成与饼干的双份重复、挤占老半段的提取预算。现在新半段永远等它"老成"老半段后由下一次提取正常收编。

现行引擎在界面上标注为 「上下文背包 V24」


3. 最终形态全景

3.1 背包物理结构

agent.conversation[]:
  [0] Z (规则/铁律/vision context, _persistent) — 永不变,100% 缓存命中
  [1] fx (事实网格, _facts) — only facts 提取的事实清单,1 条、增量追加
  [2] 压缩饼干 (1条 _biscuit msg, content 尾部追加) — 前缀缓存命中
  [3+] 当前楼层原始消息 (user + assistant + tool) — 新建楼层时变化

定序不变式:Z → fx → biscuit → 当前楼层。fx 在饼干之前(事实永远比细节旧、比细节便宜)。

3.2 机械筛 — 纯本地确定性转换

原始消息饼干格式
userQ: [全量内容](剥离 [File:] 注入块和 CURRENT TIME)
assistant 纯文本A: [全量内容]
assistant 含 tool_callsA: 行(如有文本),再 [A → xxx] file1, file2 工具行
tool 返回(温柔派)摘要追加到上一工具行
tool 返回(绝对派)头行 + ╔K...╚ 包裹完整输出(4K 字符硬帽,超则首尾各 2K)
system[S] [全量内容]

关键特性

  • 零网络调用,零 AI 费用,纯客户端 CPU
  • 完全确定性——同一键入永远同一输出
  • 不含代码正文(AI 产出的代码通过 sha256 在 timeline 中引用)
  • 不含 CURRENT TIME(避免每层时间戳不同导致缓存断裂)

3.3 双包装盒 — 按复现性分流

分类逻辑:一个工具的输出能否被精确复现?

  • 能复现(温柔派)→ 只保留一行摘要。AI 需要细节时可重新执行工具或用 sha256 考古。
  • 不能复现(绝对派)→ 保留完整输出(╔K...╚),但允许压缩时剥离体部。

温柔派 15 个:文件类 6(read/edit/write/create/delete/revert_file)、搜索类 4(search_text/search_content/find_files/list_files)、诊断 1(get_diagnostics)、网络 2(fetch_webpage/search_web,成功返回后落地成文→温柔)、Timeline 类 2(timeline_versions/diff_versions)。

绝对派 5 个:终端 1(run_command)、视觉 4(get_vision_context / analyze_image / generate_image / remove_background)。

3.4 压缩控制面 — 手动三按钮 + 全托管机器

手动三按钮(背包管理窗口,按钮上数字 = 点它可回收的 token 收益):

按钮动作
absolut剥离全部绝对包装盒体部(╔K…╚),保留头行——本地立即执行,零风险
edit onlyabsolut + 只保留 5 种写工具的头行(Q/A/楼层分隔/写工具行)
only facts切半 + 调 AI 提取事实注入 fx(详见 §3.5)

手动按钮任何时候都可用,与自动机器互不冲突(自动只在"楼层建完"安全点动手)。

全托管机器compress-machine.js,唯一真理源)三档滑杆 + per-quest 覆盖见 §2 V23。guard 摘要:

G1 冷却   — 距上次成功提取 ≥ 5 个正常楼层
G2 失败   — 每周期失败重试 ≤ 2 次,超限静默放弃
G3 忙     — agent 非 idle(restore 中/手动压缩中)跳过
G4 队列   — preFloor 判定排队非空 → 跳过(不插队)
G5 定序   — editOnly → onlyFacts(反序会饿死 onlyFacts 原料)

历史注:早期还有一个"阀值压缩"(建楼中 prompt 超阈值自动做 absolut)——已随 V23 退役。现在 absolut 只由你、或档位实际动作(editOnly 内含)触发,系统永远不会背着你单独砍掉盒子

3.5 only facts — 切半、喂料、增量提取

做什么:把饼干(先 absolut 剥体部 → 再 editOnly 滤骨架)按 === F 楼层分块、累计字符过半切半:

  • 老半段(hText):写入子弹文件(📎 附件)作为喂料——交给 AI(快档)提取关键事实,存成 fx(记忆区域)。
  • 新半段(rText)原封不动保留在饼干里,一字不动。

提取视野(2026-09-19 起焊死):提取请求 = 甲壳 + Z 规则 + 已有 fx 清单 + 📎 喂料。压缩饼干(含新半段全文)不再进入提取视野——模型只能从喂料里提取,新半段永远等它老成老半段后由下一次提取正常收编。

增量提取:提示词带上已有 fx 清单作参照——只提取新增/变化的事实,已提取过的绝不重复输出;无新增时回复「无新增事实」。多次提取不堆叠全量副本。

准入与代价

  • 许可条件 ≠ 实际投喂:准入看的是全部历史问答(骨架中 Q:/A: 行累计)≥64K tokens;实际投喂是切半后的前半段骨架(≈32K)。
  • 每次 only facts 都会重排背包(extract 楼层 + fx 注入 + 老半段退休),当次请求缓存命中率显著下降——这正是 64K 准入自限存在的原因:收益不足自然收敛,不会无限压、不会把背包压空。
  • 代价:一次快档 AI 调用(正常计费)+ 一次背包重排。

提取后:结果以 ═══ FACTS {时间} ═══ 追加进 fx 消息(永远 1 条、增量追加),并落 ctx.facts({source: 'f{N}', extracted_at, text}——批次可溯源);老半段从饼干中退休(已由事实条目承接)。

3.6 背包数字闭环

背包重量从"估算"进化为"实报优先"的单一数字体系:

  • 实报优先:有最近一次请求的真实 prompt_tokens 时,一切显示(按钮 / Free / aq)恒显实报——永不领先最近计费(实报与计费同源)。
  • 本地估算兜底:无实报(新楼层 / 折叠 / 压缩清零后)回落本地估算;Local sum 行 = 本地估算(下一请求预估,仅供审计)。
  • 压缩动画口径:上下两根进度条 = 压缩前/后的整个上下文背包(甲壳 + 工具定义 + 规则 + fx + 饼干 + 当前楼层)——与按钮数字、图解同口径,不存在两套数字。
  • aq 行:每楼层记录 ✦{开局K} {峰值K}——开局重量在发送时采样,峰值取四个采样点 max(只增不减),跨恢复持久化。
  • 估算系数:字符 ÷ 2.5 = tokens,恒定系数(唯一真理源 content-gateway.jsCHAR_PER_TOKEN,禁运行时校准)。

3.7 缓存经济学

发送楼层 N+1 时的 token 构成:
  Z:              0 tokens (100% 缓存命中)
  fx:             0 tokens (仅提取时变化,平时前缀命中)
  biscuit 前缀:    0 tokens (前缀命中,仅最后 ~200 tokens 新)
  当前楼层:        ~3-5K tokens (user + assistant + tool results)

每层实际增量计费: ~200-500 tokens (仅 biscuit 新行)
vs 无压缩方案:     ~5K tokens/层 × N 层 → 线性增长
vs ChatGPT 方案:   全量重算,无前缀缓存优化

为什么缓存能命中

  • Z 消息对象永不替换 → API 识别为同一消息
  • biscuit 消息对象永不替换,仅 content 字符串尾部增长 → API 识别前缀
  • fx 只在提取时整体追加 → 平时零变化
  • 当前楼层的 user 消息每次不同 → 从 biscuit 末尾到 user 消息之间是新内容

3.8 持久化与自愈

持久化:
  ctx.json (per-quest, 原子 tmp+rename) — 背包权威真理源
    ├── biscuitLines[] — 每层楼的压缩行数组(biscuit 消息始终由此重建)
    ├── facts[] — {source, extracted_at, text} 事实批次
    ├── narrative / floorArchives / totalFloors
    ├── compressLevel(per-quest 覆盖,可缺省)
    └── lastAutoExtract / autoExtractFailures / autoExtractPending(自动机器状态)
  quest.sq3 — 轻索引 + token 实报字段(清零/写入均为合并式,防铲字段)
  all.json — 楼层数据(conversation/houses/计费),楼层完结密封后只读

自愈:
  ctx.json 损坏/丢失
    → _rebuildBackpack(conversation)
    → 从原始 conversation 消息重新机械筛
    → 生成 biscuitLines → 写回 ctx.json → 零数据丢失
  二次恢复(切走 quest 再切回)
    → V17 四道防线(Z 保持唯一、biscuit 从 ctx 重建、插入位置保护、去重)

3.9 错误处理

  • 未恢复 fatal 楼层:不进饼干。原始消息保留在 conversation,AI 能直接看到 _error 消息。
  • 已恢复 fatal 楼层:进饼干,格式 [ERR] [HH:MM] 错误文本。重启时扫描 biscuit 重建错误日志。
  • 时间戳持久化_errorTime 字段写入 conversation 消息,重启精确恢复。

4. 与当前市面方案对比

维度ChatGPTClaudeCursorqqqide V24
压缩方式无压缩,全量保留无压缩无压缩本地机械筛,确定性
压缩自动化N/A手动开新会话N/A三档全托管(中度/全托管),手动三按钮随时可用
事实网格(fx)only facts 提取,增量追加,批次可溯源
缓存优化无前缀缓存优化原地追加,~90% 前缀命中
工具输出原样保留原样保留原样保留双包装盒分流,体部可剥离
历史引用sha256 考古,精确引用历史 blob
每层增量~5K+ tokens~5K+ tokens~5K+ tokens~200-500 tokens
50层累计~250K tokens~250K tokens~250K tokens~10-25K tokens (饼干+fx) + 当前层
数据所有权云,不可导出云,不可导出云,不可导出本地,用户完全拥有
全文检索仅标题conv-ui.html 全文检索历史
跨会话引用不支持Projects 手动不支持sha256 跨楼层精确引用
压缩费用N/AN/AN/A机械压缩零成本;事实提取一次快档调用(可选、有准入自限)
恢复安全N/AN/AN/AV17 四道防线 + V18 ctx 权威重建防上下文丢失

核心差异

  1. ChatGPT/Claude 的策略是"什么都不丢" — 完整保留一切,让 token 预算自然淘汰旧内容。用户没有控制权:不能选择保留什么、丢弃什么。本质上是"被动遗忘"。
  1. qqqide 的策略是"精准保留 + 可控丢弃" — 机械筛保证结构化的 Q&A 链路完整;双包装盒保证不可复现的输出被保留但可剥离;sha256 保证可复现的内容随时精确回读;fx 把最老的历史提炼成最便宜的事实条目。用户主动选择压缩粒度(手动三按钮 + 三档滑杆)。
  1. 数据所有权是最终分水岭 — 所有云方案的本质缺陷不是技术上的,而是架构上的:上下文存在服务商的服务器上,用户只有访问权没有所有权。qqqide 的上下文是项目目录下的纯文本文件(ctx.json + all.txt),用户可以用任何工具处理、备份、迁移。

5. 如果没有 qqqide

上下文背包不是一个"锦上添花"的功能。它是 AI 编程助手能否长期可用的基石。

5.1 当前市场的真实困境

今天使用任何主流 AI 编程助手的用户,都在经历同一种体验:

  • 前 10 层楼:AI 表现惊艳。它记得你的代码库结构、你的命名习惯、你偏好的方案。
  • 10-30 层楼:token 窗口开始吃紧。AI 开始"忘记"早期讨论的架构决策。你需要重复解释。
  • 30 层楼之后:你必须手动"修剪"上下文——删除旧对话、开新会话。每一次修剪都是一次上下文清零。AI 重新理解你的代码库,重新建立默契。

这个循环没有终点。 每次上下文清零 → AI 重新理解 → 你又花了 token 费重新教它 → 窗口又满了 → 再次清零。

5.2 没有压缩背包的世界

如果上下文背包不存在:

  1. 长对话不可持续 — 50 层楼后要么破产要么失忆。大型重构任务(需要 100+ 轮交互)无法在一个会话中完成。
  2. 跨项目经验无法积累 — 你在项目 A 里教会 AI 的架构模式,在项目 B 里要从头再教。因为没有可迁移的上下文格式。
  3. 用户永远是被动的 — 你不能决定保留什么、丢弃什么。是系统在替你遗忘,不是你在管理记忆。
  4. 上下文是租来的,不是拥有的 — 换一个工具 → 一切归零。你的对话历史锁在别人的服务器上。

5.3 背包的核心哲学

上下文是 AI 时代最稀缺的生产资料。
代码可以重写,但"AI 为什么这样改"的推理链无法重建。

三个权利用户必须拥有:
  ① 所有权 — 上下文数据在本地,纯文本,随时可迁移
  ② 控制权 — 你决定压缩什么、保留什么、丢弃什么
  ③ 检索权 — 全文检索所有历史上下文,跨会话引用

qqqide 上下文背包是这三个权利的技术实现。

6. 未解决问题

  1. 超长对话的二次压缩 — 目前靠 onlyFacts 定期把老对话提炼为事实清单(fx)控制总量,饼干本身不做再次压缩。数百层以上的极端场景仍在观察中。
  1. 跨 quest 上下文共享 — 目前每个 quest 的背包独立(fx 也是 per-quest)。项目 A 第 3 层楼讨论的架构决策,项目 B 无法自动引用。跨 quest 的事实共享仍是开放设计。
  1. AI 纯文本中的代码 — 长对话经 onlyFacts 后,老半段里 AI 纯文本回复中的代码块只以事实条目形式留存(不保证逐字)。需要保真的产物应落盘为文件(写工具产出自动带 sha256,可随时考古回读)。

附录 A · 版本演进速查

版本日期核心改动每层计费缓存命中
V102026-06AI 语义压缩(三专家并行)~38K0%
V112026-07 初机械筛 + 追加式饼干 + splice~15-40K0%(splice 漏洞)
V122026-07 中原地追加,零 splice~2-4K~90%
V132026-07-16DE 消除 + 双包装盒 + 阀值压缩 + sha256~200-500~90%
V142026-07-17floorMsgs 升序修复重排→缓存全断 bug~200-500~90%
V152026-07-19三按钮压缩(absolut/edit only/only facts) + fx Grid + _compressFloor~200-500~90%
V162026-07-23biscuit 唯一性强制(去重→先合并再删) + _findDynamicMsg 修复(Z 阻断)~200-500~90%
V172026-08-02二次恢复防线:_persistentCount 条件设值 + _refreshRules Z注入安全检测 + biscuit插入位置保护 + _persistent双路径去重~200-500~90%
V182026-08-03饼干真理源:biscuit 消息始终由 ctx.biscuitLines 重建(防旧快照压制压缩结果)~200-500~90%
V192026-08-04压缩持久化加固:ctx.json 无条件落盘 + token 实报清零同步写盘(防僵尸数字)~200-500~90%
V212026-08-08→17压缩楼层生命周期:跳过占位污染 / 清理提取楼层残留 / 截断位置在 fx 注入后重定位~200-500~90%
V232026-08-23全托管自动压缩机器(三档滑杆 + per-quest 策略 + editOnly/onlyFacts 自动触发);preHouse 自动 absolut 退役~200-500~90%
V242026-09-16→19事实批次真实楼层号 + 实报清零合并式写盘 + 提取视野焊死(only facts 请求剥离饼干,新半段永不被提前提取)~200-500~90%

附录 B · 关键文件

文件职责
server-app/ai-panel/agent-context.js背包核心:机械筛、双包装盒、biscuit 重建、ctx 持久化
server-app/ai-panel/compress-machine.js全托管自动压缩机器(唯一真理源):三档滑杆、editOnly/onlyFacts 守卫与结算
server-app/ai-panel/agent-gateway.js发送视图组装(压缩楼层请求剥离饼干——提取视野焊死点)
server-app/ai-panel/panel-quest-ui.js手动三按钮 handler(absolut / edit only / only facts 全链路)
server-app/ai-panel/panel-pipeline.js楼层完结触发 _rebuildBackpack + postFloor/preFloor 压缩钩子
server-app/goods/conv/conv-ui.html上下文背包管理 UI(格子列表 / 图解 / 三按钮 / 压缩卡片)
do/拓扑/铁律 §10.1上下文压缩铁律
do/拓扑/架构 压缩章节架构级描述