DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

lrqiisrom /

lrqiisrom/dsh-jev-memory

Verified

This plugin has no description yet.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@28da78e2

dsh-jev-memory

类型化、可审计的 DSH 长期记忆插件。 一句话定位:

AGENTS.md 是人手写的记忆;这个插件是自己长出来、按需召回、逐条可审计的记忆。

DSH 官方有会话持久化(JSONL)、会话检索(SQLite FTS)、compaction(压缩丢弃),但跨会话的长期记忆是空白:没有任何官方包回答"什么值得记住"和"这一轮该想起什么"。本插件补的就是这一层判断。


它做什么(当前已实现并实测)

环节 实现 位置
写入 挂在 agent/turn-stopping(serial + awaited,回合收尾前)抽取候选 → 判定 → 过确定性闸门 → 落盘 dsh/index.ts
判定 端口式:Jev(TypeSafe 判定模型)为首选,确定性信号为离线兜底。判定层只出标签与概率,不生成文本 dsh/lib/judge.ts、dsh/lib/jev.ts
存储 本地 memory.json + 只追加的 ledger.jsonl,原子写、内存索引(注入回调必须同步) dsh/lib/store.ts
召回 systemPrompt.context 每步注入当前工作区的 Top-K,带类型/id/日期标签 dsh/lib/recall.ts
工具 memory_search / memory_write / memory_forget,模型可主动查、主动记、主动撤 dsh/index.ts

三条纪律写进了代码,不是写在文档里:

  1. 模型不写——记忆正文只能是用户原话(逐字裁剪)或工具失败的确定性签名,判定层没有可以塞进新句子的字段;
  2. 概率只排序,阈值归代码——写入阈值是 minImportance,冲突判断用插件自己的 conflictThreshold,从不把 Noul 上调好的阈值搬到 Choice 上(官方 jaggedness 文档:P(q)+P(¬q) 未必等于 1);
  3. fail-open——判定失败降级到启发式,钩子超时直接放弃,写入路径的任何异常都被 catch,绝不让记忆成为会话失败的原因。

快速开始

方式一:开发期直接挂工作区(无需安装)

在 profile 的用户 patch 层 ~/.dsh/profiles/web/cordis.patch.yml 里加一行:

- insert:
    - id: jev-memory
      name: 'file:///absolute/path/to/dsh-jev-memory/dsh/index.ts'

行名由加载器用 new URL(name, baseUrl) 解析,所以 file: URL 与裸包名都可用。删掉这一行即完全卸载。

方式二:作为一个包安装进 profile

dsh plugin --profile web add /absolute/path/to/dsh-jev-memory
# 再把包名加进 profile package.json 的 dsh.profile.bundles

包根自带 cordis.patch.yml,dsh.bundle.patch 指向它。

配置

- insert:
    - id: jev-memory
      name: 'file:///absolute/path/to/dsh-jev-memory/dsh/index.ts'
      config:
        types: [constraint, pitfall, decision]   # 只记这三类
        judge: auto                              # auto | jev | heuristic | off
        minImportance: 0.6                       # 确定性写入阈值
        minRemember: 0.6                         # Jev 的"值得记吗"阈值
        askOnConflict: true                      # 发现矛盾时问你一句(HITL)
        askOnConflictTimeoutMs: 600000           # 等你回答的预算(10 分钟)
        askOnConflictMaxAttempts: 3              # 超时后在下一次对话开始前重问,最多几次
        repeatFailuresToWrite: 2                 # 同一个错重复几次才算"坑"
        writeSkipSubagents: true                 # 见下方"实测发现"
        writeTimeoutMs: 2500                     # 回合收尾的写入预算,超时放弃
        recall:
          maxTokens: 600
          quota: { constraint: 4, pitfall: 3, decision: 2 }
        jev:
          apiKeyEnv: TYPESAFE_API_KEY   # 从下面三处按序解析,密钥不写在配置里
          model: jev-latest             # 台账会记录实际应答的版本号,便于发现别名漂移

密钥放哪:按序解析 ① 宿主 ctx.credentials 服务的 apiKeyEnv 引用(存 ~/.dsh/.credentials.yaml,权限 0600)→ ② 直接读同一个凭据文档(服务在开机那一瞬可能还没就绪,这条兜底让"时机"不再决定判定方式)→ ③ 配置里的 apiKey / 进程环境变量。解析发生在每次判定时而不是挂载时,所以运行中新增或轮换 key,下一轮就生效、不必重启。台账的 start 行写明实际用了哪条路径(service:file / file / config / env / none)。没有 key 时 judge: auto 自动退回启发式,插件无需网络即可工作。


记忆互相矛盾时:问你一句(人机协作)

"这条值不值得记"是一回事,"它是不是和你之前说的冲突"是另一回事——后者机器不该自己拍板:

  1. Jev 若判存在冲突(概率 ≥ 0.7)且这条本身值得记,先以"待确认"状态落盘(不注入、也不丢);
  2. 回合结束时问你一句——一条问题、三个选项:用新的覆盖旧的 / 保留旧的那条 / 两条都留着;
  3. 你的选择决定结果:覆盖 → 旧的标成"已被取代"(留档,不再注入);保留旧 → 新的丢弃;都留 → 两条都生效;
  4. 没人应答 / 超时(默认 10 分钟)/ 出错 → 保持"待确认":宁可少记一条,也不猜你的意思。超时不是终点:记录留在"待确认",在你下一次发消息、模型开始干活之前再问一次(那时的你一定在场),最多重问 3 次——这就是"超时敢设长"之所以安全的原因。

为什么这和 DSH 原有的 HITL 不冲突:DSH 有两种跟人打交道的通道——审批(沙箱/权限策略发起,"这个危险操作放不放行")和提问(模型发起,"我缺一个信息")。这里复用第二种,只是换成插件的判定规则来发起:不新增 UI、不新增权限模型、不新增打断渠道,问题会出现在你熟悉的那个提问卡片里。唯一的差别是"谁决定要问"。

台账会记 conflict-ask(问了什么、和哪条冲突、配对依据的共享词)与 conflict-resolved(你选了什么)——这就是"自动判断靠不靠谱"的实测数据。

一次性失败不记:工具报错要同签名重复出现(默认 2 次)才算"坑"。一次性的环境失败(沙箱拒绝、重定向被拦)不是教训;重复发生的才是。次数从台账里数,所以重启不丢。


实测发现:写入精确率 1/4(已修)

第一次在真实 GUI 里跑,台账 ledger.jsonl 立刻暴露了问题:

{"kind":"write","type":"constraint","by":"heuristic","importance":0.96,
 "source":{"sessionId":"5883ea6a-…","seq":8,
 "quote":"调用 `memory_write`,参数:{\"text\": \"语言不要选 Java\", …} 3."},
 "signals":["/不要/u"]}

这些是我写给 subagent 的任务指令,被当成"硬约束"记了下来。根因不是阈值调错,而是:

在委派子会话里,source.kind === 'user' 的消息不是人类的话,是父代理的指令。

核实方式是从磁盘读那条会话的 header:{"delegationDepth":1,"agentPreset":"cordis"} —— 确认是子会话。

修复分两层,都带回归测试(测试用例直接用上面这些真实句子):

  1. writeSkipSubagents: true(默认开)——深度 >0 的会话不再参与学习。子会话的回合对父会话的钩子同样可见,所以不丢信息;
  2. screenSentence()——拒收任务指令(按下面步骤、第 N 步、把…贴出来、不要改写 …)与载荷({"key":、代码块)。刻意做窄:不要改动 data/ 目录 这类真约束必须活下来。

这条记录本身就是这个插件的价值主张:误记是可以被逐条追责的,因为每条记忆都带着来源会话、seq、原句和命中的信号。


指标(都能从台账算出来,不是估计)

指标 怎么算 当前
写入精确率 / 误记率 eval/report.ts 在人工标注的真实语料上算(当前 38 句已标,逐句判断见 eval/labels/README.md);eval/write-precision.ts 是 20 条的冒烟集 只过筛子 43% / 100%;离线规则判定 50% / 89%(F1 0.64);Jev 50% / 11%(F1 0.18)(精度 / 召回,实测于 2026-09-29)
Hit@K 对每条标注查询 Q:前 K 条里只要有 1 条标注正例即记 1,最终 = 命中查询数 ÷ 查询数 待测
Recall@K 对每条 Q:前 K 条里的正例数 ÷ 该 Q 的全部正例数,再取平均 待测
注入 token 数 台账 kind:"recall" 的 tokens 字段 1 条约 60 tokens(历史上 4 条时 289–360)
被拦下的候选 台账 kind:"skip" 的 reason veto:secret / veto:task-instruction / veto:payload / duplicate / subagent-session / below-min-remember / below-min-importance / type-disabled:*
判定降级 台账 kind:"degraded" jev-missing-row(候选超出单次请求上限时静默降级,现在会记账并告警)

写入精确率是怎么量的

eval/write-precision.ts 是一套带标注的候选集(20 条 = 8 条该记 + 12 条不该记),其中 8 条"不该记"取自真实会话里插件确实写过的原句(子代理任务指令、一次性环境失败、用户提问、API key 本身)。

它只有 20 条,只能证明"没有明显坏掉",不能当结论用。 真实分布的量法在 eval/labels/:从历史会话里抽出候选句、由人逐句标注,再对照三种判定(只过筛子 / 规则判定 / Jev)。那份数据的抽样口径和批次说明写在 eval/labels/README.md,逐句结果在 eval/labels/report.md(node eval/report.ts 生成)。

真实语料上的结果:结论取决于那 8 行标注

在 38 句人工标注的真实句子上(编码类 9 句已定),三种判定是这样:

判定 精确率 召回率 F1
只过筛子(当前规则) 43% 100% 0.60
离线规则判定 50% 89% 0.64
Jev 判定(remember ≥ 0.6) 50% 0–11% ≤0.18

Jev 在 9 条正例里只写了 0–1 条(重复跑会在 0% 和 11% 之间跳),而它把这些句子的类型全判对了(constraint / decision),只是 remember 只给 0.07–0.59 分。但先别下结论:这 8 条被漏记的句子全是祈使句(请你按照这个格式去写简历、改成"一条消息 → 一串任务"、明天晚上有一个淘天的面试…帮我复习),而标注标准里写着"一次性任务指令 = 0"。标注者本人也提出"有些内容确实不需要沉淀"。

所以结论对这几行标注高度敏感(V0–V3 是逐步把它们改标 0):

场景 正例数 Jev 精确/召回 离线规则判定 精确/召回
按当前标注 9 50% / 0–11% 50% / 89%
3 条明显的一次性指令改标 0 6 50% / 0% 31% / 83%
5 条可疑的改标 0 4 50% / 0% 19% / 75%
只留最明确的一条(语言不要选 java) 1 50% / 0% 6% / 100%

也就是说:若那 8 条本来就该标 0,那么有问题的是离线规则判定——它会把一次性任务指令全部写进记忆,精确率掉到 6%;Jev 的拒绝反而是对的。在这批标注重判之前,两个判定谁更好没有结论。

冲突解决不再真删旧记忆(2026-09-29)

replace 那条分支调用的是 store.remove(existing) —— 旧记忆被真删,而它上方三行的注释写着"用 superseded 而不是删除:旧记录保留可审计",给用户的选项说明也写着"保留记录,供以后查证"。也就是代码在否定自己的注释,并且对用户说了假话。数据模型其实早就支持 superseded(store.ts 的类型里有,normalizeStatus 也认),只是没人用它。

现在:旧记录保留原文、置 status='superseded' + supersededBy,新记录写 supersedes;召回与 memory_search 都跳过 superseded(历史不是当前知识,但不许悄悄消失)。memory_forget 仍然真删 —— 那是用户明确要求删除,留副本会是另一种假话。

片段筛子:只上量过的那几条(2026-09-29)

我原本打算把"以 \end{itemize} 开头""含 /var/folders/... 临时路径"的句子直接筛掉。先量代价,结果否掉了这个方案:这两类句子在你标注里都是该记(标 1) —— 它们是"内容该留、前缀该脏"的 keeper,筛掉它们等于毁掉插件存在的理由。所以最终只上线了逐条测过、零误杀的四条:

规则 命中已标注行 其中标"该记"
行首是代码注释 //… 3 0
行尾分号 ; 3 0
行首是代码关键字(if/class/public…) 1 0
行首是 markdown 结构残留(**/---/#/` `) 1

那两类 keeper 的正确修法是清洗前缀而不是丢弃 —— 已经做了,关键在于身份与展示分离:

  • id(句子的身份证)永远按"你写的原句"算,所以清洗不会改变任何已标注行的 id,你的标注照旧有效;
  • text(存下来、注入进上下文的那份)是清洗后的:去掉行首的 \end{itemize}/\item/\textbf{...},把临时路径(/var/folders/、/tmp/、粘贴暂存目录)换成签名里已在用的 <path> 占位符。通用路径不动(配置文件路径可能就是一条记忆的全部意义)。
  • 筛子改为跑在清洗后的文本上,顺带修掉一个隐藏 bug:前缀曾经能改变判定 —— 请你按照这个格式去写简历… 本来就会被"一次性任务指令"筛子挡下,是前面的 \end{itemize} 把它"救"成了候选。

这个"身份取自原句、其余取自清洗文本"的错位很容易写反,我第一版就写反了(注释说原句、代码传了清洗后的),是测试抓出来的,现在有测试钉住。

两层记忆:原句是证据,规范形式只用于注入(2026-09-29)

之前存的是你原封不动的那句话。好处是"绝不编造"——记忆永远能追回到一句你真的说过的话;坏处也真实:错别字、口水话、断句会一起进上下文。别家都改写(memsearch 把每回合变成 2–10 条第三人称 bullet,OpenViking 抽进带 as of 日期的类型化文件),照搬原句的才是异类。

所以现在是两层,分开的理由就是这张表:

内容 谁写 用途
证据层 你原句,永不被模型改写 你 台账、source.quote、逐条核对
规范层(新增字段 canonical) 模型整理过的第三人称陈述 模型,受硬约束 注入(默认不启用)

三道闸门防止它变成编造通道:

  1. 不许添加事实 —— prompt 明说,并且有确定性闸门兜底:规范形式的词元里必须有足够比例来自原句。阈值不是拍的,是量出来的:把「嗯那个啥 就是 端口别乱改啊 定了 8000」整理成「服务端口固定为 8000,不要修改。」共享 0.375 的词元,而一句编造了理由和后续动作的扩写只共享 0.19,阈值取 0.25。判错的方向是刻意选的:拒了只损失可读性,收了就把编造的内容送进上下文。
  2. 不许静默接受 —— 过不了闸门不修补、不裁剪,直接丢掉、保留原句。fail-open 在这里的含义是"保留原话",不是"保留模型猜的"。
  3. 没有路由就不做 —— Jev 根本做不了这件事(它只能回答 noul/choice/score,输出不了文本),所以走宿主自己的 llm 服务,路由取自 agentDefaultModel.currentSelection(),也可以配置覆盖。没有路由就什么都不做,并且只在挂载时报告一次(start 台账行的 normalize 字段),不会每写一条就留一行失败噪音。

配置与默认值:

config:
  normalize:
    enabled: true    # 生成规范形式(默认开)
    inject: false    # 注入时用它替换原句(默认关 —— 这会改变模型看到的内容,先看样例)
    # provider / model 留空 = 用宿主的默认模型

默认不注入,因为这一步改变的是模型看到什么。想看样例再决定的话,台账里的 kind:"normalize" 行就带着 from / to(各 160 字)—— 打开 enabled 跑几天,读那些行,满意了再把 inject 打开。

冲突窗口的排序:默认 BM25,可选 embedding(2026-09-29)

冲突确认那一步不会把全部记忆丢给模型——只给 knownForConflict 条(默认 20)。原来这 20 条是"存储顺序的前 20 条",于是排在第二十一条的矛盾永远不会被看到,模型回答"不冲突",台账里什么都不留。现在改成按相关性排序,打分函数是可替换的接口:

conflictRanking 实现 依赖 代价
lexical(默认) 对记忆做 BM25 无 无,且没有任何内容离开本机
embedding 余弦相似度(智谱 embedding-3) 一个 key 记忆正文会发给该服务商;派生向量缓存在 embeddings.json

BM25 用在这里合理吗?合理,而且中文分词的问题已经解决了 —— 不需要任何依赖。 我原来写"中文没有分词器",这个说法是错的:Intl.Segmenter 是 V8 自带的,Node 又是完整 ICU,中文词典分词本来就在机器上。

实测(查询 端口不要用 9000 了,正确结果应是"服务端口用 8000"排在"不要用 yarn"前面):

分词方案 不要用 yarn 服务端口用 8000 结论
全量字符 bigram(原来) 2.733 2.188 ❌ 排错
Intl.Segmenter 词元(现在) 1.814 4.959 ✅ 正确
分词器词元 + 词内 bigram 1.814 4.959 ✅ 正确(与上等价,取更简单的)

bigram 输的原因是 不要用 被切成 不要 + 要用,而 要用 成了罕见词、凭空制造 IDF;分词器在 不要|用 处切开,这个假词就不存在了。代码里还加了运行时自检:精简 ICU 的构建会把中文切成单字(比 bigram 更差),所以加载时用一句话探测,不通过就退回 bigram。

BM25 真正修不了的是改写(data 下的文件别碰 ↔ 不要改动 data/ 目录 只共享一个词),那是下面 embedding 开关存在的理由。

embedding 修的是另一种情况:改写。 实测(真实 API 调用,2026-09-29):

对比 余弦
不要改动 data/ 目录下的任何文件。 ↔ data 下的文件别碰(改写) 0.769
不要改动 data/ 目录下的任何文件。 ↔ 服务端口固定 8000(无关) 0.532

无关句也有 0.53 —— 所以它只能用来排序候选,不能当绝对阈值:真正区分的是相对顺序。另一方面 3 条文本一次请求 305ms、0.5 元/百万 token(全库约 3 万 token ≈ 0.015 元),缓存命中后不再请求。BM25 对这一对改写的得分接近 0,这也是为什么这个开关值得存在。

失败一律回退到 BM25(无 key、超时、报错、维度不一致),因为排序是优化,写入不能依赖它。

问法改了一版,A/B 量过(2026-09-29)

原来那句判定说明里写着"是用户对项目的说法或偏好(不是这一次任务的操作指令)"——它让模型去判句子的形式,于是祈使句一律被否,连"我要求你说设计…就说流程就行了"这种长期偏好也被当成一次性指令(0.14 分)。现在改成判内容的时效:"以后新的会话里是否仍然适用或成立……句子是以指令的形式说的并不影响判断"。

同一批 38 条标注、各跑 3 次:

问法 Jev 精确率 Jev 召回率 三次跑的结果 最明确那条约束
旧(判句子形式) 0% 0% 0%/0%、100%/0%、0%/0% 漏记(0.59)
新(判内容时效) 33% 11% 33%/11% 三次一致 记下了

改动是真实的,但很小:召回 0% → 11%,代价是多放 1 条(一段粘贴文本里的"这个一定要记住"被当成了你的要求)。剩下没救回来的仍是"祈使式的项目偏好"(是一个风格和精简程度…、改成"一条消息 → 一串任务")。

新问法下阈值扫描也好看得多(0.20 时 42% / 56%,旧问法同阈值只有 50% / 33%),但没有动默认阈值 0.6:30 行已定标注不够定阈值,而且分数排序并不单调(0.4 那档比 0.5 还差)。

没有结论的部分:如上面的敏感度表,两个判定谁更好仍取决于那几行标注;但"祈使式项目偏好会被 remember 拒掉"这件事,在两种问法下都成立。

TYPESAFE_API_KEY=... node eval/write-precision.ts              # 无 key 时只跑确定性筛查 + 启发式
TYPESAFE_API_KEY=... WP_REPEAT=10 node eval/write-precision.ts # 模型判定跑 10 次,看分数波动

实测于 2026-09-29(jev-1.13.0,20 条里确定性筛掉 4 条、16 条进判定,单次 1.3–3.4s):

确定性筛查:拦掉 4 条(2 条任务指令 + 1 条提问 + 1 条密钥),正例 0 损失
启发式     precision=0.86 recall=0.75 F1=0.80
Jev @0.3   precision=0.89 recall=1.00 F1=0.94
Jev @0.5   precision=0.88 recall=0.88 F1=0.88
Jev @0.6   precision=1.00 recall=0.75 F1=0.86    ← 默认阈值(同一次跑的另一次运行给 0.86,见下)
Jev @0.7   precision=1.00 recall=0.63 F1=0.77

两个必须一起读的事实(否则上面这张表会误导):

  1. 模型判定不是确定性的。 同一批输入重复跑 10 次,16 条候选里 15 条的分数都在变(最大波动 0.07),其中 1 条的分数在 0.54–0.60 之间反复越过阈值——单次运行会让精确率在 0.86 和 1.00 之间跳。所以 Jev 的数字必须报重复跑的区间,单次结果不能当结论。
  2. 默认阈值 0.6 会稳定地漏掉中文项目约束。 不要改动 data/ 目录下的任何文件(硬约束,标注为"该记")10 次跑下来是 0.50–0.53,sqlite 写入失败:EDQUOT…(可复现的坑)是 0.34–0.41——两条都稳定低于阈值。这不是噪声,是召回损失:@0.6 的召回 0.75 就是漏了这两条。

写进台账的判定分(remember)现在也可以直接从台账复核:kind:"remember" 记录里带 by 和 model,能看出某条记忆是 Jev 判的还是启发式判的。

默认取 0.6 而不是 F1 更高的 0.3:假阳性比假阴性贵——一条错记会进入此后每一个会话的提示。两个已知漏判也写在这里:不要改动 data/ 目录下的任何文件。(Jev 给 0.51,恰好压线)与 sqlite 写入失败:EDQUOT...(给 0.38——只看到一次,无从判断会不会复现;这条已由确定性规则补上:同签名重复 ≥ repeatFailuresToWrite 次才落盘)。

为什么分开写 Hit@K 和 Recall@K:多正例下两者会分叉(Hit@5=1 时 Recall@5 可能只有 1/3),只写"Top-3 命中"是不可复现的。这个教训来自 zilliztech/memsearch 公开的中英检索评测——它同时发布了两套定义,参见 docs/memsearch-notes.md。

指标分工也定了:Jev 只用在写入判定,不用做召回重排。memsearch 的公开评测里 Jev 作为 reranker 输给 Voyage rerank-3(Recall@5 0.7941 vs 0.8187、MRR@10 0.6884 vs 0.7754),作者据此拒绝把 Jev 设为默认——那是"给已有候选排序"这个位置上的负结果,和我们"写入判定"不是同一个任务,但任何对外表述都必须承认这条公开数据。


  • 两种投递方式:正常情况下走系统提示里的运行时上下文(便宜、稳定);检测到 preset 关掉了运行时上下文时,改走一条带插件来源的消息(form: "recall"),并在台账记 context-suppressed。这条兜底是必需的:那种情况下宿主的装配器根本不会调用我们的回调,插件既不报错也不留痕——完全静默。

明确不做(避免和生态重复)

  • 不做 UI / 面板、不做向量库(类型化 + 少量记录不需要);
  • 不做"从反复失败里长规则"(那是 dsh-jev-forge 的题目)、不做全量写入的人工分诊——只在你和旧记忆真的矛盾时才打扰你(见上一节);
  • 不碰 dsh-jev-tools 已覆盖的四个节点:工具输出精简、注入筛查、技能推荐、交付闸门;
  • 不与 AGENTS.md 争优先级:人工手写 > 自动记忆;冲突条目先落 needs-review 且默认不注入,再由你决定归属。

测试与验证

node --test test/*.test.ts      # 80 条,全离线,不联网、不启动 harness
pnpm run typecheck              # tsc --noEmit,零依赖包也能有真类型检查

包含一条完整的端到端闭环(用假 ctx 驱动真实代码):写入 → 下一会话召回 → memory_search → memory_forget。

为什么是 TypeScript 而不用构建:Node 22.23 默认开启类型擦除(process.features.typescript === 'strip'),所以 import('./dsh/index.ts') 与 node --test test/*.test.ts 都能直接跑,没有构建步骤,也就保住了"零运行时依赖 + 可被 file: URL 直接挂载"这个性质。代价是只能用可擦除语法(无 enum / namespace / 参数属性 / 装饰器),相对导入必须写显式 .ts 扩展名。类型检查靠 devDependency 里的 typescript(与运行时无关)。

真实 harness 验证已做过一次(记录在 docs/DESIGN.md 的"实测记录"一节):插件热挂进正在运行的 web profile,台账出现 start,一次子会话回合触发了 4 条写入,下一轮模型请求的系统提示里出现了 ## 长期记忆 区块。


现状与未验证项(不编造)

  • 离线全绿、真实进程内闭环已验证;
  • Jev 线上路径已用真实 key 跑通:POST https://api.typesafe.ai/v1/systemone → jev-1.13.0,docs/jev-api.md 里的请求/响应结构逐字段对上(含 answers.<id>.noul / .choice / .score / .confidence)。延迟量级 0.56–0.68s,但那是一次判 16 条、把上限临时调到 50 测出来的;默认上限是 6 条(第 7 条起退回启发式,现在会记 kind:"degraded" 并告警);
  • live 判定已确认走 Jev(2026-09-28 重启后核实):台账 start 行写着 "version":"0.6.0"、"jev":{"ready":true,"source":"service:file"},且此后的写入/跳过记录都带 "by":"jev"、"model":"jev-1.13.0"。凭据走两条路(宿主凭据服务优先、直读凭据文档兜底),所以"服务在开机那一瞬还没就绪"不再决定判定方式;
  • 多进程同时写同一个 memory.json 是 last-write-wins,单进程是受支持场景(TODO:锁文件);
  • 代码改动必须重启 harness:Cordis 的 HMR 能重挂载插件行、重读存储,但 ESM 缓存不会重新求值 lib/*.ts。这条是实测的,不是推测:一次语义变更触发的重挂载后,start 台账仍写着旧 version、也没有新字段。start.version 就是用来判断"跑的是哪份代码"的;换一个文件路径(如 .js → .ts)会强制换一套模块 URL,那是唯一不用重启的刷新方式;
  • 同机还有一个同类插件 @zilliz/memsearch-dsh(见 docs/memsearch-notes.md):它与本插件正交,但挂在同一 patch 层,同 profile 共存未实测(可能双份注入)。

目录

dsh/index.ts          插件入口:钩子、注入点、三个工具,以及本地结构类型
dsh/lib/signals.ts    确定性信号词表 + 候选筛查(含密钥/任务指令/载荷否决)
dsh/lib/extract.ts    从回合事件里抽候选
dsh/lib/judge.ts      判定端口(Jev / 启发式)+ 确定性写入闸门
dsh/lib/jev.ts        Jev HTTP 客户端(凭据解析、重试、超时、解析)
dsh/lib/store.ts      memory.json + ledger.jsonl
dsh/lib/recall.ts     选哪些记忆、怎么渲染
dsh/lib/conflict.ts   冲突配对(词重叠)+ 三选项卡片 + 答案映射
dsh/lib/credentials.ts  直读凭据文档的兜底路径(极小的 refs 段解析器)
dsh/lib/text.ts       文本工具(分句、估算 token、哈希)
test/                 80 条离线测试(plugin.test.ts 用假宿主驱动完整闭环)
eval/write-precision.ts   带标注的写入精确率评测(可对标、可复现)
tsconfig.json         noEmit + allowImportingTsExtensions(Node 擦除模式可直接跑)
docs/jev-api.md       Jev 调用契约调研(带出处链接)
docs/memsearch-notes.md  同类项目 memsearch 的源码级调研与逐项对比
docs/DESIGN.md        设计决策与实测记录
—/ 5

No ratings yet

Verified DSH bundle

Commit 28da78e23d61

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout