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
79 lines
4.1 KiB
Markdown
79 lines
4.1 KiB
Markdown
---
|
|
name: omp-search-provider-fallback-debug
|
|
description: "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):
|
|
|
|
```yaml
|
|
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"`).
|
|
|
|
## Related gotcha (same masking, different cause)
|
|
|
|
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.
|