Skip to content

WorldState环境快照

追踪 StepContext 如何构造 WorldState、生成模型上下文差异、写入 rollout,并处理环境快照与恢复边界。

基于rust-v0.150.0
CodexRustRuntime

WorldState环境快照 ​

Codex 的 WorldState 是“当前 Step 对模型可见的动态状态集合”,不是整个 Session 的序列化副本。它由多个有稳定 ID 的 WorldStateSection 组成,例如模型指令、权限、AGENTS.md、环境、工具、插件和扩展贡献。每个 section 提供自己的 typed snapshot 和 diff renderer;ContextManager 保存上一次 snapshot,rollout 则保存 full item 或 merge patch。

本文区分三个容易混淆的对象:TurnEnvironmentSnapshot 是环境解析结果的 Ready/Starting 集合;WorldState 是模型上下文状态集合;WorldStateSnapshot 是用于比较和持久化的紧凑 JSON 状态。

阅读前应先理解 Session启动上下文 中 StepContext 的请求边界。 本文只解释 WorldState 的采集、比较、消费和恢复,不把它等同于文件系统或环境变量恢复;读完后应能 沿源码定位 full baseline、patch 消费者和缺失基线时的行为。

1. Step状态链 ​

同一个 WorldStateSnapshot 同时决定两件事:是否需要把新的 model-visible fragment 写进 history,以及是否需要把 WorldStateItem::full 或 WorldStateItem::patch 写进 rollout。二者都从同一对 previous/current snapshot 计算,避免模型上下文和持久化状态出现分叉。

2. WorldState ​

源码位置:codex-rs/core/src/context/world_state/mod.rs :: WorldStateSection、WorldState、WorldStateSnapshot。

rust
pub(crate) trait WorldStateSection: Send + Sync + 'static {
    const ID: &'static str;
    type Snapshot: DeserializeOwned + Serialize;

    fn snapshot(&self) -> Self::Snapshot;

    fn should_persist(&self) -> bool {
        true
    }

    fn render_diff(
        &self,
        previous: PreviousSectionState<'_, Self::Snapshot>,
    ) -> Option<Box<dyn ContextualUserFragment>>;
}

// Live model-visible state, keyed by stable section IDs.
#[derive(Default)]
pub(crate) struct WorldState {
    sections: IndexMap<&'static str, Box<dyn ErasedWorldStateSection>>,
}

#[derive(Clone, Debug, Default, PartialEq, Serialize, serde::Deserialize)]
#[serde(transparent)]
pub(crate) struct WorldStateSnapshot {
    sections: BTreeMap<String, Value>,
}

ID 是 rollout 中的持久化键,不能随意改名;Snapshot 只应包含比较所需的数据,不应序列化为 null,因为 RFC 7386 merge patch 中的 object null 表示删除。section 自己决定是否持久化,以及相对于 Absent、Unknown 或 Known 的 previous 状态如何渲染。

add_section 和 add_extension_section 都拒绝重复 ID;扩展 section 还会在特定的 host_skills 情况下调整顺序,使其位于权限 section 之前。这里的顺序既影响渲染顺序,也影响模型可见上下文。

3. StepContext ​

源码位置:codex-rs/core/src/session/world_state.rs :: build_world_state_for_step。

rust
let (previous_model, previous_context, base_instructions) = {
    let state = self.state.lock().await;
    (
        state.previous_turn_settings().map(|previous| previous.model),
        state.reference_context_item(),
        state.session_configuration.base_instructions.clone(),
    )
};
let model_instructions = turn_context
    .model_info
    .get_model_instructions(turn_context.personality);
let personality_is_baked = turn_context.model_info.supports_personality()
    && base_instructions == model_instructions;

let mut world_state = WorldState::default();
world_state.add_section(ModelInstructionsState::new(
    &turn_context.model_info.slug,
    previous_model.as_deref(),
    model_instructions,
));
if self.features.enabled(Feature::Personality) {
    world_state.add_section(PersonalityState::new(
        &turn_context.model_info.slug,
        turn_context.personality,
        previous_context.as_ref().map(|previous| previous.model.as_str()),
        previous_context.as_ref().and_then(|previous| previous.personality),
        personality_instructions,
        personality_is_baked,
    ));
}

构造过程把 Session 级历史/previous settings 与当前 TurnContext 合并。当前实现还会按配置和 feature 添加 TokenBudget/context-window guidance、Realtime、AGENTS.md、Permissions 或 CompactPermissions、协作模式、 环境、Apps、Plugins、Deferred Tools、Managed Developer Instructions 和 Multi-Agent sections。环境和扩展不是 从某个全局缓存盲读,而是来自本次 StepContext 的 selected environments、MCP/tool router、stores 和 capability roots。

源码位置:codex-rs/core/src/session/world_state.rs :: environment and extension sections

rust
if turn_context.config.include_environment_context {
    let current_date = self
        .services
        .time_provider
        .current_time(self.thread_id())
        .await
        .map_err(|err| CodexErr::Fatal(format!("failed to read current time: {err:#}")))?
        .with_timezone(&chrono::Local)
        .format("%Y-%m-%d")
        .to_string();
    world_state.add_section(
        EnvironmentsState::from_turn_context_with_environments(
            turn_context,
            &step_context.environments,
            Some(current_date),
        )
        .with_subagents(environment_subagents),
    );
}

let environments = step_context.environments.to_selections();
for contributor in self.services.extensions.context_contributors() {
    for section in contributor
        .contribute_world_state(WorldStateContributionInput {
            thread_id: self.thread_id(),
            turn_id: turn_context.sub_id.as_str(),
            environments: &environments,
            ready_selected_capability_roots: &ready_selected_capability_roots,
            executor_capability_discovery: step_context.executor_capability_discovery.as_deref(),
            extension_metrics: Some(Arc::clone(&extension_metrics)),
            session_store: &self.services.session_extension_data,
            thread_store: &self.services.thread_extension_data,
            turn_store: turn_context.extension_data.as_ref(),
        })
        .await
    {
        world_state.add_extension_section(section);
    }
}

如果读取当前日期失败,函数返回 CodexErr::Fatal,不会发布一个缺少环境时间的半成品状态。DeferredToolWorldState 开启时,工具 namespace 也会进入 WorldState;extension contributor 返回的 section 必须遵守相同的 stable-ID/snapshot/render-diff 合约。

4. 环境快照边界 ​

源码位置:codex-rs/core/src/environment_selection.rs :: TurnEnvironmentSnapshot、TurnEnvironmentState。

rust
pub(crate) async fn snapshot(&self) -> TurnEnvironmentSnapshot {
    let selected = self.environments.load_full();
    let mut environments = Vec::with_capacity(selected.len());
    for environment in selected.iter() {
        let resolved = if self.non_blocking_snapshots {
            environment.resolution.clone().now_or_never()
        } else {
            Some(environment.resolution.clone().await)
        };
        if let Some(environment) = TurnEnvironmentState::from_resolution(
            StartingTurnEnvironment {
                selection: environment.selection.clone(),
                config: environment.config.clone(),
                resolution: environment.resolution.clone(),
            },
            resolved,
        ) {
            environments.push(environment);
        }
    }
    TurnEnvironmentSnapshot { environments }
}

环境 snapshot 保留 selection order,并允许环境处于 Starting。失败解析的环境被跳过;refresh_readiness 只尝试把已有 Starting 项提升为 Ready,不采用更新的线程 selection。WorldState 随后把这个环境 snapshot 交给 EnvironmentsState,再由 section 决定模型可见文本和比较 snapshot。

5. diff边界 ​

相关源码:

  • codex-rs/core/src/session/mod.rs :: record_step_world_state_if_changed, record_context_updates_and_set_reference_context_item
  • codex-rs/core/src/context_manager/history.rs :: update_world_state
rust
let world_state = Arc::new(self.build_world_state_for_step(step_context).await?);
let previous_snapshot = previous_world_state.snapshot();
let world_state_snapshot = world_state.snapshot();
let world_state_item = world_state_snapshot
    .merge_patch_from(&previous_snapshot)
    .map(WorldStateItem::patch);
let items = merge_contextual_fragments(world_state.render_diff(&previous_snapshot));
if !items.is_empty() {
    self.record_conversation_items(turn_context, &items).await;
}
self.state
    .lock()
    .await
    .history
    .set_world_state_baseline(world_state_snapshot);
if let Some(world_state_item) = world_state_item {
    self.persist_rollout_items(&[RolloutItem::WorldState(world_state_item)])
        .await;
}

模型上下文先写入,再持久化描述该上下文的 patch。ContextManager::update_world_state 也遵守同一顺序:没有 baseline 时生成 full item;有 baseline 时生成 patch,并把 current snapshot 设为新的 baseline。current snapshot 改变但没有 fragment 或 patch 时,可以只更新内存 baseline,不重复写 TurnContext。

源码位置:codex-rs/core/src/context_manager/history.rs :: update_world_state

rust
pub(crate) fn update_world_state(
    &mut self,
    world_state: &WorldState,
) -> (Vec<Box<dyn ContextualUserFragment>>, Option<WorldStateItem>) {
    let snapshot = world_state.snapshot();
    let fragments = world_state
        .render_history_diff(self.world_state_baseline.as_ref(), &self.items);
    let rollout_item = self.world_state_baseline.as_ref().map_or_else(
        || Some(WorldStateItem::full(snapshot.clone().into_value())),
        |previous| {
            snapshot
                .merge_patch_from(previous)
                .map(WorldStateItem::patch)
        },
    );
    self.world_state_baseline = Some(snapshot);
    (fragments, rollout_item)
}

6. patch恢复 ​

恢复器按最新 compaction window 重放 world-state rollout items:full item 建立 baseline,patch 必须在已有 baseline 上应用;没有 baseline 或 patch JSON 无法应用时,恢复器清空 baseline,而不是假装得到一个完整状态。compaction item 也会重置该窗口的 world-state replay。

7. WorldState测试 ​

核心测试分为四组:

  • world_state_tests.rs 验证稳定 section ID、null 字段删除、typed snapshot 恢复、重复 ID 拒绝、retained fragment 缺失时重新渲染,以及 nested merge patch 的增删。
  • environment_tests.rs 验证 primary environment 改变、单环境 legacy snapshot、跨 single-environment 边界时重新陈述当前环境。
  • history_tests.rs 验证 baseline 在 history replacement 后失效,并且同一状态不会重复生成更新。
  • rollout_reconstruction_tests.rs 验证 full/patch replay、compaction window 选择和损坏 patch 的降级。

这些测试覆盖状态边界和恢复行为,不等于每个 section 的内容都永久兼容;section ID、snapshot schema 或 legacy matcher 变化时仍需重新检查。

8. 快照恢复验证 ​

  1. 解释为什么 TurnEnvironmentSnapshot、WorldState 和 WorldStateSnapshot 不能合并成一个结构体,以及每个对象由谁创建、谁消费。
  2. 遇到“模型没有重复看到环境说明,但 rollout 中仍有 patch”时,分别检查 section render_diff、ContextManager baseline 和 WorldStateItem 持久化顺序。

可以用下面的只读搜索把本文的 WorldState replay 主线落回源码:

bash
rg -n "WorldState|WorldStateSnapshot|build_world_state_for_step|apply_merge_patch" codex-rs/core/src