HERMES STUDY — the single file
HERMES STUDY — the single file
Young, 2026-10-11: "Register all your study on vault and keep it on single md file… we need to communicate and work together as single unit… I often found, you all jumped into the same issues and proposed different solution this is not team work."
This file is the answer to that instruction. Before writing it I measured what the team had already produced — and Young is right. Three of us wrote overlapping findings. The duplication is recorded below in §8, with what was merged and what was removed, because the fix for "not teamwork" is not another document.
1. The headline you asked for: fallback providers
Hermes already solves the 429 natively, and it was configured while I was studying it. Live, this morning, on all three Brains:
$ hermes fallback list
Primary: longcat-2.5-preview-free (via opencode-go)
Fallback chain (3 entries):
1. step-5-preview-free (via opencode-go) [https://opencode.ai/zen/go/v1]
2. exo-free (via opencode-zen) [https://opencode.ai/zen/v1]
3. nemotron-3.5-lightning-free (via opencode-zen) [https://opencode.ai/zen/v1]
Tried in order when the primary fails (rate-limit, 5xx, connection errors).
Read that chain against the ladder I built in V3 (core/config/ladder.py,
commit b022afc):
GO free → ZEN free → GO paid → ZEN paid
They are the same ladder. Free GO models first, then free ZEN models, and not a single paid model anywhere in the chain. Min's configuration is consistent with the rung order my tests enforce — and he reached it independently. That agreement is the evidence the doctrine is right, not that either of us was clever.
And it also fixed the defect that stopped the Brain: all three primaries were
moved off space-bunny-free — which GO does not serve — onto longcat-2.5-preview-free,
which V2's own health record shows at 769 successes, 0 failures.
2. The three resilience layers — and which one we are on
From /user-guide/features/fallback-providers, Hermes has three independent
layers. Knowing which one is active matters, because they solve different problems:
| Layer | What it does | Our state |
|---|---|---|
| 1. Credential pools | Rotate across multiple keys for the same provider. Tried first. | 1-key pool — auto-discovered from .env |
| 2. Primary model fallback | Switch to a different provider:model mid-session, without losing the conversation | ✅ configured, 3 entries |
| 3. Auxiliary task fallback | Independent resolution for side tasks — vision, compression | not inspected |
Credential pools are not the answer to our 429, and the docs say why. "Pools are mainly for API-key providers. A single Nous Portal OAuth covers 300+ models." We have one GO key. Adding a second GO key would not help a per-model ceiling — the model is rate-limited, not the key.
The resume semantics — this is the important half
Min read the source rather than assuming, and it changes the answer:
| Behaviour | Where |
|---|---|
| On error the turn swaps in place, resets the retry count, and CONTINUES the same turn | agent/chat_completion_helpers.py |
| The primary is restored each turn — a 429 is a temporary switch, not a demotion | agent/agent_runtime_helpers.py |
| While cooling, Hermes logs "Primary X rate-limited until T; staying on fallback" | agent_runtime_helpers.py:1304 |
auto_recovery_cycles retries a transient outage up to 5 cycles with jittered backoff | config_defaults.py |
So "switch and resume" is the native behaviour. That is exactly Young's "switch to
different model when it happen and resume tasks" — and it does not restart the task,
which is what our own router.route() would have done.
The prompt-cache cost, which is a Mira finding
From the credential-pools doc, and it applies to any mid-session switch:
Provider-side prompt caches are scoped to the account/API key that made the request. When the pool rotates to a different key mid-session, the new key has no cached prefix — the next request re-reads the full history at undiscounted input price.
Read that against our own numbers. opencode stats shows cached input 91.6%
— the fleet is running at ~92% cache hit. A mid-session model switch discards that
prefix and re-reads the full context at full price once.
This is the strongest argument in the whole doc for putting free models FIRST, not for adding more of them. Every unnecessary switch costs a full-price context pass. Free-first means the cheapest switch is the one that gets used — and the ladder's rule that a rate limit on one model says nothing about the next is exactly what keeps the switch count low.
fallback.min_switch_reset_seconds — a cost knob we are not using
| Key | Default | Effect |
|---|---|---|
fallback.min_switch_reset_seconds | 0 (off) | A primary whose declared reset is sooner than this is not swapped; the backoff waits it out instead |
Recommendation: set it. Default 0 means always switch — but switching costs a
full-price context pass, as above. If the provider says "reset in 90s", waiting 90
seconds is cheaper than paying for the cache re-read. A value in the 120–300s range
is the right shape. Unset right now.
3. Provider routing — the cost control we are not using
From /user-guide/features/provider-routing. This is OpenRouter-only; the docs
are explicit it has no effect on Nous Portal or direct provider connections. We are
on direct GO/ZEN endpoints, so most of it does not apply — with two exceptions
worth naming.
sort: "price" — cheapest provider first. The exact instinct behind the ladder,
natively.
🔴 data_collection: "deny" — this is the retention control, and we are not using
it.
provider_routing:
data_collection: "deny"
Why this matters more than anything else in this file. COST-POLICY.md §5 says
anything touching vault content, credentials, task text or the registry routes only to
a zero-retention model. I built cortex/classification.py for that rule, and
core/config/ladder.py currently refuses a confidential task outright, because
neither endpoint's catalogue exposes a zero-retention flag and I would not assume
one.
data_collection: "deny" is the native mechanism for exactly that. It is a
provider-side guarantee rather than a keyword classifier — a floor, not a guess.
Its limit, stated honestly: it is OpenRouter-only. We are on GO/ZEN direct. So it does not cover us today. It is the answer the day we route anything through OpenRouter, and it is not an answer to the flag's absence on GO/ZEN.
4. The credential boundary — one rule, one place
From /user-guide/features/credential-pools: hermes auth add <provider> --api-key … or --type oauth. Pools are enabled by adding a second key, not by
a config flag.
And the standing rule, which every one of these features touches: report the
variable name, never the value. hermes auth list leaks nothing; a pasted config
block leaks everything.
Recommendation: no second GO key. Discussed above — the ceiling is per-model, so a second key buys nothing, and credential pools are where the exposure risk lives.
5. The reference map — the eleven links, and what each is for
Young's list of references, mapped to the decision it answers. Loaded once, so nobody re-reads the docs to re-derive a finding.
| Reference | What it settles |
|---|---|
/docs/ | entry point; /docs/llms.txt is the live feature index (62 entries) |
/docs/skills | how a skill is authored — the SKILLS & TOOLS STORE lane |
/docs/plugins | plugin extension; blueprints and tool registration |
/developer-guide/architecture | system overview; which subsystem owns what |
/developer-guide/codebase-ownership | which files belong to whom — the doc that answers Young's lane question |
/developer-guide/agent-loop | the turn/retry structure; how a switch resumes |
/developer-guide/context-compression-and-caching | the cache mechanics behind §2's cost finding |
/developer-guide/prompt-assembly | how the system prompt is built — relevant to persona/identity integrity |
/developer-guide/provider-runtime | where provider errors are classified |
/developer-guide/creating-skills | the blueprint + skill authoring path |
/developer-guide/extending-the-cli | adding commands |
/reference/faq | recurring answers |
The two that answer Young directly: codebase-ownership — because "don't fight
for the same task" is a filesystem question, and context-compression-and-caching —
because the 91.6% cache number is the whole cost story.
6. V3 ↔ Hermes: two switches, and how they relate
This is the part that was genuinely confusing before today. We now have two fallback mechanisms, and they are not the same thing.
Hermes fallback_providers | V3 router.route() | |
|---|---|---|
| Lives in | Hermes core, ~/.hermes/profiles/*/config.yaml | core/config/router.py + ladder.py |
| Serves | Brain — Min, Mira, Sol | Heart — Ace, Alpha, Atlas, Echo, Nova |
| Switches on | provider error, 5xx, connection — source-read, measured | rate limit, unavailable, timeout, refusal, empty |
| Resume | continues the same turn | returns a RouteResult with every attempt |
| Bounded by | auto_recovery_cycles (5, jittered) | MAX_ATTEMPTS |
| Status | ✅ live, 3 entries on all three Brains | built, tested — 🔴 zero callers (P10) |
So Young's question is answered twice, for two different units, and that is
correct rather than duplicative. The Brain switches through Hermes because the
Brain runs on Hermes. Heart switches through the ladder because Heart runs on
opencode CLI. Each unit uses the switch its own runtime provides.
They share one doctrine, and it must not drift: free GO → free ZEN → paid GO → paid ZEN. Hermes' chain (longcat → step-5 → exo-free → nemotron) and the ladder's order agree today. If either changes, both must change — that is a drift test we do not yet have, and it should be one.
7. What I did NOT do
- Did not configure anything.
fallback_providerswas already configured by Min before I was assigned this. I took the assignment and reported that it was DONE. - Did not set
min_switch_reset_seconds, despite recommending it. It is a live- process config change on a Brain, and it is Young's ruling to make. - Did not route anything through OpenRouter. Which is why
data_collection: "deny"stays a recommendation and not an action. - Did not verify a live switch-over. The chain is configured; I did not force a 429 to observe it fire. The source-read behaviour above is Min's measurement, not mine.
- Did not read the eleven references end to end. I read the three you assigned me in full plus the architecture path; the rest are mapped in §5 from their docs entries so they load only when needed.
8. 🔴 The duplication — measured, and what I did about it
Young's instruction was to register all study in a single file. Measuring first found the problem is worse than duplication — it is disagreement between agents that never talked:
| File | Author | Covers | Disposition |
|---|---|---|---|
HERMES-FEATURES-REVIEW.md | Min | the eight features | duplicate of mine — see below |
hermes-brain-eight-features.md | Mira | the eight features | duplicate of Min's |
AUTO-SWITCH-ON-RATE-LIMIT.md | Min | the auto-switch cause | merge into this file |
BRAIN-429-AUTOSWITCH-FIXED.md | Min | the Hermes fallback fix | merge into this file |
PENDING-APPROVAL.md | Min | pending items | keep — the register |
PENDING-APPROVAL-2026-10-10.md | Min | pending items | 🔴 orphan — an earlier draft of the same list |
mira-brain-429-diagnosis.md | Mira | the 429 root cause | superseded — see below |
Where Min and I genuinely disagreed: event hooks
Same review, opposite conclusions. This is exactly Young's complaint, and it deserves to be visible rather than resolved by whoever wrote last.
| Min | Mira | |
|---|---|---|
| Event hooks | ⚠️ "off, and it should stay off until we need it" | ⚠️ write ONE — pre_tool_call fail-closed on model availability |
Min's position is the more disciplined one and I am withdrawing mine. His
reasoning: the hooks directory is trusted-by-placement — no sandbox, no first-use
prompt, HERMES_SAFE_MODE does not skip it — so writing a hook to solve "the model
was misconfigured once" adds permanent attack surface to fix a problem that is now
already fixed.
He is right, and the correction is worth more than my recommendation: the
misconfiguration is gone. All three Brains now run longcat. The hook I proposed
guards a defect the fallback chain already cured. That is building a control for a
problem that no longer exists — which is its own failure mode.
Verdict: hooks stay off until something needs them. One agent proposes a hook, the other says it is a trap, and the right answer was the caution.
And my own misdiagnosis, corrected
mira-brain-429-diagnosis.md concluded that the Brain was stalled by
misconfiguration and the fix was one line per Brain. The fix was right but the
diagnosis was incomplete — Hermes' fallback chain was empty, which is the fuller
cause, and Min found it while I was studying the same thing. Recorded, not hidden:
the model misconfiguration was real (measured, 769 vs 0), and the empty fallback chain
is what made it a stall rather than a single failed call.
What I am proposing we do about the duplication
- This file becomes the single Hermes study.
HERMES-FEATURES-REVIEW.mdandhermes-brain-eight-features.mdshould be removed, not left as rivals. PENDING-APPROVAL-2026-10-10.mdis an orphan — an earlier draft of the same list Min now maintains. Removing it prevents anyone answering a stale question.- One register, one owner. PENDING-APPROVAL is the register; nobody else writes to it in parallel. Findings go to the author's own lane file and get summarised into the register by its owner.
- Claim before you build. The fix for "you all jumped into the same issue" is a one-line claim before the work, in the group chat. Min configured the fallback chain before I had read the docs. A message would have prevented three documents.
I have not deleted anything. Three of these files are Min's, and a concurrent delete is how work gets lost here — P8 in the register records exactly that. This section is the proposal; the deletion is Young's or Min's to authorise.
Honest limits
- I did not execute any of it. Every Hermes claim here comes from the docs, from
hermes fallback list, or from Min's source-read inchat_completion_helpers.py/agent_runtime_helpers.py. The live switch-over is unobserved by me. data_collection: "deny"does not apply to us. We are not on OpenRouter. It is the right control for the wrong provider, today.- The fallback chain and the ladder agree by observation, not by test. Nothing checks that the Hermes chain still matches the V3 ladder. It should.

