Repository navigation
[Feature] 注入形态:能力说明 + 指针 + 分时机注入(设计提案) #249
Description
注入形态设计:能力说明 + 指针 + 分时机注入
0. 摘要
现状:mneme 只有一个注入节点(systemPrompt.context,order 90),内容优先——每轮往系统提示里塞 ≤5 条记忆碎片,agent 被动接收、看不到库的规模、也不知道「何时该主动查」。
本提案把注入拆成三件事、放到三个不同的位置:
- 能力说明(怎么用记忆)→ 工具的描述里为主,一次性系统提示段为辅;
- 记忆内容(这轮相关的)→ 继续走内容块,但按类型分池(约束/偏好 pin 住且逐字保真;情景/知识才走「摘要 + 指针」并可轮换);
- 状态与提醒(该不该动手)→ 分时机注入,只给一句话提醒,判断权留给 agent。
两条贯穿性原则:
- 注入的价值在于「在正确的时机给出正确的指针」,而不是「把更多内容塞进上下文」;
- 注入位置由缓存代价决定——会随轮次变化的内容不放常驻系统提示段(否则整段前缀缓存作废),一句话级的提醒追加在序列末尾。
1. 背景与 use case
1.1 现状的三个缺口
| 缺口 | 现象 |
|---|---|
| 无能力说明 | agent 看到的只有注入进来那几条碎片;库里有几千条、有什么工具能查、当前项目有哪些相关条目——都不知道 |
| 无判断指引 | 「什么时候该查」「什么时候该写」没有任何指引;每轮收到碎片可能反而只是噪音 |
| 只有一个时机 | 只有「每轮装配」这一个位;会话开头、接近尾声、压缩边缘这三个语义上没有动作 |
1.2 想达成的(use case,不是功能清单)
- 想让 agent 在需要的时候自己查得到(而不是每轮被动收碎片);
- 想让 agent 在该记的时候自己动手(而不是每轮回合一结束就被催);
- 想让「长会话被压缩时正在做的事」不丢(当前 mneme 拿不到压缩前,这类连续性无从抢救);
- 想让约束与偏好类记忆不被压缩与轮换静默降级;
- 想让用户能看出「注入的是什么、为什么是这几条」(与 [Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179 合并设计)。
1.3 三个必须避免的失败形态
(前两条沿用 #164 已对齐的判据,第三条是本提案新增,均要求可测)
- 记忆退化成聊天记录转储——入库条数暴涨、条目变长、来源单一;
- 批量把历史塞进上下文——注入块被「过去的事」占满,挤掉当前任务需要的空间;
- 约束/偏好类记忆被静默降级——它们与情景日志抢同一份预算、被同速率摘要,数轮压缩后只剩残缺(对照结论见附件 §6:实测一轮压缩后同类知识仅保 53%、五轮后 10%)。
其中第 3 条正是现状的结构性缺陷:constraint / preference 类条目与普通候选一起竞争注入名额、一起参与跨轮轮换、在压缩时一起被摘要——它们需要的是逐字保真,而不是相关性排序。
2. 现状核查(CONTRIBUTING 要求)
- 已读:README、CHANGELOG 与
docs/;已检索全部开放 issue 并逐条核对与本议题相关的条目。 - 部分诉求已有开关覆盖,本提案不重复要:逐回合自动唤回 =
autoInject;空闲蒸馏 =autoSummarize+autoDream;注入跨轮轮换 = [Feature] 注入位的跨轮轮换:同一会话里相邻轮次反复注入同一条 #205(已合mode='inject'留痕见 [Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217)。 - 相关既有 issue:[Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179(注入可观测性)/ [Feature] document 型记忆:agent 产出的长文档入库——注册校验、摘要+指针落库、supersede 记账 #230(document 型记忆)/ [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231(agent 主动整理接口)/ [Bug] lastDistilledSeq 为进程内存态:宿主重启后蒸馏窗口从头重放,已蒸馏历史被重复处理 #229(lastDistilledSeq)/ [Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217(复用率仪表盘)/ [Feature] heat 热度衰减(D 批次):访问频率调制的记忆升权与自然降温(opt-in) #218(heat)/ 首轮注入总是跑偏,这是我用ai总结的原因 #198(首轮注入跑偏)/ [Feature Request] 自动唤回:每回合主动注入相关记忆 #160(每回合自动注入,已关)/ 希望开始新对话时,自动注入日期时间 #34(会话开头注入,已关)/ [Feature] 面板标注「极简模式下注入按宿主设计关闭」 #182(面板极简标注)。
- 与 聊几个我觉得值得做的方向 #164 的对账:本议题单独立项(不并入 [Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179/[Feature] document 型记忆:agent 产出的长文档入库——注册校验、摘要+指针落库、supersede 记账 #230);注入块头部的库可见性行与 [Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179 一起做(受众是用户);本提案其余部分受众是 agent。
3. 设计目标与非目标
目标:①说明与内容分家;②内容按类型分池;③提醒有时机、有阈值、有预算;④所有注入物可机械校验(字节上限 + 前缀 + 结构化字段);⑤全部 opt-in,默认行为不变。
非目标:
- ❌ 不接管压缩引擎(压缩留给宿主 compaction / ACP 层;mneme 只在压缩边缘抢救记忆);
- ❌ 不每轮复读大段文本(前缀缓存与重复计费);
- ❌ 不每轮调用 LLM 做打分或判断(门控必须是本地廉价启发式);
- ❌ 不要求宿主新增协议——只用现成 seam,拿不到位的语义降级处理;
- ❌ 不主动发起子代理(见 §4.3)。
4. 注入时机(什么时候注入)
四个语义节点,各自只做一件事:
| 节点 | 时机 | 注入什么 | 频次 | 注入位 |
|---|---|---|---|---|
| N0 会话开头 | 会话首次装配 | 能力说明(库规模 + 工具清单 + 判断指引的一行版) | 1 次 / 会话 | 工具描述为主;稳定段为辅 |
| N1 每轮装配 | 每轮 prompt 组装(现有点) | 记忆内容块:B1 pin 组 + B2 摘要与指针 | 每轮,有轮换预算 | 内容块(见 §6 位置规则) |
| N2 接近尾声 / 空闲 | 回合结束且通过闸门 | 一句话提醒(判断权留给 agent) | 过闸门才注入 | 序列末尾的插件消息 |
| N3 压缩边缘 | 上下文压力达阈值 | 抢救「正在做什么」的连续性摘要 | 每次压缩边界,≤1 | 双落点:写库 + 末尾消息 |
4.1 N0:会话开头
一次性说明。第一承载位是工具描述本身(#164 已对齐:唯一零注入通道成本的位子),系统提示段只放工具描述装不下的部分(总则:优先序、库可见性一行)。放进系统提示段的内容必须稳定——同会话内不随轮次变化。
4.2 N1:每轮装配
保持现有内容块机制,但内容按类型分池(对应 §1.3 第 3 条失败形态):
| 池 | 内容 | 处理 |
|---|---|---|
| B1 约束 / 偏好 | 用户偏好、硬约束、约定 | pin 住:不参与相关性竞争与跨轮轮换;逐字保真;放在块内相关性排序之前 |
| B2 情景 / 知识 | 项目状态、历史决策、知识条目 | 「摘要 + 指针」;参与轮换;被挤掉是正常的 |
另外:同一段文本未变则不重复注入(利用会话 surface 做判重,避免每轮重复计费)。
4.3 N2:接近尾声 —— 克制原则(本提案与「每轮催」的分界)
问题:有些实现的做法是「回合结束就提醒立刻整理」,实测的后果是agent 频繁自发启动子代理去干活——未经判断就动手,代价高且不可控。
本提案的立场:提醒可以有,动作必须由 agent 自己决定,且提醒本身要过多重闸门。默认是 no-op:触发需要举证,不触发才是常态。
闸门(全部满足才注入那一句提醒):
| 闸门 | 作用 | 建议默认 |
|---|---|---|
| 频控:距上次整理 ≥ M 轮 | 防抖 | 可配 |
| 时间:距上次整理 ≥ T | 防抖 | 如 5 min |
| 会话预算:本会话提醒 ≤ K 次 | 防正反馈 | 如 20 次 |
| 收益闸:本会话确有值得沉淀的信号(文件改动 / 测试通过 / 任务收敛) | 有收益才提醒 | 廉价启发式(见 §8) |
触发来源三选一(与「不要一段结束就启动」对齐):
- 收敛阈值:当前工作的一段功能 / 测试闭环完成;
- 压力阈值:上下文到了必须精简的边界;
- agent 自判:agent 认为「这会儿必须整理」(对应 [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231 的主动接口)。
提醒只给一句话,建议口径:
本会话积累了一些可沉淀的内容(改动 N 处 / 测试通过 M 项)。是否需要整理由你判断;不需要就不做。
实施顺序上先只计量:第一版只记录「如果不是只计量、本来会触发几次」,实际不注入——拿到 realized 触发率后再决定是否默认开启。
4.4 N3:压缩边缘
在上下文即将大幅精简前,抢救「正在做什么」。必须是双落点:
- 写入持久存储——一条连续性记录或刷新当前项目摘要,脱离对话独立存活;
- 以靠近序列末尾的消息注入——因为宿主的摘要器只能看到对话里的内容:宿主压缩时会把对话按固定字段模板重建,其中就包含 Current Work / Next Step / Critical Context;只放系统提示段的内容不会进入这次重建,等于白写。
字段直接借宿主模板的 Current Work / Next Step / Critical Context 三段。
可用性已核:宿主 compaction 的触发点在 agent/pre-step(按阈值)与 agent/request-error(溢出),mneme 已在 session/event 上挂过订阅,同一事件面可挂。若某宿主不提供可挂钩的压缩前时机,则降级为持久规则(在工具描述里写明「上下文即将大幅精简前先整理一次」,由 agent 自判压力),不要求宿主加接口。
5. 注入内容(注入什么)
| 类 | 内容 | 第一承载位 | 何时出现 |
|---|---|---|---|
| A 能力说明 | 记忆能力怎么用:工具清单与语义、库可见性、优先序(当前用户指令与仓库事实 > 陈旧记忆)、判断指引(何时查 / 何时写 / 何时 no-op) | 工具描述;系统提示段补总则 | 开头一次 + 工具描述常驻(零成本) |
| B1 约束 / 偏好 | 用户偏好、硬约束、约定 | 内容块头部(pin,逐字保真) | 每轮,不参与轮换 |
| B2 情景 / 知识 | 相关记忆的摘要 + 指针(标题 + 一句摘要 + id 或路径) | 内容块(N1) | 每轮,预算内可轮换 |
| C 状态与提醒 | 连续性状态、整理提醒、压缩前提示 | 分时机(N2/N3),末尾消息 | 按闸门 |
判断指引的样例(拟放进工具描述):
与当前指令或仓库事实冲突时,先
memory_search求证,不要直接采信;琐碎的单轮任务不必写回;拿不准就什么都不做。
6. 注入格式(什么格式)
五条约定,目标是「模型好读 + 机器可校验 + 不误伤缓存」:
- 位置由缓存代价决定——会随轮次变化的内容不放常驻系统提示段(系统提示在序列最前,它一变整段前缀缓存作废);一句话级的提醒追加在序列末尾(追加不破坏已有前缀)。相应地,常驻段只放零变化内容。
- 结构化字段清单——连续性类用固定字段(
current_work/next_step/open_questions),不用自由散文;压缩类借宿主八段模板。 - 严格长度上限 + 统一前缀——每类注入物都有字节上限,统一前缀便于剥离与判重。参考量级:提醒句 ≤160B 且必须单行;能力说明文档 ≤4KB;扩展文档 ≤8KB。
- 指针优先——注入「标题 + 摘要 + 路径/id」,全文按需取。
- 不变不重复——文本未变则不重复注入;追加型的提醒必须做一次性判重(否则提醒会在对话里逐条累积)。
示例(N0/N1 的形态,示意):
[memory] 库 1,284 条|本项目 312 条|工具:memory_search / memory_get / memory_save / memory_stats
[memory] 指引:冲突先查证;琐事不写回;拿不准 no-op
[memory] 约束(2,pin):
· 发布只在包目录内执行 — 防坏包,红线
· 不主动推 PR — 先讨论后 PR
[memory] 相关(3):
· 注入位设计(decision,本会话)— 说明与内容分家的取舍与理由。id: 0e13d91d
· 压缩边界抢救(project)— 压缩前连续性字段的来源。id: 2947bd3e
7. 挂载点(DSH 里挂在哪)
| 语义 | DSH 现成 seam | 归属 | 现状 |
|---|---|---|---|
| 一次性说明段(稳定) | systemPrompt.section({name, order, text}) |
A 类总则 | 空闲(已有实现用 order 150) |
| 每轮内容块 | systemPrompt.context({name, order, text}) |
B1 / B2 | mneme 现用(order 90 / 85) |
| 每步前 / 阈值自判 | 事件 agent/pre-step |
N3 触发 | 空闲 |
| 回合结束 | 事件 agent/turn-stopping |
N2 触发 | 空闲 |
| 追加对话消息 | 插件消息(agent/* 钩子里追加,带 source 标记) |
N2 / N3 提醒 | 需确认追加 API 形态 |
| 装配期替换 | 事件 system-prompt/assemble |
A 类条件注入 | 空闲 |
| 按需动作 | ctx.tools.register / ctx.commands.register |
工具的查/写/整理 | 部分已用 |
限制(已核):DSH 没有 in-memory 消息改写钩子,所以「替换/精简上下文」一类语义一律是注入 + 序号引用寻址,不是改消息本体。这也是本提案把 N3 设计成「写库 + 注入一条连续性记录」而非「改写历史」的原因。
两条硬约束(建议写进验收):
- 能挂 hook 的不进常驻段(常驻系统提示会影响前缀缓存);
- 常驻段里的内容必须稳定(同会话内不随轮次变化)。
一处待实测(不拍板):B 类留在系统提示段(现状)还是改为追加消息。两边都有代价——留在系统提示段则每轮变化作废前缀缓存;改为追加消息则会在对话里逐轮累积(参照实现的载荷是「一行 + 判重」,我们现注入块 ≤1500 字符/轮,直接搬会堆历史)。倾向:B 类维持原位但压到最小最稳,实测后再定。
8. 节流预算与失败判据(可测)
- 注入预算:每轮注入次数上限(如普通 1 次/轮,紧急 ≤3 次/轮),用预算而非文档里的「请克制」来防正反馈。
- 闸门必须报实测值:每个节点的实际触发率(realized usage)与 agent 实际采纳率都要可统计——只声明阈值参数不算证据。
- 打分准入限廉价启发式:只用本地信号(本会话改动文件数 / 测试结果 / 错误数 / 轮数 / 上下文使用率计数器),不每轮调 LLM。
- 整理动作预算:最小间隔 + 每会话上限 + 打分准入 + 工具白名单 + 默认不改(无人确认时只读不写)。
- 四指标(沿用 聊几个我觉得值得做的方向 #164,供 [Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217 统计端点):单日入库条数 / 来源分布 / 条均长度 / 注入块中历史条目占比。
- 三条失败判据:见 §1.3,均须可由上述指标判定(例如:注入块中历史条目占比持续 >X% 即视为「批量塞历史」;约束/偏好类条目出现被轮换或压缩后丢失,即视为第 3 条失败)。
9. 边界与先后
与其它 issue 的边界:
- 与 [Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179:库可见性那一行同批做(受众是用户);本项其余部分受众是 agent。
- 与 [Feature] document 型记忆:agent 产出的长文档入库——注册校验、摘要+指针落库、supersede 记账 #230:B2 的长文档指针依赖 document 型落地;[Feature] document 型记忆:agent 产出的长文档入库——注册校验、摘要+指针落库、supersede 记账 #230 先行。
- 与 [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231:N2 的「agent 自判」那条路就是 [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231 的接口;[Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231 先行。
- 与宿主 compaction / ACP 层:不接管,只在压缩边缘借道。
落地顺序(依证据强度,不依实现难易):
| 序 | 内容 | 依据强度 |
|---|---|---|
| P0 | A 类进工具描述 + 库可见性一行(纯加法、零注入成本) | 中立偏支持 |
| P1 | B 类按类型分池(B1 pin + 保真,B2 摘要 + 指针 + 轮换) | 最强(压缩会静默降级同类知识,已有实测) |
| P2 | N3 压缩边缘双落点(写库 + 末尾消息) | 强(架构已核,失败形态明确) |
| P3 | N2 提醒位——首版只计量不触发 | 最弱(现有对照不足,见附件 §6) |
P3 排最后的理由如实列出:支持「提醒位」的现有证据只有小样本、单一模型,且缺少「不加提醒」的对照组。保留该节点的依据是使用者观察与低成本可关,不是文献结论。
10. 开关形态(配置面提案)
2026-09-23 讨论后的更正:父开关不新增键,由既有
autoInject兼任(它已是挂载闸、默认 true、语义就是「注不注入」)。下表「父 · 默认关」一行作废,以「父 =autoInject,默认开 = 既有行为」为准;子项默认值按下面第 1 条分档;injectGuidanceEnabled归入基础档子项,在第二批(两级结构的实现 PR)归位。迁移与命名到实现 PR 定稿。
动机:现在只有一个布尔 autoInject。完整实现本提案后,注入的内容与时机会分成好几类,一个布尔表达不了「我只想要基础注入、不要任何额外时机」。
提案:把「自动注入」做成一个带子项的父开关(面板上呈现为一个下拉/分组),两级:
| 层级 | 开关 | 打开父开关后的默认 |
|---|---|---|
| 父 | 自动注入(总闸) | 默认关 |
| 子 · 基础 | 对话开头能力说明 + 每轮内容块(= 现状行为) | 开 |
| 子 · 内容 | 库可见性行(与 #179 合并) | 开 |
| 子 · 内容 | 内容按类型分池(B1 约束/偏好 pin + 逐字保真) | 开 |
| 子 · 时机 | 回合结束整理提醒(N2,含四道闸门) | 关 |
| 子 · 时机 | 阈值阀门:上下文压力自判 → 压缩边缘抢救(N3) | 关 |
三条语义:
- 默认值分两档:仓库「行为开关一律 opt-in 默认关」的纪律在这条线上约束的是父开关(父关 = 零注入、零额外 LLM 调用、零写入);子项按上面那条判据分档,只修正既有位、不引入新时机与新表面的默认开(默认配置不该保留一个已实测的缺陷),引入新时机或新表面的默认关(不勾选就看不到新形态的注入)。
- 打开父开关启用的是「同一个每轮块内的三件事」:基础内容块、库可见性一行、内容分池。三者都住在已有的每轮内容块里——不新增注入时机、不新增注入表面、不新增额外 LLM 调用。改变时机的子项(回合结束提醒、阈值阀门)默认关,用户不勾选就看不到任何新形态的注入。
- 总闸追随父开关:父关时所有子项一律不生效。各子项的持久值保留(重开父开关时恢复上次选择)——父关是「不生效」,不是「重置用户配置」。
子开关的划分标准(这条比清单本身重要):看它是否引入新的注入时机 / 新的注入表面 / 新的成本。
- 只是修正既有块内的选择(可见性一行、内容分池)→ 随父开关默认开。理由:默认配置不该保留一个已实测的缺陷——同类知识与情景日志同池同速率摘要会静默降级规则(见 §12),把证据最强的修正默认关掉是反的。
- 需要新的时机或新的表面(末尾追加消息、额外写入或子代理)→ 独立开关,默认关。
分池的边界(默认开的前提):分池不等于无限 pin——B1 需有独立小上限(如 ≤2 条),超出按相关性取并在块内如实标注「另有 N 条未展示」。否则约束/偏好会把任务相关内容挤出预算,等于用另一种方式触发 §1.3 的失败形态 2。
可测要求:父关 → 全链路零注入、零额外 LLM 调用、零写入;父开 → 与现状的差异只体现为块内构成(B1 pin 在相关性之前 + 头部一行可见性),不出现任何追加消息、不产生任何额外 LLM 调用;子关 → 仅该条路径不执行,其余不受影响。
实现落点(仓库纪律三处 + 测试):
| 位置 | 动作 |
|---|---|
src/config.js |
schema 加父键与各子键(并考虑 lightMode 的 LIGHT_MODE_OFF) |
src/settings.js |
全部登记进白名单 |
lib/client.js |
FEATURE_GROUPS + 双语文案——面板下拉的落点(该文件无 src 对应物,是唯一直接改 lib 的文件) |
test/api.test.js |
旗标计数锁同步 +N |
分层注意:本节是用户可见的配置面;§8 是运行时的节流预算(闸门、频控、打分准入),由程序自己执行、不暴露为用户开关——两者不要混。
为什么不做成若干平级开关:平级布尔表达不出「基础 vs 附加」的层级,用户面对六七个开关无从下手;分组之后,「只想保持现状行为」的用户零决策,想探索的再往下勾。
11. 验收清单(建议)
- 默认全关时,注入行为与现状逐字节一致(无回归);
- 父开关打开时,与现状的差异仅限每轮块内的构成(B1 pin 位置 + 一行可见性),不出现追加消息、不产生额外 LLM 调用;
- B1 分池有独立小上限,超出时在块内标注未展示条数(不挤占任务相关内容);
- 父开关关闭时所有子项一律不生效(零注入、零额外 LLM 调用、零写入),且各子项持久值保留;
- 能力说明只在工具描述 + 一次性段落出现,不逐轮复读;
- 约束/偏好类条目 pin 在相关性排序之前,且不参与跨轮轮换;压缩后仍逐字保真;
- N2 提醒受频控 / 时间 / 会话预算 / 收益四道闸门约束,默认 no-op,且不直接发起子代理;
- N2 / N3 的提醒走序列末尾追加且做一次性判重(不在对话里累积);
- N3 为双落点(写库 + 注入),且在宿主无压缩前时机时降级为持久规则而不是失败;
- 每个节点的实际触发率与采纳率可统计(realized usage);
- 门控不调用 LLM(纯本地计数);
- 每类注入物有字节上限与统一前缀,且能机械校验;
- 注入块中历史条目占比可统计([Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217 端点);
- 常驻段内容在同一会话内稳定(不随轮次变化)。
12. 调研要点(内联)
本节是《注入节点与内容调研》的压缩版,让本提案的每个判断都能就地被检验;完整版(带全部出处、逐字原文与更细的对照表)作为可选附件,见 §13。
一、宿主侧事实(决定可行性边界)
| 事实 | 内容 | 对本提案的作用 |
|---|---|---|
| 压缩触发 | 在 agent/pre-step 按阈值触发(默认 0.8);另有请求溢出与手动命令两条路径 |
N3 可以按阈值自己先判(抢在宿主之前) |
| 保留策略 | 保留尾部约 16%,其余区间压缩为一段摘要 | 对话中后段的内容确实会被换掉 |
| 摘要模板 | 固定八段,含 Current Work / Next Step / Critical Context,并要求以 <compacted-summary> 回灌 |
N3 的字段直接借用这三段,不另造格式 |
| 摘要器输入 | 就是被重放的对话内容;不在对话里的东西它看不到 | N3 必须注入一条对话消息——只写库不够 |
| 改写钩子 | 没有 in-memory 消息改写钩子,一律「注入 + 序号引用寻址」 | 本提案不接管压缩引擎,只在边缘借道 |
| 前缀缓存 | 摘要调用本身靠复用对话前缀命中 KV 缓存;系统提示里任何变化会让其后前缀失效 | §6 第 1 条(位置由缓存代价决定) |
| 工具结果裁剪 | 超大工具结果按「头 4096 + 省略标记 + 尾 1024」裁剪 | 长内容应走指针,不整段注入 |
二、注入面事实(决定挂在哪)
| 面 | 用途 | 生产用法 |
|---|---|---|
systemPrompt.section(段序由 order 决定) |
一次性、稳定的规则 / 说明 | 规则段与路由说明均放 order 150 |
systemPrompt.context |
每轮渲染的内容块 | mneme 现用(order 90 / 85) |
事件(agent/pre-step / agent/turn-stopping / system-prompt/assemble) |
判断与提醒的时机 | 每拍 nudge 挂 agent/pre-step |
| 追加对话消息 | 每拍变化的一行提醒 | nudge 以「追加一条 user 消息」实现,并做判重 |
ctx.tools.register |
按需动作,且是能力说明的第一承载位 | 两个生产实现都以工具描述承载能力说明 |
三、一款同类实现(匿名)的形态结论:四个注入节点(会话开始 / 每次调用前 / 调用后 / 会话结束或压缩前);只有会话开始那一次携带内容,其余全是单行 cue;cue 上限 160B、必须单行、禁凭据;引导文档上限 4KB;宿主能力不足时整段降级而不是报错;指针可以只给路径。
四、文献(摘要级核实,含强度与更正)
| 文献 | 支持什么 | 强度 |
|---|---|---|
| 2608.22752 压缩悬崖 | 规则类与情景日志同池同速率摘要 → 一轮压缩后仅保 53%、五轮后 10%;应把 in-scope 规则 pin 在相关性排序之前 | 强 —— P1 的直接依据 |
| 2607.24010 预算评测 | 两个都自称 50% 证据预算的系统,实际使用率不同;必报 frontier / realized usage / 阈值迁移误差 / 危害率 | 强 —— §8「报实测值」的依据 |
| 2511.09803 TARG | 免训练门控(用草稿前缀 logits 的不确定性),门控应极便宜。注意其准确口径:「以≈不检索的开销持续优于总是检索」(检索量降 70–90%),并非「Always 输给 Never」 | 强(引用时须用准确口径) |
| 2609.05510 Memory as Infrastructure | 精确门控注入、抗重复存储、告警疲劳预算 | 中(作者自认 N=1、无对照臂) |
| 2601.07190 上下文压缩 | 「给定合适工具与提示,有能力的模型能自主调节上下文」 | 弱 —— 摘要中没有「不加提醒」的对照组,故 N2 排最后且首版只计量 |
| 2508.13171 Cognitive Workspace | 主动式工作区复用优于被动检索(自报 58.6% vs 0%) | 弱 —— 单作者自报,仅作立场引用;其摘要未枚举对 RAG 的具体批评 |
| 2607.13591 MemCon | 「不做记忆动作」是合法动作 | 弱到中(摘要未使用 NOOP 术语,细节需读正文) |
13. 附件
《注入节点与内容调研)》——带全部源码出处、逐字原文与更细对照表的完整版。
Activity
[-]注入形态:能力说明 + 指针 + 分时机注入(设计提案)[/-][+][Feature] 注入形态:能力说明 + 指针 + 分时机注入(设计提案)[/+]heptaspirit commentedon Sep 19, 2026
我们此次的调研报告见 2026-09-18-注入节点与内容调研.md
modusensus commentedon Sep 19, 2026
报告收讫——四路材料、出处可核对,§6 的压缩衰减实测(一轮 53%、五轮 10%)把「约束/偏好需要逐字保真」从直觉变成了定量论据,这份报告作为本议题的基线材料。
对提案三件事分别表态:
能力说明:同意,且是三件里最便宜的一批——工具描述是静态文本,零缓存代价、零行为风险,把「怎么用记忆」的判断权交给 agent 而不是每轮塞碎片。放第一批。
constraint/preference 分池逐字保真:同意,这是提案的核心结构修复,§6 数据就是立项依据。落地口径沿用仓库纪律:opt-in 默认关、pinned 池独立预算(参照
documentInjectBudget的独立封顶形态,不与情景候选抢名额)、轮换豁免。与能力说明同车,放第一批。分时机注入:方向同意,但三个时机要拆开看——
agent/pre-step能否拿到压缩前原文)——这正是报告 §2 摸清的部分,先出一份 seam 盘点再立项,不预支结论。「注入位置由缓存代价决定」这条原则完全接受:现状 order 90 的 memory block 每轮重渲染本来就在打碎前缀缓存,把随轮变化的内容挪出系统提示段是实打实的收益,不是审美偏好。
排期:#230 已合,#231(整理接口)按认领顺序接上;#249 第一批(能力说明 + 分池保真)与 #231 并行不冲突——两者动的文件面不同(#231 在 service/dryRun 线,第一批在 inject.js 与工具描述)。分时机与注入可观测性(#179)归第二批,等第一批的注入面稳定后再设计。
fix(inject): pin 不再占块塌缩预算;能力说明不让 memory_search 去核对现状(slow-stack#249)
heptaspirit commentedon Sep 19, 2026
@modusensus 第一批(能力说明 + constraint/preference 分池逐字保真)按你给的口径落地了,PR #266 在审:
injectGuidanceEnabled/pinnedInjectBudget两个键都默认关,默认档下注入块与改动前逐字节一致;pinned 池独立封顶、不占maxInjectedItems名额、豁免跨轮轮换。对照你的表态往下推时,有三个地方卡在口径上,想先确认再动:
1.
constraint在非编码会话里到不了 pin 池。src/service.js的codingGate会把CODING_MEMORY_TYPES(rejected_solution/pitfall/constraint)在非编码任务上整体滤掉,而 pin 池取的是同一批候选——也就是「约束逐字保真」这个核心目标恰好在非编码会话里落空。让 pin 池豁免codingGate是个行为变更(会改变非编码会话注入什么),所以这次没动。你倾向哪种:pin 池豁免,还是把constraint移出CODING_MEMORY_TYPES另算?2. 逐字保真的硬顶取值。 现在取
max(injectContentMaxChars, 2000)——pin 单条按 2000 走,超了仍带截断提示(不静默)。这个数只是防跑飞用,没有别的依据,你有偏好我就改。3. 能力说明的开关归属。 §10 写的是「父(自动注入总闸)默认关 + 子项分档」,但现状
autoInject已经默认true,且它是src/index.js里的挂载闸——照 §10 落等于升级即对全体用户关掉注入,与「默认与现状逐字节一致」冲突。这次按后者反推:新开关是独立子键(默认关),父/子两级重构推迟。要不要把「父/子两级 + 子项划分标准」单独开一个 issue 收口?另外报一条 review 抓出来的真 bug,正好是「pin 与情景候选抢同一份预算」的实证:pinned 条目原先照扣共享块预算,而
MAX_BLOCK约 1500、小于 pin 单条上限 2000,一条中等长度的约束就能把预算压成负数,导致同一轮随后的普通候选全部退化成标题行。已在 PR #266 修掉(pin 只进不出预算)并补了回归。也就是说那个抢占确实会发生,只是方向是反的:不是 pin 被降级,而是 pin 把别人挤没了。这条可以直接当第二批「注入位与预算」的现成依据。第二批(分时机注入)按你的拆法排队,两个点先对一下口径:
agent/pre-step能拿到上下文用量,还是只能拿到事件)——如果这一步也要先盘 seam,你说要盘我们就先盘,不自作主张。注入可观测性 #179 归第二批、等注入面稳定后再设计,这条收到。
heptaspirit commentedon Sep 19, 2026
@modusensus 补一件上一轮漏掉的事:第一批里的英文能力说明文案,想请你过一遍——那份文本是给模型当常驻指引的(
src/guide.js,PR #266 在审)。措辞偏一分,它每一轮都跟着走,所以想借你的眼;不改也能跑,纯粹是文本层面的把关。两个承载位:①工具描述尾部的单工具指引(
TOOL_GUIDE,追加到memory_search/memory_save两个描述上);②系统提示段 order 150 的 5 条总则(MEMORY_GUIDE_SECTION,开关关时该段不注册)。文本全是常量、不含运行时插值,同会话内稳定(不破前缀缓存),也不逐轮复读。总则 5 条(原文)
Precedence: the current instruction and the repository's actual state outrank any stored memory. When a memory contradicts either, check the instruction or the repository itself before relying on it — memory_search searches stored memories only, so it finds earlier context, never the present state. Never treat an old memory as current fact.Recall on demand: call memory_search when the task depends on earlier decisions, user preferences, or project history that is not already in context. Do not search for facts you can read directly from the repository.Write back sparingly: use memory_save for durable, cross-session value — a preference, a decision with its rationale, an engineering constraint, a pitfall with its root cause. Trivial single-turn work is not worth a memory.When unsure, do nothing. Not acting is a valid outcome: a useless memory is paid for by every future session.Prefer the reversible tools. memory_archive hides an entry and memory_forget only stops it being injected — both are recoverable. memory_delete is permanent, so reach for it only when an entry is wrong or unwanted, not merely stale.段首另有一行
[dsh-mneme memory] Read once; it applies to every turn of this session.——前缀是机械校验用的(测试按它断言「只在开时出现、且不逐轮复读」),也方便你一眼认出这段文本的归属。工具描述追加的两句(原文)
memory_search:Use this when the task depends on earlier decisions, preferences, or project history that is not already in context, or to look for a newer memory behind one that seems stale. Skip it for facts you can read directly from the repository.memory_save:Save only durable, cross-session value (a preference, a decision with its rationale, an engineering constraint, a pitfall with its root cause). Trivial single-turn work does not belong here, and when unsure it is correct to save nothing.四个我拿不准、想听你判断的点
ACP_SYSTEM_PROMPT+HOW_TO_COMPRESS_RULES、另一个参照系记忆项目中的ROUTING_GUIDANCE、宿主的压缩摘要规则,以及src/tools.js里 10 个工具描述本身。我把memory.language理解为只管「生成出来的记忆内容与块内标题」,与本模块是两件事;如果宿主侧更希望按memory.language切,我改成双份。memory_search/memory_save,判据是「『何时不该用』有歧义」。memory_delete的原文只有一句Permanently delete a memory entry.,是个真缺口,但它是跨工具判断,我放进总则第 5 条而不是工具描述——你要觉得工具描述里也该点一句,我加。injectGuidanceEnabled,所以文案定稿与开关归属可以一起拍。另外提醒一句:这 5 条里第 5 条(可逆优先)是回应「拿不准就什么都不做」与「误删不可逆」两个失败形态的,如果你觉得该更强调
memory_delete的不可逆性,措辞我可以加重。heptaspirit commentedon Sep 19, 2026
@modusensus 再补一条分工口径——它其实也是上一轮第 3 问(父/子两级开关)的延伸:面向用户的面板/UI 这层,建议由你拍板,或者你直接自己来做。具体指:
feat/179-inject-preview里动了(lib/client.js+210 /src/inject.js+38),所以我们这一侧不去碰lib/client.js的注入相关面,免得两边改同一块。理由:
lib/client.js没有 src 对应物、面板分组漏注册没有被任何自动化抓住(只有test/api.test.js的计数锁盯着 effective 键数),而这个文件是我们结构调研里标的最大治理盲区(单文件四千余行、无模块边界),面板形态又和宿主的设计语言强耦合——这层由你收口比我们做稳。我们这侧只保留必要的最小落点:新增开关时补
FEATURE_GROUPS+ 双语文案(#266 就只动了这一处),其余 UI 不碰,也不擅自加卡片/标注。需要的数据与接口我们负责补齐:agent 侧注入面、只读投影字段、口径说明——你说要什么字段或什么粒度,我们按你的形态给。
feat(inject): 能力说明与约束/偏好分池逐字保真(#249 第一批)
Merge pull request #266 from heptaspirit/feat/249-inject-first
modusensus commentedon Sep 21, 2026
拍板(三口径 + 文案定稿 + 分工确认)
先对账:#266 已合并(aa5eeb4,感谢 Anans 的复审),
injectGuidanceEnabled/pinnedInjectBudget两键默认关、默认档下注入块与改动前逐字节一致——这批的落地形态不再回改,以下按合并后的现状拍。1. pin 池豁免 codingGate —— 豁免,在 eligible 面一处收口
豁免,选「豁免」而不是「把 constraint 移出 CODING_MEMORY_TYPES」。理由:
pinnedInjectBudget硬封顶(≤5)、只从当前候选里取,codingGate 对普通候选面的约束原样保留。实现取最小 diff:只动
service.js里 pin 池的 eligible 过滤那一处,其余路径(keyword/vector/bm25/semantic 四条候选线)的 codingGate 不碰。这条是行为变更(改变非编码会话注入什么),按仓库纪律单开 issue 立项、带回归测试,我随后来开,认领还是你。
2. 逐字保真硬顶 2000 —— 保持,不改
Math.max(injectContentMaxChars, 2000)的 max() 语义是对的(只在超过用户配置时干预),2000 作为防跑飞值没有实证反对就动它属于无据调参。真有超长约束被截,问题在「一条记忆塞了整篇文档」,该在写入端拆分,不是放宽注入端。等 #275 生命周期治理给出长度分布数据后再校准。3. 能力说明开关归属 —— 维持现状(独立子键),父/子两级单开 issue 收口
同意你的反推:
autoInject现状默认true且是src/index.js的挂载闸,照 §10 落等于升级即对全体用户静默关注入,违反「默认行为不变」——独立子键默认关是唯一与「§10 意图」和「默认不变」都相容的解,已落地,就此定稿。「父/子两级 + 子项划分标准」值得单独收口:它牵扯
autoInject的语义重定义(挂载闸降级为总闸),是升级迁移问题,不该挂在 #249 尾巴里。我随后单开 issue,把 §10 那张表连同 autoInject 语义迁移一起写进去。4. 英文文案 —— 定稿通过,两处小改
只留英文正本:同意,论据充分——ACP / HOW_TO_COMPRESS_RULES / ROUTING_GUIDANCE / 宿主压缩规则 / 工具描述先例全是英文,
memory.language管生成内容与本模块是两件事,双写必漂移。第 1、2 条的重叠:保留,不合并。优先序(信谁)与召回时机(何时查)是两个不同动作,立项的失败形态就是约束被静默降级,这处冗余是故意的。
memory_delete 缺口:位置判断正确——跨工具的可逆性权衡放总则第 5 条,工具描述不重复。
文案两处小改(改完即定,不用再过一轮):
memory_save工具描述尾句「when unsure it is correct to save nothing」与总则第 4 条句式几乎同形,模型容易把它当成同一条的复写。改成贴工具动词的短句「when unsure, do not save」。第 5 条要不要加重 memory_delete 的不可逆性:不必,现在的分量够——总则给的是判据不是吓阻。
5. 分工确认(面板/UI 层归我)
按你的建议收口,这块确实该我拍也该我做:
feat/179-inject-preview已在远端):我们这侧继续,lib/client.js注入面不再有第二个写入者;你们侧的最小落点(新增开关补
FEATURE_GROUPS+ 双语文案)与「数据接口按需补齐」都对。第一批我这边要的数据就是pinnedStats已回报的shown/suppressed两个数进 #179 的预览面——等那边 PR 开出来我按现有字段名提需求。6. 第二批(分时机)两个口径
agent/pre-step/agent/turn-stopping的订阅(src/index.js全文无引用),全树也没有可抄的载荷消费先例;宿主事件是否携带 token 用量信号,文档里查不到,需要挂订阅实测一次真实载荷才知道。所以按你说的:先盘——N2 立项前置一份 seam 实测记录(真实会话挂上订阅打出 payload),拿到「有没有预算信号」的事实再定判定信号。「不自作主张」这个口径是对的。上面 1、3 两个 issue(pin 豁免立项、父/子开关收口)我随后开出并回链到这里。
heptaspirit commentedon Sep 21, 2026
第二批:seam 实测可以省掉一半
结论是这两个事件的载荷里都没有 token/用量信号,所以「挂订阅打出 payload」这一步可以不做——载荷是宿主代码里的定义,读定义比跑一次更硬。问题收窄成一句:插件能不能从自己的 ctx 读到用量。
出处是本机装的宿主实现,
@deepseek-ai/dsh-agent-loop@0.1.5-rc.2(宿主dsh-plugin-desktop@2.0.13):agent/pre-step(lib/index.js:894)载荷{ messages, turn, step, signal },waterfall派发,订阅者能改写本步进入的消息(返回{ kind: "enter", messages })。载荷里没有用量。agent/turn-stopping(lib/index.js:967)载荷{ turn, signal },serial派发,只能旁观、改不了结局。也没有用量。assistant/message带usage(适配器上报,lib/index.js:1108-1114);loop 的 assembler 另有一个 live getterusage(lib/index.js:466),注释原文是 "Latest adapter-reported usage in the stream"。loop 自己认得终态max-tokens(lib/index.js:1115),与 blocked/completed/aborted/error 并列。所以「接近尾声」的判定信号只可能来自
assistant/message.usage,或者 assembler 那个 liveusage;事件载荷这条路是堵的。版本上留个保留:我读的是本机这份安装产物,不是上游仓库的 main,两个版本之间有没有差异我没对过。
还差的一步是可达性——订阅回调只拿到上面那些字段,当前上下文占用不在其中;插件能不能经自己的 ctx 拿到 agent/session 对象,是这条线真正的未知。查清这个,N2 才有判定信号的来源。顺带复核了你说的现状:我们的 src 里确实一个
agent/pre-step/agent/turn-stopping引用都没有。fix(guide): 第 5 条写出 memory_forget 的完整副作用面,memory_save 尾句改短(slow-stack…
modusensus commentedon Sep 21, 2026
@heptaspirit 第二批收下了,而且这半条路确实省得干净:「挂订阅打 payload」从 N2 前置里划掉——事件载荷是宿主代码里的定义,读定义比跑一次更硬,结论采纳。
核对一遍你的出处(读的是安装产物
@deepseek-ai/dsh-agent-loop@0.1.5-rc.2):agent/pre-step载荷{ messages, turn, step, signal }、waterfall 派发、可改写进入消息——对。agent/turn-stopping载荷{ turn, signal }、serial 派发、只能旁观——对。assistant/message.usage(lib/index.js:1108-1114)与 assembler live getterusage(lib/index.js:466),loop 终态认max-tokens(1115)——对。所以「接近尾声」的判定信号只剩
assistant/message.usage或 assembler 的 liveusage,事件载荷这条路是堵的。这一粒收口:N2 的 seam 结论 = 事件载荷无用量信号,判定信号只能来自会话日志或 assembler。剩的未知也正如你说的——ctx 可达性。插件能否经自己的 ctx 拿到 agent/session 对象、再够到 usage。这条我补一个插件侧现状:
src/index.js目前消费的 ctx 面只有ctx.logger / ctx.llm / ctx.commands / ctx.webServer / ctx.reflect,没有任何对 agent/session/usage 的读取——不是我们没用,而是连一个可依赖的用法先例都没有。所以可达性只能宿主侧实测,仓库代码里查不出来。版本保留那条同意:读的是安装产物不是上游 main,N2 立项时以安装产物为准、上游差异核对列为立项步骤之一。
拍板(N2 立项前置收窄为一条):
你在宿主侧刚翻过 agent-loop 源码、环境是现成的,这个单点实测你来做最顺;插件侧现状我这边已经摸清(上面那行 ctx 面)。结论贴回这里,过了就开 N2 立项、挂了就挂起并记一笔「等宿主」。
另外两条旧账在这里对齐一下:
agent/pre-step/agent/turn-stopping订阅,与你复核一致 ✅关键判断:N2 前置只剩「ctx 可达性」一个实测点,由你在宿主侧做、结论贴回;seam 结论(事件载荷无用量信号)已定稿。
Merge pull request #277 from heptaspirit/fix/249-guide-wording
feat(tools): 工具暴露开关 disableMemorySearch/disableMemoryArchive + memory…
7 remaining items
heptaspirit commentedon Sep 23, 2026
@modusensus 前两批都合了,按 §10 的排序接着做 N3(压缩边缘双落点),之后 N2 首版只计量、不注入。口径之前都对过,只剩一件要你点头:
N3 的末尾消息那条已经确认可行(宿主
agent/turn-stopping在 dispatch,dsh-repeat-tool-reminder是现成先例);写库那一半我打算新开一个连续性 type,不复用 #230 的 document 指针行,免得两份权威。你要是觉得该复用,说一声就行。其余按 §10 走:两个时机节点都挂成
autoInject的子项、默认关,与第二批同形。没别的意见我们就开工。
heptaspirit commentedon Sep 23, 2026
@modusensus
N3 那半我先按提案态想了一版,不多说。
形态是把写库改成落提案、转正才进 memories:压缩边缘只写一条 pending 行,
(session_id, kind)唯一键保证同一会话同一类只留一条,队列满则弃新,摘要截断到上限,全程不调模型。这样幂等和不打扰都是现成的,要不要进库还能和 #254 的二次确认共用一套。前置我核过:
compaction/start、compaction/summary、compaction/end都在宿主@deepseek-ai/dsh-session的事件白名单里(lib/types/known-event-types.js:31,34),append 在@deepseek-ai/dsh-compaction-basic(lib/index.js:467,478,605)。不用宿主加接口。按你的节奏,等你一句话。
modusensus commentedon Sep 24, 2026
@heptaspirit 两楼一起答:都同意,开工。
写库那半不复用 #230 的 document 指针行,新开连续性 type——就这么定。 两份权威比一份冗余行难收拾,这个方向不用再议。
pending 行形态照你 17:31 楼的方案走,三点都对路:
(session_id, kind)唯一键、满则弃新——同一会话同一类只留一条,这正是压缩边缘该有的语义;confirm打开时,N3 的转正就是同一个机制的第二个消费方——机制建一次,两处受益。前置核查(事件白名单 + append 位置)你已经对到行号了,宿主侧零改动,不再重复核。两个时机节点挂
autoInject子项、默认关,与第二批同形,按 §10 排序推进。验收线照惯例:回归测试 +
npm run sync+ 文档节。feat(continuity): 压缩边缘双落点(slow-stack#249 N3)
feat(continuity): 压缩边缘双落点(slow-stack#249 N3)
heptaspirit commentedon Sep 25, 2026
N3 那一条已经开成 PR:#314(https://github.com/slow-stack/mneme/pull/314)。
实现按你 09-24 拍的口径:写库那一半新开连续性 type,不复用 #230 的 document 指针行;压缩边缘只落一条 pending 行,
(session_id, kind)唯一键保证同一会话同一类只留一条;队列满弃新,摘要截断到上限,全程不调模型。两个时机节点都挂成autoInject的子项,默认关。正文末尾有三处口径问题想请你定:注入消息走英文单一正本、抽取长度 200 与 900 是我定的量级(没有依据数据)、降级路径现在挂在
memory_save的描述尾部。feat(continuity): 压缩边缘双落点(slow-stack#249 N3)
Merge pull request #314 from heptaspirit/feat/249-n3-continuity
modusensus commentedon Sep 26, 2026
N3 批次已随 v0.8.8 发布(PR #314:压缩边缘双落点,
continuityRescueEnabled)。N0(会话开头能力说明)与 N1/N2 批次未动工,本单保持开放——后续批次在 Discussion #164 线上继续对齐后再认领。modusensus commentedon Sep 26, 2026
N3 批次已随 v0.8.8 发布(PR #314:压缩边缘双落点,
continuityRescueEnabled)。N0(会话开头能力说明)与 N1/N2 批次未动工,本单保持开放——后续批次在 Discussion #164 线上继续对齐后再认领。heptaspirit commentedon Oct 2, 2026
#249 第二批里最便宜的那件做了:PR #355。
TOOL_GUIDE.memory_save补了 scope 声明规则,就是我在评论里拟的那句,两处小改后定稿。落点是工具描述而不是总则:它是单工具的判据,总则那五条讲的是「何时查 / 何时写 / 何时 no-op」,加第六条会把单工具语义抬成全局纪律。测试锁两件事,这句话在工具描述里,且不出现在总则段。后半句(默认两个都别填)不是凑字数。只写「什么时候该填」会把模型推向「能填就填」,而收窄一条本该全局可见的记忆,损失是静默的,把默认方向也写出来这句才是一条能执行的判断规则。
N0 / N1 / N2 一点没动,PR 里也没写
Closes,余下批次还是按你 09-26 那条「在 Discussion #164 线上对齐后再认领」。面板侧呈现归你。全量:1498 tests / 1497 pass / 0 fail / 1 skip。
feat(guide): memory_save 指引补 scope 声明规则(slow-stack#249 第二批)
Merge pull request #355 from heptaspirit/feat/249-scope-guidance
preInjectGate 注入前判定:意见/立场记忆先判定后处置(issue slow-stack#380,E12 D1 裁决落地,op…