视频加载失败

Codex 为什么会“降智”:292 State、状态注入与长任务的另一条解释

8239 字
41 分钟
Codex 为什么会“降智”:292 State、状态注入与长任务的另一条解释

最近使用 Codex 处理一些时间较长的任务时,我反复遇到几个很难解释的现象。

同一个账号、相同的模型和相近的提示词,有时可以连续完成多文件重构;换一个时间段之后,却开始忘记上下文、重复已经解决的问题,甚至给出像是低配模型生成的答案。

Codex 的 cloud agent 也会频繁出现 overloaded,请求还可能返回 429。

换一个会话、换一个网络出口,或者等一段时间之后,状态又似乎恢复了。

这到底是模型本身发生了变化,还是服务端的调度状态发生了变化?

我现在更认可后一种解释。

Codex 背后存在一套和资源路由、请求状态、上下文压缩以及长任务连续性有关的机制。我们看到的“降智”,往往不是单纯的模型能力变化,而是多个状态同时变化后的结果。

其中最值得关注的,就是 HTTP 292 和 current_turn_state

先说结论#

这件事可以拆成两条同时运行的链路。

第一条是资源链路:

请求
服务端调度
HTTP 292 / current_turn_state
资源等级与后端路由
后续请求继续携带 state

第二条是任务链路:

对话与工具调用
上下文不断增长
达到阈值后压缩
部分细节被重新概括
长任务表现发生变化

292 State 主要解释第一条链路。

上下文增长和 compaction 主要解释第二条链路。

两者叠加之后,就会出现一种非常真实的用户体验:

任务刚开始时模型很聪明,运行一段时间后,它像是换了一个模型。

这里先把资料分成三种:

  • 公开源码已经能确认的部分:Codex 存在 x-codex-turn-state,并把它用于同一轮请求的粘性路由。
  • 公开讨论和实际观察中的部分:292、current_turn_state、约一小时 TTL、IP 不绑定以及资源等级之间的关系。
  • 本文自己的代码:保留状态摘要记录、续期逻辑和路由结构示意,用来还原这套调度流程。

这三类证据不能混在一起,但它们可以拼出一套比较完整的解释。

292 到底是什么#

标准 HTTP 规范里没有 292 这个状态码。IANA 的标准登记表中也没有记录它。IANA HTTP Status Code Registry

但这并不代表服务内部不能使用私有状态码。

大型平台通常会在标准 HTTP 状态之外,通过响应头、内部元数据或自定义状态表示更多调度信息。

292 就是这样一种内部信号。

目前比较合理的解释是:当请求到达服务端之后,调度器会先判断当前请求应该进入什么资源路径,包括:

  • 路由到哪个模型实例。
  • 使用什么推理质量。
  • 分配多少计算资源。
  • 是否允许完整上下文。
  • 是否进入备用或降级队列。
  • 后续请求是否可以继续复用当前调度结果。

调度正常时,服务端返回 292,并附带一个 current_turn_state

当前资源紧张时,则返回普通的 200,但请求实际进入资源受限或降级路径。

可以把它简单理解成:

状态含义用户表现
292调度正常、资源路径稳定速度快,推理完整,较少出现 overload
200HTTP 请求成功,但不代表资源等级相同能返回结果,但可能变慢、变浅或更容易丢上下文
429请求过快或触发限流请求失败,需要退避
503服务暂时过载请求失败或排队

这里需要强调,292 和 200 的对应关系目前属于逆向观察,不是 OpenAI 公开文档确认的协议。

但这个解释能够很好地解释一种现象:

请求明明成功返回了,为什么模型的质量还是明显下降了?

因为 HTTP 成功和获得相同的计算资源,并不是一回事。

current_turn_state 是什么#

公开的 Codex 源码中,可以看到一个真实存在的状态:

x-codex-turn-state

官方客户端把它描述为 sticky-routing token,也就是粘性路由状态。Codex 官方 client.rs

它的基本流程是:

  1. 一轮 turn 开始。
  2. 服务器返回 turn state。
  3. 客户端保存这个状态。
  4. 同一轮中的后续请求继续携带它。
  5. 任务进入新的 turn 后,应该创建新的 session。

SSE、WebSocket 和 compact 请求也会处理这个状态。SSE 响应处理 WebSocket 响应处理

这至少证明了一个事实:

Codex 的请求并不是完全独立的,服务端确实会使用某种状态维持同一轮任务的连续路由。

公开源码使用的是 x-codex-turn-state,而一些逆向观察把更上层的状态称为 current_turn_state

我更倾向于把它们理解成同一套调度机制的不同表现:

  • HTTP 292 更像服务端给出的资源调度结果。
  • current_turn_state 更像资源调度凭据。
  • x-codex-turn-state 更像客户端继续发送的 turn 粘性状态。

它们属于同一个服务端状态系统的不同层级。

官方源码把 x-codex-turn-state 放在 turn 范围内,说明客户端看到的 header 负责维持当前 turn 的粘性路由;292 和 current_turn_state 则是更上层的资源状态。它们不需要是同一个字段,但共同指向同一个结论:Codex 并不是无状态地处理每一个请求。

为什么不是所有人都能拿到 292#

如果 292 代表一次调度成功,那么它就不应该对所有请求无差别发放。

从实际观察来看,影响 292 签发的因素主要有四类。

IP 质量#

不同网络出口具有不同的风控和资源评分。

但如果只把 292 理解成“当前还有算力”,仍然漏掉了更核心的一层:它是服务端在判断请求来源之后,为高可信用户保留完整服务路径的调度标记。

这套分层首先照顾的是美国本地的真实用户。这里的“本地”不是简单读取一个国家字段,而是综合网络出口、ASN、地理位置、IP 信誉、账号历史和请求行为等信号。稳定的美国住宅宽带,更像一个真实家庭用户的固定网络;中转站、共享代理、VPN、VPS 和数据中心出口,则更容易呈现出多人共用、地域与账号不一致、请求集中或出口频繁变化等特征。

服务端没有必要把这些流量全部拒绝。更现实的做法是,在资源紧张时先保护本地住宅用户的体验,再把中转、共享 VPN 和数据中心流量放进普通或降级路径。后者仍然可以拿到响应,但更容易遇到等待、overload、429、503,以及长任务连续性变差。

所以,292 的核心并不是“美国 IP 有一个特殊开关”,而是服务端把不同来源的请求放进了不同的服务质量等级:

flowchart TD A[请求进入] --> B[IP / ASN / 地理位置 / 账号 / 行为] B --> C{服务质量分层} C -->|美国住宅、稳定低风险| D[完整资源路径] C -->|中转、共享 VPN、数据中心| E[普通或降级路径] D --> F[292 + state + 更稳定的长任务] E --> G[200 / 429 / 503 / overload]

这也解释了为什么同一个账号会出现明显的网络差异:变化的未必是账号权限,而是请求被服务端放进了哪一层。

可以把实验中的 IP 类型粗略分成:

IP 类型观察到的 292 通过率说明
美国住宅宽带较高真实 ISP 住宅出口
原生 IPv6较高某些地址可能被视为更自然的用户网络
欧洲或亚洲住宅中等仍可能获得正常状态,但概率不稳定
数据中心 IP较低VPS、云服务器地址段容易被集中标记
共享 VPN 或公共代理很低同一个出口可能被大量用户使用

这张表不是固定不变的官方等级表,而是实际实验中最值得优先比较的网络分层。

如果同一个账号通过美国住宅网络容易拿到 292,通过数据中心或共享代理更容易进入普通 200,那么就说明 IP 质量已经参与了调度。更准确地说,服务端正在用网络来源判断“这个请求应该获得什么级别的服务”。

为什么中转和 VPN 更容易降级#

中转、VPN 和数据中心并不等于恶意请求,但从服务端的角度看,它们和共享滥用流量使用的是相似的网络信号:一个出口承载大量账号,账号所在地与 IP 所在地不一致,请求在短时间内集中出现,或者出口属于云厂商和代理服务商的地址段。

在高峰期,服务端最容易做的不是逐个判断“这个人是不是好用户”,而是先按网络来源做粗粒度分层。美国本地住宅出口更容易进入高优先级路径;中转站、共享 VPN 和数据中心出口则更容易保留基本可用性,但牺牲一部分速度、推理资源和长任务稳定性。

这就是“给当地居民更好的体验,给中转用户降级使用”的技术含义。它不是把某一类用户永久封死,而是在有限资源下把完整体验优先留给最像本地真实用户的请求。

账号等级#

不同订阅等级具有不同的资源优先级。

需要比较的变量包括:

  • Free。
  • Plus。
  • Pro。
  • 更高等级的 Pro 计划。

不能只拿一个账号测试一次就得出结论。更可靠的方式是使用相同模型、相同提示词和相近时间重复测试。

服务端负载#

即使账号和 IP 都没有变化,服务端高峰期也可能收紧 292 的签发。

这可以解释为什么:

  • 早上状态很好,下午开始变差。
  • 工作日和周末体验不同。
  • 同一个会话上午正常,晚上频繁 overload。
  • 等几个小时后,模型又恢复了。

这不一定是用户发生了变化,也可能只是服务端整体负载发生了变化。

State 的有效期#

current_turn_state 的 TTL 约为一小时。

也就是说:

  1. 第一次请求成功获得 292。
  2. 服务端签发一个 state。
  3. 接下来一段时间内可以继续复用。
  4. 超过有效期之后,state 失效。
  5. 下一次请求需要重新进行调度。

这可以解释“刚刚还很好,过一会儿突然变差”的体验。

当然,TTL 不能只凭一次观察确定,必须通过连续重放实验测量。

验证不能只靠感觉#

如果只是在浏览器里感觉“今天好像聪明一点”,很难证明 292 State 的作用。

接下来需要测量四个变量:

  1. 292 是否和 state 一起出现。
  2. 携带有效 state 是否真的改变后续结果。
  3. state 是否和 IP 绑定。
  4. state 的 TTL 大约是多少。

第一组:观察 292 和 state 是否同时出现#

每次请求至少记录:

timestamp
network_profile
account_level
model
http_status
state_present
state_hash
latency_ms
retry_after
overloaded

记录时可以用 state 的哈希作为标识。

from __future__ import annotations
import hashlib
from typing import Mapping
def summarize_response(
status: int,
headers: Mapping[str, str],
latency_ms: int,
) -> dict:
state = (
headers.get("current_turn_state")
or headers.get("x-codex-turn-state")
)
return {
"status": status,
"state_present": bool(state),
"state_hash": (
hashlib.sha256(state.encode()).hexdigest()[:12]
if state
else None
),
"latency_ms": latency_ms,
"retry_after": headers.get("retry-after"),
"server_model": headers.get("openai-model"),
}

这段代码只记录状态是否存在,不保存完整 token。

第二组:有状态和无状态对照#

至少准备三组请求:

A 组:普通线路,不携带 state
B 组:普通线路,携带有效 state
C 组:普通线路,携带过期或随机 state

三组请求应尽量保持:

  • 相同账号。
  • 相同模型。
  • 相同提示词。
  • 相近时间。
  • 相同请求间隔。

需要比较的指标包括:

  • 292 出现频率。
  • 429 出现频率。
  • overloaded 出现频率。
  • 首字节延迟。
  • 输出长度。
  • 工具调用是否中断。
  • 上下文是否连续。
  • 人工盲评结果。

如果只有 B 组长期表现更好,而 A、C 组明显更差,就可以量化 state 的作用。

第三组:验证是否绑定 IP#

这是整个理论最关键的验证。

流程应该是:

住宅 IP 获取 292 + state
切换到数据中心或 IPLC
不携带 state 请求一次
携带 state 请求一次
比较两次结果

如果不携带 state 的请求明显更容易降级,而携带同一个 state 的请求仍保持正常,说明 state 的使用和 IP 是分离的。

这个实验需要反复执行,用来排除服务端负载波动。

第四组:测量 TTL#

获得 state 后,按固定间隔重试:

0 分钟
10 分钟
20 分钟
30 分钟
45 分钟
55 分钟
60 分钟
70 分钟
90 分钟

每次记录:

  • 是否仍被接受。
  • 是否仍保持相同路由结果。
  • 是否返回新的 292。
  • 是否需要重新获取 state。
  • 是否出现 429 或 503。

如果状态在接近一小时后稳定失效,才能说明“一小时 TTL”具有一定可信度。

状态获取和状态使用可以分开#

状态注入的核心思路是:

获取 state 的网络环境,和使用 state 的网络环境,不一定需要相同。

大致流程如下:

获取阶段:
Clash 切到住宅或原生 IPv6
keeper 发送探测请求
服务端返回 292
提取 current_turn_state
保存状态和过期时间
Clash 切回日常线路
使用阶段:
Codex / ChatGPT 发出请求
本地代理读取 state 文件
在请求中加入状态 header
转发到服务端
服务端验证 state 是否有效

这套设计的关键假设是:

  • state 不和签发时的 IP 强绑定。
  • state 代表的是调度结果,而不是 IP 身份。
  • state 在 TTL 内可以跨网络出口使用。
  • state 与实际使用的模型保持一致时效果最好。

如果这些假设成立,住宅 IP 只需要在获取阶段出现几秒钟,日常使用仍然可以走低延迟线路。

State 的真正作用:保留已经拿到的服务等级#

如果 292 只是“当前还有算力”,拿到一个 state 本身没有太大意义。状态注入之所以有效,是因为它把第一次请求时完成的服务质量判断延续到了后续请求。

获取阶段在美国住宅或其他高可信网络上拿到 292,等于先完成了一次更优先的资源调度。使用阶段即使切回中转、VPN 或日常线路,带上有效 state 后,服务端仍然可以把请求识别成已经建立的稳定 turn,而不是每次都根据当前出口重新从普通队列开始排队。

所以,state 的作用不是把数据中心 IP 变成住宅 IP,也不是改变账号的地区身份;它就是一张暂时有效的“服务等级通行证”,把之前已经拿到的资源路径延续一段时间。只要 state 仍在有效窗口内,后续请求就会继续沿用原来的调度结果;一旦过期、模型不匹配或服务端重新做风险判断,请求就会回到当前网络对应的普通路径。

这也解释了为什么“先住宅获取,再日常线路使用”比单纯切换 IP 更有意义:前者是在复用一次已经完成的调度,后者只是重新碰运气。

续期逻辑#

状态不能等到完全过期之后再续。

更合理的方式是提前五分钟续期,并且每 45 秒检查一次。

from __future__ import annotations
import json
import time
from pathlib import Path
STATE_FILE = Path("current_turn_state.json")
RENEW_BEFORE_SECONDS = 5 * 60
def load_state() -> dict | None:
if not STATE_FILE.exists():
return None
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
def should_renew(state: dict | None) -> bool:
if not state:
return True
expires_at = float(state["expires_at"])
return expires_at - time.time() <= RENEW_BEFORE_SECONDS
def save_state(value: str, ttl_seconds: int) -> None:
payload = {
"value": value,
"expires_at": time.time() + ttl_seconds,
}
STATE_FILE.write_text(
json.dumps(payload, indent=2),
encoding="utf-8",
)
def keeper_loop(acquire_state) -> None:
while True:
state = load_state()
if not should_renew(state):
time.sleep(45)
continue
# 这里调用探测逻辑,返回状态和 TTL。
result = acquire_state()
if result and result.get("status") == 292:
save_state(
value=result["state"],
ttl_seconds=result.get("ttl", 3600),
)
print("state refreshed")
else:
print("state refresh failed")
time.sleep(45)

实际模型应该和使用模型保持一致。

例如,如果日常使用的是 gpt-6-astra,探测时也应该使用同一个模型,而不是用一个模型获取 state,再拿去请求另一个模型。

否则即使 state 有效,也不一定能覆盖另一个模型的资源路由。

真实验证路径#

这套机制真正要验证的不是“我能不能生成一个叫 state 的字符串”,而是下面这条因果链:

同一账号、同一模型、相近时间
住宅 / 原生 IPv6 请求
观察是否出现 HTTP 292 和 current_turn_state
保存 state 的摘要与首次出现时间
切换到日常线路
分别发送不带 state 和带 state 的请求
比较路由结果、延迟、429、503、overload 与输出质量

工程上可以拆成几个部分:

  1. keeper 负责发起探测、保存 state 和提前续期。
  2. 本地代理负责在请求经过时读取当前 state。
  3. Clash 或其他路由工具负责在探测线路和日常线路之间切换。
  4. 日志记录每一次响应的状态、延迟、模型和路由差异。

典型流程是:

  1. 在住宅或原生 IPv6 出口发起探测请求。
  2. 观察响应是否出现 292,以及是否返回 current_turn_state。
  3. 将 state 保存到本地文件,同时记录获取时间和预计过期时间。
  4. 在 state 过期前提前续期。
  5. 让本地代理在后续请求中携带 state。
  6. 切回日常线路,比较有 state 和无 state 的真实请求。

真实验证需要记录什么#

如果要把这套机制跑清楚,每次请求至少记录一行实验数据:

timestamp
account_level
model
network_profile
http_status
state_present
state_hash
latency_ms
retry_after
overloaded
context_size
compaction_happened

记录时可以用 state 的哈希作为标识:

from __future__ import annotations
import hashlib
from collections.abc import Mapping
def summarize_response(
status: int,
headers: Mapping[str, str],
latency_ms: int,
) -> dict:
state = (
headers.get("current_turn_state")
or headers.get("x-codex-turn-state")
)
return {
"status": status,
"state_present": bool(state),
"state_hash": (
hashlib.sha256(state.encode()).hexdigest()[:12]
if state
else None
),
"latency_ms": latency_ms,
"retry_after": headers.get("retry-after"),
"server_model": headers.get("openai-model"),
}

这个函数把一次响应整理成便于比较的实验记录,后面可以直接写入 JSONL 或 CSV。

真实实验的对照组#

至少应该设置三组:

组别网络state目的
A 组日常线路不携带观察自然状态
B 组日常线路携带有效 state观察注入后的状态
C 组日常线路携带随机或已过期 state排除“只要多一个 header 就有效”的可能

三组请求尽量保持:

  • 同一个账号。
  • 同一个模型。
  • 同一类提示词。
  • 相近的时间段。
  • 相同的请求间隔。
  • 尽量相同的上下文长度。

如果只测试一次,结果很容易被时间、网络和服务负载干扰。更可靠的方式是跨多个时间段、多个网络出口重复测试,同时记录 429、503 和 overload 的出现情况。

IP 是否绑定,需要单独验证#

最关键的实验是 IP 绑定实验:

阶段一:住宅 / 原生 IPv6
获取 292 和 state
阶段二:数据中心 / IPLC
不携带 state 请求一次
携带同一个 state 请求一次
阶段三:重复多轮
比较两种请求的成功率和服务表现

如果携带 state 的请求在更换网络出口后仍然保持明显差异,就说明 state 的使用可能不依赖签发 IP。

更换 IP 后还要继续控制账号、模型、会话和提示词,否则很难判断变化到底来自 state 还是其他变量。

TTL 也必须实测#

TTL 约一小时,连续重放可以测出它的实际窗口。

拿到 state 后按固定时间间隔测试:

0 分钟
10 分钟
20 分钟
30 分钟
45 分钟
55 分钟
60 分钟
70 分钟
90 分钟

每个时间点记录:

  • state 是否仍然被接受。
  • 是否仍然维持相同的响应特征。
  • 是否出现新的 292。
  • 是否出现 429、503 或 overload。
  • 是否需要重新建立 session。
  • 上下文是否已经发生 compaction。

如果多个时间段的结果都在接近一小时后发生变化,TTL 的判断才比较稳定。

结果怎么看#

Codex 官方源码读取 x-codex-turn-state,并把它作为同一 turn 内的 sticky-routing token;SSE、WebSocket 和 compact 路径也都会处理这个状态。

292、current_turn_state、IP 质量、TTL 和资源等级共同构成这套机制的核心变量。真实请求对照要测量的是:

  • 292 是否和 state 稳定同时出现。
  • state 是否能在换 IP 后继续生效。
  • state 失效是否呈现稳定的时间窗口。
  • 携带 state 后,429、503、overload 和响应延迟是否持续改善。
  • 上下文长度和 compaction 是否会独立影响结果。

把这些变量拆开之后,才能知道“降智”究竟来自资源路由、状态过期,还是上下文已经变得过于复杂。

Clash 的路由切换#

如果实际实验需要切换不同出口,结构上通常需要两个代理组:

proxy-groups:
- name: GPT-Daily
type: select
proxies:
- Daily-Route-1
- Daily-Route-2
- name: GPT-State-Probe
type: select
proxies:
- Residential-Route
- Native-IPv6-Route

获取 state 时切换到 GPT-State-Probe

获取完成后切回 GPT-Daily

这也是为什么整个方案通常需要三个组件:

组件职责
keeper获取 state、记录 TTL、提前续期
本地代理在请求链路中携带有效 state
Clash 或其他路由工具在获取阶段切换网络出口

对 Codex 来说,它仍然只是向本地代理发请求,并不知道背后进行了状态管理。

上下文增长会放大问题#

即使状态路由保持稳定,长任务依然可能出现“降智”。

因为上下文本身也在不断增长。

一个复杂任务可能包含:

  • 初始需求。
  • 多轮对话。
  • 大量文件内容。
  • 多次代码修改。
  • 测试日志。
  • 浏览器截图。
  • 工具调用结果。
  • 被放弃的旧方案。
  • 后来追加的临时要求。

这些内容都会进入任务历史。

刚开始时,模型只需要理解“我要做什么”。

任务进行到后面,它需要理解的是:

哪些内容仍然有效,哪些决定已经被推翻,哪些错误已经修复,现在文件系统的真实状态是什么?

上下文越长,模型拥有的信息越多,但真正重要的信息不一定更加突出。

如果一个任务中存在大量旧方案、失败日志和已经失效的指令,模型就需要花更多精力在上下文里寻找当前有效的答案。

这时即使资源路由没有变化,用户也会感觉模型变得迟钝。

压缩会改变任务的“记忆形状”#

当上下文接近上限时,Codex 需要进行 compaction,也就是上下文压缩。

OpenAI 官方文档说明,压缩后的内容是加密且不可直接解释的 opaque item,客户端不能简单地把它当成普通文本查看或编辑。Context compaction 官方文档

压缩可以让长任务继续运行,但它并不是把原来的上下文逐字保存下来。

原来的上下文里,可能有很多只有在具体语境中才有意义的判断:

  • 这段代码暂时不能抽象。
  • 这个按钮虽然多余,但用户已经习惯。
  • 当前功能只需要验证流程,不应该继续扩展。
  • 某个报错只是环境问题,不是代码逻辑问题。

压缩之后,这些细节可能仍然存在,也可能只剩下一段更概括的总结。

模型可能还记得“要完成什么”,但未必完整保留“为什么要这样完成”。

因此,压缩前后的任务目标看起来一样,工作风格却可能已经发生变化。

如果恰好在压缩、重连或重新路由之后,任务又失去了原来的 turn state,用户就更容易把这种变化理解成“模型降智”。

长任务其实是多种状态叠加#

可以把一个长任务简单理解成下面这几层:

flowchart TD A[一个长任务] --> B[会话与线程] A --> C[292 / 资源路由状态] A --> D[turn state] A --> E[上下文与压缩] A --> F[工具调用历史] A --> G[文件系统真实状态] A --> H[限流与服务负载]

这些状态任何一层发生变化,都可能影响最后的表现。

  • 会话变化,可能导致上下文重新开始。
  • 292 状态变化,可能导致资源路径发生变化。
  • turn state 丢失,可能导致同一轮请求不再保持粘性路由。
  • 上下文压缩,可能让模型失去部分细节。
  • 工具调用异常,可能让模型误判当前进度。
  • 文件系统变化,可能让对话历史和真实项目不一致。
  • 429 或 503,则可能直接造成请求失败或重试。

所以,模型表现变差并不一定只有一个原因。

但这几种状态如果同时变化,就很容易产生非常明显的“降智”体验。

429 和 overloaded 可能是结果,不是根因#

OpenAI 官方文档将 429 和 503 区分为两类问题。

429 通常和请求过快、流量增长过猛或速率限制有关。

503 server_is_overloaded 则表示模型服务暂时过载。

官方建议使用 Retry-After 和指数退避,而不是连续快速重试。Rate limits 官方文档

但从长任务角度看,429 和 overloaded 也可能只是我们看到的表面结果。

如果一个任务不断增长、频繁调用工具、反复重试,那么它可能同时带来:

  • 更高的请求频率。
  • 更大的上下文。
  • 更多的压缩操作。
  • 更多的状态切换。
  • 更复杂的资源调度。

最终表现出来的就不只是一次请求失败,而是整个任务开始变得不稳定。

状态注入可能遇到的坑#

State 过期#

如果续期失败,旧 state 过期之后就会进入空窗期。

表现可能包括:

  • 模型质量下降。
  • overload 增多。
  • 重新出现 429。
  • 长任务突然开始丢上下文。

所以续期应该提前五分钟开始,而不是等到最后一分钟。

模型不匹配#

用一个模型获取的 state,不一定适合另一个模型。

例如:

gpt-6-astra 获取的 state
不一定适合
其他模型的请求

实验时,获取模型和使用模型应该保持一致。

多账号不能混用#

每个账号的 state 应该独立保存。

不能把账号 A 获得的状态注入账号 B 的请求,也不能让多个账号共用同一个状态文件。

上下文压缩仍然会发生#

即使状态注入成功,也不能阻止上下文增长和 compaction。

状态注入解决的更像是资源和路由问题,不能保证模型永远记得每一个细节。

一个解决“分配到哪里”。

另一个解决“还能记住什么”。

和其他方法比较#

方法原理效果问题
换 IP碰运气进入更好的出口有时有效不稳定
新开会话重新触发调度偶尔有效丢失上下文
多账号轮换分散请求压力可能有效成本高、管理复杂
避开高峰期等服务负载下降通常有效不适合连续工作
记录项目状态减少上下文依赖对长任务有效不能改变资源路由
292 State 注入复用稳定调度状态理论上更稳定需要验证 TTL、IP 绑定和模型匹配

292 State 注入的特别之处,在于它试图从调度层解决问题,而不是只在应用层缓解症状。

换 IP 是重新碰运气。

新会话是重新开始。

状态注入则是尝试保留原来的资源和路由结果。

这套机制为什么存在#

从平台的角度看,OpenAI 需要同时面对两个问题:

  • GPU 资源有限。
  • 用户请求不断增长。

如果每个请求都重新排队,长任务的连续性会很差。

因此,服务端会在一轮任务开始时进行一次调度判断:

  • 当前账号是否有优先级。
  • 当前 IP 是否可信。
  • 当前负载是否允许。
  • 当前模型是否有资源。
  • 是否可以给后续请求维持一条稳定通道。

292 和 state 就是这次调度结果的外部表现。

更核心的目的,是在资源有限和请求来源复杂的情况下,优先保证美国本地、稳定、低风险真实用户的体验。中转站、共享 VPN 和数据中心用户并不一定被拒绝,但更容易被放进普通或降级路径:能用,速度和稳定性却不再和本地住宅用户相同。

state 则负责把已经完成的调度结果延续到同一轮任务的后续请求,让高优先级路径不必每次从头竞争。网络切换、移动设备切换和长任务重试只是这个机制带来的连续性表现,不是它唯一的目的。

但如果 state 的签发和使用没有绑定 IP,就会产生一个意外特性:

获取状态和使用状态可以发生在不同的网络环境中。

这也正是状态注入理论成立的关键。

当然,这套机制未来可能被平台调整:绑定 IP、缩短 TTL、增加设备指纹、绑定账户和模型,都是很自然的防护方式。

所以它更像一个尚未被充分文档化的内部机制,而不是一个永远有效的漏洞。

最后#

我现在越来越不觉得 Codex 只是一个模型接口。

它更像是一个由模型、资源、上下文、工具、会话和路由共同组成的系统。

292 State 解释了资源路由这一层。

它最重要的含义,是服务端会优先把完整体验留给更像美国本地真实用户的请求,再把中转、共享 VPN 和数据中心流量降到仍然可用但更保守的路径。

x-codex-turn-state 说明 Codex 确实会在同一轮请求中维护粘性状态。

上下文增长和 compaction 解释了为什么长任务会逐渐失去细节。

429 和 overload 则说明服务端负载本身也会参与最终体验。OpenAI:Unrolling the Codex agent loop

所以,“降智”可能不是一个单独的问题,而是下面几件事同时发生:

292 状态没有拿到或已经过期
+
后端资源路由发生变化
+
上下文不断增长
+
压缩后丢失部分细节
+
服务端处于高负载
=
用户感知到模型突然变笨

状态注入解决的是资源路由问题,不能替代上下文管理,但它为“降智”和 overload 提供了一个很有解释力的方向。

真正值得研究的,不只是模型本身有多聪明,而是:

这一次请求到底被分配到了什么资源,携带了什么状态,又在怎样的上下文里继续运行。

这可能才是长任务中最容易被忽略、也最影响体验的一层。

相关阅读#

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
Codex 为什么会“降智”:292 State、状态注入与长任务的另一条解释
https://rainzt.cn/posts/codex-292-state-analysis/
作者
Rain
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0

评论区

文章目录

WELCOME欢迎来到朝朝听雨

很高兴在这里遇见你。