Context length config override and detection fixes

NousResearch/hermes-agent · cluster 8430 · agent

Data as of

5 open items. Fix merged in #23980. Check it covers your case.

The short answer

3open issues
2open PRs
25items grouped
Known?
Yes. 25 related items grouped here, 8 issues already closed.
Fix event
A fix was merged to main in PR #23980. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #23980. That is separate from the fix event.
Still open
3 issues and 2 PRs.

These issues and fixes address problems where the configured or API-reported context length was ignored in favor of hardcoded minimums or stale metadata, particularly affecting small models, Kimi variants, and Ollama-hosted models. The work spans bug reports, config override enforcement, and improved fallback resolution for context window values.

Open issues3

  • Issue#22930[Setup]: For some small 2b、4b、8B、14B parameter models, how can the Hermes agent be run offline when the computing power resources are insufficient?
  • Issue#32048[Feature]: token context window is too high for small offline users as well as small LLM model APIs
  • Issue#52595Model picker can save Grok Imagine models that fail Hermes 64K context minimum

Open PRs2

  • PR#25464fix: handle stale low context metadata for curated models
  • PR#52627fix(cli): reject xai media models as main model

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #23980 fix: correct context-length resolution for kimi-k2.6 on Ollama Cloud and Kimi Coding

    merged to main

    files: agent/model_metadata.py, agent/models_dev.py, scripts/release.py

    Show the diff
    • agent/model_metadata.py
      @@ -1006,6 +1006,79 @@ def query_ollama_num_ctx(model: str, base_url: str, api_key: str = "") -> Option
           return None
       
       
      +def _query_ollama_api_show(model: str, base_url: str, api_key: str = "") -> Optional[int]:
      +    """Query an Ollama server's native ``/api/show`` for context length.
      +
      +    Provider-agnostic: works against ANY Ollama-compatible server regardless
      +    of hostname — local Ollama, Ollama Cloud (``ollama.com``), custom Ollama
      +    hosting behind a reverse proxy, etc.  For non-Ollama servers the POST
      +    returns 404/405 quickly; the function handles errors gracefully.
      +
      +    For hosted servers the GGUF ``model_info.*.context_length`` is the
      +    authoritative source: the user can't set their own ``num_ctx``, and the
      +    OpenAI-compat ``/v1/models`` endpoint correctly omits ``context_length``
      +    per the OpenAI schema.
      +
      +    Resolution order for hosted Ollama:
      +      1. ``model_info.*.context_length`` — GGUF training max (authoritative)
      +      2. ``parameters`` → ``num_ctx`` — server-side Modelfile override
      +    The order is flipped vs ``query_ollama_num_ctx()`` because local users
      +    control ``num_ctx`` themselves; hosted users can't.
      +    """
      +    import httpx
      +
      +    server_url = base_url.rstrip("/")
      +    if server_url.endswith("/v1"):
      +        server_url = server_url[:-3]
      +
      +    headers = _auth_headers(api_key)
      +
      +    try:
      +        with httpx.Client(timeout=5.0, headers=headers) as client:
      +            resp = client.post(f"{server_url}/api/show", json={"name": model})
      +            if resp.status_code != 200:
      +                return None
      +            data = resp.json()
      +
      +            # Hosted Ollama: GGUF model_info is the real max — prefer it over
      +            # num_ctx which the Cloud operator may have capped arbitrarily.
      +            model_info = data.get("model_info", {})
      +            for key, value in model_info.items():
      +                if "context_length" in key and isinstance(value, (int, float)):
      +                    ctx = int(value)
      +                    if ctx >= 1024:
      +                        return ctx
      +
      +            # Fall back to num_ctx from Modelfile parameters (rare on Cloud)
      +            params = data.get("parameters", "")
      +            if "num_ctx" in params:
      +                for line in params.split("\n"):
      +                    if "num_ctx" in line:
      +                        parts = line.strip().split()
      +                        if len(parts) >= 2:
      +                            try:
      +                                ctx = int(parts[-1])
      +                                if ctx >= 1024:
      +                                    return ctx
      +                            except ValueError:
      +                                pass
      +    except Exception:
      +        pass
      +    return None
      +
      +
      +def _model_name_suggests_kimi(model: str) -> bool:
      +    """Return True if the model name looks like a Kimi-family model.
      +
      +    Catches ``kimi-k2.6``, ``kimi-k2.5``, ``kimi-k2-thinking``,
      +    ``moonshotai/Kimi-K2.6``, and similar variants.  Used as a guard
      +    against stale OpenRouter metadata that underreports these models
      +    as 32K context when they actually support 262K+.
      +    """
      +    lower = model.lower()
      +    return lower.startswith("kimi") or "moonshot" in lower
      +
      +
       def _query_local_context_length(model: str, base_url: str, api_key: str = "") -> Optional[int]:
           """Query a local server for the model's context length."""
           import httpx
      @@ -1307,12 +1380,17 @@ def get_model_context_length(
           2. Active endpoint metadata (/models for explicit custom endpoints)
           3. Local server query (for local endpoints)
           4. Anthropic /v1/models API (API-key users only, not OAuth)
      -    5. OpenRouter live API metadata
      -    6. Nous suffix-match via OpenRouter cache
      -    7. models.dev registry lookup (provider-aware)
      -    8. Thin hardcoded defaults (broad family patterns)
      -    9. Default fallback (256K)
      -    """
      +    5. Provider-aware lookups (before generic OpenRouter cache):
      +       a. Copilot live /models API
      +       b. Nous suffix-match via OpenRouter cache
      +       c. Codex OAuth /models probe
      +       d. GMI /models endpoint
      +       e. Ollama native /api/show probe (any base_url, provider-agnostic)
      +       f. models.dev registry lookup (with :cloud/-cloud suffix fallback)
      +    6. OpenRouter live API metadata (Kimi-family 32k guard)
      +    7. Hardcoded defaults (broad family patterns, longest-key-first)
      +    8. Local server query (last resort)
      +    9. Default fallback (256K)"""
           # 0. Explicit config override — user knows best
           if config_context_length is not None and isinstance(config_context_length, int) and config_context_length > 0:
               return config_context_length
      @@ -1392,6 +1470,13 @@ def get_model_context_length(
               if context_length is not None:
                   return context_length
               if not _is_known_provider_base_url(base_url):
      +            # 2b. Ollama native /api/show — any URL might be an Ollama server
      +            # (local, cloud, or custom hosting).  Non-Ollama servers return
      +            # 404/405 quickly.  Fall through on failure.
      +            ctx = _query_ollama_api_show(model, base_url, api_key=[redacted:secret-assignment]
      +            if ctx is not None:
      +                save_context_length(model, base_url, ctx)
      +                return ctx
                   # 3. Try querying local server directly
                   if is_local_endpoint(base_url):
                       local_ctx = _query_local_context_length(model, base_url, api_key=[redacted:secret-assignment]
      @@ -1461,16 +1546,53 @@ def get_model_context_length(
               ctx = _resolve_endpoint_context_length(model, base_url, api_key=[redacted:secret-assignment]
               if ctx is not None:
                   return ctx
      +    # 5e. Ollama native /api/show probe — runs for ANY provider with a
      +    # base_url, not just ollama-cloud.  Ollama-compatible servers expose
      +    # this endpoint regardless of hostname (local Ollama, Ollama Cloud,
      +    # custom Ollama hosting).  The OpenAI-compat /v1/models endpoint
      +    # correctly omits context_length per the OpenAI schema, but /api/show
      +    # returns the authoritative GGUF model_info.context_length.
      +    # For non-Ollama servers (OpenAI, Anthropic, etc.), the POST returns
      +    # 404/405 quickly.  Results are cached, so the hit is per-model+URL,
      +    # once per hour.
      +    if base_url:
      +        ctx = _query_ollama_api_show(model, base_url, api_key=[redacted:secret-assignment]
      +        if ctx is not None:
      +            save_context_length(model, base_url, ctx)
      +            return ctx
           if effective_provider:
               from agent.models_dev import lookup_models_dev_context
               ctx = lookup_models_dev_context(effective_provider, model)
               if ctx:
                   return ctx
       
      -    # 6. OpenRouter live API metadata (provider-unaware fallback)
      -    metadata = fetch_model_metadata()
      -    if model in metadata:
      -        return metadata[model].get("context_length", DEFAULT_FALLBACK_CONTEXT)
      +    # 6. OpenRouter live API metadata — provider-unaware fallback.
      +    # Only consulted when the provider is unknown (no effective_provider),
      +    # because OpenRouter data is community-maintained and can be incorrect
      +    # for models that belong to known providers with curated defaults.
      +    if not effective_provider:
      +        metadata = fetch_model_metadata()
      +        if model in metadata:
      +            or_ctx = metadata[model].get("context_length", DEFAULT_FALLBACK_CONTEXT)
      +            # Guard against stale OpenRouter metadata for Kimi-family models.
      +            # OpenRouter reports 32768 for moonshotai/kimi-k2.6, but the model
      +            # actually supports 262144 (models.dev + official Kimi docs agree).
      +            # Providers that host their own Kimi endpoints (Ollama Cloud, Kimi
      +            # Coding, Moonshot) would otherwise trip the 64k minimum-context
      +            # guard and reject a perfectly capable model.
      +            # The filter is narrow: only reject exactly 32768 for Kimi-named
      +            # models.  If OpenRouter ever updates its data, the stale path
      +            # becomes dead code with no impact.
      +            if or_ctx == 32768 and _model_name_suggests_kimi(model):
      +                logger.info(
      +                    "Rejecting OpenRouter metadata context=%s for %r "
      +                    "(Kimi-family underreport); falling through to hardcoded defaults",
      +                    or_ctx, model,
      +                )
      +            else:
      +                return or_ctx
      +
      +    # 7. (reserved)
       
           # 8. Hardcoded defaults (fuzzy match — longest key first for specificity)
           # Only check `default_model in model` (is the key a substring of the input).
    • agent/models_dev.py
      @@ -145,7 +145,9 @@ class ProviderInfo:
           "openai": "openai",
           "openai-codex": "openai",
           "zai": "zai",
      +    "kimi": "kimi-for-coding",
           "kimi-coding": "kimi-for-coding",
      +    "moonshot": "kimi-for-coding",
           "stepfun": "stepfun",
           "kimi-coding-cn": "kimi-for-coding",
           "minimax": "minimax",
      @@ -347,6 +349,28 @@ def lookup_models_dev_context(provider: str, model: str) -> Optional[int]:
                   if ctx:
                       return ctx
       
      +    # Suffix-aware fallback: some providers (e.g. ollama-cloud) store
      +    # model IDs with :cloud / -cloud suffixes in models.dev while the
      +    # live API returns bare names.  Without this, kimi-k2.6 misses the
      +    # kimi-k2.6:cloud entry and falls through to stale OpenRouter metadata
      +    # reporting 32768 — tripping the 64k minimum-context guard.
      +    # The suffix-stripping in fetch_ollama_cloud_models() handles the
      +    # model-picker UX; this handles the context-length lookup path.
      +    for suffix in (":cloud", "-cloud"):
      +        suffixed_key = model + suffix
      +        entry = models.get(suffixed_key)
      +        if entry:
      +            ctx = _extract_context(entry)
      +            if ctx:
      +                return ctx
      +        # Also try case-insensitive
      +        suffixed_lower = model_lower + suffix
      +        for mid, mdata in models.items():
      +            if mid.lower() == suffixed_lower:
      +                ctx = _extract_context(mdata)
      +                if ctx:
      +                    return ctx
      +
           return None
       
       
    • scripts/release.py
      @@ -88,6 +88,7 @@
           "[redacted:email]": "Qwinty",
           "[redacted:email]": "CalmProton",
           "[redacted:email]": "mbac",
      +    "[redacted:email]": "nicoechaniz",
           "[redacted:email]": "Ninso112",
           "[redacted:email]": "wesleysimplicio",
           "[redacted:email]": "SiliconID",
  • #24502 Use nous portal as model metadata authority

    merged to main

    files: agent/model_metadata.py, tests/agent/test_model_metadata.py

  • #42986 fix(models): read OpenRouter live context_length before hardcoded catch-all

    merged to main

    files: agent/model_metadata.py, tests/agent/test_model_metadata.py

  • #104159 OpenRouter provider pins can now be set per model (provider_routing.models, #24495 / #100711)

    merged to main

    files: agent/chat_completion_helpers.py, agent/turn_recovery.py, cli-config.yaml.example, hermes_constants.py, plugins/model-providers/openrouter/__init__.py, tests/agent/test_per_model_provider_routing.py, website/docs/integrations/providers.md, website/docs/user-guide/features/provider-routing.md

8 PRs closed without merging

From the close comments: done elsewhere 2 · won't fix / by design 2 · duplicate 1 · superseded by another PR 1 · other 2

  • #8590 fix(model): respect explicit context_length override · duplicate → #9142
  • #9142 fix(config): respect model.context_length override for sub-64K models · won't fix / by design → #53569
  • #24066 fix(model-metadata): extend Kimi 32k guard to Nous OpenRouter suffix-match · superseded by another PR → 528bba6, #24502
  • #24156 fix: always consult OpenRouter metadata as last-resort fallback for context length · done elsewhere → #24268
  • #24441 fix(model-metadata): add explicit context length for kimi-k2.6 family · done elsewhere → 967c325, #42986
Show 3 more
  • #37548 fix(agent): respect model.context_length config · won't fix / by design → #53569
  • #54636 fix(agent): respect model.context_length config override for minimum check (#8430) · other → #8430
  • #55231 fix: honor explicit context_length config override for small-context models · other → #8430
Closed issues · 8
  • Closed#8430[Bug]: ## ## 问题描述 当使用上下文窗口大小小于 64,000 tokens 的模型时,即使在 config.yaml 中设置了 `model.context_length`,仍然会收到错误:
  • Closed#10449[Bug]: Local model context setting
  • Closed#23949kimi-k2.6 on Ollama Cloud detected as 32K context despite API reporting 256K
  • Closed#24000provider: nous falls back to 32,768-token context, blocking boot with model.context_length workaround required
  • Closed#24119get_model_context_length() unconditionally queries OpenRouter metadata even for non-OpenRouter users, causing false "below minimum 64K" rejections
  • Closed#24140All models rejected with "context window below minimum 64,000 tokens" — Telegram completely down
  • Closed#24268Wrong context length for kimi-k2.6 family: OpenRouter returns 32K, overrides correct hardcoded 256K default
  • Closed#24427[Bug]: discord-kimi-context-bug-issue
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.07: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#23980, #24502, #42986, #104159
Closed issues
8
Attempts
8 PRs closed without merging
Fix link checked
6 Sep 2026

For your agent

The same facts, typed.