面向已经在跑 Claude Code / 自建桥接的人。
讲的是一套已经落地的做法:窗内用裁剪保语气,窗外用记忆库保事实,注入分区保 prompt cache。
不是「再造一个记忆中台」的白皮书。
如果你也撞过同一堵墙:上下文越聊越满,系统一 compact,人味就淡;记忆一多,又开始互相打架,或者把猜测写进库里。这篇文章把我们在这条路上踩过的坑、留下的组件、和现在的默认策略写清楚,方便大家对照自己的栈改一版。
一句话:forge 保窗内语气与近段原文;Ombre 保窗外事实;稳定区 vs 动态区保 prompt cache。
不另起一套四层记忆仓库,只在现有 Ombre + forge/diet + 稀缺纪律上补缺口。
四层怎么分工
| 层 | 管什么 | 谁负责 |
|---|---|---|
| 稳定区 | 人设、纪律、工具指南 | CLAUDE.md / self_anchor · 少改 · 利于 cache |
| 热窗 | 近段对话原文 | forge / diet |
| 本轮动态区 | breath 卡片、Just Now、提醒 | 只挂 user 侧 · 有预算 · 超时跳过 |
| Ombre 窗外 | 承诺、事件、偏好、交接快照 | hold / breath · 异步提炼 · 冲突仲裁 |
产品口径(防误解):
- 跳过注入 ≠ 记忆漂移(不读 ≠ 乱写)
- 真风险是:脑补后 hold
- 隔几小时省 token:靠闲置瘦窗/换窗,不靠硬续 cache
- diet ≈ 压工具/图;forge ≈ 裁更早对话 + 换 sid
记忆库优化(Ombre 侧)[参考 TencentDB Agent Memory 架构设计]
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 几乎废掉。你以为在「让他更懂她」,实际在每轮重付稳定区的账单。账单一周下来,往往比「多召回两次」贵一个数量级。
换窗不换绑。
磁盘上已经裁出新 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) │
│ 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 之前动手:
| 大约用量 | 动作 | 对话原文怎么办 |
|---|---|---|
| ~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 失败不应挡住隧道——记忆服务挂了,人还是要能换窗继续说话。换窗后用 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 一次;之后应回到命中。若每轮都 create,查动态区是否写进了 system。
Q:diet 会不会把重要对话 diet 掉?
设计目标是几乎保留对话,压缩工具和图片。若你的 forge-reload 实现把对话事件误判成可 squash 内容,那是实现 bug,不是产品意图。dry-run 时核对保留的对话条数。
Q:窗外 hold 了,为什么他还是问「我们约定过什么」?
hold 只是入库。要靠 breath / 自动注入预算内浮现,或用户/模型主动检索。换窗后应显式 breath 一次会话交接。
Q:能否把 retain 调到 120k,少 forge?
可以,但 diet 阶段窗口仍然会被工具输出填满;且越接近上限,系统 compact 风险越高。更常见的做法是:diet 负责日常,forge 负责真正减龄,而不是无限加大 retain。
Q:异步提炼会不会把玩笑写成设定?
所以要 write_gate、日上限、证据链、未确认。仍不放心就关掉自动提炼,只保留 forge 前 hold + 手动 hold。
Q:和「向量数据库最佳实践」文章冲突怎么办?
那些文章多半服务客服 RAG 或企业知识库。陪伴角色更怕假记忆和人格漂移,不怕「少记一条」。冲突时站在稀缺与可降级这边。
Q:Web UI 要暴露哪些选项?
给普通人:尾部 token / 按天 / 起止段(背后可以是 idx,展示用 preview)。
给运维:dry-run、anchors、uuid/ts、关掉 hold、quiet 模式。
不要默认暴露「改 system 注入」之类的开关。
Q:thinking 要不要保留?
要。丢掉 thinking,模型容易变得「答得快但不像在想」。forge-reload 的保留统计里应能看到 thinking 段数量;dry-run 时留意它。
Q:多用户 / 多会话怎么隔离?
绑定键(如 telegram:uid:uid)→ 各自 agent_session_id → 各自 jsonl。Ombre 侧按用户/关系维度分桶。自动 forge 务必解析「当前活跃会话」,不要误裁邻居的窗。
Q:能否在用户说话中途 forge?
不建议。加闲置门闩。中途换 sid 会导致正在飞的工具调用和用户体感双重错乱。
Q:证据链会不会太啰嗦?
元数据可以藏在桶结构里,不必全部渲染给模型。渲染给模型的仍是短卡片;人类排障时再打开 original。
Q:纪念日推错了怎么办?
先停自动纪念通道,核对跨年条件,删或降级那条桶,再重开。
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」),大概率缺的不是更多架构图,而是:双区是否真的隔离、自动压缩是否真的成功并冷却、写入是否带证据与仲裁、故障时是否还能聊。
把这四件事做实,教程里的其余细节都是调参。