@@ -104,8 +104,11 @@ use crate::state::SessionServices;
104104use  crate :: state:: SessionState ; 
105105use  crate :: state:: TaskKind ; 
106106use  crate :: tasks:: CompactTask ; 
107+ use  crate :: tasks:: GhostSnapshotTask ; 
107108use  crate :: tasks:: RegularTask ; 
108109use  crate :: tasks:: ReviewTask ; 
110+ use  crate :: tasks:: SessionTask ; 
111+ use  crate :: tasks:: SessionTaskContext ; 
109112use  crate :: tools:: ToolRouter ; 
110113use  crate :: tools:: context:: SharedTurnDiffTracker ; 
111114use  crate :: tools:: parallel:: ToolCallRuntime ; 
@@ -128,6 +131,8 @@ use codex_protocol::models::ResponseInputItem;
128131use  codex_protocol:: models:: ResponseItem ; 
129132use  codex_protocol:: protocol:: InitialHistory ; 
130133use  codex_protocol:: user_input:: UserInput ; 
134+ use  codex_utils_readiness:: Readiness ; 
135+ use  codex_utils_readiness:: ReadinessFlag ; 
131136
132137pub  mod  compact; 
133138use  self :: compact:: build_compacted_history; 
@@ -178,6 +183,7 @@ impl Codex {
178183            sandbox_policy :  config. sandbox_policy . clone ( ) , 
179184            cwd :  config. cwd . clone ( ) , 
180185            original_config_do_not_use :  Arc :: clone ( & config) , 
186+             features :  config. features . clone ( ) , 
181187        } ; 
182188
183189        // Generate a unique ID for the lifetime of this Codex session. 
@@ -271,6 +277,7 @@ pub(crate) struct TurnContext {
271277    pub ( crate )  is_review_mode :  bool , 
272278    pub ( crate )  final_output_json_schema :  Option < Value > , 
273279    pub ( crate )  codex_linux_sandbox_exe :  Option < PathBuf > , 
280+     pub ( crate )  tool_call_gate :  Arc < ReadinessFlag > , 
274281} 
275282
276283impl  TurnContext  { 
@@ -312,6 +319,9 @@ pub(crate) struct SessionConfiguration {
312319/// operate deterministically. 
313320cwd :  PathBuf , 
314321
322+     /// Set of feature flags for this session 
323+ features :  Features , 
324+ 
315325    // TODO(pakrym): Remove config from here 
316326    original_config_do_not_use :  Arc < Config > , 
317327} 
@@ -406,6 +416,7 @@ impl Session {
406416            is_review_mode :  false , 
407417            final_output_json_schema :  None , 
408418            codex_linux_sandbox_exe :  config. codex_linux_sandbox_exe . clone ( ) , 
419+             tool_call_gate :  Arc :: new ( ReadinessFlag :: new ( ) ) , 
409420        } 
410421    } 
411422
@@ -1096,6 +1107,43 @@ impl Session {
10961107        self . send_event ( turn_context,  event) . await ; 
10971108    } 
10981109
1110+     async  fn  maybe_start_ghost_snapshot ( 
1111+         self :  & Arc < Self > , 
1112+         turn_context :  Arc < TurnContext > , 
1113+         cancellation_token :  CancellationToken , 
1114+     )  { 
1115+         if  turn_context. is_review_mode 
1116+             || !self 
1117+                 . state 
1118+                 . lock ( ) 
1119+                 . await 
1120+                 . session_configuration 
1121+                 . features 
1122+                 . enabled ( Feature :: GhostCommit ) 
1123+         { 
1124+             return ; 
1125+         } 
1126+ 
1127+         let  token = match  turn_context. tool_call_gate . subscribe ( ) . await  { 
1128+             Ok ( token)  => token, 
1129+             Err ( err)  => { 
1130+                 warn ! ( "failed to subscribe to ghost snapshot readiness: {err}" ) ; 
1131+                 return ; 
1132+             } 
1133+         } ; 
1134+ 
1135+         info ! ( "spawning ghost snapshot task" ) ; 
1136+         let  task = GhostSnapshotTask :: new ( token) ; 
1137+         Arc :: new ( task) 
1138+             . run ( 
1139+                 Arc :: new ( SessionTaskContext :: new ( self . clone ( ) ) ) , 
1140+                 turn_context. clone ( ) , 
1141+                 Vec :: new ( ) , 
1142+                 cancellation_token, 
1143+             ) 
1144+             . await ; 
1145+     } 
1146+ 
10991147    /// Returns the input if there was no task running to inject into 
11001148pub  async  fn  inject_input ( & self ,  input :  Vec < UserInput > )  -> Result < ( ) ,  Vec < UserInput > >  { 
11011149        let  mut  active = self . active_turn . lock ( ) . await ; 
@@ -1508,6 +1556,7 @@ async fn spawn_review_thread(
15081556        is_review_mode :  true , 
15091557        final_output_json_schema :  None , 
15101558        codex_linux_sandbox_exe :  parent_turn_context. codex_linux_sandbox_exe . clone ( ) , 
1559+         tool_call_gate :  Arc :: new ( ReadinessFlag :: new ( ) ) , 
15111560    } ; 
15121561
15131562    // Seed the child task with the review prompt as the initial user message. 
@@ -1571,6 +1620,8 @@ pub(crate) async fn run_task(
15711620            . await ; 
15721621    } 
15731622
1623+     sess. maybe_start_ghost_snapshot ( Arc :: clone ( & turn_context) ,  cancellation_token. child_token ( ) ) 
1624+         . await ; 
15741625    let  mut  last_agent_message:  Option < String >  = None ; 
15751626    // Although from the perspective of codex.rs, TurnDiffTracker has the lifecycle of a Task which contains 
15761627    // many turns, from the perspective of the user, it is a single turn. 
@@ -1763,6 +1814,13 @@ fn parse_review_output_event(text: &str) -> ReviewOutputEvent {
17631814    } 
17641815} 
17651816
1817+ fn  filter_model_visible_history ( input :  Vec < ResponseItem > )  -> Vec < ResponseItem >  { 
1818+     input
1819+         . into_iter ( ) 
1820+         . filter ( |item| !matches ! ( item,  ResponseItem :: GhostSnapshot  {  .. } ) ) 
1821+         . collect ( ) 
1822+ } 
1823+ 
17661824async  fn  run_turn ( 
17671825    sess :  Arc < Session > , 
17681826    turn_context :  Arc < TurnContext > , 
@@ -1783,7 +1841,7 @@ async fn run_turn(
17831841        . supports_parallel_tool_calls ; 
17841842    let  parallel_tool_calls = model_supports_parallel; 
17851843    let  prompt = Prompt  { 
1786-         input, 
1844+         input :   filter_model_visible_history ( input ) , 
17871845        tools :  router. specs ( ) , 
17881846        parallel_tool_calls, 
17891847        base_instructions_override :  turn_context. base_instructions . clone ( ) , 
@@ -2278,6 +2336,8 @@ fn is_mcp_client_startup_timeout_error(error: &anyhow::Error) -> bool {
22782336        || error_message. contains ( "timed out handshaking with MCP server" ) 
22792337} 
22802338
2339+ use  crate :: features:: Feature ; 
2340+ use  crate :: features:: Features ; 
22812341#[ cfg( test) ]  
22822342pub ( crate )  use  tests:: make_session_and_context; 
22832343
@@ -2594,6 +2654,7 @@ mod tests {
25942654            sandbox_policy :  config. sandbox_policy . clone ( ) , 
25952655            cwd :  config. cwd . clone ( ) , 
25962656            original_config_do_not_use :  Arc :: clone ( & config) , 
2657+             features :  Features :: default ( ) , 
25972658        } ; 
25982659
25992660        let  state = SessionState :: new ( session_configuration. clone ( ) ) ; 
@@ -2662,6 +2723,7 @@ mod tests {
26622723            sandbox_policy :  config. sandbox_policy . clone ( ) , 
26632724            cwd :  config. cwd . clone ( ) , 
26642725            original_config_do_not_use :  Arc :: clone ( & config) , 
2726+             features :  Features :: default ( ) , 
26652727        } ; 
26662728
26672729        let  state = SessionState :: new ( session_configuration. clone ( ) ) ; 
0 commit comments