Search Layer v2.2 — Intent-Aware Multi-Source Retrieval Protocol
Four equal-tier sources: Brave (
) + Exa + Tavily + Grok. Automatically selects strategies, adjusts weights, and performs synthesis based on intent.
Execution Flow
User Query
↓
[Phase 1] Intent Classification → Determine Search Strategy
↓
[Phase 2] Query Decomposition & Expansion → Generate Sub-Queries
↓
[Phase 3] Multi-Source Parallel Retrieval → Brave + search.py (Exa + Tavily + Grok)
↓
[Phase 4] Result Merging & Sorting → Deduplication + Intent-Weighted Scoring
↓
[Phase 5] Knowledge Synthesis → Structured Output
Phase 1: Intent Classification
Upon receiving a search request, first determine the intent type, then decide the search strategy. Do not ask the user which mode to use.
| Intent | Identification Signals | Mode | Freshness | Weight Bias |
|---|
| Factual | "What is X", "Definition of X", "What is X" | answer | — | Authority 0.5 |
| Status | "Latest developments of X", "Current status of X", "latest X" | deep | pw/pm | Freshness 0.5 |
| Comparison | "X vs Y", "Differences between X and Y" | deep | py | Keywords 0.4 + Authority 0.4 |
| Tutorial | "How to do X", "Tutorial for X", "how to X" | answer | py | Authority 0.5 |
| Exploratory | "Deep dive into X", "Ecosystem of X", "about X" | deep | — | Authority 0.5 |
| News | "News about X", "X this week", "X this week" | deep | pd/pw | Freshness 0.6 |
| Resource | "Official website of X", "GitHub of X", "Documentation of X" | fast | — | Keywords 0.5 |
Detailed classification guidelines can be found in
references/intent-guide.md
Judgment Rules:
- Scan for signal words in the query
- Select the most specific type when multiple types match
- Default to if intent cannot be determined
Phase 2: Query Decomposition & Expansion
Expand the user's query into a set of sub-queries based on intent type:
General Rules
- Automatic expansion of technical synonyms: k8s→Kubernetes, JS→JavaScript, Go→Golang, Postgres→PostgreSQL
- Chinese technical queries: Generate English variants simultaneously (e.g., "Rust 异步编程" → additionally search for "Rust async programming")
Expansion by Intent
| Intent | Expansion Strategy | Example |
|---|
| Factual | Add "definition", "explained" | "WebTransport" → "WebTransport", "WebTransport explained overview" |
| Status | Add year, "latest", "update" | "Deno 进展" → "Deno 2.0 latest 2026", "Deno update release" |
| Comparison | Split into 3 sub-queries | "Bun vs Deno" → "Bun vs Deno", "Bun advantages", "Deno advantages" |
| Tutorial | Add "tutorial", "guide", "step by step" | "Rust CLI" → "Rust CLI tutorial", "Rust CLI guide step by step" |
| Exploratory | Split into 2-3 perspectives | "RISC-V" → "RISC-V overview", "RISC-V ecosystem", "RISC-V use cases" |
| News | Add "news", "announcement", date | "AI 新闻" → "AI news this week 2026", "AI announcement latest" |
| Resource | Add specific resource types | "Anthropic MCP" → "Anthropic MCP official documentation" |
Phase 3: Multi-Source Parallel Retrieval
Step 1: Brave (All Modes)
Call
for each sub-query. If the intent has freshness requirements, pass the
parameter:
web_search(query="Deno 2.0 latest 2026", freshness="pw")
Step 2: Exa + Tavily + Grok (Deep / Answer Modes)
Call search.py for sub-queries, passing intent and freshness:
bash
python3 /home/node/.openclaw/workspace/skills/search-layer/scripts/search.py \
--queries "sub-query1" "sub-query2" "sub-query3" \
--mode deep \
--intent status \
--freshness pw \
--num 5
Source Participation Matrix by Mode:
| Mode | Exa | Tavily | Grok | Notes |
|---|
| fast | ✅ | ❌ | fallback | Prioritize Exa; use Grok if Exa key is unavailable |
| deep | ✅ | ✅ | ✅ | Three sources in parallel |
| answer | ❌ | ✅ | ❌ | Only Tavily (includes AI answer) |
Parameter Explanation:
| Parameter | Description |
|---|
| Execute multiple sub-queries in parallel (can also pass a single query as a positional parameter) |
| fast / deep / answer |
| Intent type, affects scoring weights (if not passed, no scoring is performed, behavior is consistent with v1) |
| pd(24h) / pw(week) / pm(month) / py(year) |
| Comma-separated domains, matching results get +0.2 to authority score |
| Number of results per source per query |
Grok Source Notes:
- Call the Grok model () via the completions API, leveraging its real-time knowledge to return structured search results
- Automatically detects time-sensitive queries and injects current time context
- Executes in parallel with Exa and Tavily in deep mode
- Requires configuring , , in TOOLS.md
- If Grok configuration is missing, automatically downgrades to Exa + Tavily dual sources
Step 3: Merging
Merge Brave results with search.py output. Deduplicate by canonical URL and mark sources.
If search.py returns a
field, sort using it; for Brave results without a score, calculate it using the same intent weight formula.
Phase 4: Result Sorting
Scoring Formula
score = w_keyword × keyword_match + w_freshness × freshness_score + w_authority × authority_score
Weights are determined by intent (see Phase 1 table). Breakdown of each component:
- keyword_match (0-1): Coverage of query terms in title + abstract
- freshness_score (0-1): Based on publication date, newer results score higher (0.5 if no date)
- authority_score (0-1): Based on domain authority level
- Tier 1 (1.0): github.com, stackoverflow.com, official documentation sites
- Tier 2 (0.8): HN, dev.to, well-known technical blogs
- Tier 3 (0.6): Medium, 掘金 (Juejin), InfoQ
- Tier 4 (0.4): Others
Complete domain scoring table can be found in
references/authority-domains.json
Domain Boost
Manually specify domains to boost via the
parameter (matching results get +0.2 to authority score):
bash
search.py "query" --mode deep --intent tutorial --domain-boost dev.to,freecodecamp.org
Recommended Pairings:
- Tutorial →
dev.to, freecodecamp.org, realpython.com, baeldung.com
- Resource →
- News →
techcrunch.com, arstechnica.com, theverge.com
Phase 5: Knowledge Synthesis
Select synthesis strategy based on the number of results:
Small Result Set (≤5 entries)
Display each entry with source tags and score:
1. [Title](url) — snippet... `[brave, exa]` ⭐0.85
2. [Title](url) — snippet... `[tavily]` ⭐0.72
Medium Result Set (5-15 entries)
Cluster by topic + summary per group:
**Topic A: [Description]**
- [Result1] — Key points... `[source]`
- [Result2] — Key points... `[source]`
**Topic B: [Description]**
- [Result3] — Key points... `[source]`
Large Result Set (15+ entries)
High-level overview + Top 5 + deep dive prompt:
[A paragraph summarizing key findings]
**Top 5 Most Relevant Results:**
1. ...
2. ...
A total of N results found, covering [source list]. Which aspect would you like to explore in depth?
Synthesis Rules
- Provide the answer first, then list sources (do not start with "I searched for...")
- Aggregate by topic, not by source (do not use "Brave results: ... Exa results: ...")
- Explicitly mark conflicting information: Clearly indicate when different sources have contradictory statements
- Express confidence:
- Consistent across multiple sources + fresh → State directly
- Single source or older → "According to [source], ..."
- Conflicting or uncertain → "Different opinions exist: A believes..., B believes..."
Degradation Strategy
- Exa 429/5xx → Continue with Brave + Tavily + Grok
- Tavily 429/5xx → Continue with Brave + Exa + Grok
- Grok timeout/error → Continue with Brave + Exa + Tavily
- search.py fails entirely → Use only Brave (always available)
- Never block the main process due to failure of a single source
Backward Compatibility
When the
parameter is not passed, search.py behavior is fully consistent with v1 (no scoring, output in original order).
Existing callers (e.g., github-explorer) do not need modification.
Quick Reference
| Scenario | Command |
|---|
| Quick Facts | + search.py --mode answer --intent factual
|
| Deep Research | + search.py --mode deep --intent exploratory
|
| Latest Updates | web_search(freshness="pw")
+ search.py --mode deep --intent status --freshness pw
|
| Comparative Analysis | × 3 queries + search.py --queries "A vs B" "A pros" "B pros" --intent comparison
|
| Resource Finding | + search.py --mode fast --intent resource
|