gigaxity-deep-research
yoloshii/gigaxity-deep-research/AGENTS.md
This is the agent reference for Gigaxity Deep Research, an open-source deep research MCP server for Claude Code, Hermes, Cursor, and other MCP-compatible agents. Qwen3-30B-A3B-Thinking runs via OpenRouter, the Triple Stack search MCPs (Context7, Exa, Jina) handle web/docs/code retrieval, and the bundled research-workflow skill routes queries to the right tool per query class. This file is loaded by Claude Code (CLAUDE.md) and other MCP-compatible agents (AGENTS.md is byte-identical). It documents how to operate the six MCP tools this server exposes…
- Reads credentials
# Gigaxity Deep Research — Agent Reference
This is the agent reference for Gigaxity Deep Research, an open-source deep research MCP server for Claude Code, Hermes, Cursor, and other MCP-compatible agents. Qwen3-30B-A3B-Thinking runs via OpenRouter, the Triple Stack search MCPs (Context7, Exa, Jina) handle web/docs/code retrieval, and the bundled `research-workflow` skill routes queries to the right tool per query class.
This file is loaded by Claude Code (`CLAUDE.md`) and other MCP-compatible agents (`AGENTS.md` is byte-identical). It documents how to operate the six MCP tools this server exposes (two primitives plus four deep-research tools) and how to plug them into the broader deep research stack.
If your harness loads a global `CLAUDE.md` or `AGENTS.md` (Claude Code, Codex, Cursor, Hermes, etc.), copy the **instruction block** at the bottom of this file into that global file. For standalone agents that take a system prompt instead, paste the block directly into the system prompt. That single block makes any compatible agent automatically route research queries through this MCP plus the six companion MCPs (Context7, Exa, Exa Answer, Jina, Brightdata fallback, gptr-mcp) in the full deep research stack.
---
## Tool surface
The MCP server exposes **two primitives** plus **four deep-research tools** — six tools total. Pick a primitive when you want raw or combined behavior in one call; pick a deep-research tool when you want to drive discovery, synthesis, or reasoning as a discrete step.
**Primitives**
| Tool | Use for | Token cost (typical) |
|---|---|---|
| `mcp__gigaxity-deep-research__search` | Raw multi-source aggregation (SearXNG + Tavily + LinkUp + Brave + RRF). No LLM call. | 0 LLM tokens; search-API quotas only |
| `mcp__gigaxity-deep-research__research` | Combined search + synthesis with citations in a single call. The simple pipeline. | ~3000–8000 |
**Deep-research tools**
| Tool | Use for | Token cost (typical) |
|---|---|---|
| `mcp__gigaxity-deep-research__ask` | Quick conversational answer; speed > depth (direct LLM, no search hop) | ~500–1500 |
| `mcp__gigaxity-deep-research__discover` | Cold-start exploration; surfaces explicit/implicit/related/contrasting angles + gap detection | ~2000–5000 |
| `mcp__gigaxity-deep-research__synthesize` | Citation-aware fusion of pre-gathered content; CRAG quality gate, contradiction surfacing | ~5000–10000 |
| `mcp__gigaxity-deep-research__reason` | Deep synthesis with explicit chain-of-thought depth control over pre-gathered content | ~5000–15000 |
All six tools accept an optional `openrouter_api_key` parameter for per-request key override (multi-tenant deployments). REST callers can use the `X-OpenRouter-Api-Key` header for the same purpose.
---
## When to call which tool
```
Query class?
├── "what is X right now / latest version" (single fact, speed-critical)
│ → ask
│
├── "tell me about X" (cold start, no prior context, want breadth)
│ → discover
│
├── "compare X vs Y" or "best practice for X" (cross-source synthesis, citations matter)
│ → synthesize
│
└── "why did X happen" or "explain the reasoning behind X" (CoT reasoning matters)
→ reason
```
For the full classification tree across the **entire** Triple Stack (when to use Context7, Exa, Jina, Brightdata fallback alongside this MCP), see the bundled [`skills/research-workflow/SKILL.md`](skills/research-workflow/SKILL.md).
---
## Environment variables
All variables are prefixed `RESEARCH_`. Set in `.env` (gitignored) or pass via the MCP `env` config block.
| Variable | Default | Purpose |
|---|---|---|
| `RESEARCH_LLM_API_BASE` | `https://openrouter.ai/api/v1` | LLM endpoint. For local inference, set to `http://localhost:8000/v1` etc. |
| `RESEARCH_LLM_API_KEY` | *(empty — required)* | OpenRouter or local-server API key |
| `RESEARCH_LLM_MODEL` | `qwen/qwen3-30b-a3b-thinking-2507` | Any OpenAI-compatible chat-completions model |
| `RESEARCH_LLM_TEMPERATURE` | `0.85` | |
| `RESEARCH_LLM_TOP_P` | `0.95` | |
| `RESEARCH_LLM_MAX_TOKENS` | `16384` | |
| `RESEARCH_LLM_TIMEOUT` | `120` | Per-request seconds. An httpx *operation* timeout, not a wall-clock bound on the call |
| `RESEARCH_LLM_MAX_RETRIES` | `2` | SDK retries per request → `(value + 1)` attempts. Multiplies the timeout above, since read-timeouts retry like 429s. Lowering it shortens a stalled chain and gives up a recovery attempt |
| `RESEARCH_LLM_WALL_CLOCK_CAP` | `0` (disabled) | Absolute ceiling in seconds for one model call including its retry chain. Server-wide: applies to MCP, REST and library calls made through the project's client wrapper (a client injected into `SynthesisEngine` owns its own timeouts). Off by default — a slow local endpoint can legitimately exceed any fixed ceiling |
| `RESEARCH_PROGRESS_HEARTBEAT_INTERVAL` | `30` | Seconds between "still running" progress notifications during a model call. A call shorter than one interval emits none |
| `RESEARCH_PROGRESS_SEND_TIMEOUT` | `10` | Seconds one notification may take before reporting is disabled for that request |
| `RESEARCH_SEARXNG_HOST` | `http://localhost:8888` | Primary search source — required |
| `RESEARCH_SEARXNG_ENGINES` | `brave,duckduckgo,startpage,mojeek,wikipedia` | Matches the bundled SearXNG `settings.yml.example` enabled list |
| `RESEARCH_TAVILY_API_KEY` | *(empty)* | Optional additional connector — runs in parallel with SearXNG, RRF-fused |
| `RESEARCH_LINKUP_API_KEY` | *(empty)* | Optional additional connector — runs in parallel with SearXNG, RRF-fused |
| `RESEARCH_BRAVE_API_KEY` | *(empty)* | Optional additional connector — official Brave index, keyed API (no CAPTCHA risk). Free tier ~1,000 queries/month |
| `RESEARCH_BRAVE_COUNTRY` | *(empty)* | Optional ISO country code for geo-targeting, e.g. `us` |
| `RESEARCH_BRAVE_SAFESEARCH` | `off` | `off`, `moderate`, or `strict` |
| `RESEARCH_DEFAULT_TOP_K` | `10` | Results per source |
| `RESEARCH_RRF_K` | `60` | RRF fusion constant |
| `RESEARCH_HOST` | `127.0.0.1` | REST mode only. Default loopback; bind `0.0.0.0` only behind an authenticated reverse proxy. |
| `RESEARCH_PORT` | `8000` | REST mode only |
**Critical:** `RESEARCH_LLM_API_KEY` and `RESEARCH_SEARXNG_HOST` are the only two values you must set. Everything else has a working default.
---
## Anti-patterns
```
❌ Use ask() for cross-source comparisons
✅ Use synthesize() — it runs the quality gate + contradiction detector
❌ Use discover() when you already have the URLs you want analyzed
✅ Use synthesize() with the URLs already in the prompt
❌ Use reason() for "what is X" lookups
✅ Use ask() — reason() burns tokens on a CoT you don't need
❌ Pass the same OpenRouter key in every request body
✅ Set RESEARCH_LLM_API_KEY in env, override per-request only when multi-tenant
❌ Treat Tavily, LinkUp and Brave as failover-on-error for SearXNG
✅ SearXNG, Tavily, LinkUp and Brave all run in parallel via `asyncio.gather` and get RRF-fused. The three keyed connectors are optional **additional** parallel sources, not fallbacks — they fire whenever their key is configured. SearXNG is the only one that's required (the others silently drop out when their key is empty).
❌ Rely on SearXNG alone for general web search under automated load
✅ Configure at least one keyed connector. SearXNG's engines are scraped, so they CAPTCHA under sustained automation and fail *silently* — HTTP 200 with degraded results. Brave is the cheapest durable lane: official index, keyed API, ~1,000 free queries/month.
❌ Run REST mode bound to 0.0.0.0 on a shared/exposed machine
✅ Bind to 127.0.0.1 unless behind an authenticated reverse proxy
```
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| `RESEARCH_LLM_API_KEY` missing on startup | env var not set | Set in `.env` or MCP `env` block |
| 401 from OpenRouter on every call | Invalid or expired key | Regenerate at https://openrouter.ai/keys |
| Empty results from `discover` / `synthesize` | SearXNG host unreachable | `curl $RESEARCH_SEARXNG_HOST/healthz` — should return 200. REST mode: `GET /api/v1/health/connectors` probes every connector at once |
| `Qwen3-30B-A3B-Thinking not found` from OpenRouter | Model slug typo | Use exactly `qwen/qwen3-30b-a3b-thinking-2507` |
| MCP server boots but Claude Code shows no tools | stdio path / venv mismatch | Confirm `command` in `~/.claude.json` points at the venv's Python (not system Python) |
| 429 rate limit from OpenRouter | Quota exceeded | Reduce `RESEARCH_DEFAULT_TOP_K`; consider local-inference branch |
| Latency > 30 s on `synthesize` | Quality gate enabled with many sources | Lower `RESEARCH_DEFAULT_TOP_K` to 5; switch preset to `fast` |
| Per-request `X-OpenRouter-Api-Key` header ignored | Header name typo | Exact header is `X-OpenRouter-Api-Key` (case-insensitive in HTTP, but exact spelling in alias) |
| Repeating an identical request is never fast (Docker) | The `research_cache` named volume is root-owned; the container runs as uid 1000, so no entry is ever written. Volumes created before v0.11.0 are affected. | `docker compose down && docker volume rm <project>_research_cache && docker compose up -d`, or chown the volume's mountpoint to `1000:1000`. Details: [`docs/troubleshooting.md`](docs/troubleshooting.md) → "Cache never hits" |
---
## Architecture quick-reference
| Layer | Path | Notes |
|---|---|---|
| MCP entry | `run_mcp.py` → `src/mcp_server.py` | FastMCP, stdio transport |
| REST entry | `src/main.py` → `src/api/routes.py` | FastAPI, uvicorn |
| LLM client | `src/llm_client.py` | OpenRouter on `main`, generic OpenAI-compat on `local-inference` branch |
| Discovery | `src/discovery/` | Routing, expansion, decomposition, focus modes |
| Synthesis | `src/synthesis/` | Quality gate, contradictions, presets, outline, RCS |
| Connectors | `src/connectors/` | SearXNG, Tavily, LinkUp, Brave |
| Config | `src/config.py` | All `RESEARCH_*` env vars; pydantic settings |
---
## Companion MCPs — full deep research setup
The full deep-research workflow uses seven MCPs. The middle three (`context7` + `exa` + `jina`) form the **Triple Stack** — the search/docs/code trio. This repo ships the most complex one (`gigaxity-deep-research`); the other six each take 30 seconds to register in `~/.claude.json`.
### Companion MCP configs
```json
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp", "--api-key", "YOUR_CONTEXT7_API_KEY_PLACEHOLDER"]
},
"exa": {
"type": "http",
"url": "https://mcp.exa.ai/mcp?exaApiKey=YOUR_EXA_API_KEY_PLACEHOLDER&tools=web_search_exa,web_search_advanced_exa,get_code_context_exa,crawling_exa"
},
"exa-answer": {
"type": "stdio",
"command": "/absolute/path/to/gigaxity-deep-research/companions/exa-answer/.venv/bin/python",
"args": ["/absolute/path/to/gigaxity-deep-research/companions/exa-answer/mcp_server.py"],
"env": { "EXA_API_KEY": "YOUR_EXA_API_KEY" }
},
"jina": {
"type": "stdio",
"command": "python3",
"args": ["/absolute/path/to/gigaxity-deep-research/companions/jina-mcp/mcp_server.py"],
"env": { "JINA_API_KEY": "YOUR_JINA_API_KEY" }
},
"brightdata_fallback": {
"type": "stdio",
"command": "/absolute/path/to/gigaxity-deep-research/companions/brightdata-fallback/.venv/bin/python",
"args": ["/absolute/path/to/gigaxity-deep-research/companions/brightdata-fallback/mcp_server.py"],
"cwd": "/absolute/path/to/gigaxity-deep-research/companions/brightdata-fallback",
"env": {
"BRIGHTDATA_API_TOKEN": "YOUR_BRIGHTDATA_API_TOKEN",
"BRIGHTDATA_ZONE": "YOUR_WEB_UNLOCKER_ZONE_NAME"
}
},
"gptr-mcp": {
"type": "stdio",
"command": "/absolute/path/to/gptr-mcp-source/.venv/bin/python",
"args": ["/absolute/path/to/gptr-mcp-source/server.py"],
"cwd": "/absolute/path/to/gptr-mcp-source",
"env": {
"OPENAI_API_KEY": "YOUR_OPENAI_API_KEY",
"TAVILY_API_KEY": "YOUR_TAVILY_API_KEY",
"TWITTERAPI_IO_KEY": "YOUR_TWITTERAPI_IO_KEY",
"RETRIEVER": "social_openai,twitterapi,tavily",
"SOCIAL_OPENAI_DOMAINS": "reddit.com,youtube.com",
"SOCIAL_OPENAI_MODEL": "gpt-4o",
"FAST_LLM": "openai:gpt-4o-mini",
"SMART_LLM": "openai:gpt-4o",
"STRATEGIC_LLM": "openai:gpt-4o-mini"
}
}
```
> The `social_openai` + `twitterapi` retrievers above are a **first-party opt-in add-on**, not part of a vanilla GPT Researcher install. Enable them once via [`companions/gptr-mcp/CUSTOM_RETRIEVERS.md`](companions/gptr-mcp/CUSTOM_RETRIEVERS.md). Until you do, use `"RETRIEVER": "tavily"` — otherwise GPT Researcher silently falls back to Tavily for the unknown names (no error, no social results). No paid X key? Drop `twitterapi` and set `SOCIAL_OPENAI_DOMAINS` to `reddit.com,x.com,youtube.com`. `social_openai`'s Reddit results come from the third-party [Arctic Shift](https://github.com/ArthurHeitmann/arctic_shift) archive (off-IP, not reddit.com) — comment text is near-real-time but engagement scores lag ~36h, so don't treat Reddit scores as current.
Sign-up links:
- Context7: https://context7.com
- Exa (one key for both `exa` and `exa-answer`): https://exa.ai
- Jina (free 10M tier): https://jina.ai
- OpenRouter (for `gigaxity-deep-research`): https://openrouter.ai/keys
- Brightdata Web Unlocker (paid; optional): https://brightdata.com
- OpenAI (for gptr-mcp): https://platform.openai.com/api-keys
- Tavily (for gptr-mcp fallback retriever): https://tavily.com
- TwitterAPI.io (for gptr-mcp's `twitterapi` retriever; paid, opt-in): https://twitterapi.io
`exa-answer`, `jina`, and `brightdata_fallback` are **bundled in this repo** under [`companions/`](companions/) — each is a single-file Python MCP server with a `requirements.txt`. `jina` is self-hosted rather than pointed at `https://mcp.jina.ai/v1` because the hosted server's search family routes through a paid-only lane that refuses free-tier trial credits and reports the refusal as `Internal Server Error`; see [`companions/jina-mcp/README.md`](companions/jina-mcp/README.md). `gptr-mcp` is the [upstream MCP shim](https://github.com/assafelovic/gptr-mcp) around [GPT Researcher](https://github.com/assafelovic/gpt-researcher) — `companions/gptr-mcp/install.sh` clones it into a sibling directory rather than vendoring source. Install per [`docs/guides/setup-companions.md`](docs/guides/setup-companions.md).
### Bundled skill
[`skills/research-workflow/SKILL.md`](skills/research-workflow/SKILL.md) is a vendored copy of the universal-format skill that drives the classification logic across all seven MCPs. Symlink or copy it into your skills directory:
```bash
# Claude Code (per-user skills)
mkdir -p ~/.claude/skills
ln -s /path/to/gigaxity-deep-research/skills/research-workflow ~/.claude/skills/research-workflow
```
---
## Instruction block — paste into your harness's global CLAUDE.md / AGENTS.md (or system prompt)
Drop the block below into the global file your harness loads (`~/.claude/CLAUDE.md` for Claude Code; the equivalent `AGENTS.md` for Codex / Cursor / Hermes / other AGENTS-compatible harnesses), or paste it directly into the system prompt of a standalone agent. It tells the agent when to trigger the `research-workflow` skill, how to route between MCPs, and what the standard subagent dispatch looks like.
````markdown
## Research Skill Trigger (DEFAULT BEHAVIOR)
**MANDATORY**: Execute `research-workflow` skill for ANY external knowledge query.
**DEFAULT = TRIGGER SKILL:**
- Questions about facts, concepts, technologies, events
- Documentation, APIs, libraries, frameworks
- Comparisons, best practices, recommendations
- Current/recent information (dates, versions, news)
- "What is...", "How to...", "Explain...", "Compare...", "Find..."
- Ambiguous queries that MIGHT need external info
**Execution:**
Use Skill tool with skill="research-workflow"
**ONLY skip when ALL conditions met:**
1. User explicitly names a specific tool: "use Jina to..."
2. OR user provides a specific URL to read directly
3. OR query is about LOCAL codebase files (use native Read/Grep/Glob)
4. OR query is purely conversational with NO external knowledge need
**When in doubt → TRIGGER THE SKILL.**
**DEFAULT: Use Task tool with subagent for research** (unless user says "don't use subagent").
---
## Research Subagent Spawning (RECOMMENDED)
If your client supports parallel subagents (e.g. Claude Code's `Task` tool), dispatching research as a subagent keeps the main thread's context clean. Optional — skip this section if your client has no subagent primitive.
**Parallelism limit:** Max 2 research subagents in parallel. For 3+ research topics, queue: run 2, wait for completion, run next batch.
❌ Spawn 3+ research subagents in a single Agent tool message
✅ Spawn at most 2, wait for completion, spawn next batch
**Subagent prompt should include:**
1. All relevant conversation context (prior decisions, constraints, what's established/ruled out)
2. Research-workflow instructions (the body below, or reference to skills/research-workflow/SKILL.md)
```
Task tool:
subagent_type: "general-purpose"
prompt: |
Research query: [QUERY]
## Context
[All relevant conversation context that affects research scope, constraints, or expected outcomes]
## Research Workflow Instructions
**MCP schema loading (MANDATORY, applies to ALL workflows below).** MCP tool schemas are deferred. Bare `mcp__X__Y(...)` calls fail with `InputValidationError`. Before calling any `mcp__X__Y`, first call `ToolSearch(query='select:<tool_name>')` (or `select:<tool1>,<tool2>` to load multiple in one call). If a tool call appears to silently fail and you find yourself reaching for native `WebFetch` / `WebSearch`, STOP — those are NOT in the Triple Stack; the failure means a schema wasn't loaded. Re-run with ToolSearch first.
Classify query and execute appropriate workflow:
**QUICK FACTUAL** (mid-task lookup, speed-critical, single answer):
→ ToolSearch(query='select:mcp__exa-answer__exa_answer') → mcp__exa-answer__exa_answer(query) — 1-2s, 94% accuracy
→ Fallback: ToolSearch(query='select:mcp__exa__web_search_advanced_exa') → mcp__exa__web_search_advanced_exa with highlights
**DIRECT** (specific library/API, single source sufficient):
→ ToolSearch(query='select:mcp__context7__resolve-library-id,mcp__context7__query-docs,mcp__exa__get_code_context_exa')
→ mcp__context7__resolve-library-id(libraryName, query) → mcp__context7__query-docs(libraryId, query) OR mcp__exa__get_code_context_exa
**EXPLORATORY** (general concept, cold-start, learning):
→ ToolSearch(query='select:mcp__gigaxity-deep-research__discover,mcp__jina__parallel_read_url,mcp__gigaxity-deep-research__synthesize')
→ mcp__gigaxity-deep-research__discover(query, focus_mode, identify_gaps=True)
→ Score URLs from result, select top 3-5
→ mcp__jina__parallel_read_url(urls)
→ mcp__gigaxity-deep-research__synthesize(query, sources, preset)
**SYNTHESIS** (comparison, best practices, consensus, cross-validation):
→ ToolSearch(query='select:mcp__context7__resolve-library-id,mcp__context7__query-docs,mcp__exa__get_code_context_exa,mcp__jina__parallel_search_web,mcp__jina__sort_by_relevance,mcp__jina__deduplicate_strings,mcp__gigaxity-deep-research__synthesize')
→ Execute in parallel:
- mcp__context7__resolve-library-id(libraryName, query) → mcp__context7__query-docs(libraryId, query) # library/API docs (two-step)
- mcp__exa__get_code_context_exa(query) # code-specific
- mcp__jina__parallel_search_web(queries=[3-5 query strings]) # free-tier, ~107 tokens for 3 queries
→ Optional free middleware before synthesis:
- mcp__jina__sort_by_relevance(query, documents) — 0 tokens
- mcp__jina__deduplicate_strings(strings) — 0 tokens
→ mcp__gigaxity-deep-research__synthesize(query, sources, preset) # MANDATORY — do NOT freehand-synthesize in subagent
**SOCIAL-FIRST** (community sentiment, real user experiences, Reddit/X/YouTube):
→ ToolSearch(query='select:mcp__gptr-mcp__quick_search,mcp__gptr-mcp__deep_research,mcp__jina__search_web,mcp__jina__read_url')
→ mcp__gptr-mcp__quick_search(query) — single-call social-first lookup
→ mcp__gptr-mcp__deep_research(query) — multi-hop social-first research with cross-platform sentiment
→ For LinkedIn-specific queries: mcp__exa__web_search_advanced_exa(query, includeDomains=["linkedin.com"])
→ Combine with SYNTHESIS when comparing community sentiment to documentation/spec
→ On href-only returns (empty body/title from Reddit/X/YouTube), follow up with mcp__jina__read_url on the href — never cite from URL slug alone.
**CRITICAL:**
- NEVER use native WebSearch or WebFetch — use Triple Stack (Context7 + Exa + Jina). If you reach for those, you skipped ToolSearch.
- NEVER stop after gathering sources — ALWAYS call mcp__gigaxity-deep-research__synthesize. Do NOT freehand the synthesis in the subagent — the verifier is load-bearing.
- ALWAYS return the COMPLETE synthesized result — do not truncate.
- WHEN a tool output is wrapped in `<persisted-output>...</persisted-output>`, the 2KB preview is NOT evidence. MANDATORY: Read(path) on the persisted path before using the result in synthesis or citations.
- WHEN `mcp__gptr-mcp__quick_search` returns href entries with empty `body` and `title` (anti-scraped domains like Reddit / X / YouTube), the URL is a CANDIDATE, not evidence. Follow up with mcp__jina__read_url to get actual content, or drop the claim. Never infer content from URL slugs.
- WHEN `synthesize` output starts with `# Synthesis verification FAILED`, the verifier hard-gated it on a STRUCTURAL failure (empty / reasoning-only / truncated-at-ceiling / a failed multi-section subcall / zero citations when sources were provided). ONE retry with different preset/sources is permitted; if also FAILS, fall back to main-thread synthesis from raw sources with an explicit verifier-failure note in the final answer. Entity-coverage is NOT a hard-fail class (demoted to advisory soft 2026-06-24): when the synthesis discusses query entities absent from every retained source, the verifier now PASSES the synthesis and appends a `*Verification notes:*` caveat (e.g. `... treat those cited claims as UNVERIFIED ...`, `surface-form variant present`, or `emphasis/framing`) folded into the returned text. Do NOT retry or fall back on it — relay the synthesis and surface the caveat. Contract consequence: `passed=True` (and a cache hit) no longer implies entity-coverage is clean — inspect `soft_warnings` / the `*Verification notes:*` line for grounding caveats.
- WHEN `synthesize` returns `## Source quality insufficient` (REJECT) or `## Source quality insufficient (partial, zero passed)` (PARTIAL-with-zero-good), the relevance gate refused to synthesize at all — the synthesizer was NEVER invoked, output is NOT cached. As of v0.6.0 this refusal only fires when NO source clears the fail-open floor (`RESEARCH_FAIL_OPEN_MIN_SOURCE_SCORE`, default 0.3); above the floor the gate FAILS OPEN instead — it returns a normal synthesis whose text opens with a `low source relevance (fail-open)` caveat, and that fail-open result is NOT cached either. So a bare refusal means the corpus is genuinely below the floor: do NOT retry with the same source set — gather more relevant sources (broader queries, different focus mode, fresh Triple Stack pass) and re-call. A `low source relevance (fail-open)` caveat is a soft warning, not a refusal: relay the synthesis as weakly grounded and flag the caveat (do NOT retry on it). Distinct from the verifier hard-fail above — those mean the synthesizer ran; a bare refusal here means it was deliberately skipped.
- WHEN a `*Verification notes:*` line contains a note starting `contradiction detection …`, the conflict list you received is **NON-EXHAUSTIVE** — detection is advisory and never gates the synthesis, so a degraded run still passes and simply reports fewer disagreements than exist. Do NOT report "no contradictions were found" on any of these; say the check degraded and name which. `could not be parsed` = the model emitted the labels but the block was unreadable (grammar problem). `returned no structured output` = the model never attempted the format (prose/refusal — re-run or accept the gap; NOT a parser bug). `returned both findings and a 'no contradictions' declaration` = the response contradicted itself, so treat the listed conflicts as candidates rather than confirmed. `used the degraded heuristic detector` / `failed and fell back to a heuristic (<error>)` = low-confidence keyword pairing, or a transport failure named in the error. This is a SOFT note, never a retry trigger on its own — surface it in the final answer alongside the synthesis. (v0.12.0 also made the parser tolerate markdown-decorated labels; before it, a markdown-heavy model could lose its whole contradiction list to formatting.)
- WHEN any research tool returns an error envelope (HTTP 402/401/429 markers, "Insufficient balance", "out of credits", "quota", "rate limit", "Unauthorized", "Invalid API key") OR an unexpected empty result OR you triggered a fallback chain, emit a `## ⚠️ Tool Health Issues` header at the TOP of your final response (before the synthesis content). Format and severity language per skills/research-workflow/SKILL.md "Tool Health Detection". Use the exact severity phrases `<TOOL> QUOTA EXHAUSTED` / `AUTH FAILURE` / `RATE LIMITED` / `DEGRADED` so the main agent can detect them. Especially critical for Jina (10M trial tier, depletes fastest) — the user must be notified to pause / address before further Jina-dependent work. NEVER silently fall through quota errors.
Execute research now and return full synthesis.
```
**Post-subagent:** Output the COMPLETE result to user. NEVER truncate or summarize.
**Subagent health report inspection (MANDATORY).** After each research subagent return, scan the first ~1000 chars for `## ⚠️ Tool Health Issues`. If present:
1. **Surface to user BEFORE the synthesis output** — explicit user-facing message. Do NOT bury it in the synthesis. Format:
> ⚠️ Research subagent reported tool health issues:
> - <each issue from header, one bullet per tool>
>
> <Impact line from the health header, verbatim>
>
> Continuing with the subagent's synthesis below.
2. Then relay the full synthesis (subagent's content below the `---` separator) verbatim per the existing Post-subagent rule.
3. **Track recurring patterns in-session.** If 3+ subagent runs in the same session all report the same `<TOOL> QUOTA EXHAUSTED` (especially Jina), STOP spawning further subagents that depend on that tool. Surface a session-level escalation to the user: `⚠️ <TOOL> has reported QUOTA EXHAUSTED across N subagent runs this session. Pausing further <TOOL>-dependent research until you address the underlying issue.` Do not document or attempt recovery procedures — the user handles that out-of-band.
If the header is absent: relay the subagent's full output as normal.
---
## Tool Selection Matrix (Triple Stack)
| Need | Primary | Fallback |
|---|---|---|
| Quick factual answer (1-2 s) | mcp__exa-answer__exa_answer | mcp__exa__web_search_advanced_exa |
| Library / API documentation | mcp__context7__resolve-library-id → query-docs | mcp__exa__get_code_context_exa |
| Code examples / patterns | mcp__exa__get_code_context_exa | mcp__exa__web_search_advanced_exa with `includeDomains=["github.com"]` |
| General web (single query) | mcp__jina__search_web (~63 tokens) | mcp__exa__web_search_exa |
| Parallel multi-query web (3-5 variants) | mcp__jina__parallel_search_web (~107 / 3) | sequential mcp__exa__web_search_exa |
| Advanced web (date-bounded, highlights, domain filters) | mcp__exa__web_search_advanced_exa | mcp__jina__search_web with manual filtering |
| Company info / company research | mcp__exa__web_search_advanced_exa with `category="company"` | mcp__jina__search_web |
| People / OSINT / attribute-based | mcp__exa__web_search_advanced_exa with `category="people"` | mcp__jina__search_web |
| Financial reports (SEC, earnings) | mcp__exa__web_search_advanced_exa with `category="financial report"` | — |
| News (date-bounded) | mcp__exa__web_search_advanced_exa with `category="news"` | mcp__jina__search_web |
| GitHub repo discovery | mcp__exa__web_search_advanced_exa with `category="github"` | mcp__exa__web_search_advanced_exa with `includeDomains=["github.com"]` |
| PDFs / whitepapers (search) | mcp__exa__web_search_advanced_exa with `category="pdf"` | — |
| URL subpage crawl (multiple pages from one site) | mcp__exa__crawling_exa with `subpages` / `subpageTarget` | — (Jina has no subpage mode) |
| URL → markdown (single) | mcp__jina__read_url (0 tokens) | mcp__brightdata_fallback__scrape_as_markdown |
| URL → markdown (bulk 3-5) | mcp__jina__parallel_read_url | per-URL fallback to Brightdata for blocked ones |
| URL freshness / credibility check | mcp__jina__guess_datetime_url (free) | — |
| Academic (arXiv) — single query | mcp__jina__search_arxiv (field syntax: `cat:cs.CL`, `abs:"..."`, `au:...`, boolean AND/OR; `sort="date"` for newest-first) | — |
| Academic (arXiv) — multi-query parallel | mcp__jina__parallel_search_arxiv (key-less, 0 Jina tokens — use generous `num`) | mcp__exa__web_search_advanced_exa with `category="research paper"` |
| Academic (SSRN — econ/law/finance) | mcp__jina__search_ssrn / parallel_search_ssrn (OpenAlex, key-less, citation counts) | mcp__exa__web_search_advanced_exa with `category="research paper"` |
| BibTeX citations | mcp__jina__search_bibtex (DBLP → Semantic Scholar, key-less, 0 Jina tokens) | mcp__exa__web_search_advanced_exa with `category="research paper"` |
| PDF layout extraction (figures, tables, equations) | mcp__jina__extract_pdf | — |
| Images | mcp__jina__search_images (needs PAID Jina balance — no free-lane equivalent) | mcp__exa__web_search_advanced_exa |
| Screenshots | mcp__jina__capture_screenshot_url | — |
| Free reranker (before synthesis) | mcp__jina__sort_by_relevance (0 tokens) | — |
| Free dedup (before synthesis) | mcp__jina__deduplicate_strings (0 tokens) | — |
| Synthesis with citations | mcp__gigaxity-deep-research__synthesize | — |
| CoT reasoning over evidence | mcp__gigaxity-deep-research__reason | — |
| Exploratory expansion + gap detection | mcp__gigaxity-deep-research__discover | — |
| Quick conversational LLM answer | mcp__gigaxity-deep-research__ask | mcp__exa-answer__exa_answer |
| Social-first research (Reddit / X / YouTube — "what do people think") | mcp__gptr-mcp__quick_search | mcp__exa__web_search_advanced_exa with `includeDomains=["reddit.com"]` |
| Deep social research (multi-hop community sentiment) | mcp__gptr-mcp__deep_research | — |
**AVOID:** `mcp__jina__expand_query` — 12,000 tokens/call. Rewrite query variants in the prompt instead. Not exposed by the bundled server at all.
**Prefer Exa for domain-scoped search.** `mcp__exa__web_search_advanced_exa` with `includeDomains=[...]` is a real multi-domain filter rather than a query-string hint; Jina's `site` argument takes one domain only. Both work — Jina's `site` and the `site:` operator returned HTTP 500 during a ~2026-08-01 upstream incident ([jina-ai/reader#1258](https://github.com/jina-ai/reader/issues/1258)) and were verified working again on 2026-08-03.
**A Jina search fault is transient until a retest proves otherwise.** The same incident window also degraded general search ranking — `s.jina.ai` locked onto one query term and returned that term's popular pages at HTTP 200 with no error field. It read as a permanent ranking defect because reordering, distinctive-term-first and phrase-quoting all failed; a retest the same day returned correct results on every affected query, with no key rotation. **The absence of a query-rewriting workaround does not distinguish a permanent defect from a current outage.** On junk or off-topic results: wait, retest, then conclude — and never rotate the key over it, since the symptom is neither a quota nor an auth fault.
**One Jina search 4xx is deterministic and benign — 422 `AssertionFailureError` status 42206 means ZERO RESULTS.** `s.jina.ai` encodes an empty SERP as HTTP 422 with that exact signature rather than an empty list, typically reached through a long exact-phrase quote (observed 2026-08-04). The bundled companion (v0.11.2+) classifies it and returns a plain `No results for …` line; on other deployments broaden the query or loosen quotes and retry — never flag tool health on this signature alone.
**Exa MCP 3.2.0 caveat:** the `type="deep"` parameter previously documented for `web_search_exa` does NOT exist in the current MCP. The deprecated `deep_researcher_start` / `deep_researcher_check` have no MCP replacement either. For deep multi-hop research, use the chain `mcp__gigaxity-deep-research__discover` → `mcp__jina__parallel_read_url` → `mcp__gigaxity-deep-research__synthesize`.
---
## Specific URL → Tool Mapping
When the user supplies a specific URL, route by URL type:
| URL pattern | Primary | On error/block |
|---|---|---|
| GitHub issues / PRs / discussions | mcp__jina__read_url | mcp__brightdata_fallback__scrape_as_markdown |
| Documentation / API references | mcp__jina__read_url | mcp__brightdata_fallback__scrape_as_markdown |
| General articles / blogs | mcp__jina__read_url | mcp__brightdata_fallback__scrape_as_markdown |
| Paywalled / Cloudflare / CAPTCHA | mcp__brightdata_fallback__scrape_as_markdown (direct) | mcp__exa__crawling_exa |
| PDF files | dedicated PDF reader (e.g. pdf_reader MCP if installed) | mcp__jina__extract_pdf |
| URL subpage crawl (multiple pages from one site) | mcp__exa__crawling_exa with subpages | — |
| Reddit / X / YouTube discussion or community sentiment | mcp__gptr-mcp__quick_search | mcp__exa__web_search_advanced_exa with `includeDomains=["reddit.com"]` |
| LinkedIn (gptr-mcp's social retriever excludes LinkedIn) | mcp__exa__web_search_advanced_exa with `includeDomains=["linkedin.com"]` | mcp__jina__search_web with `site="linkedin.com"` |
---
## Brightdata Fallback Chain (URL Reading)
Triggered automatically when an upstream URL fetcher fails. The chain is **per-URL**, not per-query — retry on the SAME URL before falling back to a different source.
```
Step 1: mcp__jina__read_url(url) (0 tokens, free reader)
│
│ on empty / 404 / CAPTCHA / paywall / 403 / Cloudflare
▼
Step 2: mcp__brightdata_fallback__scrape_as_markdown(url) (paid, ~$0.01/req)
│
│ on persistent failure
▼
Step 3: mcp__exa__crawling_exa(url) (paid, last resort)
```
**When Brightdata fires (typical 5-15% of URL fetches):**
- News paywalls (NYT, FT, WSJ) — login walls or paywall HTML
- Cloudflare-protected sites — "Verify you are human" challenges
- LinkedIn / X / Reddit — auth gates
- Heavy-JS sites that don't render server-side — empty bodies / spinners
**If Brightdata isn't installed:** the chain skips Step 2 and goes straight to Step 3 (or fails out). Most SYNTHESIS workflows tolerate 10-15% URL loss because they pull from many sources — but for single-URL queries, install Brightdata or expect occasional gaps.
---
## CRITICAL Rules
```
❌ Use native WebSearch tool ✅ Use the Triple Stack (Context7 + Exa + Jina)
❌ Stop after gathering sources ✅ ALWAYS synthesize via gigaxity-deep-research
❌ Truncate or summarize the synthesis output ✅ Return the COMPLETE result verbatim
❌ Spawn subagent without conversation context ✅ Include prior decisions, constraints, ruled-out approaches
❌ Use ask() for cross-source comparisons ✅ Use synthesize() — runs quality gate + contradictions
❌ Use discover() when URLs are already chosen ✅ Use synthesize() with the URLs already in the prompt
❌ Use reason() for "what is X" lookups ✅ Use ask() — reason() burns tokens on a CoT you don't need
❌ Pass the same OpenRouter key in every request body ✅ Set RESEARCH_LLM_API_KEY in env, override per-request only when multi-tenant
❌ Call synthesize() expecting it to fetch sources ✅ synthesize NEVER re-searches — pass it pre-gathered sources from discover() or your own URL reads
❌ Use mcp__jina__expand_query ✅ Rewrite query variants in the prompt — expand_query burns 12k tokens/call
❌ Pass type="deep" to web_search_exa ✅ MCP 3.2.0 has no deep type — use discover→jina→synthesize chain instead
```
````
End of pasteable block.
---
## Notes on the pasteable block
- Adjust subagent parallelism (`Max 2`) to match your model and quota tolerance.
- The block assumes the five companion MCPs are registered under the exact aliases shown (`context7`, `exa`, `exa-answer`, `jina`, `brightdata_fallback`). The `mcp__<alias>__<tool>` names in the block are derived from those aliases — change the block if you register them under different names.
- The bundled skill in `skills/research-workflow/` contains the deep version of the routing matrix (token costs, presets, focus modes, per-tool capabilities). The block above is the abridged trigger logic; the skill is the full reference.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

