Why restarting your gateway pays for the same cache twice


One gateway, three profiles: yours, one for a team workflow, one bot in a group chat. You ask the bot a question in the group, open your own session a second later and ask the same thing, and both are billed as first contacts. The cache write lines in the bill look unremarkable, yet the first message after every gateway restart is slow in a way that suggests the model has never seen your system prompt.

The rule is simple: the provider only reuses a byte-identical prefix. Change one character in it and everything after is recomputed. Hermes already splits its system prompt into stable, context and volatile tiers exactly for this reason. Three fixes merged on 2026-09-28 chased one shared failure mode: something had moved into the stable tier that shouldn’t be there, or something that should have been inherited wasn’t.

1. A line in the stable tier that differed per profile

In agent/system_prompt.py the system prompt is assembled as stable, context and volatile blocks. Tool schemas and the stable system prompt live in stable — those bytes have to stay identical no matter what, or none of the conversation history behind them can be reused.

The problem was the Active Hermes profile: ... line. Its whole job is to tell the model which directories it reads and writes, so its text contains the profile’s home path. It sat in the stable tier — which meant that every different profile, every different machine, every session with a different home produced a different stable prefix. A multiplexed gateway serving N profiles paid N cache writes instead of sharing one entry.

That line now rides in the volatile tier, after the other volatile content. The result is a stable prefix that is byte-identical across profiles and homes. The new test in the repository says it plainly: test_stable_tier_is_identical_across_homes.

The production change is five added lines and two removed. The benchmark shipped with the same PR shows what that is worth: 16 tasks (9 standard, 7 hard), 2 models × 2 repetitions, every request routed through the same inference endpoint, and a deterministic hidden grader per task.

Configuration (n=32 each) Score Tokens/run $/run Requests/run
reference CLI agent A, opus-5.5 1.000 146k 0.158 5.4
Hermes before, opus-5.5 0.969 155k 0.245 6.8
Hermes after, opus-5.5 1.000 152k 0.173 6.1
reference CLI agent B, gpt-5.6-terra 0.995 145k 0.123 9.4
Hermes before, gpt-5.6-terra 0.968 121k 0.110 7.5
Hermes after, gpt-5.6-terra 1.000 115k 0.108 7.1

Just the cache side: before, the first request of an Opus run read 0 cached tokens and wrote about 20k. After, it reads 13.5k, and cache writes per run fall from 27.3k to 15.7k.

One honest caveat so you don’t over-expect: a single-profile install with a fixed home already reused this prefix across sessions, so you will not feel this there. The win is on multi-profile and multiplexed hosts, and in benchmarks like the one above where every run gets a fresh home.

2. After a restart, the first internal event flipped the prompt A → B → A

The second fix is about the first breath after a restart.

The first thing to arrive after a gateway restart is usually not a human message but an internal event — a kanban wake, a delegation completing, a startup resume. Those turns used to render one system prompt, then the next human turn rendered the original one again: A → B → A. The cost was a prompt-cache miss plus an agent rebuild and a full re-prefill of the conversation.

Internal turns now reuse the system-prompt bytes of the last human turn. Mechanically it is a pin snapshot:

  • the routing entry (state.db gateway_routing plus a sessions.json mirror) stores it;
  • it is written before the agent runs on each human turn and read back on the first internal turn after a restart;
  • reads and writes are fenced on the session id;
  • /resume and CLI handoff clear the pin — a switched session must not inherit old bytes.

There is one privacy detail worth calling out on its own: the pin records which privacy.redact_pii policy it was rendered under, and bytes rendered under a different policy are never reused. Otherwise a single restart could send content that should have been redacted in its unredacted form. That covers internal events after a restart and, since the same bypass existed there too, internal events inside one process.

3. The half-sentence a /steer leaves behind dropped the channel prompt

The third one happens after you correct a run with /steer, or interrupt a generation.

The text you appended — or the interruption itself — often becomes the next turn. That turn used to start with channel_prompt = None, which removed the Telegram group identity line, your YAML channel_prompts, and the Slack identity from the system prompt. The prompt changed, so the cached agent was rebuilt and the whole conversation re-prefilled. Then your next human message flipped it back and you paid for the same thing twice.

The effective fix is one assignment: when there is no pending event, the follow-up inherits the channel prompt of the turn it follows (_run_agent_queued_followup in gateway/run_turn.py). Event-backed follow-ups are untouched — they still resolve their own prompt.

What this means for you

  • Multi-profile hosts: N profiles now share one stable-prefix cache entry instead of writing N of them. There is nothing to configure; update and you have it.
  • The first message after a restart: no longer a cold start. It hits the cache it should hit.
  • After an interruption in a group chat: no more “fixed one sentence, recomputed the whole conversation” on the invoice.

To see the tiers and their sizes yourself, run hermes prompt-size — it prints a byte breakdown of the system prompt plus tool schemas. The multi-profile setup itself is covered in our multiplexing and profiles guide; if you are tuning routing, the auxiliary model lane guide explains which calls write cache entries of their own. All three fixes need the new code, so run hermes update (the upgrade walkthrough). And since it’s the same week: here is how MoA avoids breaking this cache — the Mixture of Agents guide.