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_primarycompares 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 revokeclears it. /coach modelshows 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.”
| Control | Setting | Values |
|---|---|---|
| Review every turn | active_enabled | Off by default; a cheap model keeps it close to free |
| When to review | active_timing | After the turn, before the turn, or both |
| Which turns | active_cadence | Turns that used tools, every turn, or every N |
| Turns per review | active_every_n | Shown 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.
| Scope | File | Applied |
|---|---|---|
project (also local, cwd) | <project>/COACH.md | That project only; committed with the repo |
global (also all, empty) | $HERMES_HOME/coach/guidance.md | Every 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_KEYfor the defaultjev-openrouterbackend.TYPESAFE_API_KEYforjev-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 setrefuses to write a key intoconfig.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, thenos.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 logprints them and/coach log <n> fullprints long fields whole.