perf: retrieval, import, LangChain, and session-context optimizations - #3569
Merged
qin-ctx merged 9 commits intoJul 30, 2026
Merged
Conversation
qin-ctx
requested changes
Jul 28, 2026
qin-ctx
left a comment
Collaborator
There was a problem hiding this comment.
本次 review 发现 3 个 blocking 问题:archive 失败后的上下文恢复与 RFC/公开语义不一致,Excel 新配置会静默改变既有导入行为,LangChain 的 pending_tokens 优化未接入真实 client/server 返回契约。另有 2 个 non-blocking 设计问题,涉及异步路径的一致性和 working-memory archive 的实际 I/O 范围。具体触发条件、执行过程和影响见 inline comments。
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
…nfig Address review on PR volcengine#3569. Remove the OPENVIKING_MARKDOWN_APPLY_FAST_WRITE path. It replaced _write_section with a raw viking_fs.write, which silently bypassed _ensure_parent_dirs and _run_with_encrypted_write_lock. Telemetry puts its share of the 16-way Excel batch at roughly 135ms out of a 17.9s total, so the correctness risk was not worth the gain. Section writes go back through write_file; OPENVIKING_MARKDOWN_APPLY_CONCURRENCY is gone with the path it gated, and the profile log stays. The Excel process pool, which accounts for the rest of that speedup, is unchanged. Keep Excel sectioning following parsers.markdown when parsers.excel does not set it. ExcelParser converts to Markdown and delegates sectioning to an inner MarkdownParser, so pointing the registry at the new config section would have silently changed section boundaries, node structure and stable Viking URIs for deployments that had tuned parsers.markdown. Explicit parsers.excel values still win, and the process-pool knobs are never inherited. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. Archive history grows without bound, but get_session_context walked all of it: one marker read per archive, an overview read per Working-Memory archive, and a .meta.json read per completed archive for checkpoints. The previous commit only moved the overview cut-off, so the message and checkpoint paths still scanned everything and the intended saving did not materialize. Scan newest to oldest and stop at the first terminal marker. Nothing at or older than that terminal is read: overview comes from the terminal archive when it is completed and readable, raw messages come only from newer non-terminal archives, and checkpoints come only from the terminal archive. A session with 100 archives now touches exactly one. Deliberate deviations from RFC volcengine#3330, kept narrow and confined to this read path: - An uncovered failed archive no longer replays its raw messages into get_session_context, dropping the failed-raw term from logical live. The raw file stays durable and Phase 2 roll-forward still absorbs it into a later overview; the gap is the window between the failure and the next successful archive. - Only the newest terminal archive's checkpoints are restored, so a long User Turn committed partially more than once keeps just its newest compressed prefix. - stats.failedArchives becomes 0/1 ("is the newest terminal a failure") because an exact count needs the full marker scan this change removes. stats.totalArchives stays exact via the directory listing. Memory extraction is unaffected: _scan_archive_states still performs a full scan and remains the source of truth for Phase 2 waiting, the coverage frontier and roll-forward. Marker probing uses exists() rather than read_file exception handling. Tests cover the terminal-stop cost bound, the deviations above, and that raw files stay durable while the read path stops. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. The commit-policy optimization read pending_tokens from the write response, but no real producer supplied it: the REST add_message and batch_add_messages handlers returned only session_id/message_count/added, and LocalClient matched them. Only the LangChain in-memory test double returned the field, so persisted_pending_tokens was always None on real deployments, apply_commit_policy always fell back to get_session, and the fake-based tests reported an optimization that never took effect in production. Return the post-write pending_tokens from both REST handlers and both LocalClient methods. The value is already maintained in O(1) inside _append_messages, so it is exact at the point the write returns, which is what the commit policy needs. Reading it goes through a helper that degrades to 0 when a session object does not expose meta, keeping lightweight and legacy session implementations working. Also update the REST failed-archive context test for the terminal-stop read path, and add coverage that exercises the real endpoints rather than a test double: the write-returned value must be positive, grow across writes, and match what get_session would have reported, which is exactly the round trip this field removes. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. The previous commit changed only the synchronous path, so LangGraph callers, which use the async entry points, kept paying for every optimization it claimed: - aassemble still called _aensure_session unconditionally before each read, so an existing session took an extra create_session per turn. - _aget_session_context had no NOT_FOUND branch at all; its correctness relied on that unconditional ensure, so removing the ensure alone would have left recall pointing at a session that was never created. - aget_messages created a session after any exception, while the sync path only did so on NOT_FOUND. A 5xx or a timeout therefore added a create_session call during exactly the incidents when the service was already struggling. Read first and create only on NOT_FOUND, mirroring the sync path, and add the missing ensure branches to _aget_session_context so the first use still materializes an empty session without a second context read. Tests assert that repeated assembles on an existing session issue no create_session, that a missing session issues exactly one, and that a non-NOT_FOUND failure issues none. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Run Excel-to-Markdown conversion and layout in a process pool gated by excel.enable_process_pool / excel.process_pool_workers, add a Markdown fast-write layout path, and skip image scanning when a layout has no local image refs. Includes unit tests for each path. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
force-pushed
the
perf/ov0616-main-split
branch
from
July 29, 2026 08:15
1c0934f to
847e537
Compare
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
…nfig Address review on PR volcengine#3569. Remove the OPENVIKING_MARKDOWN_APPLY_FAST_WRITE path. It replaced _write_section with a raw viking_fs.write, which silently bypassed _ensure_parent_dirs and _run_with_encrypted_write_lock. Telemetry puts its share of the 16-way Excel batch at roughly 135ms out of a 17.9s total, so the correctness risk was not worth the gain. Section writes go back through write_file; OPENVIKING_MARKDOWN_APPLY_CONCURRENCY is gone with the path it gated, and the profile log stays. The Excel process pool, which accounts for the rest of that speedup, is unchanged. Keep Excel sectioning following parsers.markdown when parsers.excel does not set it. ExcelParser converts to Markdown and delegates sectioning to an inner MarkdownParser, so pointing the registry at the new config section would have silently changed section boundaries, node structure and stable Viking URIs for deployments that had tuned parsers.markdown. Explicit parsers.excel values still win, and the process-pool knobs are never inherited. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. Archive history grows without bound, but get_session_context walked all of it: one marker read per archive, an overview read per Working-Memory archive, and a .meta.json read per completed archive for checkpoints. The previous commit only moved the overview cut-off, so the message and checkpoint paths still scanned everything and the intended saving did not materialize. Scan newest to oldest and stop at the first terminal marker. Nothing at or older than that terminal is read: overview comes from the terminal archive when it is completed and readable, raw messages come only from newer non-terminal archives, and checkpoints come only from the terminal archive. A session with 100 archives now touches exactly one. Deliberate deviations from RFC volcengine#3330, kept narrow and confined to this read path: - An uncovered failed archive no longer replays its raw messages into get_session_context, dropping the failed-raw term from logical live. The raw file stays durable and Phase 2 roll-forward still absorbs it into a later overview; the gap is the window between the failure and the next successful archive. - Only the newest terminal archive's checkpoints are restored, so a long User Turn committed partially more than once keeps just its newest compressed prefix. - stats.failedArchives becomes 0/1 ("is the newest terminal a failure") because an exact count needs the full marker scan this change removes. stats.totalArchives stays exact via the directory listing. Memory extraction is unaffected: _scan_archive_states still performs a full scan and remains the source of truth for Phase 2 waiting, the coverage frontier and roll-forward. Marker probing uses exists() rather than read_file exception handling. Tests cover the terminal-stop cost bound, the deviations above, and that raw files stay durable while the read path stops. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. The commit-policy optimization read pending_tokens from the write response, but no real producer supplied it: the REST add_message and batch_add_messages handlers returned only session_id/message_count/added, and LocalClient matched them. Only the LangChain in-memory test double returned the field, so persisted_pending_tokens was always None on real deployments, apply_commit_policy always fell back to get_session, and the fake-based tests reported an optimization that never took effect in production. Return the post-write pending_tokens from both REST handlers and both LocalClient methods. The value is already maintained in O(1) inside _append_messages, so it is exact at the point the write returns, which is what the commit policy needs. Reading it goes through a helper that degrades to 0 when a session object does not expose meta, keeping lightweight and legacy session implementations working. Also update the REST failed-archive context test for the terminal-stop read path, and add coverage that exercises the real endpoints rather than a test double: the write-returned value must be positive, grow across writes, and match what get_session would have reported, which is exactly the round trip this field removes. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
Address review on PR volcengine#3569. The previous commit changed only the synchronous path, so LangGraph callers, which use the async entry points, kept paying for every optimization it claimed: - aassemble still called _aensure_session unconditionally before each read, so an existing session took an extra create_session per turn. - _aget_session_context had no NOT_FOUND branch at all; its correctness relied on that unconditional ensure, so removing the ensure alone would have left recall pointing at a session that was never created. - aget_messages created a session after any exception, while the sync path only did so on NOT_FOUND. A 5xx or a timeout therefore added a create_session call during exactly the incidents when the service was already struggling. Read first and create only on NOT_FOUND, mirroring the sync path, and add the missing ensure branches to _aget_session_context so the first use still materializes an empty session without a second context read. Tests assert that repeated assembles on an existing session issue no create_session, that a missing session issues exactly one, and that a non-NOT_FOUND failure issues none. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Create sessions only after context/history NOT_FOUND (including code-based detection), and prefer batch/add write-returned pending_tokens for commit policy with legacy get_session fallback. Surface pending_tokens on in-memory test client add/batch responses. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Walk archives newest-to-oldest and stop at the first completed/failed terminal: inject overview only for completed; on failed stop without overview and never fall back to older archives. Keep RFC volcengine#3330 coverage/uncovered/checkpoint message assembly; skip unused abstract reads. Lazy-load overview for non-WM completed markers during scan. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
…nfig Address review on PR volcengine#3569. Remove the OPENVIKING_MARKDOWN_APPLY_FAST_WRITE path. It replaced _write_section with a raw viking_fs.write, which silently bypassed _ensure_parent_dirs and _run_with_encrypted_write_lock. Telemetry puts its share of the 16-way Excel batch at roughly 135ms out of a 17.9s total, so the correctness risk was not worth the gain. Section writes go back through write_file; OPENVIKING_MARKDOWN_APPLY_CONCURRENCY is gone with the path it gated, and the profile log stays. The Excel process pool, which accounts for the rest of that speedup, is unchanged. Keep Excel sectioning following parsers.markdown when parsers.excel does not set it. ExcelParser converts to Markdown and delegates sectioning to an inner MarkdownParser, so pointing the registry at the new config section would have silently changed section boundaries, node structure and stable Viking URIs for deployments that had tuned parsers.markdown. Explicit parsers.excel values still win, and the process-pool knobs are never inherited. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Address review on PR volcengine#3569. Archive history grows without bound, but get_session_context walked all of it: one marker read per archive, an overview read per Working-Memory archive, and a .meta.json read per completed archive for checkpoints. The previous commit only moved the overview cut-off, so the message and checkpoint paths still scanned everything and the intended saving did not materialize. Scan newest to oldest and stop at the first terminal marker. Nothing at or older than that terminal is read: overview comes from the terminal archive when it is completed and readable, raw messages come only from newer non-terminal archives, and checkpoints come only from the terminal archive. A session with 100 archives now touches exactly one. Deliberate deviations from RFC volcengine#3330, kept narrow and confined to this read path: - An uncovered failed archive no longer replays its raw messages into get_session_context, dropping the failed-raw term from logical live. The raw file stays durable and Phase 2 roll-forward still absorbs it into a later overview; the gap is the window between the failure and the next successful archive. - Only the newest terminal archive's checkpoints are restored, so a long User Turn committed partially more than once keeps just its newest compressed prefix. - stats.failedArchives becomes 0/1 ("is the newest terminal a failure") because an exact count needs the full marker scan this change removes. stats.totalArchives stays exact via the directory listing. Memory extraction is unaffected: _scan_archive_states still performs a full scan and remains the source of truth for Phase 2 waiting, the coverage frontier and roll-forward. Marker probing uses exists() rather than read_file exception handling. Tests cover the terminal-stop cost bound, the deviations above, and that raw files stay durable while the read path stops. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Address review on PR volcengine#3569. The commit-policy optimization read pending_tokens from the write response, but no real producer supplied it: the REST add_message and batch_add_messages handlers returned only session_id/message_count/added, and LocalClient matched them. Only the LangChain in-memory test double returned the field, so persisted_pending_tokens was always None on real deployments, apply_commit_policy always fell back to get_session, and the fake-based tests reported an optimization that never took effect in production. Return the post-write pending_tokens from both REST handlers and both LocalClient methods. The value is already maintained in O(1) inside _append_messages, so it is exact at the point the write returns, which is what the commit policy needs. Reading it goes through a helper that degrades to 0 when a session object does not expose meta, keeping lightweight and legacy session implementations working. Also update the REST failed-archive context test for the terminal-stop read path, and add coverage that exercises the real endpoints rather than a test double: the write-returned value must be positive, grow across writes, and match what get_session would have reported, which is exactly the round trip this field removes. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Address review on PR volcengine#3569. The previous commit changed only the synchronous path, so LangGraph callers, which use the async entry points, kept paying for every optimization it claimed: - aassemble still called _aensure_session unconditionally before each read, so an existing session took an extra create_session per turn. - _aget_session_context had no NOT_FOUND branch at all; its correctness relied on that unconditional ensure, so removing the ensure alone would have left recall pointing at a session that was never created. - aget_messages created a session after any exception, while the sync path only did so on NOT_FOUND. A 5xx or a timeout therefore added a create_session call during exactly the incidents when the service was already struggling. Read first and create only on NOT_FOUND, mirroring the sync path, and add the missing ensure branches to _aget_session_context so the first use still materializes an empty session without a second context read. Tests assert that repeated assembles on an existing session issue no create_session, that a missing session issues exactly one, and that a non-NOT_FOUND failure issues none. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
Add a `retrieval.enable_intent` config switch, default true, matching current behavior. When it is false, search() skips session.load, get_context_for_search, and IntentAnalyzer, and searches with the raw query on the same path as a no-session search. Session scanning is skipped at all three entry points (local client, REST, MCP) via SearchService.is_intent_enabled, so a disabled intent path does not pay for a session load whose result VikingFS would ignore. Intent analysis fans a single request out into several typed queries, each with its own vector search. On a 10-concurrent search benchmark, turning it off took typed queries per request from 3-5 down to 1. The rerank changes that were originally part of this commit have been dropped: maintainers indicated rerank is likely to be refactored, so those are better submitted separately against the new structure. This commit no longer touches hierarchical_retriever.py, openai_rerank.py, or the rerank config. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
force-pushed
the
perf/ov0616-main-split
branch
from
July 29, 2026 09:25
847e537 to
c0b87a9
Compare
qin-ctx
requested changes
Jul 29, 2026
qin-ctx
left a comment
Collaborator
There was a problem hiding this comment.
本轮基于当前 Head c0b87a9 复查。前一轮提出的 pending_tokens 真实返回契约、LangChain 异步路径一致性和历史 overview O(N) 读取问题已经处理;Excel 对 Markdown 分段配置的默认继承也已补齐。当前仍有 3 个需要合并前解决的问题:最新 terminal 失败时已接受的 overview 与 failed raw 都会从 Session Context 消失;只读取最新 terminal checkpoint 会丢失同一 Turn 的较早压缩前缀;Excel 显式设置为类默认值时会被误判为未配置并被 Markdown 值覆盖。具体触发条件和影响见 inline comments。
huangxun375-stack
pushed a commit
to huangxun375-stack/OpenViking
that referenced
this pull request
Jul 29, 2026
…ault Address review on PR volcengine#3569. `with_sectioning_defaults_from` decided whether a field was unset by comparing it against the class default. That cannot distinguish "the key was absent" from "the user wrote a value that happens to equal the default", so a config like parsers.markdown.max_section_size: 512 parsers.excel.max_section_size: 2048 resolved Excel to 512 — the documented "explicit parsers.excel values always win" contract was false for exactly the case where it matters, and there was no way to express "Markdown at 512, Excel deliberately at 2048". `ExcelConfig.from_dict` now records which keys were present, and inheritance skips those. The field is excluded from equality and repr so two configs with the same values remain equal regardless of how they were built. Two consequences worth noting: - A config built directly, without `from_dict`, carries no key provenance and is treated as fully explicit, so a hand-constructed `ExcelConfig` is never silently rewritten. - `OpenVikingConfig.excel` therefore defaults via `ExcelConfig.from_dict({})` rather than the bare constructor. Without that, a deployment with no `parsers.excel` section at all would have been read as fully explicit and would have stopped following `parsers.markdown` — the exact compatibility case this inheritance exists to preserve. Tests pin the reviewer's case, both full-config paths (absent section inherits, explicit section wins), provenance-independent equality, and the hand-constructed behavior. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
…ault Address review on PR volcengine#3569. `with_sectioning_defaults_from` decided whether a field was unset by comparing it against the class default. That cannot distinguish "the key was absent" from "the user wrote a value that happens to equal the default", so a config like parsers.markdown.max_section_size: 512 parsers.excel.max_section_size: 2048 resolved Excel to 512. The documented "explicit parsers.excel values always win" contract was false for exactly the case where it matters, and there was no way to express "Markdown at 512, Excel deliberately at 2048". `ExcelConfig` now records which keys a config source actually provided, and inheritance skips those. Provenance is a plain instance attribute reached via `with_explicit_keys` / `explicit_keys` rather than a dataclass field, which matters for three reasons found while implementing it: - A dataclass field appears in `dataclasses.asdict()` and `model_dump()` output, and being a frozenset it makes those results non-JSON-serializable for callers. - A dataclass field is a legal config key, so a config file could forge provenance and steer the inheritance decision. - Equality must ignore it, so two configs with the same values stay equal regardless of how they were built. Both parser-config entry points now route through `from_dict` even with no data. `get_parser_config` and `load_parser_configs_from_dict` previously used a bare constructor for an absent section, and `OpenVikingConfig.excel` defaulted the same way; provenance would then be unknown and an absent `parsers.excel` section would stop inheriting from `parsers.markdown` — the compatibility case this inheritance exists to preserve. A config built directly, without `from_dict`, carries no key information and is treated as fully explicit, so a hand-constructed `ExcelConfig` is never silently rewritten. Tests cover the reviewer's case, both full-config paths (absent section inherits, explicit section wins), inheritance through the parser loader, serialization cleanliness, forged-provenance rejection, and preservation across replace/copy/deepcopy. Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部) Co-authored-by: Eurekaxun <eurekaxun@163.com>
huangxun375-stack
force-pushed
the
perf/ov0616-main-split
branch
from
July 29, 2026 13:15
45a63f6 to
4a983d3
Compare
qin-ctx
approved these changes
Jul 30, 2026
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.
Description
This PR reduces event-loop blocking and redundant I/O on hot retrieval, import, LangChain session, and session-context paths under concurrent load. It is rebased onto current main, including RFC #3330 session recovery and #3575 async recording.
The parse, retrieve, and LangChain changes are conservative: their knobs are opt-in or preserve existing behavior when unset. The session-context change is different — it changes default behavior with no opt-out, and deliberately deviates from RFC #3330 in two places. See "Behavioral change: session context assembly" below.
Human Involvement
Related Issue
N/A
Type of Change
Motivation (before → after)
Measured on current
mainvs this branch, same host, same corpus, single round.wait=false, 16 concurrent batch wall timeget_session_contextarchive reads, session with N archives.meta.jsonget_sessioncalls per turn for commit policyReproduction notes:
enable_intent=false. Typed queries per request drop from 3–5 to 1, so this measures the PR plus that config, not a single-variable algorithm comparison. Do not read it as a like-for-like speedup with all features on.excel.enable_process_pool. Without it the two arms are equivalent — our first attempt measured 17.975 s vs 18.531 s because the gate was off, and that run was discarded.88.2s → 11.6s / 4.8s) are not comparable to this table and have been removed to avoid mixing baselines.Enabling the parse optimization
Top-level
excelconfig — note this is notparsers.excel; a wrong key fails startup:Confirm the gate is active in the log before trusting any measurement:
Changes Made
When off, search skips session load, get_context_for_search, and
IntentAnalyzer, and runs the raw query on the same path as a no-session search.
pending_tokensfrom the REST and LocalClient write responses so the above optimization actually applies to real clients.Behavioral change: session context assembly
Why
Archive history grows without bound, but
get_session_contextwalked all of it on every call: one marker read per archive, an overview read per Working-Memory archive, and a.meta.jsonread per completed archive for checkpoints. On long-lived sessions this dominates the call.What changed
The scan goes newest → oldest and stops at the first terminal marker (
.doneor.failed.json). Nothing at or older than that terminal is read..done, overview readable.done, overview missing/unreadable.failed.jsonA session with 100 archives now touches exactly one archive directory.
Deviations from RFC #3330 — please review explicitly
These are intentional and accepted to make the read path independent of history length. We are not claiming RFC parity.
An uncovered
failedarchive no longer replays its raw messages intoget_session_context. RFC [RFC] OpenViking Session 上下文优化:Turn-aware Retention、Pending/Failed 恢复与预算控制 #3330 defineslogical live = uncovered pending raw + uncovered failed raw + root live; this PR drops the failed-raw term. The raw file stays durable, and Phase 2 roll-forward (_prepare_phase2_archive_messages) still replays uncovered failed archives into the next archive's input, so those messages remain reachable and are still compressed into a later overview. The gap is the window between the failure and the next successful archive.Only the newest terminal archive's checkpoints are restored. When one long User Turn is partially committed more than once, earlier disjoint prefixes are no longer merged into the anchor's checkpoint. Restoring them requires reading
.meta.jsonfor an unbounded number of archives.stats.failedArchivesis now 0/1 ("is the newest terminal a failure") rather than a count of uncovered failed archives, because an exact count needs the full marker scan this change removes.stats.totalArchivesstays exact — it comes from the directory listing.We are happy to gate item 1 behind a config flag or adopt a different recovery semantic. A bounded alternative we considered but did not implement: prune using the newest completed archive's
coverage_start_archive, which would keep the common case at O(1) without dropping the failed-raw term. We did not pursue it because it reaches further into RFC #3330 than we were comfortable changing unilaterally. Guidance from the RFC author on items 1 and 2 would help.What is unchanged
_scan_archive_states()still performs a full scan and remains the source of truth for Phase 2 waiting, the coverage frontier, and roll-forward. Memory extraction is unaffected..donecoverage metadata (coverage_start_archive,coverage_end_archive,covered_failed_archives) is written exactly as before.message.id;pre_archive_abstractsstill returns[].Removed since the first review round
The Markdown fast-write path (
OPENVIKING_MARKDOWN_APPLY_FAST_WRITE) has been dropped. It replaced_write_sectionwith a rawviking_fs.write, which silently bypassed_ensure_parent_dirsand_run_with_encrypted_write_lock. That was an oversight during a path substitution, not a decision that the lock was unnecessary. Telemetry puts its share of the 16-way Excel batch at roughly 135 ms out of a 17.9 s total, so the correctness risk was not worth the gain.Section writes now go back through
write_file, andOPENVIKING_MARKDOWN_APPLY_CONCURRENCYis gone with the path it gated.OPENVIKING_MARKDOWN_APPLY_PROFILEremains as a pure logging switch. The Excel process pool, which accounts for the rest of the import speedup, is unchanged. A regression test now asserts section writes do not bypasswrite_file.Testing
Post-rebase on Linux:
tests/integration/langchain_langgraph/test_async_recording.py(from #3575)tests/unit/test_langchain_integration.py+ asynctests/session/test_session_context.pytests/server/test_api_sessions.pytests/parse/test_excel_process_pool.py+test_markdown_apply_layout.pytests/session/test_session_retention_integration.pyThe one failure,
test_stale_worker_uses_lock_snapshot_memory_policy_for_queue_message, reproduces identically without this branch and is unrelated. Full-suite runs oftests/session/andtests/parse/were compared before and after these changes; the sets of failing tests are identical.Notable new coverage:
test_get_session_context_does_not_touch_archives_older_than_terminal— no marker, overview,.meta.json, or messages read may reference an archive older than the terminal.test_write_responses_return_pending_tokens_for_commit_policy— drives the real REST endpoints, and asserts the write-returned value equals whatget_sessionwould report.test_async_assemble_skips_create_for_existing_session/..._creates_session_only_on_not_found/test_async_history_does_not_create_session_on_non_not_found_error— async parity and error semantics.test_registry_resolves_excel_against_markdown— the innerMarkdownParserreally receives inherited sectioning values.test_sections_go_through_write_section_not_raw_write— guards against reintroducing a lock-bypassing write path.test_context_stops_at_newest_terminal_without_replaying_older_failed_raw,test_repeated_partial_commits_restore_only_newest_checkpoint, and durability assertions intest_queue_enqueue_failure_marks_archive_failed_and_keeps_raw_durable/test_phase1_root_rewrite_failure_marks_orphan_archive_failed.Suggested focused re-checks:
addResourcebatch wall time, pool on/off, with the gate log line present.get_session_context→ (create only if NOT_FOUND) →batch_add_messages→ commit, with no extraget_session. Worth checking on both the sync and async entry points..failedyields an empty overview; archives older than the terminal are not read.Checklist
Documentation is not updated yet. New or changed surfaces needing docs:
excel.enable_process_pool/excel.process_pool_workersand their inheritance fromparsers.markdown,retrieval.enable_intent,pending_tokenson write responses, and theget_session_context/latest_archive_overview/stats.failedArchivessemantics above. Happy to add these here or in a follow-up, whichever maintainers prefer.Screenshots (if applicable)
N/A — performance tables above.
Additional Notes
Organizational contribution: Information Technology Department of China Merchants Bank (招商银行信息技术部)
Co-authored-by:
ceppetellilines-dot ningshaopeng1990@cmbchina.com
Eurekaxun eurekaxun@163.com