Skip to content
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
<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-1535%20passed-3E63DD?style=flat-square" alt="tests"></a>
<<<<<<< HEAD
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1537%20passed-3E63DD?style=flat-square" alt="tests"></a>
=======
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1537%20passed-3E63DD?style=flat-square" alt="tests"></a>
>>>>>>> feat/serve-daemon
<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 @@ -151,7 +155,7 @@ dsh web

## 不开 DSH 也能服务(独立服务 daemon)

`dsh-mneme-serve` 把记忆库跑成常驻数据面——DSH 关着,第三方集成(网页端桥接、脚本、自有面板)照样读写同一份记忆:token 与 DSH 面板/CLI 共用,路由与「外部访问 API」同源,端口被占直接报错(与 DSH 外部访问二选一)。第一期无 LLM(巩固/蒸馏仍属 DSH 宿主),检索为关键词 + BM25,向量接入在后续版本。
`dsh-mneme-serve` 把记忆库跑成常驻数据面——DSH 关着,第三方集成(网页端桥接、脚本、自有面板)照样读写同一份记忆:token 与 DSH 面板/CLI 共用,路由与「外部访问 API」同源,端口被占直接报错(与 DSH 外部访问二选一)。第一期无 LLM(巩固/蒸馏仍属 DSH 宿主);检索默认 local 嵌入(首次启动自动取件模型,`--embed off` 可关)。

```bash
npm i -g @modusensus/dsh-mneme
Expand Down Expand Up @@ -193,7 +197,11 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790

```bash
cd dsh-mneme && npm install
npm test # 1535 个测试
<<<<<<< HEAD
npm test # 1537 个测试
=======
npm test # 1537 个测试
>>>>>>> feat/serve-daemon
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -344,7 +352,7 @@ The plugin ships a zero-dependency stdio MCP server (standalone npm package **`m

## Serve memories without DSH (standalone daemon)

`dsh-mneme-serve` runs the memory store as a long-lived data plane — with DSH closed, third-party integrations (web-bridge tools, scripts, your own panels) still read and write the same memories: the Bearer token is shared with the DSH panel/CLI, routes mirror the external API, and a busy port is a hard error (pick either the daemon or DSH's external API, not both). Phase 1 is LLM-free (consolidation/distillation stay with the DSH host); retrieval is keyword + BM25, with vector search arriving in a later release.
`dsh-mneme-serve` runs the memory store as a long-lived data plane — with DSH closed, third-party integrations (web-bridge tools, scripts, your own panels) still read and write the same memories: the Bearer token is shared with the DSH panel/CLI, routes mirror the external API, and a busy port is a hard error (pick either the daemon or DSH's external API, not both). Phase 1 is LLM-free (consolidation/distillation stay with the DSH host); retrieval defaults to local embeddings (runtime + model auto-provisioned on first boot, `--embed off` to disable).

```bash
npm i -g @modusensus/dsh-mneme
Expand Down Expand Up @@ -386,7 +394,11 @@ dsh-mneme-serve # defaults: ~/.dsh/memory + 127.0.0.1:8790

```bash
cd dsh-mneme && npm install
npm test # 1535 tests
<<<<<<< HEAD
npm test # 1537 tests
=======
npm test # 1537 tests
>>>>>>> feat/serve-daemon
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
4 changes: 3 additions & 1 deletion dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

## 🆕 新增

- **独立服务 daemon(`dsh-mneme-serve`,#363)**:mneme 现在能在 DSH 宿主之外常驻——`src/serve.js` 的 `createServeRuntime` 用最小装配(store → settings → mirror → service → maintenance → standalone API,每步锚定 index.js 装配行号)把数据面跑成独立进程,第三方集成(网页端桥接等)不必为挂载记忆库而保持 DSH 开机。第一期刻意无 LLM:巩固(autoDream)与蒸馏结构性不在 daemon 内,这是与宿主「单写者」的机械保证,不靠用户自觉;检索为关键词 + BM25(向量由后续 PR 抽取 semantic 装配后接入)。token 与 DSH 面板/CLI 共用同一 kv 凭证,端口/主机解析链与外部访问一致;`createStandaloneApi` 新增 `strictPort` 选项——daemon 的配置端口被占即报错退出而非顺延(第三方把 URL 写死,静默换端口等于坏),不传该选项的宿主旁路行为不变。`/search` 照常落 recall_runs,第三方检索的复用统计不缺数。多进程共存(daemon 与宿主同库互写互读)有专门回归锁;已知限制(双进程去重竞态、镜像双写、版本偏斜)见 docs/DAEMON.md。
- **daemon 向量检索(PR2,#363)**:`dsh-mneme-serve` 的 `/search` 接入完整语义管线——embedder/reranker 装配与 boot 自动回填从 `index.js` **纯搬移**至 `src/semantic.js`(宿主与 daemon 共用同一份,调用时序契约原样;`backfillMissingEmbeddings` 经 index.js barrel 再出口,测试调用方零改动),daemon 侧新增 `createVectorIndex` 接线与 `--embed` 参数:`local`(默认,自管 runtime/嵌入模型缺失时经 `provisionRuntime` download 档自动取件,可用 `DSH_MNEME_RUNTIME_TARBALL_DIR`/`DSH_MNEME_RUNTIME_MIRROR` 换离线/镜像来源;失败降级关键词并打可操作日志)、`ollama`、`openai`(读宿主面板 vector-config)、`off`。向量轴有注入假 embedder 的回归锁;`createServeRuntime` 因此转为 async、语义键默认值在 `daemonSemanticCfg` 逐键锚定 config.js。
- **独立服务 daemon(`dsh-mneme-serve`,#363)**:mneme 现在能在 DSH 宿主之外常驻——`src/serve.js` 的 `createServeRuntime` 用最小装配(store → settings → mirror → service → maintenance → standalone API,每步锚定 index.js 装配行号)把数据面跑成独立进程,第三方集成(网页端桥接等)不必为挂载记忆库而保持 DSH 开机。第一期刻意无 LLM:巩固(autoDream)与蒸馏结构性不在 daemon 内,这是与宿主「单写者」的机械保证,不靠用户自觉。token 与 DSH 面板/CLI 共用同一 kv 凭证,端口/主机解析链与外部访问一致;`createStandaloneApi` 新增 `strictPort` 选项——daemon 的配置端口被占即报错退出而非顺延(第三方把 URL 写死,静默换端口等于坏),不传该选项的宿主旁路行为不变。`/search` 照常落 recall_runs,第三方检索的复用统计不缺数。多进程共存(daemon 与宿主同库互写互读)有专门回归锁;已知限制(双进程去重竞态、镜像双写、版本偏斜)见 docs/DAEMON.md。

## 🧹 工程

- **发布准备脚本在 CRLF 检出上不再假成功(`scripts/release-prep.mjs`)**:该脚本用 `/^(# Changelog\n\n)/` 匹配 CHANGELOG 文件头,而 Windows 检出是 CRLF——正则命中不了,`replace` 退化成空操作,**脚本却照样打印 `✓ … 占位节`**,`git status` 里看不出任何异常(CI 跑在 ubuntu 是 LF,所以只有本机发版会中招,v0.8.13 那次即如此、最后靠人工补的占位节)。规则抽成 `dsh-mneme/scripts/changelog-prep.mjs` 的纯函数:行尾两种都吃、插入内容跟随原文件行尾、带 BOM 也认;匹配不上则如实回报 `header-not-found`,入口**报错退出(exit 1)**而不是假打印成功。配 6 条回归测试(LF / CRLF / BOM / 幂等 / 回报契约 / detectEol)。
Expand Down
9 changes: 5 additions & 4 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-1535%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-1537%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 @@ -522,11 +522,12 @@ dsh-mneme config show # 查看当前配置(toke
```bash
dsh-mneme-serve # 默认 ~/.dsh/memory + 8790
dsh-mneme-serve --memory-dir "D:\my mem" --port 8790 --host 127.0.0.1
dsh-mneme-serve --embed off # 纯关键词 + BM25(不取件模型)
```

- **鉴权与端口**:Bearer token 与 DSH 面板 / CLI 共用同一份(kv `external_api`,首次启动自动生成并持久化到 `memory.db`);端口/主机解析链与「外部访问」一致(显式参数 > 持久值 > 默认 8790/127.0.0.1)。配置端口被占会**直接报错退出**(不做端口顺延)——第三方把 URL 写死,静默换端口等于坏。因此 **daemon 与 DSH 的「外部访问」二选一**,不要同端口同开。
- **安全**:daemon 使用明文 HTTP,不提供原生 TLS。指定非回环 `--host` 时,请勿直接把服务暴露给不可信网络;远程访问请走 TLS 终止代理或 SSH 隧道。
- **能力边界(第一期,无 LLM)**:存储 / 检索(关键词 + BM25)/ 镜像同步与人改合并 / `POST /maintenance/reclaim` / `/bootstrap` 全可用;巩固(autoDream)与蒸馏不在 daemon 内——巩固只属于 DSH 宿主进程,这是与宿主「单写者」的机械保证。向量检索暂缺(后续版本接入),`/search` 退化为关键词 + BM25 属预期。
- **能力边界(第一期,无 LLM)**:存储 / 检索(关键词 + BM25 + 向量)/ 镜像同步与人改合并 / `POST /maintenance/reclaim` / `/bootstrap` 全可用;巩固(autoDream)与蒸馏不在 daemon 内——巩固只属于 DSH 宿主进程,这是与宿主「单写者」的机械保证。`--embed` 默认 `local`(自管 runtime 与嵌入模型缺失时自动取件,约 200MB;失败降级关键词并打日志),也可选 `ollama` / `openai`(读宿主面板的 vector-config)/ `off`。
- **检索回执**:daemon 的 `/search` 同样落 `recall_runs`,第三方检索的复用统计不缺数。
- **生命周期**:stdout 仅就绪时打一行 `dsh-mneme-serve listening on http://host:port (pid N)`(供脚本解析实际端口),日志走 stderr;SIGINT/SIGTERM 优雅收库,Windows 强杀由 WAL 回放兜底。
- **已知限制**:与 DSH 同时运行属设计内场景(WAL 多进程并发),但去重是先查后写、库层无 UNIQUE 约束,双进程并发写同一 `(type, title, scope)` 有极小概率产生重复;daemon 与插件请同版本升级。细节与坑清单见 [docs/DAEMON.md](docs/DAEMON.md)。
Expand Down Expand Up @@ -600,7 +601,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 1535 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 1537 个 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 @@ -609,7 +610,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 1535 个测试
npm test # 运行 1537 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
29 changes: 26 additions & 3 deletions dsh-mneme/bin/dsh-mneme-serve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,27 @@ const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));

const USAGE = `${BIN_NAME} — run the mneme data plane as a standalone service (no DSH required)

Usage: dsh-mneme-serve [--memory-dir <dir>] [--port <n>] [--host <addr>]
Usage: dsh-mneme-serve [--memory-dir <dir>] [--port <n>] [--host <addr>] [--embed <provider>]

Options:
--memory-dir <dir> data directory (default: ~/.dsh/memory, same as the plugin)
--port <n> HTTP port (default: persisted external_api port, else 8790)
--host <addr> bind address (default: persisted external_api host, else 127.0.0.1)
--embed <provider> semantic retrieval: local (default, downloads the ONNX runtime
+ embedding model on first boot) | ollama | openai (uses the
vector-config saved by the DSH panel) | off (keyword + BM25 only)
-h, --help show this help
-V, --version print version

Auth: Bearer token is shared with the DSH panel / CLI (kv "external_api" in
memory.db); it is generated on first boot. A busy configured port is a hard
error — the DSH external API and this daemon must not share a port (pick one).`;
error — the DSH external API and this daemon must not share a port (pick one).

Environment (runtime provisioning, local provider only):
DSH_MNEME_MEMORY_DIR data directory override
DSH_MNEME_RUNTIME_DIR self-managed runtime dir (default ~/.dsh/mneme/runtime)
DSH_MNEME_RUNTIME_TARBALL_DIR offline .tgz dir preferred over the network
DSH_MNEME_RUNTIME_MIRROR npm registry mirror prefix (e.g. npmmirror)`;

/** 极简 argv 解析(--k=v / --k v / 旗标);够用即可,完整 CLI 在 bin/cli.mjs。 */
function parseArgv(argv) {
Expand Down Expand Up @@ -93,7 +102,21 @@ async function main(argv) {
}
const host = typeof args.host === "string" && args.host ? args.host : undefined;

const rt = createServeRuntime({ memoryDir, port, host, logger });
let embed = "local";
if (args.embed !== undefined) {
if (typeof args.embed !== "string" || !["off", "local", "ollama", "openai"].includes(args.embed)) {
fail(`--embed 需要 off|local|ollama|openai,收到: ${String(args.embed)}`);
}
embed = args.embed;
}

let rt;
try {
// 装配是异步的:embed=local 时可能要先取件 runtime(download 档,失败内部降级)
rt = await createServeRuntime({ memoryDir, port, host, logger, embed });
} catch (err) {
fail(`启动失败: ${err?.message ?? err}`);
}
try {
await rt.api.ready;
} catch (err) {
Expand Down
Loading
Loading