Skip to content

[4/5] Propagate step context through execution paths#31736

Closed
aibrahim-oai wants to merge 1 commit into
step-context/active-stepfrom
step-context/propagate-step
Closed

[4/5] Propagate step context through execution paths#31736
aibrahim-oai wants to merge 1 commit into
step-context/active-stepfrom
step-context/propagate-step

Conversation

@aibrahim-oai

@aibrahim-oai aibrahim-oai commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Why

Approvals, delegated agents, compaction, and execution runtimes need to carry the same request snapshot that selected environments, MCP state, and tools. Passing both a turn and a step through these paths duplicates context and makes it possible for asynchronous work to use a different snapshot from the request that produced it.

What changed

  • Replace redundant TurnContext parameters and stored fields with StepContext through guardian, delegated-agent, compaction, approval, tool, unified-exec, and shell-escalation paths.
  • Derive the turn from step_context.turn where turn-level data is still required.
  • Build persisted turn-context items from the step snapshot and update existing fixtures accordingly.
  • Use one initial-context builder now that StepContext carries the exact MCP snapshot, removing the former latest-MCP forwarding wrapper.
  • Pass the already-registered manual-compaction step into both remote compaction variants so the active and executed snapshots are identical.

Stacked on #31735. This is PR 4 of 5. Reasoning effort remains on TurnContext; all existing raw-versus-effective reads are preserved.

Validation

  • cargo check -p codex-core --tests --message-format short

@aibrahim-oai aibrahim-oai force-pushed the step-context/active-step branch from b17eb83 to c40fc26 Compare July 9, 2026 06:13
@aibrahim-oai aibrahim-oai force-pushed the step-context/propagate-step branch 2 times, most recently from c1b75f0 to fcc9c54 Compare July 9, 2026 06:53
@aibrahim-oai aibrahim-oai force-pushed the step-context/active-step branch from c40fc26 to b607801 Compare July 9, 2026 06:53
@aibrahim-oai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcc9c54d70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.as_ref()
.map(|owner_call| owner_call.step_context.clone())
else {
return NetworkDecision::deny(REASON_NOT_ALLOWED);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Set a decision for unowned Guardian network requests

When Guardian is enabled and resolve_request_attribution returns no owner_call (for example, a managed-network request tagged only with an environment id and no execution attribution), this early return happens after get_or_create_pending_approval(...) has registered the host key. The owner request returns deny without setting a PendingApprovalDecision or removing pending_host_approvals, so any concurrent or subsequent request for the same host will block forever in pending.wait_for_decision(). Set/remove the pending entry, or fall back to the non-Guardian approval path, before returning.

Useful? React with 👍 / 👎.

Comment on lines +2494 to 2497
let turn_context = &step_context.turn;
let turn_environment = match args.environment_id.as_deref() {
Some(environment_id) => turn_context
.environments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the step environment snapshot for permission requests

Now that this path receives a StepContext, looking up the target environment through step_context.turn.environments ignores the dynamic snapshot captured for the request. With DeferredExecutor enabled, a delegated child can request permissions for an environment id/cwd that exists in step_context.environments but not in the turn-frozen list, causing this helper to return the default empty permission response or build the request from stale environment data.

Useful? React with 👍 / 👎.

subagent_source: SubAgentSource,
initial_history: Option<InitialHistory>,
) -> Result<Codex, CodexErr> {
let parent_ctx = step_context.turn.as_ref();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Spawn delegated Codex with step environments

This aliases the step down to only its turn, and the Codex::spawn arguments below still inherit parent_ctx.environments/to_selections(). In sessions where the step captured a refreshed environment snapshot, delegated reviewers or sub-Codex tasks started from this request will run against the old turn-frozen environments rather than the environments/tools that produced the request.

Useful? React with 👍 / 👎.

Comment on lines +90 to +93
let mut config = build_agent_spawn_config(
&session.get_base_instructions().await,
step_context.as_ref(),
)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass step environments to spawned agents

The spawn config now comes from step_context, but the SpawnAgentOptions below still pass turn.environments.to_selections(). When the active step has a newer environment snapshot, agents created by this tool start in the stale turn environment instead of the one used for the tool call; the v2 spawn and agent-job spawn paths need the same step-environment handoff.

Useful? React with 👍 / 👎.

@aibrahim-oai aibrahim-oai changed the title Propagate step context through execution paths [3/4] Propagate step context through execution paths Jul 9, 2026
@aibrahim-oai aibrahim-oai force-pushed the step-context/active-step branch from b607801 to a369b88 Compare July 9, 2026 21:41
@aibrahim-oai aibrahim-oai force-pushed the step-context/propagate-step branch from fcc9c54 to 8d52444 Compare July 9, 2026 21:41
@aibrahim-oai aibrahim-oai changed the title [3/4] Propagate step context through execution paths [4/5] Propagate step context through execution paths Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@aibrahim-oai aibrahim-oai force-pushed the step-context/propagate-step branch from 8d52444 to 842106d Compare July 9, 2026 21:57
@aibrahim-oai aibrahim-oai force-pushed the step-context/active-step branch from a369b88 to 3e61417 Compare July 9, 2026 21:57
@aibrahim-oai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

inherited_environments: Some(parent_ctx.environments.clone()),
inherited_exec_policy: Some(Arc::clone(&parent_session.services.exec_policy)),
parent_rollout_thread_trace: codex_rollout_trace::ThreadTraceContext::disabled(),
parent_trace: None,
environment_selections: parent_ctx.environments.to_selections(),

P2 Badge Use the step snapshot when spawning delegates

When the deferred executor refreshes environments, step_context.environments can differ from the frozen step_context.turn.environments; this still seeds subagents and guardian reviewers from parent_ctx.environments, so a delegated agent spawned after an environment change starts with stale environment selections and cannot operate on the same snapshot whose tools were advertised for the parent step. Use the environments from the passed StepContext for both inherited environments and selections.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 2494 to 2495
Some(environment_id) => turn_context
.environments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Look up permission environments from the step snapshot

For permission requests forwarded with a StepContext, this lookup still walks turn_context.environments, which is stale after a deferred-executor environment refresh; a delegated request for an environment that exists in the current step but not in the turn snapshot falls through to the default empty permission response. The lookup should use step_context.environments so approvals are evaluated against the same environment snapshot that produced the request.

Useful? React with 👍 / 👎.

Comment on lines 259 to 262
let (new_history, world_state_baseline) = process_compacted_history(
sess.as_ref(),
compaction_turn_context.as_ref(),
step_context.as_ref(),
new_history,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the fallback compaction step when installing history

When previous-model remote compaction falls back after an InvalidRequest, attempt and compaction_turn_context can come from fallback_step_context, but this call now still processes and persists context from the original step_context. That reinjects the old model/settings into the replacement history and reference TurnContextItem, so the next request or resumed rollout can see stale model-visible context after a successful fallback; keep the step paired with the successful attempt (the v2 path has the same substitution).

AGENTS.md reference: AGENTS.md:L91-L100

Useful? React with 👍 / 👎.

Comment on lines +639 to +643
let Some(step_context) = owner_call
.as_ref()
.map(|owner_call| owner_call.step_context.clone())
else {
return NetworkDecision::deny(REASON_NOT_ALLOWED);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not deny unattributed guardian network approvals

When guardian review is enabled and the network proxy reports a blocked request that is attributable only to the active turn (no execution_id/owner call), this new else returns not_allowed instead of asking guardian, even though the non-guardian branch below and GuardianApprovalRequest::NetworkAccess { trigger: None } both support that case. This regresses session-level/background proxy requests that previously could be reviewed and approved.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 842106df92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +642 to +643
else {
return NetworkDecision::deny(REASON_NOT_ALLOWED);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve pending approval before denying ownerless guardian requests

When guardian routing is enabled and an inline network policy request reaches this branch without an attributable owner_call (for example, an environment-scoped request with no matching single active call), this early return denies the current request after get_or_create_pending_approval has inserted a pending entry, but it never calls pending.set_decision(...) or removes key from pending_host_approvals. A later request for the same host becomes a non-owner and waits on that unresolved pending approval indefinitely; use the same cleanup/decision path as the other denial branches before returning.

Useful? React with 👍 / 👎.

subagent_source: SubAgentSource,
initial_history: Option<InitialHistory>,
) -> Result<Codex, CodexErr> {
let parent_ctx = step_context.turn.as_ref();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use step environments when spawning delegated threads

When DeferredExecutor is enabled, step_context.environments is refreshed for the exact sampling request, but this immediately collapses the new StepContext back to step_context.turn and the spawn args below still fill inherited_environments and environment_selections from parent_ctx.environments. Delegated review/subagent sessions can therefore start with the turn’s stale environment/cwd instead of the environment snapshot used to advertise and execute the parent tool call; pass step_context.environments into those spawn fields.

Useful? React with 👍 / 👎.

step_context: Arc<StepContext>,
arguments: String,
) -> Result<FunctionToolOutput, FunctionCallError> {
let turn = step_context.turn.clone();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use step environments for CSV agent jobs

This clones the turn out of the new StepContext, and the rest of the CSV job path still uses turn.environments to choose the CSV cwd and to seed worker environment selections. In a deferred-executor step where the request snapshot has a different or newly-ready environment, spawn_agents_on_csv will read from the stale turn cwd or reject the job as not exactly one local environment even though the model saw the refreshed step; use step_context.environments for the cwd check and worker spawn selections.

Useful? React with 👍 / 👎.

@aibrahim-oai aibrahim-oai force-pushed the step-context/active-step branch from 3e61417 to d884a54 Compare July 9, 2026 23:35
@aibrahim-oai aibrahim-oai force-pushed the step-context/propagate-step branch from 842106d to 9f8d7de Compare July 9, 2026 23:35
@aibrahim-oai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f8d7de0e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +639 to +643
let Some(step_context) = owner_call
.as_ref()
.map(|owner_call| owner_call.step_context.clone())
else {
return NetworkDecision::deny(REASON_NOT_ALLOWED);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve pending network approval before denying

When guardian review is active but a policy request cannot be attributed to an owner call, this new early return exits after get_or_create_pending_approval inserted key but before pending.set_decision(...) and before removing it from pending_host_approvals. A later request for the same host/environment will find the stale pending entry, take the !is_owner path, and wait forever in wait_for_decision() instead of receiving the denial. Please set a deny decision and remove the pending approval before returning, or route the request with an active step context.

Useful? React with 👍 / 👎.

Comment on lines +63 to +66
let mut config = build_agent_spawn_config(
&session.get_base_instructions().await,
step_context.as_ref(),
)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use step environments for spawned agents

When DeferredExecutor refreshes environments for the current request, the model-visible world state is built from step_context.environments, but this spawn path only uses the step for the child config and still passes turn.environments.to_selections() in SpawnAgentOptions below. In a turn where a remote/attached environment becomes ready after the turn snapshot, spawn_agent creates a child without the environments the parent just saw, so the child can run in the wrong/default environment or miss the requested one. Please pass the step snapshot's selections into the spawn options.

Useful? React with 👍 / 👎.

subagent_source: SubAgentSource,
initial_history: Option<InitialHistory>,
) -> Result<Codex, CodexErr> {
let parent_ctx = step_context.turn.as_ref();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve step environments when starting delegates

This converts the delegate API to accept a StepContext, but immediately reduces it to step_context.turn; the CodexSpawnArgs below still inherit parent_ctx.environments and parent_ctx.environments.to_selections(). For review/guardian/delegated subagent sessions started after a step refresh, the child session therefore loses the exact environment snapshot that was advertised and used for the parent request. Please use step_context.environments for inherited environments and selections.

Useful? React with 👍 / 👎.

@aibrahim-oai

Copy link
Copy Markdown
Contributor Author

Migrated to https://github.com/openai/codex-internal/pull/1672 following the codex-internal cutover. Please continue review there; this public PR is superseded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant