跳过主要内容
Z

almost-for-ever

zwebzzz/almost-for-ever

每轮从你的 Markdown 记忆文件现场派生索引,不维护手写索引表,所以改名、拆库、删库都不会留下指向虚空的旧条目。检索按库标签、章节标签、标题词与正文多维排序,注入命中的行号区间而不是整篇文件。写入由模型提议、你在便利贴上确认,落盘前先探测目标章节的格式再追加。自带结构面板(每个库与章节的行号区间和 token 估算)、空记忆目录的一键起手模板,以及可选的外观调整。

安装

dsh plugin --profile web add github:zwebzzz/almost-for-ever

README

Almost For Ever

Live memory index for DSH —— 记忆的现场索引、精确检索与便利贴写入。

索引不存盘:每次都从你的记忆文件结构现场派生。写入不靠猜:插件收集候选,你点一下才算数。

包名:almost-for-ever        许可证:MIT        版本:0.4.0

它解决什么

加了 AGENTS.md 之后,大多数人会掉进同一个坑:索引表抄在 AGENTS.md 里,而库名和章节名本来就在记忆文件里——同一份信息存两处,改文件时没人记得改第二处。库改名 → 表里留旧名;拆库 → 表里还是一个;删库 → 表里留着指向虚空的入口。

这个插件把索引换成现场派生:每个会话轮次读一遍记忆目录,有什么文件、文件里有什么 ## 标题,索引就是什么。没有第二份真相可以腐烂。


三层成本

层内容成本时机
L1 目录库名 + 标题 + 章节数~750 tok检索时现场算,不常驻
L2 命中命中条目的标题 + 行号110~220 tok随用户消息
L3 正文那一条的全文中位 90 tok模型用 read 精确读

对比「把整张手写索引表常驻 + 命中后整文件读」的做法,典型一次问答从 5,700 + 数千 tok 降到 约 500 tok。


空记忆库怎么办

第一次装上、还没写过任何记忆文件时,插件不会静默装死,而是给一键起手模板:

🗂  还没有找到任何记忆库。
    可以一键生成一套起手模板,也可以自己放 markdown 文件进来。

    [ 三本起手 ]      [ 全部八本 ]      [ 英文三本 ]
    偏好/项目/环境      再加软件/网络/      Preferences/
                      命令/清理/排障      Projects/Environment

    生成后可以直接改文件,也可以删。不想要就整批删掉。

为什么默认是三本:小白第一次打开就看到一堆空表格,认知负担太重。三本够用,需要再加——所以「全部八本」留在旁边。

绝不覆盖:同名文件已存在就跳过,结果回给用户看「哪些建了、哪些没动」。


读:多维交叉检索

不靠单一关键词。任一维度命中都能排序,说错了别的维度兜住:

维度来源权重
① 库主标签文件名 → 标签表(04*=项目、10b*=清理…)+4 最高
② 章节标签文件内 <!-- 标签: ... -->+3
③ 泛标签父类(项目/游戏/工具/学术/网络)+0.5
④ 标题字面中文二元组覆盖率;短查询打 55% 折+8×覆盖率
⑤ 正文兜底只在已有主题分时参与+2×覆盖率
⑥ 状态意图「做到哪了」→ 未结项 ×1.15、已结项 ×0.85乘法微调

命中后再精确定位到某一条,给 文件:起行-止行,而不是整个章节——章节可能 4,900 tok,条目中位只有 90。

实测

指标结果
训练集(18 条真实问法)前 1 命中 17/18 · 前 3 命中 18/18
留出验证(10 条调参时没见过的说法)前 1 命中 9/10

留出验证才是可信数字。训练集 18/18 只说明没被自己写的测试集难住。

三个调出来的坑(都有实测依据)

  1. 状态不能参与主题分 —— 把「进行中」当 +2 分加,study-a 这种带「进行中」的库会压过真正对口的那个。
  2. 库主标签必须压过标题字面 —— 问「插件怎么写」时,主标签是「插件开发」的库输给了另一本标题里恰好含「插件市场」的库。
  3. 短查询的标题匹配不可信 —— 查询只有 1 个词时命中 1 个就是 100%,会把不相干的顶上来。

换个环境也能用吗

能,靠三处「不假设用户跟作者一样」的设计:

作者这边换个环境会怎样插件怎么处理
库名固定 13 个用户的库叫 工作日志 / health标签表查不到时动态生成(文件名 + H1 + 章节名)
每库都有 ## 章节用户随手记成一个文件没有 H2 时按条目切节,每条自成一段
中文查询英文 / 其它语言加英文词干 + 前缀匹配 + 停用词过滤
下拉写死 16 个目标用户的库一个都选不到下拉目标从实际扫描到的库来

两档检索:有高质量人工标签的库走严格档(正文只在已有主题分时参与,防噪音);没标签的库走宽松档(正文可以单独命中,否则彻底查不到东西)。

实测四类「别人」的场景:

场景结果
小白随手记(无章节、无标签)3/3 命中
英文记忆库 + 英文查询3/3 命中
中文但库名完全不同4/4 命中
最糟情况(纯散文无结构)2/2 命中
合计12/12

而作者自己那份 27 个库没有退化:训练集前 3 命中 18/18,留出验证 9/10。

这组数字来自作者的机器。你的库结构不同,命中率也会不同 —— 想让插件在你的库上跑出可信数字,见下面「开发」一节。


写:模型提议,你确认

📌 值得记下来吗
┌──────────────────────────────────────┐
│ 以后别用 agentrouter 当默认模型了       │
│ 偏好                                  │
│ 写到 [AI 工具和渠道 ▾]                 │
│         [记住]  [不要]                │
└──────────────────────────────────────┘

为什么是「模型判断 + 用户确认」而不是「规则猜」

第一版用正则规则猜「哪句话值得记、该写进哪本书」。实测:

规则驱动(v0.2)模型驱动(v0.3)
判断写进哪本书30%(留出验证 1/10)模型读过记忆库,直接给库名
判断逻辑代码量404 行启发式0 行
维护成本加新主题要改词表不用改

那 404 行在做的事——主题词表、项目提示、偏好加权、前缀剥离、裸口令识别——本质都在手工复刻模型已经会做的事。

现在的流程:

每 N 轮 / 你说「记一下」
      ↓
往提示里插一句「回头看看,有值得记的用 memory_propose 提交」
      ↓
模型自己判断 → 调工具 → 写队列
      ↓
便利贴弹出 → 你点「记住」→ 追加进记忆库

触发时机:默认每 7 轮自动,或你说「记一下 / 记住 / 记录一下」立刻触发。 不做「会话结束」兜底 —— 模型判断不了会话结束了没有,用户做完事就走了。

提示词里有一句是必须的:「没有值得记的就不用调用,不要为了交差硬凑」。否则模型会为了让工具看起来有用而乱提案。

落盘时的格式守卫

写进目标章节前,插件会先探测那一节的主流格式:

目标节行为
- 列表补 - 前缀
1. 编号接着最大编号往下数
表格拒绝,回一句「插件不猜列结构」

表格拒绝是有意的:列数、对齐、该进哪一列必须人判断。宁可让你手动写一行,也不要往表格里塞格式不对的东西。

队列存在哪

~/.dsh/almost-for-ever/queue.json —— 不放记忆目录。没确认的东西不配叫记忆。


界面:记忆库结构面板

右下角一枚「🗂 记忆库」小圆片,点开是面板:

  • 每个库的白话名 + 文件名 + 标签
  • 展开看每个章节:行号区间 / 估算 token / 条目数 / 标签
  • 顶部概览:最大的几个库各占多少
  • 搜索框:按库名 / 章节名筛

用它可以一眼看出「哪个库最胖、该拆了」。

外观:可以按自己的眼睛调

两个浮层(📌 待确认、🗂 记忆库)都能改:

能改什么怎么做
位置按住浮层拖动,松手就记住(位置夹在视口内,拖不丢)
形状胶囊 / 圆角 / 方形 / 圆形(圆形只留图标)
颜色跟随主题,或海蓝 / 雾紫 / 苔绿 / 琥珀 / 玫红
大小小 / 中 / 大
复位面板里的「复位到右下角」

入口有两个:右键那个圆片,或点开面板后按 ⚙。

存在浏览器 localStorage(键 almost-for-ever:look)—— 这是这台机器的界面偏好,不是记忆内容,所以不进 memory/、不走接口、不同步到别的机器。清掉它会回到默认外观。

为什么默认是「跟随主题 + 胶囊 + 中」:不抢戏。觉得不显眼就挑一个配色 —— 默认特意做得低调,是因为一个常驻在屏幕上的东西太吵会让人想关掉它。


安装

还没发布到 npm,从 GitHub 装:

git clone https://github.com/zwebzzz/almost-for-ever.git
dsh plugin --profile <profile> add <clone 下来的目录绝对路径>

装完关掉 DSH 窗口再打开(桌面端没有「刷新页面」这个操作)。

装完想确认链路通不通:python tools/probe.py —— 把「状态目录 / 接口 / 队列 / 日志」四个环节逐个点亮。

第一次装上、记忆目录是空的也别慌:右下角的 🗂 会提示「还没有找到任何记忆库」,点一下就能生成起手模板(三本 / 八本 / 英文三本)。


配置

- id: almost-for-ever
  config:
    memoryDir: 'C:\Users\<you>\.dsh\memory'   # 记忆库目录
    maxHits: 3                                 # 读取时最多注入几条
    budgetChars: 700                           # 注入文本的硬预算(字符)
    writeEnabled: true                         # 写入开关
    intervalUnit: turns                        # 触发口径:turns | tokens
    intervalValue: 7                           # 该口径下的阈值
    debug: false                               # 写 ~/.dsh/almost-for-ever/debug.log

两种触发口径

intervalUnitintervalValue 含义适合谁
turns(默认)每 N 个用户轮次一般使用;长会话自然会触发多次
tokens累计输入 token 达到 N想按「花了多少钱」而不是「聊了几轮」来控制
# 例:每 20k 输入 token 审视一次
intervalUnit: tokens
intervalValue: 20000

给记忆库加标签

在章节标题下加一行 HTML 注释,不改渲染、不占正文:

## game-a / 某个卡牌游戏项目
<!-- 标签: 卡牌, 游戏, 回合制, 进行中 -->

为什么写在文件里,不单独建索引表:删掉这个项目 → 标签跟着消失,不留指向虚空的索引;改名 → 结构变,索引自然变;永远不需要「记得同步」。

⚠️ 别写「用户可能怎么叫」的别名 —— 那是在猜,猜错会让模型产生虚假的信心。标签应该来自正文能证明的事实。


诊断

python tools\probe.py                # 状态目录 / HTTP 接口 / 队列 / debug.log
python tools\probe.py --clear        # 清空队列
AFE_BASE=http://127.0.0.1:<端口> ...  # GUI 换了端口时覆盖

插件的失败模式大多是静默的——收集通道没通、异常被 try/catch 吞掉、接口没注册,界面上都看不出来。这个脚本把四个环节逐个点亮。


开发

test/                        ← 谁 clone 下来都能跑(不需要你自己的记忆库)
  run.mjs                    结构漂移验证(改名 / 删库后索引是否自动跟上)
  portability.mjs            **通用性**:4 类别人的环境,12 个查询
  portability-write.mjs      写入下拉是否跟着用户的库走
  portability-diag.mjs       逐条打印「命中了什么、差在哪」
  format-detect.mjs          章节格式探测
  scaffold.mjs               起手模板生成 + 派生
  manifest.mjs               三处模块 id 是否逐字一致
  fake-ctx.mjs               共用的假 ctx

test/local/                  ← 作者的本地校准(不在仓库里)
  eval.mjs                   18 条真实问法(训练集)
  holdout.mjs                10 条留出问法(**这才是可信数字**)
  entry-level.mjs            条目级定位
  e2e-format.mjs             三种格式节的落盘行为
  smoke-model.mjs            全链路冒烟:注册→计数→提示→工具→队列→落盘
  smoke-inject.mjs / smoke-tokens.mjs
node test\portability.mjs     # 换环境能不能用(谁都能跑)
node test\manifest.mjs        # 改了包名 / 模块 id 之后必跑

想在自己的记忆库上测命中率,用你的真实目录:

# PowerShell
$env:DSH_MEMORY_DIR = "$env:USERPROFILE\.dsh\memory"
node test\run.mjs

不设这个变量时,需要真实库的测试会打印「⏭ 跳过」并正常退出(不是失败)。

本地数据文件(不进仓库)

文件内容没有它会怎样
labels.local.json你自己那些库的白话名({"04b-xxx": "某项目"})结构面板 / 下拉里显示成从文件名推出来的默认名

设计取舍

没做为什么
自动写入(不经确认)记忆库一旦被污染很难发现;确认只需 1 秒
语义检索(embedding)需要额外服务与成本;结构 + 标签已到 9/10
从正文自动抽标签噪音大:game-b 会因为正文提了一句验证脚本就被贴上「脚本」
会话结束兜底触发模型判断不了「结束了没有」

许可证

MIT。见 LICENSE。

相关插件