Skip to content

Commit 7abc9ce

Browse files
JezzaHehnteknium1
andcommitted
fix(gateway): read /status token totals from SessionDB (NousResearch#17158)
/status was reading session_entry.total_tokens from the in-memory SessionStore (gateway/session.py), which the agent never writes to — so the token count was always 0. The agent already persists token deltas to the SQLite SessionDB (run_agent.py:11497) for every platform with a session_id. Route /status through that single source of truth instead of duplicating token writes into a second store. Fix: - gateway/run.py: _handle_status_command now calls self._session_db.get_session(session_id) and sums the five token component columns (input/output/cache_read/cache_write/reasoning). Falls back to 0 when no SessionDB is configured or no row exists. - Two new regression tests covering the populated-row and missing-row paths. Co-authored-by: Hermes <127238744+teknium1@users.noreply.github.com>
1 parent a178081 commit 7abc9ce

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

gateway/run.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6568,11 +6568,30 @@ async def _handle_status_command(self, event: MessageEvent) -> str:
65686568
queue_depth = self._queue_depth(session_key, adapter=adapter)
65696569

65706570
title = None
6571+
# Pull token totals from the SQLite session DB rather than the
6572+
# in-memory SessionStore. The agent's per-turn token deltas are
6573+
# persisted into sessions_db (run_agent.py), not into SessionEntry,
6574+
# so session_entry.total_tokens is always 0. SessionDB is the
6575+
# single source of truth; reading it here keeps /status accurate
6576+
# without duplicating token writes into two stores.
6577+
db_total_tokens = 0
65716578
if self._session_db:
65726579
try:
65736580
title = self._session_db.get_session_title(session_entry.session_id)
65746581
except Exception:
65756582
title = None
6583+
try:
6584+
row = self._session_db.get_session(session_entry.session_id)
6585+
if row:
6586+
db_total_tokens = (
6587+
(row.get("input_tokens") or 0)
6588+
+ (row.get("output_tokens") or 0)
6589+
+ (row.get("cache_read_tokens") or 0)
6590+
+ (row.get("cache_write_tokens") or 0)
6591+
+ (row.get("reasoning_tokens") or 0)
6592+
)
6593+
except Exception:
6594+
db_total_tokens = 0
65766595

65776596
lines = [
65786597
"📊 **Hermes Gateway Status**",
@@ -6584,7 +6603,7 @@ async def _handle_status_command(self, event: MessageEvent) -> str:
65846603
lines.extend([
65856604
f"**Created:** {session_entry.created_at.strftime('%Y-%m-%d %H:%M')}",
65866605
f"**Last Activity:** {session_entry.updated_at.strftime('%Y-%m-%d %H:%M')}",
6587-
f"**Tokens:** {session_entry.total_tokens:,}",
6606+
f"**Tokens:** {db_total_tokens:,}",
65886607
f"**Agent Running:** {'Yes ⚡' if is_running else 'No'}",
65896608
])
65906609
if queue_depth:

tests/gateway/test_status_command.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def _make_runner(session_entry: SessionEntry, *, platform: Platform = Platform.T
5555
runner._pending_approvals = {}
5656
runner._session_db = MagicMock()
5757
runner._session_db.get_session_title.return_value = None
58+
# Default: no DB row → /status reports 0 tokens. Tests that exercise
59+
# the populated path override this.
60+
runner._session_db.get_session.return_value = None
5861
runner._reasoning_config = None
5962
runner._provider_routing = {}
6063
runner._fallback_model = None
@@ -80,6 +83,14 @@ async def test_status_command_reports_running_agent_without_interrupt(monkeypatc
8083
total_tokens=321,
8184
)
8285
runner = _make_runner(session_entry)
86+
# Token total comes from the SQLite SessionDB, not SessionEntry.
87+
runner._session_db.get_session.return_value = {
88+
"input_tokens": 200,
89+
"output_tokens": 121,
90+
"cache_read_tokens": 0,
91+
"cache_write_tokens": 0,
92+
"reasoning_tokens": 0,
93+
}
8394
running_agent = MagicMock()
8495
runner._running_agents[build_session_key(_make_source())] = running_agent
8596

@@ -113,6 +124,56 @@ async def test_status_command_includes_session_title_when_present():
113124
assert "**Title:** My titled session" in result
114125

115126

127+
@pytest.mark.asyncio
128+
async def test_status_command_reads_token_totals_from_session_db():
129+
"""Regression test for #17158: /status must source token totals from the
130+
SQLite SessionDB (where run_agent.py persists them) and sum all component
131+
counts, not from SessionEntry (which the agent never writes)."""
132+
session_entry = SessionEntry(
133+
session_key=build_session_key(_make_source()),
134+
session_id="sess-1",
135+
created_at=datetime.now(),
136+
updated_at=datetime.now(),
137+
platform=Platform.TELEGRAM,
138+
chat_type="dm",
139+
total_tokens=0, # SessionEntry never gets written to — always 0.
140+
)
141+
runner = _make_runner(session_entry)
142+
runner._session_db.get_session.return_value = {
143+
"input_tokens": 1000,
144+
"output_tokens": 250,
145+
"cache_read_tokens": 500,
146+
"cache_write_tokens": 100,
147+
"reasoning_tokens": 50,
148+
}
149+
150+
result = await runner._handle_message(_make_event("/status"))
151+
152+
# 1000 + 250 + 500 + 100 + 50 = 1,900
153+
assert "**Tokens:** 1,900" in result
154+
155+
156+
@pytest.mark.asyncio
157+
async def test_status_command_tokens_zero_when_session_db_row_missing():
158+
"""When the SessionDB has no row for the current session yet (fresh
159+
session, no agent calls), /status reports 0 without raising."""
160+
session_entry = SessionEntry(
161+
session_key=build_session_key(_make_source()),
162+
session_id="sess-1",
163+
created_at=datetime.now(),
164+
updated_at=datetime.now(),
165+
platform=Platform.TELEGRAM,
166+
chat_type="dm",
167+
total_tokens=999, # This should be ignored.
168+
)
169+
runner = _make_runner(session_entry)
170+
runner._session_db.get_session.return_value = None
171+
172+
result = await runner._handle_message(_make_event("/status"))
173+
174+
assert "**Tokens:** 0" in result
175+
176+
116177
@pytest.mark.asyncio
117178
async def test_agents_command_reports_active_agents_and_processes(monkeypatch):
118179
session_key = build_session_key(_make_source())

0 commit comments

Comments
 (0)