Files
codex/codex-rs/external-agent-sessions/src/ledger_tests.rs
stefanstokic-oai 6d8e12ac42 [codex] Speed up external agent session imports (#26637)
## 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.
2026-06-08 14:16:32 -04:00

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());
}