雨天小六

读懂 Codex(2.3):Feature Gate 怎样决定实际启用的 Runtime 分支

· 更新于 2026-08-02 · 专栏:读懂 Codex

#Codex#Agent Runtime#软件架构#Feature Gate#Runtime

Feature Gate 常被理解为“界面里藏一个实验按钮”。在 Codex 中,它更接近 Runtime 组装输入:同一 份源码可以根据有效 Feature 集合选择不同 Code Mode Provider、工具规格、网络服务、多 Agent 版本 和模型请求参数。

因此不能只问“某 Feature 是 true 还是 false”。还要问:默认值来自哪里、Profile 是否覆盖、旧键 怎样迁移、依赖怎样补齐、受管要求是否允许,以及哪个组件最终消费它。

一个 Feature 有三层身份

数据解决的问题
RegistryFeature ID、canonical key、Stage、default这个开关叫什么,处于什么生命周期
Resolutiondefaults、base、profile、override、dependency当前 Session 中最终是否启用
ConsumptionThreadManager、Tool Plan、Session、Turn启用后究竟改变哪条 Runtime 分支

Stage 不是 enabled。Stable Feature 仍可以被关闭;Experimental Feature 可以被显式开启;Removed Feature 可能为了旧配置可解析而保留枚举项,却没有任何生产行为。

Registry 把开关元数据集中起来

每个 Feature 的规范条目包含 ID、配置 key、生命周期阶段和默认值。集中 Registry 带来三个约束:

  • CLI --enable name、TOML key 和诊断共享 canonical name;
  • UnderDevelopment 默认必须关闭;
  • 默认开启项必须是 Stable 或兼容性的 Removed 项。

这些约束由 Registry 测试保护,而不是依赖开发者记住约定。

@dataclass(frozen=True)
class FeatureSpec:
    feature: Feature
    key: str
    stage: Stage
    default_enabled: bool


def validate_registry(registry: list[FeatureSpec]) -> None:
    assert unique(spec.feature for spec in registry)
    assert unique(spec.key for spec in registry)
    for spec in registry:
        if spec.stage is UNDER_DEVELOPMENT:
            assert not spec.default_enabled
        if spec.default_enabled:
            assert spec.stage in {STABLE, REMOVED}

Stage 主要服务发布与诊断。真正的运行分支只应读取规范化后的有效集合。

有效集合按五步形成

当前解析顺序是:

Registry defaults
→ base config
→ selected profile
→ explicit overrides
→ dependency normalization

后一步覆盖前一步。Base 和 Profile 都会先应用旧顶层兼容开关,再应用 [features] 表;显式 override 用于更靠近 Runtime 的特殊选择。最后才补依赖。

Feature Registry 默认值经过 base、profile、override、依赖规范化和 Requirements 形成有效集合的流程图
图 2.3-1:Feature 解析复用配置优先级,但在输出前还要处理兼容键、依赖和受管要求。
def resolve_features(
    registry: FeatureRegistry,
    base: FeatureConfigSource,
    profile: FeatureConfigSource,
    overrides: FeatureOverrides,
    requirements: FeatureRequirements,
) -> EffectiveFeatures:
    enabled = registry.default_enabled_set()

    for source in (base, profile):
        apply_legacy_toggles(enabled, source)
        apply_feature_table(enabled, source.features)

    apply_explicit_overrides(enabled, overrides)
    normalize_dependencies(enabled)
    return requirements.constrain(enabled)

不要在伪代码里加入一套通用依赖图。当前源码明确规范化的依赖只有:开启 CodeModeOnly 时确保 CodeMode 同时开启。其他组合通常在消费点求交集,而不是自动打开更多能力。

bool 和结构化 Feature 共用一个开关语义

简单 Feature 可以写成布尔值:

[features]
plugins = true

需要参数的 Feature 可以写成表:

[features.code_mode_host]
enabled = true
disable_in_process_fallback = true

结构化表不能只解析参数而忘记 enabled,也不能在把 resolved state 写回配置时覆盖其他参数。内部 的 FeatureToml 因而支持 Enabled(bool)Config(T) 两种形状,统一提取开关,同时保留配置体。

def read_feature_entry(entry: bool | FeatureConfig) -> tuple[bool | None, FeatureConfig | None]:
    if isinstance(entry, bool):
        return entry, None
    return entry.enabled, entry


def materialize_resolved_state(entry: FeatureEntry | None, enabled: bool) -> FeatureEntry:
    if entry is None:
        return FeatureEntry(enabled=enabled)
    entry.enabled = enabled
    return entry  # 其他结构化参数保持不变

兼容键不等于兼容行为

旧配置可能还包含已删除的 undojs_repl、旧 Tool Search 或 TUI App Server 开关。解析层可以 识别并忽略这些键,避免旧文件让新版本直接崩溃;但它不会重新启用已经删除的实现。

另一些 legacy alias 会映射到 canonical Feature,并记录迁移提示。因而必须区分:

  • canonical key:直接控制当前 Feature;
  • legacy alias:映射当前 Feature,同时产生诊断;
  • removed compatibility key:接受输入但不改变有效集合;
  • unknown key:记录 warning,不发明功能。

如果文章看到枚举里有 RemovedFeature 就说“Codex 支持该功能”,会把解析兼容误写成产品能力。

Enabled 仍然只是必要条件

Apps 是最直接的例子。只有 Feature 开启且认证模式满足条件时,Apps 才真正可用。Tool Plan 还可能 要求 Plugins 同时开启。平台、模型能力、执行环境能力和受管 Requirements 也可以继续收窄。

可以把最终能力写成谓词:

apps_available = (
    features.enabled(APPS)
    and features.enabled(PLUGINS)
    and auth.has_chatgpt_account
    and requirements.allow_apps
)

这个谓词不意味着某个单一函数恰好这样书写,而是解释为什么“Feature=true”与“模型看得到工具” 之间还有条件。

Feature 在不同时间尺度被消费

某些分支在 ThreadManager 创建时冻结。例如 CodeModeHost 决定使用进程外 Provider、禁用 Provider 还是进程内 Provider。某些分支在 Session 或 Step 构建工具计划时读取,例如 CodeModeOnly、 UnifiedExec、Plugins 与 Apps。FastMode 还要进入 TurnContext,与当轮 service tier 共同决定请求。

有效 Feature 集合分别改变 ThreadManager、Tool Plan、Session Services 和 Turn Context 的分支图
图 2.3-2:同一个有效集合被不同生命周期的组件消费。改变 Feature 后,是否需要新 Thread 取决于分支在何时冻结。

这带来一个重要后果:运行期间修改配置,不保证现有 Thread 的所有服务立即重组。若 Provider 在 ThreadManager 创建时选择,简单刷新下一 Step 的 Tool Plan 无法替换它。后续章节会分别说明配置 刷新与 Thread/Step 快照。

几个真实 Runtime 分支

Code Mode Provider

def choose_code_mode_provider(config: Config) -> CodeModeProvider:
    if config.features.enabled(CODE_MODE_HOST):
        provider = ProcessOwnedProvider()
        return provider.with_fallback(
            enabled=not config.code_mode.disable_in_process_fallback
        )
    if config.code_mode.disable_in_process_fallback:
        return DisabledProvider()
    return InProcessProvider()

这里 Feature 与参数共同决定三分支。只看 CodeModeHost bool 会漏掉“host 关闭但 fallback 也被禁用” 的 Disabled 状态。

多 Agent 版本

MultiAgentV2 开启时强制 V2;若总 Agent 能力关闭则 Disabled;否则再结合模型声明与 Collab Feature 选择 V1 或 Disabled。它不是一个简单 if collab

Deferred Executor

开启后,Session 可以在部分执行器仍启动时允许 Turn 进入;关闭时,启动顺序更严格。该 Feature 改变并发时序和失败位置,不只是模型是否看到一个工具名称。

Network Proxy

开启后 Config/Session 可能建立受管代理,并让沙箱网络路径使用它。关闭不代表网络一定不可用, 只表示不走这条受管代理组合。

UnderDevelopment 警告只针对实际显式启用项

系统会检查有效 [features] 表,收集启用且 Stage 为 UnderDevelopment 的 canonical key,形成 Warning Event。若开关随后被 Requirements 禁用,不应继续把它列为“正在运行”。用户也可以显式 抑制警告,但这不会改变 Feature 风险或能力。

失败矩阵

输入或条件解析/消费结果
未知 Feature keywarning,忽略,不创建动态 Feature
legacy alias映射 canonical Feature并记录迁移提示
removed compatibility key可反序列化但不改变 Runtime
CodeModeOnly=true、CodeMode=false依赖规范化后 CodeMode=true
Apps=true、认证不满足Apps 不可用
结构化 Feature 缺少 enabled采用该配置类型的默认/未指定语义
UnderDevelopment 显式开启运行并产生不稳定警告,除非被约束或抑制
Requirements 禁止 Feature领域 Config 阶段拒绝或关闭,普通层不能绕过
Feature 在 Manager 创建后改变已冻结的 Provider 不自动替换

测试重点不是数量,而是组合

def test_code_mode_only_normalizes_dependency() -> None:
    resolved = resolve(base={"code_mode_only": True, "code_mode": False})
    assert resolved.enabled("code_mode_only")
    assert resolved.enabled("code_mode")


def test_removed_key_is_not_runtime_capability() -> None:
    resolved = resolve(base={"undo": True})
    assert not resolved.has_runtime_capability("undo")


def test_apps_requires_auth_after_feature_resolution() -> None:
    resolved = resolve(base={"apps": True})
    assert not apps_available(resolved, auth=ApiKeyAuth())
    assert apps_available(resolved, auth=ChatGptAuth())

生产测试还覆盖默认开启项阶段、结构化 Feature 参数、来源覆盖顺序、Warning Event,以及 Core Config 和 Tool Plan 的具体消费点。新增 Feature 若只给 Registry 加一行而没有消费测试,很容易成为永远不 生效或无法关闭的“幽灵开关”。

小结

Feature Gate 是 Runtime 组合协议。Registry 定义身份与默认值,Resolver 处理来源、兼容键和依赖, Requirements 收窄权限,具体组件再在自己的生命周期读取有效集合。

当配置与 Feature 都确定后,App Server 才能创建共享的 ThreadManager。下一节将追踪一个 Thread 怎样从新建、恢复或派生请求进入 Session,为什么必须等到第一个 SessionConfigured 事件后才能 注册为可见 Thread。

评论


← 返回文章列表