Skip to content

Latest commit

 

History

History
1513 lines (1177 loc) · 93.7 KB

File metadata and controls

1513 lines (1177 loc) · 93.7 KB
feature_ids
F102
related_features
F024
F100
F042
F186
F188
topics
memory
adapter
evidence-store
architecture
doc_kind spec
created 2026-03-11

F102: 记忆组件 Adapter 化重构 — IEvidenceStore + 本地索引

Status: done | Owner: Ragdoll | Priority: P1 | Completed: 2026-04-04 (Phase A~J) | Reopened: 2026-04-13 (Phase K) | Re-closed: 2026-04-14 (Phase K done; AC-K3 later completed by F209 Phase A; AC-K4 remains future work)

F188 Phase F 关联(2026-05-10)

F102 owns search_evidence (semantic retrieval entry, IEvidenceStore.search()). 而 F188 Phase F 新增的 cat_cafe_graph_resolvecat_cafe_list_recentF188 agent-facing navigation tools — graph_resolve 复用 F188 Phase C GraphResolver;list_recent 是新增 RecentBrowseResolver (metadata browse read-model)。不属于 F102 索引层,不扩 IEvidenceStore(Maine Coon 二审 P2 architecture boundary)。

三入口路由(精确 anchor / 零先验 / 语义模糊)在 CLAUDE.md / AGENTS.md / GEMINI.md / OPENCODE.md 同步,single source of truth 在 cat-cafe-skills/refs/memory-routing-partial.md

给其他猫的快速现状(2026-05-22 更新)

Message 级别检索已上线运行。search_evidence(scope="threads", depth="raw") 可搜到具体消息(speaker + timestamp + passageId)。F209 Phase A 已补上 passage vector path:depth="raw" 支持 lexical / semantic / hybrid,hybrid 用 passage BM25 + passage vector NN 做 RRF;embedding unavailable 时会 fail-open 到 lexical,并显式返回 [DEGRADED] / effectiveMode="lexical"。日常用 mode="hybrid" 搜 thread 时 depth 默认 summary 级别;需要定位具体消息时切 depth="raw"

近期修复链(2026-04-14~16,PR #1155/#1160/#1179/#1192/#1195/#1204):depth=raw 降级信号 → passage 排序 → heading→keywords 索引 → auto-rebuild 机制 → lexical recall backfill → docs scope filter 修正。核心检索能力已经过三轮 dogfood 验证。

晚间复测(2026-04-15,F148 深术语样本)显示:scope="threads", depth="raw"briefing→invocation link telemetryB+A AutoSummarizer + regex 这类 query 已能命中具体 passage;scope="docs", depth="summary" 暴露出的 scope=docs 混入 thread digest 异常已由 PR #1204 修复(排 thread/session,保留 file-backed discussion 文档)。下一步按同一组 F148 样本 re-dogfood,确认 docs summary 对深实现名词的残余短板到底是过滤还是排序。

后续优化方向:评测基准从手挑 query 升级为“固定回归集 + seeded 随机 feature 抽样 + query 扰动变体”,避免 recall dogfood 对已知样例过拟合。

Why

Hindsight(外部记忆服务)已停用——operator觉得实在难用。当前 HindsightClient 硬编码在路由和启动链路中,无法替换。我们需要:

  1. 把记忆组件从 Hindsight 硬绑定改为可插拔 Adapter 接口
  2. 实现一个轻量的本地替代方案(结构化索引 + feat 体系自动维护)
  3. 避免重蹈覆辙:retain 不能直写长期库(碎片化垃圾入库教训)

终态架构(P1 面向终态,从这里反推)

truth sources (git-tracked)
  docs/*.md                          — 项目文档(feat/decision/plan/lesson)
  docs/markers/*.yaml                — marker 审核日志(durable workflow state)
  global profiles/rules/lessons      — Skills + 家规 + MEMORY.md

compiled indices (gitignore + rebuild)
  evidence.sqlite                    — 项目索引(evidence_docs + evidence_fts + edges)
  global_knowledge.sqlite            — 全局索引(read-only,从 Skills/家规/MEMORY.md 编译)

services (6 个接口)
  IIndexBuilder                      — scan/hash/rebuild/schema migration/fts consistency
  IEvidenceStore                     — search/upsert/delete/get/health
  IMarkerQueue                       — submit/list/transition(真相源在 docs/markers/)
  IMaterializationService            — approved → .md patch → git commit → trigger reindex
  IReflectionService                 — LLM 编排,独立于存储
  IKnowledgeResolver                 — query planning → fan-out → normalize → RRF rank fusion

关键设计决策

  • 全局记忆 = Skills + 家规 + MEMORY.md(F100 Self-Evolution 体系,已有基础设施)
  • 项目记忆 = SQLite 数据库(evidence.sqlite),每个项目一个文件,物理隔离
  • SQLite 是终态存储基座(不是终态检索策略):FTS5 全文搜索 + SQLite vector extension(按当时稳定版本启用) + edges 关系表,Phase 1 建的东西 Phase N 还在。纯 lexical 不够,Phase C 向量增强是预期路径
  • 真相源分层
    • 索引(evidence.sqlite/global_knowledge.sqlite)= 编译产物,gitignore + rebuild
    • 工作流状态(docs/markers/*.yaml)= git-tracked durable store,rebuild 不能蒸发审核历史
    • 知识真相源 = docs/*.md 文件;approved marker 必须先 materialize 到 .md 才算沉淀
  • 联邦检索KnowledgeResolver 融合两个同质 SQLite index(全局 read-only + 项目 read-write),用 RRF rank fusion,不混用 raw filesystem 和 SQLite MATCH
  • 过期知识防护superseded_by 字段 + supersedes/invalidates 关系,过时高相似决策比查不到更危险
  • 猫猫出征新项目 → 带走全局层(skills/家规/记忆),新项目自动初始化空的 evidence.sqlite

What

Phase A: 6 接口 + SQLite 基座 + 解耦

A1. 接口定义:6 个接口(KD-13)。

// 编译器:scan → hash → incremental rebuild → schema version → fts consistency
interface IIndexBuilder {
  rebuild(options?: RebuildOptions): Promise<RebuildResult>;
  incrementalUpdate(changedPaths: string[]): Promise<void>;
  checkConsistency(): Promise<ConsistencyReport>;
}

// 项目知识索引(编译产物,从 docs/*.md 重建)
interface IEvidenceStore {
  search(query: string, options?: SearchOptions): Promise<EvidenceItem[]>;
  upsert(items: EvidenceItem[]): Promise<void>;
  deleteByAnchor(anchor: string): Promise<void>;
  getByAnchor(anchor: string): Promise<EvidenceItem | null>;
  health(): Promise<boolean>;
  initialize(): Promise<void>;  // idempotent migrations + schema version + PRAGMA setup
}

// 候选记忆队列(真相源在 docs/markers/*.yaml,不是 SQLite)
interface IMarkerQueue {
  submit(marker: Marker): Promise<void>;
  list(filter?: MarkerFilter): Promise<Marker[]>;
  transition(id: string, to: MarkerStatus): Promise<void>;
}

// 晋升服务:approved marker → .md patch → git commit → trigger reindex
interface IMaterializationService {
  materialize(markerId: string): Promise<MaterializeResult>;
  canMaterialize(markerId: string): Promise<boolean>;
}

// 反思服务(独立于存储层,LLM 编排能力)
interface IReflectionService {
  reflect(query: string, context?: ReflectionContext): Promise<string>;
}

// 联邦检索:query planning → fan-out → normalize → RRF rank fusion
interface IKnowledgeResolver {
  resolve(query: string, options?: ResolveOptions): Promise<KnowledgeResult>;
}

interface SearchOptions {
  kind?: 'feature' | 'decision' | 'plan' | 'session' | 'lesson';
  status?: 'active' | 'done' | 'archived';
  keywords?: string[];
  limit?: number;
  scope?: 'global' | 'project' | 'workspace';  // 预留中间层 scope
}

// captured → normalized → approved → materialized → indexed(+ rejected 分支)
type MarkerStatus = 'captured' | 'normalized' | 'approved' | 'rejected' | 'needs_review' | 'materialized' | 'indexed';

接口关系SqliteProjectMemory 实现 IEvidenceStoreIMarkerQueue 真相源在 docs/markers/*.yaml(git-tracked),SQLite 内的 markers 表只是工作缓存。IIndexBuilder 负责 SQLite 编译。IMaterializationService 负责 approved → .md patch → reindex。IKnowledgeResolver 融合两个同质 SQLite index(全局 read-only + 项目 read-write),用 RRF rank fusion。

A2. SQLite 存储(终态基座)SqliteEvidenceStore 实现 IEvidenceStore

-- 结构化元数据表(常规表,精确过滤 + freshness check + join)
CREATE TABLE evidence_docs (
  anchor TEXT PRIMARY KEY,    -- F042, ADR-005, session-xxx
  kind TEXT NOT NULL,         -- feature/decision/plan/session/lesson
  status TEXT NOT NULL,       -- active/done/archived
  title TEXT NOT NULL,
  summary TEXT,
  keywords TEXT,              -- JSON array
  source_path TEXT,           -- docs/features/F042.md
  source_hash TEXT,           -- 变更检测
  superseded_by TEXT,         -- KD-16: 过期知识指向替代文档的 anchor
  materialized_from TEXT,     -- 关联 marker id(如从 marker 晋升而来)
  updated_at TEXT NOT NULL
);

-- 全文搜索(FTS5 外部内容表,索引 title + summary)
-- KD-18: tokenchars 处理 snake_case/feature ID,bm25 列权重 title > summary
CREATE VIRTUAL TABLE evidence_fts USING fts5(
  title, summary,
  content=evidence_docs, content_rowid=rowid,
  tokenize='unicode61 tokenchars "_-"'
);

-- 关系表(1-hop 扩展)
CREATE TABLE edges (
  from_anchor TEXT NOT NULL,
  to_anchor TEXT NOT NULL,
  relation TEXT NOT NULL,  -- evolved_from/blocked_by/related/supersedes/invalidates
  PRIMARY KEY (from_anchor, to_anchor, relation)
);

-- 候选队列工作缓存(真相源在 docs/markers/*.yaml,KD-8')
CREATE TABLE markers (
  id TEXT PRIMARY KEY,
  content TEXT NOT NULL,
  source TEXT NOT NULL,        -- cat_id + thread_id
  status TEXT DEFAULT 'captured',  -- KD-12: captured/normalized/approved/rejected/needs_review/materialized/indexed
  target_kind TEXT,            -- 预期 materialize 的类型
  created_at TEXT NOT NULL
);

-- KD-15: 预留 passage 级索引(v1 不填,1000+ docs 或 Phase C 启用)
-- CREATE TABLE evidence_passages (
--   doc_anchor TEXT NOT NULL REFERENCES evidence_docs(anchor),
--   passage_id TEXT NOT NULL,
--   content TEXT NOT NULL,
--   position INTEGER,
--   PRIMARY KEY (doc_anchor, passage_id)
-- );

-- Schema 版本(idempotent migration)
CREATE TABLE schema_version (
  version INTEGER PRIMARY KEY,
  applied_at TEXT NOT NULL
);

关键规则

  • approved marker 必须先 materialize 到稳定 source anchor(.md 文件),再由 IIndexBuilder 写入 evidence_docs。SQLite 是编译产物——rebuild 不会丢知识,因为真相源在文件系统
  • markers 工作流状态的真相源是 docs/markers/*.yaml(git-tracked),SQLite 只是工作缓存
  • 检索时 superseded_by IS NOT NULL 的结果降权或过滤(KD-16)
  • WAL 模式 + 显式单写者队列(KD-18)
  • FTS5 external-content 一致性封装到 IIndexBuilder(KD-18)

A3. 路由解耦:所有硬编码文件改为 DI 注入。

改造文件:

  • HindsightClient.ts → 保留为 HindsightEvidenceStore(legacy adapter)
  • evidence.ts 路由 → 注入 IEvidenceStore
  • callback-memory-routes.ts → 注入 IEvidenceStore,retain 改写 markers 表
  • reflect.ts → 拆为独立 ReflectionService(不属于存储层)
  • index.ts → factory 按配置选实现
  • hindsight-import-p0.ts → 适配新接口

Phase B: 自动索引 + SOP 集成

数据源自动索引(解析 frontmatter → upsert 到 SQLite):

  • docs/features/*.md — feat-lifecycle 立项/关闭时
  • docs/decisions/*.md — ADR 创建时
  • sealed session digest — session 封存时

检索链路:metadata filter (kind/status) → FTS5 search → edges 1-hop expand → source read

Phase C: 向量增强(预期路径)

在同一个 evidence.sqlite 上加表,不换存储——终态基座不变。纯 lexical 检索是已知短板(KD-5),Phase C 是预期路径而非可选。

C1. Embedding 模型选型

模型 角色 ONNX int8 维度 C-MTEB Transformers.js
Qwen3-Embedding-0.6B 主方案 614MB 32-1024 (MRL) 66.33 onnx-community ✅
multilingual-e5-small 兜底 ~130MB 384 ~50

选 Qwen3 原因:与项目 Qwen 语音 pipeline 统一技术栈;中英混排 C-MTEB 66.33 远超候选;MRL 支持维度可调(KD-19)。

C2. 三态开关 + fail-open

EMBED_MODE = off | shadow | on    # 默认 off
EMBED_MODEL = qwen3-embedding-0.6b | multilingual-e5-small  # 默认 qwen3
  • off:纯 Phase B lexical 检索,不加载模型
  • shadow:lexical 为主,后台异步跑 embedding 并记录 A/B 指标(不影响用户结果)
  • on:embedding rerank 生效,lexical 作为 fallback

fail-open 规则:模型下载 / 加载 / 推理任一失败 → 自动回落 Phase B lexical(不 block 检索)。

C3. 资源门禁

max_model_mem_mb = 800       # 超阈值直接降级到兜底模型或 off
embed_timeout_ms = 3000      # 单次推理超时 → 该请求走 lexical

C4. 向量存储

-- vec0 虚拟表(单一向量真相源,不在 evidence_docs 加列)
CREATE VIRTUAL TABLE evidence_vectors USING vec0(
  anchor TEXT PRIMARY KEY,
  embedding float[256]         -- MRL 维度,shadow 期 A/B 后确定
);

C5. 可复现版本锚

-- 索引元数据:模型/维度变更时触发全量 re-embed(不能静默混跑)
CREATE TABLE embedding_meta (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL
);
-- 初始写入:embedding_model_id, embedding_model_rev, embedding_dim

模型或维度变更检测到与 embedding_meta 不一致 → 清空 evidence_vectors + 全量 re-embed。

C6. Shadow 期 A/B

shadow 模式下 dim=128dim=256 各跑一轮评测(复用 Phase B memory_eval_corpus.yaml),对比 Recall@k 后再决定 on 的默认维度。

C7. 检索链路(Phase C 增强后)

metadata filter → FTS5 search → edges 1-hop expand → [embedding rerank] → source read
                                                       ^-- Phase C 新增

语义 rerank 仅对 FTS5 候选集做重排序(不替代 lexical 召回),保证 fail-open 时链路不断。

Phase D: 激活 — Hindsight 清理 + 数据源扩大 + 检索协议 + 提示词集成

触发:operator指示"Hindsight 去掉,把记忆组件跑起来"。 外部输入:Artem Zhutov《Grep Is Dead》— QMD 本地检索层方案(collection 分层 + BM25/vec/hybrid + /recall 协议)。 两猫共识:F102 引擎和 QMD 同构(都是 SQLite FTS5 + sqlite-vec + RRF),不引入 QMD,扩大 F102 数据源 + 给猫猫检索协议。 operator核心指示:功能做完必须修改提示词/skills,让猫猫感知到并主动使用。否则建了也白建。

D-1. Hindsight 全量清理(三层拆解)

范围 说明
Runtime routes 的 Hindsight 分支、factory 'hindsight' 类型、HindsightClient/Adapter 切断运行链路
Config hindsight-runtime-config.ts、ConfigSnapshot、env-registry 12 个 HINDSIGHT_* 变量、前端 config-viewer tab 清理配置面
Legacy docker-compose.hindsight.ymlscripts/hindsight/、P0 import pipeline、~26 test files 归档资产

D-2. 启动自动 rebuild + 可观测

  • 进程启动后自动执行 indexBuilder.rebuild()(带锁防并发)
  • search_evidence MCP 工具默认走 SQLite FTS5(不是 grep fallback)
  • Memory status 可观测:docs_count / last_rebuild_at / backend=sqlite

D-3. 数据源扩大:thread digest → evidence_docs

  • IndexBuilder.discoverFiles() 增加 session digest 数据源
  • Session digest 已有结构(topics/decisions/participants),直接 parse 进 evidence_docs
  • kind='session' 默认权重低于 feature/decision(避免聊天噪音淹没文档)
  • 分层检索策略(summary-first, raw-on-demand):
    1. 先搜 docs + memory(feature/decision/plan/lesson/memory)
    2. 不够再搜 threads-summary(kind=session)
    3. 还不够才下钻 raw transcript(通过现有 MCP cat_cafe_get_thread_context

D-3b. 自动 edges 提取 + Memory invalidation(GitNexus 理念吸收)

来源:GitNexus 研讨([thread-id]),三猫+operator共识。 原则:吸收"预计算结构 + 变更影响检测",不吸收图数据库/AST/聚类算法。 Maine Coon红线:edges 只能来自显式锚点(frontmatter),不能从语义相似度推断。推断关系不可信。

  • 自动 edges 提取IIndexBuilder.rebuild() 时从 frontmatter related_features/feature_ids/decision_id 交叉引用自动 upsert edges(零手工维护)
  • Memory invalidationincrementalUpdate() 检测到文档变更时,反向查询 edges 找依赖文档,标记为 needs_review(翻译自 GitNexus 的 detect_changes

D-4. 检索协议升级

// search 接口增强(三维参数)
search(query, {
  kind: ['feature', 'decision'],           // 过滤层(精确 kind)
  mode: 'lexical' | 'semantic' | 'hybrid', // 检索模式
  scope: 'docs' | 'memory' | 'threads' | 'sessions' | 'all', // 快捷分层
  depth: 'summary' | 'raw'                 // 噪音控制:默认 summary
})

检索路由策略:

  • 找 feature / ADR / 明确术语 → lexical(BM25 first)
  • 找"我们当时为什么这么决定" → lexical + semantic
  • 找长聊天里隐含的同义表达 → semantichybrid
  • 找源码 symbol / API 实现 → 继续 code search,不走记忆组件

D-5. MCP 工具收敛(operator指示:不能老的一套新的一套)

现有 27 个记忆/检索相关 MCP 工具,分 4 条平行链路。Phase D 收敛为两层架构:

Layer 1: 统一检索入口(猫猫日常用)

工具 定位
search_evidence 唯一的 recall/search 入口。支持 scope/mode/depth 参数,覆盖 docs + memory + threads + sessions
search_evidence(query, {
  scope: 'docs' | 'memory' | 'threads' | 'sessions' | 'all',
  mode: 'lexical' | 'semantic' | 'hybrid',
  depth: 'summary' | 'raw'  // summary-first, raw-on-demand
})

Layer 2: Drill-down 工具(按需深入)

工具 定位 何时用
get_thread_context by-id fetch(实时上下文) 已知 threadId,需要最近 N 条消息
list_threads 元数据查询 找 thread 列表
list_session_chain session 列表 已知 threadId,看 session 链
read_session_digest session 摘要 search_evidence 命中 session 后深入
read_session_events session 详情(3 视图) 需要看 raw transcript
read_invocation_detail invocation 级取证 审计/调试
reflect 反思(证据之上的总结) 需要 LLM 综合,不是检索入口
retain_memory 记忆沉淀 marker 提交,不是检索工具

废弃/吸收

工具 处置 原因
search_evidence_callback 合并到 search_evidence callback auth 是实现细节,不该暴露两个工具
reflect_callback 合并到 reflect 同上
search_messages 吸收为 search_evidence(scope=threads, depth=raw) 的底层实现 不再作为独立一级入口
session_search 吸收为 search_evidence(scope=sessions) 的底层实现 不再作为独立一级入口

不动

工具 原因
signal_*(12 个) 独立系统,外部信息源,不是项目记忆
feat_index 元数据查询,不是内容检索
list_tasks 任务管理,不是记忆

SystemPromptBuilder 也要改:不能再把 session-chain 三件套排成"默认找历史的第一选择"。要先教猫猫用统一 search_evidence,再教怎么 drill-down。

D-6. 提示词 + Skill 集成(operator重点指示:最重要的一步)

这不是"有了再说"的事——这是 Phase D 的验收门槛。

  • 系统提示词注入:在 CLAUDE.md / AGENTS.md 中告知猫猫"你有记忆组件,该这样用"
    • 类似 Claude 的 memory 机制:系统提示词里告诉 agent 它有 memory、该怎么查
    • 包含检索策略表(什么场景用什么模式)
  • Recall Skill:写一个 recall skill(或融入现有 skill),让猫猫开工前自动检索
    • 取当前任务标题 / feature_id / thread topic
    • 先查 docs + memory,不够再查 threads-summary
    • 只注入 5-10 条最相关的 snippet 到上下文
  • feat-lifecycle 集成:立项/状态变更/关闭时自动 incrementalUpdate
  • SOP 更新:在 docs/SOP.md 中加入"开工前先 recall"的步骤

Phase E: Thread 内容索引 — 从"空壳"到"300 thread 可搜"

触发:Phase D runtime 测试暴露核心 gap——thread 对话内容不可搜。 Maine Coon(GPT-5.4) 愿景守护结论:Phase D AC 文档闭合度 90%,runtime 验收完成度 60%。 operator核心需求:"把我们的整个 thread 检索归一到记忆组件" 三层真相源设计(两猫共识):threadMemory.summary + sealed transcript events.jsonl + live MessageStore

当前 Gap(Phase D 测试 thread 暴露)

优先级 Gap 根因
P1 scope=threads/sessions 返回 0 结果 session digest 路径解析问题(类似 docsRoot CWD bug,PR #524 修了 docs 但 transcriptDataDir 可能仍有问题)
P1 300 个 thread 对话内容不可搜 thread 消息在 Redis(TTL=0 永久),但从未被索引到 evidence.sqlite
P2 reflect 返回空 ReflectionService 仍是空壳 async () => ''
P2 lesson/pitfall 召回偏 redis pitfall 命中无关 F048

E-1. Thread Summary Layer(Step 1)

目标:让 thread 在统一入口里"有摘要层可命中"。不是"thread 内容可搜已完成"。

  • 新增 kind='thread'(区别于 session = sealed session digest)
  • anchor = thread-{threadId}
  • title = thread.title
  • summary = 从 messageStore 读消息内容拼接 turn-by-turn 文本(KD-32/33:不靠 threadMemory.summary,不导出 markdown)
    • [speaker] content 格式,截取合理长度
    • 340 个 thread 全部入库(不再跳过无 summary 的)
  • keywords = [参与者 catId, backlogItemId, feature_ids]
  • dirty-thread + 30s debounce flush 基础设施
    • messageStore.append() 后标记 threadId dirty
    • 每 30 秒批量刷新 dirty threads 到 SQLite
    • 启动时全量 catch-up

E-2. Thread Raw Passage Layer(Step 2)

目标:让"Redis 坑在第 47 条消息"也能命中。这才是真正兑现"thread 内容可搜"。

  • 启用 evidence_passages 表(Schema V3)
  • 数据源:sealed transcript events.jsonl chat 文本 + live MessageStore 未封存增量
  • 切 passage 策略:按 turn/消息,每条消息一个 passage
  • depth=raw 时搜 passages,聚合回 thread-{threadId}
  • FTS5 索引扩展到 passages 表

E-3. 辅修

  • reflect 返回显式降级消息(不再返回空字符串)
  • lesson/pitfall 召回质量改进(keywords 补充 + FTS5 索引调优)
  • session digest 路径修复(确认 transcriptDataDir 解析正确)

Phase F: 多项目记忆 — 猫猫出征新家/接手老项目(F-1/F-2/F-3 ✅,F-4 ✅)

触发:operator问"猫出征到 dare/studio-flow 怎么办?记忆系统怎么办?" 核心决策:KD-35(两种策略)+ KD-36(遗留项目 frontmatter formatter)

F-1. 新项目策略:家规引导建标准 docs 体系

  • 猫带着 Skills 出征 → Skills 里 feat-lifecycle 引导建 docs/features/docs/decisions/ 等标准目录
  • IndexBuilder 的 KIND_DIRS 直接适配(13 个标准目录)
  • 需要:project-init skill 或脚本,猫到新项目时自动创建 docs 骨架

F-2. 遗留项目策略:通用递归扫描

  • discoverFiles() 增加 fallback:先扫 KIND_DIRS 标准目录,再递归扫剩余 .md
  • Kind 从路径推断(KIND_DIRS 表)→ frontmatter → 默认 plan
  • 不要求遗留项目有特定目录结构——有 .md 就索引

F-3. 遗留项目 Frontmatter Formatter

  • 扫描遗留项目的 .md 文件,自动补充 frontmatter metadata
  • 推断 doc_kind(从路径/内容关键词)、topics(从标题/内容提取)、anchor(从文件名)
  • 可选人工确认或全自动
  • 提升 kind 推断准确度和检索质量

F-4. 全局知识层(跟猫走) ✅ PR #886

  • 编译 global_knowledge.sqlite:从 Skills/家规/MEMORY.md/lessons-learned 编译只读索引
  • 放在猫猫 home 目录(~/.cat-cafe/global_knowledge.sqlite),不在项目里
  • KnowledgeResolver 联邦检索:search 时同时查 project + global 两个 SQLite,RRF 融合
  • 猫出征新项目 → 带走全局层,在新项目搜"Redis 坑"能命中 cat-cafe 的教训

当前可用度(无需 Phase F 即可用):

  • 新项目 docs 自动索引 ✅(如果按标准目录建)
  • thread 消息自动入库 ✅(append listener)
  • Skills/提示词跟猫走 ✅
  • "开工前先搜"的习惯跟猫走 ✅
  • SessionBootstrap auto-recall ✅

Phase F 后新增

  • 遗留项目任意 .md 可索引
  • 跨项目检索(在 dare 里搜 cat-cafe 的教训)
  • frontmatter 自动补全工具

Phase G: Abstractive Summary + Durable Memory Lifecycle(✅ 基础设施 + 运行时验收已合入)

触发:operator发起 Lossless Claw(LCM)调研,三猫(opus + opencode + gpt52)协作对比 LC 与 session chain / F102,收敛出可学习的改进点。 核心学习:从 LC 学到的不是 DAG 数据结构,是"压缩不等于丢弃,摘要必须可穿透"的理念。 三猫共识:LC 最对标的是 F065 Session Continuity,不是 F102。但 F065→F102 的写路径(pre-seal → durable knowledge)是核心改进点。

G-1. Thread-level Abstractive Digest + Durable Candidate Extraction(KD-37/38/41)

operator关键修正(KD-41):摘要单元是 thread(不是 session),触发方式是定时任务(不是 seal)。理由:(a) session strategy 可配置(compress/handoff/hybrid),不一定有 seal;(b) thread 是所有猫共享的对话空间,对每只猫的 session 分别摘要 = 同一段对话重复摘要;(c) 定时任务比事件驱动更稳健。

Maine Coon(GPT-5.4) 关键收紧:digest 和 candidate extraction 合并成一次 Opus 调用。输出 schema、candidate 硬边界、skip path 仍然适用。

一次调用同时产出两样东西:

  • Abstractive digest:回答"这个 thread 最近讨论了什么、决定了什么、风险和下一步"
  • Durable candidates[]:回答"这里面哪些内容值得升格为长期知识"

模型:Opus 4.6(通过 F062 provider-profiles 反代 API,零新增基础设施)——operator明确指示不用 Haiku。 fail-open:API 调用失败不影响现有拼接摘要,下次定时任务重试。

输入:thread 消息增量(lastSummarizedMessageId 水位线之后的新消息),不是某只猫的 session transcript。所有猫在同一个 thread 的对话只摘要一次

Output Schema(Maine Coon定义,thread 化适配)

{
  "digest": {
    "title": "本 thread 最近讨论了什么",
    "summary": "200-400 字,讲清讨论/决定/风险/下一步"
  },
  "candidates": [
    {
      "kind": "decision | lesson | method",
      "title": "为什么 thread 摘要不能实时调 Opus",
      "claim": "运行时 dirty thread 只做拼接,Opus 摘要走异步批处理",
      "why_durable": "这是后续实现和运维都要遵守的规则",
      "evidence": [
        {"threadId": "thread_xxx", "messageId": "msg_xxx", "span": "原文摘录"}
      ],
      "relatedAnchors": ["F102", "F065"],
      "confidence": "explicit | inferred"
    }
  ]
}

注意 evidence 改为 threadId + messageId(不是 sessionId),因为摘要单元是 thread。

Candidate 硬边界(Maine Coon红线,不变)

  • 只允许 3 类:decision / lesson / method——硬编码枚举
  • 必须带 evidence(threadId + messageId + 原文 span)和 relatedAnchors
  • confidence: "explicit" → 默认 normalized"inferred" → 默认 needs_review一律不直接 approved
  • explicit 判定收窄(Maine Coon R2):仅 (a) operator/owner 明确拍板;(b) 有明确共识语句可直接引用;(c) 已对应到 merged doc/code 事实。"说得像决定"不算 explicit
  • 禁止提取:未定方案/brainstorm、临时 TODO/WIP、碎片上下文、"模型总结性发挥"
  • Prompt 定位是"抽取器"不是"总结器"

Eligibility Rule(Maine Coon R3 统一,G-1 和 G-2 共用同一套规则)

eligible =
  quietWindow >= 10min
  AND (
    pendingMessages >= 20
    OR pendingTokens >= 1500
    OR decision/code/error-fix markers hit    ← 高价值信号 bypass 消息数
  )
  AND (
    cooldown >= 2h
    OR high-signal bypass                     ← 重要决策不等 2h
  )

纯闲聊/路由 thread(无高价值信号且消息数/token 均未达标)继续只用拼接 summary。

API 接入(金渐层确认)

// 复用 F062 provider-profiles,零新增基础设施
const profile = await resolveAnthropicRuntimeProfile(projectRoot);
// profile.baseUrl + profile.apiKey → 标准 Anthropic Messages API
// model: 'claude-opus-4-6', max_tokens: 1024

Rate limit 5000 次/天(operator确认),日常消耗远低于限额。

G-2. LSM-style Compaction 摘要架构(KD-39/41/42,三猫+operator收敛版)

operator类比:"参考存储是怎么压缩内存的"——LSM-tree(Log-Structured Merge Tree)的分层 compaction 和我们的问题同构。 三猫独立收敛:三猫都独立想到了 LSM compaction 类比(L0 实时拼接 / L1 定时摘要 / L2 deferred 凝结)。 Maine Coon提出分段模型(segment-based):每次产出独立 summary segment 而不是覆写单一摘要——解决漂移/不可审计/错误放大。 架构决策(KD-42 修正):采纳Maine Coon的分段 ledger 设计——evidence_docs.summary 作为 read model,summary_segments 作为 append-only provenance。成本几乎不变(多一张表一次 INSERT),但解决漂移/不可审计/错误放大。L2 凝结仍 deferred。

触发 产物 状态
L0 实时拼接 dirty-thread flush(30s debounce,<100ms) summaryType=concat,现有拼接 已有,不改
L1 定时摘要 定时任务 + 三条件门槛 L1 summary segment + candidates[] 新增(MVP)
L2 分段凝结 (deferred)L1 segment 积累到一定数量/体积 L2 rollup segment(supersedes 多个 L1) 预留,但 segment ledger 让升级成本很低

双写路径(read model + append-only ledger):

evidence_docs.summary     ← read model(搜索/bootstrap 直接读这个,始终是"当前最优摘要")
summary_segments           ← append-only ledger(每次 L1/L2 摘要都插入一条,永不删除)

dirty thread 结合方式(dirty 管 L0,定时任务管 L1,两层解耦):

消息写入 → markThreadDirty(threadId)     ← 已有,不改
         → 每 30s flushDirtyThreads()   ← 已有(L0 拼接)
              → 新增:pendingMessageCount++ (为 L1 调度提供信号)

定时任务(每 30 分钟,per-tick 最多处理 5 个 thread)
  → 读 pendingMessageCount
  → 统一 eligibility rule 判断(G-1 共用):
     quietWindow >= 10min
     AND (pending >= 20 OR tokens >= 1500 OR high-signal)
     AND (cooldown >= 2h OR high-signal bypass)
  → 输入:上次 evidence_docs.summary + 水位线后的增量消息
  → 输出:1..N 个 topic segments(Opus 按话题切分) + candidates
  → (1) INSERT 1..N summary_segments(append-only,永不删除)
  → (2) UPDATE evidence_docs.summary(从所有最新 segments 合成 read model)
  → (3) 更新水位线 + 重置 pendingCount

不依赖 session chain / seal / session strategy——只看 thread 消息增量。

存量 backfill:首次启动时对历史 thread 做一轮全量,串行跑,每次间隔 2s。

Topic Segment 切分规则(operator提出 + Maine Coon约束,KD-43)

一次 delta batch 可产出 1..N 个连续 topic segments(Opus 按话题边界切分),而不是强制一段。

硬约束(Maine Coon R4,模型在笼子里工作):

  • segments 必须连续、按顺序、互不重叠
  • segments 必须完整覆盖当前 batch(不能跳消息)
  • fromMessageId 不能早于本次水位线,toMessageId 不能晚于 batch 末尾
  • 最多切 3 段,避免过碎
  • 最小切分门槛:batch < 600 tokens 或 < 8 条消息时,强制 1 段(Maine Coon R4b)
  • 不确定时退化成 1 段(宁可混话题也不乱切)
  • 跨时间窗的话题连续性:只做 link(relatedSegmentIds),不做 merge/回改旧 segment
  • 真正的跨时间窗话题合并留给 L2 rollup

summary_segments 表(Maine Coon定义 + R4 topic 字段,append-only ledger)

{
  id: string;                        // segment UUID
  threadId: string;
  level: 1 | 2;                     // L1 = delta, L2 = rollup
  fromMessageId: string;            // 本段覆盖的消息范围起点
  toMessageId: string;              // 本段覆盖的消息范围终点
  messageCount: number;
  summary: string;                  // 本段的摘要文本
  topicKey: string;                 // 稳定话题 key(canonical slug,L2 按此 rollup/聚类)
  topicLabel: string;               // 给人看的话题标题(可变,不用于关联)
  boundaryReason: string;           // 为什么在这里切分(可审计)
  boundaryConfidence: 'high' | 'medium' | 'low';
  relatedSegmentIds?: string[];     // 与历史哪些 segment 主题连续(link,不 merge)
  candidates?: DurableCandidate[];  // 本段提取的候选知识
  supersedesSegmentIds?: string[];  // L2 才有:supersede 哪些 L1 segment
  modelId: string;                  // e.g., 'claude-opus-4-6'
  promptVersion: string;            // e.g., 'g2-thread-abstract-v1'
  generatedAt: string;              // ISO timestamp
}

summary_state 水位线(Maine Coon R3:补 token + signal,不然调度器实现不了 eligibility rule):

// evidence_docs 扩展或独立表
{
  lastSummarizedMessageId: string;   // 上次摘要覆盖到的消息 ID
  pendingMessageCount: number;       // dirty flush 累积的增量消息数
  pendingTokenCount: number;         // dirty flush 累积的增量 token 估算
  pendingSignalFlags: number;        // bitflags: decision=1, code=2, error-fix=4
  summaryType: 'concat' | 'abstractive';
  lastAbstractiveAt?: string;        // 上次 L1 摘要时间
  abstractiveTokenCount?: number;    // 当前摘要长度(监控漂移信号)
}

发现摘要偏了 → 从 summary_segments 审计哪一段出问题 → 从原始消息 rebuild 该段以后的所有摘要(WAL 重放)。

三个可配置常量(Maine Coon nit:不要散成裸字面量):

const SUMMARY_CONFIG = {
  pendingMessageThreshold: 20,   // 消息数门槛
  pendingTokenThreshold: 1500,   // token 门槛(覆盖"消息少但重"的情况)
  cooldownHours: 2,              // 距上次摘要最小间隔
  quietWindowMinutes: 10,        // 安静窗口
  perTickBudget: 5,              // 每次定时任务最多处理几个 thread
  backfillIntervalMs: 2000,      // 存量 backfill 间隔
  driftAlertTokenThreshold: 800, // Phase 2 升级监控:连续 3 次 > 此值 = 漂移信号
};

Phase 2 预留:L2 Rollup + 多段读模型(Maine Coon R3 可观测升级触发器)

注意:Phase 1 MVP 已经有 summary_segments append-only ledger(每次 L1 都 INSERT)。Phase 2 新增的不是"分段"(已有),而是:(a) L2 rollup 凝结;(b) bootstrap 从多段拼装;(c) 坏段隔离能力。

升级触发条件——任一可观测条件命中

  1. 漂移信号:某 thread 的 abstractiveTokenCount 连续 3 次 L1 摘要后上升且 > 800 tokens
  2. 质量信号:canary thread 的摘要人工抽检连续 2 次失败(计划:每月抽 3 个活跃 thread)
  3. 事故信号:出现 1 次明确的"摘要漂移导致错误 recall"的事故(记入 lessons-learned)

升级后新增能力:

  • L2 rollup segment:多个 L1 segment 凝结为一个 L2(level=2, supersedes L1 segments)
  • Bootstrap 改为:最新 L2 + 若干最近 L1 + raw tail(而不是单一 evidence_docs.summary)
  • evidence_docs.summary 仍由定时任务从最新 L2 + 最近 L1 合成(read model 不变)
  • 坏段可丢弃不影响其他段(因为 L1 segment 是独立的)

segment ledger 在 Phase 1 已存在,Phase 2 升级只需改读路径和凝结逻辑

G-3. ThreadMemory 增强(KD-40)

当前 buildThreadMemory 是 append-to-head + trim-from-tail 的滚动文本——老信息会被彻底删除。

升级:当 thread 有 summaryType=abstractive 的摘要时,bootstrap 直接用 abstractive summary 而不是拼接文本。近期消息仍然带原文(和现在一样),远期部分由 abstractive summary 覆盖。

定时任务每次跑的时候,输入是"上次 abstractive summary + 增量消息" → 产出新的合并摘要。这本身就是渐进凝结(LSM minor compaction),不需要额外的凝结步骤。

G-4. 搜索结果穿透路标

search_evidence 命中 kind=sessionkind=thread 结果时,返回值增加 drillDown 字段:

interface EvidenceItemWithDrillDown extends EvidenceItem {
  drillDown?: {
    tool: string;           // e.g., 'read_session_events'
    params: Record<string, string>;  // e.g., { sessionId, view: 'handoff' }
    hint: string;           // e.g., '可用此工具查看完整对话'
  };
}

让猫从"搜到但不知怎么看详情"变成"搜到→一键下钻"。

G-5. Conversation Identity 统一语义边界(ADR 待立项)

五个概念(Thread / Session Chain / Active Slot / Connector Binding / CLI Resume)各自定义清晰,但缺少"它们如何协同"的统一叙事。需要一份 ADR 把端到端流转路径画清楚:

用户消息 → connector binding 找 thread → thread 找猫的 active slot
  → active slot 找 session → 事件写入 session → seal → digest → F102 索引

此项不在 F102 内实现,作为独立 ADR 立项,link 回 F102 + F065 + F088。

Embedding 状态收敛(2026-04-01 更新)

以下三条最初是在 runtime 验收里暴露出来的 gap。到 2026-04-01 为止,它们对我们当前 runtime已经不是未解决问题,但保留在此作为历史追踪和开源默认值说明。

Gap-1(已闭环): 我们的 runtime embedding 已开启

当前真相

  • 我们仓库根 .env 已设 EMBED_MODE=on
  • index.ts 会把 process.env.EMBED_MODE 透传进 memory factory
  • ConfigRegistryf102.embedMode 也会反映真实 env 值

结论

  • 我们当前 runtime,Phase C 的 embedding / vector rerank 基础设施不是空转,Gap-1 已闭环
  • 开源默认,不传 env 仍然是 off,这是有意保留的保守默认值,不是我们 runtime 的现状

Gap-2(已收敛): shadow 不再是待补日志的问题,而是已废弃路径

当前真相

  • 运行时检索只有 mode === 'on' 才会真正启用 embedding rerank
  • shadow 没有继续作为运营中的 A/B 模式推进
  • 我们已经收敛成 off → on,而不是继续投资 shadow logging

结论

  • “shadow 跑了白跑,需要补日志”这条不再是 active gap
  • 当前策略是:保留类型/配置兼容,但产品与 runtime 路线按 off → on

Gap-3(已随 Gap-1 收敛): Stories/Lessons 中英混搜

结论

  • 对我们当前 runtime,随着 EMBED_MODE=on,中英混搜不再是未处理 gap
  • 对开源默认或未开 embedding 的环境,这个能力仍会退化回纯 lexical,这是配置差异,不是我们 runtime 的未完成项
  • 临时补 frontmatter topics 能治标但不可持续——每个新文档都要手动加。

建议实现顺序

  1. 先开 EMBED_MODE=on ✅ 已完成(PR #618 auto-derive from EMBED_ENABLED)
  2. 验证 Recall 提升 ✅ 已验证:hybrid 搜 "cat naming origin story" 命中花名册
  3. 如果要保留 shadow 模式 ✅ 已废弃 shadow(直接 off → on)

Gap-4: semantic/hybrid 模式未正确实现(Phase C 缺口)

现状SqliteEvidenceStore.search() 不读 mode 参数。三种模式走同一条路径:

  • BM25 召回 → embedding rerank(如果可用)

问题

  • mode=semantic 应该跳过 BM25,纯向量 NN 搜索。当前等同 hybrid。
  • mode=hybrid 应该 BM25 召回 + 向量 NN 召回 → 合并去重 → RRF 融合。当前只做 rerank。
  • mode=lexical 应该纯 BM25。当前行为恰好是对的(rerank 在 embedDeps=null 时跳过)。

影响:搜 "why are cats named Ragdoll Maine Coon Siamese" 时,BM25 召回不到猫名故事, embedding 无法补救(rerank 只重排已召回的,不发现新文档)。

正确实现

// mode=lexical: 纯 BM25(现有)
// mode=semantic: 纯向量 NN → evidence_vectors nearest-neighbor
// mode=hybrid: BM25 召回一批 + 向量 NN 召回一批 → 合并去重 → RRF 融合排序

修改文件SqliteEvidenceStore.tssearch() 方法

KD-44:三种检索模式各有独立实现路径,semantic 不依赖 BM25 召回。

Phase H: 知识涌现 Feed — Durable Candidate → Hub 可视化 → 人猫协同审核(✅ H-1/H-2/H-3/H-8 merged)

触发:operator问"Durable Candidate 怎么审核?需要 UX"。 核心理念:不是"审核 marker",而是"知识涌现 feed"——像 GitHub Notifications 一样的集中入口。

H-1. 知识涌现 Feed(Hub 前端页面)

Hub 里新增一个"知识动态"页面,集中展示所有从 thread 对话中涌现的 decision/lesson/method:

📋 本周涌现的知识 (5 条)

🔵 [decision] 摘要单元是 thread 不是 session
   来源:f102 学习 lossless claw thread · 3 只猫共识 · operator拍板
   置信度:explicit → 已自动写入 docs/decisions/ADR-020.md ✓
   [撤回] [编辑]

🟡 [lesson] embedding 不能偷懒用 in-process CPU
   来源:operator"你这实现我拒绝" · LL-034
   置信度:explicit → 已自动写入 docs/public-lessons.md ✓
   [撤回] [编辑]

🟢 [method] 让模型说人话程序加格式
   来源:operator验证有效
   置信度:inferred → 待确认
   [写入 Skills] [写入 Lessons] [忽略]

核心 UX

  • **explicit(operator拍板/明确共识)**→ 自动沉淀到 docs/,Feed 里标 ✓,operator只需"撤回"错的
  • **inferred(模型推断)**→ 展示在 Feed 等确认,operator选去向或忽略
  • 不是每条都审——默认信任 explicit,异常才介入

H-2. 自然语言联动(Workspace Navigator 集成)

operator说"帮我看看这周有什么新知识" → 猫猫用 workspace-navigator 打开知识 Feed 页面。 operator说"把那条 lesson 写入 Skills" → 猫猫调 IMaterializationService 执行。

H-3. 后端:Candidate → MarkerQueue → Materialization 全链路

Opus 摘要提取 [decision]/[lesson]/[method]
  ↓
parseNaturalLanguageOutput() → DurableCandidate
  ↓
MarkerQueue.submit() → status: 'captured'
  ↓
自动 normalize → status: 'normalized'
  ↓
explicit → auto-approve → materialize → docs/*.md → reindex
inferred → Hub Feed 展示 → operator确认/忽略
  ↓
approved → IMaterializationService.materialize()
  → git commit → trigger reindex → evidence.sqlite 更新

H-4. 用户角色(跨项目终态)

角色 体验
项目 Owner(operator) Feed 里看涌现知识 · 一键确认/撤回 · 自然语言操作
猫猫团队 auto-recall 自动引用已沉淀知识 · 不重蹈覆辙
新人/新猫 Onboarding 自动化 · "这个项目的核心决策是什么?" → 搜到 ADR/LL
跨项目的猫 全局层 global_knowledge.sqlite 带着走 · 在新项目搜到旧教训

H-5. 头脑风暴收敛(Ragdoll + Maine Coon,2026-03-22)

产品定义:Knowledge Emergence Workspace — 让知识从对话里自然浮现 → 被猫整理 → 被人轻确认 → 反哺团队搜索与行动。 不是:静态 wiki / marker 审核后台 / docs 生成器。

4 条产品原则

# 原则 含义
P1 单入口 所有待确认/已沉淀/高频命中知识,都能从 Hub Feed 到达
P2 先建议后自动 除 explicit 高置信度外,系统先给建议,不直接替人拍板
P3 所有自动动作可撤回 自动沉淀必须可追溯、可编辑、可撤回
P4 关系服务于行动 edges 先做上下文增强(卡片内联),不先做大图展示

Feed 按"动作价值"分组(Maine Coon提出):

  • 需要你确认 — inferred candidates、冲突更新、低置信高影响
  • 已自动沉淀 — explicit decision/lesson/method,显示来源 + 可撤回
  • 高频命中 — 正在帮助团队的知识("哪些知识真的活着")
  • 值得升级的草稿 — 某 lesson 被 3+ thread 提到 → 建议升级为 method/ADR

每条卡片信息:标题 · kind · 2-3 句摘要 · 来源 thread/feat · 置信度 · 为什么现在出现 · 建议动作(Approve / Edit / Dismiss)

operator隐性需求(两猫挖掘):

  1. "为什么现在告诉我?" — 每条要说明触发原因(Maine Coon)
  2. "我想看变化不想重看全文" — 同一知识展示 delta(Maine Coon)
  3. 重要性分级:阻塞型/常用型/背景型(Maine Coon)
  4. "我不想二次录入" — 系统先生成候选,人只做 approve/edit(Maine Coon)
  5. 知识涟漪 — 改了 decision → edges 自动提示关联文档需要更新(Ragdoll)
  6. 知识成长可视化 — 像 GitHub contribution graph 看积累(Ragdoll)
  7. 知识对话 — "我们为什么放弃 Hindsight?" → 综合叙事回答(Ragdoll,IReflectionService 终态)

猫猫主动提议模式(两猫一致):

  • 对话中温和提醒:"这条像一个 decision,要沉淀吗?"
  • Feed 里正式处理:结构化 candidate + approve/dismiss

关系可视化(两猫一致):

  • 卡片内联最有用的 3 类:来源 threads · 引用的 decision/lesson · 影响的 feat/docs
  • 详情页里才展开关系图,首页不做大图

H-6. Workspace 集成方案(operator确认 2026-03-23)

入口位置:Workspace 面板模式切换器(不加 Tab、不做 Hub 侧边栏):

Workspace 面板顶部:
  [<> 开发]  [✨ 知识 ②]     ← 两个 pill 按钮切换模式

开发模式 = 现有 FILES/CHANGES/GIT/TERM/PREVIEW
知识模式 = 知识涌现 Feed(待确认/已沉淀/高频/升级)
  • 设计稿 1:designs/F102-knowledge-emergence-feed.pen — Feed 页面全貌(Header + 4 Tab + 两种卡片 + 统计栏 + 自然语言输入栏)
  • 设计稿 2:designs/F102-knowledge-emergence-workspace-integration.pen — Workspace Before/After 对比([开发]/[知识] 模式切换器)
  • SVG/图标资产:Lucide icon set(sparkles/check/file-text/lightbulb/bell/search/send)— 实现前从 .pen 导出
  • 任意页面/任意 thread 都能联动打开知识 Feed(和 Workspace 其他功能一样)
  • operator说"帮我看看知识"→ 猫猫用 workspace-navigator 切到知识模式

H-7. 实现前必做清单(operator铁律:设计 → 代码一致性)

说明
SVG/图标资产 提前从 .pen 导出所有用到的图标,不要到写代码时现画
设计对照 代码实现后必须截图和 .pen 设计稿逐像素对比
风格一致 复用现有 Hub 配色/字体/圆角/间距,不引入新风格变量
任意页面联动 不管在哪个 thread/页面,都能通过自然语言或按钮打开知识 Feed
配套 Skill 猫猫得知道有 Knowledge Feed 能力 → 写 skill 或更新 CLAUDE.md/AGENTS.md

H-8. 配套 Skill(让猫猫知道有这个能力)

猫猫如果不知道 Knowledge Feed 存在,就不会主动提议沉淀知识、不会帮operator打开 Feed。已完成:

  1. CLAUDE.md/AGENTS.md 更新 — 在记忆系统段落加"知识涌现 Feed"指引 + 猫猫主动提醒职责
  2. workspace-navigator 扩展POST /api/workspace/navigate 支持 action: 'knowledge-feed',前端 chatStore.setWorkspaceMode 联动
  3. 猫猫主动提议的 prompt guidance — CLAUDE.md/AGENTS.md 写明"对话中发现有价值的 decision/lesson 时,主动提醒operator"

待做:IMaterializationService(approved → docs/*.md 自动写入) · Siamese精细视觉设计

Phase I: Message-Level Permanence Repair — JSONL-backed passage reconciliation ✅

触发:金渐层(operator)深度使用 search_evidence 暴露核心架构空洞——Session JSONL 永久保存了所有消息,但搜索链路完全绕过它。Passage 索引数据源是 Redis(7 天 TTL 默认),rebuild 后过期消息的 passage 会丢失。 Ragdoll + Maine Coon(GPT-5.4) 讨论收敛(2026-03-30):共识优先级 P1 JSONL backfill > P2 时间过滤 > P3 配置透明化。命名 "message-level permanence repair"——本质是永久性修复,不是搜索增强。

当前架构空洞

L0 热状态:Redis messages(默认 7 天 TTL)
L1 永久原文:Session transcript JSONL(永不删除)
L2 检索投影:evidence_passages / passage_fts(SQLite)

问题:L2 从 L0 构建,不从 L1 构建。
      → rebuild 时 L0 过期的消息不会进入 L2
      → L1 永久保存了一切但搜索链路绕过它
      → "永久记忆" 对 message-level recall 是半假的

KD-32 修正:原决策假设"真相源在 Redis(TTL=0 永久)",但代码默认 DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60(7 天),且 .env 未配置覆盖值。Passage 索引依赖 Redis 作为唯一数据源 = 依赖一个 7 天 TTL 的临时层。

I-1. Passage Reconciliation Pipeline(P1 — 核心修复)

改造 IndexBuilder.indexPassages() 的数据源策略:

当前:messageListFn(threadId) → Redis only → delete-all + insert

改为:
  messageListFn(threadId) → Redis(热路径)
  if Redis 返回消息数 < SQLite 已有 passage 数(说明有过期):
    → TranscriptReader.readEvents(threadId) → JSONL 补全
  rebuild 时 passage 只增不减(incremental merge,不 delete-all-then-insert)

约束

  • 热路径不变——新消息仍从 Redis 写 passage(<5ms 延迟)
  • JSONL fallback 只在 rebuild/reconcile 时触发(不影响实时性能)
  • Session → thread 映射天然存在(JSONL 目录结构 threads/<threadId>/<catId>/sessions/

I-2. search_evidence 时间范围过滤(P2)

SearchOptionsdateFrom/dateTo 参数:

  • evidence_docs:用 updatedAt 过滤
  • evidence_passages:用 created_at 过滤
  • 必须在 I-1 之后做——否则时间过滤会放大"旧消息明明在 transcript 里却搜不到"的体验落差(Maine Coon风险分析)

I-3. 消息真相源分层显式化(P3)

  • 代码内明确 L0/L1/L2 三层关系(注释 + 架构文档)
  • env-registry.tsMESSAGE_TTL_SECONDS 描述补充默认 7 天行为 + TTL=0 含义
  • 考虑 depth=raw 搜索结果标注 source: 'redis' | 'transcript'(便于调试)

Phase J: Memory Hub — 记忆系统的人类产品面 ✅

触发:operator发现社区用户用不起来记忆系统——"藏得太死了"。F088/F137/定时任务都有前端页面,记忆系统却完全隐形。 operator核心洞察:"你们在收记忆的时候,我要是能偷偷看一眼你们到底搜到了什么记忆,这种体验最好。" Maine Coon(GPT-5.4) 评审:Workspace 方案是绕路——Memory 已是一级产品能力,不能继续伪装成侧栏模式。主入口必须是独立页面。 收敛(2026-03-30):Ragdoll+Maine Coon+operator三方共识——两面入口 + Recall Feed。

产品定位:Memory 不是开发者工具,是人猫共用的知识中枢。人能主动探索,也能在猫用记忆时被动看到过程。

J-1. 主入口:/memory 独立路由页面(左侧 sidebar 底部按钮)

位置:左侧 sidebar 底部按钮区(ThreadSidebar),排列顺序:[猫猫新手训练营] [Memory] [IM Hub]。SVG 图标,不用 emoji。(operator 2026-03-31 拍板)

/memory
├── 搜索栏(人类直接可用,不需要让猫帮忙搜)
├── Tab 1: 涌现 Feed(现有 Knowledge Feed 迁移,从 Workspace 知识模式升级而来)
├── Tab 2: 知识检索(evidence search + passage drill-down + 来源标注)
├── Tab 3: 索引状态(docs/threads/passages 数量、rebuild 时间、TTL、embedding mode)
└── [Phase F] 项目切换器(当前项目 / 全局记忆 / 其他项目)

设计原则

  • /signals 同等级的独立路由(不是 Hub 模态弹窗里的 tab)
  • 搜索体验对标 evidence MCP 工具的能力——mode(lexical/semantic/hybrid)、scope、depth 都可调
  • 索引状态让operator一眼看到"记忆系统是不是健康的"

设计约束(Maine Coon V2 review + operator 2026-03-31 拍板)

  • ?from=threadId 返回链路:和 /signals 一样,/memory?from=<threadId> 支持 "Back to Chat" 稳定返回来源对话
  • 移动端入口:sidebar 底部按钮在移动端随 sidebar collapse/expand 自然隐藏/展示,无需额外策略
  • 图标:SVG 图标(不用 emoji),风格与训练营/IM Hub 按钮一致

J-2. 上下文入口:Workspace Recall Feed(对话中联动)

operator"偷偷看一眼"的核心体验:

对话区(左)                    |  Recall 面板(右)
                                |
[operator] 问题...                |  🔍 猫正在搜索...
                                |  query: "放弃 Hindsight 决策"
[Ragdoll] 正在思考...             |  mode: hybrid | scope: docs
                                |
                                |  📋 命中 3 条:
                                |  ① ADR-005 (0.92) "本地优先"
                                |  ② F102 KD-1 (0.87) "三猫全票"
                                |  ③ LL-012 (0.71) "实在难用"
                                |
[Ragdoll] 根据 ADR-005...        |  ← 猫引用了 ①,高亮

技术路径:猫调 search_evidence → invocation 层拦截 tool_use 事件 → 向前端推送 recall event(query + results + scores)→ Workspace Recall 面板实时渲染。猫不需要做额外事情——照常搜,前端自动展示。

J-3. 快捷入口:Hub "记忆" tab(监控与治理组)

Hub 模态弹窗 Group 3(监控与治理)加一个轻量 tab:

  • 索引状态速览(docs/threads/passages 数量 + 最近 rebuild 时间)
  • "打开 Memory" 一键跳转 /memory
  • 不做完整功能——Hub 已有 12 个 tab,不宜再塞重内容

J-4. Knowledge Feed 归属调整

Knowledge Feed(Phase H)从 Workspace "知识模式"迁移到 /memory Tab 1。Workspace 保留上下文级的 Recall Feed(J-2),不再承载完整 Knowledge Feed。

产品面命名

  • Workspace 原 [知识] 模式 → 改名为 [记忆][Recall]
  • Hub 里 → "记忆状态"
  • 独立页面 → /memory(Memory Hub)

Phase D 完成后的预期效果

operator指示:做完后要讲清楚"operator日常使用感受到什么优化"和"猫猫自己感受到什么优化"。跑一段时间才知道做得好不好。

operator视角(日常使用中的变化)

之前

  • operator问"我们之前怎么决定的?"→ 猫猫 grep docs/ → 翻一堆文件 → 可能漏掉关键讨论
  • operator问"上次那个 Redis 坑是怎么回事?"→ 猫猫不记得在哪个 thread → grep 关键词 → 找到 threadId → 拉全量消息 → 人肉翻
  • operator让猫做新 feature → 猫从零开始,不知道历史上类似功能踩过什么坑
  • 改了一个 ADR → 没人提醒依赖这个 ADR 的 3 个 feature docs 需要同步更新

之后

  • operator问"我们之前怎么决定的?"→ 猫猫自动 search_evidence("memory adapter 决策", scope=docs) → 直接返回 ADR-005 + F102 spec + 相关讨论摘要,带 score 排序
  • operator问"上次那个 Redis 坑?"→ 猫猫 search_evidence("Redis 坑", scope=all) → 命中 LL-001 lesson + session digest → 一步到位,不用先找 threadId
  • operator让猫做新 feature → 开工前自动 recall(系统提示词 + skill 驱动)→ 猫带着历史上下文开始工作,不重蹈覆辙
  • 改了 ADR → incrementalUpdate 自动查 edges → 提醒"F042/F088 依赖这个 ADR,需要 review"

operator最直观的感受:猫猫回答问题时不再说"让我搜搜看"然后翻半天。它们开工时自带上下文,像一个有记忆的同事而不是每次都从零开始的实习生。

猫猫视角(自身工作流的变化)

之前

  • 4 条平行检索链路(evidence/session/thread/grep),不知道该用哪个
  • search_evidence 搜空库(evidence.sqlite 从没被创建)
  • 想找历史讨论 → grep → 噪音大 → 经常找不到关键信息
  • 接手不熟悉的 feature → 读 spec → 漏掉相关讨论和教训

之后

  • 一个入口search_evidence 覆盖 docs + memory + threads + sessions
  • 开工前自动 recall:系统提示词告诉猫"你有记忆组件",skill 引导猫开工前先搜
  • 搜到即用:FTS5 + 向量 rerank,中英混排,命中结果带 source_path + score
  • 知识不过期:edges 自动维护,文档变更自动标依赖文档 needs_review
  • 不重蹈覆辙:lessons-learned、教训、踩坑经验都在索引里,recall 时自动浮现

可量化的验收指标

指标 目标
启动到可检索 ≤60 秒
Canary query 命中率 3/3 固定 query 稳定返回预期 anchor
增量 freshness 改 doc 后 ≤30 秒可检索新内容
Embedding fail-open 检索成功率不下降
MCP 工具数量 从 4 条平行链路 → 1 个入口 + 8 个 drill-down
猫猫检索步骤 从"grep → threadId → grab → 人肉翻"→ "search_evidence 一步"

Acceptance Criteria

Phase A(6 接口 + SQLite 基座 + 解耦)

  • AC-A1: 六个接口定义(IIndexBuilder + IEvidenceStore + IMarkerQueue + IMaterializationService + IReflectionService + IKnowledgeResolver),不含 Hindsight 术语
  • AC-A2: SqliteProjectMemory 实现 IEvidenceStore,使用 evidence_docs(常规表)+ evidence_fts(FTS5 外部内容表)+ WAL 模式
  • AC-A3: HindsightEvidenceStore 实现 IEvidenceStore(legacy 兼容)
  • AC-A4: 所有路由通过 DI 注入接口,不直接 import HindsightClient — Phase B 闭合(PR #409)
  • AC-A5: ReflectionService 独立实现,不在 IEvidenceStore 接口内
  • AC-A6: retain-memory callback 写入 markers(状态 captured),approved marker 必须先 materialize 到 .md 才算沉淀
  • AC-A7: Factory 函数按配置选择实现(EVIDENCE_STORE_TYPE=sqlite|hindsight
  • AC-A8: edges 表支持文档间关系查询(含 supersedes/invalidates 关系,1-hop expand)
  • AC-A9: KnowledgeResolver 联邦检索两个同质 SQLite index — Phase B 闭合(PR #409)
  • AC-A10: IIndexBuilder.rebuild() 含 idempotent migrations + schema version + PRAGMA setup + FTS5 consistency check
  • AC-A11: IMaterializationService 实现 approved → .md patch → trigger reindex 流程(skeleton,Phase B 完善 frontmatter 兼容)
  • AC-A12: markers 真相源在 docs/markers/*.yaml(git-tracked),SQLite markers 表仅为工作缓存

Phase B(自动索引 + SOP 集成 + 评测)

  • AC-B1: frontmatter 解析器,从 .md 提取 anchor/kind/status/title/summary
  • AC-B3: feat-lifecycle 立项/关闭时自动 upsert 索引(与 SOP 集成)
  • AC-B4: search 支持 kind/status/keyword 过滤,检索时 superseded_by IS NOT NULL 降权
  • AC-B5: 比 grep docs/ 信噪比可测量提升(不返回 internal-archive/废案/discussion)
  • AC-B6: 新项目初始化时自动创建空 evidence.sqlite
  • AC-B7: memory_eval_corpus.yaml 评测集:检索评测(Recall@k)+ 状态评测(DB 变化验证),含 10-15 条 Hindsight 失败案例

Phase C(向量增强——预期路径,非可选)✅

  • AC-C1: EMBED_MODE 三态开关(off|shadow|on,默认 off),EMBED_MODEL 可配置(qwen3-embedding-0.6b 默认 + multilingual-e5-small 兜底)
  • AC-C2: Qwen3-Embedding-0.6B ONNX 本地推理(Transformers.js),MRL 维度可配置
  • AC-C3: evidence_vectors vec0 虚拟表(单一向量真相源),不在 evidence_docs 加 embedding 列
  • AC-C4: fail-open — 模型下载/加载/推理任一失败自动回落 Phase B lexical
  • AC-C5: 资源门禁 max_model_mem_mb + embed_timeout_ms,超阈值降级
  • AC-C6: embedding_meta 版本锚——模型/维度变更触发全量 re-embed(禁止静默混跑)
  • AC-C7: shadow 期 A/B(dim=128/256),复用 memory_eval_corpus.yaml 对比 Recall@k
  • AC-C8: 语义 rerank 对 FTS5 候选集重排序(不替代 lexical 召回)
  • AC-C9: evidence_passages 表按需启用(passage 级检索粒度,1000+ docs 后评估)— Phase E PR #531 实现(thread passages)

Phase D(激活 — Hindsight 清理 + 数据源扩大 + 检索协议 + 提示词集成)

  • AC-D1: 运行链路中无 Hindsight 调用分支,factory 只有 sqlite 路径 — PR #501 merged
  • AC-D2: 12 个 HINDSIGHT_* 环境变量、ConfigSnapshot hindsight 段、前端 config-viewer hindsight tab 全部移除 — PR #503 merged
  • AC-D3: Hindsight legacy 资产归档(docker-compose、scripts、P0 import、~26 tests) — PR #503 merged
  • AC-D4: 启动 60 秒内 evidence.sqlite 存在且 evidence_docs > 0(自动 rebuild) — PR #503 merged
  • AC-D5: search_evidence MCP 工具默认走 SQLite FTS5,至少 3 条 canary query 稳定返回预期 anchor — PR #509 merged
  • AC-D6: Session digest 索引为 kind='session',默认检索权重低于 feature/decision — PR #518 merged
  • AC-D7: 检索接口支持 mode(lexical/semantic/hybrid)和 scope(docs/memory/threads/all)参数 — PR #513 merged
  • AC-D8: Memory status 可观测(docs_count / last_rebuild_at / backend) — PR #511 merged
  • AC-D9: CLAUDE.md / AGENTS.md 提示词更新——告知猫猫记忆组件存在、检索策略、使用方式 — PR #509 merged
  • AC-D10: Recall Skill 或等效 SOP 集成——猫猫开工前自动/主动检索相关上下文 — PR #509 merged(等效 SOP:CLAUDE.md/AGENTS.md 策略表)
  • AC-D11: feat-lifecycle 集成——立项/状态变更/关闭时自动 incrementalUpdatePR #521 merged(POST /api/evidence/reindex)
  • AC-D12: 修改 feature 文档后 30 秒内可检索到新标题/摘要(增量 freshness) — PR #521 merged
  • AC-D13: Embedding load 失败时检索成功率不下降(fail-open lexical 保底) — Phase C AC-C4 已实现,PR #511 验证
  • AC-D14: search_evidence 成为统一检索入口,支持 scope/mode/depth 参数 — PR #513 merged
  • AC-D15: search_messagessession_search 降级为内部实现,不再作为独立 MCP 工具暴露 — PR #523 merged
  • AC-D16: callback auth 版本合并到主版本(search_evidence_callbacksearch_evidencereflect_callbackreflect) — PR #523 merged
  • AC-D17: SystemPromptBuilder 更新——search_evidence 排在记忆工具第一位,drill-down 工具排在后面 — PR #523 merged
  • AC-D18: IIndexBuilder.rebuild() 自动从 frontmatter 交叉引用(related_features/feature_ids/decision_id)提取 edges(零手工维护) — PR #509 merged
  • AC-D19: incrementalUpdate() 变更检测 → edges 反向查询 → 依赖文档标 needs_review(memory invalidation) — PR #521 merged

Phase E(Thread 内容索引 — 从"空壳"到"300 thread 可搜")

  • AC-E1: Thread summary 索引为 kind='thread'anchor=thread-{threadId}summary=threadMemory.summary) — PR #526 merged
  • AC-E2: dirty-thread + 30s debounce flush 基础设施(messageStore.append → dirty → 30s batch flush) — PR #526 merged
  • AC-E3: evidence_passages 表启用(Schema V3)+ sealed transcript chat 文本切 passage — PR #531 merged
  • AC-E4: live MessageStore 未封存增量切 passage 入库 — PR #531 merged
  • AC-E5: scope=threads + depth=raw 搜 passages 并聚合回 thread — PR #531 merged
  • AC-E6: reflect 返回显式降级消息(不再返回空字符串) — PR #526 merged
  • AC-E7: session digest 路径修复(transcriptDataDir 解析确认正确) — PR #537 merged
  • AC-E8: lesson/pitfall 召回质量改进 — PR #537 merged(splitLessonsLearned 32 个独立条目)

Phase I(Message-Level Permanence Repair — JSONL-backed passage reconciliation)

  • AC-I1: indexPassages() 优先从 Redis 取消息,Redis 缺失(消息数 < 已有 passage 数)时从 JSONL transcript fallback 补全
  • AC-I2: rebuild 时 passage 只增不减——不因 Redis 消息过期导致已索引 passage 被删除
  • AC-I3: 新消息热路径不变(Redis → passage,延迟 <5ms)
  • AC-I4: SearchOptions 支持 dateFrom/dateTo 参数,evidence_docsevidence_passages 均支持时间范围过滤
  • AC-I5: env-registry.tsMESSAGE_TTL_SECONDS 描述明确说明默认 7 天行为 + TTL≤0 变为永不过期的含义
  • AC-I6: 回归测试——模拟 Redis 消息过期场景下 rebuild 仍能通过 JSONL 恢复 passage(红→绿)

Phase I Follow-up: Passage 返回丰富化 + 上下文窗口(operator 2026-03-31 指示)

金渐层痛点:"搜到了只知道某个 thread 讨论过 X,不知道具体哪条消息"。Phase I 的 passage 已存了消息级内容,但返回字段太少、没有上下文窗口。

  • AC-I7: searchPassages() 返回增加 created_atpassageId(含 messageId/invocationId)字段,猫和人都能定位到具体消息 — PR #885 merged
  • AC-I8: searchPassages() 支持上下文窗口参数(类似 grep -C),返回命中 passage 前后 N 条 passage — PR #885 merged
  • AC-I9: MCP search_evidence(depth=raw) 返回值包含 passage 级细节(speaker + timestamp + 上下文),猫猫可直接引用具体消息 — PR #885 merged
  • AC-I10: CLAUDE.md / SystemPromptBuilder 中 search_evidence 用法指南更新——教猫用 depth=raw 做消息级定位,而非只用 drill-down 工具链 — PR #885 merged

Phase F-1/F-2/F-3(多项目记忆 — Project Onboarding & Ingestion)✅

  • AC-F1-1: project-init CLI 命令存在(pnpm project:init <dir>),在目标目录创建 13 个标准 KIND_DIRS 子目录 + 基础骨架文件(ROADMAP.md / VISION.md)
  • AC-F1-2: 初始化后 IndexBuilder.rebuild() 能正常运行,产出健康的 evidence.sqlite(docsIndexed >= 0, ok=true)
  • AC-F1-3: 已有 cat-cafe 标准目录的项目(如 cat-cafe 自身)跑 project:init 不覆盖已有文件(幂等安全)
  • AC-F2-1: discoverFiles() 增加通用递归 fallback——KIND_DIRS 扫完后,递归扫 docsRoot 下剩余 .md 文件(排除 node_modules / .git / archive)
  • AC-F2-2: 递归发现的 .md 文件 kind 推断链:frontmatter doc_kind → 父目录名匹配 KIND_DIRS → 默认 plan
  • AC-F2-3: 遗留项目(无标准目录结构,只有散落的 .md)rebuild 后 search_evidence 可搜到这些文档
  • AC-F3-1: frontmatter-formatter CLI 命令存在,扫描指定目录的 .md 文件,报告缺失 frontmatter 的文件列表
  • AC-F3-2: 自动推断并补充 doc_kind(从路径/内容关键词)、topics(从标题提取)、anchor(从文件名)
  • AC-F3-3: 支持 --dry-run(只报告不修改)和 --apply(实际写入 frontmatter)两种模式
  • AC-F3-4: 已有完整 frontmatter 的文件不被修改(幂等安全)

Phase J(Memory Hub — 记忆系统的人类产品面)✅

  • AC-J1: /memory 独立路由页面存在,左侧 sidebar 底部有 SVG 按钮(训练营→Memory→IM Hub 顺序),支持 ?from=threadId 返回链路
  • AC-J2: /memory 页面包含人类可用的搜索栏,支持 mode/scope/depth 参数调节
  • AC-J3: Knowledge Feed(Phase H)从 Workspace 知识模式迁移到 /memory Tab 1
  • AC-J4: /memory Tab 3 展示索引状态(docs/threads/passages 数量、最近 rebuild 时间、TTL 配置、embedding mode)
  • AC-J5: Workspace Recall Feed——猫调 search_evidence 时,右侧面板实时展示 query + results + scores
  • AC-J6: Recall Feed 不需要猫做额外工作——invocation 层自动拦截 tool_use 事件并推送前端
  • AC-J7: Hub Group 3(监控与治理)有 Memory 状态 tab,含索引速览 + "打开 Memory" 跳转按钮
  • AC-J8: Workspace 原"知识"模式更名为"记忆" / "Recall",承载 Recall Feed 而非完整 Knowledge Feed

Dependencies

  • Evolved from: F024(Session Chain — 提供了 sealed session digest 数据源)
  • Related: F003(原始记忆系统研究)
  • Related: F042(三层信息架构 — 索引结构参考)
  • Related: F100(Self-Evolution — 全局记忆/Skills 体系,F102 的项目层与 F100 的全局层互补)

Risk

风险 缓解
索引与文档不同步(stale index) 索引记录 source_hash,IIndexBuilder 增量更新 + consistency check
FTS5 关键词检索精度不够 Phase C 向量增强是预期路径(KD-5),不是可选
重蹈 retain 碎片化覆辙 marker candidate queue + 分层审批(KD-3/9/12)
多项目 SQLite 文件管理复杂度 每项目根目录一个 evidence.sqlite,gitignore + rebuild
rebuild 后丢失工作流状态 markers 真相源在 git-tracked docs/markers/*.yaml(KD-8)
过期知识高相似误召回 superseded_by 字段 + 检索降权(KD-16)
评测缺失导致上线后才发现检索质量差 Phase B 加评测集(KD-17)
614MB ONNX 模型拖慢启动/OOM 资源门禁 + 兜底模型 + fail-open(KD-20)
Passage 索引依赖 Redis(7 天 TTL),rebuild 后丢失过期消息 Phase I: JSONL fallback + incremental merge(KD-45/46)
模型/维度变更后向量不一致 版本锚 + 全量 re-embed(KD-22)

Key Decisions

# 决策 理由 日期
KD-1 本地优先,不上外部服务/图数据库 三猫全票通过 2026-03-11
KD-2 reflect 从存储层拆出 它是 LLM 编排能力,不是存储 primitive 2026-03-11
KD-3 retain 降级为 candidate/marker queue 防止碎片化垃圾入库(Hindsight 失败教训) 2026-03-11
KD-4 自动索引 > 手动 retain 与 feat-lifecycle SOP 集成,90% 记忆沉淀自动化 2026-03-11
KD-5 SQLite 是终态存储基座(不是终态检索策略),纯 lexical 不够,Phase C 向量增强是预期路径 GPT Pro 打回:KD-5 原文把存储和检索混为一谈 2026-03-11
KD-6 全局记忆跟猫走,项目记忆留在项目 全局=Skills/家规/MEMORY.md(F100),项目=evidence.sqlite 2026-03-11
KD-7 每项目一个 evidence.sqlite(物理隔离) 猫出征新项目不带旧项目 feat 细节 2026-03-11
KD-8 索引 = gitignore + rebuild;markers = git-tracked durable store GPT Pro 打回:markers 有审核历史,不是编译产物,rebuild 会蒸发 2026-03-11
KD-9 markers 分层审批:项目内知识有 anchor+dedupe → 自动 approve;影响全局层 → needs_review 走 F100 GPT-5.4 建议,避免全自动/全人工二选一 2026-03-11
KD-10 Schema 拆分:evidence_docs(常规表)+ evidence_fts(FTS5 外部内容表) 结构化过滤不该塞 FTS5,GPT-5.4 P1 2026-03-11
KD-11 联邦检索 KnowledgeResolver:全局层只读接入,不写进项目库 F100 定了"不发明新沉淀库",GPT-5.4 P1 2026-03-11
KD-12 marker 状态机:captured→normalized→approved→materialized→indexed(+ rejected/needs_review 分支) GPT Pro 打回:accepted ≠ truth,materialized 才是终态 2026-03-11
KD-13 新增 IMaterializationService + IIndexBuilder 接口(共 6 接口) 晋升瞬间和编译器是一等公民,不能散落在角落 2026-03-11
KD-14 全局层也编译 read-only global_knowledge.sqlite resolver 不应混用 raw filesystem 和 SQLite MATCH 2026-03-11
KD-15 预留 evidence_passages 表(v1 不填) 检索粒度太粗,1000+ docs 后 summary 不够 2026-03-11
KD-16 superseded_by 字段 + supersedes/invalidates 关系类型 过时高相似决策比查不到更危险 2026-03-11
KD-17 Phase B 加评测集 memory_eval_corpus.yaml 上次痛点是"找不对"不是"存不了" 2026-03-11
KD-18 WAL 模式 + 单写者队列 + tokenchars + bm25() 列权重 SQLite 实操最佳实践,GPT Pro 建议 2026-03-11
KD-19 Embedding 模型选 Qwen3-Embedding-0.6B(主方案)+ multilingual-e5-small(兜底),MRL 维度可调 operator指示统一 Qwen 技术栈;C-MTEB 66.33 远超 MiniLM;中英混排核心场景 2026-03-12
KD-20 三态开关 off|shadow|on + fail-open 到 lexical codex review:增强层不能拖累基础能力 2026-03-12
KD-21 单一向量真相源 evidence_vectors(vec0 虚拟表),不在 evidence_docs 加 embedding 列 codex review:避免双真相源 2026-03-12
KD-22 可复现版本锚 embedding_meta 表:model_id/model_rev/dim 变更 → 全量 re-embed codex review:禁止静默混跑不同模型/维度的向量 2026-03-12
KD-23 不引入 QMD 外部依赖——F102 引擎与 QMD 同构(SQLite FTS5 + sqlite-vec + RRF),扩大数据源即可 两猫共识:双轨维护成本 > 收益,违反 KD-1 2026-03-16
KD-24 thread 检索 summary-first, raw-on-demand——默认搜 session digest,不搜 raw transcript 聊天噪音会淹没文档;Artem 方案的核心也是分层 2026-03-16
KD-25 检索路由 BM25-first——大多数查询先 lexical,semantic 是增强层不是主路 冷启动快、稳定、三猫并发友好 2026-03-16
KD-26 提示词/Skill 集成是验收门槛——功能做完必须修改系统提示词,否则猫猫不会用 operator直接指示:"就算做了超酷功能,没有感知到也不会用" 2026-03-16
KD-27 MCP 工具两层收敛——统一入口 search_evidence + drill-down 层(thread/session/reflect),废弃 4 个冗余工具 两猫+operator共识:不能老一套新一套双轨并存 2026-03-16
KD-28 search_evidence 加 depth 参数(summary/raw)——默认 summary-first,raw-on-demand Maine Coon补充:scope 不够,depth 维度决定噪音量 2026-03-16
KD-29 edges 只从显式锚点提取(frontmatter),不从语义相似度推断——推断关系不可信 Maine Coon红线:错边会把猫带去错误历史 2026-03-16
KD-30 Memory invalidation 翻译自 GitNexus detect_changes——不做 code impact,做 knowledge invalidation 三猫共识:对 F102 更有价值的是"改了 ADR → 标依赖文档 needs_review" 2026-03-16
KD-31 不做代码图谱——图数据库/Tree-sitter/Leiden/Cypher 是代码智能方案,不是记忆方案 三猫+operator共识:"太重了",解的是错层问题 2026-03-16
KD-32 Thread 索引不导出 markdown——直接从 messageStore 读消息内容编译索引,不转中间层 md 文件 operator明确否决 + Maine Coon方案共识:真相源在 Redis(TTL=0 永久),索引是编译产物,导出 md = 重复真相源 2026-03-18
KD-33 Thread 索引不靠 threadMemory.summary——340 thread 中 326 个 summary 为空,必须从消息内容本身提取可搜文本 operator指出"threadMemory.summary 不靠谱",回溯 QMD proposal 确认:正确做法是 turn-by-turn 消息拼接 2026-03-18
KD-34 Thread 索引增量更新必须覆盖所有 messageStore.append 调用点(36 个)——不能只 hook 2 条 HTTP 路由,必须在 messageStore 内部加 post-append callback operator问"好几天不重启怎么办",代价分析:IO/CPU 可忽略(<5ms/thread),真实代价只是"确保覆盖所有写入路径" 2026-03-18
KD-35 多项目记忆分两种策略:(1) 新项目:猫按家规建标准 docs 体系(feat-lifecycle/Skills 引导),IndexBuilder KIND_DIRS 直接适配;(2) 遗留老项目:通用递归扫描所有 .md,不硬编码目录名。两种共存,先标准后兜底 operator指出"新项目猫不知道要建 docs 体系",分两种情况设计 2026-03-19
KD-36 遗留项目需要 frontmatter formatter——老项目 .md 文件可能没有 frontmatter(feature_ids/doc_kind/topics),需要一个工具自动扫描并补充 metadata,提升 kind 推断和检索质量 operator提出"接手垃圾项目也需要 formatter 那个 metadata" 2026-03-19
KD-37 Abstractive digest 模型用 Opus 4.6(金渐层/反代 API,复用 F062 provider-profiles),不用 Haiku operator引用实测:Haiku 带坑里,Sonnet 需推断,Opus 完全准确 2026-03-19
KD-38 Thread-level durable candidate extraction——digest + candidate extraction 合并为一次 Opus 调用(定时任务触发,不绑 seal);candidate 只允许 decision/lesson/method,必须带证据,不直写长期真相源 三猫共识(LC 调研 + Maine Coon收紧 + operator打回 seal 绑定):吸收 LC"lifecycle 节点触发 durable write",保留 marker→materialize 门禁 2026-03-20
KD-39 定时任务跑 thread 增量摘要——每 30min 扫描增量达标的 thread 调 Opus,不和 session seal 绑定 operator修正:session strategy 可配置(不一定有 seal),绑定 seal = 绑定特定策略;定时任务更稳健 2026-03-20
KD-40 ThreadMemory 用 abstractive summary 替代拼接——有 abstractive 时 bootstrap 直接用,不需要独立凝结层 简化:定时任务每次合并增量 + 上次摘要 = 渐进凝结,不需要额外步骤 2026-03-20
KD-41 摘要单元是 thread(不是 session)——thread 是所有猫共享的对话空间,对每只猫的 session 分别摘要 = 同一段对话重复摘要 operator指出:多猫 session 有重合,保存多份很奇怪;thread 概念全部猫都用,应该基于 thread 而不是 session 2026-03-20
KD-42 LSM-style compaction + 双写(read model + append-only segment ledger)——evidence_docs.summary 是 read model,summary_segments 是 append-only provenance。L2 凝结 deferred 但 segment ledger 让升级成本很低 Maine Coon坚持 segment ledger 防漂移/不可审计/错误放大,架构师采纳——成本仅多一张表一次 INSERT,收益是完整可审计性 2026-03-20
KD-43 一次 delta batch 产出 1..N 个 topic segments(Opus 按话题切分,最多 3 段,不确定退化 1 段)——跨时间窗只 link 不 merge,merge 留给 L2 operator提出动态语义窗口(一个增量可能混多个话题),Maine Coon约束:连续/覆盖/最多 3 段/不回改旧 segment/必须带 topicKey + boundaryReason 2026-03-20
KD-44 三种检索模式各有独立路径——lexical=纯 BM25,semantic=纯向量 NN(跳过 BM25),hybrid=BM25+NN 双路召回 → RRF 融合。Phase C 只实现了 rerank(BM25 上重排序),不是真的 semantic/hybrid operator实测:semantic 搜 "why are cats named Ragdoll Maine Coon Siamese" 搜不到猫名故事——因为 BM25 没召回,rerank 无法补救。真的 semantic 应该直接 NN 搜索 2026-03-21
KD-45 消息真相源三层分层(L0/L1/L2)——L0 Redis(热状态,TTL-bound)/ L1 Session JSONL(永久原文)/ L2 evidence_passages(检索投影)。L2 构建必须以 L1 为终极兜底,不能只依赖 L0 金渐层深度使用暴露:JSONL 永久保存但搜索链路绕过它;Ragdoll+Maine Coon共识 2026-03-30
KD-46 KD-32 修正:Redis 默认 7 天 TTL,非永久——KD-32 假设"真相源在 Redis(TTL=0 永久)",实际 DEFAULT_TTL_SECONDS = 604800(7 天),.env 未覆盖。Passage 索引不能假设 Redis 永久可用 代码审计 + .env 检查确认 2026-03-30
KD-47 时间过滤必须排在 JSONL backfill 之后——先保证旧消息永远能搜到,再做按时间切片搜。否则时间过滤会放大"明明 transcript 在但搜不到"的体验落差 Maine Coon风险分析 2026-03-30
KD-48 Memory 主入口是独立路由页面 /memory,不是 Workspace 模式——Workspace 只做上下文 Recall Feed(副入口)。物理位置:左侧 sidebar 底部按钮区(训练营→Memory→IM Hub),SVG 图标。operator 2026-03-31 拍板 Maine Coon评审 + operator拍板 2026-03-30; 2026-03-31
KD-49 Recall Feed = 猫搜记忆时人实时可见——invocation 层拦截 search_evidence tool_use → 推送 query+results 到前端 Workspace 面板。猫不需要额外工作,前端自动展示 operator核心洞察:"偷偷看一眼猫搜到了什么记忆" 2026-03-30

Known Issues(operator 2026-04-01 Report)— ✅ 已全部修复 (PR #908)

Issue 1: Workspace Recall Feed 全部显示 (unknown)

严重度: P1(功能不可用) 位置: packages/web/src/hooks/useRecallEvents.ts:112 根因: 参数名不匹配 — parseDetail() 解析 params.q,但 MCP 工具 cat_cafe_search_evidence 的参数名是 query(见 packages/mcp-server/src/tools/evidence-tools.ts:16)。toStoredToolEvent 序列化出 {"query":"...","mode":"hybrid"},前端找 .q 永远 undefined → fallback 到 '(unknown)'修法: params.qparams.query(一行修)

Issue 2: Memory Hub 搜索展示粗糙,后端元数据未被前端利用

严重度: P2(可用但体验差) 位置: packages/web/src/components/memory/EvidenceSearch.tsx:161-162 现象:

  • 搜索结果默认只返回 5 条(effectiveLimit = limit ?? 5),无分页、无"加载更多"
  • 所有 doc_kind 标签统一紫色(bg-cocreator-light),英文原值(discussion/phase),无图标
  • 后端 frontmatter-formatter 补全的 doc_kind/topics/anchor 元数据,前端只用了 sourceType 显示原值,topics 完全未展示
  • 已有更好的 EvidenceCard 组件(含 SOURCE_CONFIG 分类图标 + confidence 分色),但 EvidenceSearch 没复用

改进方向:

  1. 复用 EvidenceCard 替换搜索结果卡片(分类图标 + 分色)
  2. doc_kind 标签中文化 + 按类型分色
  3. limit 提到 10-15 + 加载更多
  4. topics 作为可点击筛选标签

Issue 3: Recall Feed 展开后只显示 1 条结果(实际 5 hits)

严重度: P1(关键信息丢失) 位置: packages/api/src/domains/cats/services/agents/routing/route-helpers.ts:178 根因: tool_result 的 detail 被 truncateDetail(raw, 220) 硬截断到 220 字符。search_evidence 返回 5 条结果(每条含 [confidence] title + anchor + type + snippet),完整文本远超 220 字符,截断后 parseTextResults() 只能解析出第 1 条,其余 4 条丢失。 影响: operator看到 "5 hits" 但展开只看到 1 条结果,无法知道猫猫到底搜到了什么。 修法: 对 search_evidence 类 tool_result 使用更大的 detail 限制(如 1500 字符),或单独序列化结构化结果(不依赖截断文本解析)。

Issue 4: Knowledge Feed "已沉淀" 标签语义不准确(Maine Coon愿景守护 2026-04-01)

严重度: P1(愿景级 — 语义在撒谎) 位置: packages/web/src/components/workspace/KnowledgeFeed.tsx:115,227 根因: F102 的真相源约束明确区分三个状态:approved(候选通过)→ materialized(写入 docs/*.md)→ indexed(被 IndexBuilder 索引)。但前端 KnowledgeFeed 的 tab 名叫"已沉淀"(line 115),卡片状态也显示"已沉淀"(line 227),而后端 settled 桶实际混合了 approved + materialized + indexed。用户看到"已沉淀"会以为知识已经持久化到文档,但实际可能只是被批准了还没写入。 修法: tab 改名"已确认"或按真实状态分 3 列;至少不要把 approved 叫"已沉淀"。

Issue 5: classifySource() 把 7+ 种 doc_kind 压扁为 4 种 sourceType

严重度: P2(语义丢失) 位置: packages/api/src/routes/evidence-helpers.ts:65-71 根因: classifySource() 只按路径匹配 4 种类型(decision/phase/discussion/commit)。lesson/research/feature/plan 等 doc_kind 如果不在对应标准路径下,全部 fallback 到 commit。frontmatter-formatter 辛苦补的 doc_kind 在搜索结果层被丢弃。 修法: classifySource() 应优先读 frontmatter 的 doc_kind,路径匹配作为 fallback;EvidenceSourceType 扩展到覆盖所有 KIND_DIRS 类型。

Issue 6: IndexStatus 面板交付少于 AC-J4 承诺

严重度: P2(功能缩水) 位置: packages/web/src/components/memory/IndexStatus.tsx:96-102 根因: AC-J4 承诺展示 "docs/threads/passages 数量、最近 rebuild 时间、TTL 配置、embedding mode"。实际只展示 Backend/Documents/Edges/Last rebuild。缺失:threads 数量、passages 数量、TTL 配置、embedding mode。 修法: 后端 /api/evidence/status 补充返回 threads/passages count + TTL + embedding mode;前端 IndexStatus 增加对应行。

Maine Coon建议但需后续讨论的项

  • 跨项目切换器:Maine Coon认为 /memory 缺少 "当前项目 vs 全局记忆 vs 其他项目" 维度。核实:AC-J2 只承诺了 mode/scope/depth,项目切换器在 spec wireframe 里标注为 [Phase F] 功能,不属于 Phase J 范围。后端 F-4 联邦检索已就绪,前端呈现属于后续 Phase。
  • Recall Feed 缺 snippet/source link/drill-down:✅ 已全部补齐 — snippet (PR #915) + inline expand (PR #923) + source link (PR #939)。

实现路线图(F/G/Gap 整体规划)

当前状态:Phase A~E ✅ + G foundation ✅ + H ✅ + I ✅ + F-4 ✅ + J ✅ + F-1/2/3 ✅ + Known Issues fix ✅ (PR #908) + Batch 1/2/3 ✅ + follow-up ✅ + Phase K ✅(AC-K1/K2 闭环,PR #1155)+ post-K dogfood fixes ✅(PR #1160/#1179/#1192/#1195/#1204 — passage ranking + heading keywords + auto-rebuild + recall backfill + docs scope filter)+ AC-K3 completed by F209 Phase A ✅(PR #1842)。AC-K4 remains future work。 operator指示:开源同步时增强功能需要开关,默认 off。

收尾三批次(2026-04-01 三方收敛:Ragdoll+Maine Coon GPT-5.4+operator)

原则:先补真相源闭环,再验运行时,再打磨人类入口。

Batch 1: IMaterializationService 终态 ✅ PR #911
         approved → docs/*.md 写入 → git commit → reindex trigger → 冲突处理
         验收:工程闭环 + operator短验收(改真相源文档,语义风险高)

Batch 2: Phase G 运行时验收闭环 ✅ PR #912
         thread 摘要 / dirty thread 调度 / candidate extraction → 真实运行质量确认
         前提:Batch 1 完成(否则 candidate 生命周期链不完整)
         验收:真实 thread / candidate / approve 全链路跑通

Batch 3: /memory 体验层收口 ✅ PR #915
         a. project/global 维度切换器(后端 F-4 联邦检索已就绪,补前端入口)
         b. Recall Feed snippet / source link / drill-down(从"能看"到"好用")
         验收:必须operator亲手体验,才能说收口

历史整体顺序(2026-03-30 三方收敛,已全部完成)

① 已完成 Gap-1: runtime EMBED_MODE=on(PR #618 auto-derive + 当前 .env 已启用)
② Stage 1: Phase I — Message-Level Permanence Repair ✅ PR #884 + #885
③ Stage 2: Phase F-4 — Global Knowledge Foundation ✅ PR #886
④ Stage 3: Phase J — Memory Hub ✅ PR #899
            注:跨项目切换器属于 Phase F 范围(wireframe 标注 [Phase F]),不在 J 内
⑤ Stage 4: Phase F-1/F-2/F-3 — Project Onboarding & Ingestion ✅ PR #904
⑥ Known Issues 1-6 fix ✅ PR #908
⑦ Batch 1: IMaterializationService 终态 ✅ PR #911
⑧ Batch 2: Phase G 运行时验收闭环 ✅ PR #912
⑨ Batch 3: /memory 体验层收口 ✅ PR #915
⑩ Batch 3 follow-up: inline expand + brain icon + config panel + source link ✅ PR #923/#935/#937/#939

Why this order(Maine Coon 2026-03-30 收紧):

  • 不并行 I 和 F-4——两者都动 KnowledgeResolver / memory 边界,并行容易交叉返工
  • I 先于 F-4——先修单项目 permanence 再叠加全局层,层次更干净;否则全局层只是把单项目的问题复制到全局
  • J 必须等 I + F-4——否则 UI 会自然滑向"先做单项目版再补跨项目"的脚手架模式
  • F-1/2/3 最后做——给 Memory Hub 持续喂内容,但不阻塞 Hub 的产品形态

旧路线图(仅供参考,已被上述替代)

(旧)② 第一批  F-1 + F-2(通用扫描 + formatter)  ←─┐ 可并行
               G-2 + G-3(schema + 定时任务调度器) ←─┘
(旧)③ 第二批  G-1(Opus 调用 + topic segment 切分)← 依赖 G-2/G-3
(旧)④ 第三批  G-4 + G-5(bootstrap + drillDown)  ← 依赖 G-1
(旧)⑤ 最后   F-3 + F-4(全局知识层 + project-init)← 独立但较大

Gap 处理

Gap 处理方式
Gap-1(embedding off) 我们的 runtime env 加 embed: { embedMode: 'on' }。开源默认仍 off(不传即 off)。零代码改动
Gap-2(shadow 无日志) 废弃 shadow 模式,直接 off → on。不修 shadow 日志
Gap-3(中英混搜) 随 Gap-1 解决(embedding 开启后向量空间自然桥接中英)

开源开关策略

代码级:已有的 EMBED_MODE 三态开关覆盖 embedding。Phase F/G 新增功能用 feature flag:

// 在 createMemoryServices 调用处按 env 传参
{
  embed: { embedMode: process.env.EMBED_MODE ?? 'off' },           // 已有
  abstractive: {                                                     // Phase G 新增
    enabled: process.env.F102_ABSTRACTIVE === 'on',                 // 默认 off
    topicSegments: process.env.F102_TOPIC_SEGMENTS === 'on',        // 默认 off
    durableCandidates: process.env.F102_DURABLE_CANDIDATES === 'on', // 默认 off
  },
  multiProject: {                                                    // Phase F 新增
    legacyScan: process.env.F102_LEGACY_SCAN === 'on',              // 默认 off
    globalKnowledge: process.env.F102_GLOBAL_KNOWLEDGE === 'on',    // 默认 off
  },
}

我们自己 = 全部 on。开源仓 = 全部 off(不设 env 即 off),README 说明开启条件。

开启前提:

Flag 开启条件
EMBED_MODE=on 首次下载 Qwen3 ONNX ~614MB,内存 ~1GB
F102_ABSTRACTIVE 需要 Anthropic API key(provider-profiles 配置)
F102_TOPIC_SEGMENTS 同上(abstractive 的子功能)
F102_DURABLE_CANDIDATES 同上 + marker/materialization 流水线
F102_LEGACY_SCAN 无特殊前提(有 .md 就能跑)
F102_GLOBAL_KNOWLEDGE 需要 ~/.cat-cafe/ + Skills 体系

Phase A~E 的全部功能(FTS5 + 向量检索 + thread passages + session chain drill-down)在 flag off 时照常工作。增强功能是 additive,不影响基础能力。

Phase K: Contract Closure — 对外契约闭环(2026-04-13 重新打开)

起因:其他线程的猫猫投诉"F102 没做完"。Maine Coon(GPT-5.4) 审计后定位到 4 项未闭环, 其中 2 项是契约缺口(P1),2 项是能力增强(P3 deferred)。 operator指示:不做脚手架,完整挂在 F102 issue 里实现。

P1: 契约缺口修复

AC-K1: depth=raw 强制降级必须告知调用方

当前状态:SqliteEvidenceStore.ts:299depth=raw 时短路返回,跳过 mode 分支。 API route evidence.ts:99 始终返回 degraded: false。前端仍允许选择 semantic/hybrid

  • 后端:当 depth=raw && mode !== 'lexical' 时,在返回中设 degraded: truedegradeReason: 'raw_lexical_only',附 effectiveMode: 'lexical'
  • 前端:当 depth=raw 时,mode 下拉锁定为"精确"并显示提示("消息级检索仅支持精确匹配")
  • SearchOptions / EvidenceSearchResponseeffectiveMode 字段

AC-K2: passage 字段类型对齐

当前状态:后端返回 { passageId, content, speaker, createdAt, context }(evidence-helpers.ts:26), 前端期望 { text, score }(EvidenceSearch.tsx:22)。p.text 渲染为 undefined。

  • 前端 SearchResultItem.passages 类型改为匹配后端实际返回
  • passage 渲染展示 contentspeakercreatedAt,不再渲染不存在的 text/score
  • context passages(上下文窗口)也正确渲染

P3: 能力增强(F209 Phase A 已接手 AC-K3)

AC-K3: passage-level vector pathdepth=raw 支持 semantic/hybrid

  • F209 Phase A / PR #1842 已实现 passage-level vector path
  • depth=raw semantic/hybrid 现在走 passage NN / passage BM25+NN RRF
  • embedding unavailable 时 fail-open 到 lexical,并显式标 degraded/effectiveMode

AC-K4: L2 Rollup(多 L1 segment 凝结为更高层摘要)

  • ADR-020 KD-42 已记录为 deferred
  • segment ledger 已就绪,升级只改读路径
  • 开启条件:session chain 长度达到 L1 瓶颈

Phase K 验收标准

  • AC-K1/K2 全部打勾 → Phase K done → F102 re-close
  • AC-K3 已由 F209 Phase A 补齐;AC-K4 仍是 future work,不阻塞 K close

Review Gate

  • Phase A: 跨 family review(Maine Coon优先)— 接口设计需要多方确认
  • Phase B: 同 family review(Ragdoll Sonnet 可)— 实现层面
  • Phase G foundation: Maine Coon(GPT-5.4) review 4 轮放行 — 8 findings 全部闭环(PR #604)
  • Phase K: 跨 family review(Maine Coon优先)— 对外契约改动