Skip to content

Commit cb4773a

Browse files
committed
fix(session): wire auto-commit threshold into add_messages
_auto_commit_threshold (default 8000) and the pending_tokens signal were both maintained by _append_messages but never connected — the threshold param was dead code, assigned at __init__ and never read. Only the offline ingest poller (maybe_commit_on_threshold in replay.py) committed on threshold. In-process sessions (HTTP/SDK add_messages path used by the CC plugin and HTTP clients) never auto-committed, so real-time sessions accumulated messages without extraction — a root cause of the low session→memory conversion rate. Add _maybe_auto_commit() called at the end of add_messages: when pending_tokens >= threshold, fire commit_async(keep_recent_count) as a background task. Fire-and-forget keeps add_messages non-blocking; commit_async's path-lock + empty-messages early-return make a concurrent plugin-initiated commit a safe no-op. threshold <= 0 disables; no-op without a running event loop (sync CLI callers). Adds 5 unit tests (tests/unit/session/test_session_autocommit.py) covering fire/skip/disable/forward/boundary via Session.__new__ — no VikingFS/vectordb dependency.
1 parent fdcfa0e commit cb4773a

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

openviking/session/session.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,40 @@ def add_messages(
956956
all_messages.append(msg)
957957

958958
self._append_messages(all_messages)
959+
self._maybe_auto_commit()
959960
return all_messages
960961

962+
def _maybe_auto_commit(self) -> None:
963+
"""Trigger a background commit when accumulated pending tokens cross the
964+
auto-commit threshold.
965+
966+
The threshold (``_auto_commit_threshold``) and the ``pending_tokens``
967+
signal were both maintained by ``_append_messages`` but never connected,
968+
so in-process sessions (HTTP/SDK ``add_messages`` path) never
969+
auto-committed — only the offline ingest poller
970+
(``maybe_commit_on_threshold``) did. This left real-time sessions
971+
accumulating without extraction, the root cause of the low
972+
session→memory conversion rate.
973+
974+
Fire-and-forget: archive (Phase 1) and extraction (Phase 2) both run
975+
in the background so ``add_messages`` stays non-blocking.
976+
``commit_async``'s path-lock + empty-messages early-return make a
977+
concurrent plugin-initiated commit a safe no-op. Threshold <= 0
978+
disables. No-op when no running event loop (sync CLI callers).
979+
"""
980+
threshold = int(self._auto_commit_threshold or 0)
981+
if threshold <= 0:
982+
return
983+
if int(self._meta.pending_tokens or 0) < threshold:
984+
return
985+
try:
986+
loop = asyncio.get_running_loop()
987+
except RuntimeError:
988+
return # No running loop (sync CLI path) — skip, plugin/HTTP will commit.
989+
loop.create_task(
990+
self.commit_async(keep_recent_count=int(self._meta.keep_recent_count or 0))
991+
)
992+
961993
def add_message(
962994
self,
963995
role: str,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
2+
# SPDX-License-Identifier: AGPL-3.0
3+
4+
"""Unit tests for threshold-driven auto-commit on Session.add_messages.
5+
6+
The auto-commit threshold (_auto_commit_threshold) and the pending_tokens
7+
signal were both maintained by _append_messages but never connected, so
8+
in-process sessions (HTTP/SDK add_messages path) never auto-committed —
9+
only the offline ingest poller (maybe_commit_on_threshold) did. This left
10+
real-time sessions accumulating without extraction, the root cause of the
11+
low session→memory conversion rate. _maybe_auto_commit wires them.
12+
13+
These target the _maybe_auto_commit wiring on Session via __new__ to avoid
14+
the heavy VikingFS/vectordb __init__ path — no running server needed.
15+
"""
16+
17+
import asyncio
18+
from unittest.mock import AsyncMock
19+
20+
import pytest
21+
22+
from openviking.session.session import Session, SessionMeta
23+
24+
25+
def _bare_session() -> Session:
26+
"""A Session with only the fields _maybe_auto_commit reads."""
27+
s = Session.__new__(Session)
28+
s._auto_commit_threshold = 0
29+
s._meta = SessionMeta(
30+
session_id="test",
31+
created_at=0,
32+
updated_at=0,
33+
created_by_account_id="",
34+
created_by_user_id="",
35+
message_count=0,
36+
commit_count=0,
37+
)
38+
s.commit_async = AsyncMock(return_value={"status": "accepted"})
39+
return s
40+
41+
42+
class TestAutoCommit:
43+
"""Threshold-driven auto-commit wiring."""
44+
45+
async def test_auto_commit_fires_when_threshold_crossed(self):
46+
"""pending_tokens >= threshold schedules a background commit_async."""
47+
s = _bare_session()
48+
s._auto_commit_threshold = 100
49+
s._meta.pending_tokens = 150 # over threshold
50+
51+
s._maybe_auto_commit()
52+
await asyncio.sleep(0) # let the fire-and-forget task run
53+
54+
assert s.commit_async.called
55+
56+
async def test_auto_commit_skipped_below_threshold(self):
57+
"""Below threshold, _maybe_auto_commit must not schedule a commit."""
58+
s = _bare_session()
59+
s._auto_commit_threshold = 999_999
60+
s._meta.pending_tokens = 10
61+
62+
s._maybe_auto_commit()
63+
await asyncio.sleep(0)
64+
65+
assert not s.commit_async.called
66+
67+
async def test_auto_commit_disabled_when_threshold_zero(self):
68+
"""threshold <= 0 disables auto-commit entirely."""
69+
s = _bare_session()
70+
s._auto_commit_threshold = 0
71+
s._meta.pending_tokens = 10_000
72+
73+
s._maybe_auto_commit()
74+
await asyncio.sleep(0)
75+
76+
assert not s.commit_async.called
77+
78+
async def test_auto_commit_forwards_keep_recent_count(self):
79+
"""keep_recent_count from session meta is forwarded to commit_async."""
80+
s = _bare_session()
81+
s._auto_commit_threshold = 1
82+
s._meta.pending_tokens = 10
83+
s._meta.keep_recent_count = 7
84+
85+
s._maybe_auto_commit()
86+
await asyncio.sleep(0)
87+
88+
assert s.commit_async.call_args.kwargs["keep_recent_count"] == 7
89+
90+
async def test_auto_commit_equal_threshold_fires(self):
91+
"""Boundary: pending_tokens == threshold should fire (>=)."""
92+
s = _bare_session()
93+
s._auto_commit_threshold = 50
94+
s._meta.pending_tokens = 50 # exactly at threshold
95+
96+
s._maybe_auto_commit()
97+
await asyncio.sleep(0)
98+
99+
assert s.commit_async.called

0 commit comments

Comments
 (0)