跳到主内容
概念fail-closed故障关闭默认拒绝agent 安全推理准入kill switch熔断

故障关闭(Fail-Closed)

故障关闭指的是:系统在自身出错、被停用或被篡改时,默认拒绝服务而不是默认放行。放到 Agent 场景里,它意味着「权限服务挂了就没有 token」——代价是全集群一起停,收益是失控任务不可能趁故障跑完。与之相对的是绝大多数系统默认的 fail-open。

发布 2026-11-11更新 2026-11-11核实 2026-11-11

什么是故障关闭

故障关闭(fail-closed)是一种失效时的默认行为约定:当系统组件出错、被停用、被篡改或无法判断时,它进入「拒绝」状态,而不是「放行」状态。

放到权限系统里,这句话可以写得很具体:没有许可,就没有服务。鉴权服务活着,请求按规则走;鉴权服务死了,所有请求一律拒。

它有一个天然的对立面——fail-open,也就是失效时默认放行。绝大多数互联网系统默认是 fail-open,因为可用性优先:缓存挂了直接回源、鉴权超时就先放过、配置中心不可用就用本地副本。这个取舍在「拒绝的代价远大于放行的风险」时完全合理。

判断该选哪一侧,只需要问一个问题:放行造成的后果可不可逆?

场景放行后果合理默认
商品详情页缓存失效回源慢一点fail-open
推荐服务的实验配置读取失败用默认推荐位fail-open
支付风控服务超时可能放过欺诈交易fail-closed
Agent 的推理鉴权服务不可用失控任务继续生成 token、继续发外部请求fail-closed
数据库行级权限服务抖动可能越权读数据fail-closed

为什么 2026 年这个词突然重要

三年以前,「能不能停下模型」还是一个原则性问题——模型是一次请求一次输出,停掉请求就停了。现在不同了:Agent 会连续运行几小时到几天,中途自己调工具、发网络请求、写文件、花钱。风险面从「一次输出」变成了「一整段自主运行」。

这个变化带来两个直接后果:

第一,「停止」从瞬时动作变成了工程问题。任务可能同时跑在多个引擎、多个模型上,你发出停止指令时,它手上可能还有几百个在途请求。停得多快、停得多干净,需要专门的机制来保证。OpenAI 公布的 Control Room 把这件事量化了:从决策到任务完全拿不到 token,上限 35 秒,而这个延迟来自推理授权前的缓存层。

第二,业务代码不可信。这不是说工程师不老实,而是训练脚本、评估脚本、数据管道由各个团队自己写、变更频繁、散落在几十个仓库里,没法保证每一份都执行「我是否已被停止」的检查。所以强制点必须放在变更少、路径窄的地方。推理层正好符合——只有少数几条代码路径,集中部署,改一次全覆盖。

工作原理

以 Control Room 公开的架构为例,fail-closed 的落地通常包含三层。

1. 任务层:领票进场

每个任务启动时向中央服务申请一个 job_id,并且必须绑定一份已批准的安全案例(一份说明「这件事为什么可以跑」的文档)。

task.start()
  → job_id = control_room.request_job(safety_case="rl-run-20261110-03")
  → 之后每次采样:engine.sample(job_id, prompt)

job_id 是这条链上唯一的身份凭证。没有它,请求不被受理。

2. 推理层:每次请求都要许可

推理引擎在生成任何 token 之前,拿 job_id 去问中央服务:这个任务还能不能跑?

engine.sample(job_id, prompt)
  → control_room.check(job_id)
      ├─ allowed  → 生成 token
      └─ stopped  → 返回拒绝,不产生任何 token

关键点在于检查发生在服务端,不在任务端。任务进程可以继续跑、继续 make 请求,但拿回来的永远是拒绝。爆炸半径被限制在这一个任务内,其他授权任务照常服务。

3. 决策层:人和监控都能拉闸

停止指令由两类主体发出:人(值班、研究员、安全团队)和监控模型。监控模型不属于「可选项」——它盯着被训练模型在做什么,按风险等级发告警,够严重就直接把 job_id 送进熔断层,跳过人工确认这一步。

4. 服务自身:坏了就全停

这是 fail-closed 的核心。中央服务被配置成:一旦它自己出错、被停用或被篡改,所有推理请求默认被拒绝,直到服务恢复。

if control_room.unavailable():
    deny_all()          # 不是 allow_all()

这条约定的含义值得重复一遍:敲掉闸门不会让任务获得自由,而是让整个集群一起停。 攻击者想要的是「绕过检查继续跑」,而 fail-closed 让「绕过检查」等于「全部停摆」——这让攻击变得没有收益。

三种主流落地形态

形态机制优点代价
准入式(Control Room 型)每次请求向中央服务换许可,中央服务不可用即全拒粒度细(任务级)、可解释、能审计引入一次网络往返;授权缓存造成延迟(35 秒)
令牌式(短期凭证)任务持短期 token,过期必须续签,续签服务挂了自然全拒无需每请求检查,性能好窗口期内仍可运行,停止不是即时
断路器式(circuit breaker)检测到异常行为(如异常外联)时熔断该任务或整个集群反应快、不需要侵入业务代码判定依赖启发式规则,误报会误伤

三者不互斥。成熟的做法是叠起来:令牌式做常态、准入式做高危动作、断路器做兜底。

常见的坑

坑一:把 fail-closed 当成全部安全。 35 秒不是安全,是上限。半分钟足够一个 Agent 发出成百上千次外部请求。真正的边界在任务本身能碰到什么——沙箱、网络白名单、文件系统权限,这些比熔断更底层。熔断是最后一道闸,不是第一道。

坑二:在业务代码里做检查。 「我在每个训练脚本开头加一行 if stopped: exit()」看起来省事,实际上不可维护:脚本会新增、会被人复制、会有人注释掉调试。检查必须在收敛的少数路径上做。

坑三:默认值写反了却没人发现。 这类 bug 不会在正常路径暴露,只会在故障时暴露——而那正是最不该出错的时刻。建议在故障演练里专门测一次「把鉴权服务杀掉,系统表现是否符合预期」。

坑四:只考虑技术故障,不考虑人为停用。 「被停用」和「被篡改」必须和「出错」同等对待。运维为了排障临时关掉鉴权服务,是最常见的绕过路径。

坑五:没有区分「拒绝」与「降级」。 拒绝是明确的失败信号,客户端应该重试或报错;降级是返回次优结果。把 fail-closed 实现成「返回空结果」会让上游误以为任务完成,比直接报错更危险。

怎么配:三步落地

第一步,给每个任务一个身份。

# 环境变量注入,任务侧无法伪造
export AGENT_JOB_ID="$(control-room issue --safety-case ./safety.yaml)"

第二步,把鉴权收口到一个中间层。 所有对模型、对外部工具的调用都经过同一处,在这一处检查许可。不要指望每个调用点自己检查。

第三步,把默认值写成拒绝,并演练它。

def authorize(job_id: str) -> bool:
    try:
        return control_room.check(job_id) == "allowed"
    except Exception:
        # 宁可全停,不可放行
        log.error("control_room unreachable, failing closed")
        return False

写完这段代码,做一次演练:手动把 control_room 指向一个不存在的地址,确认所有任务真的停下来,而不是悄悄继续跑。

哪些工具和系统用到了它

  • OpenAI Agents API:Agents SDK 0.24 引入的 RunBudget(根请求准入与用量上限)本质上就是准入式控制——预算耗尽即拒绝新请求,而不是事后告警。
  • Claude Code:权限与 deny 规则在沙箱层生效,v2.1.321 修复的三处问题(MCP OAuth 重定向泄露凭证、只读 Bash 命令逃出沙箱、deny 规则未覆盖 cp/mv 写入目标)都属于「检查没覆盖到实际动作」这一类缺口。
  • Codex CLI:Windows 原生沙箱与审批模式,本质是把「允许什么」变成默认拒绝、逐项批准。
  • Dify / n8n 这类编排平台:靠节点级权限与人工审核节点实现弱化版的 fail-closed。

与几个近义概念的区别

概念关注点与 fail-closed 的关系
Agent 沙箱任务能碰到什么(文件、网络、系统调用)更底层。沙箱限制爆炸半径,fail-closed 保证检查不被绕过
Agent 结果核验产物对不对事后。fail-closed 是事中,两者互补
Reward Hacking模型取巧拿高分fail-closed 是防取巧的工程手段之一
提示词注入输入侧的攻击注入成功后的止损依赖 fail-closed
断路器(circuit breaker)故障时切断流量fail-closed 的一种实现形态
熔断(kill switch)主动停止fail-closed 定义「停不下来时怎么办」

一句话记住

fail-open 赌的是「不会出事」,fail-closed 赌的是「出事也停得住」。 当你的系统开始自主行动、开始花钱、开始改别人的数据,第二个赌注更划算。

延伸阅读

来源

常见问题

fail-closed 和 fail-safe 是一回事吗?

常被混用,但严格说不是。fail-safe 指故障时进入「安全状态」,这个安全状态可能是关闭也可能是保持运行(如飞机起落架故障时放下轮子仍然安全);fail-closed 是 fail-safe 的一种具体实现——安全状态就是拒绝。在权限与鉴权语境下,两者通常同义。

为什么大多数系统默认是 fail-open?

因为可用性优先。鉴权服务、缓存、配置中心挂掉时,先放行能让业务继续跑,用户无感;拒绝会让整个系统停摆。这个取舍在「拒绝的代价 > 放行的风险」时成立,在「放行可能导致不可逆后果」时不成立。

Agent 系统为什么更适合 fail-closed?

因为 Agent 的越界行为常常不可逆:对外发请求、改生产数据、写文件、产生费用。这些动作一旦发生无法回滚,而「暂停几分钟」通常只是延迟。代价不对称时,默认拒绝是对的。

fail-closed 会不会导致误伤?

会。鉴权服务抖动一次,可能让整个集群的合法任务一起失败。缓解手段是让拒绝逻辑足够简单可靠(少依赖、无状态、独立部署)、缩短故障窗口、并给出明确的重试语义,而不是把默认值改回放行。

有真实的生产案例吗?

OpenAI 2026-11 公布的 Control Room 是目前公开细节最完整的案例:任务领 job_id、推理引擎向中央服务请求许可、服务不可用时一律拒绝。详见本站资讯《OpenAI 给自家研究集群装了总闸》。

相关对比

Aider vs Claude Code:终端 AI 编程双雄怎么选

Aider vs Claude Code 2026 选型对比:开源 BYOK 多模型 vs Anthropic 订阅长任务 Agent,从编程能力、多模型支持、价格、Git 集成、国内可用性和适合人群判断,帮你选对终端 AI 编程工具。

Claude Code vs Cline:CLI AI Agent 怎么选?2026 对比

Claude Code vs Cline 2026 选型对比:Anthropic 官方闭源 CLI Agent vs Apache-2.0 开源 VS Code 插件。从模型绑定、工作流、MCP 支持、价格、隐私和适合人群 6 个维度帮你选对 CLI AI 编程工具。

Claude Code vs Codex CLI:终端 AI Agent 双雄对比

Claude Code vs Codex CLI 2026 选型对比:Anthropic 与 OpenAI 两大官方终端 Agent 的模型、长任务、MCP 生态、Windows 支持、订阅打包价格和国内可用性全方位对比,帮你判断该用哪个终端 AI Agent,以及能不能两个一起用。

Claude Code vs Crush:Anthropic 官方 vs 多模型 TUI(2026 实测选型)

Claude Code vs Crush 怎么选:Anthropic 官方 CLI(长任务最稳),对上 Charmbracelet 开源 TUI(多模型切换 + LSP)。按模型、长任务、价格对比。

Cursor vs Claude Code:什么时候用哪个?(2026 实测选型)

Cursor 和 Claude Code 到底怎么选?一句话结论 + 决策树 + 价格实测 + 国内可用性对比。GUI 派选 Cursor,终端长任务派选 Claude Code,最优解其实是共存。

Devin vs Claude Code:AI 编程 Agent 怎么选?异步自主 vs 终端同步对比

Devin vs Claude Code 2026 选型对比:Cognition 异步自主 Cloud Agent vs Anthropic 终端同步 CLI Agent,从形态、工作模式、长任务能力、并发、价格、中文支持和适合人群 8 个维度判断,帮你选对 AI 编程 Agent。