Du zahlst schon fürs Denken, das du nicht brauchst: Reasoning-Aufwand in jedem Hermes-Modell-Picker


Es ist 23:30 Uhr, und du bittest Hermes, formatTs in utils/date.ts in formatTimestamp umzubenennen. Es ist in sieben Sekunden fertig – und der Trace rechnet ein paar tausend Denk-Tokens mit ultra Reasoning-Aufwand ab. Das Modell ist nicht das Problem. „Ultra“ ist ein globaler Default, den du letzte Woche für ein Architektur-Review gesetzt hast, und seitdem hat ihn niemand angefasst. Hermes ließ dich schon immer den Reasoning-Aufwand einstellen; die Regler waren nur über /reasoning, config.yaml und ein Desktop-Untermenü verstreut, das nur beim Hovern erschien. Zu wissen, dass das Feature existiert, und es tatsächlich zu nutzen, waren zwei verschiedene Personengruppen.

Das hat sich mit PR #110346 geändert: Jeder Modell-Picker fragt jetzt nach dem Effort-Level, direkt nachdem du das Modell gewählt hast. Dieser Leitfaden geht die sechs Orte durch, an denen du den Aufwand setzen kannst (Einmal-Lauf, Session, das /model-Flag, die interaktiven Picker, hermes model und Desktop), plus die Rangfolge zwischen ihnen – damit du für das Denken bezahlst, das eine Aufgabe braucht, statt für das Denken, das du letzte Woche konfiguriert hast.

Was der Reasoning-Aufwand tatsächlich steuert

Der Reasoning-Aufwand ist das Denk-Budget, das ein Modell verbrauchen darf, bevor es antwortet. Bei Modellen, die den Regler anbieten, wird das Level in die Thinking-/Reasoning-Parameter der Anfrage übersetzt; bei Modellen, die das nicht tun, ist die Einstellung schlicht wirkungslos.

Es gibt genau eine Quelle der Wahrheit für die Leiter, in hermes_constants.py:

VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")

Sieben Stufen, von niedrig nach hoch. parse_reasoning_effort() verwandelt ein eingetipptes Wort in ein Config-Dict: Ein erkanntes Level wird zu {"enabled": True, "effort": "<level>"}, ein leerer String oder ein unbekanntes Wort liefert None (was „den Provider-Default verwenden“ bedeutet), und none ist ein expliziter Aus-Schalter – reasoning_effort: false und none bedeuten beide „nicht denken“.

Der wichtige Teil: Das gilt pro Anfrage. Beim selben Modell ist der Qualitätsunterschied zwischen low und ultra meist weit kleiner als der Latenz- und Kostenunterschied. Die eigentliche Frage ist also nie „welches Level ist das beste“ – sondern „wie viele Level ist diese Aufgabe wert“.

Geltungsbereich Wie du es setzt Wo es landet
Nur ein Lauf hermes --reasoning low -z "..." dieser Prozess, nichts wird geschrieben
Aktuelle Session (klassische CLI) /reasoning high Session-Override
Aktuelle Session, mit Modellwechsel /model X --reasoning high Session-Pin
Dein globaler Default /reasoning high --global agent.reasoning_effort
Jedes Modell, das du nennst agent.reasoning_overrides pro Modell, schlägt den globalen Default
Eine Hilfsaufgabe hermes model → Configure auxiliary models… auxiliary.vision.reasoning_effort
Jeder Subagent delegation.reasoning_effort der Delegation-Block
Ein Desktop-Chat die Reasoning-Pille im Composer diese Session

Der kleinste Hebel: Einmal-Läufe

Wenn du einen einzelnen Befehl für eine gut verstandene Aufgabe ausführst, musst du die Config gar nicht anfassen. Das Top-Level-Flag --reasoning existiert genau dafür:

# Einmalig: eine Umbenennung. Low reicht völlig.
hermes --reasoning low -z "rename formatTs to formatTimestamp in utils/date.ts"

# Interaktiv, aber diese Session räumt unordentliche Minuten auf
hermes chat --reasoning high

Der Hilfetext sagt explizit, dass dies den effektiven Aufwand nur für diesen Lauf überschreibt; das persistente Level lebt weiterhin in config.yaml (oder pro Modell unter agent.reasoning_overrides). Für Shell-Skripte, CI-Jobs und Batch-Dateiarbeit ist das die sauberste Option: Der Aufwand reist mit dem Befehl, statt still zum Default von morgen zu werden.

Sessions: /reasoning und /model --reasoning

In der klassischen CLI ist /reasoning der dedizierte Befehl – er deckt sowohl das Effort-Level als auch die Darstellung des Reasonings ab:

/reasoning high           # nur diese Session
/reasoning high --global  # schreibt agent.reasoning_effort für zukünftige Läufe
/reasoning                # ohne Argument: interaktiv auswählen
/reasoning show           # den Reasoning-Stream ein-/ausblenden (full gibt alles aus)
/reasoning none           # Reasoning abschalten

PR #110346 machte das Modellwechseln und das Setzen des Aufwands zu einer einzigen Geste. Der /model-Parser akzeptiert jetzt --reasoning <level>, sodass eine Anfrage sowohl die Auswahl als auch ihren Aufwand mitbringt:

/model openrouter/anthropic/claude-opus-4.8 --reasoning high

Drei Details, die ständig auftauchen:

  • Der Geltungsbereich folgt dem Modellwechsel. Standard ist die Session; --global schreibt agent.reasoning_effort; --once sichert den aktuellen Wert, wendet den Aufwand für einen einzelnen Turn an und stellt ihn danach wieder her.
  • Der Aufwand wird nach dem Agent-Swap angewendet. Das ist keine Spitzfindigkeit: Der Wechselpfad löst reasoning_config aus config.yaml neu auf, ein vorher geschriebener Aufwand würde also überschrieben. Die Reihenfolge ist Absicht.
  • Ein ungültiges Level führt zu einem Fehler, statt still nichts zu tun. /model --reasoning turbo liefert /model --reasoning takes none, minimal, low, medium, high, xhigh, max or ultra.

Du kannst die Flags auch komplett überspringen. Der interaktive /model-Picker hat jetzt drei Stufen – Provider, Modell, dann eine dritte Stufe mit dem Titel Reasoning effort for <model>, die die komplette Leiter plus eine Zeile none (disable reasoning) und eine Zeile „aktuellen Wert behalten“ auflistet. Wenn die Capability-Map der Route sagt, dass es keine Reasoning-Steuerung gibt, wird diese dritte Stufe ausgeblendet, statt einen Regler anzubieten, der nichts tut.

Die Ink TUI (hermes --tui) durchläuft denselben dreistufigen Flow; Schritt 3 ist mit Reasoning effort (step 3/3) beschriftet und sendet Modell, Provider, Aufwand und Geltungsbereich als einen einzigen Gateway-Befehl. Auf Messaging-Plattformen (Telegram, Slack und Verwandte) läuft /model --reasoning durch denselben Applier, den /reasoning nutzt, sodass die beiden nicht auseinanderdriften können.

hermes model: der Effort-Schritt für das Hauptmodell und jede Hilfsaufgabe

hermes model ist der Einstiegspunkt, den die meisten zuerst nutzen (siehe die hermes model Referenz). Vor PR #110346 fragte er nur auf Copilot-Routen nach dem Aufwand, und der Auxiliary-Flow fragte überhaupt nie.

Jetzt:

  • Nachdem das Hauptmodell gespeichert ist, folgt ein gemeinsamer Schritt: die sieben Level, Disable reasoning und Skip, um den aktuellen Wert zu behalten. Modelle, die der Katalog als ohne Reasoning-Steuerung markiert, überspringen den Schritt.
  • Die Provider-Liste bekam eine Zeile Reasoning effort for the current model… für den Fall, dass du nur den Aufwand ändern willst.
  • Configure auxiliary models… (Provider → Modell) endet mit demselben Effort-Schritt plus einer Zeile Provider default, die das Level dem Provider überlässt. Diese Werte werden als auxiliary.<task>.reasoning_effort gespeichert, für Delegation als delegation.reasoning_effort.
  • Die Aufgabenliste zeigt das Level inline, z. B. openrouter · model · high, und Reset all to auto löscht alle Overrides auf einmal.
  • Blöcke, die bewusst keinen solchen Key haben – die MoA-Slots und memory_query_rewrite –, überspringen den Schritt. Sie fehlen nicht; sie konfigurieren die Tiefe nur woanders.

Warum das wichtiger ist, als es aussieht: Kompression, Titelgenerierung und Approval-Checks laufen in jedem Turn. Diese von high auf minimal (oder aus) zu senken, spart mehr Geld, als das Feintuning des Levels des Hauptmodells je einsparen wird.

Desktop: die Composer-Pille und der Aufwand pro Aufgabe in den Settings

Der Desktop-Modell-Picker sitzt im Composer, direkt links neben dem Mikrofon. Vor PR #109907 war der einzige Effort-Regler ein Hover-Untermenü in einer Modellzeile; die kombinierte Pille las Deepseek V4 Flash · Med, Nutzer lasen das als „dieses Modell kann nur medium“, und das Untermenü blieb unentdeckt.

Jetzt hat der Aufwand seine eigene Affordanz:

  • Eine Reasoning-Pille neben der Modell-Pille zeigt das tatsächlich wirksame Level für diese Oberfläche (zuerst der Session-Wert, sonst der Config-Default) und öffnet direkt Zeilen für Thinking / Fast / Effort. Sie verschwindet, wenn der Katalog reasoning: false für das aktive Modell meldet, und bleibt sichtbar, solange die Capabilities noch unbekannt sind, sodass sie während des Katalog-Fetches nie flackert.
  • Die Modell-Pille faltet den Aufwand nicht mehr in ihr eigenes Label; jedes Control sagt eine Sache.

Der Aufwand pro Aufgabe auf der Desktop-Seite brauchte zwei weitere PRs. #110435 fügte ein Select Reasoning in jede Zeile unter Settings → Model → Auxiliary models ein: inherit · main model effort (der Default, der den Override dieser Aufgabe entfernt), Off oder ein Level – und schreibt denselben Key auxiliary.<task>.reasoning_effort, den hermes model schreibt. Der API-Vertrag dahinter ist es wert, gekannt zu werden: Feld weglassen lässt einen Override unangetastet, ein explizites null löscht ihn (zurück zu inherit), ein Level setzt ihn. Ein unbekannter Wert wie turbo liefert einen 400 mit einer Auflistung aller akzeptierten Level, statt einfach verworfen zu werden.

PR #110535 schloss den Kreis bei den Desktop-Auxiliary-Pins: Alle 11 Slots können gepinnt werden, das Speichern unabhängiger Einstellungen schreibt diese Pins nicht mehr um und verwirft sie nicht (Config-Writes wurden section-scoped, statt einen vollständig expandierten Snapshot zurück auf die Platte zu schreiben), und wenn eine Review-Aufgabe auf ein anderes Modell geroutet wird, wird ihr Reasoning-Aufwand berücksichtigt, statt von der Einstellung der Eltern-Session verschluckt zu werden.

Ein Stolperstein: Die Desktop-App merkt sich ein Effort/Fast-Preset pro Modell lokal. Das ist eine Bequemlichkeit für die App und ändert keine Crons oder Subagents – nutze dafür hermes model oder die Config-Datei.

Wie die Ebenen sich stapeln und wo model_overrides hineinpasst

Rangfolge, höchste zuerst (das ist auch die Auflösungsreihenfolge zur Laufzeit): Session-Override (/reasoning --session, /model --reasoning) → Override pro Modell agent.reasoning_overrides → globaler Default agent.reasoning_effort. Eine config.yaml zum Kopieren:

agent:
  # Der globale Default; das ist die Zeile, die `/reasoning high --global` schreibt
  reasoning_effort: medium
  # Pro Modell: Keys sind schreibweisentolerant (exakt, Punkte/Bindestriche austauschbar,
  # Provider-Präfix optional), der erste Treffer gewinnt und schlägt den Default oben.
  # Kein `hermes config set`-Support für diesen Key – editiere das YAML direkt.
  reasoning_overrides:
    "openrouter/anthropic/claude-opus-4.8": high
    "deepseek/deepseek-v4-flash": low

auxiliary:
  compression:
    reasoning_effort: minimal   # das Zusammenfassen der Historie braucht kein tiefes Nachdenken
  title_generation:
    reasoning_effort: none      # Titel: einfach abschalten
  vision:
    reasoning_effort: ""        # leer = Provider-Default

delegation:
  reasoning_effort: low         # Subagents; leer lassen, um vom Parent zu erben

model_overrides spielt hier eine andere Rolle (siehe den model_overrides-Leitfaden): Dort leben die Modell-Metadaten – context_window, supports_vision, supports_reasoning und Verwandte. Es setzt den Aufwand nicht, aber es entscheidet, ob das Aufwands-Control überhaupt existiert: Wenn die Capability-Map sagt, dass eine Route keine Reasoning-Steuerung hat, verstecken sich der hermes model-Schritt, die dritte Stufe des /model-Pickers und die Desktop-Pille gemeinsam. Wenn du also auf einem lokalen oder brandneuen Modell unterwegs bist, von dem der Katalog nie gehört hat, deklariere zuerst die Capabilities, und die Effort-Controls tauchen auf.

Zwei Ausnahmen, die man sich einprägen sollte:

  • background_review.reasoning_effort wird ignoriert, solange das Review auf dem Hauptmodell bleibt. In diesem Fall erbt der Fork die Reasoning-Config des Gesprächs wortwörtlich, und genau das hält seine Request-Bytes auf dem Prompt-Cache-Präfix des Parents. Um ein anderes Modell und einen anderen Aufwand zu bekommen, setze auch Provider/Modell des Reviews – sonst warnt Hermes dich einmal, absichtlich.
  • Der MoA-Aufwand wird pro Slot im Preset konfiguriert, nicht im Auxiliary-Block: moa.presets.<name>.reference_models[].reasoning_effort und aggregator.reasoning_effort. Jede Referenz kann in ihrer eigenen Tiefe laufen und der Aggregator tiefer gehen – deshalb haben die MoA-Blöcke by design keinen reasoning_effort-Key.

Ein Start-Setup, das sich selbst bezahlt

Wenn du die ganze Rangfolge-Kette lieber nicht im Kopf behalten willst, mach diese fünf Dinge:

  1. Lass das Hauptmodell auf medium. Erhöhe es für eine einzelne Aufgabe mit /model ... --reasoning high, statt den globalen Default zu bearbeiten.
  2. Führe hermes model → Configure auxiliary models… aus und setze compression auf minimal und title_generation auf aus. Diese laufen in jedem Turn und zahlen sich sofort aus.
  3. Setze delegation.reasoning_effort: low. Subagents führen meist eine Aufgabe aus, die schon zerlegt wurde; sie müssen sie nicht neu planen.
  4. Nutze hermes --reasoning low -z "..." für Skripte und Einmal-Jobs, damit ein temporärer Bedarf nie zu einem dauerhaften Default wird.
  5. Wenn ein langer Lauf hängt, prüfe das Turn-Budget, bevor du nach mehr Denken greifst – darum geht es im max_turns-Leitfaden. Und auf Free-Tier-Routen passt das Setup ohne API-Key gut zu minimal.

Die Kurzfassung

Der Reasoning-Aufwand ist kein Regler nach dem Motto „höher ist besser“; er ist ein Scoping-Problem. In einer config.yaml hast du getrennte Regler für die Session, pro Modell, pro Hilfsaufgabe und pro Subagent. PR #110346 hat diese Regler auf die Picker verschoben, die du ohnehin nutzt – alles, was bleibt, ist zu entscheiden, wie viele Level jede Aufgabe verdient, damit ein Review von letztem Monat nicht mehr Ultra-Preise für eine Umbenennung von diesem Monat zahlt.