hermes - ✅(Solved) Fix [Feature]: Session JSON file (_save_session_log) missing token usage counters [3 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#20253Fetched 2026-05-06 06:37:49
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Timeline (top)
cross-referenced ×3labeled ×3commented ×1

Root Cause

Token usage data is accessible via the SQLite state.db — specifically the sessions table columns input_tokens, output_tokens, cache_read_tokens, etc. The /usage and /insights commands work correctly because they query this database.

Fix Action

Workaround

Token usage data is accessible via the SQLite state.db — specifically the sessions table columns input_tokens, output_tokens, cache_read_tokens, etc. The /usage and /insights commands work correctly because they query this database.

PR fix notes

PR #20267: feat(session-log): mirror token usage and cost counters into session JSON (#20253)

Description (problem / solution / changelog)

Summary

`_save_session_log` only emitted message history and metadata, not the cumulative token / cost counters Hermes already tracks in-memory and persists to the SQLite `sessions` table. Anyone reading the raw session JSON for auditing, custom analytics, or post-mortem inspection had to cross-reference `state.db` to recover figures that were one `json.dumps` field away from being self-contained.

Fix

Add the existing per-session counters into the entry dict that `atomic_json_write` writes out:

New JSON fieldSource attribute
`input_tokens``self.session_input_tokens`
`output_tokens``self.session_output_tokens`
`cache_read_tokens``self.session_cache_read_tokens`
`cache_write_tokens``self.session_cache_write_tokens`
`reasoning_tokens``self.session_reasoning_tokens`
`total_tokens``self.session_total_tokens`
`api_call_count``self.session_api_calls`
`estimated_cost_usd``self.session_estimated_cost_usd`
`cost_status``self.session_cost_status`
`cost_source``self.session_cost_source`

The values come straight from instance state populated by `reset_session_state` (`run_agent.py` L2076–L2088) and accumulated per API call in the agent loop (L11050–L11077), so this is purely a serialise-side change with no behavioural risk. Fresh sessions (no API calls yet) emit zero-valued counters so downstream readers can rely on the shape without checking presence.

Test plan

  • `tests/run_agent/test_run_agent.py::TestSaveSessionLogAtomicWrite` — 3 tests pass (1 existing + 2 new):
    • `test_payload_includes_token_usage_counters` — populated counters propagate through.
    • `test_payload_includes_zeroed_counters_for_fresh_session` — fresh sessions emit zeros, defaults match `reset_session_state`.
  • Adjacent token-tracking tests (`test_context_token_tracking.py`, `test_session_reset_fix.py`) — 9 tests pass.

Fixes #20253

Changed files

  • run_agent.py (modified, +16/-0)
  • tests/run_agent/test_run_agent.py (modified, +60/-0)

PR #20345: feat(session): add token usage counters to _save_session_log JSON output

Description (problem / solution / changelog)

Summary

Fixes #20253.

The session JSON file produced by _save_session_log() now includes all cumulative token counters that are already tracked in-memory and persisted to SQLite, making post-mortem inspection and auditing possible without cross-referencing the database.

Changes

run_agent.py — Added 12 token fields to the entry dict in _save_session_log():

  • input_tokens, output_tokens, total_tokens
  • prompt_tokens, completion_tokens
  • cache_read_tokens, cache_write_tokens
  • reasoning_tokens
  • api_call_count
  • estimated_cost_usd, cost_status, cost_source

Verification

All attributes are initialized at lines ~2073–2084 and accumulated per API call at lines ~11591–11620. The fix simply serializes values already maintained by update_token_counts() into the JSON output.

Changed files

  • hermes_state.py (modified, +16/-0)
  • run_agent.py (modified, +13/-0)
  • tests/hermes_state/test_resolve_resume_session_id.py (modified, +26/-0)

PR #20522: feat: include token usage counters in session JSON log

Description (problem / solution / changelog)

Summary

The session JSON file produced by _save_session_log() did not include token usage counters even though these values are tracked in-memory and persisted to the SQLite state.db via update_token_counts(). This made it impossible to see token usage from the raw session JSON without querying the database.

Changes

Added the following fields to the JSON entry written by _save_session_log() in run_agent.py:

  • input_tokens — total input tokens consumed
  • output_tokens — total output tokens generated
  • cache_read_tokens — tokens read from cache
  • cache_write_tokens — tokens written to cache
  • reasoning_tokens — tokens used for reasoning (if applicable)
  • prompt_tokens — total prompt tokens
  • api_call_count — number of API calls made in the session
  • estimated_cost_usd — estimated cost in USD
  • cost_status — cost tracking status

These are the same counter attributes already included in the conversation result dict returned by run_conversation().

Testing

  • python3 -m py_compile run_agent.py — passes
  • pytest tests/run_agent/test_run_agent.py::TestSaveSessionLogAtomicWrite — passes

Closes #20253

Changed files

  • run_agent.py (modified, +9/-0)

Code Example

entry = {
    "session_id": self.session_id,
    "model": self.model,
    "base_url": self.base_url,
    "platform": self.platform,
    "session_start": self.session_start.isoformat(),
    "last_updated": datetime.now().isoformat(),
    "system_prompt": self._cached_system_prompt or "",
    "tools": self.tools or [],
    "message_count": len(cleaned),
    "messages": cleaned,
}

---

entry = {
    "session_id": self.session_id,
    "model": self.model,
    "base_url": self.base_url,
    "platform": self.platform,
    "session_start": self.session_start.isoformat(),
    "last_updated": datetime.now().isoformat(),
    "system_prompt": self._cached_system_prompt or "",
    "tools": self.tools or [],
    "message_count": len(cleaned),
    "input_tokens": self.session_input_tokens,
    "output_tokens": self.session_output_tokens,
    "cache_read_tokens": self.session_cache_read_tokens,
    "cache_write_tokens": self.session_cache_write_tokens,
    "reasoning_tokens": self.session_reasoning_tokens,
    "api_call_count": self.session_api_calls,
    "estimated_cost_usd": self.session_estimated_cost_usd,
    "cost_status": self.session_cost_status,
    "cost_source": self.session_cost_source,
    "messages": cleaned,
}
RAW_BUFFERClick to expand / collapse

Problem

The session JSON file produced by _save_session_log() does not include token usage counters (input_tokens, output_tokens, cache_read_tokens, reasoning_tokens, estimated_cost_usd, etc.), even though these values are tracked in-memory and persisted to the SQLite state.db via update_token_counts().

This means anyone reading the raw session JSON (e.g. for auditing, custom analytics, or post-mortem inspection) cannot see how many tokens were consumed without cross-referencing the SQLite database.

Proposed Solution

Include the cumulative token counters in the entry dict inside _save_session_log() in run_agent.py.

The counters are already available as instance attributes:

  • self.session_prompt_tokens
  • self.session_completion_tokens
  • self.session_total_tokens
  • self.session_input_tokens
  • self.session_output_tokens
  • self.session_cache_read_tokens
  • self.session_cache_write_tokens
  • self.session_reasoning_tokens
  • self.session_estimated_cost_usd
  • self.session_cost_status
  • self.session_cost_source
  • self.session_api_calls

These are initialized at lines 2088–2096 and accumulated per API call at lines 11676–11684.

Current Behavior

_save_session_log() (line 4274) builds the session JSON entry without any token fields:

entry = {
    "session_id": self.session_id,
    "model": self.model,
    "base_url": self.base_url,
    "platform": self.platform,
    "session_start": self.session_start.isoformat(),
    "last_updated": datetime.now().isoformat(),
    "system_prompt": self._cached_system_prompt or "",
    "tools": self.tools or [],
    "message_count": len(cleaned),
    "messages": cleaned,
}

Expected Behavior

The JSON entry should include something like:

entry = {
    "session_id": self.session_id,
    "model": self.model,
    "base_url": self.base_url,
    "platform": self.platform,
    "session_start": self.session_start.isoformat(),
    "last_updated": datetime.now().isoformat(),
    "system_prompt": self._cached_system_prompt or "",
    "tools": self.tools or [],
    "message_count": len(cleaned),
    "input_tokens": self.session_input_tokens,
    "output_tokens": self.session_output_tokens,
    "cache_read_tokens": self.session_cache_read_tokens,
    "cache_write_tokens": self.session_cache_write_tokens,
    "reasoning_tokens": self.session_reasoning_tokens,
    "api_call_count": self.session_api_calls,
    "estimated_cost_usd": self.session_estimated_cost_usd,
    "cost_status": self.session_cost_status,
    "cost_source": self.session_cost_source,
    "messages": cleaned,
}

Workaround

Token usage data is accessible via the SQLite state.db — specifically the sessions table columns input_tokens, output_tokens, cache_read_tokens, etc. The /usage and /insights commands work correctly because they query this database.

Affected Component

  • Agent Core (conversation loop, context compression, memory)

Feature Type

  • Performance / reliability

Scope

  • Small (single file, < 50 lines)

extent analysis

TL;DR

Update the _save_session_log() function in run_agent.py to include token usage counters in the session JSON entry.

Guidance

  • Modify the entry dictionary in _save_session_log() to include the token counters, such as self.session_input_tokens, self.session_output_tokens, and self.session_estimated_cost_usd.
  • Verify that the updated entry dictionary includes the expected token fields by checking the resulting session JSON file.
  • Review the proposed solution and expected behavior sections for the exact fields and formatting required.
  • Test the updated code to ensure that the token usage data is correctly written to the session JSON file.

Example

entry = {
    # ... existing fields ...
    "input_tokens": self.session_input_tokens,
    "output_tokens": self.session_output_tokens,
    "cache_read_tokens": self.session_cache_read_tokens,
    "cache_write_tokens": self.session_cache_write_tokens,
    "reasoning_tokens": self.session_reasoning_tokens,
    "api_call_count": self.session_api_calls,
    "estimated_cost_usd": self.session_estimated_cost_usd,
    "cost_status": self.session_cost_status,
    "cost_source": self.session_cost_source,
    # ... existing fields ...
}

Notes

The proposed solution suggests updating a single file, run_agent.py, with less than 50 lines of changes, making this a relatively small and targeted fix.

Recommendation

Apply the proposed workaround by updating the _save_session_log() function to include the token usage counters in the session JSON entry, as this will provide the necessary data for auditing, custom analytics, and post-mortem inspection.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING