fix(gateway): /status command always shows 0 tokens#11088
Conversation
SessionEntry.total_tokens was initialized to 0 in the constructor but never updated during the session lifecycle. The agent tracks cumulative tokens on agent.session_total_tokens, but that value was never written back to the session store. Changes: - session.py: update_session() accepts optional total_tokens parameter - run.py: pipe agent.session_total_tokens through result dict into update_session() after each agent run (both success and error paths) - test_status_command.py: updated assertion for new call signature
There was a problem hiding this comment.
Pull request overview
Fixes the gateway /status command reporting Tokens: 0 by persisting the agent’s cumulative session token count into SessionEntry.total_tokens.
Changes:
- Extend
SessionStore.update_session()to optionally persisttotal_tokens. - Pipe
agent.session_total_tokensinto the gateway’s session-store update path after agent runs. - Update
/status-related test to match the newupdate_session()call signature.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
gateway/session.py |
Adds optional total_tokens persistence to session metadata updates. |
gateway/run.py |
Passes session total token count through agent results into update_session(). |
tests/gateway/test_status_command.py |
Updates assertion to include the new total_tokens kwarg. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self.session_store.update_session( | ||
| session_entry.session_key, | ||
| last_prompt_tokens=agent_result.get("last_prompt_tokens", 0), | ||
| total_tokens=agent_result.get("session_total_tokens", 0), |
There was a problem hiding this comment.
agent_result.get("session_total_tokens", 0) will write 0 into the session store whenever the key is missing (e.g., proxy-mode results and early error returns that don't include session_total_tokens). That can clobber a previously accumulated SessionEntry.total_tokens and reintroduce the “Tokens: 0” issue after any non-token-reporting run. Prefer passing None when the key is absent (or conditionally include the kwarg only when present) so update_session() leaves the stored value unchanged.
| total_tokens=agent_result.get("session_total_tokens", 0), | |
| total_tokens=agent_result.get("session_total_tokens"), |
| runner.session_store.update_session.assert_called_once_with( | ||
| session_entry.session_key, | ||
| last_prompt_tokens=80, | ||
| total_tokens=0, | ||
| ) |
There was a problem hiding this comment.
This test currently expects total_tokens=0 even though the mocked _run_agent() result does not include session_total_tokens. That assertion will still pass if the implementation mistakenly defaults missing totals to 0 (which can overwrite real stored totals). Update the mock result to include a non-zero session_total_tokens and assert it’s passed through (or, if missing is valid, assert update_session() is called without total_tokens).
| # Token counts and model are now persisted by the agent directly. | ||
| # Keep only last_prompt_tokens here for context-window tracking and | ||
| # compression decisions. | ||
| self.session_store.update_session( | ||
| session_entry.session_key, | ||
| last_prompt_tokens=agent_result.get("last_prompt_tokens", 0), | ||
| total_tokens=agent_result.get("session_total_tokens", 0), |
There was a problem hiding this comment.
The comment says “Keep only last_prompt_tokens here…”, but this block now also persists total_tokens. Update the comment to reflect the new behavior so future readers don’t assume total_tokens is intentionally omitted.
Code Review — PR #11088Verdict: Changes Requested (1 critical bug) 🔴 Critical: Key name mismatch — success path always returns 0**** — The success path reads the wrong key from the agent result dict: total_tokens=agent_result.get("session_total_tokens", 0), # ← WRONG KEYBut "total_tokens": self.session_total_tokens,So Fix: Change the key to total_tokens=agent_result.get("total_tokens", 0),The error/fallback paths at lines 8723, 8739, 8829 are correct — they read directly from the agent object via
|
| self.session_store.update_session( | ||
| session_entry.session_key, | ||
| last_prompt_tokens=agent_result.get("last_prompt_tokens", 0), | ||
| total_tokens=agent_result.get("session_total_tokens", 0), |
There was a problem hiding this comment.
🔴 Critical: wrong key name. agent.run_conversation() returns total_tokens (run_agent.py:10721), not session_total_tokens. This will always resolve to 0. Change to agent_result.get("total_tokens", 0).
zeapsu
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Changes Requested (1 critical bug)
🔴 Critical
- gateway/run.py:4071 — Key name mismatch: reads
session_total_tokensfrom agent_result, butrun_conversation()returnstotal_tokens. This means the success path always resolves to 0.
💡 Suggestion
- Consider also propagating
input_tokensandoutput_tokensto the session store for future/statusenrichment.
✅ Looks Good
- Clean
update_session()signature extension insession.py - Both error and success return paths in
run_sync()are covered - Test updated to match new signature
|
Correction to my review — I was wrong about the key name mismatch. 🙏 I was comparing against which returns , but in comes from , which wraps the result through . The inner function constructs its own return dict with the key (line 8829). So is correct. The PR LGTM — withdrawing my request for changes. |
zeapsu
left a comment
There was a problem hiding this comment.
Correction: I was wrong about the key name mismatch. agent_result comes from _run_agent(), not run_conversation(). The run_sync() inner function constructs its own dict with key session_total_tokens (line 8829). So agent_result.get("session_total_tokens", 0) is correct. LGTM!
Summary
Fixes #11087
SessionEntry.total_tokenswas initialized to 0 but never written to. The agent tracks cumulative tokens onagent.session_total_tokens, but nobody copies that back to the session store.Changes
gateway/session.py:update_session()accepts optionaltotal_tokensparametergateway/run.py: Pipesagent.session_total_tokensthrough the result dict intoupdate_session()after each agent run (both success and error return paths inrun_sync())tests/gateway/test_status_command.py: Updated assertion for new call signatureTest Plan
pytest tests/gateway/test_status_command.py -v # 4 passedVerified manually on Discord —
/statusnow shows cumulative session tokens after gateway restart.