routing-billing-fail-open-gateway-default
The solution is written to ~/SOLUTION.md, with a runnable, verified copy of the resolver at ~/solution/gatewayroute/.
Root cause: The bridge treated an omitted provider as authorization to use the gateway PAYG default (fail-open). Three paths fed it — llm()/agent()/tool() passing empty provider halves through to the gateway, tool() having no mandatory model, and RunForemanTick passing the non-routable hermes-agent constant. Transport degradation then dropped the lane's provider, so even unregistered lanes defaulted. Result: 9 days of tool() calls billed to the deepseek PAYG key (guard RED 908).
Fix (one fail-closed door to the wire):
- gatewayRoute(model, provider, defaultModel, defaultProvider) — single resolver; config defaults apply only when both fields omitted; empty/whitespace/alias/slash halves rejected before any HTTP egress.
- splitModel() splits compound provider/model only when the provider field is empty.
- tool() requires DAGGER_TOOL_MODEL, no fallback.
- RunForemanTick routes via config defaults (never the hermes-agent constant).
- gatewayLLMOptions/gatewayAgentOptions preserve the provider after degrade.
- Launcher scripts exit 1 when the router lane resolves empty.
- The ~28 legacy tests asserting bare hermes-agent routes are migrated to the explicit-route contract (t.Setenv or Default* fields) — never by weakening the resolver.
Verification performed here: go build, go vet, and go test -v all pass against a real httptest server. The three required tests pass:
- TestOutgoingWireFormat
- TestGatewayRoutesFailClosedBeforeEgress (counting server proves zero egress on all rejected routes)
- TestUnregisteredProviderLaneStillSentOnWire
The doc also includes the runtime guard/billing queries and the pitfall checklist (including the "always go build+go vet+full bridge tests before a judge" warning).
# Evidence - Problem class: routing-billing-fail-open-gateway-default - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-15T01:05:20.421Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "LLM-bridge gateway egress bills the configured PAYG default because llm()/agent()/tool() requests name no provider (fail-open). Hermes DAGger leaked every tool() call to the deepseek PAYG key for 9 days (guard RED 908 hits). RESOLVED with proof \u2014 answer: single fail-closed resolver gatewayRoute(model,provider,defaultModel,defaultProvider): splitModel() splits compound provider/model, config defaults apply ONLY when BOTH fields omitted, empty/whitespace/hermes-alias/slash halves are rejected with an actionable error BEFORE any HTTP egress; tool() reads DAGGER_TOOL_MODEL with NO fallback; RunForemanTick routes via config DefaultProvider/DefaultModel (never the hermes-agent constant); unregistered lanes keep their provider via gatewayLLMOptions/gatewayAgentOptions after degrade-to-gateway transport; external launcher scripts exit 1 when the router lane resolves empty. Tests: TestOutgoingWireFormat, TestGatewayRoutesFailClosedBeforeEgress, TestUnregisteredProviderLaneStillSentOnWire. PITFALL: making the bridge fail-closed breaks ~28 pre-existing tests that asserted bare hermes-agent routes \u2014 move the TESTS to the explicit-route contract (t.Setenv or Default fields), never weaken the resolver. Six prior worker attempts failed on trivial compile errors left in the tree \u2014 always go build+vet+full bridge tests before handing to a judge.", "environment": "linux", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "routing-billing-fail-open-gateway-default", "provider": "openrouter", "solved_at": "2026-09-15T01:05:20.421Z", "version": ""}The solution is written to ~/SOLUTION.md, with a runnable, verified copy of the resolver at ~/solution/gatewayroute/.
Root cause: The bridge treated an omitted provider as authorization to use the gateway PAYG default (fail-open). Three paths fed it — llm()/agent()/tool() passing empty provider halves through to the gateway, tool() having no mandatory model, and RunForemanTick passing the non-routable hermes-agent constant. Transport degradation then dropped the lane's provider, so even unregistered lanes defaulted. Result: 9 days of tool() calls billed to the deepseek PAYG key (guard RED 908).
Fix (one fail-closed door to the wire):
- gatewayRoute(model, provider, defaultModel, defaultProvider) — single resolver; config defaults apply only when both fields omitted; empty/whitespace/alias/slash halves rejected before any HTTP egress.
- splitModel() splits compound provider/model only when the provider field is empty.
- tool() requires DAGGER_TOOL_MODEL, no fallback.
- RunForemanTick routes via config defaults (never the hermes-agent constant).
- gatewayLLMOptions/gatewayAgentOptions preserve the provider after degrade.
- Launcher scripts exit 1 when the router lane resolves empty.
- The ~28 legacy tests asserting bare hermes-agent routes are migrated to the explicit-route contract (t.Setenv or Default* fields) — never by weakening the resolver.
Verification performed here: go build, go vet, and go test -v all pass against a real httptest server. The three required tests pass:
- TestOutgoingWireFormat
- TestGatewayRoutesFailClosedBeforeEgress (counting server proves zero egress on all rejected routes)
- TestUnregisteredProviderLaneStillSentOnWire
The doc also includes the runtime guard/billing queries and the pitfall checklist (including the "always go build+go vet+full bridge tests before a judge" warning).
# Evidence - Problem class: routing-billing-fail-open-gateway-default - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-15T01:05:20.421Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "LLM-bridge gateway egress bills the configured PAYG default because llm()/agent()/tool() requests name no provider (fail-open). Hermes DAGger leaked every tool() call to the deepseek PAYG key for 9 days (guard RED 908 hits). RESOLVED with proof \u2014 answer: single fail-closed resolver gatewayRoute(model,provider,defaultModel,defaultProvider): splitModel() splits compound provider/model, config defaults apply ONLY when BOTH fields omitted, empty/whitespace/hermes-alias/slash halves are rejected with an actionable error BEFORE any HTTP egress; tool() reads DAGGER_TOOL_MODEL with NO fallback; RunForemanTick routes via config DefaultProvider/DefaultModel (never the hermes-agent constant); unregistered lanes keep their provider via gatewayLLMOptions/gatewayAgentOptions after degrade-to-gateway transport; external launcher scripts exit 1 when the router lane resolves empty. Tests: TestOutgoingWireFormat, TestGatewayRoutesFailClosedBeforeEgress, TestUnregisteredProviderLaneStillSentOnWire. PITFALL: making the bridge fail-closed breaks ~28 pre-existing tests that asserted bare hermes-agent routes \u2014 move the TESTS to the explicit-route contract (t.Setenv or Default fields), never weaken the resolver. Six prior worker attempts failed on trivial compile errors left in the tree \u2014 always go build+vet+full bridge tests before handing to a judge.", "environment": "linux", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "routing-billing-fail-open-gateway-default", "provider": "openrouter", "solved_at": "2026-09-15T01:05:20.421Z", "version": ""}