Skip to content

feat(providers): add Hermes Agent provider#544

Merged
iamtoruk merged 2 commits into
mainfrom
feat/hermes-provider-rebased
Jun 21, 2026
Merged

feat(providers): add Hermes Agent provider#544
iamtoruk merged 2 commits into
mainfrom
feat/hermes-provider-rebased

Conversation

@iamtoruk

Copy link
Copy Markdown
Member

Summary

Adds a Hermes Agent provider that reads session-level usage from Hermes' SQLite store (~/.hermes/state.db and per-profile ~/.hermes/profiles/<name>/state.db): tokens (input, output, cache read/write, reasoning), cost, models, tools, and shell commands.

Rebased from #442 by @anthonyarmijo onto current main (the original was 87 commits behind and conflicting). Supersedes #442. Closes #368. PR #469 (a JSON-snapshot reader) was closed in favor of this SQLite approach.

Why SQLite, verified empirically

We installed Hermes and ran real sessions to confirm the storage model:

  • ~/.hermes/state.db is canonical. Per-session session_*.json snapshots are gated behind sessions.write_json_snapshots (false by default), so on a standard install that directory has no session files. Hermes' own docs say "state.db is canonical."
  • The sessions table carries input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, estimated_cost_usd, actual_cost_usd, billing_provider, billing_mode, cost_status, and cwd, and they are populated on real runs.
  • started_at / ended_at are Unix epoch-seconds floats.

Polish applied on top of #442 (commit e80da31)

A five-agent adversarial validation pass (correctness, break-it, data-integrity, testing, consistency) ran against the real schema. It surfaced one ship-blocker, now fixed, plus the items below.

  • Project grouping now reads the populated sessions.cwd column, falling back to scraping a "Current working directory:" line from the transcript (older builds), then the profile name. The current Hermes build does not write that line into messages, so the previous text-only approach grouped everything under the profile name.
  • costIsEstimated is set when the cost is the LiteLLM fallback (Hermes recorded none).
  • Blocker fix: SQLITE_BUSY from the discovery and read paths is now re-thrown so a transient lock on the live, actively-written state.db is retried by the cache layer instead of being swallowed and cached as an empty result, which would silently drop a session's tokens and cost.

Cost model

Priority is recorded actual_cost_usd, then recorded estimated_cost_usd, then a LiteLLM estimate from the session token totals. Subscription-billed sessions (e.g. Codex via Hermes) record no cost, so they fall to the LiteLLM estimate, which is consistent with how CodeBurn prices Claude Code and Codex subscriptions; plan and proxy-path handle subscription coverage at a higher level. Note: costIsEstimated is set but, like several existing providers (warp, kiro, codex, grok), has no downstream consumer yet, so it is not user-visible.

Validation

  • Full suite: 1287 passed, tsc --noEmit clean.
  • Tests added: cwd-column grouping, the estimated-cost flag, and tool-result tool_name extraction; fixtures updated to include the real cwd column.

Deferred decisions (validators upheld these)

  • Subscription cost is not zeroed (kept the LiteLLM estimate for consistency).
  • Archived sessions are not filtered (the spend still happened).
  • parent_session_id is not special-cased. It is unproven whether Hermes rolls delegated child token usage into the parent row, and none of the verified sessions used delegate_task. If a parent includes child tokens, counting both would double-count. Worth verifying with one delegate session before relying on it.

Follow-ups (non-blocking)

  • Trim dead column reads in both SELECTs.
  • Decide whether to surface api_call_count (today the dashboard "API calls" equals session count) or document it.
  • Confirm whether input_tokens is cache-inclusive; if so, subtract cache-read before pricing the estimate.
  • Wire costIsEstimated through to a consumer (cross-provider cleanup), or drop it.

anthonyarmijo and others added 2 commits June 21, 2026 23:29
Reads session-level usage from Hermes SQLite state databases
(~/.hermes/state.db and per-profile state.dbs). Tracks input,
output, cache read/write, reasoning tokens, stored costs, tools,
shell commands, and inferred projects.

- Provider reads both root and profile state databases
- Cost fallback: actual_cost_usd > estimated_cost_usd > LiteLLM pricing
- inferProject filters to user/system messages only
- Discovery query capped at LIMIT 10000
- sanitizeProject handles repeated leading separators
- All five review items from PR #386 addressed
- Tested against real Hermes data (557MB state.db with 198 sessions)

Closes #368. Supersedes #386.
…BUSY

- Read the populated sessions.cwd column for project grouping; fall back to
  scraping the transcript, then the profile name. The current Hermes build no
  longer writes "Current working directory:" lines into messages.
- Set costIsEstimated when the figure is the LiteLLM fallback (no recorded cost).
- Re-throw SQLITE_BUSY from the discovery and read paths so a transient lock on
  the live state.db is retried instead of being cached as an empty result.
- Tests: add the cwd column to fixtures; cover cwd grouping, the estimated flag,
  and tool-result tool_name extraction.
@iamtoruk iamtoruk merged commit d68e8ec into main Jun 21, 2026
3 checks passed
@iamtoruk iamtoruk deleted the feat/hermes-provider-rebased branch June 21, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support for Hermes-agent CLI

2 participants