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
22 changes: 12 additions & 10 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-3375%20total-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-3399%20total-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 All @@ -29,10 +29,11 @@

## 📰 最近更新

**v0.14.0-beta.1 — 两条记忆打架,现在有账可查了**(2026-11-11,预发布)
**v0.14.0-beta.2 — 模型终于知道一条记忆有多旧了**(2026-11-11,预发布)

矛盾对不再直接交给模型判生死:先记进 `conflict_ledger`,再按四档分诊——近重复走取代、跨作用域只记账、时序演进落取代链、真矛盾两条都活。默认 `conflictLedgerMode: "off"`,行为与 0.13.0 完全一致;想先看分档分布可以把档位拨到 `shadow`,它只记账不动任何一条记忆。
同一批还修掉两个「不跑就没人知道」的故障:归档把活跃量压到阈值基线之下后,夜间巩固从此静默失联;热记忆让注入块在一轮里反复变化,同一轮快照能追出十份。
此前注入给模型的每一行记忆都没有日期——「三天前说要 A、昨天改口说 B」在它眼里是两条等重的陈述,先后关系全靠正文里有没有恰好写着日期。这一版把日期补进所有渲染面:注入行、检索回执、单条读取。年龄一律看 `created_at`(`updated_at` 会被巩固刷新,实测最久会年轻 51 天),只给日期不给时分,坏值不渲染。
同一批还上了**图像描述档**(`imageDescribeEnabled`,默认关):蒸馏前让一个能看图的模型给每张图写一句描述并进转录——此前一张报错截图进库只剩 `[图: xxx.png 1543x983]`,模型知道「有过一张图」但不知道图里是什么。不读图字节(宿主按模型能力决定编不编)、必须独立路由、描述失败绝不影响蒸馏。
另修面板三处「有渲染、没登记」与地志子开关渲染两遍。

> 逐条见 [CHANGELOG](dsh-mneme/CHANGELOG.md) · 完整版本历史见 [Releases](https://github.com/slow-stack/mneme/releases)

Expand Down Expand Up @@ -297,7 +298,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790

```bash
cd dsh-mneme && npm install
npm test # 3375 个测试
npm test # 3399 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -336,12 +337,13 @@ dsh-mneme 的机制设计与下列公开研究相互印证(以下引用均逐

## 📰 Recent updates

**v0.14.0-beta.1 — When two memories disagree, there is now a ledger** (2026-11-11, pre-release)
**v0.14.0-beta.2 — the model can now see how old a memory is** (2026-11-11, pre-release)

Contradicting pairs are no longer handed straight to the model to decide who dies. They are written to `conflict_ledger` first and then triaged in four tiers — near-duplicates get superseded, cross-scope pairs are only recorded, temporal evolutions get a supersede chain, and true contradictions both stay alive. The default is `conflictLedgerMode: "off"`, so behaviour is unchanged from 0.13.0; switch it to `shadow` to watch the tiering on your own library without touching a single memory.
The same batch fixes two failures that stayed silent precisely because nothing ran: once archiving pushed the active store below the threshold baseline, nightly consolidation stopped for good without a trace; and hot memory made the injected block churn within a round, chasing up to ten snapshots for one round.
Every injected line used to arrive with no date at all, so "user wanted A three days ago, changed their mind yesterday" read as two equally weighted statements and the order had to be inferred from whether a date happened to be written in the body. This release adds it to every rendering surface: injected lines, search receipts, single-item reads. Age always comes from `created_at` — `updated_at` is refreshed by consolidation and measured up to 51 days too young on a real library — as a date only, never a time, and never rendered when the value is malformed.
The same batch ships **image descriptions** (`imageDescribeEnabled`, off by default): before distillation, a vision-capable model writes one line about each image and that line joins the transcript. Previously a screenshot of an error landed as `[图: shot.png 1543x983]` — the model knew an image existed but not what was in it. Image bytes are never read by the plugin (the host encodes them only when the routed model accepts images), the route must be configured separately, and a failed description never breaks distillation.
Also fixes three panel entries that were rendered but never registered, and a chorography sub-toggle that appeared twice.

> Per-item notes in the [CHANGELOG](dsh-mneme/CHANGELOG.md) · full history in [Releases](https://github.com/slow-stack/mneme/releases)
> Full list in [CHANGELOG](dsh-mneme/CHANGELOG.md) · version history in [Releases](https://github.com/slow-stack/mneme/releases)

## What problem does it solve

Expand Down Expand Up @@ -604,7 +606,7 @@ Community projects that wire mneme into more platforms — not part of this repo

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

## [Unreleased]

## [0.14.0-beta.2] - 2026-11-11

- **模型从此看得见记忆有多旧(时间线索):注入行与三个检索工具的回执都带日期,`occurred_at` 优先、缺省回落到 `created_at`**:模型此前拿到的记忆只有标题、类型与正文——`created_at` 从头到尾没进过任何一条渲染路径(`src/inject.js` 全文零命中,`memory_get` 连 `updated_at` 都不给,唯一露出的时间戳是 `renderTimePrefix` 的「当前时间」一句,且默认关)。于是「三天前用户说要 A、昨天改口说 B」在模型眼里是两条等重的陈述,先后关系全靠正文里有没有恰好写着日期。
- **为什么年龄必须看 `created_at`**:`updated_at` 会被巩固刷新——实测本库 `updated_at > created_at` 的 4528 行里,2765 行差值不足 1 分钟(插入时两列写同一个毫秒,正常),真漂移(≥1 天)656 行、中位 30 天、最长 51 天。拿 `updated_at` 当年龄,最长会年轻一个半月。
- **只给日期不给时分**:注入块预算紧(`MAX_BLOCK = maxContent + 600`),秒级精度在这里没有信息量,`2026-11-11` 足够回答「这是什么时候的」。新增 `src/date-hint.js` 统一切法:只认 `YYYY-MM-DD` 前缀、按 UTC 切、坏值返回空串(不渲染占位符也不抛错)——落库是 ISO UTC,跨机器要确定性,最坏差一天不影响「多旧」的判断。
- **改动面 3 个文件 4 个位置**:`src/lang.js` 的 `entryLine` / `graphHintLine` 各加一个可选 `date` 参数(不传时输出逐字节不变,三条旧断言一条没动);`src/inject.js` 在类型括号后、标题前插入日期,**降级标题行是独立渲染点,同步带上**;`src/tools.js` 三处(`memory_search` / `memory_list` / `memory_get`)由新的 `MEMORY_AGE` 同时给出 `created` 与 `updated`。`MEMORY_PROVENANCE` 一个字节没动——它有「未标注行渲染与 A2 前逐字节一致」的契约,往里加字段会让所有行都变。
- **测试**:`test/date-hint.test.js` 8 条(形状、优先级、不传时逐字节不变、传时位置正确、三工具回执同形、降级行、坏值不渲染)。三条变异各自变红:降级行去日期、`MEMORY_AGE` 里 `created` 置空、`entryLine` 不传 `date`。
- **图像描述档(`imageDescribeEnabled`,默认关):蒸馏前让一个能看图的模型给每张图写一句描述,把描述并进转录**:图片此前只以 `[图: 文件名 image/png 1543x983]` 占位进蒸馏(#611 补的,`src/attachment-text.js` 明确不调 vision、不读字节、不落原图)。占位能让模型知道「这里有过一张图」,但不知道图里是什么——用户发一张报错截图再问「这个怎么修」,蒸馏出来的记忆只剩「用户发了张图」。库已 273MB,原图档不做是既定口径(`docs/DISTILL.md`),描述档是它的替代。
- **不碰图字节**:`@deepseek-ai/dsh-llm` 的 adapter 会按 `modelInfo.inputModalities` 决定编不编——含 `image` 就原样把字节发出去,不含就换 `textOnlyImageText` 占位。所以本档只负责「把 image 块放进一次调用」,不读文件、不做 base64、不需要 attachments store。
- **必须独立 provider 槽,留空 = 整档不跑**:蒸馏路由(`summarizeProvider` 或会话主模型)多半是纯文本模型,拿它看图只会换回一句「本模型不支持图片」的占位,白花钱且看不出错。`imageDescribeProvider` / `imageDescribeModel` 两条留空的语义与其它 provider 键**不同**:不是「跟随当前模型」,是「这一档不跑」。
- **准入三态,unknown 照发**:`canSeeImages` 返回 `true` / `false` / `unknown`,只有模型**明确声明**不含 image 才跳过;查询能力失败、`inputModalities` 非数组、根本没有 `resolveModelInfo` 全按 `unknown` 处理并照发。理由与宿主一致:对未知能力是不投影,赌它不支持会静默丢掉描述。
- **300 字、单行、同附件只描述一次**:四张图各写 800 字会把真正要蒸馏的正文挤出 `trimTranscript` 的尾部窗口,所以压到 300 字并折成单行。同一次蒸馏里同一个 `attachmentId` 只描述一次(按 attachmentId 缓存),重复挂载的图不会产生两行「描述:」。
- **描述失败绝不影响蒸馏**:单块 try/catch 吞异常,失败的图退回占位、带 `描述:` 前缀的行不出现;`finish` 报 `error` / `aborted` 就停手。默认关着时蒸馏输出与 0.14.0-beta.1 逐字节一致。
- **测试**:`test/image-description.test.js` 14 条(准入三态、只有明确不支持才跳过、能力查询抛错、默认关逐字节不变、路由留空整档不跑、描述进转录且 attachmentId 未被改写、同附件只描述一次、失败退化、300 字截断、换行压平、只替换第一处、imageBlocks 过滤)。变异自证三条,其中「把 `sight !== false` 改成 `sight === true`」第一次跑出假绿——因为用例传 `undefined` 正好落进解构默认值,改成 `null` 才真变红。
- **设置面板三处「有渲染、没登记」(#656)**:`FEATURE_FIRST_SCREEN` 缺整个 `group.dream`(巩固与睡眠那一组的 `sleepModeEnabled` 是与 `group.ext` 同级的总闸,却整组躺在二级折叠里,首屏可点项 11 → 13);`affective` / `diary` 两个页签无条件注册,总闸关着时能点进去看一页全关的开关(改成由各自总闸决定,`features` 未到手前先不渲染,拉取失败按全关处理);`conflictLedgerMode` 那个三档下拉是手写的——**渲染里有、常量表里没有,意味着它被误删也不会有任何测试红**,而它确实被误删过(#641)。收进 `FEATURE_ENUMS` + `enumRow` 后由表驱动,并补上 i18n 完整性测试对枚举行标签与每一档取值的扫描(那四组文案此前从没被任何测试碰过)。
- **仓库地志两个子开关各渲染两遍(#658)**:`chorographyAutoRefresh` / `chorographyT1FromPanel` 既在 `FEATURE_CHILDREN` 里(`flagRow` 会展开),又由 `choroSub` 手写了一份——5c41d31 先写手写行,5bad8a6 补父关系时没删旧的。面板上这两个开关各出现两行、点上面那行下面不动,看起来像开关失灵;持久值与后端行为一直是正常的。子项回到 `flagRow` 统一展开,顺带补上父关时的「不生效」标注(值保留仍可预置)。**复现条件是地志总闸开着(默认 false)**,所以只影响开了这个功能的用户。附带改掉一句误导人的注释:`FEATURE_CHILDREN` 是面板渲染关系,`INJECT_CHILD_FLAGS` 只收「父关但子项仍有执行路径」的键,两张表本就不必相等。

## [0.14.0-beta.1] - 2026-11-11

- **冲突裁决改成可查账的账本(#641,beta):矛盾对先记进 `conflict_ledger` 再按档位处置,`enforce` 档下存储层不再由 LLM 决定谁赢**:此前「两条记忆打架」只有一条路——交给模型判一条留、一条归档(#126 之前的自动裁决),判完只剩结果、没有过程:哪一对判过、按什么理由判的、有没有反复判,库里都看不出来。报告者统计 788 对已裁决里近重复 46%、跨作用域 20%、真矛盾 27%、时序演进 5%,而两侧 `created_at` 差不到 1 天的占 76%——「谁更新谁赢」在这个分布上约等于掷硬币,P(毒胜出) 86.8% 正是这么来的。
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-3375%20total-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-3399%20total-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 @@ -763,7 +763,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/ # 3375 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 3399 个 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 @@ -772,7 +772,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 3375 个测试
npm test # 运行 3399 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
3 changes: 3 additions & 0 deletions dsh-mneme/docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@
| `autoSummarize` | `true` | 会话/回合结束自动蒸馏记忆。E10 EdgeMem 考卷(#339)实测:对「未来才问的具体值」类细节,零 LLM 原样写入保真 100% 而蒸馏丢 17/120——蒸馏的归纳收益在别的口径,二者是并存关系而非替代(原样写入并存的产品化见讨论 #361) | 关 = 只剩工具与命令写入口 |
| `summarizeProvider` / `summarizeModel` | 空 | 蒸馏专用模型路由 | 两者都非空才生效;空 = 用会话当前模型 |
| `distillMaxChars` | `24000` | 喂给蒸馏 LLM 的完整转录上限(字符,1000–200000) | 调大更完整、更贵 |
| `imageDescribeEnabled` **light** | `false` | 图像描述档([DISTILL.md](DISTILL.md) 的「描述档」):蒸馏时先用一个能看图的模型给每张图写一句描述,把描述并入转录再蒸馏 | opt-in;每张图多一次视觉调用,进 lightMode 强制关闭清单 |
| `imageDescribeProvider` / `imageDescribeModel` | 空 | 描述档专用路由 | **两者都非空才生效,且留空 = 整档不跑**——与 `summarizeProvider` 的「空 = 跟随会话当前模型」语义刻意不同:蒸馏路由多是纯文本模型,拿它看图只会换回占位。宿主侧按 `inputModalities` 决定发不发图字节,本插件不读图、不存原图 |
| `summarizeMinIntervalMinutes` | `0` | 同会话两次蒸馏的最小间隔(分钟,≤10080) | 0 = 不限(#127 节流三键之一) |
| `summarizeMaxEntriesPerRun` | `0` | 单次蒸馏最多落库条数(≤50) | 0 = 不限 |
| `summarizeMinWindowChars` | `0` | 窗口可蒸馏文本下限(字符),不足直接跳过 LLM(#239) | 被挡窗口照常消费游标并留 skip 审计 |
Expand Down Expand Up @@ -398,6 +400,7 @@
`profileExtractEnabled`(#497) ·
`profileFactsEnabled`(2026-11-06) ·
`preInjectGate.enabled`(#380) · `distillRecords.enabled`(#399) ·
`imageDescribeEnabled`(每张图一次视觉调用) ·
`diaryEnabled`(v0.13,每天一篇 = 一次 LLM 调用,属重型)

预设只是默认值而非强制:用户显式写进 feature_flags 的值在装配顺序上后展开、仍然生效
Expand Down
Loading
Loading