This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

coaxmetal/hermes: named custom provider so the cluster token is actually sent

Bare `provider = "custom"` resolves its credential from a host-gated
candidate list and only forwards OPENAI_API_KEY when base_url points at
openai.com (upstream #28660 / GHSA-76xc-57q6-vm5m). For any other host it
substitutes the literal placeholder "no-key-required", so the llama
cluster answered 401. Use a named custom_providers entry with key_env,
which is not host-gated, and rename the env var to LLAMA_API_TOKEN.

+54 -34
+54 -34
machines/coaxmetal/hermes.nix
··· 10 10 ]; 11 11 12 12 # Hermes Agent (Nous Research) — autonomous agent run as a native systemd 13 - # gateway, reachable over Matrix (@hermes:lassul.us). Model backend is Nous 14 - # Portal (one key, 266 models) driving MiniMax M2.5 — an agentic model, per 15 - # Nous's own guidance that the Hermes-4 chat models are NOT for the agent. 16 - # Any Portal model is a one-line settings.model.default swap. 13 + # gateway, reachable over Matrix (@hermes:lassul.us). Model backend is our 14 + # shared llama cluster: one vLLM instance behind an OpenAI-compatible 15 + # endpoint, so the model is whatever the cluster currently serves and a swap 16 + # is a one-line settings.model.default change. 17 17 services.hermes-agent = { 18 18 enable = true; 19 19 ··· 30 30 ]; 31 31 32 32 settings.model = { 33 - # Nous does NOT recommend the Hermes-4 chat models for Hermes Agent — it 34 - # wants a dedicated *agentic* model. Community/Nous consensus agentic pick 35 - # is the MiniMax M2 family (Nous co-optimizes it for Hermes Agent); M2.5 36 - # is the cheapest tool-calling option ($0.12/$0.48 per 1M). All 266 Nous 37 - # Portal models share this key, so this is just a model-id swap. 38 - # Alternatives: minimax/minimax-m2.7, z-ai/glm-4.6, deepseek/deepseek-v3.2. 39 - default = "minimax/minimax-m2.5"; 33 + # The cluster serves Qwen3.6-27B-FP8 (also aliased as "default" in 34 + # /v1/models — both resolve to the same weights); pin the explicit id so 35 + # logs and 400s name the actual model. Verified against the live endpoint 36 + # that it emits native tool_calls, which Hermes Agent requires. 37 + default = "Qwen3.6-27B-FP8"; 40 38 # hermes's built-in "nous" provider is OAuth-only and hardcodes the dead 41 39 # host inference.nousresearch.com (NXDOMAIN) — true on both 0.17.0 and 42 - # main; "nous-api" exists only in docs, not code. So we drive Nous Portal 43 - # through the generic OpenAI-compatible "custom" provider pointed at the 44 - # live endpoint, authenticating with the Nous key as OPENAI_API_KEY. 45 - provider = "custom"; 46 - base_url = "https://inference-api.nousresearch.com/v1"; 47 - # The custom provider can't auto-detect limits, so it left context 48 - # uncompressed and defaulted max_tokens to 65536 — once history passed 49 - # ~65k tokens, input + max_tokens exceeded the 131072 window and the 50 - # endpoint 400'd ("check the model name and other parameters"). Pin both: 51 - # context_length so hermes compresses before the window fills, and a sane 52 - # output cap (covers thinking + reply) so input + max_tokens stays under. 53 - context_length = 204800; 40 + # main, so we drive the cluster's OpenAI-compatible endpoint instead. 41 + # 42 + # It MUST be a *named* custom provider ("custom:<name>" + a 43 + # custom_providers entry), not bare "custom". Bare custom resolves its 44 + # credential from a host-gated candidate list 45 + # (hermes_cli/runtime_provider.py:1244) that only forwards 46 + # OPENAI_API_KEY when base_url's host is openai.com — a deliberate 47 + # anti-credential-leak measure (upstream #28660 / GHSA-76xc-57q6-vm5m). 48 + # For any other host it silently substitutes the literal placeholder 49 + # "no-key-required" and the endpoint 401s. A named entry instead reads 50 + # the key from its declared key_env (runtime_provider.py:692), which is 51 + # not host-gated. Named entries also ignore config.yaml's model.api_key, 52 + # so a stray `hermes model` run can no longer shadow this with a stale 53 + # key (that is exactly how the Nous→cluster switch broke). 54 + provider = "custom:llama"; 55 + base_url = "https://inference.p0.contact/v1"; 56 + # The custom provider can't auto-detect limits, so unset it leaves 57 + # context uncompressed and defaults max_tokens to 65536 — once history 58 + # outgrows the window, input + max_tokens exceeds it and the endpoint 59 + # 400s ("check the model name and other parameters"). vLLM reports 60 + # max_model_len 262144 for this model: pin that as the window so hermes 61 + # compresses in time, and keep a sane output cap (covers thinking + 62 + # reply) so input + max_tokens always stays under it. 63 + context_length = 262144; 54 64 max_tokens = 16384; 55 - 56 - # Frontier-on-tap alternative (uncomment to default to Claude instead): 57 - # default = "anthropic/claude-sonnet-4-6"; 58 65 }; 66 + 67 + # The endpoint hermes actually authenticates against. key_env names the 68 + # env var carrying the token; it comes from the hermes-env generator below 69 + # via environmentFiles, so the secret never enters the nix store. 70 + # api_mode is pinned because auto-detection only runs as a fallback. 71 + settings.custom_providers = [ 72 + { 73 + name = "llama"; 74 + base_url = "https://inference.p0.contact/v1"; 75 + key_env = "LLAMA_API_TOKEN"; 76 + api_mode = "chat_completions"; 77 + } 78 + ]; 59 79 60 80 # In rooms Hermes requires an @mention by default; DMs always respond. 61 81 settings.matrix.session_scope = "room"; ··· 88 108 # MATRIX_ALLOWED_ROOMS = "!yourRoomId:lassul.us"; 89 109 }; 90 110 91 - # Secrets (MATRIX_ACCESS_TOKEN + NOUS_API_KEY) come from clan vars below, 111 + # Secrets (MATRIX_ACCESS_TOKEN + LLAMA_API_TOKEN) come from clan vars below, 92 112 # merged into $HERMES_HOME/.env at activation. 93 113 environmentFiles = [ 94 114 config.clan.core.vars.generators.hermes-env.files."hermes.env".path ··· 122 142 persist = true; 123 143 }; 124 144 125 - # Nous Portal API key (portal.nousresearch.com, Plus subscription). 126 - clan.core.vars.generators.hermes-nous.prompts.nous-api-key = { 127 - description = "Nous Portal API key (NOUS_API_KEY) from portal.nousresearch.com"; 145 + # API token for the shared llama cluster, surfaced to hermes as 146 + # LLAMA_API_TOKEN (the custom_providers key_env above). 147 + # persist=true so the prompt value is stored once. 148 + clan.core.vars.generators.hermes-llama.prompts.llama-api-token = { 149 + description = "API token for the shared llama cluster (inference.p0.contact)"; 128 150 type = "hidden"; 129 151 persist = true; 130 152 }; ··· 133 155 clan.core.vars.generators.hermes-env = { 134 156 dependencies = [ 135 157 "hermes-matrix" 136 - "hermes-nous" 158 + "hermes-llama" 137 159 ]; 138 160 files."hermes.env" = { }; 139 161 runtimeInputs = [ pkgs.coreutils ]; 140 162 script = '' 141 - key=$(cat "$in"/hermes-nous/nous-api-key) 142 163 cat > "$out/hermes.env" <<EOF 143 164 MATRIX_ACCESS_TOKEN=$(cat "$in"/hermes-matrix/matrix-access-token) 144 - NOUS_API_KEY=$key 145 - OPENAI_API_KEY=$key 165 + LLAMA_API_TOKEN=$(cat "$in"/hermes-llama/llama-api-token) 146 166 EOF 147 167 ''; 148 168 };