Compare commits

...

1 Commits

Author SHA1 Message Date
Dylan Hurd
6fa7aebf42 fix(core) Use original base_instructions when resuming 2026-01-17 01:01:01 -08:00
2 changed files with 134 additions and 1 deletions

View File

@@ -250,6 +250,7 @@ impl Codex {
);
}
let base_instructions_from_history = load_instructions_from_history(&conversation_history);
let user_instructions = get_user_instructions(&config, Some(&loaded_skills.skills)).await;
let exec_policy = ExecPolicyManager::load(&config.features, &config.config_layer_stack)
@@ -277,7 +278,10 @@ impl Codex {
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
user_instructions,
base_instructions: config.base_instructions.clone(),
base_instructions: config
.base_instructions
.clone()
.or(base_instructions_from_history),
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.approval_policy.clone(),
sandbox_policy: config.sandbox_policy.clone(),
@@ -362,6 +366,22 @@ impl Codex {
}
}
fn load_instructions_from_history(history: &InitialHistory) -> Option<String> {
let items = match history {
InitialHistory::Resumed(resumed) => resumed.history.as_slice(),
InitialHistory::Forked(forked) => forked.as_slice(),
InitialHistory::New => return None,
};
items.iter().find_map(|item| {
if let RolloutItem::TurnContext(ctx) = item {
ctx.base_instructions.clone()
} else {
None
}
})
}
/// Context for an initialized model agent
///
/// A session has at most 1 running task at a time, and can be interrupted by user input.
@@ -3267,6 +3287,36 @@ mod tests {
assert_eq!(expected, history.raw_items());
}
#[test]
fn load_instructions_from_history_uses_initial_turn_context() {
let history = InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: vec![RolloutItem::TurnContext(TurnContextItem {
cwd: PathBuf::from("/tmp"),
approval_policy: AskForApproval::default(),
sandbox_policy: SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
},
model: "test-model".to_string(),
effort: None,
summary: ReasoningSummaryConfig::default(),
base_instructions: Some("base override".to_string()),
user_instructions: None,
developer_instructions: None,
final_output_json_schema: None,
truncation_policy: None,
})],
rollout_path: PathBuf::from("/tmp/rollout.jsonl"),
});
let restored = super::load_instructions_from_history(&history);
assert_eq!(restored, Some("base override".to_string()));
}
#[tokio::test]
async fn thread_rollback_drops_last_turn_from_history() {
let (sess, tc, rx) = make_session_and_context_with_rx().await;

View File

@@ -131,3 +131,86 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()>
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn resume_restores_base_instructions_from_rollout() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mut initial_builder = test_codex().with_config(|config| {
config.base_instructions = Some("base instructions override".to_string());
});
let initial = initial_builder.build(&server).await?;
let codex = Arc::clone(&initial.codex);
let home = initial.home.clone();
let rollout_path = initial.session_configured.rollout_path.clone();
let initial_mock = mount_sse_once(
&server,
sse(vec![
ev_response_created("resp-initial"),
ev_assistant_message("msg-1", "Completed first turn"),
ev_completed("resp-initial"),
]),
)
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "Record base instructions".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await?;
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
let initial_request = initial_mock.single_request();
let initial_body = initial_request.body_json();
let initial_instructions = initial_body["instructions"]
.as_str()
.expect("instructions string");
assert_eq!(initial_instructions, "base instructions override");
let mut resume_builder = test_codex().with_config(|config| {
config.base_instructions = None;
});
let resumed = resume_builder.resume(&server, home, rollout_path).await?;
let resumed_codex = Arc::clone(&resumed.codex);
let resumed_mock = mount_sse_once(
&server,
sse(vec![
ev_response_created("resp-resumed"),
ev_assistant_message("msg-2", "Completed resumed turn"),
ev_completed("resp-resumed"),
]),
)
.await;
resumed_codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "Verify base instructions".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await?;
wait_for_event(&resumed_codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
let resumed_request = resumed_mock.single_request();
let resumed_body = resumed_request.body_json();
let resumed_instructions = resumed_body["instructions"]
.as_str()
.expect("instructions string");
assert_eq!(resumed_instructions, "base instructions override");
Ok(())
}