From 4b07886d593546a4aee64a09aab219dd6660497f Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 12 Aug 2026 23:41:49 +0000 Subject: [PATCH] Represent persisted world state as JSON objects (#38274) ## Why World-state snapshots and merge patches are keyed collections of sections. Allowing their persisted `state` field to contain any JSON value required replay code to handle shapes that cannot represent world state. ## What changed - Type `WorldStateItem.state` and snapshot patches as JSON object maps. - Reconstruct full snapshots directly from those maps and apply patches without fallible arbitrary-value conversions. - Regenerate the app-server protocol schema for the object-only representation. GitOrigin-RevId: 3d7662b74f3dd1f3ccc5fdd0200074c13a73cbe5 --- .../app-server-exports-stable.json.zst | Bin 132312 -> 135046 bytes .../tests/suite/v2/git_attribution.rs | 3 +-- .../context/world_state/environment_tests.rs | 3 ++- codex-rs/core/src/context/world_state/mod.rs | 25 +++++++++++------- .../context/world_state/world_state_tests.rs | 19 ++++--------- codex-rs/core/src/context_manager/history.rs | 2 +- codex-rs/core/src/session/mod.rs | 4 +-- .../src/session/rollout_reconstruction.rs | 13 ++------- .../session/rollout_reconstruction_tests.rs | 18 ++++++++----- codex-rs/core/tests/suite/agents_md.rs | 4 +-- codex-rs/core/tests/suite/remote_env.rs | 14 +++------- .../tests/suite/spawn_agent_description.rs | 5 ++-- codex-rs/protocol/src/protocol.rs | 7 ++--- 13 files changed, 53 insertions(+), 64 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index e4f780c00fa2a9ad7c6c5a57afe0f530898207d7..929b9b875e99fc6b447a18d19ac3d5d140939446 100644 GIT binary patch delta 19594 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 G0&oud>JxMT diff --git a/codex-rs/app-server/tests/suite/v2/git_attribution.rs b/codex-rs/app-server/tests/suite/v2/git_attribution.rs index f8c17225b9..241be5fab3 100644 --- a/codex-rs/app-server/tests/suite/v2/git_attribution.rs +++ b/codex-rs/app-server/tests/suite/v2/git_attribution.rs @@ -354,8 +354,7 @@ fn replace_attribution_fragment_with_legacy( } } if let RolloutItem::WorldState(world_state) = &mut line.item - && let Some(state) = world_state.state.as_object_mut() - && state.remove("git_attribution").is_some() + && world_state.state.remove("git_attribution").is_some() { removed_saved_attribution = true; } diff --git a/codex-rs/core/src/context/world_state/environment_tests.rs b/codex-rs/core/src/context/world_state/environment_tests.rs index 9a9c66e483..043dd67839 100644 --- a/codex-rs/core/src/context/world_state/environment_tests.rs +++ b/codex-rs/core/src/context/world_state/environment_tests.rs @@ -194,7 +194,8 @@ fn changing_primary_environment_updates_model_context_and_persisted_state() -> R assert_eq!( current_world_state .snapshot() - .merge_patch_from(&previous_world_state.snapshot()), + .merge_patch_from(&previous_world_state.snapshot()) + .map(serde_json::Value::Object), Some(json!({ "environments": { "environments": { diff --git a/codex-rs/core/src/context/world_state/mod.rs b/codex-rs/core/src/context/world_state/mod.rs index abd2c1d096..ac90fc9f55 100644 --- a/codex-rs/core/src/context/world_state/mod.rs +++ b/codex-rs/core/src/context/world_state/mod.rs @@ -278,13 +278,24 @@ pub(crate) struct WorldStateSnapshot { sections: BTreeMap, } +impl From<&Map> for WorldStateSnapshot { + fn from(state: &Map) -> Self { + Self { + sections: state + .iter() + .map(|(key, value)| (key.clone(), value.clone())) + .collect(), + } + } +} + impl WorldStateSnapshot { - pub(crate) fn into_value(self) -> Value { - Value::Object(self.sections.into_iter().collect()) + pub(crate) fn into_object(self) -> Map { + self.sections.into_iter().collect() } /// Returns the RFC 7386 merge patch that advances `previous` to `self`. - pub(crate) fn merge_patch_from(&self, previous: &Self) -> Option { + pub(crate) fn merge_patch_from(&self, previous: &Self) -> Option> { let mut patch = Map::new(); // Emit removals first to preserve insertion-ordered JSON patch output. for key in previous.sections.keys() { @@ -301,13 +312,10 @@ impl WorldStateSnapshot { patch.insert(key.clone(), current.clone()); } } - (!patch.is_empty()).then_some(Value::Object(patch)) + (!patch.is_empty()).then_some(patch) } - pub(crate) fn apply_merge_patch(&mut self, patch: &Value) -> serde_json::Result<()> { - let Value::Object(patch) = patch else { - return serde_json::from_value::(patch.clone()).map(drop); - }; + pub(crate) fn apply_merge_patch(&mut self, patch: &Map) { // Borrow existing keys; only newly inserted sections need owned keys. for (key, value) in patch { if value.is_null() { @@ -320,7 +328,6 @@ impl WorldStateSnapshot { self.sections.insert(key.clone(), current); } } - Ok(()) } } diff --git a/codex-rs/core/src/context/world_state/world_state_tests.rs b/codex-rs/core/src/context/world_state/world_state_tests.rs index 41e6580908..7f6f629791 100644 --- a/codex-rs/core/src/context/world_state/world_state_tests.rs +++ b/codex-rs/core/src/context/world_state/world_state_tests.rs @@ -258,25 +258,16 @@ fn snapshot_merge_patch_changes_and_removes_nested_values() { }; assert_eq!( - current.merge_patch_from(&previous), + current.merge_patch_from(&previous).map(Value::Object), Some(json!({ "kept": {"changed": "after", "removed": null}, "removed_section": null, })) ); - previous - .apply_merge_patch( - ¤t - .merge_patch_from(&previous) - .expect("changed snapshots should produce a patch"), - ) - .expect("apply world-state merge patch"); + let patch = current + .merge_patch_from(&previous) + .expect("changed snapshots should produce a patch"); + previous.apply_merge_patch(&patch); assert_eq!(previous, current); assert_eq!(current.merge_patch_from(¤t), None); - - for invalid in [Value::Null, json!(true), json!([])] { - let original = previous.clone(); - assert!(previous.apply_merge_patch(&invalid).is_err()); - assert_eq!(previous, original); - } } diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index d953d09644..41d4b27f10 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -100,7 +100,7 @@ impl ContextManager { let fragments = world_state.render_history_diff(self.world_state_baseline.as_ref(), self.raw_items()); let rollout_item = self.world_state_baseline.as_ref().map_or_else( - || Some(WorldStateItem::full(snapshot.clone().into_value())), + || Some(WorldStateItem::full(snapshot.clone().into_object())), |previous| { snapshot .merge_patch_from(previous) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 8d54232fd9..961b46e9f0 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -3362,7 +3362,7 @@ impl Session { state.replace_annotated_history(items, reference_context_item.clone()); if let Some(world_state) = world_state_baseline { let snapshot = world_state.snapshot(); - world_state_item = Some(WorldStateItem::full(snapshot.clone().into_value())); + world_state_item = Some(WorldStateItem::full(snapshot.clone().into_object())); state.history.set_world_state_baseline(snapshot); } } @@ -3799,7 +3799,7 @@ impl Session { .set_world_state_baseline(snapshot.clone()); ( context_items, - Some(WorldStateItem::full(snapshot.into_value())), + Some(WorldStateItem::full(snapshot.into_object())), ) } else { let (world_state_items, world_state_item) = { diff --git a/codex-rs/core/src/session/rollout_reconstruction.rs b/codex-rs/core/src/session/rollout_reconstruction.rs index 59ae3eba7b..f7f0e8d2fe 100644 --- a/codex-rs/core/src/session/rollout_reconstruction.rs +++ b/codex-rs/core/src/session/rollout_reconstruction.rs @@ -395,23 +395,14 @@ impl Session { match item { RolloutItem::Compacted(_) => world_state_baseline = None, RolloutItem::WorldState(world_state) if world_state.full => { - world_state_baseline = match serde_json::from_value(world_state.state.clone()) { - Ok(snapshot) => Some(snapshot), - Err(err) => { - tracing::warn!(%err, "failed to restore world-state snapshot"); - None - } - }; + world_state_baseline = Some(WorldStateSnapshot::from(&world_state.state)); } RolloutItem::WorldState(world_state) => { let Some(baseline) = world_state_baseline.as_mut() else { tracing::warn!("ignored world-state patch without a full snapshot"); continue; }; - if let Err(err) = baseline.apply_merge_patch(&world_state.state) { - tracing::warn!(%err, "failed to apply world-state patch"); - world_state_baseline = None; - } + baseline.apply_merge_patch(&world_state.state); } RolloutItem::SessionMeta(_) | RolloutItem::ResponseItem(_) diff --git a/codex-rs/core/src/session/rollout_reconstruction_tests.rs b/codex-rs/core/src/session/rollout_reconstruction_tests.rs index 44f597f500..5b6614154a 100644 --- a/codex-rs/core/src/session/rollout_reconstruction_tests.rs +++ b/codex-rs/core/src/session/rollout_reconstruction_tests.rs @@ -21,6 +21,12 @@ use serde_json::json; use std::path::PathBuf; use uuid::Uuid; +macro_rules! object { + ($value:tt) => { + serde_json::from_value(json!($value)).unwrap() + }; +} + fn user_message(text: &str) -> ResponseItem { ResponseItem::Message { id: None, @@ -157,7 +163,7 @@ async fn record_initial_history_restores_world_state_baseline() { .map(RolloutItem::ResponseItem) .collect::>(); world_state_items.push(RolloutItem::WorldState(WorldStateItem::full( - world_state.snapshot().into_value(), + world_state.snapshot().into_object(), ))); let rollout_items = completed_user_turn_rollout(turn_context.to_turn_context_item(), world_state_items); @@ -355,7 +361,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com }, )), RolloutItem::TurnContext(first_context_item.clone()), - RolloutItem::WorldState(WorldStateItem::full(json!({ + RolloutItem::WorldState(WorldStateItem::full(object!({ "test": {"environment": "first"} }))), RolloutItem::ResponseItem(turn_one_user.clone().into()), @@ -391,7 +397,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com }, )), RolloutItem::TurnContext(rolled_back_context_item), - RolloutItem::WorldState(WorldStateItem::patch(json!({ + RolloutItem::WorldState(WorldStateItem::patch(object!({ "test": {"environment": "rolled-back"} }))), RolloutItem::ResponseItem(turn_two_user.into()), @@ -1131,7 +1137,7 @@ async fn reconstruct_history_replays_world_state_from_latest_compaction_window() let rollout_items = completed_user_turn_rollout( turn_context.to_turn_context_item(), vec![ - RolloutItem::WorldState(WorldStateItem::full(json!({ + RolloutItem::WorldState(WorldStateItem::full(object!({ "environment": {"status": "old"} }))), RolloutItem::Compacted(CompactedItem { @@ -1142,10 +1148,10 @@ async fn reconstruct_history_replays_world_state_from_latest_compaction_window() previous_window_id: None, window_id: None, }), - RolloutItem::WorldState(WorldStateItem::full(json!({ + RolloutItem::WorldState(WorldStateItem::full(object!({ "environment": {"status": "starting", "cwd": "/workspace"} }))), - RolloutItem::WorldState(WorldStateItem::patch(json!({ + RolloutItem::WorldState(WorldStateItem::patch(object!({ "environment": {"status": "ready"} }))), ], diff --git a/codex-rs/core/tests/suite/agents_md.rs b/codex-rs/core/tests/suite/agents_md.rs index 5e647c0e34..9007591390 100644 --- a/codex-rs/core/tests/suite/agents_md.rs +++ b/codex-rs/core/tests/suite/agents_md.rs @@ -90,9 +90,9 @@ fn remove_agents_md_world_state_section(rollout_path: &Path) -> Result<()> { .into_iter() .map(|mut line| { if let RolloutItem::WorldState(world_state) = &mut line.item - && let Some(state) = world_state.state.as_object_mut() + && world_state.state.remove("agents_md").is_some() { - removed_section |= state.remove("agents_md").is_some(); + removed_section = true; } serde_json::to_string(&line) }) diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 3702838d37..2cbfb09f67 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -747,7 +747,7 @@ async fn deferred_executor_promotes_primary_environment_when_startup_completes() .collect::>>()? .into_iter() .filter_map(|line| match line.item { - RolloutItem::WorldState(item) if !item.full => Some(item.state), + RolloutItem::WorldState(item) if !item.full => Some(Value::Object(item.state)), _ => None, }) .find(|patch| { @@ -1982,21 +1982,15 @@ async fn deferred_executor_compaction_preserves_then_updates_environment_once() vec![true, true, false] ); assert_eq!( - world_state_items[0] - .state - .pointer("/environments/environments/remote/status"), + world_state_items[0].state["environments"].pointer("/environments/remote/status"), Some(&json!("starting")) ); assert_eq!( - world_state_items[2] - .state - .pointer("/environments/environments/remote/status"), + world_state_items[2].state["environments"].pointer("/environments/remote/status"), Some(&json!("available")) ); assert_eq!( - world_state_items[2] - .state - .pointer("/environments/environments/remote/shell"), + world_state_items[2].state["environments"].pointer("/environments/remote/shell"), Some(&json!("zsh")) ); diff --git a/codex-rs/core/tests/suite/spawn_agent_description.rs b/codex-rs/core/tests/suite/spawn_agent_description.rs index d78511193a..8cb49c11e9 100644 --- a/codex-rs/core/tests/suite/spawn_agent_description.rs +++ b/codex-rs/core/tests/suite/spawn_agent_description.rs @@ -440,9 +440,8 @@ async fn multi_agent_v2_cold_resume_refreshes_legacy_usage_hints_once( .collect::, _>>()? .into_iter() .map(|mut line| { - if let RolloutItem::WorldState(world_state) = &mut line.item - && let Some(state) = world_state.state.as_object_mut() - { + if let RolloutItem::WorldState(world_state) = &mut line.item { + let state = &mut world_state.state; removed_recorded_usage_hint |= state.remove("multi_agent_usage_hint").is_some(); if let Some(mode) = state .get_mut("multi_agent_mode") diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 2bd7a8ab97..9167b82f78 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -63,6 +63,7 @@ use serde::Deserialize; use serde::Deserializer; use serde::Serialize; use serde::de::Error as _; +use serde_json::Map; use serde_json::Value; use serde_with::serde_as; use strum_macros::Display; @@ -2996,15 +2997,15 @@ impl<'de> Deserialize<'de> for SessionMetaLine { pub struct WorldStateItem { /// Full snapshots establish a new baseline; patches update the current baseline. pub full: bool, - pub state: Value, + pub state: Map, } impl WorldStateItem { - pub fn full(state: Value) -> Self { + pub fn full(state: Map) -> Self { Self { full: true, state } } - pub fn patch(state: Value) -> Self { + pub fn patch(state: Map) -> Self { Self { full: false, state } } }