# TKMind Search Capability 插拔式接入架构与开发计划 日期: 2026-07-10 状态: 设计评估完成,等待用户明确指令后再开始开发 ## 0. 文档目的 本文定义如何在现有 Memind Portal + Goose Runtime 架构中,以插拔方式增加以下四项外部信息能力: - `search.web`: 公共网页搜索。 - `search.news`: 新闻和时效信息搜索。 - `search.code`: GitHub 等公共代码与开源项目搜索。 - `search.read`: 根据 URL 读取并清洗网页正文。 本文只描述架构、边界、协议和后续开发计划。本阶段不修改业务代码、不启动新服务、不调用生产 Provider、不改变生产配置,也不执行发布。 第一阶段不考虑 Skill Store、第三方开发者发布、商业化、Credit 扣费、收益分成和第三方代码托管。 ## 1. 结论 现有项目已经具备适合 Search Capability 的扩展入口,不需要重构聊天和会话主链路。 推荐目标: ```text H5 / Web / WeChat | v 现有 Agent Run Gateway | v 现有 tkmind-proxy | v Goose | | 仅在功能开关和用户能力同时允许时注册 v tkmind-search MCP Extension | v Search Hub - request validation - fixed provider routing - result normalization - citation construction - URL safety - timeout / limits | +--> SearXNG: web / news +--> GitHub API: code +--> Reader Adapter: read +--> optional commercial provider adapters ``` 核心决策: 1. 不替换现有 Goose `platform/web` 扩展。 2. 不覆盖现有 `web` Skill。 3. 不覆盖现有本地代码搜索 `search` Skill。 4. 新增独立扩展名 `tkmind-search`,默认关闭。 5. Search Hub 不进入 Portal HTTP 主请求链路。 6. Search 失败只能影响当前工具调用,不能导致会话、SSE 或 Agent Run 基础设施失效。 7. 第一阶段使用固定 Provider 路由,不引入 LLM Router 和动态评分。 ## 2. 现有项目基础 ### 2.1 已有能力注入机制 `capabilities.mjs` 已经通过 `buildAgentExtensionPolicy()` 生成 Goose `extension_overrides`。 当前 `web` capability 会注入: ```json { "type": "platform", "name": "web", "available_tools": ["web_search", "fetch_url"] } ``` 这证明现有架构已经把“用户能力授权”和“Goose 实际工具集合”连接起来。新 Search 不应另建一条绕过该策略的入口。 ### 2.2 已有禁用和会话对账机制 `policies.mjs` 会在 `network_egress=deny` 时移除联网能力。 `session-reconcile.mjs` 会在恢复会话时: - 获取当前会话扩展。 - 移除不再允许的扩展。 - 添加缺失的扩展。 - 在扩展集合变化时重启 Goose session runtime。 因此,新 Search Capability 可以复用当前权限和会话对账体系,形成可验证的关闭语义。 ### 2.3 已有搜索相关 Skill 当前仓库中两个名称必须保留原义: - `skills/web`: Goose 内置 `web_search` 和 `fetch_url` 的使用说明。 - `skills/search`: 使用 ripgrep 搜索当前工作区代码和文件,不是公网代码搜索。 为避免命名冲突,新能力内部使用: - Goose extension: `tkmind-search` - Capability grant: `search_external` - Search protocol capability: `search.web` / `search.news` / `search.code` / `search.read` - 可选用户 Skill 文档名: `search-external` ### 2.4 现有路由基础 当前 `chat-intent-router.mjs` 已将实时新闻、天气、行情等需要联网查询的请求判定为 `agent_orchestration`。 第一阶段不扩大自动路由范围。测试和灰度优先通过显式选择 Search Skill 或专用测试账号进入 Agent,避免普通问答被误路由。 ## 3. 硬性隔离边界 ### 3.1 保持不变的模块 第一阶段禁止因 Search 接入而改变以下模块的职责或协议: - H5 发送消息和订阅事件的顺序。 - `POST /agent/runs` 的请求语义。 - Agent Run 状态机。 - `tkmind-proxy` 作为 Portal 到 goosed 的唯一适配器边界。 - Session Broker ownership 和 goosed target mapping。 - Session SSE 和 Run SSE 协议。 - `chat-finish-sync` 的消息 merge 行为。 - Memory V2 的读取、写入和压缩行为。 - MindSpace 页面、文件和发布链路。 - UserDataSpace 数据访问。 - MySQL 用户、会话、订单和计费表。 - 现有 `platform/web` 和本地 `search` Skill。 ### 3.2 Search Hub 允许做的事情 Search Hub 只负责: - 校验搜索或读取请求。 - 根据 capability 使用固定规则选择 Provider。 - 调用公共搜索、GitHub 或 Reader Provider。 - 标准化结果。 - 去除明显重复 URL。 - 构建来源和引用对象。 - 执行超时、响应大小和 URL 安全限制。 - 返回结构化结果或统一错误。 ### 3.3 Search Hub 禁止做的事情 Search Hub 不得: - 创建、恢复或修改 Goose session。 - 写入 Agent Run 状态。 - 直接写 Memory V2。 - 直接写 MindSpace 页面或文件。 - 读写 UserDataSpace。 - 读取完整聊天记录。 - 直接访问用户业务 MySQL 表。 - 接管 Goose 最终回答。 - 把搜索结果自动保存为长期资产。 - 因 Provider 故障修改 Portal 或 Goose 服务状态。 ## 4. 运行形态 ### 4.1 第一阶段: 独立 stdio MCP 第一阶段建议增加一个独立进程运行的 MCP Extension: ```text Goose session -> stdio MCP: tkmind-search -> Search Hub modules -> outbound HTTPS ``` 优点: - 复用仓库已经验证过的 stdio MCP 接入方式。 - Search 代码不进入 Portal 进程请求处理路径。 - MCP 进程异常不会直接终止 Portal。 - 关闭扩展后 Goose 不启动对应进程。 - 不新增公网监听端口。 限制: - 每个运行实例的缓存和健康状态不天然共享。 - 多会话下可能产生多个 MCP 子进程。 - Provider 限流第一阶段只能依赖 Provider 和轻量本地限制。 这些限制可以接受,因为第一阶段目标是安全验证四项能力,而不是建设大规模 Search 平台。 ### 4.2 后续可选: 内部 Search Sidecar 当调用量需要共享缓存、全局限流和统一 Provider 熔断时,可以演进为: ```text Goose -> tkmind-search MCP bridge -> private Search Hub sidecar -> Providers ``` Sidecar 只能监听内部地址,并且不属于第一阶段范围。 ## 5. Goose 工具设计 推荐使用一个扩展、两个工具,而不是四个独立插件。 ### 5.1 `tkmind_search` 负责: - `search.web` - `search.news` - `search.code` 请求示例: ```json { "capability": "search.web", "query": "Goose Agent latest release", "language": "zh-CN", "region": "CN", "freshness": { "type": "days", "value": 30 }, "limit": 10 } ``` ### 5.2 `tkmind_read` 负责: - `search.read` 请求示例: ```json { "url": "https://example.com/article", "max_chars": 30000, "include_metadata": true } ``` `search.read` 不与搜索请求共用 `query` 字段。Reader 的输入、SSRF 风险和响应上限与搜索不同,应保持独立工具 Schema。 ## 6. 统一返回协议 搜索结果最小结构: ```json { "request_id": "srch_example", "status": "success", "capability": "search.web", "provider": "searxng", "query": "Goose Agent latest release", "results": [ { "id": "result_001", "type": "webpage", "title": "Example title", "url": "https://example.com/article", "snippet": "Example snippet", "published_at": null, "retrieved_at": "2026-07-10T00:00:00Z", "source_name": "Example", "language": "en", "provider_rank": 1, "metadata": {} } ], "citations": [ { "citation_id": "cite_001", "result_id": "result_001", "url": "https://example.com/article", "title": "Example title" } ], "timing": { "duration_ms": 800 } } ``` Reader 结果最小结构: ```json { "request_id": "read_example", "status": "success", "capability": "search.read", "url": "https://example.com/article", "final_url": "https://example.com/article", "title": "Example title", "content": "Cleaned article text", "content_type": "text/html", "content_hash": "sha256:...", "retrieved_at": "2026-07-10T00:00:00Z", "trust_level": "untrusted_external_content", "truncated": false } ``` Provider 自带摘要只能作为结果字段,不能直接作为 TKMind 最终答案。最终回答继续由 Goose 生成。 ## 7. Provider 方案 ### 7.1 第一阶段推荐 | Capability | 默认 Provider | 说明 | |---|---|---| | `search.web` | SearXNG | 自建、固定配置、返回标准化网页结果 | | `search.news` | SearXNG news category | 第一阶段复用同一搜索服务 | | `search.code` | GitHub Search API | 搜索 repository、code、issue、release 等 | | `search.read` | Readability Adapter 或受控 Reader | 读取 URL 并输出正文和元数据 | SearXNG 的生产部署和对外服务方式需要在实施前完成许可证合规确认。 ### 7.2 可选 Provider Brave、Tavily、Jina Reader 可以后续作为 Provider Adapter 接入,但不应成为第一阶段启动条件。 第一阶段不做多 Provider 并行聚合,不做质量动态评分,也不做自动商业 Provider 降级。 ## 8. 功能开关和授权 ### 8.1 建议开关 ```text TKMIND_SEARCH_ENABLED=false TKMIND_SEARCH_PROVIDER_SEARXNG_ENABLED=false TKMIND_SEARCH_PROVIDER_GITHUB_ENABLED=false TKMIND_SEARCH_READER_ENABLED=false ``` ### 8.2 生效条件 普通用户只有同时满足以下条件才能获得新扩展: ```text TKMIND_SEARCH_ENABLED = true AND user capability search_external = true AND network_egress = allow ``` 任意条件不满足时,不得把 `tkmind-search` 加入 `extension_overrides`。 ### 8.3 关闭语义 当 `TKMIND_SEARCH_ENABLED=false` 时: - 不注册 `tkmind-search` Extension。 - 不启动 Search MCP 子进程。 - 不读取 Provider Secret。 - 不产生 Search 外部网络请求。 - 不改变现有 `platform/web`。 - 不影响现有 Skill、MCP、会话、SSE、Memory、MindSpace 和 UserDataSpace。 - 老会话恢复时,由现有 session reconcile 移除 `tkmind-search`。 ### 8.4 灰度原则 第一阶段只允许专用测试账号开启 `search_external`。 禁止一开始修改默认用户能力,也禁止对所有用户替换现有 `web` capability。 ## 9. 安全要求 ### 9.1 URL 和 SSRF Reader 必须拒绝: - `localhost` 和回环地址。 - RFC1918 私网地址。 - Link-local 和云元数据地址。 - Docker、Colima、Kubernetes 和宿主机内部域名。 - 非 HTTP/HTTPS 协议。 - 解析后落入受限地址的域名。 每次重定向都必须重新执行 URL 和解析后 IP 校验。还需要限制: - 最大重定向次数。 - 最大响应体积。 - 请求总超时。 - 允许的 MIME 类型。 - 压缩后解包体积。 ### 9.2 Prompt Injection 所有 Reader 正文必须标记: ```json { "trust_level": "untrusted_external_content" } ``` 网页内容只能作为资料,不得改变系统指令、请求其他工具、读取用户数据或执行命令。 ### 9.3 数据最小化 Provider 请求不得包含: - 真实 `user_id`。 - `tenant_id`。 - 手机号、邮箱或用户名。 - 完整会话内容。 - Memory V2 内容。 - MindSpace 私有文件。 - UserDataSpace 数据。 Provider 只接收搜索参数、随机 request ID 和必要的 Provider 鉴权信息。 ### 9.4 Secret Provider Key 只能从运行环境或 Secret 引用读取,禁止写入仓库、Skill 文档、日志和 Goose 对话上下文。 ## 10. 超时和错误隔离 第一阶段建议: | 操作 | 超时上限 | |---|---:| | Web/News Search | 10 秒 | | Code Search | 10 秒 | | Reader 单页 | 10 秒 | | MCP 工具总调用 | 12 秒 | 统一错误示例: ```json { "status": "error", "error": { "code": "PROVIDER_TIMEOUT", "message": "Search provider timed out", "retryable": true } } ``` Search MCP 不得主动重启 Portal 或 goosed,不得修改 Agent Run 数据。Provider 失败应作为普通工具错误返回,由 Goose 决定是否解释、重试一次或继续回答。 ## 11. 可观测性 第一阶段只记录技术指标,不接用户扣费: - request ID。 - capability。 - Provider 名称。 - 成功或错误码。 - 响应耗时。 - 结果数量。 - 是否触发 Reader。 - 是否截断。 默认不记录完整搜索词。调试环境需要查询内容时,应显式开启并做脱敏处理。 Search 日志应与 Portal 会话日志分离,避免 Provider 波动污染现有服务健康判断。 ## 12. 对现有项目的预期影响 ### 12.1 功能关闭时 预期影响为零: - Goose 工具列表不变化。 - Portal 请求链路不变化。 - 会话和 SSE 不变化。 - 不新增网络请求。 - 不新增运行进程。 ### 12.2 测试账号开启时 只影响该账号的 Goose 工具集合: - Goose 可以调用新 Search MCP。 - 搜索轮次可能增加 1 到 12 秒延迟。 - 结构化结果会占用一定会话上下文 Token。 - Provider 故障只影响当前搜索工具调用。 ### 12.3 需要避免的间接影响 - 不要扩大聊天 Router 规则导致普通问答大量进入 Agent。 - 不要让前端只根据 Skill grant 显示搜索入口,而忽略有效 capability 和 `network_egress`。 - 不要让新旧搜索工具使用相同名称。 - 不要把 Search Provider 健康状态加入 Portal 总体存活条件。 - 不要在 Search 稳定前关闭现有 `platform/web`。 ## 13. 开发计划 所有开发阶段均等待用户后续明确指令。每个 Patch 必须可以单独测试和停止,不自动进入下一阶段。 ### Patch 0: 基线确认 目标: - 记录开发前分支和工作区状态。 - 确认现有 capability、session reconcile、Agent Run 和 SSE 测试基线。 - 确认当前 goosed 对自定义 stdio MCP 配置字段的实际兼容性。 产物: - 基线测试结果。 - 不改业务行为的接入点确认。 停止条件: - 现有关键测试未通过。 - goosed 当前版本无法可靠加载独立 stdio MCP。 ### Patch 1: 协议和纯函数 目标: - 定义 Search、Read、Result 和 Error Schema。 - 实现请求校验、URL canonicalization、结果标准化和错误映射纯函数。 - 不连接任何 Provider。 建议新增范围: ```text search-capability/ schemas/ search-errors.mjs request-validator.mjs result-normalizer.mjs citation-builder.mjs ``` 验收: - Schema 正反例单测。 - Provider 原始结果不能穿透到 Goose。 - 所有成功结果必须包含来源 URL。 ### Patch 2: Search MCP 空运行时 目标: - 新增独立 `tkmind-search` stdio MCP。 - 暴露 `tkmind_search` 和 `tkmind_read`。 - Provider 未启用时返回 `CAPABILITY_DISABLED`。 - 默认不开启、不注入任何用户会话。 验收: - MCP initialize、tools/list、tools/call 单测。 - MCP 子进程异常不影响 Portal 测试。 - 功能关闭时 extension policy 与开发前完全一致。 ### Patch 3: SearXNG Web/News Adapter 目标: - 接入 `search.web` 和 `search.news`。 - 使用固定 Provider 路由。 - 完成超时、结果上限和标准化。 验收: - 使用本地 mock Provider 完成确定性测试。 - Provider 超时、空结果、无效 JSON 和 5xx 均返回统一错误。 - 新闻结果保留发布时间;未知发布时间不得伪造。 ### Patch 4: GitHub Code Adapter 目标: - 接入 `search.code`。 - 与现有本地 `search` Skill 保持名称和行为隔离。 验收: - Repository、Issue 和 Code 等结果映射为统一结构。 - API 限流返回可重试错误。 - 不把 GitHub Token 返回到日志或工具结果。 ### Patch 5: Reader 和 URL Safety 目标: - 接入 `search.read`。 - 实现 URL Safety Validator。 - 增加重定向、MIME、体积和正文长度限制。 验收: - 回环、私网、metadata、IPv6 特殊地址和重定向到私网均被拒绝。 - 非 HTML/允许类型被拒绝或明确标记。 - 正文统一标记为不可信外部内容。 - 超大页面被截断且返回 `truncated=true`。 ### Patch 6: Capability 和功能开关接入 目标: - 增加默认关闭的 `search_external` capability。 - 增加全局和 Provider 功能开关。 - 在 `buildAgentExtensionPolicy()` 中条件化注入 `tkmind-search`。 - 复用 `network_egress` 和 session reconcile。 验收: - 默认用户 extension policy 不变化。 - 只有三层条件全部满足时才注入扩展。 - 开关关闭后,恢复会话会移除扩展。 - 现有 `web` 和 `search` Skill 测试不变化。 ### Patch 7: 测试账号本地端到端验证 目标: - 只为专用测试账号开启能力。 - 验证 Search 工具调用、结果引用和失败隔离。 - 不改变默认用户能力。 验收场景: 1. 普通聊天不调用 Search。 2. Web 搜索返回来源。 3. News 搜索保留时间信息。 4. Code 搜索不调用本地 ripgrep Skill。 5. Reader 拒绝私网 URL。 6. Provider 超时后会话仍可继续。 7. Search 关闭后同一账号恢复为原工具集合。 8. Run SSE 和 Session SSE 的终态行为不变。 ### Patch 8: 可选 UI 和窄范围路由 只有后端本地验证稳定后才评估: - 是否增加独立“联网搜索”入口。 - 前端是否按有效 capability 而不是单纯 Skill grant 展示。 - 是否增加窄范围、可关闭的自动 Search 意图规则。 第一阶段可以完全不改 UI,通过显式测试请求验证后端能力。 ## 14. 测试与回归范围 实际开发时至少需要覆盖: - Search Schema 和 Adapter 单测。 - MCP 协议单测。 - Capability policy 单测。 - Session reconcile 增删扩展单测。 - `tkmind-proxy` 会话启动和恢复测试。 - Agent Run Gateway 回归测试。 - SSE taxonomy 和 Finish sync 回归测试。 - 现有 `web`、本地 `search` 和 chat skill 路由测试。 - URL Safety 专项测试。 如果任何改动触及 MindSpace 回归守卫路径,必须按照仓库 `AGENTS.md` 执行对应 verify;本方案原则上不要求触及这些路径。 ## 15. 回退方案 本地或后续灰度发现问题时,按以下顺序回退: 1. 设置 `TKMIND_SEARCH_ENABLED=false`。 2. 确认新会话不再包含 `tkmind-search`。 3. 恢复测试账号会话,确认 reconcile 移除扩展。 4. 保留现有 `platform/web`,继续提供原搜索能力。 5. Search MCP 和 Provider 不进入 Portal 健康检查,不需要重启现有服务完成逻辑回退。 代码回退不得使用脏工作区或直接修改生产文件,仍须遵守仓库开发和发布规范。 ## 16. 第一阶段明确不做 - Skill Store。 - 第三方开发者接入。 - 用户支付和 Search Credit。 - 开发者收益结算。 - Tavily Deep Research 编排。 - 多 Provider 并行聚合。 - AI Provider Router。 - LLM 全量重排。 - Platform Hosted 第三方代码。 - 企业私有搜索。 - 搜索结果自动写 Memory 或 MindSpace。 - 替换或删除现有 Goose `platform/web`。 - 修改现有 Run SSE 和 Session SSE 协议。 ## 17. 开发启动闸门 开始任何代码开发前,必须再次获得用户明确指令。 开发指令不自动授权: - `git push`。 - 合并 `main`。 - 构建或发布 `103` runtime/artifact。 - 修改生产配置。 - 启动生产 Provider。 - 执行任何生产动作。 开发完成后必须先完成对应本地测试和 verify,再由用户决定下一步。