Configuring Hermes Coach

Coach settings: the shared and per-mode models, session budgets and price fallbacks, project and global guidance files, secret scope, and where session state lives on disk.

Where settings live

Settings are flat snake_case keys under plugins.entries.coach.settings in config.yaml. Dots are reserved — the host reads a dotted key as a path, so active.enabled would nest instead of naming one setting.

There are 40 keys. Defaults live only in coach/settings.py, and a checker asserts the Desktop page has a control for every key — that is the drift guard that replaced a manifest schema.

Bad input degrades instead of raising: booleans, integers, floats and choices fall back per value, _as_float rejects NaN and infinity, and minimums are applied after validation (ledger_max_tokens ≥ 500, max_reply_tokens ≥ 80, call caps ≥ 1, worker_timeout_s ≥ 5, budgets ≥ 0).

The Desktop page has six tabs: Models, Reviews, Delivery, Budget, Guidance, Status. Eight tuning constants stay in config.yaml only: worker_timeout_s, ledger_max_tool_result_chars, ledger_max_recent_turns, ledger_note_max_chars, ledger_max_tokens, skip_same_model, allow_in_subagents, debug_log_decisions.

Models

One shared model serves every mode unless a mode has its own. model_for(mode) returns the override or the shared choice, so “inherit” needs no special case.

  • The coach refuses to run on the primary model; same_as_primary compares model and provider case-insensitively.
  • Passing a per-mode provider and model requires the model-override permission: Grant on the Status tab, or /coach grant. /coach revoke clears it.
  • /coach model shows the current choice; /coach model <mode> targets one mode.
  • An empty model picker means the host returned no providers — authenticate one in Hermes first.

Reviews and timing

The Active review card decides whether a review runs and when. Its description in the Desktop page is the short version: “Reviews finished turns, or steers the next one when timing is before. Pre-turn adds latency and is off unless chosen.”

ControlSettingValues
Review every turnactive_enabledOff by default; a cheap model keeps it close to free
When to reviewactive_timingAfter the turn, before the turn, or both
Which turnsactive_cadenceTurns that used tools, every turn, or every N
Turns per reviewactive_every_nShown when the cadence is every_n; default 3

Pre-turn guidance adds one call before the agent works and is bounded twice: the request is capped at ten seconds or worker_timeout_s when that is lower, and every failure path returns without advice rather than holding the turn. It is skipped when the session is paused or over budget, and every_n counts guided turns rather than reviewed ones, so the first guided turn of a session is always due.

With both, the pre-turn call steers the turn and the post-turn call still reviews the finished work; the guidance is recorded on the turn’s provenance, so the review knows what the agent was told. review_enabled, review_authority and review_max_holds belong to the completion gate on the Reviews tab, not to Active.

Budget and spend

budget_session_usd defaults to 1.00, and 0 means no budget. Spend is measured from real usage returned by the host, priced per call and checked before every automatic request.

A soft stop disables Active, Review and Decide but leaves explicit consults working; hard stops every coach call. That distinction is why reviews can stop on their own while /coach still answers.

Some provider routes return amount_usd=None. Those calls are estimated from token counts at fallback_price_per_mtok and the review log row is marked cost_estimated: true — without the fallback the call would cost 0.0 and the budget could never fire. Set it to 0 to leave unpriced calls free.

/coach status prints the model, modes with their cadence and timing, spend and pending notes for the session.

Guidance files

Guidance is how you tell the coach what your project cares about — review criteria, house rules, things it should stop flagging.

ScopeFileApplied
project (also local, cwd)<project>/COACH.mdThat project only; committed with the repo
global (also all, empty)$HERMES_HOME/coach/guidance.mdEvery project

Both are read capped at 4000 characters and wrapped in <coach-guidance>. Opening and closing tags inside the files are stripped first, case-insensitively, so a repository cannot close the block and become free-standing instructions.

Only a missing file reads as empty. A permission or I/O error propagates, and the block degrades to no guidance rather than failing the turn.

Guidance is on by default and can be switched off entirely with guidance_enabled; the toggle sits on the Guidance tab.

Secrets

Only the optional Decide backends need credentials. Both keys are declared in plugin.yaml under requires_env and are offered as masked prompts at install.

  • OPENROUTER_API_KEY for the default jev-openrouter backend.
  • TYPESAFE_API_KEY for jev-direct.
  • Set them in ~/.hermes/.env. Keys are read through the host’s profile-scoped secret scope, so one profile’s key never leaks into another profile’s turn; hosts that predate that seam fall back to the process environment.
  • /coach set refuses to write a key into config.yaml, and the dashboard rejects the key by name. No code path reads a credential file directly.
  • Under a multiplexing host with no profile scope bound, the lookup returns nothing instead of guessing — the key reads as unset rather than as another profile’s.

State on disk

State lives in $HERMES_HOME/coach/: one JSON file per session, a JSONL review log per session, and the global guidance file. Sessions are pruned to the newest twenty.

  • Writes go to a same-directory temp file with mode 0600, then os.replace, so a crash mid-write never leaves a half-parsed state.
  • Session file names are percent-encoded with an 8-character hash suffix, because session ids contain / and can differ only by case on some filesystems.
  • A corrupt file becomes fresh state with last_error="state reset". An unreadable one becomes fresh state naming the error, rather than pretending the history never existed.
  • A file that fails to parse is renamed to <file>.corrupt-<timestamp> rather than deleted, so an incident stays diagnosable; quarantined files older than thirty days are swept.
  • The log keeps the newest 200 records; /coach log prints them and /coach log <n> full prints long fields whole.