mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
## Why Importing large external-agent session histories currently starts a full live Codex thread for every imported session. This initializes unrelated runtime systems and repeats expensive transcript, metadata, hashing, and ledger work. On a 50-session, 238 MiB fixture, the existing path took roughly 70 seconds to complete the import and 77 seconds end to end. ## What changed - Persist imported sessions directly through `ThreadStore` instead of starting full live threads. - Process imports through a bounded five-session pipeline. - Parse, extract, and hash each source file in one pass. - Move blocking source preparation onto the blocking thread pool. - Reuse prepared content hashes and update the import ledger once per batch. - Avoid metadata readback for newly written rollouts. - Preserve imported conversation history and visible thread metadata. - Keep the implementation out of `codex-core` and avoid changes to the public `ThreadStore` trait. ## Performance For the same 50-session, 238 MiB fixture: | Path | Import completion | End to end | | --- | ---: | ---: | | Existing import | 69.61s | 76.62s | | This change | 5.95s | 6.58s | All 50 sessions imported successfully with no warnings or contention signals. ## Validation - `just test -p codex-external-agent-sessions` - `just test -p codex-app-server external_agent_config_import` - Verified imports do not initialize unrelated required MCP servers. - Verified previously imported source versions are skipped and changed sources can be imported again. - Verified imported rollouts remain readable through thread listing and history APIs.
86 lines
3.2 KiB
Rust
86 lines
3.2 KiB
Rust
use super::CompletedExternalAgentSessionImport;
|
|
use super::ImportedExternalAgentSessionLedger;
|
|
use super::record_completed_session_imports;
|
|
use codex_protocol::ThreadId;
|
|
use sha2::Digest;
|
|
use sha2::Sha256;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn empty_ledger_does_not_read_source() {
|
|
let root = TempDir::new().expect("tempdir");
|
|
let missing_source = root.path().join("missing-session.jsonl");
|
|
|
|
assert!(
|
|
!ImportedExternalAgentSessionLedger::default()
|
|
.contains_current_source(&missing_source)
|
|
.expect("empty ledger cannot contain sources")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn completed_imports_do_not_read_source_files() {
|
|
let root = TempDir::new().expect("tempdir");
|
|
let codex_home = root.path().join("codex-home");
|
|
let source_path = root.path().join("session.jsonl");
|
|
let contents = b"session contents";
|
|
std::fs::write(&source_path, contents).expect("source");
|
|
let source_path = std::fs::canonicalize(&source_path).expect("canonical source");
|
|
std::fs::remove_file(&source_path).expect("remove source");
|
|
let imported_thread_id = ThreadId::new();
|
|
|
|
record_completed_session_imports(
|
|
&codex_home,
|
|
vec![CompletedExternalAgentSessionImport {
|
|
source_path: source_path.clone(),
|
|
source_content_sha256: format!("{:x}", Sha256::digest(contents)),
|
|
imported_thread_id,
|
|
}],
|
|
)
|
|
.expect("record completed imports");
|
|
|
|
let ledger = super::load_import_ledger(&codex_home).expect("ledger");
|
|
assert_eq!(ledger.records.len(), 1);
|
|
assert_eq!(ledger.records[0].source_path, source_path);
|
|
assert_eq!(ledger.records[0].imported_thread_id, imported_thread_id);
|
|
assert_eq!(ledger.records[0].source_modified_at, None);
|
|
}
|
|
|
|
#[test]
|
|
fn completed_import_refreshes_existing_record_metadata() {
|
|
let root = TempDir::new().expect("tempdir");
|
|
let codex_home = root.path().join("codex-home");
|
|
let source_path = root.path().join("session.jsonl");
|
|
let contents = b"session contents";
|
|
std::fs::write(&source_path, contents).expect("source");
|
|
let source_path = std::fs::canonicalize(source_path).expect("canonical source");
|
|
let content_sha256 = format!("{:x}", Sha256::digest(contents));
|
|
let first_thread_id = ThreadId::new();
|
|
let second_thread_id = ThreadId::new();
|
|
|
|
record_completed_session_imports(
|
|
&codex_home,
|
|
vec![CompletedExternalAgentSessionImport {
|
|
source_path: source_path.clone(),
|
|
source_content_sha256: content_sha256.clone(),
|
|
imported_thread_id: first_thread_id,
|
|
}],
|
|
)
|
|
.expect("record first import");
|
|
record_completed_session_imports(
|
|
&codex_home,
|
|
vec![CompletedExternalAgentSessionImport {
|
|
source_path: source_path.clone(),
|
|
source_content_sha256: content_sha256,
|
|
imported_thread_id: second_thread_id,
|
|
}],
|
|
)
|
|
.expect("record replacement import");
|
|
|
|
let ledger = super::load_import_ledger(&codex_home).expect("ledger");
|
|
assert_eq!(ledger.records.len(), 1);
|
|
assert_eq!(ledger.records[0].source_path, source_path);
|
|
assert_eq!(ledger.records[0].imported_thread_id, second_thread_id);
|
|
assert!(ledger.records[0].source_modified_at.is_some());
|
|
}
|