{"repo":"NousResearch/hermes-agent","public_id":"8430","display_label":"Context length config override and detection fixes","summary":"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.","subsystem":"agent","counts":{"members":25,"open_issues":3,"open_issues_distinct":3,"open_prs":2,"closed_issues":8,"merged_prs":4,"attempt_prs":10},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":23980},"resolution":{"state":"partially_resolved","by":"pr-23980"},"freshness":{"state":"verified","last_verified_at":"2026-09-06T09:18:15+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.06706666666666664},"fix_edges":[{"issue_number":23949,"pr_number":23980,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":24000,"pr_number":24502,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":24268,"pr_number":42986,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":24268,"pr_number":104159,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":24493,"pr_number":104159,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":8430,"title":"[Bug]: ## ## 问题描述 当使用上下文窗口大小小于 64,000 tokens 的模型时，即使在 config.yaml 中设置了 `model.context_length`，仍然会收到错误：","url":null,"state":"closed"},{"kind":"issue","number":10449,"title":"[Bug]: Local model context setting","url":null,"state":"closed"},{"kind":"issue","number":22930,"title":"[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?","url":null,"state":"open"},{"kind":"issue","number":23949,"title":"kimi-k2.6 on Ollama Cloud detected as 32K context despite API reporting 256K","url":null,"state":"closed"},{"kind":"issue","number":24000,"title":"provider: nous falls back to 32,768-token context, blocking boot with model.context_length workaround required","url":null,"state":"closed"},{"kind":"issue","number":24119,"title":"get_model_context_length() unconditionally queries OpenRouter metadata even for non-OpenRouter users, causing false \"below minimum 64K\" rejections","url":null,"state":"closed"},{"kind":"issue","number":24140,"title":"All models rejected with \"context window below minimum 64,000 tokens\" — Telegram completely down","url":null,"state":"closed"},{"kind":"issue","number":24268,"title":"Wrong context length for kimi-k2.6 family: OpenRouter returns 32K, overrides correct hardcoded 256K default","url":null,"state":"closed"},{"kind":"issue","number":24427,"title":"[Bug]: discord-kimi-context-bug-issue","url":null,"state":"closed"},{"kind":"issue","number":32048,"title":"[Feature]: token context window is too high for small offline users as well as small LLM model APIs","url":null,"state":"open"},{"kind":"issue","number":52595,"title":"Model picker can save Grok Imagine models that fail Hermes 64K context minimum","url":null,"state":"open"},{"kind":"pr","number":8590,"title":"fix(model): respect explicit context_length override","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#9142","number":9142},{"kind":"pr","value":"#9142","number":9142}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":9142,"title":"fix(config): respect model.context_length override for sub-64K models","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53569","number":53569}],"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":23980,"title":"fix: correct context-length resolution for kimi-k2.6 on Ollama Cloud and Kimi Coding","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","agent/models_dev.py","scripts/release.py"],"fix_hunks":[{"file":"agent/model_metadata.py","hunks":["@@ -1006,6 +1006,79 @@ def query_ollama_num_ctx(model: str, base_url: str, api_key: str = \"\") -> Option\n     return None\n \n \n+def _query_ollama_api_show(model: str, base_url: str, api_key: str = \"\") -> Optional[int]:\n+    \"\"\"Query an Ollama server's native ``/api/show`` for context length.\n+\n+    Provider-agnostic: works against ANY Ollama-compatible server regardless\n+    of hostname — local Ollama, Ollama Cloud (``ollama.com``), custom Ollama\n+    hosting behind a reverse proxy, etc.  For non-Ollama servers the POST\n+    returns 404/405 quickly; the function handles errors gracefully.\n+\n+    For hosted servers the GGUF ``model_info.*.context_length`` is the\n+    authoritative source: the user can't set their own ``num_ctx``, and the\n+    OpenAI-compat ``/v1/models`` endpoint correctly omits ``context_length``\n+    per the OpenAI schema.\n+\n+    Resolution order for hosted Ollama:\n+      1. ``model_info.*.context_length`` — GGUF training max (authoritative)\n+      2. ``parameters`` → ``num_ctx`` — server-side Modelfile override\n+    The order is flipped vs ``query_ollama_num_ctx()`` because local users\n+    control ``num_ctx`` themselves; hosted users can't.\n+    \"\"\"\n+    import httpx\n+\n+    server_url = base_url.rstrip(\"/\")\n+    if server_url.endswith(\"/v1\"):\n+        server_url = server_url[:-3]\n+\n+    headers = _auth_headers(api_key)\n+\n+    try:\n+        with httpx.Client(timeout=5.0, headers=headers) as client:\n+            resp = client.post(f\"{server_url}/api/show\", json={\"name\": model})\n+            if resp.status_code != 200:\n+                return None\n+            data = resp.json()\n+\n+            # Hosted Ollama: GGUF model_info is the real max — prefer it over\n+            # num_ctx which the Cloud operator may have capped arbitrarily.\n+            model_info = data.get(\"model_info\", {})\n+            for key, value in model_info.items():\n+                if \"context_length\" in key and isinstance(value, (int, float)):\n+                    ctx = int(value)\n+                    if ctx >= 1024:\n+                        return ctx\n+\n+            # Fall back to num_ctx from Modelfile parameters (rare on Cloud)\n+            params = data.get(\"parameters\", \"\")\n+            if \"num_ctx\" in params:\n+                for line in params.split(\"\\n\"):\n+                    if \"num_ctx\" in line:\n+                        parts = line.strip().split()\n+                        if len(parts) >= 2:\n+                            try:\n+                                ctx = int(parts[-1])\n+                                if ctx >= 1024:\n+                                    return ctx\n+                            except ValueError:\n+                                pass\n+    except Exception:\n+        pass\n+    return None\n+\n+\n+def _model_name_suggests_kimi(model: str) -> bool:\n+    \"\"\"Return True if the model name looks like a Kimi-family model.\n+\n+    Catches ``kimi-k2.6``, ``kimi-k2.5``, ``kimi-k2-thinking``,\n+    ``moonshotai/Kimi-K2.6``, and similar variants.  Used as a guard\n+    against stale OpenRouter metadata that underreports these models\n+    as 32K context when they actually support 262K+.\n+    \"\"\"\n+    lower = model.lower()\n+    return lower.startswith(\"kimi\") or \"moonshot\" in lower\n+\n+\n def _query_local_context_length(model: str, base_url: str, api_key: str = \"\") -> Optional[int]:\n     \"\"\"Query a local server for the model's context length.\"\"\"\n     import httpx","@@ -1307,12 +1380,17 @@ def get_model_context_length(\n     2. Active endpoint metadata (/models for explicit custom endpoints)\n     3. Local server query (for local endpoints)\n     4. Anthropic /v1/models API (API-key users only, not OAuth)\n-    5. OpenRouter live API metadata\n-    6. Nous suffix-match via OpenRouter cache\n-    7. models.dev registry lookup (provider-aware)\n-    8. Thin hardcoded defaults (broad family patterns)\n-    9. Default fallback (256K)\n-    \"\"\"\n+    5. Provider-aware lookups (before generic OpenRouter cache):\n+       a. Copilot live /models API\n+       b. Nous suffix-match via OpenRouter cache\n+       c. Codex OAuth /models probe\n+       d. GMI /models endpoint\n+       e. Ollama native /api/show probe (any base_url, provider-agnostic)\n+       f. models.dev registry lookup (with :cloud/-cloud suffix fallback)\n+    6. OpenRouter live API metadata (Kimi-family 32k guard)\n+    7. Hardcoded defaults (broad family patterns, longest-key-first)\n+    8. Local server query (last resort)\n+    9. Default fallback (256K)\"\"\"\n     # 0. Explicit config override — user knows best\n     if config_context_length is not None and isinstance(config_context_length, int) and config_context_length > 0:\n         return config_context_length","@@ -1392,6 +1470,13 @@ def get_model_context_length(\n         if context_length is not None:\n             return context_length\n         if not _is_known_provider_base_url(base_url):\n+            # 2b. Ollama native /api/show — any URL might be an Ollama server\n+            # (local, cloud, or custom hosting).  Non-Ollama servers return\n+            # 404/405 quickly.  Fall through on failure.\n+            ctx = _query_ollama_api_show(model, base_url, api_key=[redacted:secret-assignment]\n+            if ctx is not None:\n+                save_context_length(model, base_url, ctx)\n+                return ctx\n             # 3. Try querying local server directly\n             if is_local_endpoint(base_url):\n                 local_ctx = _query_local_context_length(model, base_url, api_key=[redacted:secret-assignment]","@@ -1461,16 +1546,53 @@ def get_model_context_length(\n         ctx = _resolve_endpoint_context_length(model, base_url, api_key=[redacted:secret-assignment]\n         if ctx is not None:\n             return ctx\n+    # 5e. Ollama native /api/show probe — runs for ANY provider with a\n+    # base_url, not just ollama-cloud.  Ollama-compatible servers expose\n+    # this endpoint regardless of hostname (local Ollama, Ollama Cloud,\n+    # custom Ollama hosting).  The OpenAI-compat /v1/models endpoint\n+    # correctly omits context_length per the OpenAI schema, but /api/show\n+    # returns the authoritative GGUF model_info.context_length.\n+    # For non-Ollama servers (OpenAI, Anthropic, etc.), the POST returns\n+    # 404/405 quickly.  Results are cached, so the hit is per-model+URL,\n+    # once per hour.\n+    if base_url:\n+        ctx = _query_ollama_api_show(model, base_url, api_key=[redacted:secret-assignment]\n+        if ctx is not None:\n+            save_context_length(model, base_url, ctx)\n+            return ctx\n     if effective_provider:\n         from agent.models_dev import lookup_models_dev_context\n         ctx = lookup_models_dev_context(effective_provider, model)\n         if ctx:\n             return ctx\n \n-    # 6. OpenRouter live API metadata (provider-unaware fallback)\n-    metadata = fetch_model_metadata()\n-    if model in metadata:\n-        return metadata[model].get(\"context_length\", DEFAULT_FALLBACK_CONTEXT)\n+    # 6. OpenRouter live API metadata — provider-unaware fallback.\n+    # Only consulted when the provider is unknown (no effective_provider),\n+    # because OpenRouter data is community-maintained and can be incorrect\n+    # for models that belong to known providers with curated defaults.\n+    if not effective_provider:\n+        metadata = fetch_model_metadata()\n+        if model in metadata:\n+            or_ctx = metadata[model].get(\"context_length\", DEFAULT_FALLBACK_CONTEXT)\n+            # Guard against stale OpenRouter metadata for Kimi-family models.\n+            # OpenRouter reports 32768 for moonshotai/kimi-k2.6, but the model\n+            # actually supports 262144 (models.dev + official Kimi docs agree).\n+            # Providers that host their own Kimi endpoints (Ollama Cloud, Kimi\n+            # Coding, Moonshot) would otherwise trip the 64k minimum-context\n+            # guard and reject a perfectly capable model.\n+            # The filter is narrow: only reject exactly 32768 for Kimi-named\n+            # models.  If OpenRouter ever updates its data, the stale path\n+            # becomes dead code with no impact.\n+            if or_ctx == 32768 and _model_name_suggests_kimi(model):\n+                logger.info(\n+                    \"Rejecting OpenRouter metadata context=%s for %r \"\n+                    \"(Kimi-family underreport); falling through to hardcoded defaults\",\n+                    or_ctx, model,\n+                )\n+            else:\n+                return or_ctx\n+\n+    # 7. (reserved)\n \n     # 8. Hardcoded defaults (fuzzy match — longest key first for specificity)\n     # Only check `default_model in model` (is the key a substring of the input)."]},{"file":"agent/models_dev.py","hunks":["@@ -145,7 +145,9 @@ class ProviderInfo:\n     \"openai\": \"openai\",\n     \"openai-codex\": \"openai\",\n     \"zai\": \"zai\",\n+    \"kimi\": \"kimi-for-coding\",\n     \"kimi-coding\": \"kimi-for-coding\",\n+    \"moonshot\": \"kimi-for-coding\",\n     \"stepfun\": \"stepfun\",\n     \"kimi-coding-cn\": \"kimi-for-coding\",\n     \"minimax\": \"minimax\",","@@ -347,6 +349,28 @@ def lookup_models_dev_context(provider: str, model: str) -> Optional[int]:\n             if ctx:\n                 return ctx\n \n+    # Suffix-aware fallback: some providers (e.g. ollama-cloud) store\n+    # model IDs with :cloud / -cloud suffixes in models.dev while the\n+    # live API returns bare names.  Without this, kimi-k2.6 misses the\n+    # kimi-k2.6:cloud entry and falls through to stale OpenRouter metadata\n+    # reporting 32768 — tripping the 64k minimum-context guard.\n+    # The suffix-stripping in fetch_ollama_cloud_models() handles the\n+    # model-picker UX; this handles the context-length lookup path.\n+    for suffix in (\":cloud\", \"-cloud\"):\n+        suffixed_key = model + suffix\n+        entry = models.get(suffixed_key)\n+        if entry:\n+            ctx = _extract_context(entry)\n+            if ctx:\n+                return ctx\n+        # Also try case-insensitive\n+        suffixed_lower = model_lower + suffix\n+        for mid, mdata in models.items():\n+            if mid.lower() == suffixed_lower:\n+                ctx = _extract_context(mdata)\n+                if ctx:\n+                    return ctx\n+\n     return None\n \n "]},{"file":"scripts/release.py","hunks":["@@ -88,6 +88,7 @@\n     \"[redacted:email]\": \"Qwinty\",\n     \"[redacted:email]\": \"CalmProton\",\n     \"[redacted:email]\": \"mbac\",\n+    \"[redacted:email]\": \"nicoechaniz\",\n     \"[redacted:email]\": \"Ninso112\",\n     \"[redacted:email]\": \"wesleysimplicio\",\n     \"[redacted:email]\": \"SiliconID\",\n"]}]},{"kind":"pr","number":24066,"title":"fix(model-metadata): extend Kimi 32k guard to Nous OpenRouter suffix-match","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"528bba673","number":0},{"kind":"commit","value":"528bba673","number":0},{"kind":"pr","value":"#24502","number":24502}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":24156,"title":"fix: always consult OpenRouter metadata as last-resort fallback for context length","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#24268","number":24268}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":24441,"title":"fix(model-metadata): add explicit context length for kimi-k2.6 family","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"967c325da8a04e9722794e0d2b01dd79659d7127","number":0},{"kind":"pr","value":"#42986","number":42986}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":24502,"title":"Use nous portal as model metadata authority","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","tests/agent/test_model_metadata.py"]},{"kind":"pr","number":25464,"title":"fix: handle stale low context metadata for curated models","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":37548,"title":"fix(agent): respect model.context_length config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53569","number":53569}],"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":42986,"title":"fix(models): read OpenRouter live context_length before hardcoded catch-all","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/model_metadata.py","tests/agent/test_model_metadata.py"]},{"kind":"pr","number":52627,"title":"fix(cli): reject xai media models as main model","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":54636,"title":"fix(agent): respect model.context_length config override for minimum check (#8430)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#8430","number":8430},{"kind":"pr","value":"#8430","number":8430}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":55231,"title":"fix: honor explicit context_length config override for small-context models","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#8430","number":8430}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":104159,"title":"OpenRouter provider pins can now be set per model (provider_routing.models, #24495 / #100711)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["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"]}],"trust":{"default_tier":"verified","includes_suggested":false}}