English 书架

第 18 章:Skills、Plugins、Connectors 与类型化扩展

阅读契约: 本章回答一个问题:当 Codex turn 被 extensions 塑形时,哪一层贡献了 instruction,哪一层贡献了 packaging,哪一层贡献了 hosted-app metadata,哪一层贡献了 compiled prompt state?阅读时跟住四个 planes:skills、plugins、connectors、typed contributors。读完后,应该能判断一个 capability 是 discovery summary、package install、account access gate,还是 compiled process contribution。

扩展打包平面:分开 skills、plugins、connectors、typed extensions、trust checks 与 context injection
Skills、plugins、connectors、apps 和 typed contributors 是 packaging planes;它们以受治理的 context 或 tools 进入 turn。

源码边界: 本章只有在链接到固定 Codex commit 或本章源码地图的 files、structs、enums、constants、schemas、loading functions 时,才把说法视为 verified source。把这些机制概括成 extension planes 与 trust boundaries,是从可见 owner 得出的 surrounding contract inference。本章不声称所有 plugin marketplace、hosted app 或 typed contributor 共享一套隐藏 runtime。

第 17 章把 MCP 作为 external tools 的 protocol boundary。本章上移一层。一个 tool 能被 routing 之前,Codex 必须先决定哪些 workflow instructions、package contributions、hosted app identities 和 in-process prompt fragments 可以塑造 thread。

你在这里:external tool calls 已经有清晰的 provenance 与 routing boundary。

问题:agent platform 需要 reusable instructions、packaged assets、hosted app metadata 和 compiled contributors,但它们的 trust model 与 lifecycle 不同。

心智模型:Codex 有四个汇合的 extension planes:skills 教行为,plugins 分发 contributions,connectors 描述 hosted app access,typed extensions 通过显式 in-process APIs 贡献内容。

这些 surface 彼此相关,但不能互换。Skill 主要是 instruction/workflow unit。Plugin 是 distribution package,可以贡献 skills、MCP server definitions、app connector IDs、hooks 和 interface metadata。Connector 是 hosted app directory metadata 加 account-scoped accessibility。Typed extension 编译进进程,通过窄 API 贡献内容。

只要每个 plane 回答不同问题,架构就清楚:

Plane它回答的问题源码中可见的 ownerBoundary pressure
Skills哪些 instructions 与 workflow files 可用?skills manager 与 metadata modelprompt budget 与 invocation discipline
Pluginscontribution assets 如何到达并保持 package scope?plugin manifest 与 plugin managerpackage trust、path validation、sync failure
Connectors哪些 hosted apps 已知且 account-accessible?connector directory cache 与 app metadatadirectory knowledge vs usable capability
Typed extensionscompiled code 可以添加哪些 prompt fragments?extension API typescompile-time contract,而不是 dynamic code loading

不变量是:任何 plane 都不该静默变成其他所有 planes。Plugin 可以带来 skill root,但 skill loader 仍决定如何 discovery 和 disable skills。Connector 可以描述 app,但 account access 仍决定 tools 是否可用。Typed prompt fragment 可以贡献 text,但它通过 explicit slots,而不是任意 runtime mutation。

一、Skills:带预算的 instruction

Skill 是有名称的 instructions、metadata、policy、可选 interface data、可选 dependencies,以及声明它的 SKILL.md path。源码 model 很直接:SkillMetadata 包含 namedescriptioninterfacedependenciespolicypath_to_skills_mdscope 和可选 plugin_idSkillLoadOutcome 则把 loaded skills、errors、disabled paths、roots、file systems 和 implicit invocation indexes 分开。

Shape-level 上,一个 loaded skill 不只是 text:

{
  "metadata": {
    "name": "review-pr",
    "description": "Review a pull request",
    "scope": "repo",
    "plugin_id": null,
    "policy": { "allow_implicit_invocation": true }
  },
  "load_outcome": {
    "enabled": true,
    "implicit_indexes": ["scripts dir", "doc path"],
    "errors": []
  }
}

这就是 skill budget 的理由。如果每个 skill body 永远进入 model view,那么 extension 就会变成永久 prompt 膨胀。即使不掌握所有 prompt-rendering 细节,源码也已经显示出生命周期:discover roots、load metadata、按 product 与 disabled paths 过滤、构建 implicit indexes、缓存 outcome。

1.1 Discovery、disablement 与 implicit invocation 是不同阶段

SkillsManager::skills_for_config() 按 effective skill-relevant config state 缓存,避免共享同一目录的 sessions 因 overrides 不同而互相污染。build_skill_outcome() 从 roots 加载、按 product 过滤、解析 disabled skill paths,并 finalize implicit indexes。finalize_skill_outcome() 再从允许 implicit invocation 的 skills 构建 script/doc-path indexes。

这些阶段保护不同不变量:

PhaseRuntime pressure保护的不变量
root selectionsystem、user、admin、repo、plugin roots 可能重叠scope 显式,cache keys 包含 roots
metadata loadinginvalid skill files 不该变成 instructionserrors 保持 load outcomes,不变成 prompt text
disablementuser/config policy 可以 suppress skilldisabled paths 被保留并检查
implicit indexingworkflow 可能不被点名就触发只有 enabled 且 policy 允许 implicit invocation 的 skills 会建索引

简单做法是拼接所有发现的 instructions;这会让 invalid、disabled 或 irrelevant workflows 拥有与当前任务相同的权威。

Optional context budget board:skills、plugins、memory、hooks、tool output 和 images 竞争 model window space
Extension loading 也是 context-budget 问题:known material 仍要决定是否进入当前 model view。

二、Plugins:package 是 classifier,不是 runtime authority

Plugins 回答另一个问题:extension assets 如何到达、如何保持 package scope、如何贡献到其他 planes?Manifest model 让这一点可见。PluginManifest 有 identity、interface fields 和 PluginManifestPaths。这些 paths 可以指向 skillsmcp_serversappshooks

Shape-level 上,plugin manifest contribution 是 package boundary:

{
  "name": "example-plugin",
  "paths": {
    "skills": "./skills",
    "mcpServers": "./mcp.json",
    "apps": "./apps.json",
    "hooks": "./hooks.json"
  },
  "interface": {
    "displayName": "Example",
    "defaultPrompt": ["Start with the plugin's workflow"]
  }
}

Loader 不应该把这些字符串当作 filesystem authority。resolve_manifest_path() 要求 path 以 ./ 开头,拒绝空的 ./,拒绝 ..,也拒绝会逃出 plugin root 的 components。Interface default prompts 也在接受前限制数量和长度。

2.1 Plugin sync 是 degraded state,不是 thread blocker

Plugin manager 源码还显示了第二个压力:remote state 可以独立于 thread 失败。PluginsManager 拥有 plugin store、marketplace caches、remote installed plugin caches、remote sync lock、cached enabled outcome 和 product restriction context。plugins_for_config_with_force_reload() 在 effective config version 与 hook setting 匹配时返回 cached outcome,否则加载 configured plugins 并记录 load errors。

这就是 optional packaging infrastructure 应有的 lifecycle:

plugin config
  -> local store / remote cache
  -> manifest parse and path validation
  -> contribution classification
  -> skill roots, MCP configs, app IDs, hooks
  -> downstream loaders apply their own policy

Plugin layer 负责 classify 和 validate contributions。它不应该静默让 hook 变 trusted、让 skill 变 enabled,或让 hosted app 变 account-accessible。

三、Connectors:directory knowledge 不是 usable capability

Connectors 规范化 hosted app directory metadata。Connector source 首先定义了 cache key,包含 base URL、account ID、ChatGPT user ID,以及是否 workspace account。ConnectorDirectoryCacheKey 因而是 account-scoped,不是全局 app catalog key。

list_all_connectors_with_options() 也展示 directory lifecycle:非 forced 时使用未过期 in-memory cache,拉取 directory 和 workspace pages,merge apps,转成 AppInfo,normalize names/descriptions,写 cache,并在这个 directory 阶段把 is_accessible 设为 false

最后这个细节就是边界:connector 可以先被知道,但还不可用。

StateCodex 知道什么不能假设什么
directory connectorapp ID、display metadata、logo/branding、install URL当前 credentials 能调用 tools
accessible connectoraccount-specific access information每个 tool 都应该 direct 进入 model request
hosted app MCP toolconnector-backed tool metadataorigin/provenance 等同于 local MCP server

这就是第 17 章的 MCP exposure 必须按 connector 和 app-tool enablement 过滤 hosted app MCP tools 的原因。Connector plane 提供 directory 与 access facts;model-facing tool boundary 在更后面。

Tool spec planner 接收 config、features、MCP、dynamic 和 hosted 输入,并把 model specs 与 registry handlers 分成两条输出
Extension planes 只在 planning 后汇合:model-visible specs 与 registry handlers 仍是不同输出。

四、Typed extensions:compile-time slots 代替 dynamic code

Typed extensions 是动态性最低的 plane。Prompt contributor API 很小:PromptSlot 枚举 DeveloperPolicyDeveloperCapabilitiesContextualUserSeparateDeveloper 等 slots。PromptFragment 只携带 slot 和 model-visible text。

Shape-level:

{
  "slot": "DeveloperCapabilities",
  "text": "A compiled contributor may add a bounded capability note here."
}

这个窄形状正是价值所在。Typed extensions 适合 extension author 与 runtime author 共享 compile-time contract 的场景。它们不是 plugins 的替代品,因为它们不解决 distribution、marketplace sync、plugin-relative assets 或 hosted app access。它们解决的是另一件事:不用 arbitrary runtime code loading,也能做 in-process contribution。

五、Loading pattern:边界内严格,边界外 fail-soft

四个 planes 想长期稳定,就要各自验证自己的 boundary,并用各自的 vocabulary 降级。

PlanePressure source简单方案为何失败MechanismFailure boundary
skills可能的 instructions 太多总是加载完整 bodies 会膨胀 model viewmetadata、disabled paths、implicit indexesinvalid/disabled skills 不进入 prompt authority
pluginspackages 包含 paths 和 remote state信任 manifest strings 会授予 filesystem authority./ path validation、cache/load outcomesinvalid paths 或 sync errors 变成 warnings/outcomes
connectorsapps 先存在,access 后证明把 directory apps 当 usable tools 会误导用户account-scoped cache 与 access separationunauthenticated apps 保持 unavailable
typed extensionsinternal integrations 需要 prompt hooksdynamic code loading 会削弱 runtime ownershiptyped slots 与 PromptFragmentincompatible data 在 API/build boundary 被拒绝

共同不变量是 ownership。Skills 拥有 workflow instruction。Plugins 拥有 package classification。Connectors 拥有 hosted app metadata 与 account access。Typed extensions 拥有 compiled prompt contributions。Turn 看到的应该是受治理的 context 与 tools,而不是每个 extension 到达方式的偶然细节。

Trace Ledger

问题第 18 章答案
用户请求现在在哪里?它在 thread construction 与 tool exposure 之前/期间被 extension context 塑形。
什么数据结构携带它?SkillMetadataSkillLoadOutcome、plugin manifests、plugin load outcomes、connector directory entries、MCP configs、hook configs、app connector IDs 和 typed prompt fragments。
谁拥有下一步决策?Skill policy 与 loaders 决定 instruction availability;plugin loading 分类 package contributions;connector access gate hosted tools;typed APIs 只接受 compiled fragments;模型在这些决定之后行动。
必须保持什么不变?Package identity 不能变成 filesystem authority,directory metadata 不能变成 account access,summaries/metadata 也不能在需要 full-body loading 时静默替代 source-bound instructions。
这里可能怎么失败?invalid metadata、disabled skills、untrusted plugin paths、stale remote caches、plugin sync errors、missing connector access、unavailable hosted app tools 或 incompatible typed data。

应用到实践

  1. 先命名 plane。 当 capability 来自 extension 时使用。先判断它是 instruction、package contribution、hosted app metadata,还是 compiled prompt state。风险:让每种 package format 都有同等 runtime authority。
  2. 分开 discovery 与 activation。 适用于 skills 和 connectors。Skill 可以 known 但 disabled;connector 可以 listed 但 inaccessible。风险:把“known”当成“active in this turn”。
  3. 验证 package-relative paths。 适用于 plugins。注册 contributions 前要求 paths 留在 package root 内。风险:把 manifest strings 当成 filesystem authority。
  4. 按真实 owner 缓存。 适用于 skill 与 connector state。在 cache keys 中包含 config roots 或 account identity。风险:在不同 policy 的 sessions 之间共享 stale extension state。
  5. compiled contributions 优先使用 typed slots。 适用于 in-process integrations。贡献小而显式的 records,而不是 dynamic runtime code。风险:把 typed extensions 当成 plugin ABI。

接下来

Extension loading 处理的是 native capability surfaces。第 19 章转向更难的兼容性问题:当用户带来另一个 agent system 的 configurations 和 sessions,而它们的语义并不完全匹配 Codex 时,系统该如何处理?

源码地图

概念源码锚点
Skills managercodex-rs/core-skills/src/manager.rs
Skill metadata modelcodex-rs/core-skills/src/model.rs
Plugin manifest and path validationcodex-rs/core-plugins/src/manifest.rs
Plugin managercodex-rs/core-plugins/src/manager.rs
Connector directory modelcodex-rs/connectors/src/lib.rs
Typed prompt extension APIcodex-rs/ext/extension-api/src/contributors/prompt.rs