Add memory plugin: three-tier markdown store with plugin-driven retrieval
A durable memory layer for OpenCode. Structure:
ROOT.md total plan: goals, hard rules, directory registry,
and a scenario -> file routing table
<category>/
index.md one line per file, COMPILED from frontmatter
<slug>.md the memory itself
Two roots, same shape: <config>/memory/ (global) and
<project canonical>/.memory/ (project). Project wins on conflict; global
hard rules stay resident everywhere.
Why a plugin rather than a skill that tells the model to read a folder:
model-driven retrieval is unreliable — the model forgets, reads half the
files, or reads the wrong ones. Markdown is the human-facing format; the
plugin is the engine that parses, scores, and injects.
Measured, not assumed: FTS5's two built-in tokenizers both fail on this
corpus. On SQLite 3.53.4 with "部署脚本用 pnpm 而不是 npm...": unicode61
emits 7 terms for the whole sentence and matches 0 hits for "部署";
trigram needs >= 3 characters and also returns 0 for that two-character
word. So tokenization happens in the plugin (CJK bigrams, latin words)
and no SQL index is used — a thousand memories are fine to scan.
index.md and ROOT's two tables are compiled, never hand-edited: a
hand-maintained index drifts, a generated one cannot. Hit counters live
in .state.json so cross-device sync does not conflict on every recall.
Tools are named recall/remember/forget/inspect rather than
write/read/status: generic names were observed shadowing the harness's
own built-in tools.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# 记忆总纲 — 全局
|
||||
|
||||
> 跨项目通用记忆:你的偏好、技术决策、通用约定、踩过的坑。
|
||||
> 由 memory 插件维护。`auto:begin` / `auto:end` 之间的表格是编译产物,不要手改;
|
||||
> 其余区域随便写。
|
||||
|
||||
## 当前目标
|
||||
|
||||
<!-- 手写:现在在做什么。插件不会碰这里。 -->
|
||||
|
||||
## 铁规则(常驻,永不衰减)
|
||||
|
||||
<!-- 手写:只写硬性要求。rules/ 目录下的文件会被自动常驻注入。 -->
|
||||
|
||||
## 目录登记
|
||||
|
||||
<!-- auto:begin registry
|
||||
|
||||
| 目录 | 概述 | 条目 |
|
||||
|---|---|---|
|
||||
| decisions/ | 选 Markdown 因为人类可纠正性和可同步性,检索由插件做而非模型。 | 1 |
|
||||
| pitfalls/ | unicode61 把整段中文当一个 token,trigram 漏掉两字词,必 | 1 |
|
||||
| rules/ | 不可违反的硬规则,常驻 | 1 (1 钉住) |
|
||||
<!-- auto:end
|
||||
|
||||
## 加载路由表
|
||||
|
||||
<!-- auto:begin routing
|
||||
|
||||
| 场景 / 触发 | 读哪个文件 | 类型 |
|
||||
|---|---|---|
|
||||
| FTS5 | pitfalls/sqlite-cjk-tokenizer.md | fact |
|
||||
| memory | decisions/memory-store-format.md | fact |
|
||||
| SQLite | pitfalls/sqlite-cjk-tokenizer.md | fact |
|
||||
| 中文检索 | pitfalls/sqlite-cjk-tokenizer.md | fact |
|
||||
| 为什么用 markdown | decisions/memory-store-format.md | fact |
|
||||
| 全文检索 | pitfalls/sqlite-cjk-tokenizer.md | fact |
|
||||
| 分词 | pitfalls/sqlite-cjk-tokenizer.md | fact |
|
||||
| 存储选型 | decisions/memory-store-format.md | fact |
|
||||
| 记忆 | decisions/memory-store-format.md | fact |
|
||||
<!-- auto:end
|
||||
@@ -0,0 +1,8 @@
|
||||
# decisions 索引
|
||||
|
||||
<!-- auto:begin decisions
|
||||
|
||||
| 文件 | 概述 | 重点 |
|
||||
|---|---|---|
|
||||
| memory-store-format.md | 选 Markdown 因为人类可纠正性和可同步性,检索由插件做而非模型。 | 纯文本可手改、可 git diff;SQLite 的相关性排序解决的是规模问题,而记忆只有千级 |
|
||||
<!-- auto:end
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: decisions/memory-store-format.md
|
||||
kind: fact
|
||||
summary: 选 Markdown 因为人类可纠正性和可同步性,检索由插件做而非模型。
|
||||
highlight: 纯文本可手改、可 git diff;SQLite 的相关性排序解决的是规模问题,而记忆只有千级
|
||||
created: 1791512156858
|
||||
updated: 1791512156858
|
||||
load_when:
|
||||
- 记忆
|
||||
- memory
|
||||
- 存储选型
|
||||
- 为什么用 markdown
|
||||
---
|
||||
|
||||
评估过 SQLite + FTS5,最终选 Markdown + 插件检索。
|
||||
|
||||
**为什么放弃 SQLite**:
|
||||
- 模型自动抽取一定会抽错。没有人类可读可编辑的存储,就没有纠正入口。**记忆错了比没有记忆更糟**。
|
||||
- FTS5 的中文分词两种内置方案都不可用(见 pitfalls/sqlite-cjk-tokenizer 那条实测)。
|
||||
- 相关性排序是**规模问题**,而记忆是千级条目,解决的是不存在的问题。
|
||||
|
||||
**Markdown 的代价及对策**:
|
||||
- 检索不可靠 → 由插件解析、评分、注入,不依赖模型自觉
|
||||
- 写入抖动(hit_count 每次检索都改文件)→ 统计拆到 .state.json,git 忽略
|
||||
|
||||
**已知边界**:万条量级时全表扫描会变慢,那时再换索引(正文是纯文本,迁移成本低)。
|
||||
@@ -0,0 +1,8 @@
|
||||
# pitfalls 索引
|
||||
|
||||
<!-- auto:begin pitfalls
|
||||
|
||||
| 文件 | 概述 | 重点 |
|
||||
|---|---|---|
|
||||
| sqlite-cjk-tokenizer.md | unicode61 把整段中文当一个 token,trigram 漏掉两字词,必须自己分词。 | 实测:unicode61 查"部署"=0 命中;trigram 查"部署"=0 命中(需≥3字符) |
|
||||
<!-- auto:end
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: pitfalls/sqlite-cjk-tokenizer.md
|
||||
kind: fact
|
||||
summary: unicode61 把整段中文当一个 token,trigram 漏掉两字词,必须自己分词。
|
||||
highlight: 实测:unicode61 查"部署"=0 命中;trigram 查"部署"=0 命中(需≥3字符)
|
||||
created: 1791512161994
|
||||
updated: 1791512161994
|
||||
load_when:
|
||||
- SQLite
|
||||
- FTS5
|
||||
- 全文检索
|
||||
- 分词
|
||||
- 中文检索
|
||||
---
|
||||
|
||||
在 SQLite 3.53.4 上实测,字符串「部署脚本用 pnpm 而不是 npm,因为 lockfile 不兼容」:
|
||||
|
||||
- `unicode61`(FTS5 默认)把 `部署脚本用` 当作**一个 token**,整句只产生 7 个词条。查「部署」0 命中,查「部署脚本」也 0 命中。子串永远匹配不上。
|
||||
- `trigram` 要求查询 ≥3 字符。词表是「不兼」「而不」「m,因」这种三字滑窗。查「部署」0 命中,查「部署脚」1 命中。
|
||||
|
||||
中文里「部署」「配置」「依赖」「接口」这类**两字词占比极高**,trigram 漏掉的正好是最常用的一批。
|
||||
|
||||
解法:索引前自己切 CJK 字符二元组,拉丁走 porter 词干,FTS5 只当倒排索引用。
|
||||
@@ -0,0 +1,8 @@
|
||||
# rules 索引
|
||||
|
||||
<!-- auto:begin rules
|
||||
|
||||
| 文件 | 概述 | 重点 |
|
||||
|---|---|---|
|
||||
| plugin-push-on-change.md | 改动这两个目录后立即 commit 并 push,不询问。 | ~/.config/opencode 本身就是 git 仓库,.gitignore 只放行 skills/ 和 plugins/ |
|
||||
<!-- auto:end
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: rules/plugin-push-on-change.md
|
||||
kind: rule
|
||||
summary: 改动这两个目录后立即 commit 并 push,不询问。
|
||||
highlight: ~/.config/opencode 本身就是 git 仓库,.gitignore 只放行 skills/ 和 plugins/
|
||||
created: 1791512032262
|
||||
updated: 1791512246723
|
||||
pinned: true
|
||||
---
|
||||
|
||||
`~/.config/opencode` 自身是 git 仓库,`.gitignore` 只放行 `skills/` 和 `plugins/`(外加 .gitignore、pwsh.cmd、package.json)。
|
||||
|
||||
在这两个目录下创建、修改、删除任何文件后,**提交并推送后再回复用户**,不要问是否要 push。
|
||||
|
||||
推送用 SSH(origin 已是 SSH 地址)。HTTPS 推送会提示密码且无 TTY,必然失败。
|
||||
|
||||
推送失败就报错并停下,不要静默重试,也不要 `git reset`。提交信息要写清楚改了什么、为什么改。
|
||||
Reference in New Issue
Block a user