* docs: update ReMe documentation URL * docs: localize ReMe Studio social image * docs(AGENTS): update agent guidelines and repository documentation structure - Clarify coding agent guidance for keeping changes small and consistent - Revise project principle descriptions for clarity and modern terminology - Expand repository map with detailed component and folder explanations - Add configuration and CLI usage instructions, including syntax and merging rules - Elaborate on component, step registration, and application lifecycle processes - Define jobs, steps, and state handling conventions for stateless design - Specify workspace and file safety policies, including path restrictions and locking - Update validation commands and testing environment recommendations - Clarify coding and test conventions, including style and dependency policies - Distinguish documentation boundaries and update website content contribution notes - Reinforce change guardrails to avoid breaking backward compatibility and data loss - Improve svg diagram formatting and textual details in auto dream and proactive flow image * style(docs): fix font-family syntax in SVG style definitions - Correct quotation marks around font-family names in memory-as-file.svg - Standardize font-family formatting by removing unnecessary quotes in reme-blog-architecture.svg - Ensure consistent CSS style formatting within SVG files for better rendering fidelity * docs: add ReMe blog to news * style(docs): inline svg styles and improve text formatting - Convert multiline SVG style tags into single-line for compactness in multiple figures - Remove redundant line breaks in subtitle text elements for consistency - Shorten descriptive texts in SVG figures for clarity and conciseness - Adjust font sizes and text for better readability in SVG elements - Correct whitespace issues in Chinese markdown document for improved formatting - Remove unused style blocks from framework structure SVG for cleaner code
8.8 KiB
Memory Search
Memory Search 是 ReMe 的记忆检索入口。默认后台持续把 daily/、digest/ 里的 Markdown 构建成可搜索的 chunk 索引和
wikilink 图谱;查询时先召回最相关的片段,再沿着片段所在文件的双向链接展开上下文。reme reindex 的重建范围更宽,会额外扫描
resource/ 和 JSONL;这与实时 watcher 的默认范围不同。
文件分层、frontmatter、wikilink 和 chunking 的通用语义见 Memory as File。这里重点说明索引维护和查询执行。
workspace files
├─ index_update_loop: 发现 added / modified / deleted
├─ update_index_step: 文件 -> FileNode + FileChunk[]
├─ file_store: 保存 chunk、BM25、可选 embedding、wikilink graph
└─ search_step: BM25 / vector 召回 -> RRF 融合 -> link expansion
它搜索什么
默认配置里的 index_update_loop 监听两类记忆目录:
daily_dir:Auto Memory 生成的每日工作记忆和 session 记忆卡片。digest_dir:长期沉淀后的 digest 节点。
默认实时后缀只有 md。resource_dir 由独立的 resource_watch_loop 监听,并经 Auto Resource 转换成 daily 卡片后进入实时索引。
如果手动运行 reme reindex,其配置会扫描 daily_dir、digest_dir、resource_dir 下的 md 和 jsonl;Markdown 用
markdown chunker,JSONL 用 jsonl chunker。
索引怎么构建
索引更新
索引由后台 Job index_update_loop 维护,配置来自 reme/config/default.yaml:
index_update_loop:
backend: background
watch_dirs: [daily_dir, digest_dir]
watch_suffixes: [md]
steps:
- backend: init_changes_step
monitor_type: file_store
monitor_name: default
dispatch_steps: [ update_index_step ]
- backend: watch_changes_step
dispatch_steps: [ update_index_step ]
启动时先跑 init_changes_step。它扫描 watch 目录,把磁盘上的文件 mtime 和 file_store 里已有的 FileNode.st_mtime
对比,算出新增、修改、删除三类变化,然后把 context["changes"] 交给 update_index_step。
服务运行期间由 watch_changes_step 接手。它用 watchfiles.awatch() 监听同一批目录,按 quiet window 聚合文件事件,再用
coalesce_changes() 把同一路径上的重复事件压成一批稳定变化。
update_index_step 真正写索引:
- 按后缀选择 file chunker。
- 把文件解析成一个
FileNode和多个FileChunk。 - 对新增或修改的文件,先删除旧 chunk,再 upsert 新 chunk。
- 对删除的文件,从
file_store、keyword_index和file_graph清掉对应记录。 - 有变化时 dump 到
metadata/,让下次启动可以恢复。
Markdown chunker 会解析 YAML frontmatter、标题结构和 wikilink,产出 FileNode、FileChunk 和 FileLink。更细的分块规则见
Memory as File。
索引优化
BM25 和 FAISS HNSW 向量索引在删除节点时都采用墓碑(tombstone)标记而非物理移除,积累过多会拖慢搜索。为此内置了闲暇时间索引优化机制——
optimize_index_cron 定时任务在低峰期压缩墓碑并重建索引:
optimize_index_cron:
backend: cron
cron: "0 2 * * *"
steps:
- backend: optimize_index_step
默认每天凌晨 2 点执行,调整 cron 表达式即可自定义调度时间。
file_store 里有什么
默认 file_store.default 是 local:
file_store:
default:
backend: local
embedding_store: ""
keyword_index: default
file_graph: default
它组合三类能力:
| 部件 | 默认状态 | 作用 |
|---|---|---|
file_chunks |
启用 | 保存 FileChunk 文本、行号、分数、可选 embedding |
keyword_index.default |
启用 | BM25 倒排索引,chunk id 是 doc id |
file_graph.default |
启用 | 保存 FileNode 和 wikilink 边 |
embedding_store |
默认关闭 | 开启后为 chunk 生成 embedding,并支持向量召回 |
所以开箱搜索主要是 BM25 + 链接展开。把 embedding_store: default 打开后,SearchStep 会同时跑向量召回和关键词召回。此时若将
file_store 的 backend 从 local 改为 faiss,向量检索会从线性扫描升级为 FAISS HNSW 索引,在大规模 chunk 场景下召回效率更高。
怎么搜索
search Job 也是在 default.yaml 中配置:
search:
backend: base
description: "Hybrid workspace search (vector + BM25, RRF-fused)."
parameters:
query: string
limit: integer
min_score: number
start_date: string
end_date: string
steps:
- backend: search_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: true
max_links_per_direction: 10
调用时:
reme search query="最近关于索引的讨论" limit=5
start_date 和 end_date 可以按 YYYY-MM-DD 做包含边界的日期过滤:
reme search query="索引回归" start_date=2026-06-01 end_date=2026-06-20 limit=10
search_step 的执行顺序是:
flowchart LR
A["query + limit"] --> B["candidates = min(200, limit * candidate_multiplier)"]
B --> C["file_store.vector_search(...)"]
B --> D["file_store.keyword_search(...)"]
C --> E["RRF 融合"]
D --> E
E --> F["min_score 过滤"]
F --> G["截断到 limit"]
G --> H["expand_links(...)"]
H --> I["Response.answer + metadata"]
如果只有 BM25 有结果,就直接返回 BM25 排名;如果只有向量有结果,就直接返回向量排名;两边都有结果时,用 RRF 融合。RRF 不直接比较 BM25 分数和 cosine 分数,而是比较两个列表里的名次:
fused_score = vector_weight / (60 + vector_rank)
+ keyword_weight / (60 + keyword_rank)
默认 vector_weight=0.7,所以启用 embedding 后语义召回权重更高;关键词仍能把精确词命中的 chunk 拉上来。
BM25 怎么工作
keyword_search() 调用 keyword_index.retrieve(query, limit)。BM25 索引里每个 chunk 是一篇文档:
doc_id是FileChunk.id。content是FileChunk.text。- tokenizer 把文本切成 token。
- 倒排表记录 token 出现在哪些 chunk 里、每个 chunk 的词频是多少。
- 查询时只对 query token 命中的 posting list 打分,再返回分数最高的 chunk id。
当文件被修改时,LocalFileStore.upsert() 会先删除该文件旧 chunk_ids 对应的 BM25 doc,再添加新 chunk 文本。删除采用 lazy
delete,后续可通过 optimize 压缩索引。
渐进式展开怎么看
Memory Search 的“渐进式”不是一次把全库内容塞进结果,而是分三层展开:
- 第一层是 chunk 召回:只返回最相关的
limit个文本片段。 - 第二层是文件定位:每个结果带
path:start_line-end_line。调用read时需要把它们分别作为path、start_line和end_line传入,行号范围不是path的一部分。 - 第三层是链接邻居:对命中文件调用
expand_links(),展开最多max_links_per_direction个 outlinks 和 inlinks。
展开的数据来自 file_graph,不是重新扫文件:
命中 chunk
-> chunk.path
-> file_store.get_outlinks(path)
-> file_store.get_inlinks(path)
-> file_store.get_nodes(neighbor_paths)
-> 渲染邻居的 path、name、description、anchor
这让搜索结果既保持短,又能看到“这条记忆连接到哪些长期节点、资源或其他 daily note”。如果某条结果值得继续追,可以用
read path=... 打开原文,或用 traverse path=... depth=2 沿 wikilink 图谱继续扩展。
返回结果长什么样
SearchStep 会把结果写到两个地方:
response.answer:给人看的文本,每个命中块包含路径、行号、分数和 chunk 内容,后面跟 outlinks / inlinks。response.metadata:给程序看的结构化结果,包括results、link_expansion、counts。
典型文本结构:
========== daily/2026-06-20/retrieval-regression.md:12-28 [score=0.0317 keyword=4.8120] ==========
...命中的记忆片段...
outlinks (2):
-> digest/indexing.md name="Indexing" description="..."
inlinks (1):
<- daily/2026-06-19.md name="..."
counts 会告诉你本次向量、关键词各召回了多少候选,以及最终返回多少条。默认 embedding 关闭时,vector 通常是 0,hybrid 是
false。