Files
command-center/agent/managed-skills/omp-search-provider-fallback-debug/SKILL.md
T
Frank 8af40c402b chore(agent): commit the machine tier and the doctrine text the checkout was already running
Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and
`merge --ff-only` refuses while tracked paths are dirty. Discard is not available here
(workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed.

- `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already
  byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new
  prose — the commit exists so the trunk merge can proceed.
- `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat
  owns (AGENTS.md § Live-sync tiers).

`ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the
only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the
inert all-zero AWS example id — and they appear inside the two skill pages that teach
that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged
gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal
dies with this landing. No `--no-verify` was used: the gate ran and its verdict is
recorded here.

Refs #61
2026-10-21 01:44:51 -07:00

4.1 KiB

name, description
name description
omp-search-provider-fallback-debug Diagnose why the omp harness web_search silently uses a fallback provider (e.g. Kagi) instead of the configured primary (SearXNG): the fallbackChains masking pattern, the searxng.endpoint base-URL scheme/guard-chain trap, and the cli.js + content-type probes that prove it.

omp web_search silently using the wrong provider

Symptom: agent searches return results from a fallback provider (e.g. Kagi) even though modelRoles.web is set to the intended primary (e.g. web/searxng). The primary errors every time and the failure is invisible because a fallback answers successfully.

The masking mechanism (always check this first)

~/.omp/agent/config.yml:

  • modelRoles.web: is the primary (e.g. web/searxng).
  • retry.fallbackChains.web: is an ordered list tried when the primary throws. The FIRST entry silently serves whenever the primary errors. In this fleet it is web/kagi, so any SearXNG error == Kagi answers and you never see an error.

So "searches go through Kagi" almost always means "SearXNG is failing", not a routing mistake.

The SearXNG endpoint trap (the usual root cause here)

searxng.endpoint in config.yml must be a full base URL pointing at the direct JSON port, used verbatim by omp (no scheme auto-added, no normalize — confirmed in cli.js). Two ways it breaks:

  1. Bare host (e.g. search.franksdank.cloud, no scheme): omp does new URL(\${endpoint}/search`)` → throws immediately (URL needs a scheme). SearXNG never contacted.
  2. The guarded vhost (https://search.franksdank.cloud): returns Anubis' "Making sure you're not a bot!" challenge page at HTTP 200 text/html. omp calls o.json() unconditionally regardless of content-type → throws on the leading <. There is deliberately NO API path through the guard chain (JS challenge + UA denylist).

Correct value (direct published port, http scheme preserved — reachable from the LAN only):

searxng:
  endpoint: http://192.168.0.143:8081   # Dankclaw; NOT the vhost, NOT schemeless

The published port source (192.168.x/loopback/LAN) is in SearXNG's pass_ip, so it bypasses the JSON rate limiter (hardcoded API_MAX=4/IP/hr, not settable except via pass_ip).

Prove it (don't guess)

  1. Content-type probe, both paths — the decisive check:
    curl -sk -o /tmp/vh -w "vhost ct=%{content_type}\n" "https://search.franksdank.cloud/search?q=test&format=json"   # expect text/html (Anubis)
    curl -s  -o /tmp/dp -w "direct ct=%{content_type}\n" "http://192.168.0.143:8081/search?q=test&format=json"          # expect application/json
    
    vhost = text/html + direct = application/json confirms the trap.
  2. Code path, in the installed omp dist (NOT a worktree): /home/blank/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js (find install: dirname $(readlink -f $(which omp))). Grep the SearXNG provider:
    • searxng.endpoint config def → description "Base URL of a self-hosted SearXNG instance".
    • the request builder ...=e.replace(/\/+$/,""),new URL(\${r}/search`)...` → endpoint is verbatim, scheme NOT added.
    • X2c/J2c → if(!o.ok)throw then return await o.json() → HTML 200 passes .ok and blows up in .json().

Apply + verify

  • Fix is a harness config edit (cfg://searxng/endpoint prompts the user, or edit config.yml). Confirm with the user first — it is their live harness config.
  • Verification after applying: fire a real web_search and confirm it returns provider:"searxng" (not "searxng"-then-fallback / not "kagi").

If SearXNG answers JSON but returns empty results with unresponsive_engines (VPN-exit-IP CAPTCHA/429 on google/brave), omp's vcs throws a 503 → Kagi again. That's an engine-health problem, fix in [[SearXNG]]/[[SearXNG Providers]], not the endpoint.

Hardening option

Drop or reorder web/kagi / web/perplexity in retry.fallbackChains.web so primary failures surface instead of silently spending a paid Kagi credit. Flag to user before editing.