Repository navigation
Closed
Labels
Description
背景
误归因探针(D:/memory-hub/reports/attribution-probe/PREREGISTRATION.md)的 structured 臂依赖一个带 attribution 的情感关系类型 affective_context,用来区分「情境状态(situational)」与「稳定特质(trait)」。
代码里现在不存在这个东西。不先实现它,structured 臂只是把同样的文本换个表存,与 flat 臂在存储层没有可测差别,FGR 差值测到的是噪声。
现状实测(主 checkout 054c74edc51a0b3add746dbf45861b1c77ac3750,2026-11-09 21:5x)
grep -rn "affective" src/→ 零命中。整个src/没有任何情感/状态类关系类型。src/entities/extractor.js:2const VALID_RELATIONS = new Set(["uses", "depends_on", "part_of", "related_to"]);
src/entities/extractor.js:22prompt 里的 JSON 结构把枚举写死成"type": "uses|depends_on|part_of|related_to"。src/entities/extractor.js:75-78→ LLM 抽出的未知类型静默降级成function sanitizeRelationType(type) { if (VALID_RELATIONS.has(type)) return type; return "related_to"; }
related_to:写入成功、不报错、图上没有情感轴。
存储层不用改,已验证:
src/store.js:462relation_type TEXT NOT NULL,无 CHECK 约束。src/dream/decisions.js:846已经直写relation_type: "supersedes"并成功落库(live 库 108 行)。
要做的事
1. 抽取器三处(必改)
| 文件:行 | 现状 | 改成 |
|---|---|---|
src/entities/extractor.js:2 |
白名单只有四种 | 加 affective_context(及后续可能的状态类) |
src/entities/extractor.js:22 |
prompt 枚举写死四种 | 同步更新,并说明何时该用 affective_context |
src/entities/extractor.js:75-78 |
未知类型一律降级 related_to |
不静默降级:报错或至少 logger.warn 带原始类型名 |
2. 落点:新开 sleep 相位,或挂 phasePatterns 旁
不能接 phaseRelations(src/dream/sleep.js:984 附近的既有相位):
- 它只扫孤立实体(
getRelations(e.id).length === 0)做同记忆共现补边,语义是「把没连上的连上」,与情感归因无关。 inferRelationType(a, b)只有三种类型硬编码,末行return "related_to"兜底——它本来就没有情感输入端,接进去只是又一处假装能推断。
正确做法:新开一个相位,或挂在 phasePatterns(src/dream/sleep.js:984)旁边,前提是共用同一套 applyDecisions 校验(maxCreatePerRun / maxArchivePerRun 限额照旧生效)。
相关常量,实现时别踩:
src/config.js:622sleepPatternMinMemories默认 100(min 10 / max 1000)src/config.js:626sleepMaxPatternPerRun默认 3(min 0 / max 10)src/dream/decisions.js:358maxCreatePerRun ?? 5、:366maxArchivePerRun ?? 8
3. 语义护栏(产品行为,不只是探针需要)
- 情境情感只进
affective_context关系,带 attribution(归因到谁/什么场景)。 - 稳定特质才进 pattern 行(
memory_type = pattern的既有铸造通道)。 - 泛化阈值写死:同一情境类型在 N 个独立场合(不同会话)重复才允许升特质,
N=1一律不许。
验收
- 临时库(不碰
D:\DSH\.dsh\memory\memory.db)灌一条构造段 → 断言entity_relations.relation_type = 'affective_context'真实落库。 - 同一批断言
related_to计数没有跟着涨(证明没被静默降级)。 - 未知关系类型的降级路径有测试覆盖:喂一个不存在的类型,断言产生 warn/错误而非静默
related_to。 node --test test/*.test.js基线绿;node scripts/check-sync.js文件数一致(改了src/记得跑node scripts/sync-lib.js)。- 用变异自证:把
sanitizeRelationType改回静默降级,测试必须变红。
明确不做
- 不绕白名单直写
saveRelation:能落库,但等于废掉整个抽取器,structured臂测到的是一个只存在于实验里的存储层,与用户装到的东西不是一回事。 - 不接
phaseRelations:见上,语义不对、也没有情感输入端。 - 不把探针的判分逻辑塞进插件:插件只负责存储与巩固,评测在
D:/memory-hub/.eval/那边。
关联
- 预注册 HC-7:
D:/memory-hub/reports/attribution-probe/PREREGISTRATION.md - 上游方案:
D:/memory-hub/reports/voicemem-mneme-proposal.md - 同批静默降级问题(另一条线,暂不并入本 issue):
inferRelationType无输入端、vision 关闭时整条消息静默丢弃——三者修法不同,合并会写成含糊清单。
Activity
modusensus commentedon Nov 9, 2026
合并与版本策略(2026-11-10 定)
affective_context落地 + 抽取器不静默降级 + 探针侧脚本)一起进一个版本,不单独发版。工作时的口径:从 issue 出发对照实现,PR 标题带 issue 号,合并前 rebase 到最新 main。
modusensus commentedon Nov 9, 2026
实现进展(本地分支
feat/619-affective-context,未 push、未开 PR)三笔提交,从
origin/main=514d238起:62a3b3f962d3e9f58ac22完全按本 issue 的「合并节奏」执行:分支挂着,等同批活做完一次性并入同一个版本号。
验收五条
relation_type='affective_context'真落库test/sleep.test.js的 one-session / three-session 两条,断言getRelations里类型正确、source === "llm"related_to计数没涨sleep: the affective phase never adds a related_to edgerejects unknown relation types、drops a relation whose type is not even a stringcheck-sync一致 +sync-libcheck-sync118 文件一致变异自证
sanitizeRelationType改回return "related_to"const downgraded = false;relation_type改成"related_to"三条都验完还原,还原后全绿。
与 issue 正文不一致的一处决定
issue 原来写的是「
src/entities/extractor.js:2白名单加affective_context」。实现时改成不加,抽取器遇到它当未知类型拒收并 warn。理由:实体抽取发生在写入当下,手里没有会话边界,判不了「这一次」和「反复出现」;它写出来的边只有
{model},没有emotion/attribution/sessionCount,同一张表会出现两种 schema。这个关系类型只有一个写入者——sleep 的 affective 相位。docs/ENTITIES.md已写明这一点。审计发现的四处,均已修
:0:相位写边不走applied,一轮明明写了边,审计行却是「跑了但什么都没做」。现在相位单独报edgeWrites,totalApplied一并累加,但两个数分开保留含义。entity_relations没有 UNIQUE,同批段重放会叠出完全相同的边,把sessionCount抬高、主指标失真。写前先查(from, to, memory_id),重复的进dropped: duplicate edge。只在相位侧挡,没有动 store 层的通用约束——加 UNIQUE 会波及phaseRelations与抽取器两条既有写入路径,需要单独评估。noop:原来只有「写过东西」才报aborted。改成只要signal.aborted就报aborted——没写东西时循环同样断在任意下标、没扫完,noop等于说「整个池子看过了、没什么」。if (ok)无 else:校验拒绝 trait 决策时照旧返回noop,把「被拒」伪装成「没得铸」。补了failed分支。这条分支在合法决策下不可达(决策是按合法形状拼的),不写假用例假装测过。仍未做
store.saveRelation的通用幂等:待评估是否并入另一个 issuefeat(sleep): add affective attribution phase with an affective_contex…
docs(changelog): record the affective attribution phase (#619)
fix(sleep): close four audit holes in the affective phase (#619)
fix(sleep): key the affective dedupe on emotion and evidence overlap (#…
fix(sleep): count a merge-only run as a write in the receipt (#619)
fix(sleep): put the target in a trait title so two targets cannot col…
fix(sleep): refresh pattern rows instead of appending to them (#619)
fix(sleep): make a refreshed pattern countable, auditable and unborn
fix(sleep): close the four review gaps in the affective phase
Merge pull request #628 from slow-stack/feat/619-affective-context
modusensus commentedon Nov 10, 2026
Merged as 7e49452 (merge commit) after PR #628 cleared review.
What landed, in order of the ten commits: the affective attribution phase and the
affective_contextrelation type; the changelog entry; four audit holes (receipt accounting, edge idempotency keyed on the session set, interruption reported asabortedrather thanok, and a failed trait validation reported asfailedrather than silently passing); same-occasion emotions merged by session instead of dropped; merge-only runs counted in the receipt; the target put into the trait title; patterns refreshed in place instead of appended; a refreshed pattern made countable, auditable and unborn; and the four review findings below.Review findings closed in bf607b6, all reproduced before fixing:
replaceAll("N", …)also hit the wordJSON, so both the zh and the en prompt told the model to answer with aJSO3array — the only output this phase parses. Prompts now use{{MIN_SESSIONS}}, the first named placeholder inlang.js.sameOccasionnever matched for memories without provenance.memory_savewrites no provenance row, sosessionsFor()is empty and the session-overlap key cannot fire; the same evidence wrote a fresh edge on every run and inflatedsessionCount. Measured on the live database: 8517 active memories, 6846 with a provenance link, so 1671 (19.6%) were in that state. The session key stays primary; when either side has none, an evidence intersection decides.sleepMaxPatternPerRunvanished. They were neither minted, demoted nor recorded whiletraitsstill counted them. They are nowdroppedwith reasontrait cap, the same way the edge cap accounts for edges.Verification:
node --test test/*.test.js→ 3262 tests, 3261 pass, 0 fail, 1 skipped;node scripts/sync-lib.jsthennode scripts/check-sync.js→ 119 files consistent. Each of the three new tests was checked by reverting the corresponding fix and watching it go red.Two things this does not cover, deliberately:
sleepAffectiveEnableddefaults to off and does not exist in the live settings row. The phase has never run against the production database: zeroaffective_contextedges, and zero patterns carrying an appended body or a---separator. No cleanup or migration is needed.affective_context.VALID_RELATIONSkeeps its four types, and an unknown type is normalised rather than written. Entity extraction has no session boundary at the moment of writing, so the sleep phase is the only writer. This differs from the original issue text, which asked for the allowlist change as well.Merge pull request #642 from slow-stack/feat/619-affective-visible
fix(dream): 冲突账本处置留痕、取代链单向门与回执口径 (#650 评审)
fix(dream): 冲突账本处置留痕、取代链单向门与回执口径 (#650 评审)
fix(dream): 冲突账本处置留痕、取代链单向门与回执口径 (#650 评审)
fix(dream): 冲突账本处置留痕、取代链单向门与回执口径 (#650 评审)