Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3E63DD?style=flat-square" alt="license"></a>
<a href="https://github.com/slow-stack/mneme/actions"><img src="https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml?style=flat-square&label=CI" alt="CI"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-22%2B-3E63DD?style=flat-square&logo=nodedotjs&logoColor=white" alt="node"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-2660%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-2680%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://codecov.io/gh/slow-stack/mneme"><img src="https://img.shields.io/codecov/c/github/slow-stack/mneme/main?style=flat-square" alt="coverage"></a>
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome"></a>
</p>
Expand Down Expand Up @@ -272,7 +272,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790

```bash
cd dsh-mneme && npm install
npm test # 2660 个测试
npm test # 2680 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -554,7 +554,7 @@ Community projects that wire mneme into more platforms — not part of this repo

```bash
cd dsh-mneme && npm install
npm test # 2660 tests
npm test # 2680 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
14 changes: 14 additions & 0 deletions dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## [Unreleased]

- **图谱的锚定层补上另外两路起点,被动确认有了能查的地方(#24 收尾)**:issue #24 的四块设计里,块 1 的「精确名 / Tag / 向量三路起点」当初只接了精确名一路——`anchoring.js` 本身早就支持多路(`anchorSeeds` 累加、`HOP_QUOTA` 分层都在),缺的是 `service.js` 的调用点只喂了 `normalizePath(..., { path: "exact" })` 一路,而通路身份在合并那一步就丢了(`normalizePath` 的返回值里没有 `path`,`anchorSeeds` 里 `p.path ?? "?"` 把每一路都记成 `"?"`)。块 4 的「例外管理」同样只有自动确认、没有可见面——面板里 `passiveConfirm` 一次都不出现。

**Tag 路走全等匹配,不走子串**:`store.findEntitiesByTags` 把查询按标点和空白切成词、滤掉单字(中文单字作 tag 没有区分度),再要求 `tags` 数组里**某个 tag 与词完全相等**。子串匹配会让 `ai` 命中 `mail`、让 `Java` 命中 `JavaScript`,而这条路的作用是「用户提到了某个主题词」,模糊命中的代价是把整类无关记忆拉进图轴。排序按命中记忆数降序、名称升序——同分时要有确定性顺序,否则同一查询两次跑出来的种子次序不同。

**锚点反查要看关系两端,不能只看 attr 行**:`findEntitiesByTags` 与 `entityIdsForMemories` 首版都只读 `entity_attrs`,而 `dream/sleep.js` 的 `phaseRelations` 只写 `entity_relations.memory_id`、不建 attr 行——仅由关系关联的记忆,对关系两端一个种子都生不出来(实测:只建关系不建 attr 时两个反查都返回空,而走 `saveAttr` 的对照组正常)。两个函数改为 `UNION` 三源(attrs / from / to),与 `getLinkedMemoryIds` 早就有的 attr|relation 双 tier 口径一致。同一条语句有三处 IN 列表共用记忆 id 集合,命中一多就撞 SQLite 变量上限,而调用方 `try/catch` 兜着——越限的表现是**整条 Tag 锚点静默消失**,故按 500 一批分块,块间 `via` 直接相加(各块记忆集合互不相交)。

**向量路复用同一份 query 向量**:`searchMemories` 里已经有 `qv`,把它以 `{ queryVector }` 传进 `entityRecall`,而不是在图轴内部再嵌一次——二次嵌入既多一次模型调用,也会因为本地模型的随机性让「检索用的向量」和「图轴用的向量」不是同一个,而这种偏差只在图轴结果上体现,排查时看不到。近邻记忆到实体仍是 MAX 语义(一条记忆挂多个实体时取最高分),最低分项**必须滤掉**:min-max 会把这路里最弱的那条压成 0,而 `anchorSeeds` 照收 0 分种子,下游给种子挂联记忆用的是**固定满权 tier**(attr 1.0 / relation 0.9),不滤就等于「0 分的种子按满权拉记忆」。

**早退条件也得改**:原来 `if (!entities.length && !tagEntities.length) return []` 会把一个只有向量路能工作的查询直接掐死(比如查询里不含任何已知实体名,但语义上和若干记忆很近)——那恰恰是向量路存在的理由。

**被动确认的复核面**:新增 `GET /semantic/graph/boosted-relations`(`policy: "open"`,同 `/semantic/graph/*` 前缀,`SECURITY.md` 两张表不必新增行),返回「合法抬升量 > 0 的边」——`gain` 必须由后端算,因为要和**来源基线**比对(`RELATION_SOURCE_DEFAULTS`:manual 1.0 / confirmed 1.0 / tag 0.3 / llm 0.4),前端手里只有当前 `weight`。判据用 `gain > 0` 而不是 `>= 0`:`weight` 是 REAL,`1.0 - 1.0` 在浮点里可能是 `1e-17`,用 `>=` 会把每一条从未被抬过的边都列进来。**排序也必须在算完 `gain` 之后做**:SQL 按 `weight DESC` 排、循环里再按条数截断,会让 llm 边(0.95,gain 0.55)压掉 tag 边(0.90,gain 0.60),被 `limit` 砍掉的恰恰是抬得更多的那条——故收全后按 `gain` 降序再 `slice`。ego 路由的 `edges` 同时补上 `weight` 与 `source`——只看 `weight` 会把「tag 边被抬到 0.5」误读成「比 manual 边弱」,而 `0.3 → 0.5` 恰恰是被用出来的证据。面板里这一面**默认收起、展开才取数**:它是审计视角而不是日常视角;闸关时只说「今后不会再自动抬权;下面列的是此前已经抬过的边」,**不隐藏列表**(关闸之后正是复核既有提升的时候),且空表提示与闸关状态解耦——否则「闸关 + 一条都没有」会只剩一句提示、没有任何行,读起来像这一屏坏了;取数失败单独说,不冒充闸关。

验收:`test/graph-multipath.test.js`(10 条)与 `test/graph-boosted-relations.test.js`(9 条)新增,面板侧锁在 `test/client.test.js` 的 #24 用例;每条新用例都做过变异自证(把被它锁住的那行改坏,看它是否真的变红),其中两处首版用例是**空转的**、被变异自证当场抓住后重写:0 分种子用例原本把两条记忆挂在同一个实体上,滤不滤 0 分都到不了;排序用例把宽实体命名成字母序靠前的名字,按名字排也能过。全量 2680 tests / 2679 pass / 0 fail / 1 skipped。

## [0.10.0] - 2026-11-02

## 🆕 新增
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![tests](https://img.shields.io/badge/tests-2660%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-2680%20passed-success)](https://github.com/slow-stack/mneme)
[![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
[![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
[![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
Expand Down Expand Up @@ -721,7 +721,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 2660 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 2680 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
```

Expand All @@ -730,7 +730,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 2660 个测试
npm test # 运行 2680 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
52 changes: 51 additions & 1 deletion dsh-mneme/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,57 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
to: e.to_entity,
relation_type: e.relation_type,
memory_id: e.memory_id ?? null,
created_at: e.created_at
created_at: e.created_at,
// issue #24 块4:被动确认的复核要靠这两个字段——weight 是「用出来
// 的分」,source 决定它的起点(manual 1.0 / tag 0.3 / llm 0.4)。
// 少了它们,「这条边被抬过」在面板上根本看不出来。取不到时给
// 初值形状而不是 undefined,前端不必到处判空。
weight: e.weight ?? 1.0,
source: e.source ?? "manual"
}))
});
} catch {
sendJson(res, 500, { error: "internal" });
}
}
});

// --- graph boosted relations: the review side of passive confirmation ---
// issue #24 块4(被动确认)的复核出口。正常触达即 bump 边权重是安静的设计,
// 但「抬过哪些边」必须能看见,否则「例外管理」只剩自动确认没有例外。
// 判据是 gain = 现权 − 该 source 初值 > 0(见 store.listBoostedRelations),
// 不另设记账表:抬升是唯一会改变检索形状的动作,它自己就是证据。
// 只读、挂在 /semantic/graph/ 前缀下(SECURITY.md 的开放读清单已含该通配)。
register({
kind: "exact",
path: "/api/dsh-mneme/semantic/graph/boosted-relations",
policy: "open",
handler(req, res) {
try {
const url = new URL(req.url, "http://localhost");
const limit = Math.max(1, Math.min(200, Number(url.searchParams.get("limit") ?? 100) || 100));
const rows = service.listBoostedRelations?.({ limit }) ?? [];
sendJson(res, 200, {
count: rows.length,
// 提示「这张表为什么可能为空」:被确认抬升的边只在图真的被用过之后
// 才出现,两个相关闸默认都关。面板据此说明而不是让人以为坏了。
// 必须两个闸同时开才算「会继续产生新提升」——touchRecalled 里
// passive = graphPassiveConfirm && graphWeightEnabled,只看前者会让面板
// 说「会自动提升」而实际上一条都不会抬(评审指出)。
passiveConfirmEnabled: config?.graphPassiveConfirm === true && config?.graphWeightEnabled === true,
items: rows.map((r) => ({
id: r.id,
from: r.from_entity,
to: r.to_entity,
from_name: r.from_name ?? null,
to_name: r.to_name ?? null,
relation_type: r.relation_type,
memory_id: r.memory_id ?? null,
created_at: r.created_at,
weight: r.weight,
base_weight: r.base_weight,
gain: r.gain,
source: r.source
}))
});
} catch {
Expand Down
Loading
Loading