feat(gateway): decision-only degrade + --config/WAYFINDER_CONFIG (desktop backend enablers) - #68
feat(gateway): decision-only degrade + --config/WAYFINDER_CONFIG (desktop backend enablers)#68tcballard wants to merge 2 commits into
Conversation
…configured [roadmap:WF-ROADMAP-0007]
WF-ADR-0042: a LIVE gateway with no [gateway.models] now returns the routing decision
({"wayfinder": {...decision..., "decision_only": true}} + an x-wayfinder-router-decision-only
header) from /v1/chat/completions instead of a 500. This is the onboarding floor for the desktop
app and the bare `serve` command: you see real routing the instant the gateway is up — before
wiring any backend — and a client can render decisions while a local model (Ollama) is still
warming up.
Only DELIVERY is skipped; the decision is computed offline by the same path as `--dry-run`, so it
is byte-identical to the dry-run decision (WF-ADR-0001 — no model call, key, or network enters the
scored path). The branch is gated strictly on an empty model set, placed after the dry-run return
(which keeps its own `dry_run` flag) and before the endpoint lookup; a genuine outage (models
configured but all cooling down) still returns its 503, and a misconfigured tier still 500s.
The no-models boot warning is updated to match (requests now return decisions, not failures).
Tests: empty-models live -> 200 decision_only with zero upstream calls; dry-run still flags
dry_run; the two decisions match field-for-field; the misconfig 500 is unaffected.
…ateway loads a fixed file [roadmap:WF-ROADMAP-0007] WF-ADR-0042: the gateway discovers its config by walking up from the working directory, but a launchd / systemd-launched gateway has an unpredictable cwd — and the desktop app wants the app and the service to share one well-known file (~/Library/Application Support/Wayfinder/wayfinder-router.toml). Add an explicit override: - config.find_config_file consults WAYFINDER_CONFIG first; a set-but-missing override resolves to None (a clear "your configured file isn't there") rather than silently walking up to another config above the cwd. - `serve --config PATH` sets WAYFINDER_CONFIG for the process; `service install --config PATH` threads it through _resolve_serve_args into the unit's ProgramArguments so the service loads a fixed file regardless of where it was launched. Precedence: --config -> WAYFINDER_CONFIG -> cwd walk-up -> built-in defaults. Fully backward-compatible (unset == the unchanged behavior). No change to the deterministic core (WF-ADR-0001). Tests: env override wins over walk-up; a missing override -> None (not a walk-up); unset -> walk-up unchanged; --config appears in the generated unit. Verified live: both `serve --config` and WAYFINDER_CONFIG load a config from a foreign cwd (healthz lists the configured model).
8b8a957 to
d91f232
Compare
…on-render contract WF-ROADMAP-0009 Phase 2, fixtures before UI. tools/record-fixtures.mjs spawns a deterministic in-process fake upstream and the real gateway (one temp-dir config per scenario) and records its actual responses — never hand-written JSON: dry-run decision payloads for a local turn (0.13) and a calibrated cloud turn (0.62 past the 0.5 cut, verified against the scorer), healthz in ok/degraded/offline shapes, a verbatim SSE transcript with the trailing `event: wayfinder` enrichment plus its response headers, a priced savings report with both routes populated, and an offline-config turn showing the cloud decision served local. The only normalization is request ids (12-hex short ids -> "fx-<scenario>", applied only at the request_id key and header — price_table_version is also 12 hex chars and stays verbatim) and ts fields, so re-running the recorder against the same gateway version produces an empty diff (verified across consecutive runs). The decision_only scenario self-skips until the gateway supports it (PR #68) and will record on the first re-run after it lands. These fixtures are the decision-render contract the Phase 2 component tests are table-driven from (WF-DESIGN-0012 "Testing the contract"). [roadmap:WF-ROADMAP-0009] [design:WF-DESIGN-0012]
|
Status: not rebaseable against current Since this PR was opened, This PR changes Both enablers still look worth having in the Rust gateway if they aren't already covered there:
Leaving this open pending a decision on whether to reimplement against the Rust gateway or close it as superseded. Generated by Claude Code |
Summary
Two small, backward-compatible gateway enablers for the macOS desktop app (WF-ADR-0042), each also
useful on its own. Both keep the deterministic decision offline (WF-ADR-0001) — no model call, key,
or network enters the scored path.
Decision-only degrade. A live gateway with no
[gateway.models]configured now answers/v1/chat/completionswith the routing decision ({"wayfinder": {…, "decision_only": true}}plus an
x-wayfinder-router-decision-only: trueheader) instead of a500. This is theonboarding floor: you see real routing the instant the gateway starts — before wiring any backend
— and a client can render decisions while a local model (e.g. Ollama) is still warming up. The
decision is computed by the same offline path as
--dry-run, so it is byte-identical to it.serve --config/WAYFINDER_CONFIG. Point the gateway at a specificwayfinder-router.tomlinstead of relying on the working-directory walk-up, so a launchd / systemd gateway (whose cwd is
unpredictable) and the desktop app can share one well-known file.
service install --config PATHbakes it into the unit. Precedence:
--config→WAYFINDER_CONFIG→ cwd walk-up → defaults.Scope
Included
gateway.py: the no-models → decision-only branch — gated strictly on an empty model set, placedafter the
--dry-runreturn (which keeps its owndry_runflag) and before the endpoint lookup;plus the updated no-models boot warning.
config.py:WAYFINDER_CONFIGprecedence infind_config_file(a set-but-missing override is aclear
None, never a silent walk-up).cli.py:serve --config,service --config, and_resolve_serve_args(host, port, config=None).test_gateway.py(3),test_config.py(3),test_service.py(2).CHANGELOG.mdentries under## Unreleased.Excluded
desktop-v*lane.WF-ADR-0042document — lands with the desktop app; these enablers reference it forward.503path(a genuine outage — models configured but all cooling down — still returns its
503; amisconfigured tier still
500s).Verification
ruff check . && mypy wayfinder_router && pytest -q:Decision-only — a live gateway with no models, from a clean config (no upstream is contacted):
--config/WAYFINDER_CONFIGloading a fixed file from a foreign cwd (/, where the walk-up findsnothing):