Skip to content
dsh.fish
Agent preset

ds

LMA (Layered Memory Architecture) 是一个为 DeepSeek Harness 设计的地基插件(Foundation Plugin)。它定义了 “记忆体(Memory Body)” 这一抽象概念及其挂载/卸载协议。 它与其他记忆插件的本质区别在于:LMA 是一个容器和载体,让 dsh-memory-evolve、EchoCore 等“能力性插件”可以挂载其上,它本身也内置了一套可开关的基础记忆流程,作为可选能力。LMA结束记忆功能的“一锅乱炖”,实现跨会话的结构化记忆机制。(目前功能还在持续完善以及测试,对于其他插件的接口在构想完整后逐步推动)

Source
szx-a
stars
3 stars
License
MIT
Updated
Updated 44 minutes ago

Readme

# dsh 记忆体(Memory Body)插件

> 社区体验版 · 跨会话记忆:多体 + 挂载 + 自动总结 + FTS5 检索
>
> 这是 GitHub Discussions [#1822(记忆体 Memory Body)](https://github.com/deepseek-ai/deepseek-harness/discussions/1822) 提案的一个可运行实现。

---

## 这是什么

一个 DeepSeek Harness 的记忆插件,实现「命名、跨会话、隔离、可挂载」的记忆单元(Memory Body):

- **多个记忆体(body)**:每个体是一组相关记忆,物理上是一个目录 + JSONL 文件
- **挂载(mount)**:控制当前会话能检索哪些体(数据永久、挂载按会话)
- **双权威**:`user`(用户钦定的文档)vs `model`(模型自动总结的经验)
- **自动总结**:把会话提炼成经验写入记忆体
- **GUI + 后台 JSONL 双编辑**:既能在设置页管体,也能直接改文件
- **FTS5 全文检索**:中英文 3 字以上任意子串命中

核心闭环:/remember → 存储 → memory_search → 检索。

---

## 特性

- 多体 + 挂载:`/mount` `/unmount` 按会话挂载
- 降权不删除:`/forget` 只标记失效(superseded),历史可追溯、可恢复
- 事件溯源:JSONL 权威层 + SQLite FTS5 可重建读模型(索引可丢弃、可重建)
- 中文检索:FTS5 `trigram` 分词器,3 字以上任意子串命中
- 三平面架构:host(存储 + 命令 + Remote)+ preset(工具)+ client(GUI)

---

## 安装

### 前置:版本对齐(重要)

源码已适配 dsh `0.1.2-rc.1`(9 处改动,含挂载标签实时刷新)。npm 发布版目前仍是 `0.1.1-rc.2`,`0.1.2` 版待发布。

### 方式一:手动接入(当前可用的方式)

需要改动 5 个官方文件 + 放入 2 个插件目录:

**1. `packages/bundle/web-app/package.json`** —— `dependencies` 加 2 行:

```json
"@szx-a/dsh-layered-memory-architecture": "workspace:^",
"@szx-a/dsh-layered-memory-architecture-preset": "workspace:^"
```

**2. `packages/bundle/web-app/cordis.patch.yml`** —— 加 2 个 row(host 平面):

```yaml
- id: memory-store
  name: '@szx-a/dsh-layered-memory-architecture/memory-store'
  config:
    root: 'F:/dp/memory-body-data'   # ⚠️ 改成你自己的数据目录路径!
    defaultBodies: [code]            # 默认挂载的体,可改成自己的(如 [physics]),该体需先创建

- id: memory-body
  name: '@szx-a/dsh-layered-memory-architecture'
```

> ⚠️ `root` 是**数据存放目录**,请改成你自己的绝对路径(如 `'D:/ds/memory-data'`),首次启动会自动建目录。
>
> ⚠️ `defaultBodies` 是**默认挂载清单,不是创建命令**:`code` 只是示例名,可改成任意体 id(如 `[physics]`),但该体**必须先在磁盘上创建**(设置页建,或手动建 `body.json`),否则 `/remember` 会报 `does not exist`。

**3. `packages/preset/agent-presets/presets/standard/agent.cordis.yml`**(⚠️ 0.1.2 新路径,旧版在 `apps/cli/config/agent-presets/standard/`)—— 末尾加 1 个 row(preset 平面):

```yaml
- id: memory-body-preset
  name: '@szx-a/dsh-layered-memory-architecture-preset'
  config:
    autoSummarize: false
```

**4. `tsconfig.host.json`** —— `references` 加 2 行:

```json
{ "path": "./packages/memory/memory-body/tsconfig.host.json" },
{ "path": "./packages/memory/memory-body-preset" }
```

**5. `tsconfig.client.json`** —— `references` 加 1 行:

```json
{ "path": "./packages/memory/memory-body/tsconfig.client.json" }
```

**6~7. 放入插件目录**:

```
packages/memory/memory-body/           # host 包:存储 + 命令 + Remote + GUI
packages/memory/memory-body-preset/    # preset 包:工具 + 自动总结
```

**8. 构建**(在 harness 根目录,必须跑 host + client 两个 face):

```bash
pnpm run clean                 # 更新版本后先清旧 lib 产物
pnpm install
pnpm run build:lib:host        # tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host
pnpm run build:lib:client      # tsc -b tsconfig.client.json && tsdown --env.DSH_BUILD_FACE client
```

> 只跑 host face 会让 `dsh web` 报 `MissingClientBundleError`(缺全图 `lib/client.js`)。

**9. 重启**:`Ctrl+C` 停掉 `pnpm dsh web` 再重启(命令在 node 进程启动时注册,只刷新浏览器不会加载)。

**10. 初始化一个体**:默认挂载 `[code]`,但本仓库**不含记忆数据**(数据是私有的,不随源码分发)。重启后先建体:

- 方式 A:设置页 → 「记忆体」tab → 新建体,id 填 `code`(或改成你自己的 id)
- 方式 B:手动在 `root` 目录建 `code/body.json`(内容见下方「后台编辑」)

建完体才能 `/remember` / `memory_search`,否则会报「body does not exist」。

> ⚠️ 本仓库是**源码存档**,不含 `lib/` 构建产物,且依赖 harness monorepo 的 `@deepseek-ai/*` 包 —— 必须放进 harness 源码树内构建,不能独立编译运行。

### 方式二:npm 安装(推荐)

已发布到 npm(`@szx-a/dsh-layered-memory-architecture@0.1.1-rc.2` + `-preset`,基于 dsh `0.1.1-rc.2`),适合**不想放源码、不想自己构建**的情况(npm 包已含编译好的 `lib/` 产物和类型声明)。

> ⚠️ 当前 npm 版是 `0.1.1-rc.2`(dsh rc.2),尚未发布 `0.1.2` 版。若你的 dsh 是 `0.1.2-rc.1`,请用**方式一(手动接入)**,npm 版暂时不兼容 0.1.2。

**1. 安装两个包**(装到 web-app bundle):

```bash
pnpm --filter @deepseek-ai/dsh-web-app add @szx-a/dsh-layered-memory-architecture @szx-a/dsh-layered-memory-architecture-preset
```

**2. 改 `packages/bundle/web-app/cordis.patch.yml`**(同方式一第 2 步):加 `memory-store` + `memory-body` 两个 row。

**3. 改 `packages/preset/agent-presets/presets/standard/agent.cordis.yml`**(⚠️ 0.1.2 新路径,同方式一第 3 步):加 `memory-body-preset` row。

**4. 重启**:`Ctrl+C` 停掉 `pnpm dsh web` 再重启。

**5. 初始化体**(同方式一第 10 步)。

> npm 安装**省掉了**方式一的第 1、4、5、6~7、8 步:不用手动加 web-app 依赖(`pnpm add` 自动写)、不用改 tsconfig、不用放源码、不用构建。

---

## 用法

### 命令

| 命令 | 作用 | 示例 |
|---|---|---|
| `/remember <内容>` | 存一条你钦定的记忆 | `/remember 用 pnpm 构建,别用 npm` |
| `/remember <体id> <内容>` | 存到指定体 | `/remember physics 牛顿三定律` |
| `/summarize` | 把当前对话总结成经验存下 | `/summarize` |
| `/forget <关键词>` | 降权(不删除,检索跳过) | `/forget 测试` |
| `/mount <体id>` | 把体挂到**当前会话** | `/mount physics` |
| `/unmount <体id>` | 从当前会话卸下(数据保留) | `/unmount code` |

### 记忆写到哪里?(默认写入目标)

最容易踩坑的地方,单独说明。

**先分清三个动作**:

- **建体**(设置页建,或手动建 `body.json`)= 创建,只在磁盘生成一个体,**不等于授权**
- **挂载**(`/mount`)= 授权,「这个会话能读写这个体」
- **写入**(`/remember` `/summarize`)= 实际存内容

只有**挂载**的体才能被写。刚建好的体**不会自动挂载**,要先 `/mount <体id>`。所以「在 GUI 里建了体」之后直接 `/remember` 会报 `No memory body mounted` —— 那不是体不存在,是还没挂载。

**挂载是累加,可挂多个**(`/unmount` 只删那一个,不影响其他):

```
/mount wd-231567   → [wd-231567]
/mount code        → [code, wd-231567]        # 后挂的插到最前
/mount physics     → [physics, code, wd-231567]
```

**默认写入「最近挂载的体」**(挂载集第一个):

- 刚启动、没挂过任何体 → 挂载集 = `defaultBodies`(默认 `[code]`),默认写 `code`
- `/mount physics` → 把 `physics` 插到最前,默认写 `physics`

**体必须先存在**:默认目标体(或任何你指定的体)若没创建,`/remember` `/summarize` 会报 `Memory body "xxx" does not exist`,**不会自动创建**。

**显式指定体**(绕过默认,挂多个时才有意义):

- `/remember <体id> <内容>` → 存到指定体
- `/summarize <体id>` → 总结到指定体

> ⚠️ 显式指定的体**也必须已挂载**:`/remember x 内容` 里若 `x` 长得像体 id(全小写字母数字/连字符)却未挂载,会**报错** `Memory body "x" is not mounted`,不会静默当文本。所以先 `/mount x` 再点名。

**所有操作的默认目标一览**(命令 + 模型工具都遵循同一规则):

| 操作 | 权威 | 不指定体时的目标 |
|---|---|---|
| `/remember` | user | 最近挂载的体(挂载集第一个) |
| `memory_remember`(模型自动) | user | 最近挂载的体 |
| `/summarize` | model | 最近挂载的体 |
| 自动总结 | model | 最近挂载的体 |
| `memory_search`(模型自动) | — | 搜**所有**挂载的体 |

**核心规则一句话**:所有写入(命令和模型工具)默认写到「**最近挂载的体**」——因为 `/mount` 会把体插到挂载集最前。想写别的体就显式指定体 id。

**举例**(挂 `physics` 和 `code` 两个):

| 操作 | 挂载集 | `/remember 内容` 存到 | `/remember code 内容` 存到 |
|---|---|---|---|
| (无操作) | `[code]` | `code` | `code` |
| `/mount physics` | `[physics, code]` | `physics` | `code` |
| `/mount physics` 后 `/unmount code` | `[physics]` | `physics` | ❌ 报错 `code` 未挂载 |

### 模型工具(自动调用,无需手动)

- `memory_search <关键词>` —— 跨会话回忆,检索挂载的体
- `memory_remember <内容>` —— 你明确说「记住 xxx」时自动写入
- `memory_forget <关键词>` —— 模型发现记忆过时/错误时主动降权(标记失效、不删除、可追溯)
- `memory_correct <关键词> <新内容>` —— 模型主动纠正:降权旧条目 + 写入纠正后的内容

### GUI

- **设置页 → 「记忆体」tab**:查看体列表、新建体、删除体、查看条目
- **输入框 dock 挂载标签**:聊天输入框附近实时显示当前会话挂载的记忆体(如「记忆体:code, physics」),`/mount` `/unmount` 后自动刷新

### 后台编辑

数据是纯文本,可直接改,改完下次检索自动重建索引:

```
<root>/
  <bodyId>/            # 一个体 = 一个目录
    body.json          # 体元数据(name/description/kind/trust)
    entries.jsonl      # 记忆条目,一行一条,append-only
```

---

## 架构(LMA — Layered Memory Architecture)

2 个包(host + preset)+ 3 个加载平面:

```
┌─ host 平面(cordis.patch.yml)─────────────────────────────┐
│  memory-store   共享存储服务(JSONL + FTS5)               │
│  memory-body    Remote(体管理 GUI)+ /remember 等命令     │
└────────────────────────────────────────────────────────────┘
┌─ agent preset(agent.cordis.yml)──────────────────────────┐
│  memory-body-preset   工具(search/remember/forget/correct)│
│                        + 自动总结                          │
└────────────────────────────────────────────────────────────┘
┌─ client(dsh.client + exports["./client"])────────────────┐
│  设置页「记忆体」tab + 自 mount Remote                     │
└────────────────────────────────────────────────────────────┘
```

### 服务组件

| 组件 | 形态 | 职责 |
|---|---|---|
| `MemoryStore` | Service 类 | 共享存储:JSONL + FTS + 挂载集 |
| `MemoryBodyService` | TypertRemoteService | 体管理 Remote + 命令注册 |
| preset 插件 | namespace plugin | 工具 + 自动总结 |
| client 插件 | 双面 React 插件 | GUI + 自 mount Remote |

### 存储:事件溯源

- **权威层**:JSONL(append-only、可手改、可审计)
- **读模型**:SQLite FTS5(可丢弃、每次 search 前从 JSONL 重建)
- **降权不删除**:supersede 追加标记行,同 id 折叠取最新
- **双权威**:`user`(用户钦定,只读)/ `model`(模型总结,可编辑)

### 作用域与挂载机制

**作用域**(LMA 的设计哲学):

| 维度 | 作用域 | 说明 |
|---|---|---|
| 体(body) | 全局共享 | 一个 root,所有工作区/会话可见同一套体 |
| 挂载(mount) | 按会话 | 每个会话挂载自己的子集,互不影响 |
| 默认挂载集 | 全局 | `defaultBodies` 对所有新会话生效 |

> 隔离靠「体本身」(独立目录 + 独立 JSONL),不靠工作区限制 —— 用户通过「建体 + 挂载」自由管理,而非系统预设边界。

**挂载机制**:

- **建体 ≠ 挂载**:建体只是磁盘生成 body.json,挂载才是「授权会话读写」
- **挂载累加**:`/mount` 插到最前(最近挂载 = 默认写入目标),`/unmount` 只删单个
- **挂载持久化**:会话挂载集存 `root/mounts.json`,`/mount` `/unmount` 立即落盘;会话重启自动恢复,新会话回退默认

### 设计取舍

1. **隔离在存储层,而非检索层** —— 记忆按体分开存放,挂载圈定范围,从源头不乱炖
2. **双权威 vs 混在一起** —— user/model 严格分层
3. **降权不删除 vs 覆盖** —— 可审计可回滚
4. **GUI + JSONL 双编辑 vs 黑盒** —— 透明可手改
5. **事件溯源 vs 单一数据库** —— 索引可重建

---

## 开发历程与踩坑

### 迭代主线

1. 三大基石提案投递 GitHub Discussions(#1822 记忆体 / #1825 插件市场)
2. 拆分 host 包(存储 + Remote + 命令)与 preset 包(工具 + 自动总结)
3. 修 client 插件自 mount Remote(第三方包不在 api-remotes 白名单)
4. 加会话级挂载(`/mount` `/unmount`)
5. FTS5 分词器 unicode61 → trigram(修中文检索)
6. 在 worktree 副本完成 `v0.1.2-alpha.1` 适配并验证完整跑通(9 处改动,构建 0 错误,挂载标签实时刷新已恢复)
7. 官方出 `v0.1.2-rc.1` 后,主体已更新到 rc.1 并跑通(9 处适配跨 alpha.1→alpha.4→rc.1 全程零改动);`0.1.2` npm 版待发布

### 关键踩坑(已固化到 `RECOVERY.md`)

| 坑 | 现象 | 根因 | 解法 |
|---|---|---|---|
| 循环等待 | `pending (waiting for service: remote.memoryBody)` | 把「自己 `$mount` 出来的服务」写进 `inject`,apply 前死等 | `inject` 只写外部依赖,`$mount` 后用 `ctx.get()` 读 |
| 假阳性 | 删 row 后「启动成功」但功能全没 | 删 row = 弃用服务,服务不加载自然无 pending | 用 `SQLite` warning 判断服务是否真加载 |
| `waiting for memoryStore` | preset 工具不激活 | 前置 `MODULE_NOT_FOUND` 把 memory-store row 带崩 | 修模块解析(leaf 包进 fallback 闭包) |
| 中文搜不到 | 搜「用中文」搜不到「用中文交流」 | unicode61 把连续中文粘成一个 token | 换 trigram 分词器 |

---

## 测试与后续开发

### 已知限制

1. **检索最短 3 字符**:trigram 天生不支持 2 字及以下的搜索(`中文` 返回空)
2. **自动总结默认关**:`autoSummarize: false`,需手动开启
3. **安装门槛**:npm 安装仍需手动改 2 个接入文件(cordis.patch.yml / agent.cordis.yml),未接入一键安装通道

### 测试

- **单元测试未补**:`store.ts` / `fts.ts` / `parse.ts` 是纯函数,尚未补测试

### 后续开发(按优先级)

1. **记忆提供者接口**:让第三方记忆插件(dsh-memory-evolve / EchoCore 等)挂 LMA 之上,PLM「地基插件」定位验证点
2. **挂载体元信息注入**:模型只加载已挂载体的 `id + 描述`
3. **量化对比数据**:benchmark(token 节省量、检索命中率)
4. **向量检索**:`BodyKind` 已预留 `vector` 扩展位,混合检索
5. **权重自然衰减**:`weight` 字段支持记忆自然衰减

---

## 版本提示

| 项 | 值 |
|---|---|
| dsh 版本 | `0.1.2-rc.1`(源码已适配并跑通) |
| 插件 version | `0.1.1-rc.2`(npm 发布版,dsh rc.2);源码已适配 `0.1.2-rc.1` |
| npm 包 | `@szx-a/dsh-layered-memory-architecture` + `-preset` |
| peerDependencies | 发布时由 `pnpm publish` 自动替换 `workspace:^` → 对应官方版本(发布 `0.1.2` 版时为 `^0.1.2-rc.1`) |

### 兼容性说明

| 版本 | 支持状态 |
|---|---|
| dsh `v0.1.1-rc.2` | ✅ npm 发布版(`@szx-a/dsh-layered-memory-architecture@0.1.1-rc.2` + `-preset`) |
| dsh `v0.1.2-rc.1` | ✅ 源码已适配并跑通(主体已更新验证),`0.1.2` npm 版待发布 |

**`v0.1.2` 适配结论**:官方从 rc.2 到 0.1.2 经历重大重构。作者已在 worktree 副本(隔离环境)完成适配,并已**将主体更新到 `0.1.2-rc.1` 验证跑通、功能不降级**——9 处改动、host/preset/client 三侧构建 0 错误、`dsh web` 干净启动、挂载标签实时刷新已恢复。适配跨 alpha.1→alpha.4→rc.1 全程零改动。破坏点与实测结论:

| 破坏点 | 影响 LMA 的 | 实测结论 |
|---|---|---|
| agent-presets 目录迁移(`apps/cli/config` → `packages/preset`) | preset 接入点 | memory-body-preset row 需搬到新位置 |
| `@deepseek-ai/dsh-client-runtime` 被拆散 | client 插件依赖 | `ClientContext` → cordis `Context`;`ConversationSnapshot.chat.legacy.nodes` → chat 包的 `ChatSnapshot.legacy.nodes`;store 引擎 → `dsh-client-store`。共 9 处改动,详见下方适配步骤 |
| ApiProxy 移除 | Remote 层 | ✅ `ctx.remote.$mount` / `TypertRemoteNamespaceMap` 未变,**零改动** |

**作者立场**:LMA 已适配 `0.1.2-rc.1` 并在主体跑通。官方在 rc.1 之后已开启 `0.1.3-alpha`,`0.1.2` 进入稳定期。`0.1.2` 的 npm 发布版待定。

### LMA 适配新版本的步骤(供先行者自担风险参考)

> 教程级清单,具体到文件 + 行 + 前后代码。实测 alpha.1→alpha.4 这 9 处改动原样成立、零改动。

**A. 接入点(4 处,路径变化)**

**1. host 接入** — `packages/bundle/web-app/cordis.patch.yml`,在 `plugin-inventory` row 后加:

```yaml
    # LMA 记忆体:共享存储服务 + Remote(体管理 GUI)+ 命令
    - id: memory-store
      name: '@szx-a/dsh-layered-memory-architecture/memory-store'
      config:
        root: 'F:/dp/memory-body-data'   # ⚠️ 改成你自己的数据目录
        defaultBodies: [code]

    - id: memory-body
      name: '@szx-a/dsh-layered-memory-architecture'
```

**2. preset 接入** — `packages/preset/agent-presets/presets/standard/agent.cordis.yml`(⚠️ 0.1.2 新路径,旧版在 apps/cli/config/agent-presets/standard/),末尾加:

```yaml
# LMA 记忆体:模型面向的工具 + 自动总结
- id: memory-body-preset
  name: '@szx-a/dsh-layered-memory-architecture-preset'
  config:
    autoSummarize: false
```

**3. 依赖** — `packages/bundle/web-app/package.json` 的 `dependencies` 加 2 行:

```json
"@szx-a/dsh-layered-memory-architecture": "workspace:^",
"@szx-a/dsh-layered-memory-architecture-preset": "workspace:^"
```

**4. references** — `tsconfig.host.json` 的 `references` 加:

```json
{ "path": "./packages/memory/memory-body/tsconfig.host.json" },
{ "path": "./packages/memory/memory-body-preset" }
```

`tsconfig.client.json` 的 `references` 加:

```json
{ "path": "./packages/memory/memory-body/tsconfig.client.json" }
```

**B. client 源码改动(`@deepseek-ai/dsh-client-runtime` 被拆散导致,5 处)**

**5. `src/client/index.tsx` 第 8 行** — `ClientContext` 换包:

```ts
// 旧
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
// 新
import type { Context as ClientContext } from '@deepseek-ai/cordis'
```

**6. `src/client/MountedBodiesLine.tsx` 第 2-3 行** — import 换成 `ChatSnapshot`:

```ts
// 旧
import type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
// 新
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'   // SnapshotSelectorHook 仍在 ui-slots
import type { ChatSnapshot } from '@deepseek-ai/dsh-client-ui-chat/client'     // 消息节点在 ChatSnapshot.legacy.nodes
```

**7. `package.json`** — 删 3 处 `dsh-client-runtime`,新增 `ui-chat`:

```jsonc
// dsh.client.inject 数组:删 "@deepseek-ai/dsh-client-runtime",加
"@deepseek-ai/dsh-client-ui-chat"

// peerDependencies / devDependencies:删 "@deepseek-ai/dsh-client-runtime": "workspace:^",加
"@deepseek-ai/dsh-client-ui-chat": "workspace:^"
```

> 我们 LMA 没直接用 store 引擎,删掉 runtime 即可,无需加 `@deepseek-ai/dsh-client-store`。

**8. `tsconfig.client.json`** — 删 `client/runtime` reference,加:

```json
{ "path": "../../client/ui-chat" }
```

**9. `src/client/index.tsx`** — 补 2 个空 import + slot 参数类型:

```ts
// 顶部补(提供 client 服务类型声明)
import type {} from '@deepseek-ai/dsh-api-remotes/client'        // ctx.remote 类型
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'  // ctx.slots 类型

// conversation.composer.dock slot 的 inject factory(第 63、65 行)
// 旧
inject: (sessionId: string) => ({
  listMounted: async (): Promise<string[]> => {
    const r = await remote.listMounted(sessionId)
    return r.ok ? r.value : []
  },
}),
// 新(去掉 `: string` 注解,用 String() 转换 branded 类型 SessionIdOf)
inject: (sessionId) => ({
  listMounted: async (): Promise<string[]> => {
    const r = await remote.listMounted(String(sessionId))
    return r.ok ? r.value : []
  },
}),
```

> 实时刷新信号也已恢复:`MountedBodiesLine` 里 `useSession(s => s.chat.legacy.nodes.length)` → `useChat(s => s.legacy.nodes.length)`(照官方 `StatsLine` 先例,`useChat` 由 ui-chat merge 进 `SessionStandardProps`,session 作用域 slot 组件自动获得)。

**C. 构建 + 验证(⚠️ 关键教训)**

```bash
pnpm run clean                 # 更新版本后必先清旧 lib 产物,否则假 MISSING_EXPORT
pnpm install                   # 链接新依赖
pnpm run build:lib:host        # = tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host
pnpm run build:lib:client      # = tsc -b tsconfig.client.json && tsdown --env.DSH_BUILD_FACE client
pnpm dsh web --no-open --port 0
```

- **必须跑 host + client 两个 face**:只跑 host face 会让 `dsh web` 报 `MissingClientBundleError`(缺全图 `lib/client.js`)。
- **通过标准**:打印 URL + `ExperimentalWarning: SQLite`,无 `pending`、无 `typert manifest`、无 `MissingClientBundleError`;HTTP 探测返回 401 即确认监听。
- **编译错误先分真假**:`Cannot find module .../remote`、`MISSING_EXPORT` 类报错,多数是「依赖产物没构建 / 旧 lib 残留」,先 clean + 全量构建,剩下的才是真 API 变化,**别急着改好代码**。

---

## 目录结构

### LMA 源码(两个版本相同,官方从未改动)

```
packages/memory/
├── memory-body/                 # host 包 @szx-a/dsh-layered-memory-architecture
│   └── src/
│       ├── index.ts             # MemoryBodyService(Remote + 命令注册)
│       ├── memory-store.ts      # MemoryStore(共享存储服务)
│       ├── store.ts             # JSONL 权威存储(纯函数)
│       ├── fts.ts               # FTS5 检索(trigram)
│       ├── command.ts           # /remember /summarize /forget /mount /unmount
│       ├── summarize.ts         # 总结逻辑
│       ├── parse.ts             # 输入解析
│       └── client/              # GUI(记忆体 tab)
└── memory-body-preset/          # preset 包(-preset 后缀)
    └── src/
        ├── index.ts             # 工具 + 自动总结装配
        ├── tool.ts              # memory_search / remember / forget / correct
        └── auto-summarize.ts    # 自动总结触发
```

> LMA 自己的源码目录在 0.1.1-rc.2 和 0.1.2 里**完全一致**——官方重构从没碰过 `packages/memory/`(实测 alpha.1→alpha.4 共 648 个提交,对 `packages/memory/` 零改动)。变的是下面的**接入点**。

### 接入点目录(dsh 版本不同,路径不同)

| 接入点 | 0.1.1-rc.2(旧) | 0.1.2(新) |
|---|---|---|
| host 行(memory-store / memory-body) | `packages/bundle/web-app/cordis.patch.yml` | `packages/bundle/web-app/cordis.patch.yml`(不变) |
| preset 行(memory-body-preset) | `apps/cli/config/agent-presets/standard/agent.cordis.yml` | **`packages/preset/agent-presets/presets/standard/agent.cordis.yml`**(⚠️ 迁移) |
| web-app 依赖 | `packages/bundle/web-app/package.json` | `packages/bundle/web-app/package.json`(不变) |
| tsconfig references | `tsconfig.host.json` / `tsconfig.client.json` | `tsconfig.host.json` / `tsconfig.client.json`(不变) |

> 0.1.2 里 `agent-presets` 目录从 `apps/cli/config/` 整体迁移到了 `packages/preset/`,所以 preset 接入点路径变了;其余三个接入点路径不变。完整 9 处适配见上方「LMA 适配新版本的步骤」。

---

## 许可证

见 [LICENSE](./LICENSE)。

Install

# Copy the composition to $DSH_HOME/.agent-presets/ds/agent.cordis.yml

Profile: web

Source