From 21dedb85867722c03a68fa7b25f20957a9852a78 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 16 Jul 2026 05:39:33 -0700 Subject: [PATCH] fix(insights): include auxiliary usage in overview token totals (#65603) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overview's total_input/output/cache token counts summed only the sessions counters (main-loop usage), while the per-model breakdown already included auxiliary usage rows (task dimension from #65537) and reconciled residuals. Result: hermes insights top-line totals undercounted aux spend (compression summarizer, vision, titles) and disagreed with the per-model table below them — the symptom reported in #58592 and requested in #9979. When the per-model breakdown is available, derive the overview token totals from it (same pattern total_cost already used). Verified no double-count across incremental CLI deltas, gateway absolute overwrites, and aux rows. --- agent/insights.py | 12 +++++ .../hermes_state/test_aux_usage_accounting.py | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/agent/insights.py b/agent/insights.py index 6adba7011..086150c27 100644 --- a/agent/insights.py +++ b/agent/insights.py @@ -439,6 +439,18 @@ class InsightsEngine: if models: total_cost = sum(float(m.get("cost") or 0.0) for m in models) + # Token totals likewise: the per-model breakdown includes + # auxiliary usage rows (vision/compression/titles — task + # dimension in session_model_usage, #23270) plus reconciled + # residuals, while the sessions counters carry main-loop usage + # only. Summing the breakdown keeps overview totals consistent + # with the per-model table and stops `hermes insights` + # undercounting aux spend (#58592, #9979). + total_input = sum(int(m.get("input_tokens") or 0) for m in models) + total_output = sum(int(m.get("output_tokens") or 0) for m in models) + total_cache_read = sum(int(m.get("cache_read_tokens") or 0) for m in models) + total_cache_write = sum(int(m.get("cache_write_tokens") or 0) for m in models) + total_tokens = total_input + total_output + total_cache_read + total_cache_write # Session duration stats (guard against negative durations from clock drift) durations = [] diff --git a/tests/hermes_state/test_aux_usage_accounting.py b/tests/hermes_state/test_aux_usage_accounting.py index 1797e1a46..493effad8 100644 --- a/tests/hermes_state/test_aux_usage_accounting.py +++ b/tests/hermes_state/test_aux_usage_accounting.py @@ -340,3 +340,49 @@ class TestAnalyticsAuxRows: tasks = _aux_task_summary(aux) assert {t["task"] for t in tasks} == {"vision", "compression"} + + +class TestInsightsAuxTotals: + def test_overview_totals_include_aux_usage(self, db): + """`hermes insights` overview must count aux tokens, not just the + sessions counters (issues #58592, #9979).""" + from agent.insights import InsightsEngine + + db.create_session("s1", source="cli") + db.update_token_counts( + "s1", input_tokens=1000, output_tokens=100, + model="main-model", billing_provider="nous", api_call_count=1, + ) + db.record_auxiliary_usage( + "s1", "compression", model="glm-5", + billing_provider="openrouter", input_tokens=5000, output_tokens=500, + ) + report = InsightsEngine(db).generate(days=30) + ov = report["overview"] + assert ov["total_input_tokens"] == 6000 + assert ov["total_output_tokens"] == 600 + models = {m["model"] for m in report["models"]} + assert {"main-model", "glm-5"} <= models + + def test_overview_totals_not_double_counted_with_absolute_updates(self, db): + """Gateway absolute overwrites + aux rows must not inflate totals.""" + from agent.insights import InsightsEngine + + db.create_session("s2", source="telegram") + db.update_token_counts( + "s2", input_tokens=2000, output_tokens=200, + model="main-model", billing_provider="nous", api_call_count=1, + ) + db.update_token_counts( + "s2", input_tokens=2000, output_tokens=200, + model="main-model", billing_provider="nous", + absolute=True, api_call_count=1, + ) + db.record_auxiliary_usage( + "s2", "title_generation", model="main-model", + billing_provider="nous", input_tokens=40, output_tokens=8, + ) + report = InsightsEngine(db).generate(days=30) + ov = report["overview"] + assert ov["total_input_tokens"] == 2040 + assert ov["total_output_tokens"] == 208