fix(session): route hydrate_from_event_log through bounded replay#5861
Merged
Conversation
hydrate_from_event_log called ReplayEngine::fold(events.clone(), up_to) instead of the bounded/chunked ReplayEngine::replay added by #5844, doubling peak memory on every session-resume path (ACP resume/load/fork, CLI sessions resume, /conv resume) and defeating #5844's memory bound where it matters most. The messages fold now goes through ReplayEngine::replay; events is still read once via read_all for reconcile_projection and Hydrated::events, which need the full owned list — a deliberate second sequential read, not a second in-memory copy. Also extract a shared finish_torn_tail helper in zeph-session/src/log.rs for the torn-tail warn+repair epilogue duplicated between read_events and read_events_chunked. Closes #5851, closes #5852
bug-ops
enabled auto-merge (squash)
July 10, 2026 13:40
The rustc/clippy 1.97.0 (2026-07-07) toolchain bump tightened several lints (manual_assert_eq, useless_borrows_in_formatting, question_mark), newly flagging 10 pre-existing call sites across the workspace and blocking the full clippy gate for every open PR, including this one. Trivial, mechanical, behavior-preserving fixes, unrelated to #5851/#5852: - crates/zeph-scheduler/src/durable.rs: assert! -> assert_ne! - crates/zeph-orchestration/src/adaptorch.rs: assert! -> assert_eq! - crates/zeph-core/src/agent/experiment_cmd.rs: drop redundant & in writeln! - crates/zeph-core/src/agent/learning/background.rs: drop redundant & in format! - crates/zeph-tui/src/widgets/chat.rs: assert! -> assert_eq! - crates/zeph-tui/src/widgets/subagents.rs: drop redundant & (x2) - src/runner.rs: drop redundant & in println! - src/scheduler_executor.rs: use ? instead of an explicit else/return None - src/tests.rs: assert! -> assert_eq! Full CI-matching clippy (both the desktop/ide/server/chat/pdf/scheduler feature set and the bench matrix), fmt, and nextest on every touched crate all verified clean. See #5860
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hydrate_from_event_log(crates/zeph-agent-persistence/src/hydrate.rs) is the single choke point every session-resume path (ACP resume/load/fork, CLIsessions resume,/conv resume) routes through, but it never called the bounded/chunkedReplayEngine::replayadded by perf(session): bound replay memory via chunked event-log read #5844 — it still didReplayEngine::fold(events.clone(), up_to), doubling peak memory (the clone plus theeventsVecalready held forreconcile_projection/Hydrated::events) and defeating perf(session): bound replay memory via chunked event-log read #5844's memory bound on the production resume path.messagesviaReplayEngine::replay(session_path, up_to)(bounded/chunked reader, ≤ 100 envelopes in memory at once).eventsis still fully read vialog.read_all()— a deliberate second sequential I/O pass, not a second in-memory copy — sincereconcile_projectionandmaybe_condense_on_resumeneed the full owned event list.read_events/read_events_chunked(crates/zeph-session/src/log.rs) into a sharedfinish_torn_tailhelper.Closes #5851, closes #5852.
Design notes
ReplayEngine::replay's internal locklessSessionEventLog::opencannot deadlock againsthydrate_from_event_log's already-open exclusive-lockedloghandle —open()never acquires theflock.up_tosemantics are shared betweenfoldandreplayvia the samefold_stephelper; a new regression test (up_to_bounds_messages_but_not_events) exercisesup_to = Some(n)throughhydrate_from_event_logfor the first time and pins down thatHydrated::eventsis always the full, unbounded log (pre-existing, unchanged behavior —reconcile_projection/condensation need the whole list regardless of the fold's cutoff)."dropped torn tail"warning twice per resume instead of once, since the fix performs two independent reads ofevents.jsonl(one lockless insidereplay, one exclusive viaread_all). Both reads observe identical bytes (no concurrent writer during resume, per D-10), so this is a duplicate log line, not a correctness issue.Test plan
cargo +nightly fmt --check— cleancargo clippy --profile ci -p zeph-session -p zeph-agent-persistence --all-targets --features sqlite -- -D warnings— cleancargo nextest run --config-file .github/nextest.toml -p zeph-session -p zeph-agent-persistence— 101/101 pass (includes newup_to_bounds_messages_but_not_eventsregression test)RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps -p zeph-session -p zeph-agent-persistence— cleanhydrate.rstests pass unchanged (behavioral equivalence confirmed)cargo clippy --profile ci --workspace ...gate — currently blocked by an unrelated, pre-existing failure onmainHEAD incrates/zeph-scheduler/src/durable.rs:239(clippy::manual_assert_eq), filed separately as workspace clippy gate fails on main HEAD: manual_assert_eq in zeph-scheduler durable.rs test #5860; not introduced by this PR (scoped clippy on both touched crates is clean).local/testing/coverage-status.md, marked "Untested (live)" pending next CI cycle)