Skip to content

[Feature] 注入形态:能力说明 + 指针 + 分时机注入(设计提案) #249

Description

@heptaspirit

注入形态设计:能力说明 + 指针 + 分时机注入

0. 摘要

现状:mneme 只有一个注入节点(systemPrompt.context,order 90),内容优先——每轮往系统提示里塞 ≤5 条记忆碎片,agent 被动接收、看不到库的规模、也不知道「何时该主动查」。

本提案把注入拆成三件事、放到三个不同的位置:

  1. 能力说明(怎么用记忆)→ 工具的描述里为主,一次性系统提示段为辅;
  2. 记忆内容(这轮相关的)→ 继续走内容块,但按类型分池(约束/偏好 pin 住且逐字保真;情景/知识才走「摘要 + 指针」并可轮换);
  3. 状态与提醒(该不该动手)→ 分时机注入,只给一句话提醒,判断权留给 agent。

两条贯穿性原则:

  • 注入的价值在于「在正确的时机给出正确的指针」,而不是「把更多内容塞进上下文」;
  • 注入位置由缓存代价决定——会随轮次变化的内容不放常驻系统提示段(否则整段前缀缓存作废),一句话级的提醒追加在序列末尾。

1. 背景与 use case

1.1 现状的三个缺口

缺口 现象
无能力说明 agent 看到的只有注入进来那几条碎片;库里有几千条、有什么工具能查、当前项目有哪些相关条目——都不知道
无判断指引 「什么时候该查」「什么时候该写」没有任何指引;每轮收到碎片可能反而只是噪音
只有一个时机 只有「每轮装配」这一个位;会话开头、接近尾声、压缩边缘这三个语义上没有动作

1.2 想达成的(use case,不是功能清单)

  • 想让 agent 在需要的时候自己查得到(而不是每轮被动收碎片);
  • 想让 agent 在该记的时候自己动手(而不是每轮回合一结束就被催);
  • 想让「长会话被压缩时正在做的事」不丢(当前 mneme 拿不到压缩前,这类连续性无从抢救);
  • 想让约束与偏好类记忆不被压缩与轮换静默降级;
  • 想让用户能看出「注入的是什么、为什么是这几条」(与 [Feature] 注入可观测性:让用户看见「此刻注入了什么」 #179 合并设计)。

1.3 三个必须避免的失败形态

(前两条沿用 #164 已对齐的判据,第三条是本提案新增,均要求可测)

  1. 记忆退化成聊天记录转储——入库条数暴涨、条目变长、来源单一;
  2. 批量把历史塞进上下文——注入块被「过去的事」占满,挤掉当前任务需要的空间;
  3. 约束/偏好类记忆被静默降级——它们与情景日志抢同一份预算、被同速率摘要,数轮压缩后只剩残缺(对照结论见附件 §6:实测一轮压缩后同类知识仅保 53%、五轮后 10%)。

其中第 3 条正是现状的结构性缺陷:constraint / preference 类条目与普通候选一起竞争注入名额、一起参与跨轮轮换、在压缩时一起被摘要——它们需要的是逐字保真,而不是相关性排序。

2. 现状核查(CONTRIBUTING 要求)

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)

触发来源三选一(与「不要一段结束就启动」对齐):

  1. 收敛阈值:当前工作的一段功能 / 测试闭环完成;
  2. 压力阈值:上下文到了必须精简的边界;
  3. agent 自判:agent 认为「这会儿必须整理」(对应 [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231 的主动接口)。

提醒只给一句话,建议口径:

本会话积累了一些可沉淀的内容(改动 N 处 / 测试通过 M 项)。是否需要整理由你判断;不需要就不做。

实施顺序上先只计量:第一版只记录「如果不是只计量、本来会触发几次」,实际不注入——拿到 realized 触发率后再决定是否默认开启。

4.4 N3:压缩边缘

在上下文即将大幅精简前,抢救「正在做什么」。必须是双落点:

  1. 写入持久存储——一条连续性记录或刷新当前项目摘要,脱离对话独立存活;
  2. 以靠近序列末尾的消息注入——因为宿主的摘要器只能看到对话里的内容:宿主压缩时会把对话按固定字段模板重建,其中就包含 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. 注入格式(什么格式)

五条约定,目标是「模型好读 + 机器可校验 + 不误伤缓存」:

  1. 位置由缓存代价决定——会随轮次变化的内容不放常驻系统提示段(系统提示在序列最前,它一变整段前缀缓存作废);一句话级的提醒追加在序列末尾(追加不破坏已有前缀)。相应地,常驻段只放零变化内容。
  2. 结构化字段清单——连续性类用固定字段(current_work / next_step / open_questions),不用自由散文;压缩类借宿主八段模板。
  3. 严格长度上限 + 统一前缀——每类注入物都有字节上限,统一前缀便于剥离与判重。参考量级:提醒句 ≤160B 且必须单行;能力说明文档 ≤4KB;扩展文档 ≤8KB。
  4. 指针优先——注入「标题 + 摘要 + 路径/id」,全文按需取。
  5. 不变不重复——文本未变则不重复注入;追加型的提醒必须做一次性判重(否则提醒会在对话里逐条累积)。

示例(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 设计成「写库 + 注入一条连续性记录」而非「改写历史」的原因。

两条硬约束(建议写进验收):

  1. 能挂 hook 的不进常驻段(常驻系统提示会影响前缀缓存);
  2. 常驻段里的内容必须稳定(同会话内不随轮次变化)。

一处待实测(不拍板):B 类留在系统提示段(现状)还是改为追加消息。两边都有代价——留在系统提示段则每轮变化作废前缀缓存;改为追加消息则会在对话里逐轮累积(参照实现的载荷是「一行 + 判重」,我们现注入块 ≤1500 字符/轮,直接搬会堆历史)。倾向:B 类维持原位但压到最小最稳,实测后再定。

8. 节流预算与失败判据(可测)

  • 注入预算:每轮注入次数上限(如普通 1 次/轮,紧急 ≤3 次/轮),用预算而非文档里的「请克制」来防正反馈。
  • 闸门必须报实测值:每个节点的实际触发率(realized usage)与 agent 实际采纳率都要可统计——只声明阈值参数不算证据。
  • 打分准入限廉价启发式:只用本地信号(本会话改动文件数 / 测试结果 / 错误数 / 轮数 / 上下文使用率计数器),不每轮调 LLM。
  • 整理动作预算:最小间隔 + 每会话上限 + 打分准入 + 工具白名单 + 默认不改(无人确认时只读不写)。
  • 四指标(沿用 聊几个我觉得值得做的方向 #164,供 [Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217 统计端点):单日入库条数 / 来源分布 / 条均长度 / 注入块中历史条目占比。
  • 三条失败判据:见 §1.3,均须可由上述指标判定(例如:注入块中历史条目占比持续 >X% 即视为「批量塞历史」;约束/偏好类条目出现被轮换或压缩后丢失,即视为第 3 条失败)。

9. 边界与先后

与其它 issue 的边界:

落地顺序(依证据强度,不依实现难易):

序 内容 依据强度
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) 关

三条语义:

  1. 默认值分两档:仓库「行为开关一律 opt-in 默认关」的纪律在这条线上约束的是父开关(父关 = 零注入、零额外 LLM 调用、零写入);子项按上面那条判据分档,只修正既有位、不引入新时机与新表面的默认开(默认配置不该保留一个已实测的缺陷),引入新时机或新表面的默认关(不勾选就看不到新形态的注入)。
  2. 打开父开关启用的是「同一个每轮块内的三件事」:基础内容块、库可见性一行、内容分池。三者都住在已有的每轮内容块里——不新增注入时机、不新增注入表面、不新增额外 LLM 调用。改变时机的子项(回合结束提醒、阈值阀门)默认关,用户不勾选就看不到任何新形态的注入。
  3. 总闸追随父开关:父关时所有子项一律不生效。各子项的持久值保留(重开父开关时恢复上次选择)——父关是「不生效」,不是「重置用户配置」。

子开关的划分标准(这条比清单本身重要):看它是否引入新的注入时机 / 新的注入表面 / 新的成本。

  • 只是修正既有块内的选择(可见性一行、内容分池)→ 随父开关默认开。理由:默认配置不该保留一个已实测的缺陷——同类知识与情景日志同池同速率摘要会静默降级规则(见 §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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions