A local, subscription-powered, multi-model research / orchestration harness.
New model entering this project: read this whole file first. It defines the goal, what every file is and why it's here, the verified facts you can't otherwise know, and the goals (not prescriptions) for what to build. Verify before you trust — re-run the smoke tests at the bottom. Then read
HANDOFF.md— the latest audit findings, stack decision, and the owner-approved plan.
You give a research prompt. A thin "brain" model decomposes it into sub-tasks, distributes them across different model families in parallel (Google Gemini, OpenAI GPT, Anthropic Claude/Opus), has them cross-check each other, and synthesizes a verified answer.
Two first-class goals, equally important:
- Better research than any single model — parallel breadth + genuine cross-vendor disagreement + fact-verification, so independent models catch each other's errors and hallucinations.
- Token economy. Gemini and GPT are FREE here (subscription); Opus/Claude is rate-limited. Push ALL heavy lifting (web research, reading, drafting) onto Gemini/GPT and keep Opus a THIN orchestrator/judge. Improve quality AND cut Opus consumption at the same time.
Domain-agnostic: extracted from a finance project (see Provenance) but carries none of that logic.
Opus = thin brain only (decompose tasks, judge, final synthesis). Gemini/GPT = all the heavy work. Do NOT wrap each cheap-vendor call inside an Opus subagent — that re-spends the exact Opus tokens you are trying to save. Keep the control-flow cheap:
- cheapest: orchestrate the parallel
ask_gemini.sh/ask_codex.shcalls with a plain bash/Python fan-out (background jobs), then let ONE model read the assembled results in a single pass; - or run any "glue/transport" subagents on Haiku/Sonnet, reserving Opus for the final judge/synthesis.
The three vendor legs live in the shared llm-legs
library, consumed as a git submodule at lib/legs/ (clone this repo with
git clone --recurse-submodules, or run git submodule update --init after a plain clone).
One wrapper fix there propagates to every consumer project via a pin bump. Descriptions below
document the legs as used here:
lib/legs/ask_gemini.sh— Google leg via the Antigravity CLI (agy), subscription-only. Headless--printwith the model PINNED per call (AGY_MODEL, default"Gemini 3.1 Pro (High)"; in-family fallbackAGY_MODEL_FALLBACK, default"Gemini 3.1 Pro (Low)"). Weak flash/lite tiers are refused (GEMINI_ALLOW_WEAK=1overrides). Quota exhaustion is detected per call and exits 5 (the orchestrator drops the leg instantly). agy does NOT report the served model — the audit row records the pin, unverified. ~8-15 s per simple call. Modes:--probe(no model call),--list-models. The retired multi-transport Gemini-CLI wrapper lives inlib/legacy/ask_gemini_cli.sh(the Gemini CLI dies ~2026-06-18; migrated early 2026-06-12).lib/legs/ask_codex.sh— OpenAI GPT (gpt-5.5) leg via Codex CLI. Subscription, read-only sandbox. Does NOT hardcode the model (auto-tracks the CLI flagship) but guards the tier — rejects weak tiers (exit 3). Reasoning effort pinnedxhigh(CODEX_EFFORT=mediumfor light calls). Logs{requested, served}. Mode:--probe. Overrides:CODEX_MODEL,CODEX_ALLOW_WEAK=1.lib/legs/ask_claude.sh— Anthropic Claude leg. Subscription (OAuth), headlessclaude -p --output-format json— runs from ANY environment, no Claude Code session required. Defaults to theopusalias; guards the tier by parsing.modelUsage(auxiliary haiku entries do not poison the guard — the requested-alias match wins, else dominant-by-outputTokens). UnsetsANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN(billing trap), disallows mutating tools,--setting-sources projectso user-level prefs never leak into judgments. Modes:--probe,--extract-served-model. Overrides:CLAUDE_MODEL(sonnet/opus are the working tiers),CLAUDE_ALLOW_WEAK=1. Cost ceiling: Opus — Mythos-class models (fable/mythos) are refused outright, exit 4, no override (owner decision 2026-06-11). THIN roles only (token economy): dispute adjudication + adversarial review; never heavy search.data/served-models.jsonl— append-only audit of which model actually served each Gemini/GPT call (so you can PROVE pro vs flash). Created on first call..gitignore— ignores.secrets/,.venv/,data/cache/, logs.
Calling a leg: lib/legs/ask_codex.sh "<prompt>" or echo "<prompt>" | lib/legs/ask_gemini.sh → prints the
model's text answer to stdout. A leg that cannot serve a strong model exits non-zero and logs
weak_tier:1 — treat that leg as unavailable rather than trusting a cheap model.
research.py is the first dependency-free orchestration layer for offer research (products,
services, subscriptions, accounts).
CLI:
python3 research.py "find the cheapest MacBook Air M2 in Ukraine"python3 research.py --effort 3 --site olx.ua "<prompt>"— deeper run restricted to one sitepython3 research.py --list-runs
Local Web UI — easiest way to use the system:
- Double-click
start.commandin Finder (or run./start.command) — it starts the server and opens the browser. Ctrl+C in its Terminal window stops it. - Manual equivalent:
python3 research.py --serve --port 8765→ openhttp://127.0.0.1:8765. - The form has a prompt box, an effort slider (1-4), an optional site restriction field, a clickable run history, live progress (model-call counter), and a degradation pill when a model leg goes down mid-run.
Effort levels (--effort, slider, or names quick/standard/deep/max). Scaling adds breadth and
verification layers; it NEVER swaps in weaker model tiers:
| level | tasks | rescue rounds | max rescue items | legs per rescue | adversarial review | Claude seat (adjudication/review) | codex effort |
|---|---|---|---|---|---|---|---|
| 1 quick | 3 | 1 | 4 | 1 | – | Sonnet | medium |
| 2 standard (default) | 4 | 1 | 6 | 1 | – | Sonnet | medium search / xhigh judge |
| 3 deep | 5 | 2 | 8 | 1 (rotates) | Gemini | Opus | medium search / xhigh judge |
| 4 max | 6 | 2 | 12 | 2 (both) | Gemini + Opus | Opus | xhigh everywhere |
The Claude seat is tiered by cost: Sonnet at low effort, Opus at high effort. Opus is the hard
cost ceiling — lib/legs/ask_claude.sh refuses Mythos-class models (fable/mythos) outright, no
override. Dispute adjudication runs at EVERY level (it is cheap and only fires when models
disagree); review layers appear from level 3.
What v1 does:
- asks Codex to decompose the prompt into independent search tasks (count from effort);
- runs Codex and Gemini in parallel for the primary search, with per-leg query templates
(Codex never sees
site:operators — it returns empty findings on them; Gemini gets them appended when a site restriction is active); - asks both legs for structured findings with
title,price,currency,url,marketplace,availability,location,shipping,evidence,confidence,source_model, andchecked_at; - dedupes by marketplace listing ID (OLX
-ID..., PrompNNN-, Rozetka) so the same listing behind different language prefixes merges; flags cross-leg price disagreements >10% asdisputed(kept, never averaged); - verifies URLs with stdlib HTTP
HEADandGETfallback; with--siteit also rejects any off-domain URL (off_site); - verifies marketplace listings against the LIVE page (Stage 2 minimal): for URLs with a
known listing-ID pattern the page itself is fetched — non-active ads are rejected
(
listing_inactive), and the live page price overrides the model's claim (price_corrected_fromkeeps the trail). Search-index caches feed models stale prices that BOTH vendors agree on — only the page is the truth; - never silently discards a failed-verification item — a broken/moved URL, timeout, or
missing price is a failure to verify, not a disproof. Such items get RESCUE rounds: a model
that has not tried yet (other vendor first, then the originator; BOTH legs at effort 4) hunts
for the same item's working URL / live price. Items the rescue budget skipped are counted in
run.json.recheck_dropped(shown in the UI). Only disproven/policy rejections are final:out_of_stock,off_site,parse_failed,adjudicated_reject; - items still unverified after rescue go into the final report's "Unverified — check manually" section with URLs and what to check — they may be exactly what the user wanted;
- Claude (thin arbiter: Sonnet at effort 1-2, Opus at 3-4) adjudicates surviving price disputes by evidence at every effort level;
- uses Codex as the final judge/synthesizer; at effort >= 3 the draft is adversarially reviewed by another vendor (Gemini, then Claude at effort 4) and revised by Codex on concrete issues;
- marks a "running" run with no heartbeat for ~25 min as failed (stale-run detection);
- streams a live event feed: every run appends to
runs/<id>/events.jsonl(lifecycle, every model call start/finish, breaker trips, straggler kills), served as SSE atGET /api/runs/<id>/events; runs can be cancelled (POST /api/runs/<id>/cancelor the UI button) — in-flight calls are killed and a partial report is still produced; - survives provider failures: a leg with 3 consecutive failed calls is disabled for the rest of the run (circuit breaker; shown in the UI and warned about in the report), slow stragglers are killed once 75% of a phase has returned (their items go to rescue), per-phase timeouts come from the effort profile, and the judge seat falls back codex → claude → gemini;
- protects the scarce Gemini quota: the CLI call is hard-bounded (
GEMINI_CLI_TIMEOUT, default 240 s — the CLI HANGS in internal retries when the pro quota is exhausted, it does not fail), quota errors exit with code 5 and instantly disable the leg for the run (same for Codex usage-limit errors), search calls fail fast instead of riding the 5-12 min agy fallback, at mostRESEARCH_GEMINI_CONCURRENCY(default 2) gemini calls run at once, and each run has a gemini call budget from its effort profile (6/9/12/16) — primary search is funded first.
Run artifacts are saved under runs/<timestamp-slug>/:
run.json— status and metadata;tasks.json— decomposed tasks;raw/— raw stdout/stderr/meta for each model call;findings.json— parsed and deduplicated findings;verification.json— verified and rejected items with reasons;final.md— final user-facing report.
Aggregated model quality rows are appended to data/model-stats.jsonl.
Useful local tuning knobs:
RESEARCH_MODEL_TIMEOUT_SEC=900— per model-call timeout;RESEARCH_URL_TIMEOUT_SEC=12— per URL check timeout;RESEARCH_MAX_TASKS— overrides the effort profile's task count when set (capped 3-6);RESEARCH_MAX_RECHECK_ITEMS— overrides the effort profile's per-round recheck cap when set;RESEARCH_MAX_PRIMARY_WORKERS=6— primary fan-out concurrency.
v1 limitations:
- Claude is used only in THIN seats (adjudication, review) at effort >= 3 — by design, not a gap.
- There are no site-specific OLX / Prom.ua scrapers; models do web search, code verifies the returned links and facts.
- The Web UI is local-only and uses Python stdlib, not a production web framework.
- For Ukrainian shopping prompts, missing currency defaults to UAH when a price exists.
Confirmed live in the source project (2026-06-07/09); re-verify here.
- Cross-vendor BY SUBSCRIPTION runs headless, no per-call API: Claude (
claude -p --output-format json), Codex (codex exec --sandbox read-only --skip-git-repo-check, gpt-5.5), Gemini (gemini -m pro --approval-mode plan -o json). - Gemini tier trap: the CLI silently defaults to a flash-lite tier — you MUST force
-m proand VERIFY the served model (the wrapper does both by readingstats.models[*].roles.main, not by grepping every model name in stats). The only Gemini-3 pro id on the consumer CLI isgemini-3.1-pro-preview(gemini-3.1-pro/-latestreturn 404). - Web access is real (verified): all three browse/ground. Codex
web_searchis a hosted tool (works even in the read-only sandbox); Gemini uses Google Search grounding. - Auth/billing traps: a stray
ANTHROPIC_API_KEYsilently switches Claude to API billing —unsetit in any headless/cron run; neverclaude --bare(ignores the OAuth). The Codex global config may bedanger-full-access— the wrapper always forces--sandbox read-only. - Dated risk: Gemini CLI subscription access reportedly ends ~2026-06-18 → migrate to
Antigravity CLI (
agy; the wrapper already falls through to it) or a free Gemini Flash API key.claude -pdraws from a capped Agent-SDK credit pool — another reason to keep Claude thin.
- Blind independent fan-out — ask each vendor without it seeing the others; independence is the whole point, don't let them converge prematurely.
- Adjudicate disagreements, don't average them — an independent step that finds WHERE they diverge and WHY, and which side rests on a verified fact vs a generic default.
- Verify load-bearing claims against the live web (the free legs do this well).
- Cross-vendor adversarial re-check — have a DIFFERENT family try to refute the synthesis before you trust it (3 models agreeing ≠ proof; beware herding and false precision).
- Resolve by verified fact, not by vote — a verified minority can override a generic majority.
Design the orchestrator yourself — the Claude Code Workflow tool, a plain bash/Python fan-out, or something else. The only fixed, proven pieces are the two wrappers and the facts above. Optimize for the two goals (better research + minimal Opus).
Prior tests were in the source project; auth/paths must be re-confirmed in THIS folder:
lib/legs/ask_codex.sh --probe→ reports a non-weak served model (gpt-5.5).lib/legs/ask_gemini.sh --probe→ reportsgemini-3.1-pro-preview(NOT a flash/lite tier). 2b.lib/legs/ask_claude.sh --probe→ reports a non-weak served model (e.g.claude-opus-4-8, weak_tier=0). Requires a logged-inclaudeCLI (subscription OAuth), works headless.- Ask each leg for a current web fact + source URL; confirm it's real (web grounding works).
- Run a normal Gemini call such as
lib/legs/ask_gemini.sh "Reply with exactly: ok";--probedoes not append an audit row. cat data/served-models.jsonl→ confirm Gemini'sservedis the pro tier withweak_tier:0.
Extracted from the find-truth finance risk-radar. Only the vendor-transport CORE + the verified
subscription/headless knowledge were carried over — none of the finance logic, data, strategy, or
reports. Wrapper comments may reference pipeline/preflight.sh (a find-truth file, not copied); the
--probe mode they mention is generic and reusable here.
Subscription-only (no per-call API unless you deliberately add a key under .secrets/). Read-only
legs. The owner controls all git — do not git init / commit / push without an explicit instruction.