Skip to content

fix: reasoning effort 'ultra' no longer 400s on non-Anthropic wires (#89503 class, salvage #89509) - #90330

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-9882d200
Aug 19, 2026
Merged

teknium1 merged 2 commits into
mainfrom
hermes/hermes-9882d200

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

reasoning_effort: ultra no longer 400s on non-Anthropic wires — the whole effort-vocabulary bug class is fixed at every translation site, not just the DeepSeek/OpenRouter one reported in #89503.

Root cause: ultra is Hermes-internal ladder vocabulary (VALID_REASONING_EFFORTS), but the wire translation was per-vendor patchwork — the chat-completions chokepoint and the Codex transport only translated ultra → max for gpt-5.6, and several per-provider maps either dropped unknown levels to a weak default (inverting the ladder: the strongest ask resolved weaker than an explicit high) or forwarded them verbatim (guaranteed HTTP 400).

Changes

  • agent/transports/chat_completions.py _reasoning_config_for_model(): ultra → max for every model on this transport (cherry-picked from fix(agent): cap the ultra reasoning level at the wire vocabulary #89509 by @liuhao1024, with tests). This alone fixes the reported Portal dsv4-pro-0813 400 (expected one of "max"|"xhigh"|…).
  • agent/transports/chat_completions.py Kimi path: K3 accepts low/high/max only — the old code forwarded only {low,medium,high} and dropped everything else to medium (400s on K3 for medium, ladder inversion for ultra). Full ladder now mapped per family (K3 map mirrors the kimi-coding plugin; K2-era caps upper levels at high).
  • agent/transports/chat_completions.py TokenHub path: full ladder mapped onto low/medium/high; minimal previously fell through to the high default (asked least, got most).
  • agent/transports/codex.py: ultra → max baseline for all Responses-API models (was gpt-5.6-gated); xAI/Actual branches still override with their narrower ceilings.
  • agent/auxiliary_client.py Responses path: ultra → max beside the existing minimal → low clamp.
  • plugins/model-providers/custom: ultra capped at max instead of forwarded verbatim to GLM/vLLM/SGLang endpoints.
  • plugins/model-providers/copilot: ad-hoc downgrade rules replaced with the shared clamp_reasoning_effort_to_supported ladder walk — ultra/max now resolve to the strongest catalog-supported level instead of medium (Copilot route: reasoning_effort 'ultra' clamps to 'medium', making the strongest setting weaker than 'high' #74295).

Sites audited and already correct (no change): Anthropic adapter (ADAPTIVE_EFFORT_MAP), openrouter plugin (catalog-driven clamp), LM Studio (_LM_EFFORT_CLAMP), deepseek/zai/opencode-zen/ollama-cloud/meta-ai/upstage plugins, Gemini thinkingLevel mapping.

Validation

Before After
ultra on Portal/OpenRouter chat-completions HTTP 400 max on the wire
ultra on Kimi K3 dropped to medium default max
medium on Kimi K3 forwarded (K3 rejects it) high
ultra on Copilot (catalog low..high) medium (ladder inversion) high
minimal on TokenHub high (inversion) low

195 targeted tests pass (transports + copilot/custom/kimi profiles). New sibling-site test file sabotage-verified: 6/10 tests fail with the fixes reverted.

Fixes #89503, #70058, #74295. Salvages #89509 (@liuhao1024, cherry-picked with authorship).

Infographic

reasoning effort wire translation

liuhao1024 and others added 2 commits August 19, 2026 15:28
Hermes' internal effort vocabulary extends the wire set with ultra
(documented by /reasoning as none..xhigh|max|ultra). OpenAI-compatible
wires — OpenRouter chief among them — accept exactly
max|xhigh|high|medium|low|minimal|none and reject the extension with
HTTP 400, so an ultra configured while the default model was Anthropic
worked (the Anthropic adapter maps its own levels) but leaked
untranslated the moment a per-job override pinned a non-Anthropic
model, failing every call for that job.

The wire-compat chokepoint for this transport previously mapped
ultra to max only for gpt-5.6; generalize the cap to every model.
…class)

The chat_completions chokepoint fix (ultra->max for every model,
cherry-picked from #89509) has siblings with the same bug shape:

- codex.py: ultra->max was gated on gpt-5.6 only; now baseline for all
  Responses-API models (backend-specific branches still override).
- Kimi top-level reasoning_effort: K3 accepts low/high/max only —
  'medium' and upper-ladder levels were dropped to the medium default
  (400s on K3, ladder inversion on K2). Full ladder mapped per family,
  mirroring the kimi-coding plugin's K3 map.
- TokenHub: 'minimal' fell through to the 'high' default (asked least,
  got most); full ladder now mapped onto low/medium/high.
- auxiliary_client Responses path: ultra->max alongside the existing
  minimal->low clamp.
- custom provider plugin: ultra capped at max instead of forwarded
  verbatim to GLM/vLLM/SGLang backends that reject it.
- copilot plugin: ad-hoc downgrade rules replaced with the shared
  clamp_reasoning_effort_to_supported ladder walk so ultra/max resolve
  to the strongest supported level instead of medium (#74295).

Sabotage-verified: new sibling-site tests fail 6/10 without the fixes.
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 952e0d9 — fix: widen reasoning-effort wire translation to sibling site

⚠️ Warnings

CI timings · View report · View job

Wall time 4m26s vs 3m31s (+26.1%). 10 job(s) slower, 12 faster, 2 unchanged.

  • Python tests / Run tests slice 8/12: +38.0s
  • Python tests / Run tests slice 5/12: +38.0s
  • Python tests / Run tests slice 4/12: +34.0s
  • Python tests / Run tests slice 9/12: -29.0s
  • Python tests / Run tests slice 3/12: -25.0s

OSV vulnerability scan · View job

7 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/openai OpenAI / Codex Responses API provider/kimi Kimi / Moonshot provider/copilot GitHub Copilot (ACP + Chat) labels Aug 19, 2026
@teknium1
teknium1 merged commit 9ec5750 into main Aug 19, 2026
51 checks passed
@teknium1
teknium1 deleted the hermes/hermes-9882d200 branch August 19, 2026 23:04

@andrexibiza andrexibiza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge root-cause review

Verdict

#90330 is a valid hotfix and should remain merged. Its claim that the “whole effort-vocabulary bug class is fixed at every translation site” is not supportable. It closes the known ultra leaks and several reasoning-ladder inversions. It does not remove the architecture that produced them.

Merged as 9ec5750aca33bf6e977cfde468868b0f0d66e339 from reviewed head 952e0d9330ee0fd73b35e134e48571d8bcde3a7f.

Root cause

The root cause is an uncompiled semantic intent crossing a late-bound provider boundary.

Hermes has a global, user-facing reasoning ladder:

none < minimal < low < medium < high < xhigh < max < ultra

But reasoning_config is represented as a largely provider-agnostic dictionary whose effort string is also treated as though it were already a valid wire value. Provider and model selection can change after that configuration is created—through cron overrides, fallback, auxiliary routing, MoA/delegation, or per-turn model selection.

There is no mandatory final step equivalent to:

compile_reasoning(
    requested_intent,
    final_provider,
    final_model,
    final_transport,
    final_endpoint,
)

Instead, each transport and provider profile independently chooses among:

  • passing the value through;
  • applying a local lookup table;
  • recognizing a handful of special cases;
  • substituting an arbitrary default when the value is unknown.

That creates two failure modes:

  1. Hard incompatibility: an internal value such as ultra reaches a provider that accepts only max|xhigh|high|medium|low|minimal|none, producing HTTP 400.
  2. Semantic inversion: an unsupported strong or weak level falls to an unrelated default—for example, ultra → medium or minimal → high—so the effective ordering contradicts the user-visible ordering.

The linked incidents exhibit both forms:

  • #89503: a cron provider override reached OpenRouter with an invalid reasoning value and failed immediately, while Anthropic worked because it uses a different translation path.
  • #70058: GLM rejected ultra, after which Hermes could silently move the session to Claude through provider fallback.
  • #74295: Copilot mapped the strongest Hermes setting, ultra, to medium, making it weaker than explicitly selecting high.

Cron was the trigger, not the cause. OpenRouter and GLM were schema enforcers, not the cause. The defect was created locally before the request left Hermes.

Introducing change

The latent architecture predates the incident, but #62650 activated the class at ecosystem scale.

That PR promoted max and ultra into the shared contract across CLI, gateway, dashboard, desktop, delegation, batch processing, and provider routes. Its compatibility mechanism, however, normalized ultra → max only when the model name matched GPT-5.6.

This was effectively a schema migration treated as a feature addition:

  • The producer vocabulary was widened globally.
  • Existing consumers were not required to prove total handling of the widened enum.
  • No registry-level conformance test failed when a consumer had no mapping for the new values.
  • Validation sampled known happy paths instead of proving the provider × transport × effort cross-product.

The live proof attached to #62650 consisted of GPT-5.6 calls through OpenRouter and OpenAI direct. Those correctly emitted max, but did not test a non-GPT provider, late provider switch, custom endpoint, auxiliary route, or unsupported-level behavior.

So the process-level root cause is:

A globally shared enum was widened without a total-consumer migration gate.

What #90330 fixed correctly

The merged patch is broader than the original report and materially improves correctness:

  • Generalizes Chat Completions ultra → max.
  • Adds explicit full-ladder mappings for Kimi and TokenHub.
  • Generalizes Codex/Responses ultra → max.
  • Adds the missing auxiliary Responses clamp.
  • Replaces Copilot’s ad hoc downgrade cases with the shared ordered-ladder helper.
  • Prevents custom providers from forwarding ultra verbatim.
  • Adds regression tests for the general Chat Completions normalization and selected sibling sites.

The Copilot change is especially sound: it moves away from individually enumerated exceptions and toward an ordered semantic ladder. That directly repairs the monotonicity defect identified in #74295.

This should be understood as correct incident mitigation plus a substantial known-site sweep.

Why it does not close the class

1. Provider capabilities still are not first-class

ProviderProfile accepts a raw reasoning_config and exposes provider-specific hooks, but it does not declare a common reasoning capability contract: supported levels, wire field, disable semantics, model-specific discovery, or unsupported-value policy.

Consequently, every provider remains free to reinvent translation. Nous and Vercel AI Gateway still perform broad passthrough, while OpenRouter independently performs catalog-driven clamping. OpenRouter’s behavior is a good local implementation, but it remains local rather than constituting the system contract.

2. “Wire-compatible” is still not target-compatible

The new _reasoning_config_for_model() treats all values through max as generic wire-native values and only rewrites ultra. Its regression test deliberately asserts that none through max pass through for "any/model".

But this PR itself demonstrates that the abstraction is insufficient:

  • Kimi K3 accepts only low|high|max.
  • TokenHub accepts only low|medium|high.
  • Copilot support is model-catalog-dependent.
  • Some providers use binary thinking toggles rather than effort strings.

A value can therefore be valid in some “OpenAI-compatible” schema and still be invalid for the actual provider/model selected.

3. Custom endpoints remain unknowable without an explicit contract

The custom profile intentionally represents arbitrary Ollama, vLLM, llama.cpp, GLM, and other user-supplied endpoints. Their supported vocabularies are not uniform.

Changing ultra → max prevents one known invalid token. It cannot prove that max, xhigh, minimal, or explicit none is valid on an arbitrary endpoint.

For this route, the system needs one of:

  • explicit user/provider capability metadata;
  • trustworthy endpoint/model discovery;
  • a conservative omission policy;
  • or a local preflight error explaining that the requested intent cannot be represented.

Blanket passthrough with one exception cannot close the class.

4. Requested intent is erased before effective behavior is known

A global early rewrite from ultra to max discards the distinction between:

requested = ultra
effective = max
reason = target ceiling

Downstream code sees only max. That prevents accurate diagnostics, UI disclosure of adaptation, telemetry comparing requested and effective behavior, support for a future provider with a native ultra-equivalent tier, and auditing whether a fallback or model switch changed execution semantics.

A proper compiler must preserve both requested and effective values.

5. Deterministic local payload failures are still treated like provider failures

#70058 documents the operational consequence: Hermes generated an invalid request, retried it, and could then activate provider fallback.

That is the wrong failure domain. A deterministic locally generated schema error should not:

  • retry the same invalid payload;
  • count against provider health;
  • rotate credentials;
  • silently switch models/providers;
  • obscure the original construction defect.

The still-open #34794 adds reactive classification and same-provider adaptation for rejected reasoning parameters. That is useful containment, but because it adapts only after a rejected request, it is not a substitute for correct compilation before transmission.

Verification gaps

The test count is useful but not equivalent to route-complete proof.

  • The new Kimi test drives the transport’s legacy is_kimi=True path, while registered Kimi providers normally use KimiProfile. The profile implementation has the correct full map, but existing profile coverage checks only the high case rather than the complete ladder.
  • The custom-profile regression suite parametrizes through max and does not directly assert the newly changed ultra → max behavior.
  • Existing Copilot tests cover xhigh, minimal, and unrecognized input, but do not directly pin ultra/max monotonicity across catalog ceilings.
  • There is no single end-to-end matrix proving that the same final target gets the same effective reasoning behavior through main chat, cron, auxiliary calls, delegation/MoA, and fallback.

These are acceptance gaps, not evidence that the merged implementation is presently broken.

Class-closing architecture

There is already a canonical anchor: #34968 — “User-Centric Reasoning Architecture with Intelligent Adaptation.” It identifies the absence of capability awareness and proposes the correct three-layer division: user intent, provider capability, and adapter/compiler. It should be refreshed and interlocked with #62650, #90330, #89503, #70058, #74295, and #34794 rather than duplicated.

The minimum durable design is:

@dataclass(frozen=True)
class ReasoningIntent:
    enabled: bool | None       # None = unset, False = explicitly disabled
    level: ReasoningLevel | None

@dataclass(frozen=True)
class ReasoningCapabilities:
    supported_levels: tuple[ReasoningLevel, ...] | None
    wire_shape: WireShape
    disable_semantics: DisableSemantics
    unknown_level_policy: UnknownLevelPolicy
    source: CapabilitySource

@dataclass(frozen=True)
class CompiledReasoning:
    requested: ReasoningIntent
    effective_level: ReasoningLevel | None
    wire_kwargs: dict
    wire_extra_body: dict
    adaptation_reason: str | None

Compilation must occur after final provider, model, transport, base URL, and route are resolved. Cron, main chat, fallback, auxiliary calls, MoA, and delegation must all call the same compiler.

The permanent acceptance invariants are:

  1. Totality: every global reasoning level has an explicit policy for every capability family.
  2. Membership: every emitted wire value belongs to the final target’s supported set.
  3. Monotonicity: increasing requested effort never decreases effective effort.
  4. Path consistency: identical final targets compile identically across main, cron, auxiliary, delegate, MoA, and fallback paths.
  5. Unset/disabled separation: omission never silently replaces explicit none.
  6. Provenance: requested and effective values remain separately observable.
  7. Migration enforcement: adding a global reasoning level fails CI until all capability families are total over it.
  8. Local-fault containment: invalid locally constructed parameters do not trigger identical retries, provider-health penalties, or silent cross-provider fallback.

Disposition

Keep #90330 merged. It is a good repair.

Do not describe the reasoning-effort class as closed. The accurate claim is:

Known ultra leakage and known ladder inversions were repaired across the audited translation sites.

Promote and refresh #34968 as the architectural closure vehicle. #34794 belongs underneath it as reactive containment. The class is closed only when raw reasoning intent can no longer cross a final provider boundary without one typed, capability-aware compilation step and cross-path conformance proof.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/copilot GitHub Copilot (ACP + Chat) provider/kimi Kimi / Moonshot provider/openai OpenAI / Codex Responses API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cron: per-job model override fails for non-Anthropic providers — reasoning.effort not translated

4 participants