【免费下载链接】crabtalk
The long-running embedded agent
🦀 Crabtalk 是一个长期运行的嵌入式 agent 守护进程(Agent daemon),而会话搜索(Session Search)正是它"记得住过去"的核心能力:内置的search_sessions工具基于BM25算法对所有历史会话消息建立倒排索引,按关键词返回窗口化摘录(windowed excerpts)——命中的那条消息加上它前后若干条上下文,而不是整个会话全文。
这篇文章带你从原理到实战,完整看懂这套会话搜索是如何实现的。
🎯 会话搜索解决了什么问题?
Agent 与用户之间的对话会持续沉淀。当用户问"我们上周关于 cron 重构是怎么决定的?"时,Agent 需要一种手段去翻出那次对话——而不是一遍遍全量加载会话(既慢又撑爆上下文窗口)。
早期的方案是给会话打标签、切分 topic,但实践中"打标签"和"找内容"是两回事:标签命名会漂移(cron refactorvscron cleanup),还得靠 Agent 自觉维护。Crabtalk 的最终结论是:内容即索引——直接对消息文本建 BM25 倒排索引,用查询词去召回真正讨论过相关内容的会话,无需任何预先分类(设计动机详见 0185-session-search.md)。
💡 一句话概括:
search_memory搜"沉淀下来的记忆条目",search_sessions搜"发生过的对话",两者互补,各司其职。
🧩 工具参数一览:调用 search_sessions 需要知道什么
search_sessions作为 Agent 工具暴露给 LLM,参数定义在 search.rs:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
query | string | 必填 | 要匹配的关键词或短语 |
limit | usize | 5 | 返回的会话命中数上限 |
context_before | usize | 4 | 每条命中消息之前包含几条消息 |
context_after | usize | 4 | 每条命中消息之后包含几条消息 |
agent | string | 无 | 只搜指定 agent 的会话 |
sender | string | 无 | 只搜指定发送者发起的会话 |
官方工具提示词 sessions.md 给 Agent 的使用建议也很实用:先用用户自己的措辞查询,查询词保持 2~6 个单词;命中后如果上下文不够,再加大context_before/context_after。
🔎 BM25 打分:一条消息凭什么排得更靠前
底层是一个零外部依赖、手写实现的小型 BM25 倒排索引,位于 bm25.rs,与search_memory共用同一份代码:
- IDF 公式采用 Lucene 变体:
ln((n - df + 0.5)/(df + 0.5) + 1.0),+1.0保证常见词的 IDF 不为负(bm25.rs) - K1 = 1.2,B = 0.75,即短消息中出现关键词会被更明显地"奖励"
- 分词器按非字母数字字符切分、小写化、过滤单字符词与约 60 个英文停用词(bm25.rs)
在原始 BM25 分数之上,还有两层"加权"让排序更符合直觉:
① 角色权重(index.rs)——同一句话,谁说的更有"分量":
| 消息角色 | 权重 | 设计含义 |
|---|---|---|
| 用户消息 | 1.5× | 用户诉求最值得关注 |
| 工具调用(assistant 发起) | 1.3× | 说明"动手做了解决",是方案落地的信号 |
| 助手普通回复 | 1.0× | 基准 |
| 系统消息 | 0.5× | 降权 |
② 会话级字段加成——标题和摘要参与二次打分(index.rs):
summary(compact 压缩时生成的会话摘要)命中查询词 →3.0×加成title(会话标题)命中查询词 →2.0×加成
所以"标题里就写着 rollback 的会话"会稳稳排在前面,哪怕正文只提过一次。这一点由测试 sessions_search.rs 明确保证。
🪟 窗口化摘录:为什么返回的永远只是"一小段"
搜索最大的敌人是失控的输出:消息里可能藏着大段工具结果、文件内容,返回原始Message对象会瞬间撑爆上下文窗口。因此命中结果被投射成固定小形状SessionHit(hit.rs),并有三条硬性上限(hit.rs):
| 常量 | 值 | 含义 |
|---|---|---|
MAX_SNIPPET_BYTES | 1024 | 单条摘录最大字节数,超长截断并标记truncated |
MAX_WINDOW_ITEMS | 16 | 单个命中最多携带的消息条数 |
MAX_HITS_PER_QUERY | 20 | 单次查询最多返回的会话数 |
窗口构建逻辑在 index.rs:以命中消息为锚点向前取context_before条、向后取context_after条;如果超出 16 条上限,会从两端等量裁切、保留锚点居中。
另一个关键设计是每个会话最多返回一条命中(best-hit-per-session,index.rs)——避免某个超长会话刷屏霸占整个结果列表。想看某个会话的完整内容?用分页的list_messages去钻取,搜索接口刻意不提供"加载整个会话"的原语。
工具返回给 Agent 的最终文本格式(search.rs)长这样,结构清晰、一眼可读:
## cron refactor session: crab_tester_1 · agent: crab · sender: tester updated: 2026-04-25T00:00:01Z · matched message #12 - [user #10] 上次部署超时的问题还在吗 - [tool-call:shell #11] shell({"command": "cron list"}) - [user #12] planning the cron refactor ← 命中🔒 安全细节:哪些内容永远不会进索引
这是整个实现里最值得细看的一节(index.rs)。索引只收录可检索文本extract_indexable_text,规则是:
| 内容类型 | 是否进入 BM25 索引 | 为什么 |
|---|---|---|
| 用户 / 助手消息正文 | ✅ | 核心检索对象 |
工具调用函数名(如shell) | ✅ | 支持"找我上次运行 shell 的会话"这类查询 |
| 工具调用参数、工具返回结果 | ❌ | 常含凭据、文件内容等敏感信息,不能被关键词搜出来 |
| 自动注入条目(环境块、记忆块等) | ❌ | 系统产物,非真实对话 |
对应测试 sessions_search.rs 验证:一段包含Authorization: Bearer sk-very-secret-token的工具结果搜不到Bearer,但搜工具名shell依然能命中那条会话。
⚖️ 注意边界:命中后,窗口摘录里仍会显示工具输出原文——因为搜索到会话就意味着本可以恢复该会话,可见性边界与恢复会话一致,并不额外泄密。
⚡ 性能预算与冷启动重建
RFC 给搜索定了硬性性能目标,并由 criterion 基准测试持续守护(session_search.rs):
| 指标 | 目标 | 规模 |
|---|---|---|
| 查询延迟 p99 | ≤ 50ms | 10 万条已索引消息 |
| 查询延迟 p99 | ≤ 200ms | 100 万条 |
| 单条消息索引开销 | ≤ 1ms | 任意索引规模(纯 CPU) |
| 冷启动重建 | ≤ 500ms(CPU 部分) | 10 万条 |
索引常驻内存,新消息在append_message时增量入索引,所以新对话随时可搜。daemon 重启后的冷启动重建由 rebuild_session_index 完成:它在锁外构建全新索引后原子换入——并发搜索者永远看到旧索引或新索引,绝不会看到"中间的空索引";重建过程中已有会话即可搜索,旧会话随重建推进逐步变得可搜。
📂 源码导读:从工具调用到倒排索引
想深入源码的话,按这条链路读效率最高:
| 环节 | 文件 |
|---|---|
| 工具参数与结果格式化 | crates/crabtalk/src/hooks/sessions/search.rs |
| 工具注册与系统提示词注入 | crates/crabtalk/src/hooks/sessions/mod.rs |
| Agent 侧使用提示词 | crates/crabtalk/prompts/sessions.md |
| Runtime 公开搜索接口 | crates/runtime/src/engine/session_search.rs |
| BM25 会话索引与窗口构建 | crates/runtime/src/sessions/index.rs |
| 命中形状与硬性上限 | crates/runtime/src/sessions/hit.rs |
| 通用 BM25 倒排索引 | crates/memory/src/bm25.rs |
| 行为测试(排序/截断/安全) | crates/runtime/tests/sessions_search.rs |
| 设计 RFC(含性能预算) | docs/src/rfcs/0185-session-search.md |
📌 写在最后
Crabtalk 的会话搜索可以浓缩成三个设计决策:
- 用内容索引取代人工标签——BM25 召回"真正讨论过"的会话,零维护成本;
- 窗口化 + 硬上限——摘录永远有界(1KB 片段 / 16 条窗口 / 20 条命中),保护上下文窗口不被搜索结果反噬;
- 敏感内容不入库——工具参数与工具结果不进倒排索引,关键词检索搜不到凭据。
理解这套机制后,你可以更放心地让长期运行的 Agent"翻旧账"了。🦀
【免费下载链接】crabtalk
The long-running embedded agent
相关推荐
Zoom MCP 会议转录与摘要检索实战:基于 knowledge-work-plugins 的搜索、资产与录制资源工作流
Zoom MCP 会议转录与摘要检索实战:基于 knowledge work plugins 的搜索、资产与录制资源工作流 本指南以 transcript re
AI 技能AI 插件人工智能MCP ClientsPyWxDump微信数据恢复终极指南:从零开始完整备份聊天记录
PyWxDump微信数据恢复终极指南:从零开始完整备份聊天记录 还在为微信聊天记录丢失而烦恼?想要安全备份重要对话却不知如何入手?PyWxDump是一款专门针对
BeautySearch:Windows 搜索窗口美化工具
BeautySearch:Windows 搜索窗口美化工具 项目基础介绍 BeautySearch 是一个开源项目,旨在为 Windows 10 及更高版本的操
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考