Hermes Search Pipeline

冒泡进,冒泡出

rg 是发动机,search_files 是控制系统。用户的一句话被收束成可审计的参数,深入文件树;原始命中再经过解析、安全过滤与分页,回到 Agent 成为可行动的答复。

入站:意图 → 执行出站:输出 → 答复实测样本:TypeScript · context=1
就绪 · 点击节点查看真实细节

输入:「帮我找出请求超时配置在哪里,只看 TypeScript」

用户给出语义目标与文件类型约束,没有要求 Agent 猜目录或直接运行任意 shell。

Agent 将“请求超时配置”收束为代码标识符 REQUEST_TIMEOUT_MS,并选择 search_files 的内容搜索模式。*.ts 明确落实“只看 TypeScript”。

{
  "pattern": "REQUEST_TIMEOUT_MS",
  "target": "content",
  "path": "/tmp/hermes-search-pipeline-demo",
  "file_glob": "*.ts",
  "limit": 10,
  "offset": 0,
  "output_mode": "content",
  "context": 1
}

tools/file_tools.py:2550–2615

先规范化 offset/limit;连续相同搜索第 3 次提示、第 4 次阻断;解析任务路径并检查受保护路径;命中“不存在路径”缓存时避免重复 shell;随后调用 file_ops.search(...)

tools/file_operations.py:3231–3278

set -o pipefail; rg --line-number --no-heading --with-filename -C 1 --glob '*.ts' 'REQUEST_TIMEOUT_MS' '/tmp/hermes-search-pipeline-demo' | head -n 210

210 = limit 10 + offset 0 + context 模式额外 200。参数经 shell 转义;Windows 原生路径另行处理;含正则换行时自动启用 multiline。

在已 git init 的演示仓库中,rg --files 实测仅枚举:

src/client.ts
src/secrets.ts
README.md

node_modules/fake-sdk/index.tsignored.log.env.git 未进入枚举。边界:ignore 行为依赖实际仓库与层级语境;仅放置孤立 .gitignore、尚未初始化 Git 时,本演示里的 node_modules 与 ignored.log 并未被排除。

/tmp/hermes-search-pipeline-demo/src/client.ts:1:export const REQUEST_TIMEOUT_MS = 8000;
/tmp/hermes-search-pipeline-demo/src/client.ts-2-
--
/tmp/hermes-search-pipeline-demo/src/client.ts-4-  return fetch(`/api/users/${userId}`, {
/tmp/hermes-search-pipeline-demo/src/client.ts:5:    signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
/tmp/hermes-search-pipeline-demo/src/client.ts-6-  });

-- 是上下文组分隔符,不是搜索命中。

file_operations.py:3282–3373 先分离 rg diagnostics;兼容 Unix/Windows 路径;跳过 --;区分命中行与上下文行;每条 content 最多 500 字符;计算 total 后再按 offset/limit 切页。

file_tools.py:2616–2648 再过滤 credential/token/cache/secret-bearing env 文件,对每条内容调用 redact_sensitive_text(file_read=True),执行 to_dict(densify=True),并追加 omitted 与截断提示。

export const DEMO_API_KEY = "«redacted:sk-…»";

实测 total_count=5matches_format="path-grouped"。上下文行也作为记录进入结果,因此是行 1、2、4、5、6,而不是“2 个关键字命中”。

{
  "total_count": 5,
  "matches_format": "path-grouped",
  "matches_text": "/tmp/hermes-search-pipeline-demo/src/client.ts:\n  1: export const REQUEST_TIMEOUT_MS = 8000;\n  2: \n  4:   return fetch(`/api/users/${userId}`, {\n  5:     signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n  6:   });"
}

Agent 读取路径、行号与上下文:第 1 行是配置定义,值为 8000ms;第 5 行将它传给 AbortSignal.timeout。这一步把“搜索记录”提升为“可回答的定位结论”。

答复示例:请求超时配置位于 src/client.ts:1REQUEST_TIMEOUT_MS = 8000(8 秒);它在 src/client.ts:5 传给 AbortSignal.timeout(...)

同一次搜索,两种视图

切换观察 rg 的行协议如何变成 Hermes 的密集结构。

/tmp/hermes-search-pipeline-demo/src/client.ts:1:export const REQUEST_TIMEOUT_MS = 8000;
/tmp/hermes-search-pipeline-demo/src/client.ts-2-
--  ← 解析时跳过
/tmp/hermes-search-pipeline-demo/src/client.ts-4-  return fetch(`/api/users/${userId}`, {
/tmp/hermes-search-pipeline-demo/src/client.ts:5:    signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
/tmp/hermes-search-pipeline-demo/src/client.ts-6-  });

发动机做什么

rg 遍历候选文件、执行正则并吐出带路径/行号/上下文的文本。它负责快。

控制系统做什么

search_files 约束参数与路径,处理诊断、解析、敏感内容、分页和结果形态。它负责可控。

不要误读计数

total_count=5 是解析后的 5 条记录,包含 3 条上下文;关键字实际出现在第 1、5 行。