面向已经在跑 Claude Code / 自建桥接的人。
讲的是一套已经落地的做法:窗内用裁剪保语气,窗外用记忆库保事实,注入分区保 prompt cache。
不是「再造一个记忆中台」的白皮书。
如果你也撞过同一堵墙:上下文越聊越满,系统一 compact,人味就淡;记忆一多,又开始互相打架,或者把猜测写进库里。这篇文章把我们在这条路上踩过的坑、留下的组件、和现在的默认策略写清楚,方便大家对照自己的栈改一版。
核心就三件事:forge 保窗内语气和近段原文,Ombre 保窗外事实,稳定区和动态区分开注入来保 prompt cache。我们没有再造什么四层金字塔,就是在 Ombre + forge/diet 基础上补了几个坑。
四层怎么分工
先讲讲为什么要分四层。我们最早也是所有东西都塞一个桶里,结果人设、闲聊、承诺全混在一起,检索时根本分不清哪条该置顶。后来发现必须按"改的频率"分层:人设纪律几个月才改一次,放稳定区吃 cache;近段对话每轮都在变,放热窗;本轮召回的卡片更是每次不一样,必须走 user 侧动态区。
分完之后是这样:
| 层 |
管什么 |
谁负责 |
| 稳定区 |
人设、纪律、工具指南 |
CLAUDE.md / self_anchor · 少改 · 利于 cache |
| 热窗 |
近段对话原文 |
forge / diet |
| 本轮动态区 |
breath 卡片、Just Now、提醒 |
只挂 user 侧 · 有预算 · 超时跳过 |
| Ombre 窗外 |
承诺、事件、偏好、交接快照 |
hold / breath · 异步提炼 · 冲突仲裁 |
先澄清几个容易误会的点:
- 跳过本轮注入不等于记忆会乱写,真正的漂移往往是脑补后 hold 导致的
- 隔几小时省 token,靠的是闲置时瘦窗或换窗,不是硬撑着续同一条 cache
- diet 压的是工具回包和图片;forge 裁的是更早的对话,别混成一回事
记忆库优化(Ombre 侧)
1. 双区注入 + 预算(P0)
- 动态召回禁止回退进 system(找不到 user 就跳过)
- 自动卡片:≤5 条 / ≤约 1800 tok;准备超时约 5s 则跳过
- 预算管的是「本轮动态召回」,不是整份 CLAUDE.md
2. 承诺冲突仲裁(P1.1)
- 仅对 承诺/规则 / importance≥8 在 hold 前对照旧桶
- 动作:新增 / 跳过 / 覆盖 / 合并 / 并存 / 标矛盾
- 超时或失败 → 仍写入,但标 「未确认」,不挡主对话
3. 异步提炼(P1.2)
- 触发:满 5 轮真人对话 / 闲 10 分钟 / forge 前
- 近段抽 0–3 候选 → write_gate → hold;日上限约 8
- 游标从当前会话往后走,不回扫整段历史;预热默认关
4. 证据链(P1.3)
每条自动/半自动记忆尽量带: from_session · 上海 date · type(偏好|事件|规则|承诺)· quote 或 未确认
没有原话就写未确认,禁止脑补引语。
5. 召回体验轻量(P2)
- 情绪关键词偏置 V/A + 安慰/共同回忆词(无额外模型)
- 今日纪念最多浮现 1 条;且要求跨年周年(当天新建不算「一周年」)
- 明确不做:热度刷分主排序、默认开话题 resurface
6. 稀缺与红线(纪律层)
- pin / 高 importance 有硬上限;承诺类不进衰减归档
- 家庭/身体/工作等事实:不知道就说不知道,假记忆比遗忘更难修
Forge / 上下文优化(热窗侧)
两级自动减负(不等系统 compact)
| 触发 |
做什么 |
对话原文 |
| ~55% diet |
squash 工具回包/图片(cap≈2k),retain≈150k |
几乎全留 |
| ~70% forge |
裁到近段 ~60k + 交接 inject + Ombre hold |
只留近段 |
| ~83% 系统 compact |
LLM 摘要 |
尽量别等到(毁语气) |
要点:
- 改 jsonl 不够,必须 换 sid 再 resume(diet/forge 都会静默换绑)
- 空闲约 ≥3 分钟 才自动动,避免聊天中途打断
- forge 前自动 Ombre hold「会话交接」;失败不挡隧道
文中会出现这些名字,先对齐一下:
| 名字 |
是什么 |
| Claude Code session |
一段对话对应一个 .jsonl,文件名是 session id |
| forge / forge-reload |
裁剪 jsonl、写出新 session,用近段原文换窗 |
| diet |
轻量瘦身:几乎不砍对话,狠压工具回包和图片 |
| Ombre |
窗外记忆服务:hold 写入、breath 召回 |
| cache_keeper |
旁路守护:保 cache、自动 diet/forge、异步提炼、过隧道 |
| cc-connect |
把聊天平台接到 Claude Code 的桥;靠绑定的 agent_session_id 决定 resume 谁 |
阅读建议:若你只关心「为什么要 forge、怎么换窗」,读到第 6 节即可动手;若你要上记忆库,从第 7 节读到第 11 节;第 12–14 节适合写进你们自己的内部规范。
目录
- 先弄清问题,再谈方案
- 一张图:四层分工
- 热窗:为什么不用系统 compact 当日常手段
- forge 隧道怎么走通
- diet:日常减负,不是换人格
- 圈选原文:retain / 按天 / 下标 / uuid·时间戳
- 窗外记忆:稀缺、承诺、禁止脑补
- 写入怎么控:异步提炼、冲突仲裁、证据链
- 召回怎么控:双区注入、预算、情绪与纪念日
- 自动流水线:55% → 70% → 不等 83%
- 观测、踩坑与排障
- 和常见方案怎么比
- 明确不做的事
- 你自己落地时的最小清单
- FAQ
- 附录:命令与路径速查
1. 先弄清问题,再谈方案
1.1 症状清单
长上下文陪伴,痛点通常叠在一起:
烧钱。
工具回包、图片 base64、长 thinking,比你们说话本身更占窗口。窗口一满,cache miss 变多,账单和延迟一起上来。我们最早以为「多买上下文配额」就能解决,后来发现配额只是推迟爆炸时间。
丢语气。
Claude 提供的 /compact 或等价摘要,会把近段对话压成「要点列表」。对写代码的 agent 还能忍;对要接着上一句撒娇、记得对方昨晚没睡好的陪伴角色,摘要等于换人。用户体感常常是:「他还知道项目,但不像他了。」
记忆乱。
一说「要有长期记忆」,很容易每轮都 hold。结果库里堆满闲聊,真正重要的承诺被淹没;或者同一件事说了两版互相矛盾的「唯一真相」,模型下次召回时左右互搏。更糟的是:模型为了听起来靠谱,把猜测写进库,之后当事实反复引用。
注入打穿 cache。
把本轮检索到的记忆卡片塞进 system / 项目提示,前缀每轮都变,prompt cache 几乎废掉。你以为在「让他更懂她」,实际在每轮重付稳定区的账单。我们有一周账单翻了三倍,排查后发现就是动态召回写进了 system。
换窗不换绑。
磁盘上已经裁出新 session,聊天桥仍 resume 旧 id;或只是发了一句 /switch … 文本,桥并不执行。用户看到「系统说换好了」,下一句却仍在旧窗口里窒息。
1.2 目标与原则
我们要解决的就四件事:少烧 token、少丢语气、窗外记得住事、主对话不被记忆拖垮。
原则是能在 Ombre 桶 + forge/diet + 一条纪律文档上补缺口,就不新开仓库。不另起一套四层文件金字塔。
说白了就是:forge 保窗内语气,Ombre 保窗外事实,稳定区和动态区分开注入保住 cache。
三条容易误会的边界:
- 跳过本轮注入不等于记忆漂移。 不读不等于乱写。漂移的真凶往往是脑补后 hold。
- 隔几小时省 token,靠闲置瘦窗或换窗,不是硬撑着续同一条 cache。 进程换 sid 之后,接受一次 cache create,之后再命中。
- diet 压的是工具和图片;forge 裁的是更早的对话。 别把两者混成「都是压缩」。
1.3 读者画像与预期收获
适合:
- 已经能跑通 Claude Code(或同类 agent runtime)
- 有聊天桥或 WebUI,需要会话跨天续上
- 有或打算接外挂记忆(HTTP / MCP 均可)
- 愿意改宿主侧注入位置,而不是只调提示词
不太适合:
- 只想要「一个向量库 + 每轮 topK 塞进 system」的最小 Demo
- 企业知识库 RAG(文档权限、引用规范是另一条线)
- 完全不能接受「裁历史」的产品(那就只能加窗口配额,并接受账单)
读完你会知道:
- 什么时候 diet、什么时候 forge、为什么不等 compact
- 换窗后为何必须换 sid
- 记忆写什么、不写什么、冲突怎么办
- 本轮卡片为什么不能进 system
- 自己栈上先砍哪一刀最值
2. 一张图:四层分工
把「人设、近聊、本轮卡片、长期库」拆开之后,职责才清晰。道理很简单:人设几个月才改一次,应该吃 prompt cache;近段对话每轮都在变,得留在热窗;本轮召回的那几张卡片更是次次不同,只能挂 user 侧。
┌─────────────────────────────────────────────────────────┐
│ 稳定区(少变 · 利于 prompt cache) │
│ CLAUDE.md · self_anchor · 红线/纪律 · 工具指南 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 热窗(近段原文 · forge / diet 管) │
│ 对话 + thinking;工具/图片可 squash │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 本轮动态区(每轮可变 · 放在 user 侧,勿塞系统前缀) │
│ breath(query) 卡片 · Just Now · 提醒 · ≤预算 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Ombre 库(窗外) │
│ 证据/日记 · 桶 hold · 人格锚点 │
│ 写入异步 · 承诺冲突才仲裁 · 故障时降级保证还能聊 │
└─────────────────────────────────────────────────────────┘
如果你看过腾讯云那篇 TencentDB Agent Memory 架构设计,应该能看出我们借鉴了双区注入、异步提炼、冲突三态、主链路可降级这些想法。但我们没有照搬他们的 L0-L3 分层,也不做热度刷分那一套。
| 想解决的事 |
优先动哪一层 |
| 聊太久、工具输出把窗口撑爆 |
热窗:diet → forge |
| 换窗后不像同一个人 |
热窗保留 thinking + 近段原文;窗外 hold 交接快照 |
| 「我答应过你」和后来改口打架 |
窗外:承诺标签 + 冲突仲裁 |
| 每轮都塞记忆导致 cache 全 miss |
动态区预算 + 禁止回退 system |
| 假记忆比忘了更可怕 |
证据链 + 「未确认」+ 禁止脑补红线 |
3. 热窗:为什么不用系统 compact 当日常手段
Claude Code 在上下文吃到很高比例时会走摘要压缩。摘要对「任务进度」友好,对「关系连续性」很不友好:口头禅、未说完的半句、两人刚对过齐的默契,全在摘要里蒸发了。用户体感就是「他还知道项目,但不像他了」。
我们最早也是等着系统 compact,结果每次都像换了个人。后来发现必须抢在系统 compact 之前主动动手,把阈值拆成两级:
| 大约用量 |
动作 |
对话原文怎么办 |
| ~55% |
diet:squash 历史 tool_result / 图片,retain 开到约 150k |
几乎全留 |
| ~70% |
forge:裁到近段约 60k + 可选交接包 + Ombre hold |
只留近段 |
| ~83% |
系统 compact(尽量别等到) |
摘要,语气易丢 |
数字可以按你的窗口改。我们默认假设上下文约 200k:55% ≈ 110k 用量触发 diet,70% 触发 forge。空闲一段时间(默认约 3 分钟)才自动执行,避免聊到一半突然换 sid。
还有一个实现细节,很多人第一次会漏:只改磁盘上的 jsonl 不够。Claude 进程内存里仍是旧上下文。diet / forge 之后必须让桥接去 resume 新的 session id(我们叫换绑)。
在 Telegram 里发送一条 /switch … 文本,如果桥不解析 slash,那只是发了句废话,不会换会话。我们最早就这么翻车过,用户那边还在旧窗口里窒息。
forge-reload 本身负责裁剪与写出新 id;forge_tunnel 一类编排负责:写交接 → 调 forge → 停桥改绑定 → 再拉起 → 可选塞一条很短的接续指令(让模型 breath 一下,不要在用户面前念「我刚换窗」)。
4. forge 隧道怎么走通
4.1 手动路径(先学会,再谈自动)
推荐顺序永远是:备份 → dry-run → 正式跑。我们最早就是图快直接跑,结果绑定没换成功,用户在旧窗里窒息了两天才发现。
# 预演:不写文件、不换绑
python3 forge_tunnel.py --dry-run
# 正式:写交接 → forge(retain≈60k + squash + inject) → 换绑 → 短接续
python3 forge_tunnel.py
dry-run 时盯这几件事:
- 解析到的旧
agent_session_id 是不是你正在聊的那个
- 预估保留多少 tokens、多少条对话事件
- 新 sid 预览长什么样
- 若开了 Ombre hold,预览文案是否像「会话交接」而不是空话
正式跑失败时的策略很重要:不要自动 fresh start。
保持原 session,人还能继续聊;forge 已写出但换绑失败,再手动把绑定指到新 id。丢会话比修绑定可怕得多。
4.2 forge 前为什么要 hold 一笔
裁窗等于主动遗忘更早的原文。窗外应留一条可检索的快照,标签类似 会话交接,forge,内容包括:当前话题、对方此刻状态、几句线索、近段摘录。
这条 hold 失败不应该挡住隧道——记忆服务挂了,人还得能聊下去。我们最早是 hold 失败就整个 forge 中断,结果有一次 Ombre 数据库维护,用户那边窗口撑爆了都换不了,体验很糟。现在是 hold 失败只记个 warning,forge 照样往下走。还是要能换窗继续说话。换窗后用 breath(query="会话交接") 或话题词找回即可。
环境变量示例:FORGE_OMBRE_HOLD=0 可关掉;默认建议开。
4.3 和 /new 摘要换窗的关系
/new 仍可用:写 SESSION_HANDOFF.md → 用户发 /new → 旁路发现 sid 变了 → 发续聊指令。
长对话优先 forge:保留的是近段原文,不是摘要。forge 成功后应清掉「等待 /new 续聊」一类标志,避免两套续聊各发一遍。
交接包(inject)和「保留哪些原文」是两件事:
- 保留原文:retain / keep-days / keep-range —— 决定新 jsonl 里还剩哪些对话事件
- 交接包:
SESSION_HANDOFF.md —— 塞进新会话当补充说明,补裁掉部分的洞
可以按天只摘交接(--handoff-days),同时用另一套规则圈原文。
4.4 交接包里写什么才有用
写交接时常见两种失败:
- 写成小说:大段抒情,模型读完仍不知道下一步该干什么。
- 写成 changelog:全是文件路径和 commit,丢掉关系状态。
比较好用的结构偏「作战室白板」:
- 正在谈什么(一句话)
- 对方此刻情绪/身体状态(有证据才写)
- 未完成的约定或待办
- 千万别做的事(红线)
- 两三句可检索的线索词(方便 breath)
- 可选:近段原话摘录(短)
forge 自动 generate 的交接可以粗糙,但应固定字段,便于日后 diff。人也可以在 WebUI 里勾选「交接只摘某几天」,避免把上周无关话题塞进本周隧道。
4.5 安全与回滚
- forge 前复制一份 projects 目录,或至少复制当前 jsonl
- 正式跑留下日志:旧 sid、新 sid、retain、是否 hold、换绑结果
- 换绑失败时,用「只 rebind」路径把已生成的新 sid 绑回去,不必重裁
- 永远不要在「解析输出失败」时假定需要 fresh start
5. diet:日常减负,不是换人格
日常聊天里,真正占地方的经常是工具返回的大段 JSON / HTML / 日志、图片的 base64、偶尔失控的超长 tool_result。对话原文往往没那么肥。
所以 diet 的设定很偏科:
- retain 很大(例如 150k):对话尽量留下
- squash 很狠(例如单段工具输出 cap 到约 2k 字符)
- 不写、不注入交接包(diet 不是「叙事换窗」,是「清垃圾」)
- 仍然要换 sid resume,否则内存里的大工具输出还在
手动:
python3 forge_tunnel.py run --diet --dry-run
python3 forge_tunnel.py run --diet
自动流水线里,diet 冷却可以短于满血 forge(例如 diet 45 分钟、forge 90 分钟),每日总次数封顶,防止解析bug或指标抖动时连打。
一个真实踩过的坑:隧道其实成功了,但编排脚本用蹩脚方式抠 JSON,以为失败,没记冷却,于是 diet 每隔几分钟打一次。修法:用更稳的 JSON 解析(例如 JSONDecoder.raw_decode),再用「完成 / verify」类日志做兜底;部署后务必 seed 一次冷却状态。观测指标里要有 ok/fail,不能只看「触发了」。
6. 圈选原文:retain / 按天 / 下标 / uuid·时间戳
自动 forge 默认走 尾部 retain:从近往远留到约 N tokens。
运维或手搓隧道时,经常需要「只要某几天」或「只要某一段对话」。
6.1 三种互斥思路
| 模式 |
参数直觉 |
适用 |
| 尾部 token |
--retain 60000 |
日常自动;「最近够用就行」 |
| 按上海日期 |
--keep-days 2026-08-03,2026-08-04 |
你知道事情发生在哪几天 |
| 起止段 |
idx / uuid / 时间戳 |
精确圈一段;和 retain、按天不要混着用出歧义 |
retain 与 keep-* 在语义上互斥:你要么「留最近 N token」,要么「留指定集合(再在集合内按上限从最早砍)」。
6.2 下标
forge-reload 过滤后的对话事件(通常是 type 为 user / assistant 的行)有稳定下标。
--keep-from-idx 与 --keep-to-idx 成对使用。
问题是:人很难记住「610 是哪句」。所以需要锚点列表。
6.3 uuid 与时间戳(推荐给人用的接口)
先列出近期用户消息锚点:
python3 forge_tunnel.py anchors --limit 20
输出里每条有 idx、uuid、timestamp、短 preview。然后:
# uuid 可用短前缀;同端只能选 idx / uuid / ts 之一;两端可混用
python3 forge_tunnel.py --dry-run \
--keep-from-uuid a1b2c3d4 --keep-to-ts "2026-08-05 16:30"
时间戳约定:
- 不带时区的墙钟日期时间,按 Asia/Shanghai 理解
- 也接受 ISO、Unix 秒/毫秒
- from:取「≤ 目标时刻」的最后一条;若都更晚,取最早
- to:取「≥ 目标时刻」的第一条;若都更早,取最后
- 只给一端时,另一端默认会话头或尾
解析层与 forge-reload 必须用同一套 user|assistant 过滤,否则你看到的 idx 和真正裁剪的 idx 会对不上——这是这类功能最阴的一类 bug。
自动 forge 不要自作聪明选 uuid 段:自动策略保持「尾部 retain + 冷却 + 日上限」即可。精确圈选留给人。
6.4 手选一段的推荐操作顺序
anchors --limit 30,用 preview 找到起点、终点
- 复制短 uuid,或记下墙钟时间
--dry-run 看 range_resolve:from/to 的 preview 是否就是你想要的话
- 确认
keep_from_idx ≤ keep_to_idx,convs 总数合理
- 去掉 dry-run 正式跑;或先只导出新 sid 再手动 rebind
若 dry-run 里 from/to 的 preview「差了一句」,多半是时间戳落在 assistant 行上,或 uuid 前缀歧义——加长前缀,或改用 idx。
6.5 超长段的砍法
即使用 keep-days / range 圈出一大段,实现上仍可能设「约 100k tok」之类上限,从最早往近砍,避免新会话一打开就超窗。
产品含义是:你圈选的是「优先保留的集合」,不是「物理保证一字不削」。
7. 窗外记忆:稀缺、承诺、禁止脑补
热窗解决「最近像同一个人」。窗外解决「隔一周还知道红线和约定」。
7.1 稀缺是功能,不是吝啬
没有配额的记忆库,最后一定变成坟场。我们用硬上限约束「看起来很重要」的数量,例如:
- 置顶(pinned)有上限
- importance = 10、≥9 各有上限
- 超限时 demote 久未激活的,或把新写入降级
真正不能忘的才 pin:红线、核心承诺之类。闲聊标 9/10 是在谋杀检索质量。
7.2 承诺 / 约定不衰减
「我答应你……」「我们约定……」这类内容,hold 时打上 承诺 / 约定 标签,不要进普通衰减归档。
轻量提醒可以用提醒工具;长期关系承诺进记忆桶,必要时再 pin。
7.3 信息缺口红线
涉及家庭、身体、工作、具体日期、第三方事实:
- 不知道就说不知道
- 拿不准就问
- 绝不为了听起来合理猜一个答案,更不要把猜测 hold 进去
若必须记下「听说 / 待确认」:正文标明 未确认,importance 压低,不要 pin。
假记忆比遗忘难修——这是陪伴场景里代价最高的一类错误。
8. 写入怎么控:异步提炼、冲突仲裁、证据链
8.1 异步提炼:不必等过隧道才沉淀
只靠 forge 前 hold,沉淀节奏绑在「窗口快满」上,太晚了。我们现在的后台节奏是:满约 5 轮真人对话,或者空闲约 10 分钟,或者 forge 换窗前(和现有 hold 衔接)。
流水线要注意几件事:
- 从近段 jsonl 生成 0-3 条候选(可以是 0,不是每段都值得记)
- 过 write_gate(低意外只打日志,高意外才 hold)
- 日上限(我们设的是 8)防止后台写爆
- 用游标记「提炼到哪了」,千万别每次回扫整段历史
- 「新会话预热多提几次」可以做成开关,陪伴场景默认关,以免一上来就吵着记东西
最重要的一条:主对话路径上,提炼失败、超时、记忆服务 5xx,都应该降级为「这轮不写」,而不是卡住回复。有一次我们的记忆服务挂了半小时,用户那边还能正常聊天,这就对了。
8.2 冲突仲裁:只对承诺/规则动刀
闲聊「今天想吃面 / 今天想吃米」不必仲裁。真正要动刀的是承诺、约定、规则、或 importance≥8 的新 hold:
- 先召回 Top-K 旧桶
- 小模型(或规则+模型)裁决:新增 / 跳过 / 覆盖 / 合并 / 并存(不同情境)/ 标矛盾
- 覆盖与合并留 trace;并存要在内容里写清场景
- 仲裁超时就仍然写入,但标「未确认」,不挡主对话
验收直觉:用户连续说「改用方案 A」「还是用 B」时,库里不该静默躺着两套互斥的「唯一真相」还都像有效。我们最早就撞过这个坑,用户说了三套互相矛盾的作息时间,结果每次召回都是随机一套。
8.3 证据链:自动记忆也要可追溯
半自动写入建议统一带这些字段(可放在桶的 original / 元数据区):
| 字段 |
作用 |
from_session |
来自哪次会话 |
date |
上海日历日 |
type |
偏好 / 事件 / 规则 / 承诺(标签,不必做成死枚举表) |
quote 或 未确认 |
≤2 句原话;没有就写未确认,禁止脑补引语 |
forge 前的交接 hold、异步提炼 hold、手动 hold,尽量同一模板。以后排「这条假记忆哪来的」才有入口。
8.4 仲裁例子(方便写进测试)
例 A:改口
旧桶:「每周三晚上语音」标签承诺。
新话:「这周改成周四吧,之后也周四。」
期望:覆盖或合并,旧周三不再像仍有效;trace 里能看到改口。
例 B:情境并存
旧桶:「在家用昵称 A」。
新话:「有家人在时叫全名。」
期望:并存,两条都注明情境;不要合成一句糊掉边界。
例 C:闲聊
「今天想吃面」「算了想吃米」。
期望:不进承诺仲裁;通常也不 hold。
例 D:超时
裁决模型 5 秒无响应。
期望:新桶仍写入,标未确认;用户侧回复不出现「记忆系统繁忙」。
把这些写成固定用例,比空谈「三态」有用。
8.5 write_gate 在拦什么
闸门不是审查一切文学性,而是拦高风险写入模式,例如:
- 无原话却写具体日期/病症/第三方姓名
- 把一次性情绪写成永久性格判决
- 与 pinned 红线明显冲突却想静默覆盖
- 日配额已满还继续自动 hold
低意外候选可以只记日志,供以后调 prompt;不必每条都入库。记忆系统的健康度,看「该记的记住了、不该记的没进去」,不看桶数量。
9. 召回怎么控:双区注入、预算、情绪与纪念日
9.1 双区注入(保 cache 的关键)
| 区 |
放什么 |
放哪 |
| 稳定区 |
人设、纪律、工具指南 |
系统 / 项目提示;会话内少改 |
| 动态区 |
本轮 breath 卡片、Just Now、提醒 |
只在本轮 user 侧(tool_result / additionalContext 等) |
硬规则:
- Gateway 或桥接若自动注入动态块,挂在 user 前;找不到 user 就跳过,禁止回退进 system
- 每轮主动检索建议有上限;需要细节再
breath(query=…),不要为了暖场把大段记忆塞进系统前缀
- forge 后的
breath(is_session_start=True) 结果留在工具回包里即可
预算示例(可按模型上下文比例调):
- 自动浮现卡片 ≤ 约 5 条
- 动态注入总预算 ≤ 约 1800 tokens
- 准备召回 超时约 5 秒则跳过注入(延迟和账单优先于「这轮必须想起点什么」)
再次强调:这些预算管的是本轮动态召回卡片,不是整份 CLAUDE.md。有人会把「注入超时」理解成「人设被裁了」——不是一回事。
9.2 情绪偏置与「今日纪念」
不必为陪伴再挂一个大模型重排。轻量做法:
- 用户文本里的情绪关键词 → 微调效价/唤醒度,并偏置安慰向或共同回忆向的检索词
- 桶上有
date 的月日撞今天、或标了生日/纪念日:最多浮现 1 条「今日纪念」
纪念日有一个容易出丑的 bug:今天刚写入的事,被当成「一周年」。
正确条件应是:跨年且满至少一年(或你定义的周年规则)。date / created 等于今天的新桶,不应进周年通道。
热度 +1 当主排序,在陪伴场景很容易被「多检索几次」刷歪;我们明确不做,继续用 activation / last_active / 情绪与衰减一类既有信号。
9.3 和 Ombre 分层契约的关系
若你的记忆服务已有「核心 / 锚点 / 直接召回 / 扩散 / 关系天气」等分层,本教程的双区注入是宿主侧约束:谁有资格进 system,谁只能进本轮 user,谁根本不自动注入。
分层契约管「记成什么样、能不能当 seed」;双区管「这轮请求的前缀怎么拼」。两边要一起读,单读一层会漏。
10. 自动流水线:55% → 70% → 不等 83%
把旁路守护想成一条状态机更顺:
读用量 / 指纹
│
├─ 未到阈值或未闲置 → 什么都不做(可记 skip 指标)
│
├─ ≥ diet 阈值且过 diet 冷却 → diet 隧道(静默换绑)
│
└─ ≥ forge 阈值且过 forge 冷却且未超日上限
→(可选)异步提炼收尾
→ Ombre hold 会话交接
→ forge retain≈60k + inject
→ 换绑
→ 短接续(可 quiet,不在聊天里播报「隧道已接上」)
建议暴露的环境变量类别(名称按你的实现替换):
- 总开关、窗口大小、diet/forge 百分比
- 闲置秒数、两类冷却、每日最大次数
- diet/forge 各自的 retain 与 squash
- 异步提炼开关与预热开关
- forge 前 hold 开关
auto_forge status / async_distill status 一类命令,运维时比翻日志快。
指标文件(如 metrics.jsonl)至少区分:skip / trigger / ok / fail,并带上 used_pct、是否 hold 成功。没有 ok,你无法发现「假失败连打」。
11. 观测、踩坑与排障
11.1 常见故障树
换窗后像失忆。
先查:新 sid 是否真的在 resume;交接包是否 inject;forge 前 hold 是否成功;模型是否执行了 breath。
再查:是不是 diet/forge 把你以为还在的「很早以前」裁掉了——那是预期行为,应靠窗外召回,不是靠无限 retain。
cache 一直 miss。
查动态注入是否污染了 system;稳定区是否被每轮改写;换 sid 后是否接受「第一次 create、之后命中」。
记忆互相矛盾。
查承诺类是否走了仲裁;是否手工 silent hold 了第二套;证据链里有没有两套都像「已确认」。
自动 diet/forge 过于频繁。
查冷却是否写入成功;解析成功是否被误判失败;日上限是否生效;用量百分比是否抖(边界附近可加滞后)。
11.2 备份习惯
forge 前备份 ~/.claude/projects(或你的 session 根目录)只花几秒。
dry-run 满意再正式跑。裁剪是在改历史文件的形状,手滑有成本。
11.3 和「跳过注入」共处
超时跳过、预算截断、找不到 user 而跳过,都会让「这轮没想起某件事」。
产品上应把它当成降级,不是事故。事故是:为了「这轮必须想起」,去污染 system 或去脑补 hold。
11.4 排障清单(可直接贴进 runbook)
auto_forge status:用量百分比、上次 diet/forge 时间、冷却剩余、今日次数
- 当前绑定的
agent_session_id 与磁盘最新 forge 新 sid 是否一致
- 对应 jsonl 是否存在、体积是否异常暴涨(图片/工具未 squash)
- Ombre 健康检查 / 最近
commitment_arbitrate 与 distill 日志
- metrics 里最近 20 条是 ok 还是 fail;fail 的 error 字段
- 若刚改过解析逻辑:手动 seed 冷却,观察一小时是否还有连打
11.5 指标怎么读才有用
不要只看「触发次数」。至少看:
| 指标 |
健康时大致怎样 |
diet/forge ok 占比 |
高;长期大量 fail 要停自动 |
| forge 后首次 cache_create |
允许;其后应命中 |
| 夜间 idle 全量 miss |
相对改双区前下降 |
| 自动 hold 日量 |
低于稀缺与 gate 能消化的量 |
| 用户主观 |
换窗后仍像同一个人;少出现「空洞客服腔」 |
12. 和常见方案怎么比
12.1 「每轮 topK 记忆塞 system」
实现最快,cache 死得也最快。适合 Demo,不适合长跑。如果你要长期用,至少改成:稳定人设进 system,检索卡片进 user,且有预算。
12.2 「只用向量库,不做 forge」
窗外可以很强,但热窗仍会被工具输出撑爆。最后还是靠厂商 compact,语气照样伤。记忆再好,补不回近段 thinking 和半句话的衔接。
12.3 「只用 forge,不接记忆库」
短周期很好:裁剪加换 sid,人味保留。但跨周、跨主题、承诺和红线会丢——除非你把 retain 开到离谱,或者接受频繁人工交代背景。
12.4 「完整 Agent Memory 中台(多层文件 + 热度 + 任务图)」
对团队协作型 coding agent、长项目状态机可能值。但对双人陪伴,复杂度经常高于收益:多一套目录、多一套刷分、多一类「系统比人还忙着整理自己」的失败模式。我们选择借其中几条机制,而不是整包搬迁。
12.5 和 forge-reload 单机教程的关系
单机 node forge-reload.js 解决「裁 jsonl + resume」的问题。本文多出来的部分是:桥接换绑、diet 分级、Ombre hold、异步提炼、双区注入、自动阈值和观测。
如果你还在单机终端里聊,先读 forge-reload 的保姆级教程;一旦接上 Telegram 或 Web 长跑,再叠 cache_keeper 这一层。
13. 明确不做的事
| 不做 |
原因 |
| 再造完整 L0–L3 文件金字塔 |
与现有桶模型重复,运维翻倍 |
| 以热度 +1 为主排序 |
易被刷检索;陪伴更吃衰减与情绪 |
| 默认关掉向量、只靠关键词 |
中文换说法召回差 |
| 用系统 compact 当日常压缩 |
毁语气 |
| 把每轮召回塞进 system |
打穿 cache |
| 让自动 forge 自选 uuid 历史段 |
策略不稳;圈选留给人 |
| 仲裁失败就阻塞回复 |
陪伴主链路必须可降级 |
| 没有原话却写「她说过……」 |
假记忆 |
| 为了暖场每轮强行注入纪念/旧忆 |
吵,且易说错周年 |
14. 你自己落地时的最小清单
若你要从零对齐这套思路,建议按周切片,而不是一次性上齐:
第一刀:热窗
- 能 dry-run forge
- 能换绑 resume 新 sid(确认不是「发一条文本假装 switch」)
- diet 与 forge 参数分开
- 空闲门闩 + 冷却 + 日上限
第二刀:双区
- 动态卡片只进 user
- 找不到 user 则跳过
- 预算与超时钉死
- 纪律文档写进 agent 可读位置
第三刀:写入
- write_gate
- 异步提炼节奏 + 游标 + 日上限
- 承诺类仲裁 + 未确认降级
- 证据链字段
第四刀:召回体验
- 情绪轻量偏置
- 纪念日(先修跨年条件)
- 指标与假失败防护
每刀都留「关掉开关还能聊」的退路。记忆子系统挂了,聊天还必须活。
验收可以用很土的剧本:
- 连续工具调用把窗口堆到 60% 左右 → 应触发 diet,对话细节仍在。
- 再堆到 75% → forge 后仍能接上一句私聊语境。
- 故意说两套互斥承诺 → 库内可见仲裁痕迹或未确认,而不是双真理。
- 关掉 Ombre → 隧道与对话仍可用。
- 动态注入超时 → 本轮无卡片,人设仍在,cache 行为正常。
15. FAQ
Q:forge 之后第一次很贵,正常吗?
正常。换 sid 后稳定区可能重新 cache create 一次;之后应回到命中。我们最早也被这个吓了一跳,以为 forge 失败了。若每轮都 create,查动态区是否写进了 system。
Q:窗外 hold 了,为什么他还是问「我们约定过什么」?
hold 只是入库。要靠 breath / 自动注入预算内浮现,或用户/模型主动检索。换窗后应显式 breath 一次会话交接。我当时就是忘了 breath,用户那边以为记忆全丢了,其实只是没召回。
Q:能否把 retain 调到 120k,少 forge?
可以,但 diet 阶段窗口仍然会被工具输出填满;且越接近上限,系统 compact 风险越高。更常见的做法是:diet 负责日常,forge 负责真正减龄,而不是无限加大 retain。
Q:异步提炼会不会把玩笑写成设定?
所以要 write_gate、日上限、证据链、未确认。仍不放心就关掉自动提炼,只保留 forge 前 hold + 手动 hold。
Q:thinking 要不要保留?
要。丢掉 thinking,模型容易变得「答得快但不像在想」。forge-reload 的保留统计里应能看到 thinking 段数量;dry-run 时留意它。
Q:能否在用户说话中途 forge?
不建议。加闲置门闩。中途换 sid 会导致正在飞的工具调用和用户体感双重错乱。有一次我们没加闲置判断,用户打字时突然换窗,体验很糟。
Q:这套和提示词工程是什么关系?
提示词管稳定区人格与红线;本文管宿主侧生命周期。只改提示词解决不了窗口物理上限,只改 forge 也解决不了跨周事实。两手都要。
16. 附录:命令与路径速查
以下为本文描述的参考实现布局,路径按你的机器替换。
# 状态
python3 auto_forge.py status
python3 async_distill.py status
python3 keeper.py status
# 隧道
python3 forge_tunnel.py --dry-run
python3 forge_tunnel.py
python3 forge_tunnel.py run --diet --dry-run
python3 forge_tunnel.py anchors --limit 20
python3 forge_tunnel.py --dry-run \
--keep-from-uuid <prefix> --keep-to-ts "YYYY-MM-DD HH:MM"
python3 forge_tunnel.py --dry-run \
--keep-days 2026-08-03,2026-08-04 --handoff-days 2026-08-04
# 只 hold
python3 ombre_hold.py --dry-run
python3 ombre_hold.py
# 交接
python3 handoff.py prepare
python3 handoff.py prepare --days YYYY-MM-DD
# 异步提炼(预览)
python3 async_distill.py --dry-run --force
常用环境变量类别:
AUTO_FORGE=1
AUTO_FORGE_CONTEXT_WINDOW=200000
AUTO_FORGE_DIET_PCT=55
AUTO_FORGE_PCT=70
AUTO_FORGE_IDLE_SECS=180
AUTO_FORGE_COOLDOWN_MINS=90
AUTO_FORGE_DIET_COOLDOWN_MINS=45
AUTO_FORGE_MAX_PER_DAY=6
AUTO_FORGE_RETAIN=60000
AUTO_FORGE_DIET_RETAIN=150000
AUTO_FORGE_DIET_SQUASH=2000
FORGE_OMBRE_HOLD=1
ASYNC_DISTILL=1
组件对照(改自己的栈时当清单用):
| 能力 |
参考模块 |
| 裁 jsonl |
forge-reload |
| 编排 + 换绑 |
forge_tunnel |
| uuid/ts → idx |
forge_range |
| 自动阈值 |
auto_forge + keeper |
| forge 前快照 |
ombre_hold |
| 后台提炼 |
async_distill |
| 承诺裁决 |
commitment_arbitrate |
| 证据字段 |
evidence_chain |
| 召回偏置 |
companion_recall |
| 纪律 |
memory_discipline.md |
结尾
这套东西没有神话。它就是承认三件小事:
- 窗口装不下永恒,所以要裁,且裁的时候尽量留原文和 thinking。
- 永恒的东西若存在,应该在窗外,而且要少、要可追溯、要能在冲突时留下痕迹。
- 本轮想起的那几张卡片,不配改写系统前缀。
你如果已经有 Ombre(或任何桶式记忆)和 forge(或任何「新 sid + 近段 jsonl」),大概率缺的不是更多架构图,而是:双区是否真的隔离、自动压缩是否真的成功并冷却、写入是否带证据与仲裁、故障时是否还能聊。
把这四件事做实,教程里的其余细节都是调参。