# Search routing、X 聚合与 runtime capability

## 调用链与边界

```text
MCP / Pi / DSH fused_search
  → runtime.runFused
      → runtimeSnapshot / resolveSearchRoute
      → fusion.fusedSearch → engineRegistry（Web 候选）
      → community=true: runXSearch（同一 X Core，内部候选模式）
          → official xAI ∥ fallbackXSearch
              → domainSearch → engineRegistry（不调用 runFused）
              → GraphQL / oEmbed（按 X mode 路由）
      → mergeCommunityResults / createXPipeline
      → selectDiverse → 最终 max_results

MCP / Pi / DSH x_search
  → runtime.runXSearch
      → official / fallback retrieval
      → createXPipeline：normalize → dedupe → filter → max_results
```

没有 fused_search ↔ x_search 递归，也没有复制 X retrieval 实现。community 使用 keyword 模式；GraphQL user 与 oEmbed thread 路径仍供直接 x_search 使用。

主要模块：

- `lib/search/results.js`：Web URL/日期规范化、X URL 身份、域名过滤、通用去重、三态过滤、domain/author 多样性选择。
- `lib/search/routing.js`：引擎池、跨池共享策略权重、路由校验、共用 description / 原生参数定义。
- `lib/search/x/x-pipeline.js`：X 特有的数据结构、作者/日期/metadata operators；复用公共函数。
- `lib/search/x/community.js`：只做 Web/X 最终聚合，不负责检索。
- `lib/search/capability.js`：所有宿主与运行时共用的动态能力快照。
- `lib/runtime.mjs`：网络编排、缓存和最终诊断；适配器只映射参数、注册接口和渲染。

本次不改安装、全局 npm 迁移或宿主注册流程。

## 参数正交性

| 参数 | 默认 | 唯一职责 |
| --- | --- | --- |
| `complexity` | `medium` | `simple / medium / complex` 控制预算、至多 1/2/3 个 variants 和默认 basic/basic/advanced depth |
| `engine_pool` | 从兼容 layer 映射 | `free / api / hybrid` 选择默认调用集合 |
| `ranking` | `balanced` | `balanced / research / fresh` 只选择最终 engine-weight preset |
| `engines` | 省略 | 显式覆盖本次调用集合，可跨 pool；不绕过缺 key 或禁用状态 |
| `engine_weights` | 省略 | 覆盖指定引擎的评分权重；不增加或移除任何调用 |
| `community` | `false` | 需要近期开发者/社区声音时额外使用 X Core |

`queries` 是调用方提供的独立角度；query 中的 OR alternatives 也占 variant 预算。没有额外的 LLM 自动改写器。`ranking=fresh` 不隐式修改日期范围、搜索深度或请求参数；时效需求仍用 `recency`。

权重必须是有限非负数。权重为 0 仍调用该引擎；它不能贡献共识分，全零证据不会被元数据恢复。未选择引擎的 override 不生效，也不会使其被调用。显式 `engines` 选到池外引擎时，采用该引擎所属 free/api 池的同名 ranking 权重，再应用 override。空 engines、未知引擎和无效权重直接报错。

### 引擎池与权重

| 引擎 | balanced | research | fresh |
| --- | ---: | ---: | ---: |
| bing | 0.957 | 0.927 | 1.020 |
| ddg | 0.981 | 0.903 | 0.927 |
| yahoo | 0.957 | 0.877 | 0.902 |
| exa-free | 1.004 | 1.085 | 0.951 |
| tavily | 1.049 | 1.105 | 1.084 |
| brave | 1.004 | 0.951 | 1.125 |
| exa | 1.049 | 1.146 | 1.063 |
| anysearch | 1.004 | 1.042 | 0.951 |

同一引擎、同一策略的权重跨池共享。上表为固定八引擎原生先验的对数收缩冷启动值（几何均值为 1），不是实测质量排名；完整精度在 routing.js。free 为 bing/ddg/yahoo/exa-free/anysearch；api 为 tavily/brave/exa/anysearch；hybrid 为去重并集。不可按本次成功/可用引擎重新归一化。后续离线标注评估须包含全 1 权重 neutral baseline。

池成员固定，实际可用性动态读取。API-only 池没有可用 key 时返回空集合与 warnings，不偷偷启用免费池。

`anysearch` 同时属于 free/api 池，hybrid 去重后只调用一次；`ANYSEARCH_API_KEY` 或 keys.json 的 `anysearch` 保存密钥。free 匿名调用，api 要求 key，hybrid 有 key 时用 key、否则匿名。显式禁用/白名单也作用于匿名路径，不降级绕过禁用。匿名限额和网络错误正常报告，不从错误正文自动注册或保存凭据。

## 兼容方案

- 保留原 layer 配置、环境变量、`search_layer` 与 `/web_change`：旧 `free → free`，旧 `api → hybrid`。这是保留旧 api layer 原本包含免费引擎的语义，不是新 API-only 池的定义。
- 显式 `engine_pool` 优先于 layer；MCP/DSH 保留废弃的单次 `layer` alias。
- Core 暂时接受 `complexity=auto`，仍用旧 heuristic，但返回弃用 warning。公开工具 schema 仅列 simple/medium/complex；旧客户端应刷新 schema。
- 保留返回值中的 `layer`、`tier`、`engineStats` 等现有字段，增加 pool、ranking 和实际执行诊断。
- 保留 `fusion.js` 的公共 URL helper re-exports 和 tier table 导出；tier table 现在不再改变引擎集合。

## X 约束、去重与最终选择

1. 所有 X retrieval 来源经过相同 normalize/merge/filter。URL 中的 post ID 优先；现代 Snowflake 可恢复真实时间，优先于模型给出的冲突日期。缺失日期/作者/计数不会被虚构成符合过滤条件的数据。
2. `allowed_x_handles` / `excluded_x_handles` / username 与字段日期做交集；字段 `to_date` 包含整天，query `until:` 仍为排他上界。可验证的 query metadata operators 统一在本地执行；无法验证的约束会提示或排除缺元数据项。
3. X 用 post ID 去重，所以 x.com、twitter.com、mobile、`i/web/status` 与带作者路径不会重复。Web 使用去 tracking/fragment 的 URL key，保留路径大小写、有意义的 query 和端口。
4. community 候选模式不提前执行最终过滤/限量；先与普通 Web leg 中的 X 帖子合并元数据，再统一执行 X 约束，防止普通 Web 结果绕过作者/日期过滤。候选模式有独立缓存键，不会污染公开 x_search。
5. 相同来源不重复计分：同引擎多个 variant 命中的 URL 只保留最佳 rank 贡献；Web/X 两条路都找到同一帖子时，每个引擎只贡献一次。official 或无 Web provenance 的 fallback 使用中性权重 1，不属于可自定义的八个 Web engine weights。
6. 最终使用已入选结果的标题/摘要 shingle 相似度选择，质量分不变；短摘要 Web 回退为每域最多两条，单站点限制豁免；X 按作者最多两条。未知 X 作者使用同一保守桶，不允许靠缺元数据绕过作者多样性。
7. include/exclude domains（含 query 中的 site 规则）作用于全部结果。X/Twitter 域名 aliases 视为同一来源；排除 X 时跳过 community。显式 recency 在社区通道转为 UTC 日期下界，最终也检查普通 Web leg 的 X 帖子。Web recency 仍为软时效偏好/引擎提示，未知日期中立。

这些约束不能使提供方返回未检索到的帖子。GraphQL、oEmbed 和匿名索引均是 best-effort；不会保证完整线程或全平台情绪代表性。

## 动态能力与诊断

统一 capability 包含：当前兼容 layer、默认 pool/ranking/complexity、可用引擎及不可用原因、各池可用成员、X official/fallback 状态。这里的 available 是本地配置就绪，不是实时 HTTP 探测成功。

- MCP：`search-boost://capabilities`，每次读取重新生成的 JSON Resource；不是调用工具的前置步骤。
- Pi：`before_agent_start` 每次更新 `search_capabilities` system-prompt section。已有 search policy 不阻止动态能力刷新，也不重复插入 section。
- DSH：`systemPrompt.section({ text: () => ... })` 每次组装读取同一 capability，保留现有 `search:status` section 名称。

工具静态 description 不写当前可用引擎，避免改 key/layer 后描述失真。

新评分公式、分值迁移、元数据和校准边界见 [fusion-scoring.md](fusion-scoring.md)。

每次 fused 结果包含：

- `scoreVersion`：当前为 consensus-v2.1；min_score 用新质量分尺度，旧阈值需重新校准。

- `enginesUsed`：实际尝试的 Web 引擎（含 X fallback 直接调用的引擎）；失败不从列表隐藏。
- `effectiveWeights`：本次可用且已选择的引擎实际采用的权重。
- `communityUsed`：是否执行了社区通道；不是“找到了 X 证据”或“网络调用成功”的布尔值。
- `warnings`：缺 key/禁用、调用失败、community 降级、约束信息等。
- `engineStats`：attempts、successes、errors；部分 variant 失败不被误报为全部引擎失败。

缓存命中保留原始 provenance，不代表本次重新联网。缓存包含评分版本/完整配置、池、ranking、权重、community、参数与私有能力/凭据 fingerprint；credential 内容或 fingerprint 不会出现在 capability Resource/prompt 中。Web 缓存最长 6 小时，community 聚合缓存 5 分钟；保留原 X 各模式 TTL。取消不缓存为成功；失败的聚合不会作为完整成功长期缓存。

## 示例与验证

```json
{"query":"Node.js fetch API","complexity":"simple","engine_pool":"free"}
```

```json
{"query":"database vector indexing tradeoffs","queries":["vector index benchmark limitations","vector index official documentation"],"complexity":"complex","engine_pool":"api","ranking":"research","engine_weights":{"exa":1.6}}
```

```json
{"query":"Node.js migration developer experience","engine_pool":"hybrid","ranking":"fresh","community":true,"recency":"month","max_results":8}
```

```bash
npm run test:search
npm run test:x
npm run test:fusion
npm run test:adapters
npm run test:mcp
```

测试使用临时 HOME、假引擎/HTTP transport、真实 Core 编排和真实 MCP stdio 协议；不消耗真实 API 额度。原生 Pi/DSH 注册与动态 prompt 用宿主接口替身验证，不能代替真实宿主重载和当前公网服务的连通性测试。
