perf(core): make /history transcript_len/transcript_page O(1)/O(count)#6434
Merged
Conversation
MessageAccessImpl::transcript_len/transcript_page re-scanned the entire message vector on every /history call: transcript_len was O(total non-system messages) unconditionally, and transcript_page's .skip(start) ran on top of a .filter(), so it walked from index 0 even for the common bounded-default case where start sits near the end of history. Repeated /history all -> /history next paging did O(total^2/page_size) aggregate work. MessageState now maintains an incrementally-updated non_system_count (O(1) transcript_len), and transcript_page scans from whichever end of the vector is closer to the requested page, making both named hot patterns (/history N bounded-default, /history all's first page) O(count) instead of O(total). Middle-of-history /history next pages remain up to O(min(start, total-end)) by design, documented in the transcript_page doc comment. Audited and updated every messages-vector mutation site in zeph-core to keep the counter in sync: single push/insert/remove/pop sites use an O(1) delta (track_single_message), batch mutations and mutations that happen through a borrowed view in a foreign crate use an O(n) full recount (recompute_non_system_count) mirroring the existing recompute_prompt_tokens() pattern. Closes #6427
bug-ops
enabled auto-merge (squash)
July 18, 2026 00:52
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
MessageAccessImpl::transcript_len/transcript_page(crates/zeph-core/src/agent/command_context_impls.rs), which back the/historycommand, re-scanned the entire message vector on every call —transcript_lenwas O(total non-system messages) unconditionally, andtranscript_page's.skip(start)ran on top of a.filter(), so it walked from index 0 even though the common bounded-default case (/history N) computesstartnear the end of history. Repeated/history all->/history nextpaging did O(total²/page_size) aggregate work.MessageStatenow maintains an incrementally-updatednon_system_count(O(1)transcript_len), andtranscript_pagescans from whichever end of the message vector is closer to the requested page, making both named hot patterns (/history Nbounded-default tail read,/history all's first page) O(count) instead of O(total)./history nextchain remain up to O(min(start, total-end)) by design — documented in thetranscript_pagedoc comment. A full position index of non-system ordinals was rejected: arbitrary-positionSystem-message churn (LSP notes, focus checkpoints) would make that index itself O(n) per turn, moving cost onto the hotter per-turn path instead of the/historyread path.zeph-core(~15 production call sites across 9 files) to keep the counter in sync: single push/insert/remove/pop sites use an O(1) delta (track_single_message); batch mutations, and mutations that happen through a borrowed view in a foreign crate (zeph-agent-context,zeph-agent-persistence), use an O(n) full recount (recompute_non_system_count) mirroring the existingrecompute_prompt_tokens()cache-invalidation pattern already used at the same call sites.Background
Follow-up from #6420 (resume-session history visibility, spec-068 §13.6, INV-SP-6), explicitly tracked as a known perf gap when that PR merged.
Went through the reduced performance chain: developer (root cause + fix + mutation-site audit + 10 unit tests) -> parallel validation (perf: microbenchmark confirming the O(1)/O(count) claim, 2400x-26000x speedup on
transcript_len, 10.7x-87.6x on the bounded-default tail read, no regressions; tester: confirmed 2099 tests pass, found a real coverage gap — none of the batch-mutation sites were exercised by a test that readstranscript_len/transcript_pageafterward; impl-critic: verdictminor, re-verified the mutation-site audit and the bidirectional-scan boundary logic independently, no blocking bugs) -> code review (onechanges_requestedround: required 2 targeted regression tests closing the coverage gap tester found, plus a zero-costsaturating_addhardening; approved on re-review).Non-blocking follow-ups noted during review, not addressed in this PR:
#[cfg(test)]-gated context wrappers inassembly.rshave asymmetric recompute treatment vs their System-only siblings — dead in every real build (confirmed by direct read), so no live bug, but worth a uniformity pass if any of them are ever promoted to production.focus.rs'sapply_compression_removals->rebuild_knowledge_blockpath — not worth changing unless that path proves hot.Test plan
cargo +nightly fmt --check— cleancargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings— cleancargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins— 14280/14280 passed, 0 failedRUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"— cleangitleaks protect --staged— no leakscommand_context_impls.rs::tests(10 initial + 2 added during review to close a coverage gap on the batch-mutation/foreign-crate-view recompute pattern).local/testing/playbooks/session-resume-history.mdand.local/testing/coverage-status.mdupdated (main repo, not this worktree)rust-code-reviewer: approved after one fix round (test coverage gap)Closes #6427