# Research Workflow

> This skill should be used when the user asks research questions, needs information lookup, wants comparisons, asks "what is", "how does", "explain", "compare", "best practices", "latest developments", or any query requiring web search, documentation lookup, or synthesis of multiple sources. Provides optimal routing between DIRECT, EXPLORATORY, and SYNTHESIS workflows using Triple Stack (Context7, Exa, Jina) and gigaxity-deep-research tools.

- Skill: `yoloshii/research-workflow` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yoloshii/research-workflow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yoloshii/research-workflow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: yoloshii (https://skillmd.com/u/yoloshii)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yoloshii/research-workflow

---


# Research Workflow Skill

## Overview

This skill orchestrates research queries using the optimal workflow based on query type. It integrates:
- **Triple Stack**: Context7 (docs) + Exa (code/web) + Jina (web/academic/parallel)
- **gigaxity-deep-research**: synthesis engine over any OpenAI-compatible chat-completions endpoint (self-hosted vLLM/SGLang/llama.cpp on the `local-inference` branch, OpenRouter on `main`)
- **exa-answer**: speed-critical 1–2 s factual lookups
- **brightdata_fallback**: blocked-URL recovery (CAPTCHA / paywall / Cloudflare)
- **gptr-mcp**: social-first research over Reddit, X/Twitter, YouTube — wraps [GPT Researcher](https://github.com/assafelovic/gpt-researcher)

---

## Tool Schema Loading (MANDATORY)

MCP tool schemas are deferred. Bare `mcp__X__Y(...)` calls fail with `InputValidationError` because the schema isn't loaded. Load schemas first via `ToolSearch`:

```
ToolSearch(query='select:mcp__context7__resolve-library-id,mcp__context7__query-docs')   # docs (two-step)
ToolSearch(query='select:mcp__exa__web_search_exa,mcp__jina__read_url')      # multiple
ToolSearch(query='+exa-answer')                                              # keyword (rank by relevance)
```

After `ToolSearch` returns the `<function>...` block for a tool, that tool is callable for the rest of the session — no need to re-load.

**Why this matters:** if you skip `ToolSearch` and the bare call fails, the path of least resistance is to fall through to `WebFetch` / `WebSearch` — neither is in the Triple Stack. Using them is the strongest signal that schema loading was skipped.

```
❌ mcp__context7__query-docs(libraryId="...", query="...")      # fails — schema not loaded
✅ ToolSearch(query='select:mcp__context7__resolve-library-id,mcp__context7__query-docs')
   → then mcp__context7__resolve-library-id(...) → mcp__context7__query-docs(...)   # works

❌ Tool fails silently → fall back to WebFetch
✅ Tool fails → check whether schema was loaded → ToolSearch + retry
```

Subagents inherit the same deferred-loading discipline — when spawning a research subagent via the Task tool, the subagent prompt MUST include `ToolSearch(query='select:...')` ahead of every `mcp__X__Y` reference, otherwise the subagent will fall through to WebFetch the same way.

---

## Tool Output Persistence (MANDATORY)

When tool output exceeds the Claude Code harness threshold (~16 KB), the full result is written to disk and replaced with a preview-and-path wrapper:

```
<persisted-output>
Output too large (XXX KB). Full output saved to: /home/<user>/.claude/projects/<encoded>/<session>/tool-results/<random>.txt

Preview (first 2KB):
<truncated content>
...
</persisted-output>
```

**Rule:** Any time you see `<persisted-output>` wrapping a tool result, the 2KB preview is **NOT** evidence. You **MUST** call `Read(path)` on the persisted path before:

- citing the source
- making any factual claim derived from the result
- passing the result into `mcp__gigaxity-deep-research__synthesize` sources

The auto-reload mechanism does not exist. The result is on disk until you read it.

```
❌ See <persisted-output> → synthesize from the 2KB preview
✅ See <persisted-output> → Read(path) → synthesize from the full content

❌ Multiple persisted-output tools chained → synthesize from previews only
✅ For each persisted-output, Read(path) before the next dependent call
```

**Typical triggers (observed):** `mcp__exa__web_search_advanced_exa` with `numResults>=10`, `mcp__jina__parallel_read_url` on long pages, `mcp__exa__crawling_exa` with `subpages`.

---

## Tool Health Detection (MANDATORY)

MCP wrappers convert HTTP errors into 200-OK text envelopes — a quota-exhausted Jina call looks structurally like a normal "no results" response. Silent failures slip through. After every research-tool call, scan the response for error signatures BEFORE treating the result as evidence.

### Error signatures

| Tool | Quota / billing | Auth | Rate limit | Degraded empty |
|---|---|---|---|---|
| Jina (any `mcp__jina__*`) | `402` / `Insufficient balance` / `out of credits` / `payment required` / `quota` | `401` / `Invalid API key` / `Unauthorized` | `429` / `rate limit` / `too many requests` | `results: []` + no error field (could be legit — verify against query specificity) |
| Exa (any `mcp__exa__*`) | `402` / `credits` / `Insufficient` | `401` / `authentication` | `429` / `rate limit` | empty `results` array |
| Exa-answer | same as Exa | same as Exa | same as Exa | empty `answer` field |
| gptr-mcp `quick_search` | upstream OpenAI `429` / `quota` | OpenAI `401` | OpenAI `429` | `search_results: []` or `result_count: 0` — distinguish "anti-scraped href-only" (Anti-Pattern #6) from "genuine empty" |
| gigaxity-deep-research `synthesize` / `reason` | upstream LLM `402` | upstream LLM `401` | upstream LLM `429` | already covered: `# Synthesis verification FAILED` header (per Verifier Verdict Handling) |
| brightdata_fallback | `402` | `401` | `429` | empty markdown body |

### Optional pre-flight Jina probe (0 tokens)

At the start of a long-running session where Jina is load-bearing, call `mcp__jina__show_api_key()` once. It returns the bearer the server sees. Use cases:

- **Auth verification**: confirms the key the MCP loaded matches expectation. If it errors, all subsequent Jina calls will fail too — bail and notify the user before burning the rest of the workflow.
- **NOT a quota probe** — does not return remaining balance. Quota exhaustion only surfaces on the first failing call.

Jina is uniquely vulnerable to silent quota exhaustion: 10M trial tier + primary high-frequency tool in the SYNTHESIS workflow = first to deplete. Notify the user immediately on the first 402.

### Detection → escalation schema (MANDATORY for subagents)

When a tool error is detected during a research subagent run, the subagent MUST emit a structured health header at the TOP of its final response — BEFORE the synthesis content. Schema:

```
## ⚠️ Tool Health Issues

- **mcp__jina__search_web** (5 calls): 2 quota errors (HTTP 402 / "Insufficient balance" at calls 3 and 4). Fell back to mcp__exa__web_search_exa for remaining queries.
- **mcp__exa__web_search_advanced_exa** (3 calls): 1 rate limit (429) on call 2. Single retry succeeded.
- **mcp__gptr-mcp__quick_search** (2 calls): both returned empty results on Reddit slugs (anti-scrape, expected); not flagged per Anti-Pattern #6.

**Impact:** synthesis below uses Exa-heavy mix (2/5 Jina queries succeeded). Coverage may be skewed toward Exa-indexed content. JINA QUOTA EXHAUSTED — pause further Jina-dependent research until user addresses.

---

[Normal synthesis content below]
```

If no issues encountered, omit the header entirely — its absence signals a clean run.

### Trigger rules (when to emit)

Emit the health header if ANY of the following occurred during the run, even if the workflow completed overall:

- Any error envelope per the signature table
- Any fallback chain invocation (the `ON FAIL →` chain was triggered because the primary tool failed)
- Empty result on a non-trivial query that was expected to return content (skip for known degraded-empty patterns like gptr-mcp Reddit slugs per Anti-Pattern #6, or Jina search 422/42206 zero-results — a benign no-results signature, not a fault)
- Visible timeout signal (Jina parallel calls past the configured `timeout`)
- Persisted-output handling skipped (per Tool Output Persistence — agent didn't `Read(path)` on a `<persisted-output>` wrapper)

### Severity language for the Impact line

Use these exact phrases in the Impact line so the main agent's scanner catches them:

- `<TOOL> QUOTA EXHAUSTED` — 402 / billing / credits / quota errors. User-facing escalation required; further calls to that tool will fail. (e.g. `JINA QUOTA EXHAUSTED`)
- `<TOOL> AUTH FAILURE` — 401 errors. Tool is effectively dead for this session; user must address before any further use. Highest priority.
- `<TOOL> RATE LIMITED` — 429 errors, transient. Single retry permitted; if persists, fall back.
- `<TOOL> DEGRADED` — empty results when content expected; backend may be partial or query may be poorly-formed.

### Recovery decision tree

```
Tool error detected
  ↓
Single transient (429 / timeout)?
  YES → retry once after short backoff (5s for 429)
       → if succeeds: optional health flag (note recovered transient)
       → if fails: escalate per category below
  NO ↓

Quota / billing (402)?
  YES → switch to fallback chain (do NOT retry — quota persists across calls)
       → flag QUOTA EXHAUSTED in health header
       → skip this tool for the rest of the run
  NO ↓

Auth (401)?
  YES → BAIL the entire tool category (all calls to this MCP will fail)
       → flag AUTH FAILURE in health header
       → ⚠️ The whole run may be unrecoverable — surface IMMEDIATELY to user
  NO ↓

Empty result on non-trivial query?
  YES → is this a known degraded-empty pattern? (e.g. gptr-mcp Reddit slugs per Anti-Pattern #6,
        Jina search 422/42206 zero-results — benign, not a fault)
        YES → not an error; continue
        NO → retry once with reformulated query
             → if still empty, flag DEGRADED in health header
```

---

## Query Classification

### QUICK FACTUAL Queries (15-20% of queries)

Speed-critical factual lookups during ongoing agent operations. Exa /answer handles search + LLM answer + citations in a single 1-2s call (94% SimpleQA accuracy).

**Trigger Patterns:**
- Mid-task factual lookup during an ongoing workflow
- "What is the current version of X?"
- "What is X's latest pricing?"
- "When was X released?"
- Speed matters more than depth
- Single factual answer sufficient (no exploration or cross-validation)

**Decision Criteria:**
- Agent is mid-task and needs a quick fact
- A single direct answer with sources is sufficient
- No comparison, synthesis, or deep analysis needed
- Latency budget is <3 seconds

**Tool:** `exa_answer` (exa-answer MCP) — 1-2s, $0.005/query

### DIRECT Queries (25-35% of queries)

Single-source factual lookups. Use Triple Stack directly.

**Trigger Patterns:**
- "Read this URL" → Jina read_url
- "Get documentation for [library]" → Context7 (resolve-library-id → query-docs)
- "Find code examples for [function]" → Exa get_code_context_exa
- "How does [specific API] work?" → Context7 (resolve-library-id → query-docs)
- "Explain [library feature]" → Context7 (resolve-library-id → query-docs)
- "What is [programming concept]?" → Context7 (resolve-library-id → query-docs)
- "Search images for..." → Jina search_images (needs a PAID Jina balance; Exa advanced is the free-tier fallback)
- "Find papers on..." → Jina search_arxiv — use arXiv **field syntax**, not the user's question verbatim (see DIRECT workflow)
- Factual lookups with single source
- Specific library/API/framework with official docs

**Decision Criteria:**
- Query targets a SPECIFIC library, API, or framework
- Official documentation exists and would answer it
- Single source sufficient (no cross-validation needed)
- User knows what they're looking for

### EXPLORATORY Queries (40-50% of queries)

Cold-start discovery for unfamiliar topics. gigaxity-deep-research leads.

**Trigger Patterns:**
- "What is [unfamiliar topic]?" (cold start)
- "Explain [general concept/technology]" (e.g., "Explain transformers")
- "How does [general system] work?" (e.g., "How do vector databases work?")
- "Latest developments/advances in [field]"
- "Tell me about [emerging technology] in 2026"
- "Research [topic]" without specific library focus
- User doesn't know what they don't know

**Decision Criteria:**
- Unfamiliar domain (cold start)
- General concept, not specific library
- Speed priority (1-2 min target)
- Targeted depth, not comprehensive coverage
- No cross-validation required

### SYNTHESIS Queries (20-30% of queries)

Cross-source validation and comprehensive analysis. Triple Stack → gigaxity-deep-research.

**Trigger Patterns:**
- "What is the recommended/best..." (need consensus)
- "Compare X vs Y" (need multiple perspectives)
- "What are best practices for..." (need validated patterns)
- "Which is better/faster..." (need benchmarks)
- "How should I approach..." (need strategic guidance)
- "Pros and cons of..."
- "Trade-offs between..."

**Decision Criteria:**
- Cross-source validation required
- Comparison or evaluation needed
- Comprehensive coverage required
- Multiple perspectives expected
- Consensus or best practice sought

## Framing a deep-research query (EXPLORATORY / SYNTHESIS)

Before you classify, shape the query string itself. The synthesis engine has **no memory of your conversation** — it acts only on the query text and the sources you pass. A vague subject starves the decomposer; a framed query steers every downstream stage (decomposition into typed sub-aspects, gap detection, contradiction surfacing). Applies to EXPLORATORY and SYNTHESIS work — QUICK FACTUAL and DIRECT lookups skip it.

- **Lead with the goal and the decision it informs.** "…to decide whether to adopt X over Y" beats a bare topic — the engine ranks and prunes against intent it can see.
- **Embed all context in the query string.** Names, dates, versions, known facts, what's already ruled out. An unstated constraint is invisible; the engine cannot ask a follow-up.
- **State the source hierarchy when epistemics matter.** Prefer primary/authoritative sources (docs, filings, changelogs, papers); treat forum / Reddit / X / community results as **weak signal only**, never sole support for a factual claim. The `gptr` social retriever *will* surface social content — say in the query how `synthesize` should weight it.
- **Name include/avoid constraints.** "only non-Chinese vendors", "no marketing copy", "post-2025 only" — honored if stated, not if assumed.
- **One mission per query.** Cramming unrelated questions dilutes decomposition. Split them into separate calls.

The engine already decomposes into typed sub-aspects, detects gaps, and surfaces contradictions — a well-framed query is what makes those stages fire on the right axes.

## Decision Tree

```
Query arrives
     ↓
Mid-task factual lookup? (speed-critical, single answer sufficient)
  YES → QUICK FACTUAL (exa_answer — 1-2s, 94% accuracy)
  NO ↓

Single-source factual lookup? (specific library/API/framework)
  YES → DIRECT (Triple Stack tool directly)
  NO ↓

Specific library/API/framework with official docs?
  YES → DIRECT (Context7 → Exa fallback)
  NO ↓

Requires cross-validation, comparison, or comprehensive coverage?
  YES → SYNTHESIS (Triple Stack → gigaxity-deep-research synthesize/reason)
  NO ↓

Default → EXPLORATORY (gigaxity-deep-research discover → Jina → synthesize)
  # NOTE: Exa 3.2.0 MCP does NOT expose type="deep" on web_search_exa (enum: auto|fast)
  #       or web_search_advanced_exa (enum: auto|fast|instant). The deprecated
  #       deep_researcher_start/check have no MCP-surface replacement. Use the
  #       gigaxity-deep-research discover chain above for async multi-hop research.
```

---

## QUICK FACTUAL Workflow

**Use when:** Mid-task factual lookup, speed-critical, single answer sufficient

**Tool:** `exa_answer` from exa-answer MCP

```
# Simple factual lookup (1-2s, 94% SimpleQA accuracy)
exa_answer(query="What is the latest version of Next.js?")

# With sources disabled for minimal output
exa_answer(query="What port does Redis use by default?", include_sources=False)

# Detailed with full source text (for verification)
exa_answer_detailed(query="What are the system requirements for Bun?")
```

**Token cost:** ~200-500 tokens
**Time:** 1-2 seconds
**Cost:** $0.005/query

**Fallback:** If exa_answer fails, fall back to DIRECT workflow.

---

## DIRECT Workflow

**Use when:** Single-source factual lookup, specific library/API query

**Tool Selection (Jina-first for high-frequency calls — reserve Exa budget for its unique capabilities):**

| Query Type | Primary Tool | Fallback |
|------------|--------------|----------|
| API docs | `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 includeDomains=["github.com"]` |
| URL reading | `mcp__jina__read_url` (0 tokens) | `mcp__exa__crawling_exa` |
| Bulk URL reading (3-5) | `mcp__jina__parallel_read_url` (content-proportional) | `mcp__exa__crawling_exa` with urls array |
| URL subpage crawl | `mcp__exa__crawling_exa` with `subpages` + `subpageTarget` | — (Jina has no subpage mode) |
| Academic (arXiv) | `mcp__jina__search_arxiv` / `mcp__jina__parallel_search_arxiv` — supports arXiv field syntax (`cat:cs.CL`, `abs:"..."`, `au:...`, boolean AND/OR) and `sort="date"` for newest-first | `mcp__exa__web_search_advanced_exa category="research paper"` |
| Academic (SSRN — econ/law/finance) | `mcp__jina__search_ssrn` / `mcp__jina__parallel_search_ssrn` — OpenAlex-backed, key-less (0 Jina tokens); returns citation counts | `mcp__exa__web_search_advanced_exa category="research paper"` |
| BibTeX citations | `mcp__jina__search_bibtex` (DBLP → Semantic Scholar, key-less, 0 Jina tokens) | `mcp__exa__web_search_advanced_exa category="research paper"` |
| PDF layout extraction (figures/tables) | `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` | — |
| General web | `mcp__gigaxity-deep-research__search` — 4 connectors (SearXNG + Tavily + LinkUp + Brave) RRF-fused, 0 LLM tokens | `mcp__exa__web_search_exa` |
| Parallel multi-query web (3-5 variants) | `mcp__jina__parallel_search_web` (107 tokens for 3) | one `mcp__gigaxity-deep-research__search` per variant for 4-source fused depth |
| Advanced web (category/domain/date filters) | `mcp__exa__web_search_advanced_exa` | `mcp__exa__web_search_exa` |
| Company info | `mcp__exa__web_search_advanced_exa category="company"` | `mcp__gigaxity-deep-research__search "<name> company"` |
| People / OSINT / attribute-based | `mcp__exa__web_search_advanced_exa category="people"` | `mcp__exa__web_search_advanced_exa includeDomains=["linkedin.com"]` |
| Financial reports (SEC, earnings) | `mcp__exa__web_search_advanced_exa category="financial report"` | `mcp__exa__web_search_advanced_exa category="pdf"` |
| News (date-bounded) | `mcp__exa__web_search_advanced_exa category="news"` with `startPublishedDate/endPublishedDate` | `mcp__gigaxity-deep-research__search` |
| GitHub repo discovery | `mcp__exa__web_search_advanced_exa category="github"` | `mcp__exa__web_search_advanced_exa includeDomains=["github.com"]` |
| PDFs / whitepapers | `mcp__exa__web_search_advanced_exa category="pdf"` | — |
| URL freshness inference | `mcp__jina__guess_datetime_url` | — (credibility/staleness checks) |
| Deep multi-hop async research | gigaxity-deep-research discover → Jina parallel_read_url → synthesize | — (Exa MCP 3.2.0 does not expose `type="deep"`) |
| Free reranker | `mcp__jina__sort_by_relevance` (0 tokens) | — |
| Free semantic dedup | `mcp__jina__deduplicate_strings` (0 tokens) | — |
| Text classification | `mcp__jina__classify_text` | — |
| Time-aware session context | `mcp__jina__primer` (current UTC / timezone) | — |
| Quick LLM answer | `mcp__gigaxity-deep-research__ask` | — |

**AVOID:** `mcp__jina__expand_query` (12k tokens/call — rewrite queries manually instead). Not exposed by the bundled server at all.

**A Jina search fault is TRANSIENT until a retest proves otherwise — never encode one as permanent, and never rotate the key over it.** On 2026-08-03 two Jina search faults appeared together and both cleared the same day with zero rotations: `s.jina.ai` locked onto ONE query term and returned that term's popular or navigational pages at HTTP 200 with no error field (`retrieval augmented generation evaluation benchmarks` → four dictionary definitions of "retrieval"; `vLLM versus SGLang inference throughput comparison` → six vLLM pages, none mentioning SGLang), and site-restricted search returned HTTP 500 via [jina-ai/reader#1258](https://github.com/jina-ai/reader/issues/1258). Both belonged to the same server-side incident window as the `svip.jina.ai` credit gate. During that window, reordering the query, leading with a distinctive term, and phrase-quoting all failed — so **the absence of a query-rewriting workaround is not evidence that a fault is permanent.** It reads identically to a live incident. On junk or off-topic results: wait, retest, and only then conclude.

**One Jina search 4xx is deterministic and benign — 422 `AssertionFailureError` status 42206 means ZERO RESULTS (observed 2026-08-04).** `s.jina.ai` encodes an empty SERP as HTTP 422 with that exact signature (`No search results available for query …`) rather than an empty list; the practical trigger is a long exact-phrase quote, since unquoted queries fuzzy-match to something. The bundled `companions/jina-mcp/` server verifies the full signature and returns a plain `No results for …` line with a broaden-the-query hint — do not flag tool health on it, and a raw `Search failed … HTTP 422` therefore indicates a *different* 422. Scoped to `search_web`/`parallel_search_web`; other `mcp__jina__*` tools are unaffected.

**General web routing.** Primary is `mcp__gigaxity-deep-research__search` — four RRF-fused connectors (SearXNG + Tavily + LinkUp + Brave) with content snippets, 0 LLM tokens, and Brave is a keyed official API that cannot be CAPTCHA'd. That is a coverage-and-durability choice, not a workaround: `mcp__jina__search_web` is healthy and is a fine cheap single-source fallback. Jina's `read_url`, `parallel_read_url`, the key-less arXiv/SSRN/BibTeX tools, rerank and dedup run on different endpoints and were never affected.

**Domain-scoped search.** Prefer `mcp__exa__web_search_advanced_exa` with `includeDomains=[...]` — a real multi-domain filter rather than a query-string hint. Jina's `site` argument works again as of the 2026-08-03 retest and is adequate for a cheap single-domain lookup.

**Implementation:**

```
# Documentation lookup
mcp__context7__resolve-library-id(libraryName="FastAPI", query="FastAPI WebSocket API") → mcp__context7__query-docs(libraryId, query="FastAPI WebSocket API")

# Code examples
mcp__exa__get_code_context_exa(query="React useState patterns")

# URL reading
mcp__jina__read_url(url="https://docs.example.com/api")

# Academic papers — free and key-less, so don't ration `num`.
# Use arXiv field syntax; a bare query searches all fields and is usually too broad.
mcp__jina__search_arxiv(query='cat:cs.CL AND abs:"transformer architecture"', num=15, sort="date")

# Quick answer (no search needed)
mcp__gigaxity-deep-research__ask(query="What is dependency injection?")
```

**Token cost:** ~100-500 tokens
**Time:** <10 seconds

---

## EXPLORATORY Workflow

**Use when:** Cold-start, unfamiliar topic, general concepts, speed priority

**Flow:**
```
gigaxity-deep-research discover → (scored URLs) → Jina parallel_read_url → gigaxity-deep-research synthesize
```

**Focus Mode Selection:**
| Query Type | focus_mode | Why |
|------------|------------|-----|
| General tech question | `general` | Broad gaps: docs, examples, alternatives |
| Research/academic | `academic` | Gaps: methodology, limitations, citations |
| Library/API specific | `documentation` | Focused: api_reference, migration, config |
| "Which should I use?" | `comparison` | Gaps: criteria, tradeoffs, benchmarks |
| Error/bug investigation | `debugging` | Gaps: root_cause, workarounds, fixes |
| Learning/getting started | `tutorial` | Gaps: prerequisites, step_by_step |
| Recent news/announcements | `news` | Time-filtered, announcement gaps |

**Implementation:**

```
# Step 1: Discovery with gap analysis
result = mcp__gigaxity-deep-research__discover(
    query="quantum memory systems",
    top_k=10,
    identify_gaps=True,
    focus_mode="academic"  # → scientific topic, need methodology gaps
)
# Returns: landscape, knowledge_gaps, sources with scores, recommended_deep_dives

# Step 2: Score URLs from discovery result (extended thinking)
# - Gap relevance (does it fill identified gaps?)
# - Source authority (official docs, academic, reputable)
# - Uniqueness (not redundant with other sources)
# - Recency (recent for evolving topics)
# Select top 3-5 URLs based on scoring

# Step 3: Deep content fetch via Jina
content = mcp__jina__parallel_read_url(
    urls=[top_scored_urls],  # 3-5 URLs from step 2
)

# Step 4: Synthesize findings
synthesis = mcp__gigaxity-deep-research__synthesize(
    query="quantum memory systems",
    sources=[
        {"title": "Source 1", "url": "url1", "content": "fetched content 1"},
        {"title": "Source 2", "url": "url2", "content": "fetched content 2"},
        ...
    ],
    style="comprehensive",
    preset="academic"  # → matches focus_mode, structured with citations
)
```

**Key Insight:** `discover` outputs `recommended_deep_dives` URLs specifically for Jina to fetch. This prevents redundant searching.

**Token cost:** ~2000-5000 tokens
**Time:** 1-2 min

---

## SYNTHESIS Workflow

**Use when:** Comparisons, best practices, cross-validation, comprehensive coverage

**Flow:**
```
Triple Stack (Context7 + Exa + Jina parallel) → gigaxity-deep-research synthesize/reason
```

**Preset Selection:**
| Query Type | preset | Why |
|------------|--------|-----|
| Important research | `comprehensive` | Full pipeline: CRAG + RCS + contradiction + outline |
| Quick synthesis | `fast` | Direct synthesis, no preprocessing |
| Comparisons (X vs Y) | `contracrow` | Highlights conflicting claims |
| Formal reports | `academic` | Structured with proper citations |
| How-to guides | `tutorial` | Step-by-step format |

**Implementation:**

```
# Step 1: Triple Stack parallel search
# Execute ALL THREE in parallel for comprehensive coverage

ctx7_lib     = mcp__context7__resolve-library-id(libraryName="FastAPI", query="FastAPI vs Flask production")
ctx7_results = mcp__context7__query-docs(libraryId=ctx7_lib, query="FastAPI vs Flask production")
exa_results  = mcp__exa__get_code_context_exa(query="FastAPI Flask production patterns")
jina_results = mcp__jina__parallel_search_web(queries=[
    "FastAPI Flask benchmarks 2026",
    "FastAPI Flask production tradeoffs",
    "FastAPI vs Flask async performance",
])  # plain strings; 107 tokens for 3 parallel queries — broader than one search_web

# Step 2 (optional depth boost): second-pass bulk-read of top URLs surfaced by Step 1
# Rank union of URLs, bulk-read top 3-5, feed richer content to synthesis
urls = [u for r in [exa_results, jina_results] for u in extract_urls(r)]
ranked = mcp__jina__sort_by_relevance(               # 0 tokens (free reranker)
    query="FastAPI vs Flask production tradeoffs",
    documents=urls
)
top_urls = ranked[:5]
deep_content = mcp__jina__parallel_read_url(         # ~17k tokens (content-proportional)
    urls=top_urls
)

# Step 3 (optional dedup): filter near-duplicate snippets before synthesis
deduped = mcp__jina__deduplicate_strings(            # 0 tokens (free dedup)
    strings=[src["content"] for src in all_sources]
)

# Step 4: IMMEDIATELY synthesize (no waiting for user)
synthesis = mcp__gigaxity-deep-research__synthesize(
    query="Compare FastAPI vs Flask for production APIs",
    sources=[
        {"title": "Context7: FastAPI docs", "url": "url", "content": "context7 content", "origin": "context7"},
        {"title": "Exa: Production patterns", "url": "url", "content": "exa content", "origin": "exa"},
        {"title": "Jina: Benchmarks", "url": "url", "content": "jina content", "origin": "jina"},
        # ...deep_content items appended as "origin": "jina-read"
    ],
    style="comparative",
    preset="contracrow"  # → comparison query, highlight conflicts
)

# OR use reason for chain-of-thought analysis (critical decisions)
reasoning = mcp__gigaxity-deep-research__reason(
    query="Which framework is better for high-traffic production APIs?",
    context="[Summary of Triple Stack findings]",
    reasoning_depth="deep"  # → critical architectural decision
)
```

**Key Insight:** gigaxity-deep-research does NOT re-search. Triple Stack already gathered content - just synthesize it. This is the critical difference from deprecated Perplexity which would re-search.

**Free middleware (use liberally — 0 token cost on Jina):**
- `mcp__jina__sort_by_relevance(query, documents)` — rerank Triple Stack URL union before deciding which to deep-read
- `mcp__jina__deduplicate_strings(strings)` — filter near-duplicate snippets before feeding to synthesize (reduces synthesis token burn)
- `mcp__jina__guess_datetime_url(url)` — verify source freshness/credibility per-URL before trusting it

**MANDATORY:** SYNTHESIS workflow MUST end with `mcp__gigaxity-deep-research__synthesize` (or `reason` for chain-of-thought). Do NOT freehand the synthesis in the main thread. Do NOT stop after Triple Stack and wait for user input. The only valid escape hatch is the post-synthesis verifier verdict (see next).

### Verifier Verdict Handling

`synthesize` runs a post-synthesis verifier and prepends a structural header ONLY on a STRUCTURAL hard-gate failure — empty content, reasoning-only trace, truncated by token limit, sub-call failure, or zero citations on non-empty sources. (As of v0.5.0, entity-coverage — a discussed query entity absent from every retained source — is **no longer a hard gate**; it is an advisory soft warning, covered in the soft-warning note below):

```
# Synthesis verification FAILED

This output is not a reliable synthesis:
- <reason 1>
- <reason 2>

---
(unverified output below, for debugging)

<original output>
```

**When you see this header:**

1. Do NOT relay the failed output to the user as-is — the verifier explicitly says it is not a reliable synthesis.
2. Diagnose the failure reasons. Common patterns:
   - `truncated by token limit` → raise `RESEARCH_LLM_MAX_TOKENS` (env var on the MCP), or switch preset to `fast` (less preprocessing budget burn).
   - `reasoning trace instead of answer` → model spending budget on chain-of-thought; raise `RESEARCH_LLM_REASONING_HEADROOM` or pick a non-reasoning model.
   - `zero citations on N sources` → source content may not have reached the model; check disk-spill on the source-gathering tools (per "Tool Output Persistence" above) — agents commonly synthesize from 2KB previews and end up with sources whose content never made it to the model.
3. ONE retry is permitted: re-call synthesize with a different `preset` (e.g., `contracrow` → `fast`) or fewer sources.
4. If the retry also FAILS, fall back to main-thread synthesis from the raw sources, AND prepend the user-facing answer with: `> Note: gigaxity-deep-research synthesize failed verification on retry; this is a main-thread synthesis from raw sources without the verifier guarantees.`
5. Hard-failed outputs are NOT cached, so the next call will re-run — do not cache-bust manually.

Soft warnings append `*Verification notes: <warning>*` at the end of the output and are advisory; the synthesis is usable — relay it, but flag the caveat in your final answer. **Do NOT retry or fall back on a soft warning.** Entity-coverage caveats live here (demoted from the old hard-fail in v0.5.0): when the synthesis discusses a query entity that no retained source covers verbatim, the verifier appends a graduated note and the synthesis **STILL PASSES** — `treat those cited claims as UNVERIFIED` for a cited-adjacent uncovered entity, a lighter "frames the gap" note when the entity's sentence explicitly acknowledges the gap ("no source available for X", "not documented"), a `surface-form variant` note for a known alias/version form (a source saying `dockerd` for "Docker Engine", `wsl2` for "WSL"), and an `emphasis/framing` note for shouted ALL-CAPS query framing. Because passing outputs are the ones cached, **`passed=True` (or a cache hit) no longer implies entity-coverage is clean** — inspect `soft_warnings` / the `*Verification notes:*` line, surface the caveat, then move on; treat it as guidance, not a failure.

**Contradiction-detector notes are the soft-warning class most often misread (v0.12.0).** Any note beginning `contradiction detection …` means the conflict list is **NON-EXHAUSTIVE** — detection never gates the synthesis, so a degraded run passes and simply reports fewer disagreements than exist. On `contracrow` that silently removes the reason you picked the preset. **Never report "no contradictions were found" when one of these is present** — say the check degraded, and which:

| Note | Meaning | Your move |
|---|---|---|
| `could not be parsed` | Labels emitted, block unreadable — a grammar problem. | Worth reporting upstream. |
| `returned no structured output` | The model never attempted the format (prose, refusal, own shape). NOT a parser bug. | Re-run once or accept the gap. |
| `returned both findings and a 'no contradictions' declaration` | The response contradicted itself; findings retained but unconfirmed. | Present the conflicts as candidates, not established. |
| `used the degraded heuristic detector` | No LLM client — keyword-pair heuristic. | Low confidence by construction. |
| `failed and fell back to a heuristic (<error>)` | The detector call raised; cause in the error. | Transport/config issue. |

Still a soft warning: relay the synthesis, surface the caveat, do NOT retry or fall back on it alone. (v0.12.0 also made the parser tolerate markdown-decorated labels — `**TOPIC:**`, `- TOPIC:`, `1. TOPIC:`, `` `TOPIC`: `` — so a markdown-heavy model no longer loses its whole contradiction list to formatting.)

### Gate Early-Return (distinct from verifier hard-fail)

`synthesize` can also short-circuit at the **pre-synthesis relevance gate**, before the synthesizer runs. As of v0.6.0 a rejection only short-circuits when the source set is *entirely* below the fail-open floor (`RESEARCH_FAIL_OPEN_MIN_SOURCE_SCORE`, default 0.3 = the REJECT threshold); if even one source clears the floor the gate **fails open** instead (covered after the two refusal cases). The two refusal cases:

1. **`## Source quality insufficient`** (REJECT decision) — average source relevance below the gate's `reject_threshold` (defaults 0.2 for `comprehensive`/`contracrow`, 0.3 for class default). Returned as a markdown response with a header like:

   ```
   # Synthesis: {query}
   *Preset: Comprehensive*
   ## Source quality insufficient

   The pre-synthesis relevance gate rejected the input source set (avg relevance 0.15 below threshold 0.2). Synthesis skipped to prevent hallucination over irrelevant sources.

   **Suggested follow-up searches:** ...

   ---
   *Pre-synthesis source-relevance gate: 0 passed, N filtered (avg source relevance: 0.15). Synthesis NOT cached — gather better sources and re-call.*
   ```

2. **`## Source quality insufficient (partial, zero passed)`** (PARTIAL-with-zero-good edge case) — average relevance above the reject floor but no individual source clears the `pass_threshold`. Same shape, different header.

**Fail-open (the common case, v0.6.0).** Both refusals above only fire when *no* source clears the fail-open floor (`RESEARCH_FAIL_OPEN_MIN_SOURCE_SCORE`, default 0.3). When the gate would REJECT or hit PARTIAL-with-zero-good but at least one source clears the floor, `synthesize` does NOT refuse — it **fails open**, synthesizing over the set-aside (rejected) sources and opening the answer with a `low source relevance (fail-open)` caveat. Treat that result as **weakly grounded**: relay it and flag the caveat, the same way you handle a soft warning — do NOT retry on it. The fail-open result is **NOT cached** (so a later call with better sources isn't shadowed), but there IS a synthesis to use.

**When you see a `## Source quality insufficient` refusal (no source cleared the floor):**
- The synthesizer was **never invoked**; there is no synthesis to retry.
- The output is **NOT cached** — re-calling with the same sources will re-evaluate.
- Action: gather more relevant sources (Triple Stack again, broader queries, different focus mode) and re-call. Do NOT retry with the same source set; the gate's verdict is data-driven, not flaky.
- Distinct from the verifier hard-fail above — those mean the synthesizer ran but produced unreliable output; these mean the synthesizer was deliberately skipped.

**Token cost:** ~5000-10000 tokens
**Time:** 3-5 min

---

## Tool Reference

### gigaxity-deep-research Tools

| Tool | Role | Description |
|------|------|-------------|
| `discover` | EXPLORATORY | Cold-start discovery with gap analysis, returns scored URLs |
| `synthesize` | SYNTHESIS | Weave pre-gathered content into coherent narrative with citations |
| `reason` | SYNTHESIS | Chain-of-thought reasoning on pre-gathered content |
| `ask` | DIRECT | Quick LLM answer without search |
| `search` | Utility | RRF fusion search (use when simple search needed) |
| `research` | Convenience | Combined search+synthesis (standalone use only) |

#### discover: focus_mode Parameter

Controls domain-specific gap analysis and search strategy:

| Mode | Gap Categories | Search Expansion | Use When |
|------|---------------|------------------|----------|
| `general` | documentation, examples, alternatives, gotchas | ON | Broad technical questions |
| `academic` | methodology, limitations, replications, critiques | ON | Research papers, scientific topics |
| `documentation` | api_reference, examples, migration, changelog, configuration | OFF (focused) | Library/framework questions |
| `comparison` | criteria, tradeoffs, edge_cases, benchmarks, community_preference | ON | "Which should I use?" questions |
| `debugging` | error_context, similar_issues, root_cause, workarounds, fixes | ON | Error messages, stack traces |
| `tutorial` | prerequisites, step_by_step, common_mistakes, next_steps | OFF | Learning, getting started |
| `news` | announcement, reaction, impact, timeline | ON + time-filtered | "Latest" or "announced" queries |

```
# Example: Debugging query
mcp__gigaxity-deep-research__discover(
    query="TypeError: Cannot read property 'map' of undefined React",
    focus_mode="debugging"  # → triggers error_context, root_cause gaps
)

# Example: Learning query
mcp__gigaxity-deep-research__discover(
    query="How to get started with FastAPI",
    focus_mode="tutorial"  # → triggers prerequisites, step_by_step gaps
)
```

#### synthesize: preset Parameter

Controls which pipeline components run before synthesis:

| Preset | Pipeline Components | Use When |
|--------|---------------------|----------|
| `comprehensive` | Quality Gate → RCS → Contradiction Detection → Outline-Guided | Important research, best quality |
| `fast` | Direct synthesis only | Sources already high-quality, need speed |
| `contracrow` | Quality Gate → RCS → Contradiction Detection | Sources may disagree, comparisons |
| `academic` | Quality Gate → RCS → Contradiction Detection → Outline-Guided | Formal reports, documentation |
| `tutorial` | Outline-Guided only | Guides, tutorials, explanations |

**Pipeline components:**
- **Quality Gate (CRAG)**: Filter low-quality/irrelevant sources
- **RCS**: Query-focused summarization (summarize each source for the specific question)
- **Contradiction Detection**: Find conflicting claims between sources
- **Outline-Guided**: Plan structure before writing (better coverage)

```
# Example: Comparison with potential conflicts
mcp__gigaxity-deep-research__synthesize(
    query="FastAPI vs Flask",
    sources=[...],
    style="comparative",
    preset="contracrow"  # → highlights conflicting claims
)

# Example: Quick synthesis of trusted sources
mcp__gigaxity-deep-research__synthesize(
    query="React hooks",
    sources=[official_docs],
    preset="fast"  # → no preprocessing, direct synthesis
)
```

#### reason: reasoning_depth Parameter

Controls chain-of-thought thoroughness:

|

…(truncated)
