Repository navigation
Closed
Labels
Description
背景
2608.22215(Dual-Layer)的映射条目指出我们缺一块机制:写入准入。
现状核对:
| 现有 | 位置 | 性质 |
|---|---|---|
saveWithDedupe |
src/service.js |
只有 write-new / write-update 两分支——默认「都写」 |
quality-filter |
src/quality-filter.js |
写入后评分再归档,不是写入前拒绝 |
| 密钥/PII 扫描 | #164 A2(未实现) | 属于同一设计词的一类判据 |
从成本视角看这件事更清楚:#239 的第 1 项在蒸馏前拦窗口,省的是那次调用的输入;而写入准入是在更上游决定「这条根本不该进库」,省掉的是后续所有环节(写入、抽取、巩固、注入槽位、检索噪声)。量级可能大于窗口级预判。
方案
三级判定,全部留审计,默认关:
- 确定性拒绝(零 LLM):密钥/PII 模式命中(与 聊几个我觉得值得做的方向 #164 A2 合流——A2 是本级的一类判据而不是另起一套)、空白或纯噪声、命中已有条目的去重键;
- 规则放行(零 LLM):显式高价值信号(importance 显式给出、用户偏好陈述、决策动词 + 实体);
- 不确定 → 交 LLM:维持现状路径。
每次判定写一条准入决策回执(拒绝/放行/升级 + 原因),沿用 llm_audit_logs 的 skip 口径,使「这条为什么没进库」可查。
验收
- 默认关:零行为变化(默认路径逐字节一致)——
writeAdmission.enabled/enforce与sensitiveScanEnabled均默认 false,settings.js 白名单成对注册;只计量阶段的行为逐字节保留(config.js:806注释记着 feat(admission): 第 1 级确定性拒绝——密钥/PII 与空白噪声硬拒(#254) #332 合并时 sensitiveScan 就是 null); - 开启后被拒绝的写入有审计与明确原因,且可解释(不是静默丢弃)——拒绝走
write-admission.js:349status: "skipped",审计沿用llm_audit_logs口径,metadata.deny.reason是稳定键(blank/noise/sensitive,语义各自分开报); - 密钥/隐私样例不漏放(配回归样本集,聊几个我觉得值得做的方向 #164 A2 复用同一套)——
test/write-admission-samples.js26 条样本集 +sensitive-scan.test.js16 用例(第一条用例就是样本集整体跑真判据:正样本不漏、负样本不误杀);feat(admission): 写入边界的密钥/PII 判据(#164 A2 → #254 第 1 级) #354 顺手补了 AWS secret access key 的键名规则缝; - 与 聊几个我觉得值得做的方向 #164 A2 的关系写清楚:A2 是第 1 级的判据来源,不是并列的第二个机制——A2 即第 1 级的
sensitiveScan判据(src/index.js:330接线注释写明口径),独立顶层键sensitiveScanEnabled(默认关、开了也仅告警;真拦还需writeAdmission.enabled+enforce); - 新配置键按
config.jsschema +settings.js白名单成对出现,旗标计数锁同步——writeAdmission两键与sensitiveScanEnabled成对入白名单,计数锁同步。
落地记录(对照 main,2026-11-10)
批次:7fd9be7(只计量不拦截)→ 52dee2f(第 1 级确定性硬拒)→ 029dd5a(内容哈希计量 + 归档行纳入去重候选)→ 8dc2a59(A2 密钥/PII 判据,PR #354)→ 0e45a61(复审:启动期审计清理读装配后的 cfg)。测试:write-admission.test.js 35 用例 + sensitive-scan.test.js 16 用例。
还剩什么(开单理由的存续部分)
- G1/G2 阈值(会话写入预算 / 同话题重写间隔):故意只计量不拍数,等
llm_audit_logs攒出真实分布(数百活跃会话量级)后在本帖贴分布,再提第二阶段(阈值 +memory_save({confirm:true})); - A2 覆盖面:判据目前只挂会话写入路径(
service.js:1566的准入汇流点,判据要求sessionKey在场)——autoSummarize 与 dream 输出两个出口未接入(feat(admission): 写入边界的密钥/PII 判据(#164 A2 → #254 第 1 级) #354 评论区提的「第二批」,未做),共享包导入、bootstrap 这类无 sessionKey 的写入同样不在覆盖内。
参考
- 2608.22215(Dual-Layer Agentic Memory with Fast Write Routing and Slow Consolidation)——其"路由三分类
non-write / write-new / write-update"正是在说写入准入;我们的saveWithDedupe只实现了后两类 - 聊几个我觉得值得做的方向 #164 A2 密钥/PII 扫描:属于第 1 级的判据来源,不是并列的第二个机制
- [Feature] 巩固/总结的高峰感知调度:成本感知级联 + 有界检查点 + 错峰队列 #239 成本感知级联:本 issue 在其上游(拦在写入前,省掉后续全链路)
- 2609.05553(EdgeMem):能不用模型判定就不用——本 issue 前两级都是零 LLM
Activity
heptaspirit commentedon Sep 21, 2026
更新:下面另发了一条第二版整合稿(行号换成符号锚,末尾补了次数缺口一节)。这条保留原样。
正文现状表三条我按
upstream/main @ 360f7c8逐条核对过,现在仍成立。附一下代码位置;另外有两个点会改变实现口径,想先请你定。 现状核对 -saveWithDedupe只有两支:命中合并(src/service.js:1183),否则新建(:1245)——没有拒绝分支,表里「默认都写」准确。 -memoryQualityFilter在src/service.js:1143评分,处置却落在写之后(合并路径:1239、新建路径:1264,applyQualityDisposition定义在:1287),默认开(src/config.js:478)。它确实不是写入前拒绝。 - 密钥/PII:upstream/main的src/里 grepsecret|PII|redact|api_key零命中。 - 审计与旗标面可直接复用:llm_audit_logs(src/store.js:181)已有status: success|error|skipped与自由metadata,写入口saveLlmAudit(:1746);旗标三处是src/config.js的 schema、src/settings.js:42,91(嵌套键按点号平铺)、lib/client.js:1830的FEATURE_GROUPS加双语文案,计数锁在test/api.test.js:667。 两个口径 1. 第 1 级「命中已有条目的去重键」判 non-write,和现有合并语义冲突。 现在同 title 命中就走 append 合并(src/service.js:1192;_overwrite: true时直替,:1189),按你的三分类它属于 write-update。若去重键命中一律判 non-write,等于把合并一起取消;若判 write-update 放行,第 1 级就只剩密钥/PII 与空白噪声两类判据。我倾向后者,但「调用方声明新建」与「默认合并」要不要拆成两个入口,得你定。 2. 第 1 级把同步去重从「合并」升级成「拒绝」,会放大一个次序耦合。 我最近读到 2606.24535(Governed Shared Memory for Multi-Agent LLM Systems),里面记了一个同构事故:同步的近重复门抢在异步矛盾检测之前生效,本应被裁决的矛盾写入被门直接拒掉,裁决对象根本进不了库——它把这一类列为四个失效模式之一。mneme 这条路上正好是saveWithDedupe(同步)与 autoDream 冲突检测(异步)前后相接。所以「去重键命中」这条判据走「拒绝」还是走「合并」,风险不是一个量级;文献那边给的修法是去重门只标记、不拒绝。 如果第 2 点你也认为是同一类风险,第 1 级的「去重键命中」可以降级为标记 + 记审计(不拦),硬拒绝只留密钥/PII 与空白噪声。这样第 2 级的规则放行也不必和合并抢同一个判定面。 最后问一下要不要我搭把手,这个功能的面我这边已经核对过,拍板后就可以开发了。 @modusensusheptaspirit commentedon Sep 22, 2026
@modusensus
这是第二版,把 09-21 那条整合重排了一遍,末尾补两节那次没覆盖的东西。
现状核对
saveWithDedupe只有两支:命中合并(src/service.js›saveWithDedupe),否则新建,没有拒绝分支;表里「默认都写」准确。memoryQualityFilter是评分之后在写入之后处置(默认开),不是写入前拒绝。src/全树检索零命中,第 1 级目前没有现成判据可用。llm_audit_logs(status: success | error | skipped+ metadata)与saveLlmAudit;旗标三处 =src/config.jsschema、src/settings.js白名单、lib/client.js的FEATURE_GROUPS,计数锁在test/api.test.js。两条口径
第 1 级剩两类判据。 「命中已有条目的去重键」判 non-write 与现有合并语义冲突:同 title 命中走 append 合并,判 non-write 会把合并路径变成拒绝路径。倾向判 write-update 放行,第 1 级只剩密钥 / PII 与空白噪声。
去重门也走二次确认,不是硬拒。 把同步去重从「合并」升级成「拒绝」会放大次序耦合。文献 2606.24535(Governed Shared Memory for Multi-Agent LLM Systems)把这类事故列为失效模式之一:同步的近重复门抢在异步矛盾检测之前,把本该被裁决的矛盾写入直接拒掉。这里的次序是
saveWithDedupe(同步)在前、autoDream 冲突检测(异步)在后,正好是那个形状。我原先写的是「只标记、不拒绝」,现在觉得二次确认更好,但它有一个前提:提示文案必须把「这是一条需要裁决的矛盾」写成二次触发的合法理由。否则它照样退化成那个失效模式:AI 收到「已有相似条目」之后默认放弃,矛盾被静默丢掉。二次确认在这里的作用是让人停一下,不是让人写不进去。
补一节:收窄之后漏出来的次数缺口
按上面收窄,第 1 级只拦密钥 / PII / 空白噪声。那「同一件事反复记」就没人管了:去重键是 type + title + scope 三元组,换标题绕过 title 维,换 type 绕过 type 维;归档行不参与比对,所以一条被质量闸归档之后,同样的内容还能再写一遍。
建议再加两道闸门,同样走二次确认:
不做硬拒,因为硬拒在 AI 不知道闸门存在时会直接丢掉信息,而闸门一旦做内容判定就有误杀面。二次确认只加一次成本,且成本随叠加次数递增,越叠越贵。
两条都要留两个穿透口:用户显式要求「记住这个」,以及 pinned(constraint / preference)。后者与 #275 拍板 5 的 pinned 豁免同口径。
二次确认一次服务三条线
这个形态不只是 #254 的闸门,它在另外两条线上各占一个位置:
三处要用同一个形态,就值得把接口一次定清楚,而不是三个 issue 各做一套。这也是我把这件事写在 #254 而不是拆成三条的原因。
需要拍板
memory_save({ confirm: true }))、返回 token 带回,还是同话题第二次隐式放行。modusensus commentedon Sep 23, 2026
@heptaspirit 第二版收到,两条口径都同意,五个拍板项逐条定:
1. N 与 X 的取值:同意「先记录不拦,由遥测定」。 落地顺序定为两阶段:第一阶段闸门只记审计行(复用
llm_audit_logs的status:'skipped'+ metadata 标gate:g1/g2),不拦截、不改变任何行为——这一阶段对用户零风险,也正好产出第 5 点要的水位数据;等遥测有了真实分布(会话内新建条数的 P95、同话题重写间隔),第二阶段才把拦截打开(届时按仓库惯例 opt-in 默认关 + settings.js 白名单 + lightMode 考虑)。不要在没数据时拍一个数。2. 纯合并不算预算。 会话预算管的是「库里多了几行」,合并是去重机制在正常工作——把合并算进去等于惩罚系统做对了的事,还会让 AI 学会规避合并(换标题绕开,正好制造你要防的那类行)。只计新建行。
3. 二次确认用显式参数:
memory_save({ confirm: true })。 三个候选里只有它把「这是第二次、AI 确认过理由」变成审计行里可读、可回放的事实;返回 token 带回依赖 agent 恰当管理上下文(脆),同话题第二次隐式放行则把「停一下」退化成「多调一次」——隐式方案下模型重试的性价比高于思考,闸门就名存实亡。显式参数还让拦截与放行在审计里成对出现(第一次skipped/gate→ 第二次confirm:true放行),回放时因果完整。4. 要,而且是硬要求。 提示文案不写明「需要裁决的矛盾也是合法理由」,二次确认就一定退化成 2606.24535 那个失效模式——这不仅是「要不要写」,是第 2 条口径成立的前提条件,验收时把这条文案当规格审(同款红线还有:文案描述的行为必须与工具实况一致)。
5. 全局速率上限:不做。 会话预算 + 话题冷却已覆盖已观察到的失效形态(单会话内跑飞),全局上限没有数据支撑它拦的是真问题而不是正常重度使用(冷启动、批量导入这类合法场景都在会话外),多一道闸多一个误杀面。等遥测出现跨会话洪水的证据再议。
两个穿透口(用户显式要求记住、pinned 的 constraint/preference)确认,与 #275 拍板 5 的豁免同口径;G1/G2 的实现 PR 按三处共用一个接口的思路走(#249 注入时机、#275 水位遥测、本单闸门),接口形状在实现 PR 里一次定清,不三个 issue 各做一套。
heptaspirit commentedon Sep 23, 2026
@modusensus
按你拍的口径,补两个实现侧的信号选择。
计量信号用内容哈希,不用相似度。 「先记录不拦」要记录什么,决定了遥测能不能用。相似度阈值在这里天然不可分:同一件事换个说法就掉下去,不同的事共享话题词又顶上来,阈值往两边挪都是在误判之间来回。把 title 与 content 先归一化(大小写、空白、标点)再取哈希,命中就是精确重复,零成本,只落一个标记列,正落在「记录不拦」里。归一化口径要一次定死,后面改要带存量重算策略。
去重候选集把归档纳入。 你在那节点到了,我确认一遍:归档行不参与比对,所以一条被质量闸归档之后,同样内容还能再写一遍,去重键是新的,没有任何东西拦它。按结构化锚召回可以先收窄成本,不必每次全扫。两个穿透口与你说的 pinned 豁免同口径。
fix(index): 启动期审计保留清理改读装配后的 cfg(slow-stack#254 复审)
feat(admission): 写入准入第一阶段:只计量不拦截(slow-stack#254)
fix(index): 启动期审计保留清理改读装配后的 cfg(slow-stack#254 复审)
modusensus commentedon Sep 24, 2026
@heptaspirit 两条都收,按这个做:
1. 计量信号用内容哈希,同意。 相似度阈值在「只记录」阶段的不可分你论证得够清楚——记录阶段的判据要的是零成本、可复算、无争议,归一化哈希三条全占。两个前置照你说的办:归一化口径(大小写、空白、标点)一次定死并在代码注释里写明动机;后续若要改口径,带上存量重算策略再动,标记列只加不改语义。
2. 归档行纳入去重候选集,同意。 这正是 #275 楼里说的分界的另一半——归档出口止体积、不止重复,剩下的穿透口就靠去重候选集兜住。结构化锚先收窄比对成本、不做全扫,方向对;两个穿透口与 pinned 豁免同口径,一并在实现里写清。
两条都落在第一阶段「只记录」里,不改变拦截行为,不需要新立项。#305 我这边接下来逐个 update-branch 走审查合并。
Merge pull request #305 from heptaspirit/feat/254-write-gate
feat(maintenance): 存储无损回收维护入口(#275 第一批)
feat(admission): 内容哈希计量与归档行纳入去重候选集(slow-stack#254)
heptaspirit commentedon Sep 24, 2026
@modusensus
三条跟这一批相关的补充。
一、G2 现在测的那个现象,代价在注入侧。 同话题重复新建出来的行不是重复文本,是同一件事的不同侧面,所以按现在的去重键它们不会、也不该被拦(跟你定的「纯合并不算预算」同理)。它们真正的代价在注入面:单轮只有
maxInjectedItems条(默认 5),一族长到几十条之后,能给出「这件事现在按什么口径走」的概率就低了。这个后果面该由整理收,不在这批里,我在 #231 补了机制。二、候选集的锚还能多一类:实体关联。 你给去重候选集定的口径是「按结构化锚召回先收窄成本,不必每次全扫」,当时用的是话题引用与文件路径。抽取开着时还有一类现成的锚:
entities/entity_attrs/entity_relations三表都带memory_id,而store.getLinkedMemoryIds(entityIds)已经把「实体集合 → 记忆集合」这一步做好了(当前有效属性行直挂的记 attr 档、关系行挂的记 relation 档,同一记忆取更强的 attr)。同一个锚既能喂去重候选集,也能喂 #231 的整理候选,不必各造一套。要注意两个前提:抽取默认关(entityExtractionEnabled),以及entityRecall现在把归档行排除,而升格吸收不会把属性行迁走(document.js不碰实体表,对比decisions.js的migrateAttrsToMemory),所以「锚到归档行」这条路今天是不通的。三、
CHANGELOG.md里有一句普适表述站不住。 「写入准入第一阶段」一节写「会话内新建条数按会话直接计数即得」,但计量点落在llm_audit_logs上,而计数函数countLlmAudits({ source, sessionKey })只过滤来源与会话键两个维度:豁免行(metadata.exempt='pinned')与 G2 的同话题行共用同一会话键,所以这句话只在「没有豁免行、也没有同话题重复」的场景成立。代码注释给的补救是调用方自己加一个排除豁免行的条件,也就是「先计数再修正」;那句话读起来像是「直接计数即可」。要不要改成带条件的表述我听你的,不改也能用,只是按字面算会偏。heptaspirit commentedon Sep 24, 2026
@modusensus
补一条关于这批信号用途边界的看法,免得它被当成「减少库体积」的主力。
内容哈希这条量的是穿透,不是粒度。 它在「同一个事实被再写一遍」时命中;而一族碎片里更多的是同一件事的不同侧面、或不同时间的口径,那些行归一化后并不相同。所以它对注入面的又散又多基本没有作用。归档回收同理:它清的是磁盘,归档行本来就不进注入。
真正减少注入面行数的是升格聚合。 一族被吸收归档之后只剩指针行,注入池跟着降。那属于 #231 的整理动作,不在这批里。
第二阶段打开时,提示里该带什么。 你定的机制是超预算返回原因、由 AI 显式二次触发才落笔(
memory_save({confirm:true})),并且提示必须把「需要裁决的矛盾」写成合法理由。补一个同类的东西:同话题的重复命中可以在提示里带上「已有的那一条」,让 AI 有机会选择并入它而不是新写。这样闸门仍然只做成本判定、选择权在 AI,不做内容判定这条底线不动。要不要做、什么时候做,等 G2 的遥测有分布再说。Merge pull request #311 from heptaspirit/feat/254-hash-marker
8 remaining items
heptaspirit commentedon Oct 2, 2026
A2 判据模块写完了:PR #354。
按你 09-28 那两条约束落的:
metadata.deny.reason='sensitive'加kind/label,enforced由decision推出;feat(admission): 第 1 级确定性拒绝——密钥/PII 与空白噪声硬拒(#254) #332 里留的sensitiveScan注入点现在接到了真判据,write-admission.js的决策形状一行没改。referenceScan)。test/sensitive-scan.test.js第一条用例就是把test/write-admission-samples.js那 26 条整个跑一遍,正样本不漏、负样本一条不误杀。referenceScan没删,它现在只服务闸门那组测;按你说的可以删,留给你或者和下一批一起收。判据自己的闸是顶层键
sensitiveScanEnabled,默认关;开着也只是仅告警,真拦仍要writeAdmission.enabled加enforce。有一处要你拍板:我没把 A2 挂在
writeAdmission.enabled底下,而是独立了一个键。 两批判据(空白噪声 vs 密钥 PII)的误杀面差一个量级,绑在同一个开关上就没法单独看 A2 的命中分布,而kind落审计的用途正是先看分布。你要是更想要「打开写入准入就两批一起跑」的形态,说一句我折成writeAdmission.enabled的子行为,能省一个键。顺带说一个探针里挖出来的真漏放:AWS 的 secret access key 两条规则都够不着。它没有前缀,通用那条又要求
secret后面紧跟赋值号,多词键AWS_SECRET_ACCESS_KEY=不满足。补了专用的键名规则(40 位下限),并把「赋值型那条不看值的形状」一起锁进测试。第二批(
autoSummarize与 dream 输出那两个出口)没做,所以 PR 里没写Closes #254,等你认领或指派。全量:1512 tests / 1511 pass / 0 fail / 1 skip。
feat(admission): 写入边界的密钥/PII 判据(slow-stack#164 A2 → slow-stack#254 第 …
feat(admission): 写入边界的密钥/PII 判据(slow-stack#164 A2 → slow-stack#254 第 …
feat(guide): memory_save 指引补 scope 声明规则(slow-stack#249 第二批)
feat(admission): 写入边界的密钥/PII 判据(slow-stack#164 A2 → slow-stack#254 第 …
Merge pull request #354 from heptaspirit/feat/164-a2-sensitive-scan
fix(380): 判定与滤除移进 injectCandidates 的 touchRecalled 之前,并补四条护栏
preInjectGate 注入前判定:意见/立场记忆先判定后处置(issue slow-stack#380,E12 D1 裁决落地,op…
feat(386): 面板可手动新增记忆,字段与去重对齐 memory_save(slow-stack#386 第 5 条)
feat(scope): 作用域级可逆闸门——把一路记忆单独按下与松开 (#471)
fix(index): 失败表保留期从写死改成可配(reflectionFailureRetentionDays)
modusensus commentedon Oct 20, 2026
现状(2026-10-21 对照
main实测)写入准入已实现四批,但 issue 正文的五个验收项一个都没勾——而且其中三项已经满足。
已落地
config.jswriteAdmission: z.object({ enabled: z.boolean().default(false), enforce: z.boolean().default(false) });settings.js:196-197白名单成对注册write-admission.js:349status: "skipped";模块头:48写明审计口径复用llm_audit_logs(trigger_source=组件名、operation_type=…),reason是稳定键sensitive-scan.test.js16 个用例;判据来自 #164 A2(8dc2a59feat(admission): 写入边界的密钥/PII 判据)write-admission.js:2记着「#164 的『第三腿触发』」口径,52dee2f是第 1 级确定性拒绝(密钥/PII 与空白噪声硬拒)settings.js:196-197两键成对;test/settings.test.js有白名单计数锁落地批次:
7fd9be7(只计量不拦截)→52dee2f(第 1 级硬拒)→029dd5a(内容哈希计量 + 归档行纳入去重候选)→8dc2a59(A2 判据)→0e45a61(复审:启动期审计清理读装配后的 cfg)。测试:write-admission.test.js35 个用例 +sensitive-scan.test.js16 个。真正还缺的
write-admission.js头部注释写明「G1/G2 的阈值继续只计量,等遥测有分布再单独提」——即近重复/矛盾检测的阈值故意留空,等真实分布。config.js:726附近按 2026-09-22 的判定归到 write-update。还缺什么
接手建议
代码不用动。这条现在最该做的是把正文与实现对齐——否则看起来像从没开工。
modusensus commentedon Nov 9, 2026
正文已与实现对齐(2026-11-10):五个验收项勾掉(证据写在各项后面),补了落地记录与「还剩什么」两节。
开单理由还剩两条,都收进「还剩什么」节:
llm_audit_logs攒出几百活跃会话量级的分布后,把分布摘出来贴在本帖,再提第二阶段(阈值 +memory_save({confirm:true}));第 2 级(近重复/矛盾检测)短期内不做的判断不变,本帖保持开放等分布数据;若想收干净,也可以按「第 1 级已交付」拆分——拆不拆看下一步排期。
modusensus commentedon Nov 11, 2026
第 1 级已交付,五个验收项 2026-11-10 已与实现对齐(见上一条评论),按「第 1 级已交付」拆分收口:
第 2 级(近重复 / 矛盾检测)短期不做的判断不变,不开 issue。