core: route downstream reasoning effort through steps

This commit is contained in:
Ahmed Ibrahim
2026-07-08 18:49:27 -07:00
parent 051b72c370
commit eceb3ddbe6
50 changed files with 474 additions and 254 deletions
+6 -5
View File
@@ -11,6 +11,7 @@ use crate::config::ConfigBuilder;
use crate::context::ContextualUserFragment;
use crate::context::SubagentNotification;
use crate::init_state_db;
use crate::session::tests::turn_context_item_for_test;
use crate::thread_manager::StartThreadOptions;
use assert_matches::assert_matches;
use codex_extension_api::ExtensionDataInit;
@@ -983,7 +984,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
],
)
.await;
let parent_reference_context_item = turn_context.to_turn_context_item();
let parent_reference_context_item = turn_context_item_for_test(&turn_context);
parent_thread
.codex
.session
@@ -1187,7 +1188,7 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
previous_window_id: None,
window_id: None,
}),
RolloutItem::TurnContext(turn_context.to_turn_context_item()),
RolloutItem::TurnContext(turn_context_item_for_test(&turn_context)),
RolloutItem::ResponseItem(spawn_agent_call(&parent_spawn_call_id)),
])
.await;
@@ -1374,9 +1375,9 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
parent_thread
.codex
.session
.persist_rollout_items(&[RolloutItem::TurnContext(
spawn_turn_context.to_turn_context_item(),
)])
.persist_rollout_items(&[RolloutItem::TurnContext(turn_context_item_for_test(
&spawn_turn_context,
))])
.await;
parent_thread
.codex
+6 -4
View File
@@ -51,6 +51,7 @@ use crate::session::CodexSpawnOk;
use crate::session::SUBMISSION_CHANNEL_CAPACITY;
use crate::session::emit_subagent_session_started;
use crate::session::session::Session;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnContext;
use codex_login::AuthManager;
use codex_models_manager::manager::SharedModelsManager;
@@ -514,7 +515,7 @@ async fn handle_exec_approval(
let review_cancel = cancel_token.child_token();
let review_rx = spawn_approval_request_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
StepContextSeed::from_turn(Arc::clone(parent_ctx)),
new_guardian_review_id(),
GuardianApprovalRequest::Shell {
id: call_id.clone(),
@@ -627,7 +628,7 @@ async fn handle_patch_approval(
.join("\n");
let review_rx = spawn_approval_request_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
StepContextSeed::from_turn(Arc::clone(parent_ctx)),
new_guardian_review_id(),
GuardianApprovalRequest::ApplyPatch {
id: approval_id.clone(),
@@ -749,7 +750,7 @@ async fn maybe_auto_review_mcp_request_user_input(
let review_cancel = cancel_token.child_token();
let review_rx = spawn_approval_request_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
StepContextSeed::from_turn(Arc::clone(parent_ctx)),
new_guardian_review_id(),
build_guardian_mcp_tool_review_request(&event.call_id, &invocation, metadata.as_ref()),
/*retry_reason*/ None,
@@ -809,8 +810,9 @@ async fn handle_request_permissions(
#[allow(deprecated)]
parent_ctx.cwd.clone()
});
let step_context_seed = StepContextSeed::from_turn(Arc::clone(parent_ctx));
let response_fut = parent_session.request_permissions_for_cwd(
parent_ctx,
&step_context_seed,
call_id.clone(),
args,
cwd,
+4 -4
View File
@@ -70,14 +70,14 @@ pub(crate) enum InitialContextInjection {
pub(crate) async fn build_compaction_initial_context(
sess: &Session,
turn_context: &TurnContext,
step_context: &StepContext,
initial_context_injection: &InitialContextInjection,
) -> (Vec<ResponseItem>, Option<Arc<WorldState>>) {
// Return the rendered state with its items so history and its baseline stay identical.
match initial_context_injection {
InitialContextInjection::BeforeLastUserMessage(world_state) => {
let items = sess
.build_initial_context_with_world_state(turn_context, world_state.as_ref())
.build_initial_context_with_world_state(step_context, world_state.as_ref())
.await;
(items, Some(Arc::clone(world_state)))
}
@@ -337,7 +337,7 @@ async fn run_compact_task_inner_impl(
let (initial_context, world_state_baseline) = build_compaction_initial_context(
sess.as_ref(),
turn_context.as_ref(),
step_context.as_ref(),
&initial_context_injection,
)
.await;
@@ -348,7 +348,7 @@ async fn run_compact_task_inner_impl(
let reference_context_item = match initial_context_injection {
InitialContextInjection::DoNotInject => None,
InitialContextInjection::BeforeLastUserMessage(_) => {
Some(turn_context.to_turn_context_item())
Some(step_context.to_turn_context_item())
}
};
let compacted_item = CompactedItem {
+9 -8
View File
@@ -212,8 +212,8 @@ async fn run_remote_compact_task_inner_impl(
analytics_details,
)
.await;
let (attempt, compaction_turn_context) = match attempt {
Ok(attempt) => (attempt, turn_context),
let (attempt, compaction_step_context) = match attempt {
Ok(attempt) => (attempt, step_context),
Err(error) => {
let Some(fallback_step_context) = fallback_step_context else {
return Err(error);
@@ -247,11 +247,12 @@ async fn run_remote_compact_task_inner_impl(
fallback_result.as_ref().err(),
);
match fallback_result {
Ok(attempt) => (attempt, fallback_turn_context),
Ok(attempt) => (attempt, fallback_step_context),
Err(_) => return Err(error),
}
}
};
let compaction_turn_context = compaction_step_context.turn.as_ref();
let RemoteCompactAttempt {
new_history,
trace_input_history,
@@ -259,7 +260,7 @@ async fn run_remote_compact_task_inner_impl(
let (new_window_number, new_window_ids) = sess.advance_auto_compact_window().await;
let (new_history, world_state_baseline) = process_compacted_history(
sess.as_ref(),
compaction_turn_context.as_ref(),
compaction_step_context.as_ref(),
new_history,
&initial_context_injection,
)
@@ -268,7 +269,7 @@ async fn run_remote_compact_task_inner_impl(
let reference_context_item = match initial_context_injection {
InitialContextInjection::DoNotInject => None,
InitialContextInjection::BeforeLastUserMessage(_) => {
Some(compaction_turn_context.to_turn_context_item())
Some(compaction_step_context.to_turn_context_item())
}
};
let compacted_item = CompactedItem {
@@ -287,7 +288,7 @@ async fn run_remote_compact_task_inner_impl(
replacement_history: &new_history,
});
sess.replace_compacted_history(
compaction_turn_context.as_ref(),
compaction_turn_context,
new_history,
reference_context_item,
world_state_baseline,
@@ -303,7 +304,7 @@ async fn run_remote_compact_task_inner_impl(
pub(crate) async fn process_compacted_history(
sess: &Session,
turn_context: &TurnContext,
step_context: &StepContext,
mut compacted_history: Vec<ResponseItem>,
initial_context_injection: &InitialContextInjection,
) -> (Vec<ResponseItem>, Option<Arc<WorldState>>) {
@@ -311,7 +312,7 @@ pub(crate) async fn process_compacted_history(
// message in the replacement history. Pre-turn compaction instead injects context after the
// compaction item, but mid-turn compaction keeps the compaction item last for model training.
let (initial_context, world_state_baseline) =
build_compaction_initial_context(sess, turn_context, initial_context_injection).await;
build_compaction_initial_context(sess, step_context, initial_context_injection).await;
compacted_history.retain(should_keep_compacted_history_item);
(
+7 -6
View File
@@ -224,8 +224,8 @@ async fn run_remote_compact_task_inner_impl(
analytics_details,
)
.await;
let (attempt, compaction_turn_context) = match attempt {
Ok(attempt) => (attempt, turn_context),
let (attempt, compaction_step_context) = match attempt {
Ok(attempt) => (attempt, step_context),
Err(error) => {
let Some(fallback_step_context) = fallback_step_context else {
return Err(error);
@@ -259,11 +259,12 @@ async fn run_remote_compact_task_inner_impl(
fallback_result.as_ref().err(),
);
match fallback_result {
Ok(attempt) => (attempt, fallback_turn_context),
Ok(attempt) => (attempt, fallback_step_context),
Err(_) => return Err(error),
}
}
};
let compaction_turn_context = compaction_step_context.turn.as_ref();
let RemoteCompactV2Attempt {
trace_input_history,
prompt_input,
@@ -283,7 +284,7 @@ async fn run_remote_compact_task_inner_impl(
let (new_window_number, new_window_ids) = sess.advance_auto_compact_window().await;
let (new_history, world_state_baseline) = process_compacted_history(
sess.as_ref(),
compaction_turn_context.as_ref(),
compaction_step_context.as_ref(),
compacted_history,
&initial_context_injection,
)
@@ -292,7 +293,7 @@ async fn run_remote_compact_task_inner_impl(
let reference_context_item = match initial_context_injection {
InitialContextInjection::DoNotInject => None,
InitialContextInjection::BeforeLastUserMessage(_) => {
Some(compaction_turn_context.to_turn_context_item())
Some(compaction_step_context.to_turn_context_item())
}
};
let compacted_item = CompactedItem {
@@ -308,7 +309,7 @@ async fn run_remote_compact_task_inner_impl(
replacement_history: &new_history,
});
sess.replace_compacted_history(
compaction_turn_context.as_ref(),
compaction_turn_context,
new_history,
reference_context_item,
world_state_baseline,
+4 -2
View File
@@ -1,4 +1,5 @@
use super::*;
use crate::session::step_context::StepContext;
use crate::session::tests::build_world_state_from_turn_context;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
@@ -13,17 +14,18 @@ async fn process_compacted_history_with_test_session(
) -> (Vec<ResponseItem>, Vec<ResponseItem>) {
let (session, turn_context) = crate::session::tests::make_session_and_context().await;
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
session
.set_previous_turn_settings(previous_turn_settings.cloned())
.await;
let world_state = Arc::new(build_world_state_from_turn_context(&session, &turn_context).await);
let initial_context = session
.build_initial_context_with_world_state(&turn_context, world_state.as_ref())
.build_initial_context_with_world_state(&step_context, world_state.as_ref())
.await;
let initial_context_injection = InitialContextInjection::BeforeLastUserMessage(world_state);
let (refreshed, _) = crate::compact_remote::process_compacted_history(
&session,
&turn_context,
&step_context,
compacted_history,
&initial_context_injection,
)
+5 -5
View File
@@ -38,7 +38,7 @@ pub(crate) async fn run_manual_compact_task(
// Manual compaction runs outside run_turn, so it captures its own current step.
let step_context = sess.capture_step_context(Arc::clone(&turn_context)).await;
let world_state = Arc::new(sess.build_world_state_for_step(&step_context).await);
run_compact_task_inner(&sess, &turn_context, world_state, CompactionTrigger::Manual).await
run_compact_task_inner(&sess, &step_context, world_state, CompactionTrigger::Manual).await
}
/// Runs token-budget inline auto-compaction as a normal compaction lifecycle.
@@ -51,22 +51,22 @@ pub(crate) async fn run_inline_auto_compact_task(
step_context: Arc<StepContext>,
initial_context_injection: InitialContextInjection,
) -> CodexResult<()> {
let turn_context = &step_context.turn;
let world_state = match initial_context_injection {
InitialContextInjection::BeforeLastUserMessage(world_state) => world_state,
InitialContextInjection::DoNotInject => {
Arc::new(sess.build_world_state_for_step(&step_context).await)
}
};
run_compact_task_inner(&sess, turn_context, world_state, CompactionTrigger::Auto).await
run_compact_task_inner(&sess, &step_context, world_state, CompactionTrigger::Auto).await
}
async fn run_compact_task_inner(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
step_context: &Arc<StepContext>,
world_state: Arc<WorldState>,
trigger: CompactionTrigger,
) -> CodexResult<()> {
let turn_context = &step_context.turn;
let pre_compact_outcome = run_pre_compact_hooks(sess, turn_context, trigger).await;
match pre_compact_outcome {
PreCompactHookOutcome::Continue => {}
@@ -76,7 +76,7 @@ async fn run_compact_task_inner(
let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new());
sess.emit_turn_item_started(turn_context, &compaction_item)
.await;
sess.start_new_context_window(turn_context.as_ref(), world_state)
sess.start_new_context_window(step_context.as_ref(), world_state)
.await;
sess.emit_turn_item_completed(turn_context, compaction_item)
.await;
+13 -13
View File
@@ -9,6 +9,7 @@ use crate::context::RealtimeEndInstructions;
use crate::context::RealtimeStartInstructions;
use crate::context::RealtimeStartWithInstructions;
use crate::session::PreviousTurnSettings;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use codex_execpolicy::Policy;
use codex_features::Feature;
@@ -63,20 +64,18 @@ fn build_permissions_update_item(
fn build_collaboration_mode_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
next: &StepContext,
) -> Option<String> {
if !next.config.include_collaboration_mode_instructions {
if !next.turn.config.include_collaboration_mode_instructions {
return None;
}
let prev = previous?;
if prev.collaboration_mode.as_ref() != Some(&next.collaboration_mode) {
let collaboration_mode = next.collaboration_mode();
if prev.collaboration_mode.as_ref() != Some(&collaboration_mode) {
// If the next mode has empty developer instructions, this returns None and we emit no
// update, so prior collaboration instructions remain in the prompt history.
Some(
CollaborationModeInstructions::from_collaboration_mode(&next.collaboration_mode)?
.render(),
)
Some(CollaborationModeInstructions::from_collaboration_mode(&collaboration_mode)?.render())
} else {
None
}
@@ -84,7 +83,7 @@ fn build_collaboration_mode_update_item(
fn build_multi_agent_mode_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
next: &StepContext,
) -> Option<String> {
let effective_multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode(next);
let previous = previous?;
@@ -240,10 +239,11 @@ fn build_text_message(role: &str, text_sections: Vec<String>) -> Option<Response
pub(crate) fn build_settings_update_items(
previous: Option<&TurnContextItem>,
previous_turn_settings: Option<&PreviousTurnSettings>,
next: &TurnContext,
next: &StepContext,
exec_policy: &Policy,
personality_feature_enabled: bool,
) -> Vec<ResponseItem> {
let turn_context = next.turn.as_ref();
// TODO(ccunningham): build_settings_update_items still does not cover every
// model-visible item emitted by build_initial_context. Persist the remaining
// inputs or add explicit replay events so fork/resume can diff everything
@@ -251,12 +251,12 @@ pub(crate) fn build_settings_update_items(
let developer_update_sections = [
// Keep model-switch instructions first so model-specific guidance is read before
// any other context diffs on this turn.
build_model_instructions_update_item(previous_turn_settings, next),
build_permissions_update_item(previous, next, exec_policy),
build_model_instructions_update_item(previous_turn_settings, turn_context),
build_permissions_update_item(previous, turn_context, exec_policy),
build_collaboration_mode_update_item(previous, next),
build_multi_agent_mode_update_item(previous, next),
build_realtime_update_item(previous, previous_turn_settings, next),
build_personality_update_item(previous, next, personality_feature_enabled),
build_realtime_update_item(previous, previous_turn_settings, turn_context),
build_personality_update_item(previous, turn_context, personality_feature_enabled),
]
.into_iter()
.flatten()
+30 -27
View File
@@ -25,6 +25,7 @@ use tokio::time::sleep_until;
use tokio_util::sync::CancellationToken;
use crate::session::session::Session;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnContext;
use crate::turn_timing::now_unix_timestamp_ms;
use crate::util::backoff;
@@ -274,13 +275,14 @@ pub(crate) async fn record_guardian_denial_for_test(
/// caller as distinct from explicit guardian denials.
async fn run_guardian_review(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context_seed: StepContextSeed,
review_id: String,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
approval_request_source: GuardianApprovalRequestSource,
external_cancel: Option<CancellationToken>,
) -> ReviewDecision {
let turn = Arc::clone(&step_context_seed.turn);
let target_item_id = guardian_request_target_item_id(&request).map(str::to_string);
let assessment_turn_id = guardian_request_turn_id(&request, &turn.sub_id).to_string();
let action_summary = guardian_assessment_action(&request);
@@ -358,7 +360,7 @@ async fn run_guardian_review(
let terminal_action = action_summary.clone();
let (outcome, analytics_result) = Box::pin(run_guardian_review_session_with_retry(
session.clone(),
turn.clone(),
step_context_seed.clone(),
request,
retry_reason.clone(),
schema,
@@ -593,7 +595,7 @@ async fn run_guardian_review(
/// Public entrypoint for approval requests that should be reviewed by guardian.
pub(crate) async fn review_approval_request(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
step_context_seed: &StepContextSeed,
review_id: String,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
@@ -602,7 +604,7 @@ pub(crate) async fn review_approval_request(
// guardian session state machine into their own async stack.
Box::pin(run_guardian_review(
Arc::clone(session),
Arc::clone(turn),
step_context_seed.clone(),
review_id,
request,
retry_reason,
@@ -614,7 +616,7 @@ pub(crate) async fn review_approval_request(
pub(crate) async fn review_approval_request_with_cancel(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
step_context_seed: &StepContextSeed,
review_id: String,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
@@ -623,7 +625,7 @@ pub(crate) async fn review_approval_request_with_cancel(
) -> ReviewDecision {
run_guardian_review(
Arc::clone(session),
Arc::clone(turn),
step_context_seed.clone(),
review_id,
request,
retry_reason,
@@ -635,7 +637,7 @@ pub(crate) async fn review_approval_request_with_cancel(
pub(crate) fn spawn_approval_request_review(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context_seed: StepContextSeed,
review_id: String,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
@@ -653,7 +655,7 @@ pub(crate) fn spawn_approval_request_review(
};
let decision = runtime.block_on(review_approval_request_with_cancel(
&session,
&turn,
&step_context_seed,
review_id,
request,
retry_reason,
@@ -677,8 +679,9 @@ pub(super) struct GuardianReviewSessionConfig {
pub(super) async fn guardian_review_session_config(
session: &Session,
turn: &TurnContext,
step_context_seed: &StepContextSeed,
) -> anyhow::Result<GuardianReviewSessionConfig> {
let turn = step_context_seed.turn.as_ref();
let network_proxy = session.services.network_proxy.load_full();
let live_network_config = match network_proxy.as_ref() {
Some(network_proxy) => Some(network_proxy.proxy().current_cfg().await?),
@@ -722,8 +725,8 @@ pub(super) async fn guardian_review_session_config(
.supported_reasoning_levels
.iter()
.any(|preset| preset.effort == codex_protocol::openai_models::ReasoningEffort::Low),
turn.config
.model_reasoning_effort
step_context_seed
.reasoning_effort
.clone()
.or_else(|| turn.model_info.default_reasoning_level.clone()),
);
@@ -768,29 +771,29 @@ pub(super) async fn guardian_review_session_config(
/// rules.
async fn run_guardian_review_session_before_deadline(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context_seed: StepContextSeed,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
schema: serde_json::Value,
external_cancel: Option<CancellationToken>,
deadline: Instant,
) -> (GuardianReviewOutcome, GuardianReviewAnalyticsResult) {
let session_config = match guardian_review_session_config(session.as_ref(), turn.as_ref()).await
{
Ok(session_config) => session_config,
Err(err) => {
return (
GuardianReviewOutcome::Error(GuardianReviewError::prompt_build(err)),
GuardianReviewAnalyticsResult::without_session(),
);
}
};
let session_config =
match guardian_review_session_config(session.as_ref(), &step_context_seed).await {
Ok(session_config) => session_config,
Err(err) => {
return (
GuardianReviewOutcome::Error(GuardianReviewError::prompt_build(err)),
GuardianReviewAnalyticsResult::without_session(),
);
}
};
let (session_outcome, session_analytics_result) = Box::pin(
session
.guardian_review_session
.run_review(GuardianReviewSessionParams {
parent_session: Arc::clone(&session),
parent_turn: turn.clone(),
parent_turn: Arc::clone(&step_context_seed.turn),
spawn_config: session_config.spawn_config,
request,
retry_reason,
@@ -801,8 +804,8 @@ async fn run_guardian_review_session_before_deadline(
guardian_catalog_contains_auto_review: session_config.catalog_contains_auto_review,
guardian_review_model_overridden: session_config.model_overridden,
guardian_review_model_override: session_config.model_override,
reasoning_summary: turn.reasoning_summary,
personality: turn.personality,
reasoning_summary: step_context_seed.turn.reasoning_summary,
personality: step_context_seed.turn.personality,
external_cancel,
deadline,
}),
@@ -862,7 +865,7 @@ async fn run_guardian_review_session_before_deadline(
pub(super) async fn run_guardian_review_session_with_retry(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context_seed: StepContextSeed,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
schema: serde_json::Value,
@@ -875,7 +878,7 @@ pub(super) async fn run_guardian_review_session_with_retry(
loop {
let (outcome, mut analytics_result) = run_guardian_review_session_before_deadline(
Arc::clone(&session),
Arc::clone(&turn),
step_context_seed.clone(),
request.clone(),
retry_reason.clone(),
schema.clone(),
+7 -3
View File
@@ -44,6 +44,7 @@ use crate::context::ContextualUserFragment;
use crate::context::GuardianFollowupReviewReminder;
use crate::session::Codex;
use crate::session::session::Session;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnContext;
use codex_config::types::McpServerConfig;
use codex_features::Feature;
@@ -299,9 +300,12 @@ impl GuardianReviewSessionManager {
) -> BoxFuture<'_, anyhow::Result<()>> {
// Boxing breaks the Session::new -> Guardian -> Session::new future recursion.
Box::pin(async move {
let spawn_config = guardian_review_session_config(&parent_session, &parent_turn)
.await?
.spawn_config;
let spawn_config = guardian_review_session_config(
&parent_session,
&StepContextSeed::from_turn(Arc::clone(&parent_turn)),
)
.await?
.spawn_config;
let reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(
&spawn_config,
parent_session.user_instructions().await,
+9 -8
View File
@@ -7,6 +7,7 @@ use crate::config::NetworkProxySpec;
use crate::config::test_config;
use crate::guardian::approval_request::guardian_request_target_item_id;
use crate::session::session::Session;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnContext;
use crate::test_support;
use codex_analytics::GuardianApprovalRequestSource;
@@ -1151,7 +1152,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
let decision = review_approval_request_with_cancel(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-cancelled-guardian".to_string(),
GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
@@ -2321,7 +2322,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
let decision = review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-shell-guardian-error".to_string(),
GuardianApprovalRequest::Shell {
id: "shell-guardian-error".to_string(),
@@ -2461,7 +2462,7 @@ async fn guardian_review_does_not_retry_missing_assessment_payload() -> anyhow::
let decision = review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-missing-assessment".to_string(),
guardian_shell_request("shell-missing-assessment"),
/*retry_reason*/ None,
@@ -2565,7 +2566,7 @@ async fn guardian_review_exhausts_three_failures_with_one_terminal_event() -> an
let decision = review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-exhausted-retry".to_string(),
guardian_shell_request("shell-exhausted-retry"),
/*retry_reason*/ None,
@@ -2616,7 +2617,7 @@ async fn guardian_review_does_not_retry_valid_denial() -> anyhow::Result<()> {
let decision = review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-valid-denial".to_string(),
guardian_shell_request("shell-valid-denial"),
/*retry_reason*/ None,
@@ -2719,7 +2720,7 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
assert_eq!(
review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-shell-guardian-1".to_string(),
initial_request,
/*retry_reason*/ None
@@ -2773,7 +2774,7 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
let mut second_review = tokio::spawn(async move {
review_approval_request(
&session_for_second,
&turn_for_second,
&StepContextSeed::from_turn(Arc::clone(&turn_for_second)),
"review-shell-guardian-2".to_string(),
second_request,
Some("trunk follow-up".to_string()),
@@ -2820,7 +2821,7 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
let third_decision = review_approval_request(
&session,
&turn,
&StepContextSeed::from_turn(Arc::clone(&turn)),
"review-shell-guardian-3".to_string(),
third_request,
Some("parallel follow-up".to_string()),
+2 -1
View File
@@ -19,6 +19,7 @@ use crate::mcp_tool_approval_templates::RenderedMcpToolApprovalParam;
use crate::mcp_tool_approval_templates::render_mcp_tool_approval_template;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnContext;
use crate::tools::hook_names::HookToolName;
use crate::tools::sandboxing::PermissionRequestPayload;
@@ -1274,7 +1275,7 @@ async fn maybe_request_mcp_tool_approval(
let review_id = new_guardian_review_id();
let decision = review_approval_request(
sess,
turn_context,
&StepContextSeed::from(step_context.as_ref()),
review_id.clone(),
build_guardian_mcp_tool_review_request(call_id, invocation, metadata),
/*retry_reason*/ None,
+14 -8
View File
@@ -93,10 +93,11 @@ fn approval_metadata(
}
}
fn mcp_turn_metadata_context(turn_context: &TurnContext) -> McpTurnMetadataContext<'_> {
fn mcp_turn_metadata_context(step_context: &StepContext) -> McpTurnMetadataContext<'_> {
let turn_context = step_context.turn.as_ref();
McpTurnMetadataContext {
model: turn_context.model_info.slug.as_str(),
reasoning_effort: turn_context.effective_reasoning_effort(),
reasoning_effort: step_context.effective_reasoning_effort(),
}
}
@@ -1091,12 +1092,17 @@ fn truncate_mcp_tool_result_for_event_bounds_large_error() {
#[tokio::test]
async fn mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server() {
let (_, turn_context) = make_session_and_context().await;
let (_, mut turn_context) = make_session_and_context().await;
Arc::make_mut(&mut turn_context.config).model_reasoning_effort =
Some(codex_protocol::openai_models::ReasoningEffort::High);
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let mut step_context = StepContext::for_test(Arc::clone(&turn_context));
Arc::get_mut(&mut step_context)
.expect("step context should be uniquely owned")
.reasoning_effort = Some(codex_protocol::openai_models::ReasoningEffort::Low);
let expected_turn_metadata = turn_context
.turn_metadata_state
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&turn_context))
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&step_context))
.expect("turn metadata");
let meta = build_mcp_tool_call_request_meta(
@@ -1204,7 +1210,7 @@ async fn plugin_mcp_tool_call_request_meta_includes_plugin_id() {
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let expected_turn_metadata = turn_context
.turn_metadata_state
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&turn_context))
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&step_context))
.expect("turn metadata");
let mut metadata = approval_metadata(
/*connector_id*/ None, /*connector_name*/ None,
@@ -1325,7 +1331,7 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let expected_turn_metadata = turn_context
.turn_metadata_state
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&turn_context))
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&step_context))
.expect("turn metadata");
let metadata = McpToolApprovalMetadata {
annotations: None,
@@ -1378,7 +1384,7 @@ async fn codex_apps_tool_call_request_meta_includes_call_id_without_existing_cod
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let expected_turn_metadata = turn_context
.turn_metadata_state
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&turn_context))
.current_meta_value_for_mcp_request(mcp_turn_metadata_context(&step_context))
.expect("turn metadata");
assert_eq!(
@@ -11,6 +11,7 @@ use tokio::sync::watch;
use tokio_util::sync::CancellationToken;
use super::tests::make_session_and_context_with_rx;
use crate::session::step_context::StepContextSeed;
use crate::state::ActiveTurn;
async fn wait_until_held(pause_state: &mut watch::Receiver<bool>) {
@@ -114,7 +115,7 @@ async fn permission_request_holds_an_elicitation_until_response() {
.selection();
session
.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
"call-1".to_string(),
RequestPermissionsArgs {
environment_id: None,
+2 -1
View File
@@ -1,3 +1,4 @@
use super::step_context::StepContextSeed;
use super::*;
use crate::mcp::McpRuntimeProjection;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
@@ -623,7 +624,7 @@ async fn review_guardian_mcp_elicitation(
let review_id = crate::guardian::new_guardian_review_id();
let decision = crate::guardian::review_approval_request(
&session,
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
review_id.clone(),
guardian_request,
/*retry_reason*/ None,
+21 -18
View File
@@ -40,6 +40,7 @@ use crate::image_preparation::prepare_response_items;
use crate::parse_turn_item;
use crate::realtime_conversation::RealtimeConversationManager;
use crate::session::step_context::StepContext;
use crate::session::step_context::StepContextSeed;
use crate::session::turn_context::TurnEnvironment;
use crate::session_prefix::format_inter_agent_completion_message;
use crate::skills::SkillRenderSideEffects;
@@ -1729,7 +1730,7 @@ impl Session {
async fn build_settings_update_items(
&self,
reference_context_item: Option<&TurnContextItem>,
current_context: &TurnContext,
current_context: &StepContext,
) -> Vec<ResponseItem> {
// TODO: Make context updates a pure diff of persisted previous/current TurnContextItem
// state so replay/backtracking is deterministic. Runtime inputs that affect model-visible
@@ -2267,12 +2268,13 @@ impl Session {
)]
pub(crate) async fn request_permissions_for_environment(
self: &Arc<Self>,
turn_context: &Arc<TurnContext>,
step_context_seed: &StepContextSeed,
call_id: String,
args: RequestPermissionsArgs,
environment: TurnEnvironmentSelection,
cancellation_token: CancellationToken,
) -> Option<RequestPermissionsResponse> {
let turn_context = &step_context_seed.turn;
match turn_context.as_ref().approval_policy.value() {
AskForApproval::Never => {
return Some(RequestPermissionsResponse {
@@ -2316,7 +2318,6 @@ impl Session {
};
let review_id = crate::guardian::new_guardian_review_id();
let session = Arc::clone(self);
let turn = Arc::clone(turn_context);
let request = crate::guardian::GuardianApprovalRequest::RequestPermissions {
id: call_id,
turn_id: turn_context.sub_id.clone(),
@@ -2325,7 +2326,7 @@ impl Session {
};
let review_rx = crate::guardian::spawn_approval_request_review(
session,
turn,
step_context_seed.clone(),
review_id,
request,
/*retry_reason*/ None,
@@ -2435,12 +2436,13 @@ impl Session {
pub(crate) async fn request_permissions_for_cwd(
self: &Arc<Self>,
turn_context: &Arc<TurnContext>,
step_context_seed: &StepContextSeed,
call_id: String,
args: RequestPermissionsArgs,
cwd: AbsolutePathBuf,
cancellation_token: CancellationToken,
) -> Option<RequestPermissionsResponse> {
let turn_context = &step_context_seed.turn;
let turn_environment = match args.environment_id.as_deref() {
Some(environment_id) => turn_context
.environments
@@ -2459,7 +2461,7 @@ impl Session {
let mut environment = turn_environment.selection();
environment.cwd = PathUri::from_abs_path(&cwd);
self.request_permissions_for_environment(
turn_context,
step_context_seed,
call_id,
args,
environment,
@@ -3165,27 +3167,27 @@ impl Session {
pub(crate) async fn build_initial_context_with_world_state(
&self,
turn_context: &TurnContext,
step_context: &StepContext,
world_state: &WorldState,
) -> Vec<ResponseItem> {
let mcp = self.services.latest_mcp_runtime();
self.build_initial_context_with_world_state_and_mcp(turn_context, world_state, &mcp)
self.build_initial_context_with_world_state_and_mcp(step_context, world_state, &mcp)
.await
}
async fn build_initial_context_with_world_state_and_mcp(
&self,
turn_context: &TurnContext,
step_context: &StepContext,
world_state: &WorldState,
mcp: &McpRuntimeSnapshot,
) -> Vec<ResponseItem> {
let turn_context = step_context.turn.as_ref();
let mut developer_sections = Vec::<String>::with_capacity(8);
let mut contextual_user_sections = Vec::<String>::with_capacity(2);
let mut separate_developer_sections = Vec::<String>::new();
let (
reference_context_item,
previous_turn_settings,
collaboration_mode,
base_instructions,
session_source,
auto_compact_window_ids,
@@ -3194,12 +3196,12 @@ impl Session {
(
state.reference_context_item(),
state.previous_turn_settings(),
state.session_configuration.collaboration_mode.clone(),
state.session_configuration.base_instructions.clone(),
state.session_configuration.session_source.clone(),
state.auto_compact_window_ids(),
)
};
let collaboration_mode = step_context.collaboration_mode();
if let Some(model_switch_message) =
crate::context_manager::updates::build_model_instructions_update_item(
previous_turn_settings.as_ref(),
@@ -3449,7 +3451,7 @@ impl Session {
{
items.push(usage_hint_message);
}
if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(turn_context) {
if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(step_context) {
items.push(ContextualUserFragment::into(
MultiAgentModeInstructions::new(multi_agent_mode),
));
@@ -3516,18 +3518,19 @@ impl Session {
pub(crate) async fn start_new_context_window(
&self,
turn_context: &TurnContext,
step_context: &StepContext,
world_state: Arc<WorldState>,
) -> u64 {
let turn_context = step_context.turn.as_ref();
let window = {
let mut state = self.state.lock().await;
state.start_new_context_window()
};
let (window_number, window_ids) = window;
let context_items = self
.build_initial_context_with_world_state(turn_context, world_state.as_ref())
.build_initial_context_with_world_state(step_context, world_state.as_ref())
.await;
let turn_context_item = turn_context.to_turn_context_item();
let turn_context_item = step_context.to_turn_context_item();
self.replace_compacted_history(
turn_context,
context_items,
@@ -3575,7 +3578,7 @@ impl Session {
let state = self.state.lock().await;
state.reference_context_item()
};
let turn_context_item = turn_context.to_turn_context_item();
let turn_context_item = step_context.to_turn_context_item();
let turn_context_changed = reference_context_item.as_ref() != Some(&turn_context_item);
let should_inject_full_context = reference_context_item.is_none();
let world_state = Arc::new(self.build_world_state_for_step(step_context).await);
@@ -3583,7 +3586,7 @@ impl Session {
let (mut context_items, world_state_item) = if should_inject_full_context {
let context_items = self
.build_initial_context_with_world_state_and_mcp(
turn_context,
step_context,
world_state.as_ref(),
step_context.mcp.as_ref(),
)
@@ -3602,7 +3605,7 @@ impl Session {
// Steady-state path: append only built-in context diffs here; turn-scoped extension
// context is added below.
let mut context_items = self
.build_settings_update_items(reference_context_item.as_ref(), turn_context)
.build_settings_update_items(reference_context_item.as_ref(), step_context)
.await;
let (world_state_items, world_state_item) = {
let mut state = self.state.lock().await;
+4 -2
View File
@@ -1,4 +1,5 @@
use crate::config::MultiAgentV2Config;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use codex_protocol::config_types::MultiAgentMode;
use codex_protocol::openai_models::ReasoningEffort;
@@ -36,7 +37,8 @@ fn configured_usage_hint_text_for_source<'a>(
}
}
pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option<MultiAgentMode> {
pub(crate) fn effective_multi_agent_mode(step_context: &StepContext) -> Option<MultiAgentMode> {
let turn_context = step_context.turn.as_ref();
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
return None;
}
@@ -49,7 +51,7 @@ pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option<M
.multi_agent_mode_hint_text
{
Some(hint_text) => MultiAgentMode::Custom(hint_text.clone()),
None => match turn_context.effective_reasoning_effort() {
None => match step_context.effective_reasoning_effort() {
Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive,
_ => MultiAgentMode::ExplicitRequestOnly,
},
@@ -2,6 +2,7 @@ use super::*;
use super::tests::build_world_state_from_turn_context;
use super::tests::make_session_and_context;
use super::tests::turn_context_item_for_test;
use codex_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
@@ -138,7 +139,7 @@ async fn record_initial_history_restores_world_state_baseline() {
let turn_context = Arc::new(turn_context);
let world_state = build_world_state_from_turn_context(&session, &turn_context).await;
let rollout_items = completed_user_turn_rollout(
turn_context.to_turn_context_item(),
turn_context_item_for_test(&turn_context),
vec![RolloutItem::WorldState(WorldStateItem::full(
world_state.snapshot().into_value(),
))],
@@ -291,7 +292,8 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
#[tokio::test]
async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_completed_turns() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let first_context_item = turn_context_item_for_test(&turn_context);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -414,7 +416,8 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
#[tokio::test]
async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_incomplete_turn() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let first_context_item = turn_context_item_for_test(&turn_context);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -508,7 +511,8 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc
#[tokio::test]
async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metadata() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let first_context_item = turn_context_item_for_test(&turn_context);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -634,7 +638,8 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad
#[tokio::test]
async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let first_context_item = turn_context_item_for_test(&turn_context);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -735,7 +740,8 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() {
#[tokio::test]
async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding_user_turns() {
let (session, turn_context) = make_session_and_context().await;
let only_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let only_context_item = turn_context_item_for_test(&turn_context);
let only_turn_id = only_context_item
.turn_id
.clone()
@@ -789,7 +795,8 @@ async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding
#[tokio::test]
async fn record_initial_history_resumed_rollback_skips_only_user_turns() {
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&turn_context);
let user_turn_id = previous_context_item
.turn_id
.clone()
@@ -864,7 +871,8 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() {
#[tokio::test]
async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_compaction_metadata() {
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&turn_context);
let previous_turn_id = previous_context_item
.turn_id
.clone()
@@ -960,7 +968,8 @@ async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_comp
#[tokio::test]
async fn record_initial_history_resumed_bare_turn_context_does_not_seed_reference_context_item() {
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&turn_context);
let rollout_items = vec![RolloutItem::TurnContext(previous_context_item.clone())];
session
@@ -977,7 +986,8 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_seed_referenc
#[tokio::test]
async fn record_initial_history_resumed_does_not_seed_reference_context_item_after_compaction() {
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&turn_context);
let rollout_items = vec![
RolloutItem::TurnContext(previous_context_item),
RolloutItem::Compacted(CompactedItem {
@@ -1078,8 +1088,9 @@ async fn reconstruct_history_prefers_compacted_window_over_session_meta() {
#[tokio::test]
async fn reconstruct_history_replays_world_state_from_latest_compaction_window() {
let (session, turn_context) = make_session_and_context().await;
let turn_context = Arc::new(turn_context);
let rollout_items = completed_user_turn_rollout(
turn_context.to_turn_context_item(),
turn_context_item_for_test(&turn_context),
vec![
RolloutItem::WorldState(WorldStateItem::full(json!({
"environment": {"status": "old"}
@@ -1186,7 +1197,8 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_does_
async fn reconstruct_history_legacy_compaction_without_replacement_history_clears_later_reference_context_item()
{
let (session, turn_context) = make_session_and_context().await;
let current_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let current_context_item = turn_context_item_for_test(&turn_context);
let current_turn_id = current_context_item
.turn_id
.clone()
@@ -1479,7 +1491,8 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_for_later_turn_context()
{
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&turn_context);
let previous_turn_id = previous_context_item
.turn_id
.clone()
@@ -1717,7 +1730,8 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
#[tokio::test]
async fn record_initial_history_resumed_trailing_incomplete_turn_preserves_turn_context_item() {
let (session, turn_context) = make_session_and_context().await;
let current_context_item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let current_context_item = turn_context_item_for_test(&turn_context);
let current_turn_id = current_context_item
.turn_id
.clone()
+64
View File
@@ -6,7 +6,10 @@ use crate::session::McpRuntimeSnapshot;
use crate::session::turn_context::TurnContext;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
use codex_mcp::ToolInfo;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::protocol::TurnContextItem;
use tokio::sync::OnceCell;
/// Request-scoped state that may change between model sampling requests.
@@ -66,4 +69,65 @@ impl StepContext {
.map(|effort| effort.to_string())
.unwrap_or_else(|| "default".to_string())
}
pub(crate) fn collaboration_mode(&self) -> CollaborationMode {
self.turn.collaboration_mode.with_updates(
/*model*/ None,
Some(self.reasoning_effort.clone()),
/*developer_instructions*/ None,
)
}
pub(crate) fn to_turn_context_item(&self) -> TurnContextItem {
let workspace_roots = self.turn.config.effective_workspace_roots();
#[allow(deprecated)]
let cwd = self.turn.cwd.clone();
TurnContextItem {
turn_id: Some(self.turn.sub_id.clone()),
cwd,
workspace_roots: (!workspace_roots.is_empty()).then_some(workspace_roots),
current_date: self.turn.current_date.clone(),
timezone: self.turn.timezone.clone(),
approval_policy: self.turn.approval_policy.value(),
sandbox_policy: self.turn.sandbox_policy(),
permission_profile: Some(self.turn.permission_profile()),
network: self.turn.turn_context_network_item(),
file_system_sandbox_policy: self.turn.non_legacy_file_system_sandbox_policy(),
model: self.turn.model_info.slug.clone(),
comp_hash: self.turn.model_info.comp_hash.clone(),
personality: self.turn.personality,
collaboration_mode: Some(self.collaboration_mode()),
multi_agent_version: Some(self.turn.multi_agent_version),
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(self),
realtime_active: Some(self.turn.realtime_active),
effort: self.reasoning_effort.clone(),
summary: ReasoningSummaryConfig::Auto,
}
}
}
/// Frozen step inputs needed by downstream work that can outlive a sampling request.
#[derive(Clone, Debug)]
pub(crate) struct StepContextSeed {
pub(crate) turn: Arc<TurnContext>,
pub(crate) reasoning_effort: Option<ReasoningEffortConfig>,
}
impl StepContextSeed {
pub(crate) fn from_turn(turn: Arc<TurnContext>) -> Self {
let reasoning_effort = turn.config.model_reasoning_effort.clone();
Self {
turn,
reasoning_effort,
}
}
}
impl From<&StepContext> for StepContextSeed {
fn from(step_context: &StepContext) -> Self {
Self {
turn: Arc::clone(&step_context.turn),
reasoning_effort: step_context.reasoning_effort.clone(),
}
}
}
+65 -28
View File
@@ -205,6 +205,10 @@ impl StepContext {
}
}
pub(crate) fn turn_context_item_for_test(turn: &Arc<TurnContext>) -> TurnContextItem {
StepContext::for_test(Arc::clone(turn)).to_turn_context_item()
}
mod guardian_tests;
struct InstructionsTestCase {
@@ -1134,6 +1138,7 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an
let tool_ctx = crate::tools::sandboxing::ToolCtx {
session: Arc::clone(&session),
turn: Arc::clone(&turn),
step_context: StepContext::for_test(Arc::clone(&turn)),
call_id: "probe-call".to_string(),
tool_name: codex_tools::ToolName::plain("probe"),
};
@@ -2790,9 +2795,10 @@ async fn start_new_context_window_assigns_and_persists_item_ids() {
attach_thread_persistence(Arc::get_mut(&mut session).expect("unique session")).await;
let world_state =
Arc::new(build_world_state_from_turn_context(session.as_ref(), &turn_context).await);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
session
.start_new_context_window(turn_context.as_ref(), world_state)
.start_new_context_window(step_context.as_ref(), world_state)
.await;
let live_history = session.clone_history().await;
@@ -3176,7 +3182,7 @@ async fn thread_rollback_drops_last_turn_from_history() {
full_history.extend(initial_context.clone());
full_history.extend(turn_1.clone());
full_history.extend(turn_2);
sess.replace_history(full_history.clone(), Some(tc.to_turn_context_item()))
sess.replace_history(full_history.clone(), Some(turn_context_item_for_test(&tc)))
.await;
let rollout_items: Vec<RolloutItem> = full_history
.into_iter()
@@ -3191,7 +3197,7 @@ async fn thread_rollback_drops_last_turn_from_history() {
.await;
{
let mut state = sess.state.lock().await;
state.set_reference_context_item(Some(tc.to_turn_context_item()));
state.set_reference_context_item(Some(turn_context_item_for_test(&tc)));
}
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
@@ -3236,7 +3242,7 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns()
let mut full_history = Vec::new();
full_history.extend(initial_context.clone());
full_history.extend(turn_1);
sess.replace_history(full_history.clone(), Some(tc.to_turn_context_item()))
sess.replace_history(full_history.clone(), Some(turn_context_item_for_test(&tc)))
.await;
let rollout_items: Vec<RolloutItem> = full_history
.into_iter()
@@ -3283,7 +3289,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
)
.await;
let first_context_item = tc.to_turn_context_item();
let first_context_item = turn_context_item_for_test(&tc);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -3405,7 +3411,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
)
.await;
let first_context_item = tc.to_turn_context_item();
let first_context_item = turn_context_item_for_test(&tc);
let first_turn_id = first_context_item
.turn_id
.clone()
@@ -3547,7 +3553,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
Arc::get_mut(&mut sess).expect("session should not have additional references"),
)
.await;
let turn_context_item = tc.to_turn_context_item();
let turn_context_item = turn_context_item_for_test(&tc);
sess.persist_rollout_items(&[
RolloutItem::EventMsg(EventMsg::TurnStarted(
@@ -5320,8 +5326,9 @@ async fn build_initial_context(
turn_context: &Arc<TurnContext>,
) -> Vec<ResponseItem> {
let world_state = build_world_state_from_turn_context(session, turn_context).await;
let step_context = StepContext::for_test(Arc::clone(turn_context));
session
.build_initial_context_with_world_state(turn_context.as_ref(), &world_state)
.build_initial_context_with_world_state(step_context.as_ref(), &world_state)
.await
}
@@ -6118,7 +6125,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
.selection();
session
.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
call_id,
codex_protocol::request_permissions::RequestPermissionsArgs {
environment_id: None,
@@ -6357,7 +6364,7 @@ async fn request_permissions_response_materializes_session_cwd_grants_before_rec
.selection();
session
.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
call_id,
codex_protocol::request_permissions::RequestPermissionsArgs {
environment_id: None,
@@ -6448,7 +6455,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
.selection();
let response = session
.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
call_id,
codex_protocol::request_permissions::RequestPermissionsArgs {
environment_id: None,
@@ -8147,10 +8154,11 @@ async fn build_settings_update_items_emits_realtime_start_when_session_becomes_l
)
.await;
current_context.realtime_active = true;
let current_context = StepContext::for_test(Arc::new(current_context));
let update_items = session
.build_settings_update_items(
Some(&previous_context.to_turn_context_item()),
Some(&turn_context_item_for_test(&previous_context)),
&current_context,
)
.await;
@@ -8168,6 +8176,7 @@ async fn build_settings_update_items_emits_realtime_start_when_session_becomes_l
async fn build_settings_update_items_emits_realtime_end_when_session_stops_being_live() {
let (session, mut previous_context) = make_session_and_context().await;
previous_context.realtime_active = true;
let previous_context = Arc::new(previous_context);
let mut current_context = previous_context
.with_model(
previous_context.model_info.slug.clone(),
@@ -8175,10 +8184,11 @@ async fn build_settings_update_items_emits_realtime_end_when_session_stops_being
)
.await;
current_context.realtime_active = false;
let current_context = StepContext::for_test(Arc::new(current_context));
let update_items = session
.build_settings_update_items(
Some(&previous_context.to_turn_context_item()),
Some(&turn_context_item_for_test(&previous_context)),
&current_context,
)
.await;
@@ -8195,7 +8205,8 @@ async fn build_settings_update_items_emits_realtime_end_when_session_stops_being
#[tokio::test]
async fn build_settings_update_items_uses_previous_turn_settings_for_realtime_end() {
let (session, previous_context) = make_session_and_context().await;
let mut previous_context_item = previous_context.to_turn_context_item();
let previous_context = Arc::new(previous_context);
let mut previous_context_item = turn_context_item_for_test(&previous_context);
previous_context_item.realtime_active = None;
let previous_turn_settings = PreviousTurnSettings {
model: previous_context.model_info.slug.clone(),
@@ -8209,6 +8220,7 @@ async fn build_settings_update_items_uses_previous_turn_settings_for_realtime_en
)
.await;
current_context.realtime_active = false;
let current_context = StepContext::for_test(Arc::new(current_context));
session
.set_previous_turn_settings(Some(previous_turn_settings))
@@ -8241,7 +8253,7 @@ async fn build_initial_context_uses_previous_realtime_state() {
"expected initial context to describe active realtime state, got {developer_texts:?}"
);
let previous_context_item = turn_context.to_turn_context_item();
let previous_context_item = turn_context_item_for_test(&turn_context);
{
let mut state = session.state.lock().await;
state.set_reference_context_item(Some(previous_context_item));
@@ -8398,9 +8410,9 @@ async fn record_context_updates_includes_turn_context_fragments_on_steady_state_
.insert(TurnContextExtensionTestState {
expected_model_context_window: Some(50),
});
let mut previous_context_item = turn_context.to_turn_context_item();
previous_context_item.turn_id = Some("previous-turn-id".to_string());
let turn_context = Arc::new(turn_context);
let mut previous_context_item = turn_context_item_for_test(&turn_context);
previous_context_item.turn_id = Some("previous-turn-id".to_string());
let world_state = build_world_state_from_turn_context(&session, &turn_context).await;
{
let mut state = session.state.lock().await;
@@ -8974,14 +8986,16 @@ async fn turn_context_item_stores_local_cwd() {
#[allow(deprecated)]
let local_cwd = turn_context.cwd.clone();
assert_eq!(turn_context.to_turn_context_item().cwd, local_cwd);
let turn_context = Arc::new(turn_context);
assert_eq!(turn_context_item_for_test(&turn_context).cwd, local_cwd);
}
#[tokio::test]
async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() {
let (_session, turn_context) = make_session_and_context().await;
let turn_context = Arc::new(turn_context);
let item = turn_context.to_turn_context_item();
let item = turn_context_item_for_test(&turn_context);
assert_eq!(item.file_system_sandbox_policy, None);
assert_eq!(
@@ -8990,6 +9004,27 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy()
);
}
#[tokio::test]
async fn turn_context_item_uses_step_reasoning_effort() {
let (_session, mut turn_context) = make_session_and_context().await;
Arc::make_mut(&mut turn_context.config).model_reasoning_effort =
Some(ReasoningEffortConfig::High);
let turn_context = Arc::new(turn_context);
let mut step_context = StepContext::for_test(Arc::clone(&turn_context));
Arc::get_mut(&mut step_context)
.expect("step context should be uniquely owned")
.reasoning_effort = Some(ReasoningEffortConfig::Low);
let item = step_context.to_turn_context_item();
assert_eq!(item.effort, Some(ReasoningEffortConfig::Low));
assert_eq!(
item.collaboration_mode
.expect("turn context should have collaboration mode")
.reasoning_effort(),
Some(ReasoningEffortConfig::Low)
);
}
#[tokio::test]
async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() {
let (_session, mut turn_context) = make_session_and_context().await;
@@ -9000,7 +9035,8 @@ async fn turn_context_item_stores_split_file_system_sandbox_policy_when_differen
turn_context.network_sandbox_policy(),
);
let item = turn_context.to_turn_context_item();
let turn_context = Arc::new(turn_context);
let item = turn_context_item_for_test(&turn_context);
assert_eq!(
item.file_system_sandbox_policy,
@@ -9028,7 +9064,7 @@ async fn record_context_updates_and_set_reference_context_item_injects_full_cont
let current_context = session.reference_context_item().await;
assert_eq!(
serde_json::to_value(current_context).expect("serialize current context item"),
serde_json::to_value(Some(turn_context.to_turn_context_item()))
serde_json::to_value(Some(step_context.to_turn_context_item()))
.expect("serialize expected context item")
);
}
@@ -9080,8 +9116,8 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
async fn record_context_updates_and_set_reference_context_item_persists_baseline_without_emitting_diffs()
{
let (mut session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let previous_context = Arc::new(turn_context);
let previous_context_item = turn_context_item_for_test(&previous_context);
let world_state = build_world_state_from_turn_context(&session, &previous_context).await;
let mut turn_context = Arc::try_unwrap(previous_context)
.unwrap_or_else(|_| panic!("previous turn context should have no remaining references"));
@@ -9095,13 +9131,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
}
let rollout_path = attach_thread_persistence(&mut session).await;
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let update_items = session
.build_settings_update_items(Some(&previous_context_item), &turn_context)
.build_settings_update_items(Some(&previous_context_item), &step_context)
.await;
assert_eq!(update_items, Vec::new());
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
session
.record_context_updates_and_set_reference_context_item(&step_context)
.await;
@@ -9113,7 +9150,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
assert_eq!(
serde_json::to_value(session.reference_context_item().await)
.expect("serialize current context item"),
serde_json::to_value(Some(turn_context.to_turn_context_item()))
serde_json::to_value(Some(step_context.to_turn_context_item()))
.expect("serialize expected context item")
);
session.ensure_rollout_materialized().await;
@@ -9132,7 +9169,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
assert_eq!(
serde_json::to_value(persisted_turn_context)
.expect("serialize persisted turn context item"),
serde_json::to_value(Some(turn_context.to_turn_context_item()))
serde_json::to_value(Some(step_context.to_turn_context_item()))
.expect("serialize expected turn context item")
);
}
@@ -9258,7 +9295,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
assert_eq!(
serde_json::to_value(persisted_turn_context)
.expect("serialize persisted turn context item"),
serde_json::to_value(Some(turn_context.to_turn_context_item()))
serde_json::to_value(Some(step_context.to_turn_context_item()))
.expect("serialize expected turn context item")
);
}
@@ -4,6 +4,7 @@ use crate::exec_policy::ExecPolicyManager;
use crate::guardian::GUARDIAN_REVIEWER_NAME;
use crate::sandboxing::SandboxPermissions;
use crate::session::step_context::StepContext;
use crate::session::step_context::StepContextSeed;
use crate::test_support::models_manager_with_provider;
use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolOutput;
@@ -132,7 +133,7 @@ async fn request_permissions_routes_to_guardian_when_reviewer_is_enabled() {
let response = tokio::time::timeout(
Duration::from_secs(45),
session.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
"perm-call-1".to_string(),
RequestPermissionsArgs {
environment_id: None,
@@ -227,7 +228,7 @@ async fn request_permissions_guardian_review_stops_when_cancelled() {
.selection();
session
.request_permissions_for_environment(
&turn_context,
&StepContextSeed::from_turn(Arc::clone(&turn_context)),
"perm-call-cancelled".to_string(),
RequestPermissionsArgs {
environment_id: None,
@@ -531,12 +532,13 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
turn_context.session_source = guardian_source;
turn_context.developer_instructions = Some(guardian_policy.clone());
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let world_state = Arc::new(build_world_state_from_turn_context(&session, &turn_context).await);
let initial_context_injection = InitialContextInjection::BeforeLastUserMessage(world_state);
let (refreshed, _) = crate::compact_remote::process_compacted_history(
&session,
&turn_context,
&step_context,
vec![
ResponseItem::Message {
id: None,
+5 -4
View File
@@ -204,7 +204,7 @@ pub(crate) async fn run_turn(
.await;
}
track_turn_resolved_config_analytics(&sess, &turn_context, &input).await;
track_turn_resolved_config_analytics(&sess, first_step_context.as_ref(), &input).await;
let mut last_agent_message: Option<String> = None;
let mut stop_hook_active = false;
@@ -742,9 +742,10 @@ async fn build_extension_turn_input_items(
)]
async fn track_turn_resolved_config_analytics(
sess: &Session,
turn_context: &TurnContext,
step_context: &StepContext,
input: &[TurnInput],
) {
let turn_context = step_context.turn.as_ref();
let thread_config = {
let state = sess.state.lock().await;
state.session_configuration.thread_config_snapshot()
@@ -777,7 +778,7 @@ async fn track_turn_resolved_config_analytics(
permission_profile: turn_context.permission_profile(),
#[allow(deprecated)]
permission_profile_cwd: turn_context.cwd.to_path_buf(),
reasoning_effort: turn_context.config.model_reasoning_effort.clone(),
reasoning_effort: step_context.reasoning_effort.clone(),
reasoning_summary: Some(turn_context.reasoning_summary),
service_tier: turn_context
.config
@@ -1935,7 +1936,7 @@ async fn try_run_sampling_request(
prompt: &Prompt,
cancellation_token: CancellationToken,
) -> CodexResult<SamplingRequestResult> {
let turn_context = &step_context.turn;
let turn_context = Arc::clone(&step_context.turn);
feedback_tags!(
model = turn_context.model_info.slug.clone(),
approval_policy = turn_context.approval_policy.value(),
+7 -36
View File
@@ -168,19 +168,17 @@ impl TurnContext {
)
}
pub(crate) fn effective_reasoning_effort(&self) -> Option<ReasoningEffortConfig> {
if self.model_info.supports_reasoning_summaries {
/// Resolves effort for the turn span, which is created before the first step is captured.
pub(crate) fn turn_start_reasoning_effort_for_tracing(&self) -> String {
let reasoning_effort = if self.model_info.supports_reasoning_summaries {
self.config
.model_reasoning_effort
.clone()
.or_else(|| self.model_info.default_reasoning_level.clone())
} else {
None
}
}
pub(crate) fn effective_reasoning_effort_for_tracing(&self) -> String {
self.effective_reasoning_effort()
};
reasoning_effort
.map(|effort| effort.to_string())
.unwrap_or_else(|| "default".to_string())
}
@@ -333,7 +331,7 @@ impl TurnContext {
}
}
fn non_legacy_file_system_sandbox_policy(&self) -> Option<FileSystemSandboxPolicy> {
pub(super) fn non_legacy_file_system_sandbox_policy(&self) -> Option<FileSystemSandboxPolicy> {
// Omit the derived split filesystem policy when it is equivalent to
// the legacy sandbox policy. This keeps turn-context payloads stable
// while both fields exist; once callers consume only the split policy,
@@ -349,34 +347,7 @@ impl TurnContext {
.then_some(file_system_sandbox_policy)
}
pub(crate) fn to_turn_context_item(&self) -> TurnContextItem {
let workspace_roots = self.config.effective_workspace_roots();
#[allow(deprecated)]
let cwd = self.cwd.clone();
TurnContextItem {
turn_id: Some(self.sub_id.clone()),
cwd,
workspace_roots: (!workspace_roots.is_empty()).then_some(workspace_roots),
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
sandbox_policy: self.sandbox_policy(),
permission_profile: Some(self.permission_profile()),
network: self.turn_context_network_item(),
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
model: self.model_info.slug.clone(),
comp_hash: self.model_info.comp_hash.clone(),
personality: self.personality,
collaboration_mode: Some(self.collaboration_mode.clone()),
multi_agent_version: Some(self.multi_agent_version),
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(self),
realtime_active: Some(self.realtime_active),
effort: self.config.model_reasoning_effort.clone(),
summary: ReasoningSummaryConfig::Auto,
}
}
fn turn_context_network_item(&self) -> Option<TurnContextNetworkItem> {
pub(super) fn turn_context_network_item(&self) -> Option<TurnContextNetworkItem> {
let network = self
.config
.config_layer_stack
+1 -1
View File
@@ -383,7 +383,7 @@ impl Session {
let task_cancellation_token = cancellation_token.child_token();
// Task-owned turn spans keep a core-owned span open for the
// full task lifecycle after the submission dispatch span ends.
let reasoning_effort = turn_context.effective_reasoning_effort_for_tracing();
let reasoning_effort = turn_context.turn_start_reasoning_effort_for_tracing();
let task_span = info_span!(
"turn",
otel.name = span_name,
+3 -1
View File
@@ -4,6 +4,7 @@ use crate::init_state_db;
use crate::installation_id::INSTALLATION_ID_FILENAME;
use crate::rollout::RolloutRecorder;
use crate::session::session::SessionSettingsUpdate;
use crate::session::step_context::StepContext;
use crate::session::tests::build_world_state_from_turn_context;
use crate::session::tests::make_session_and_context;
use crate::tasks::InterruptedTurnHistoryMarker;
@@ -319,9 +320,10 @@ fn out_of_range_truncation_drops_pre_user_active_turn_prefix() {
async fn ignores_session_prefix_messages_when_truncating() {
let (session, turn_context) = make_session_and_context().await;
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let world_state = build_world_state_from_turn_context(&session, &turn_context).await;
let mut items = session
.build_initial_context_with_world_state(&turn_context, &world_state)
.build_initial_context_with_world_state(&step_context, &world_state)
.await;
items.push(user_msg("feature request"));
items.push(assistant_msg("ack"));
@@ -1,4 +1,5 @@
use super::*;
use crate::session::step_context::StepContext;
use crate::session::tests::build_world_state_from_turn_context;
use crate::session::tests::make_session_and_context;
use codex_protocol::AgentPath;
@@ -270,9 +271,10 @@ fn truncates_rollout_from_start_applies_thread_rollback_markers() {
async fn ignores_session_prefix_messages_when_truncating_rollout_from_start() {
let (session, turn_context) = make_session_and_context().await;
let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context));
let world_state = build_world_state_from_turn_context(&session, &turn_context).await;
let mut items = session
.build_initial_context_with_world_state(&turn_context, &world_state)
.build_initial_context_with_world_state(&step_context, &world_state)
.await;
items.push(user_msg("feature request"));
items.push(assistant_msg("ack"));
@@ -3,6 +3,7 @@ use crate::agent::status::is_final;
use crate::config::Config;
use crate::function_tool::FunctionCallError;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::tools::handlers::multi_agents::build_agent_spawn_config;
use crate::tools::handlers::parse_arguments;
@@ -107,9 +108,10 @@ fn required_state_db(
async fn build_runner_options(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
step_context: &StepContext,
requested_concurrency: Option<usize>,
) -> Result<JobRunnerOptions, FunctionCallError> {
let turn = &step_context.turn;
let multi_agent_version = turn.multi_agent_version;
if multi_agent_version == MultiAgentVersion::Disabled {
return Err(FunctionCallError::RespondToModel(
@@ -124,7 +126,7 @@ async fn build_runner_options(
}
let max_concurrency = normalize_concurrency(requested_concurrency, agent_max_threads);
let base_instructions = session.get_base_instructions().await;
let spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref())?;
let spawn_config = build_agent_spawn_config(&base_instructions, step_context)?;
Ok(JobRunnerOptions {
max_concurrency,
spawn_config,
@@ -35,7 +35,7 @@ impl SpawnAgentsOnCsvHandler {
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation {
session,
turn,
step_context,
payload,
..
} = invocation;
@@ -49,7 +49,7 @@ impl SpawnAgentsOnCsvHandler {
}
};
handle(session, turn, arguments)
handle(session, step_context, arguments)
.await
.map(boxed_tool_output)
}
@@ -68,9 +68,10 @@ impl CoreToolRuntime for SpawnAgentsOnCsvHandler {
/// `report_agent_job_result`, then exported to CSV on completion.
pub async fn handle(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context: Arc<StepContext>,
arguments: String,
) -> Result<FunctionToolOutput, FunctionCallError> {
let turn = Arc::clone(&step_context.turn);
let args: SpawnAgentsOnCsvArgs = parse_arguments(arguments.as_str())?;
if args.instruction.trim().is_empty() {
return Err(FunctionCallError::RespondToModel(
@@ -182,16 +183,17 @@ pub async fn handle(
})?;
let requested_concurrency = args.max_concurrency.or(args.max_workers);
let options = match build_runner_options(&session, &turn, requested_concurrency).await {
Ok(options) => options,
Err(err) => {
let error_message = err.to_string();
let _ = db
.mark_agent_job_failed(job_id.as_str(), error_message.as_str())
.await;
return Err(err);
}
};
let options =
match build_runner_options(&session, step_context.as_ref(), requested_concurrency).await {
Ok(options) => options,
Err(err) => {
let error_message = err.to_string();
let _ = db
.mark_agent_job_failed(job_id.as_str(), error_message.as_str())
.await;
return Err(err);
}
};
db.mark_agent_job_running(job_id.as_str())
.await
.map_err(|err| {
@@ -11,6 +11,7 @@ use crate::apply_patch::InternalApplyPatchInvocation;
use crate::apply_patch::convert_apply_patch_to_protocol;
use crate::function_tool::FunctionCallError;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use crate::tools::context::ApplyPatchToolOutput;
@@ -445,6 +446,7 @@ impl ApplyPatchHandler {
let tool_ctx = ToolCtx {
session: session.clone(),
turn: turn.clone(),
step_context: step_context.clone(),
call_id: call_id.clone(),
tool_name: tool_name.clone(),
};
@@ -550,6 +552,7 @@ pub(crate) async fn intercept_apply_patch(
turn_environment: TurnEnvironment,
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context: Arc<StepContext>,
tracker: Option<&SharedTurnDiffTracker>,
call_id: &str,
tool_name: &str,
@@ -608,6 +611,7 @@ pub(crate) async fn intercept_apply_patch(
let tool_ctx = ToolCtx {
session: session.clone(),
turn: turn.clone(),
step_context,
call_id: call_id.to_string(),
tool_name: ToolName::plain(tool_name),
};
@@ -1,7 +1,7 @@
use super::*;
use crate::agent::next_thread_spawn_depth;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::session::step_context::StepContext;
use crate::tools::handlers::multi_agents_spec::create_resume_agent_tool;
use codex_tools::ToolSpec;
use std::sync::Arc;
@@ -35,6 +35,7 @@ async fn handle_resume_agent(
let ToolInvocation {
session,
turn,
step_context,
payload,
call_id,
..
@@ -87,7 +88,7 @@ async fn handle_resume_agent(
let (receiver_agent, error) = if matches!(status, AgentStatus::NotFound) {
match Box::pin(try_resume_closed_agent(
&session,
&turn,
step_context.as_ref(),
receiver_thread_id,
child_depth,
))
@@ -187,11 +188,12 @@ impl ToolOutput for ResumeAgentResult {
async fn try_resume_closed_agent(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
step_context: &StepContext,
receiver_thread_id: ThreadId,
child_depth: i32,
) -> Result<(), FunctionCallError> {
let config = build_agent_resume_config(turn.as_ref())?;
let turn = step_context.turn.as_ref();
let config = build_agent_resume_config(step_context)?;
Box::pin(session.services.agent_control.resume_agent_from_rollout(
config,
receiver_thread_id,
@@ -34,6 +34,7 @@ impl Handler {
let ToolInvocation {
session,
turn,
step_context,
payload,
call_id,
..
@@ -48,7 +49,7 @@ impl Handler {
.agent_control
.get_agent_metadata(receiver_thread_id);
if receiver_agent.is_some() {
let resume_config = build_agent_resume_config(turn.as_ref())?;
let resume_config = build_agent_resume_config(step_context.as_ref())?;
session
.services
.agent_control
@@ -48,6 +48,7 @@ async fn handle_spawn_agent(
let ToolInvocation {
session,
turn,
step_context,
payload,
call_id,
..
@@ -86,8 +87,10 @@ async fn handle_spawn_agent(
}),
)
.await;
let mut config =
build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
let mut config = build_agent_spawn_config(
&session.get_base_instructions().await,
step_context.as_ref(),
)?;
if let Some(service_tier) = args.service_tier.as_ref() {
config.service_tier = Some(service_tier.clone());
}
@@ -3,6 +3,7 @@ use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::config::HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS;
use crate::function_tool::FunctionCallError;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolOutput;
@@ -154,34 +155,36 @@ pub(crate) fn parse_collab_input(
/// Builds the base config snapshot for a newly spawned sub-agent.
///
/// The returned config starts from the parent's effective config and then refreshes the
/// runtime-owned fields carried on `turn`, including model selection, reasoning settings,
/// runtime-owned fields carried on the turn and step contexts, including model selection, reasoning settings,
/// approval policy, sandbox, and cwd. Role-specific overrides are layered after this step;
/// skipping this helper and cloning stale config state directly can send the child agent out with
/// the wrong provider or runtime policy.
pub(crate) fn build_agent_spawn_config(
base_instructions: &BaseInstructions,
turn: &TurnContext,
step_context: &StepContext,
) -> Result<Config, FunctionCallError> {
let mut config = build_agent_shared_config(turn)?;
let mut config = build_agent_shared_config(step_context)?;
config.base_instructions = Some(base_instructions.text.clone());
Ok(config)
}
pub(crate) fn build_agent_resume_config(turn: &TurnContext) -> Result<Config, FunctionCallError> {
let mut config = build_agent_shared_config(turn)?;
pub(crate) fn build_agent_resume_config(
step_context: &StepContext,
) -> Result<Config, FunctionCallError> {
let mut config = build_agent_shared_config(step_context)?;
// For resume, keep base instructions sourced from rollout/session metadata.
config.base_instructions = None;
Ok(config)
}
fn build_agent_shared_config(turn: &TurnContext) -> Result<Config, FunctionCallError> {
fn build_agent_shared_config(step_context: &StepContext) -> Result<Config, FunctionCallError> {
let turn = step_context.turn.as_ref();
let base_config = turn.config.clone();
let mut config = (*base_config).clone();
config.model = Some(turn.model_info.slug.clone());
config.model_provider = turn.provider.info().clone();
config.model_reasoning_effort = turn
.config
.model_reasoning_effort
config.model_reasoning_effort = step_context
.reasoning_effort
.clone()
.or_else(|| turn.model_info.default_reasoning_level.clone());
config.model_reasoning_summary = Some(turn.reasoning_summary);
@@ -4487,6 +4487,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
..ShellEnvironmentPolicy::default()
};
config.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo"));
config.model_reasoning_effort = Some(ReasoningEffort::High);
turn.config = Arc::new(config);
let temp_dir = tempfile::tempdir().expect("temp dir");
#[allow(deprecated)]
@@ -4513,12 +4514,18 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
.set(AskForApproval::OnRequest)
.expect("approval policy set");
let config = build_agent_spawn_config(&base_instructions, &turn).expect("spawn config");
let turn = Arc::new(turn);
let mut step_context = StepContext::for_test(Arc::clone(&turn));
Arc::get_mut(&mut step_context)
.expect("step context should be uniquely owned")
.reasoning_effort = Some(ReasoningEffort::Low);
let config = build_agent_spawn_config(&base_instructions, &step_context).expect("spawn config");
let mut expected = (*turn.config).clone();
expected.base_instructions = Some(base_instructions.text);
expected.model = Some(turn.model_info.slug.clone());
expected.model_provider = turn.provider.info().clone();
expected.model_reasoning_effort = turn.config.model_reasoning_effort.clone();
expected.model_reasoning_effort = Some(ReasoningEffort::Low);
expected.model_reasoning_summary = Some(turn.reasoning_summary);
expected.developer_instructions = turn.developer_instructions.clone();
#[allow(deprecated)]
@@ -4542,18 +4549,25 @@ async fn build_agent_resume_config_clears_base_instructions() {
let (_session, mut turn) = make_session_and_context().await;
let mut base_config = (*turn.config).clone();
base_config.base_instructions = Some("caller-base".to_string());
base_config.model_reasoning_effort = Some(ReasoningEffort::High);
turn.config = Arc::new(base_config);
turn.approval_policy
.set(AskForApproval::OnRequest)
.expect("approval policy set");
let config = build_agent_resume_config(&turn).expect("resume config");
let turn = Arc::new(turn);
let mut step_context = StepContext::for_test(Arc::clone(&turn));
Arc::get_mut(&mut step_context)
.expect("step context should be uniquely owned")
.reasoning_effort = Some(ReasoningEffort::Low);
let config = build_agent_resume_config(&step_context).expect("resume config");
let mut expected = (*turn.config).clone();
expected.base_instructions = None;
expected.model = Some(turn.model_info.slug.clone());
expected.model_provider = turn.provider.info().clone();
expected.model_reasoning_effort = turn.config.model_reasoning_effort.clone();
expected.model_reasoning_effort = Some(ReasoningEffort::Low);
expected.model_reasoning_summary = Some(turn.reasoning_summary);
expected.developer_instructions = turn.developer_instructions.clone();
#[allow(deprecated)]
@@ -67,6 +67,7 @@ pub(crate) async fn handle_message_string_tool(
let ToolInvocation {
session,
turn,
step_context,
call_id,
..
} = invocation;
@@ -89,7 +90,7 @@ pub(crate) async fn handle_message_string_tool(
let receiver_agent_path = receiver_agent.agent_path.clone().ok_or_else(|| {
FunctionCallError::RespondToModel("target agent is missing an agent_path".to_string())
})?;
let resume_config = build_agent_resume_config(turn.as_ref())?;
let resume_config = build_agent_resume_config(step_context.as_ref())?;
session
.services
.agent_control
@@ -43,6 +43,7 @@ async fn handle_spawn_agent(
let ToolInvocation {
session,
turn,
step_context,
payload,
call_id,
..
@@ -59,8 +60,10 @@ async fn handle_spawn_agent(
let message = message_content(args.message)?;
let session_source = turn.session_source.clone();
let child_depth = next_thread_spawn_depth(&session_source);
let mut config =
build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
let mut config = build_agent_spawn_config(
&session.get_base_instructions().await,
step_context.as_ref(),
)?;
if let Some(service_tier) = args.service_tier.as_ref() {
config.service_tier = Some(service_tier.clone());
}
@@ -2,6 +2,7 @@ use codex_protocol::request_permissions::RequestPermissionsArgs;
use codex_sandboxing::policy_transforms::normalize_additional_permissions;
use crate::function_tool::FunctionCallError;
use crate::session::step_context::StepContextSeed;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
@@ -46,7 +47,6 @@ impl RequestPermissionsHandler {
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation {
session,
turn,
step_context,
cancellation_token,
call_id,
@@ -94,7 +94,7 @@ impl RequestPermissionsHandler {
let response = session
.request_permissions_for_environment(
&turn,
&StepContextSeed::from(step_context.as_ref()),
call_id,
args,
turn_environment.selection(),
@@ -7,6 +7,7 @@ use tokio_util::sync::CancellationToken;
use crate::exec::ExecParams;
use crate::exec_policy::ExecApprovalRequest;
use crate::function_tool::FunctionCallError;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use crate::shell::ShellType;
@@ -54,6 +55,7 @@ struct RunExecLikeArgs {
prefix_rule: Option<Vec<String>>,
session: Arc<crate::session::session::Session>,
turn: Arc<TurnContext>,
step_context: Arc<StepContext>,
turn_environment: TurnEnvironment,
tracker: crate::tools::context::SharedTurnDiffTracker,
call_id: String,
@@ -71,6 +73,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
prefix_rule,
session,
turn,
step_context,
turn_environment,
tracker,
call_id,
@@ -146,6 +149,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
turn_environment.clone(),
session.clone(),
turn.clone(),
step_context.clone(),
Some(&tracker),
&call_id,
tool_name.name.as_str(),
@@ -206,6 +210,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
let tool_ctx = ToolCtx {
session: session.clone(),
turn: turn.clone(),
step_context,
call_id: call_id.clone(),
tool_name,
};
@@ -227,6 +227,7 @@ impl ShellCommandHandler {
prefix_rule,
session,
turn,
step_context,
turn_environment,
tracker,
call_id,
@@ -127,7 +127,12 @@ impl ExecCommandHandler {
};
let manager: &UnifiedExecProcessManager = &session.services.unified_exec_manager;
let context = UnifiedExecContext::new(session.clone(), turn.clone(), call_id.clone());
let context = UnifiedExecContext::new(
session.clone(),
turn.clone(),
step_context.clone(),
call_id.clone(),
);
let environment_args: ExecCommandEnvironmentArgs = parse_arguments(&arguments)?;
let Some(turn_environment) = resolve_tool_environment(
&step_context.environments,
@@ -318,6 +323,7 @@ impl ExecCommandHandler {
turn_environment.clone(),
context.session.clone(),
context.turn.clone(),
context.step_context.clone(),
Some(&tracker),
&context.call_id,
"exec_command",
+17 -3
View File
@@ -8,6 +8,8 @@ use crate::guardian::routes_approval_to_guardian;
use crate::hook_runtime::run_permission_request_hooks;
use crate::network_policy_decision::denied_network_policy_message;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::step_context::StepContextSeed;
use crate::tools::sandboxing::PermissionRequestPayload;
use crate::tools::sandboxing::ToolError;
use codex_hooks::PermissionRequestDecision;
@@ -22,6 +24,7 @@ use codex_protocol::approvals::NetworkApprovalContext;
use codex_protocol::approvals::NetworkApprovalProtocol;
use codex_protocol::approvals::NetworkPolicyRuleAction;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
@@ -243,6 +246,7 @@ struct ActiveNetworkApprovalCall {
command: String,
environment_id: String,
cancellation_token: CancellationToken,
reasoning_effort: Option<ReasoningEffortConfig>,
}
enum ActiveNetworkApprovalAttribution {
@@ -298,6 +302,7 @@ impl NetworkApprovalService {
command: String,
environment_id: String,
cancellation_token: CancellationToken,
reasoning_effort: Option<ReasoningEffortConfig>,
) {
let mut calls = self.calls.lock().await;
let key = registration_id.clone();
@@ -310,6 +315,7 @@ impl NetworkApprovalService {
command,
environment_id,
cancellation_token,
reasoning_effort,
}),
);
}
@@ -632,9 +638,16 @@ impl NetworkApprovalService {
let use_guardian = routes_approval_to_guardian(&turn_context);
let guardian_review_id = use_guardian.then(new_guardian_review_id);
let approval_decision = if let Some(review_id) = guardian_review_id.clone() {
let step_context_seed = owner_call.as_ref().map_or_else(
|| StepContextSeed::from_turn(Arc::clone(&turn_context)),
|call| StepContextSeed {
turn: Arc::clone(&turn_context),
reasoning_effort: call.reasoning_effort.clone(),
},
);
review_approval_request(
&session,
&turn_context,
&step_context_seed,
review_id,
GuardianApprovalRequest::NetworkAccess {
id: guardian_approval_id.clone(),
@@ -849,7 +862,7 @@ pub(crate) fn build_network_policy_decider(
pub(crate) async fn begin_network_approval(
session: &Session,
turn_id: &str,
step_context: &StepContext,
managed_network_active: bool,
selected_sandbox: SandboxType,
spec: Option<NetworkApprovalSpec>,
@@ -890,11 +903,12 @@ pub(crate) async fn begin_network_approval(
.network_approval
.register_call(
registration_id.clone(),
turn_id.to_string(),
step_context.turn.sub_id.clone(),
trigger,
command,
environment_id,
cancellation_token.clone(),
step_context.reasoning_effort.clone(),
)
.await;
@@ -313,6 +313,7 @@ async fn register_call_with_default_shell_trigger(
"curl https://example.com".to_string(),
"local".to_string(),
cancellation_token.clone(),
/*reasoning_effort*/ None,
)
.await;
cancellation_token
@@ -340,6 +341,7 @@ async fn active_call_preserves_triggering_command_context() {
"curl https://example.com".to_string(),
"remote".to_string(),
CancellationToken::new(),
/*reasoning_effort*/ None,
)
.await;
+7 -2
View File
@@ -13,6 +13,7 @@ use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::hook_runtime::run_permission_request_hooks;
use crate::network_policy_decision::network_approval_context_from_payload;
use crate::session::step_context::StepContextSeed;
use crate::tools::flat_tool_name;
use crate::tools::network_approval::ActiveNetworkApproval;
use crate::tools::network_approval::DeferredNetworkApproval;
@@ -71,7 +72,7 @@ impl ToolOrchestrator {
{
let network_approval = match begin_network_approval(
&tool_ctx.session,
&tool_ctx.turn.sub_id,
tool_ctx.step_context.as_ref(),
managed_network_active,
attempt.sandbox,
tool.network_approval_spec(req, tool_ctx),
@@ -85,6 +86,7 @@ impl ToolOrchestrator {
let attempt_tool_ctx = ToolCtx {
session: tool_ctx.session.clone(),
turn: tool_ctx.turn.clone(),
step_context: tool_ctx.step_context.clone(),
call_id: tool_ctx.call_id.clone(),
tool_name: tool_ctx.tool_name.clone(),
};
@@ -172,6 +174,7 @@ impl ToolOrchestrator {
let approval_ctx = ApprovalCtx {
session: &tool_ctx.session,
turn: &tool_ctx.turn,
step_context: &tool_ctx.step_context,
call_id: &tool_ctx.call_id,
guardian_review_id: guardian_review_id.clone(),
retry_reason: None,
@@ -207,6 +210,7 @@ impl ToolOrchestrator {
let approval_ctx = ApprovalCtx {
session: &tool_ctx.session,
turn: &tool_ctx.turn,
step_context: &tool_ctx.step_context,
call_id: &tool_ctx.call_id,
guardian_review_id: guardian_review_id.clone(),
retry_reason: reason.clone(),
@@ -404,6 +408,7 @@ impl ToolOrchestrator {
let approval_ctx = ApprovalCtx {
session: &tool_ctx.session,
turn: &tool_ctx.turn,
step_context: &tool_ctx.step_context,
call_id: &tool_ctx.call_id,
guardian_review_id: guardian_review_id.clone(),
retry_reason: Some(retry_reason),
@@ -579,7 +584,7 @@ impl ToolOrchestrator {
Ok(action) => {
review_approval_request(
approval_ctx.session,
approval_ctx.turn,
&StepContextSeed::from(approval_ctx.step_context.as_ref()),
review_id,
action,
approval_ctx.retry_reason.clone(),
@@ -13,6 +13,7 @@ use crate::hook_runtime::run_permission_request_hooks;
use crate::sandboxing::ExecOptions;
use crate::sandboxing::ExecRequest;
use crate::sandboxing::SandboxPermissions;
use crate::session::step_context::StepContextSeed;
use crate::shell::ShellType;
use crate::tools::runtimes::build_sandbox_command;
use crate::tools::runtimes::exec_env_for_sandbox_permissions;
@@ -38,6 +39,7 @@ use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::exec_output::StreamOutput;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
@@ -236,6 +238,7 @@ pub(super) async fn try_run_zsh_fork(
policy: Arc::clone(&exec_policy),
session: Arc::clone(&ctx.session),
turn: Arc::clone(&ctx.turn),
reasoning_effort: ctx.step_context.reasoning_effort.clone(),
call_id: ctx.call_id.clone(),
environment_id: req.turn_environment.environment_id.clone(),
tool_name: GuardianCommandSource::Shell,
@@ -320,6 +323,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
policy: Arc::clone(&exec_policy),
session: Arc::clone(&ctx.session),
turn: Arc::clone(&ctx.turn),
reasoning_effort: ctx.step_context.reasoning_effort.clone(),
call_id: ctx.call_id.clone(),
environment_id: req.turn_environment.environment_id.clone(),
tool_name: GuardianCommandSource::UnifiedExec,
@@ -355,6 +359,7 @@ struct CoreShellActionProvider {
policy: Arc<RwLock<Policy>>,
session: Arc<crate::session::session::Session>,
turn: Arc<crate::session::turn_context::TurnContext>,
reasoning_effort: Option<ReasoningEffortConfig>,
call_id: String,
environment_id: String,
tool_name: GuardianCommandSource,
@@ -451,6 +456,10 @@ impl CoreShellActionProvider {
let workdir = workdir.clone();
let session = self.session.clone();
let turn = self.turn.clone();
let step_context_seed = StepContextSeed {
turn: Arc::clone(&turn),
reasoning_effort: self.reasoning_effort.clone(),
};
let call_id = self.call_id.clone();
let approval_id = Some(Uuid::new_v4().to_string());
let environment_id = Some(self.environment_id.clone());
@@ -493,7 +502,7 @@ impl CoreShellActionProvider {
if let Some(review_id) = guardian_review_id.clone() {
let decision = review_approval_request(
&session,
&turn,
&step_context_seed,
review_id.clone(),
GuardianApprovalRequest::Execve {
id: call_id.clone(),
@@ -425,6 +425,7 @@ async fn preapproved_additional_permissions_escalate_intercepted_exec() -> anyho
policy: Arc::new(RwLock::new(codex_execpolicy::Policy::empty())),
session: Arc::new(session),
turn: Arc::new(turn_context),
reasoning_effort: None,
call_id: "preapproved-additional-permissions".to_string(),
environment_id: "local".to_string(),
tool_name: GuardianCommandSource::Shell,
@@ -561,6 +562,7 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul
policy: std::sync::Arc::new(RwLock::new(codex_execpolicy::Policy::empty())),
session: std::sync::Arc::new(session),
turn: std::sync::Arc::new(turn_context),
reasoning_effort: None,
call_id: "execve-hook-call".to_string(),
environment_id: "local".to_string(),
tool_name: GuardianCommandSource::Shell,
@@ -772,6 +774,7 @@ prefix_rule(pattern = ["{cat_path_literal}"], decision = "allow")
policy: Arc::new(RwLock::new(policy)),
session: Arc::new(session),
turn: Arc::new(turn_context),
reasoning_effort: None,
call_id: "deny-read-prefix-allow".to_string(),
environment_id: "local".to_string(),
tool_name: GuardianCommandSource::Shell,
@@ -809,6 +812,7 @@ async fn denied_reads_keep_granular_sandbox_rejection_for_escalation() -> anyhow
policy: Arc::new(RwLock::new(PolicyParser::new().build())),
session: Arc::new(session),
turn: Arc::new(turn_context),
reasoning_effort: None,
call_id: "deny-read-granular-sandbox-reject".to_string(),
environment_id: "local".to_string(),
tool_name: GuardianCommandSource::Shell,
+3
View File
@@ -7,6 +7,7 @@
use crate::sandboxing::ExecOptions;
use crate::sandboxing::SandboxPermissions;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::state::SessionServices;
use crate::tools::hook_names::HookToolName;
@@ -121,6 +122,7 @@ where
pub(crate) struct ApprovalCtx<'a> {
pub session: &'a Arc<Session>,
pub turn: &'a Arc<TurnContext>,
pub step_context: &'a Arc<StepContext>,
pub call_id: &'a str,
/// Guardian review lifecycle ID for this approval, when guardian is reviewing it.
///
@@ -384,6 +386,7 @@ pub(crate) trait Sandboxable {
pub(crate) struct ToolCtx {
pub session: Arc<Session>,
pub turn: Arc<TurnContext>,
pub step_context: Arc<StepContext>,
pub call_id: String,
pub tool_name: ToolName,
}
+9 -1
View File
@@ -38,6 +38,7 @@ use tokio::sync::Mutex;
use crate::sandboxing::SandboxPermissions;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use crate::session::turn_context::TurnEnvironment;
use crate::shell::ShellType;
@@ -75,14 +76,21 @@ pub(crate) const MAX_UNIFIED_EXEC_PROCESSES: usize = 64;
pub(crate) struct UnifiedExecContext {
pub session: Arc<Session>,
pub turn: Arc<TurnContext>,
pub step_context: Arc<StepContext>,
pub call_id: String,
}
impl UnifiedExecContext {
pub fn new(session: Arc<Session>, turn: Arc<TurnContext>, call_id: String) -> Self {
pub fn new(
session: Arc<Session>,
turn: Arc<TurnContext>,
step_context: Arc<StepContext>,
call_id: String,
) -> Self {
Self {
session,
turn,
step_context,
call_id,
}
}
+6 -2
View File
@@ -121,8 +121,12 @@ async fn exec_command_with_tty(
)
.await?,
);
let context =
UnifiedExecContext::new(Arc::clone(session), Arc::clone(turn), "call".to_string());
let context = UnifiedExecContext::new(
Arc::clone(session),
Arc::clone(turn),
StepContext::for_test(Arc::clone(turn)),
"call".to_string(),
);
let started_at = Instant::now();
let process_started_alive = !process.has_exited() && process.exit_code().is_none();
if process_started_alive {
@@ -1171,6 +1171,7 @@ impl UnifiedExecProcessManager {
let tool_ctx = ToolCtx {
session: context.session.clone(),
turn: context.turn.clone(),
step_context: context.step_context.clone(),
call_id: context.call_id.clone(),
tool_name: ToolName::plain("exec_command"),
};
@@ -262,6 +262,7 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
let context = UnifiedExecContext::new(
Arc::clone(&session),
Arc::clone(&turn),
StepContext::for_test(Arc::clone(&turn)),
"call-unified-denied".to_string(),
);
let request = ExecCommandRequest {