Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ to one another. Missing translations are reported as warnings, not hard errors.

- [Track build artifacts in version control](rejected/2026-09-02-track-build-artifacts-in-git.md) — regenerable outputs stay untracked; buildability is verified, not committed
- [Keep the bookmark feature named "anchors"](rejected/2026-09-02-keep-bookmarks-named-anchors.md) — renamed to tesserae to end collision with surface/task/support anchors
- [Local hashing vectors as a retrieval fallback](rejected/2026-09-03-hashing-vector-retrieval-fallback.md) — rejected as a semantic-retrieval signal (measured zero gain at 256-d); word-level uses remain an open candidate
1 change: 1 addition & 0 deletions docs/decisions/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@

- [将构建产物纳入版本控制](rejected/2026-09-02-track-build-artifacts-in-git.zh-CN.md) — 可再生输出保持不入库;可构建性靠验证而非提交
- [书签功能继续命名为 "anchors"](rejected/2026-09-02-keep-bookmarks-named-anchors.zh-CN.md) — 改名为 tessera,终结与 surface/task/support anchors 的撞名
- [本地哈希向量作为检索兜底](rejected/2026-09-03-hashing-vector-retrieval-fallback.zh-CN.md) — 作为语义检索信号被拒(256 维实测零增益);词法级用途仍是开放候选
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Local hashing vectors as a retrieval fallback

[中文](2026-09-03-hashing-vector-retrieval-fallback.zh-CN.md)

**Status:** rejected
**Date:** 2026-09-03

## Problem

When an external embedding provider is unavailable (rate-limited, down, or not
configured), retrieval falls back to lexical-only. The store already writes a
local `nmg-hashing-v1` vector (256-d deterministic hash) for every memory, so
it was proposed to blend those vectors with lexical search as a degraded
retrieval mode that is "better than pure lexical".

## Proposal

When no external vector exists, run search as lexical + local `nmg-hashing-v1`
vector blend (a local hybrid), marking the result `degraded: true`.

## Alternatives considered

- **Raise hashing dimensions (NUMEN-style 16K–32K) so the blend is
discriminative.** Rejected: enormous fixed memory per vector is unacceptable
for a local SQLite store; the whole point is small and dependency-free.
- **Treat hashing only as a word-level tool (near-dedup, spelling) rather than
semantic retrieval.** Not part of this rejection — separately tracked as a
candidate (see ticket 7), because that role is a different task with
different dimension requirements.

## Why rejected

Measured and researched, the blend provides no gain and the known fix is
unaffordable:

- **Measurement (real store):** blending 256-d hashing vectors is
byte-identical to pure lexical — self-recall 45/154 in both arms, vector
cosine scores ≈ 0. The hashing vectors carry no discriminative signal at
256 dimensions.
- **Published dimensionality bottleneck:** deterministic character-hashing
retrieval only overtakes BM25 at very high dimensions (NUMEN, arXiv
2601.15205: 93.90% Recall@100 at 32,768 dimensions vs BM25 93.6%).
Low-dimensional hash vectors collapse distinct texts into near-orthogonal,
information-poor vectors.
- **Cost of the fix:** NUMEN-style high dimensions need enormous fixed memory
per vector (a FastText-style bucket table, or 32K floats per row), which is
unacceptable for a local SQLite-backed store whose whole value is being
small, offline, and dependency-free.
- **Out of scope — not rejected:** feature hashing and SimHash are word-level
tools whose legitimate uses are spelling-tolerant matching and
near-duplicate detection _as a complement to lexical search_, not semantic
retrieval. This decision rejects only the semantic-retrieval blend; a
word-level role (e.g. recalling near-duplicate candidates whose spelling or
word form differs from the query, before an LLM judge decides) is a
separate, independently evaluable candidate and is not covered by this
rejection.

## Consequences

- The no-external-provider path stays a plain lexical fallback that reports
`degraded: true` with a reason — honest about the degradation instead of
silently adding a signal that measures as zero.
- Semantic retrieval quality comes from a configured external embedding
provider (the hybrid path), which the embedding-default-on work makes
reliable: every remember/search tops up a bounded batch, provider presence
implies sync, and provider failures pause rather than fail the index.
- This rejection is scoped to hashing vectors as a _semantic_ retrieval
signal. Word-level uses of hashing/SimHash (near-dedup candidate recall,
spelling-tolerant matching) remain open for separate evaluation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 本地哈希向量作为检索兜底

[English](2026-09-03-hashing-vector-retrieval-fallback.md)

**Status:** rejected
**Date:** 2026-09-03

## Problem

当外部 embedding provider 不可用时(限流、宕机或未配置),检索退化为纯词法。store 已为每条记忆写入本地 `nmg-hashing-v1` 向量(256 维确定性哈希),因此有人提议把这些向量与词法检索混合,作为"优于纯词法"的降级检索模式。

## 提案

当不存在外部向量时,将检索运行为词法 + 本地 `nmg-hashing-v1` 向量混合(本地 hybrid),并将结果标记为 `degraded: true`。

## 考虑过的替代方案

- **提高哈希维度(NUMEN 式 16K–32K),让混合具备判别力。** 拒绝:每条向量巨大的固定内存对本地 SQLite store 不可接受;其价值就是小巧、零依赖。
- **仅把哈希当作词法级工具(近似去重、拼写容错),而非语义检索。** 不在本次拒绝范围内——作为独立候选另行跟踪(见工单 7),因为该角色是不同任务、不同维度需求。

## 为什么拒绝

经测量与研究,混合无增益,且已知的修复方案成本不可接受:

- **测量(真实库):** 混合 256 维哈希向量与纯词法逐字节相同——两臂自召回均为 45/154,向量余弦分数 ≈ 0。256 维哈希向量不携带可判别信号。
- **已发表的维度瓶颈:** 确定性字符哈希检索只在极高维度才超过 BM25(NUMEN,arXiv 2601.15205:32768 维时 Recall@100 93.90%,对比 BM25 93.6%)。低维哈希向量把不同文本压成近正交、信息贫乏的向量。
- **修复成本:** NUMEN 式高维需要每条向量巨大的固定内存(FastText 式桶表,或每行 32K 浮点数),对以"小巧、离线、零依赖"为核心价值的本地 SQLite store 不可接受。
- **范围外——不在拒绝之列:** 特征哈希与 SimHash 是词法级工具,其正当用途是拼写容错匹配与近似去重检测——作为词法检索的补充,而非语义检索。本决策只拒绝语义检索混合;词法级角色(例如在 LLM 判定前召回拼写或词形与查询不同的近似重复候选)是独立、可单独评估的候选项,不在本次拒绝范围内。

## Consequences

- 无外部 provider 的路径保持纯词法兜底,并报告 `degraded: true` + reason——诚实地说明降级,而不是静默加入一个实测为零的信号。
- 语义检索质量来自配置好的外部 embedding provider(hybrid 路径),embedding 默认启用工作已使其可靠:每次 remember/search 补一批有界向量、provider 存在即同步、provider 失败是暂停而非整体失败。
- 本拒绝的范围是哈希向量作为**语义**检索信号。哈希/SimHash 的词法级用途(近似去重候选召回、拼写容错匹配)仍开放供单独评估。
107 changes: 107 additions & 0 deletions docs/design/simhash-lexical-complement-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SimHash 词法层补充设计(工单 7)

**Status:** proposed
**Owner:** supersession 候选召回(写路径),与 `supersession-design.md` 互补
**Date:** 2026-09-03

## 1. 问题

`supersedeCandidates`(写路径的候选召回)目前用:
- **instr 子串预过滤**(lower(statement) 匹配 token)
- **token 规范化**(小写 + 去标点)
- **转换结构检测**(`transitionFromTokens`)
- 排序:转换命中优先 → `statementSimilarity`(word-set Jaccard)

**盲区**:所有词法判定都是**词级精确匹配**。词形变化与拼写变体导致召回为 0:

| 已有表述 | 新写入 | 词级判定 | 语义上是同一物? |
|---|---|---|---|
| "用户偏好 Chinese explanations" | "用户偏好 Chinese explanation" | instr 匹配(explanation ⊂ explanations 前缀?否,词边界)→ 漏 | 是 |
| "embedding 配置" | "embeddings 配置" | token "embedding" vs "embeddings" 不同词 → 漏 | 是 |
| "colour scheme" | "color scheme" | 拼写变体 → 漏 | 是 |

这些变体是真实的(用户在 2026-08-12 的 supersede 链实测中见到 "Employed" ≡ "employed" 靠 token 规范化救回,但**复数/拼写/派生词形**仍漏)。

## 2. 目标与边界

**目标**:用确定性词法指纹(Feature Hashing / SimHash)补上"词形/拼写变体"的近重复候选召回——让变体重复能进 judge 候选池,而不是被词级精确匹配挡住。

**边界(明确不做)**:
- 不判语义、不替代 judge——只负责**召回候选**
- 不碰搜索排序(检索路径的 hashing 语义混合已被 rejected,见
`docs/decisions/rejected/2026-09-03-hashing-vector-retrieval-fallback.md`)
- 不引入外部依赖、不建巨大索引(本地轻量:一列整数 / 内存 ~KB 每千条)
- NMG 只对**精确规范化等值**自动行动;近似判定始终交给 judge(维持
supersession-design.md 的分工红线)

## 3. 方案设计

### 3.1 指纹:64-bit SimHash(token 级)

```
simhash(text) -> 64-bit integer
tokens = 小写 + 去标点 + 分词(复用现有 token 规范化)
v[0..64) 每个 token 的 64-bit 哈希累加 ±1
指纹 = 每一位取 v[i] 符号
```

64-bit 是记忆系统先例验证过的量级(claude-memory-system issue #53:64-bit、
Hamming ≤ 3 召回近重复、每千条 < 1KB 索引)。

### 3.2 存储

`memory_records` 加一列 `simhash INTEGER`(或复用 markers 通道?——**列更优**:
可索引、可 SQL 范围查询)。写入时随 `upsertEmbedding` 同事务算好存下。

### 3.3 召回落点(候选检测内)

在 `supersedeCandidates` 现有 instr 预过滤之后、排序之前,加一个**指纹召回通道**:
- 新 statement → simhash
- `Hamming(new_simhash, old.simhash) ≤ 3` 且**词级判定未命中**的记录进候选
- 与现有候选合并、去重,仍走 `SUPERSEDE_CANDIDATE_MAX = 10` 上限

这样变体重复**先进候选池**,由 judge 判是否 supersede。

### 3.4 阈值与误召回

Hamming ≤ 3 在 64-bit 上对"同主题不同句"的误召回率需实测(不同长句可能恰好近
Hash)。实验阶段先测误召回率,若高则收紧(≤2)或加"至少共享 1 个核心 token"
的护栏——指纹只作**召回补充**,不单独成判定。

## 4. 实验设计(先测缺口,再决定实现)

工单 7 的 Done when 要求先证明"Jaccard 路径确实漏词形变体、SimHash 能召回"。
实验不需要 LLM、不需要外部——纯本地真实库 + 构造变体:

1. **取真实库全部记忆**(~330 条)
2. **构造变体对**:对每条含实质内容的记忆,程序化生成词形变体(复数化 /
拼写变体 / 派生词形——只变一个 token,其余不变)
3. **测量 A(现状)**:变体作为新写入 → `supersedeCandidates` 能否召回原记忆
4. **测量 B(加指纹)**:同一变体 → 加 SimHash 通道后能否召回
5. **结论**:B - A 的召回增益 > 0 且误召回率可接受 → 实现;否则记录"缺口已
被其他机制覆盖"并关闭

(可选)对照:真实近重复对(如 2026-08 多次出现的同名 supersede 链)验证
指纹在真实重复上不误伤。

## 5. 实现规划(实验通过后)

1. schema:`memory_records.simhash INTEGER` + migrate(旧行回填:遍历已有记录
算指纹——一次性,可复用现有 normalizeStatement)
2. 写入:remember 事务内随 upsertEmbedding 算指纹
3. 召回:`supersedeCandidates` 加指纹通道(Hamming ≤ 阈值)
4. 测试:变体召回单测("employments" vs "employment" 等)+ 误召回率上限断言
5. 文档:本设计 + supersession-design.md 候选检测节更新

## 6. 开放问题

- Hamming 阈值(3 vs 2)与误召回护栏(是否需"共享核心 token"条件)——实验定
- 指纹对**短陈述**(session 元数据等噪音)是否应跳过(无实质 token → 指纹无意义)
- SimHash 与现有 `statementSimilarity` Jaccard 的关系:Jaccard 保留(词级精确),
指纹只补变体——两者并存的排序权重

## 7. 研究基础

- [claude-memory-system: SimHash near-duplicate pre-filter](https://github.com/nikhilsitaram/claude-memory-system/issues/53):记忆系统先例——64-bit SimHash、Hamming ≤ 3、写路径预筛、每千条 < 1KB
- [qdrant: Lexical Fuzzy Filter](https://github.com/qdrant/qdrant/pull/8707)、字符 n-gram VSM:词法容错的工业做法(NMG surface anchors 的 trigram 已覆盖显式 token,本设计补**写入侧**变体召回)
- Feature hashing / SimHash 定位为词法级工具(拼写容错、近似去重)——与本设计的语义检索边界一致(见 rejected ADR)
Loading