@@ -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.
313320 cwd : 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
11001148 pub 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