04 · 搜索索引、边界与对比
本章先讲一个容易混淆的点——
dir有两套“搜索”,然后收束全局:边界、巧妙之处、横向对比、全局代码地图。
1. 两套搜索,别搞混
初学者最容易困惑的地方:dirctl search 和 dirctl routing search 是两个不同的东西,查的库不一样、能力不一样。
| 维度 | SearchService(本地索引) | RoutingService.Search(路由层) |
|---|---|---|
| 查什么 | 本地数据库里所有记录的元数据 | 本地缓存的远端 peer 标签 |
| 范围 | 本节点存的记录(含自己 push 的) | 别的 peer 发布、被拉取式发现缓存的 |
| 查询能力 | 通配符(web*、*ml*、v1.0.?)、多字段 | 层级前缀标签 + OR 打分 |
| 文件 | server/controller/search.go | server/routing/routing_remote.go |
| 心智 | “翻我自己的索引卡片” | “翻我记下的‘别人有啥’小本本” |
本章讲的是前者(本地数据库二级索引);后者已在 02 章 讲透。
2. 本地搜索索引怎么来的
回忆 01 章:每次 Push 成功,StoreController 顺手调 s.db.AddRecord 把记录加进数据库(server/controller/store.go:401)。这就是本地搜索索引的数据来源——存储是真相、数据库是可查视图(索引失败不影响 Push)。
reconciler 的 Indexer 任务还会兜底:监控本地 OCI 仓库的 tag 快照,发现新 tag 就拉记录、校验、加进搜索库(reconciler/README.md Indexer Task)——保证就算某条记录绕过了 Push 控制器(比如 sync 复制进来的),也能被索引到。
3. 通配查询怎么走
SearchService 有两个方法:SearchCIDs(只返 CID)和 SearchRecords(返完整记录)。两者都把客户端查询翻译成数 据库过滤条件(server/controller/search.go:31 SearchCIDs):
// server/controller/search.go:35-49(SearchCIDs 节选)
filterOptions, err := databaseutils.QueryToFilters(req.GetQueries())
// ... 追加 limit / offset / 排序
recordCIDs, err := c.db.GetRecordCIDs(filterOptions...)
查询类型很丰富,支持通配(proto/agntcy/dir/search/v1/record_query.proto:26 RecordQueryType):name、version、skill name/id、locator、module name/id、domain name/id、created_at、author、schema_version,以及一个 VERIFIED(按“名字是否已校验”过滤)。通配规则:* 任意多字符、? 恰好一字符(:18-22)。注意数值字段(skill_id/domain_id/module_id)只支持精确匹配,无通配。
4. 巧妙之处(可借鉴的技术)
-
内容寻址 + Push 时双算 CID 自洽校验。 ID 即防伪封条,且把“序列化漂移”这类隐蔽 bug 在写入时就拍死(
server/store/oci/oci.go:212)。任何做不可变内容存储的系统都能借鉴这招“写入即校验”。 -
拉取式发现:DHT 只放‘谁有’,标签现抽现缓存。 用 DHT 最可靠的 provider 子系统 + 按需 RPC 拉取,绕开 DHT k-closest 的标签传播不可靠问题,换来上百 peer 的可扩展性,且标签永不与内容漂移(
server/routing/routing_remote.go:682,ROUTING.md)。 -
混合快慢路径(GossipSub + DHT+Pull)。 常态走 GossipSub 广播(~15ms、~95% 带宽节省),竞态/丢失时 DHT 通知触发拉取兜底;用密码学已验证的 peer ID 写缓存防投毒(
:826-832)。 -
OR 逻辑 + min-score 一个 API 表达宽松/严格,加服务端查询去重防客户端刷分(
:311、server/routing/constants.go:52)。 -
可信做成 referrer 叠加层。 签名/校验不改记录、不挡主路径,多签名者可并存,主 CID 恒定(见 03)。
-
存储是真相、索引可重建。 数据库索引失败不阻塞写入,reconciler 兜底重建(
server/controller/store.go:401、Indexer 任务)。
5. 边界与局限(诚实)
-
它不运行 agent。
dir只登记/分发关于 agent 的描述;agent 怎么跑、怎么调用,不在范围内(README “Source tree”、Features 全是发现/存储/信任)。 -
单条记录 ≤ 4MB。 要塞进一个 gRPC 请求(
api/core/v1/record.go:20、store proto:18)。大产物得放别处、记录里只放 locator。 -
Search 给的是 CID,不是内容。 路由层 Search 只返回引用,要
pull才有字节(proto/.../routing_service.proto:39)。 -
远端标签是缓存,可能陈旧或缺失。 拉取式发现依赖后台维护;
GetProviderCount是本地点态估计、不是实时全网查询(server/routing/routing.go:175、proto:145)。proto 也直说 Search 结果“可能 stale 或不存在”(routing_service.proto:35-37)。 -
OASF schema 是外部依赖。 记录“合法”的定义在外部
agntcy/oasf仓库 + 远程 schema URL;本仓库只调校验器(server/server.go:149)。schema 服务慢/不可达时校验有 30s 超时(api/core/v1/record.go:24)。 -
名字校验需要
http(s)://前缀 + 已签名才进入流程,普通名字不被校验(强制点在server/database/gorm/naming.go:133:SQLname LIKE 'http://%' OR LIKE 'https://%';reconciler/README.md Name Task 第 1 步亦述)。
6. 横向对比(同 shelf 兄弟)
dir 在 ai-protocol-reference(协议库)里属于**“agent 互联网基础设施”**这一类。它和典型的“agent 间通信协议”关注点不同:
| 关注点 | dir(本项目)的取舍 |
|---|---|
| 核心抽象 | 目录/发现——“按能力找到 agent”,不定义 agent 怎么对话 |
| 寻址 | 内容寻址(CID) + 人类可读名字(Docker 风格 name:version@cid) |
| 分发 | P2P / libp2p DHT,去中心、无单点目录服务器 |
| 信任模型 | Sigstore 签名 + 域名所有权(JWKS),可验证声明、叠加式 |
| 存储底座 | 复 用 OCI 镜像仓库(ORAS),而非自造存储 |
| 与运行解耦 | 刻意只管发现/分发,不管运行时(运行时是 AGNTCY 其他组件如 runtime/discovery 的事,见 proto/agntcy/dir/runtime/v1/) |
相比“把元数据塞进中心化注册表”的做法,dir 的取舍是用内容寻址 + DHT 换去中心化和防篡改,代价是最终一致(缓存可能陈旧)和后台维护成本。
7. 学习路径回顾
- 01 内容寻址存储 — CID 是地基。
- 02 路由与发现 — 拉取式发现是核心创新。
- 03 可信:签名与命名 — referrer 叠加层。
- [04 本章] — 两套搜索的区分 + 全局边界。
8. 全局代码地图(导航索引)
| 主题 | 文件 | 符号 |
|---|---|---|
| 服务进程组装(注册所有子服务) | server/server.go | New / Run |
| 内容寻址 / CID | api/core/v1/record.go、api/core/v1/cid.go | (*Record).GetCid / ConvertDigestToCID |
| OCI 存储后端 | server/store/oci/oci.go | (*store).Push / Pull / Lookup |
| Store gRPC 控制器 | server/controller/store.go | (storeCtrl).Push / pushReferrer |
| 路由顶层(Publish/List/Search) | server/routing/routing.go | (*route) 方法集 |
| 远端路由 / 拉取式发现 | server/routing/routing_remote.go | (*routeRemote).handleCIDProviderNotification |
| 增强标签键 | server/routing/label_utils.go | BuildEnhancedLabelKey |
| 本地数据库搜索 | server/controller/search.go | (searchCtlr).SearchCIDs / SearchRecords |
| 查询类型(通配) | proto/agntcy/dir/search/v1/record_query.proto | RecordQueryType |
| 签名(客户端) | client/sign.go | (*Client).Sign |
| 命名/校验服务 | proto/agntcy/dir/naming/v1/naming_service.proto | NamingService |
| 后台 reconciler(索引/同步/校验) | reconciler/README.md | Indexer / Regsync / Name Task |
| 路由设计权威文档 | server/routing/ROUTING.md | — |
| CLI 子命令注册 | cli/cmd/root.go、cli/cmd/ | RootCmd.AddCommand |