fix-decouple-dispatch

Fix: decouple dispatch from OpenRouter model registry (non-built-in providers wedge dispatcher)

Metadata

Statusdone
Assignedagent-802
Created2026-07-25T14:19:29.360167938+00:00
Started2026-07-25T20:44:33.272817998+00:00
Completed2026-07-26T06:09:04.403864266+00:00
Tagsbug, dispatcher, model-registry, core
Tokens24100195 in / 60624 out
Failure reasonAgent exited with code 1

Description

Objective

Resolve the bug documented in wg-bug-openrouter-model-resolution.md (repo root) — the authoritative spec. The dispatcher wedges (zero spawns for a 60-min cooldown, re-triggered on every restart) whenever the active model uses a provider that is not a built-in registry entry (e.g. pi:zai:glm-5.2) AND the OpenRouter-backed registry refresh is unavailable (no OPENROUTER_API_KEY). The dependency is accidental: the pi executor resolves zai natively and never touches OpenRouter, yet OpenRouter's absence bricks dispatch.

Root cause (per the bug file)

wg has no static/native knowledge of non-built-in providers; its only dynamic model-learning path is wg profile refresh, hardcoded to OpenRouter (src/model_scout.rs; src/commands/service/mod.rs run_registry_refresh / do_registry_refresh). When that refresh cannot fetch (no key), it fails into a 60-min circuit breaker (record_registry_refresh_outcome, RegistryRefreshState.cooldown_until) that gates spawning; model resolution then cannot turn the spec into a spawnable model and the spawn breaker trips too.

Work — the 5-point fix from the bug file "Suggested fix"

  1. Decouple dispatch from the catalog refresh — a failed/cooling-down profile refresh MUST NOT block spawning. Refresh is metadata (pricing/rankings), not a dispatch prerequisite.
  2. Fail-soft model resolution — if a spec does not match a registry entry, defer to the executor (pi resolves zai itself); only hard-fail when the EXECUTOR reports it cannot resolve.
  3. First-class static model registration — implement the [[model_registry]] schema the warning text already implies, documented, so non-OpenRouter providers resolve with no network refresh.
  4. Allow disabling the OpenRouter refresh entirely (e.g. registry.openrouter_refresh = false). NOTE: the immediate mitigation coordinator.registry_refresh_interval = 0 is ALREADY applied in this project — it disables the periodic refresh, but that is a workaround; the decoupling in 1-3 is the real fix so dispatch never depends on it.
  5. Surface unresolvable models loudly at config-load / wg service start, not only in daemon logs.

Constraints

  • Do not regress the pi handler or built-in Anthropic resolution.
  • Backward compatible — existing OpenRouter-keyed setups keep working.
  • Add tests: (a) non-built-in provider spawns with NO OpenRouter key and an empty registry; (b) refresh failure/cooldown never gates spawning; (c) a [[model_registry]] entry resolves without network.

Validation (from the bug file "Expected behavior")

  • Dispatcher spawns workers for a model the configured executor resolves on its own, with NO OpenRouter dependency and an empty registry.
  • A failing catalog/refresh lookup for an unknown model is NON-FATAL to dispatch (no 60-min cooldown blocking spawns).
  • A documented [[model_registry]] (or equivalent) lets a non-OpenRouter provider be declared and resolved without contacting OpenRouter.
  • Auth/resolution is delegated to the executor when it owns it (pi for zai).
  • wg service start fails loudly with a clear message if a configured model is unresolvable AND refresh is unavailable, instead of silently wedging.
  • The bug file's reproduction (no-key start + wg add "noop") now spawns instead of wedging.

Reference

wg-bug-openrouter-model-resolution.md (repo root) — full report, evidence, reproduction.

Depends on

Required by

Log