Skip to content

feat(status): rich /status snapshots across CLI and gateway - #8355

Open
malaiwah wants to merge 2 commits into
NousResearch:mainfrom
malaiwah:feat/rich-status-snapshots
Open

feat(status): rich /status snapshots across CLI and gateway#8355
malaiwah wants to merge 2 commits into
NousResearch:mainfrom
malaiwah:feat/rich-status-snapshots

Conversation

@malaiwah

@malaiwah malaiwah commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces the basic /status output with a comprehensive session snapshot showing model, provider, token breakdown, cost, context window, and more.

  • Token breakdown: input/output/cache read/cache write/reasoning with totals
  • Cost display: estimated or actual cost with human-readable labels
  • Context window: live usage from context_compressor when agent is running; idle fallback via get_model_context_length when agent is idle (inspired by feat(status): show model, provider, context, and cumulative tokens #4678)
  • Cache hit rate: percentage of cache reads vs total input
  • Compression tracking: compaction count from live agent OR persisted compression_count for idle sessions — idle sessions now show compression history instead of omitting it
  • Platform categorization: separates chat platforms from services (API, webhook)
  • Queue depth: pending messages and follow-up items
  • Model override awareness: shows session-level model overrides
  • Transport mode: webhook vs polling for Telegram

Fixes #5960 (Tokens: 0 regression — reads from SessionDB instead of stale SessionStore).
Closes #7317 (context/compression state in gateway-visible status).
Closes #7714 (usage display like OpenClaw).
Supersedes #4678 (model + context in /status — this PR is a strict superset).
Supersedes #5989 (get_session_token_totals — included here plus much more).

Changes

File Action
hermes_cli/status_format.py New — 6 shared formatting helpers (no duplication between CLI and gateway)
hermes_state.py Edit — add get_session_token_totals() and get_session_last_active()
gateway/run.py Edit — add _build_status_snapshot(), _get_status_platform_sections(), _get_status_queue_depth(); replace _handle_status_command(); persist compression_count after each turn
gateway/session.py Edit — add compression_count field to SessionEntry, wire through to_dict/from_dict/update_session
run_agent.py Edit — return compression_count from run_conversation() result dict
cli.py Edit — add _build_cli_status_snapshot(), _get_cli_status_queue_depth(); replace _show_session_status()
tests/hermes_cli/test_status_format.py New — 27 unit tests for shared helpers
tests/gateway/test_status_command.py Rewrite — 13 tests covering live agent, idle fallback with persisted compression, model override, platform split, queue depth
tests/cli/test_cli_status_command.py Rewrite — 3 tests covering live agent, persisted session fallback, pending title

Example output

Gateway (Telegram/Discord) — live agent:

⚕ Hermes Agent v0.8.0
Model: `anthropic/claude-sonnet-4` · Provider: anthropic
Usage: 25,000 in · 610 out · 50,730 total · Cost: $0.1234 est.
Cache: 25,000 read · 0 write · 50% hit · 120 reasoning
Context: 28,000 / 200,000 (14%) · Compactions: 1
Session: `agent:main:telegram:dm:12345` · updated 3m ago
ID: `20260412_104559_e1760b66`
Runtime: Chat Completions · Reasoning high · Transport webhook
Queue: depth 0 · State: running
Chats: telegram, discord

Gateway — idle session (compression history persisted):

⚕ Hermes Agent v0.8.0
Model: `openai/gpt-4o` · Provider: openai
Usage: 100 in · 200 out · 3,210 total · Cost: $1.2345 est.
Cache: 10 read · 5 write · 9% hit
Context: 45,000 / 200,000 (22%) · Compactions: 3
Session: `agent:main:telegram:dm:12345` · updated 7m ago
ID: `20260412_104559_e1760b66`
Runtime: Reasoning medium
Queue: depth 0 · State: idle
Chats: telegram

Design decisions

  • Shared helpers, not duplicated methods: hermes_cli/status_format.py holds all formatting logic. Both CLI and gateway import from it.
  • Graceful degradation: Every data source is wrapped in try/except with safe defaults. Missing SessionDB, missing agent, missing cache — all handled.
  • Idle context + compression fallback: When no live agent exists, uses get_model_context_length() + session_entry.last_prompt_tokens for context, and session_entry.compression_count for compaction history. Both are persisted via update_session() after each turn.
  • Backward-compatible persistence: compression_count defaults to 0 in SessionEntry.from_dict(), so existing sessions.json files load cleanly.
  • Backward-compatible attributes: Uses getattr() with defaults for optional runner attributes (_pending_hidden_turns, _agent_cache, etc.).

Test plan

  • pytest tests/cli/test_cli_status_command.py tests/gateway/test_status_command.py tests/hermes_cli/test_status_format.py — 42 tests pass
  • Live-tested on downstream fork with 59 regression tests passing
  • Idle compression count verified via test with compression_count=3 on SessionEntry

🤖 Generated with Claude Code

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the comprehensive status-snapshot work. The token regression and the gateway model/provider/context portion have since landed on main, but compression history and the broader snapshot remain useful.

Problems

  • Current /status now lives in gateway/slash_commands.py:493, not gateway/run.py; it uses awaited AsyncSessionDB reads at gateway/slash_commands.py:533-537. The branch therefore needs a targeted port rather than a direct application.
  • Expose context/compression state in gateway-visible status command #7317 requests a last-compression time, but this diff persists only compression_count; no timestamp is stored or rendered.
  • Main localizes status fields through t(...) at gateway/slash_commands.py:608-644; the new literal English output would bypass that surface.

Suggested changes

  • Salvage the remaining fields into the current handler, preserve async DB access and localization, and add current-harness coverage for live and idle compression metadata.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
Retargeted from gateway/run.py to current code paths on main HEAD 91937a6.
- Added hermes_cli/status_format.py with shared snapshot formatting helpers
- Added compression_count + last_compressed_at to SessionEntry (session.py)
- Added get_session_token_totals() and get_session_last_compressed() to hermes_state.py
- Wired compression_count + last_compressed_at into agent_result dicts (gateway/run.py)
- Added last_compressed_at timestamp to context_compressor.py (satisfies NousResearch#7317)
- Added localization strings for rich snapshot fields (locales/en.yaml)
- Preserved async DB access and t(...) localization conventions
@malaiwah
malaiwah force-pushed the feat/rich-status-snapshots branch from 8636758 to 31c1fc5 Compare August 4, 2026 02:54
@malaiwah

malaiwah commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Retargeted to current main HEAD

The original branch targeted the pre-refactor gateway/run.py /status handler. The handler and surrounding code have since been refactored with async AsyncSessionDB reads and t(...) localization. I've rebased onto current main HEAD (91937a6dc):

New file — hermes_cli/status_format.py:

  • Shared snapshot formatting helpers (format_api_mode_label, format_reasoning_effort_label, format_status_relative_time, safe_status_float, safe_status_int)

SessionEntry changes (gateway/session.py):

  • Added compression_count: int and last_compressed_at: float fields with persistence in to_dict/from_dict/update_session

Database layer (hermes_state.py):

  • Added get_session_token_totals() and get_session_last_compressed() query methods

Agent result wiring (gateway/run.py):

  • Wired compression_count and last_compressed_at into both agent result dicts (lines ~5488 and ~5628)

Compression timestamp (agent/context_compressor.py):

Localization (locales/en.yaml):

  • Added strings for all new snapshot fields, preserving the t(...) localization convention

Ready for re-review.

The retargeted /status snapshot (PR NousResearch#8355) added 19 new keys to
locales/en.yaml under gateway.status but did not add them to the other
16 locale files. This caused test_catalog_keys_match_english and
test_catalog_placeholders_match_english to fail for all non-English
locales in CI (slice 3/8).

This commit copies the English strings as fallback values into all 16
locale files. Translators can update these at their leisure — the i18n
test only verifies key presence and placeholder parity, not translation
quality.

Closes the CI failure on slice 3/8.
@malaiwah

malaiwah commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

CI fix pushed

Slice 3/8 was failing on tests/agent/test_i18n.py — the retarget added 19 new keys to locales/en.yaml under gateway.status but didn't propagate them to the other 16 locale files (ar, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, hu).

Both failing tests enforce locale parity:

  • test_catalog_keys_match_english — every locale must have the same keys as en.yaml
  • test_catalog_placeholders_match_english — every value must use the same {placeholder} tokens

Fix: Copied the 19 English strings as fallback values into all 16 locale files (fix(i18n): add rich status snapshot keys to all 16 locale files). Standard i18n practice — untranslated keys fall back to English; translators can update at their leisure. The i18n test only verifies key presence and placeholder parity, not translation quality.

Verified locally: All 36 i18n tests pass (36 passed in 2.63s).

CI should go green on the next run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

3 participants