Skip to content

Repository files navigation

A3S Search converges browser, HTTP and RSS, and native API sources into one typed Rust metasearch result

Language / 语言: English · 中文

One query in. One typed result boundary out.

A3S Search CI a3s-search on crates.io a3s-search documentation Implemented in Rust MIT License

Run · Sources · Fields · Receipt · Configure · Extend


A3S Search is a Rust library and CLI that runs one typed query across the sources you select, then returns one SearchResults value. Browser pages, HTTP/RSS documents, and native search APIs all cross the same Engine boundary. The runtime executes concurrently, keeps partial failures, normalizes and deduplicates URLs, and fuses source ranks. The default cascade plan (EngineTierPlan) lives in the library so embedders and the CLI share one product policy.

Important

A3S Search retrieves. It does not plan queries, judge meaning, verify claims, or write reports. Callers such as DeepResearch own those decisions. A cascade receipt records structural execution, not semantic approval.

Run one search

Install the published command, then search. Moli is the default headless fallback; install it only if that tier must run.

cargo install a3s-search
# or: brew install A3S-Lab/tap/a3s-search

a3s-search "Rust async runtime guidance" --format json --limit 10
curl --proto '=https' --tlsv1.2 -fsSL \
  https://github.com/lexmount/moli/releases/latest/download/moli-installer.sh | sh

Set A3S_MOLI_EXECUTABLE when the executable is not on PATH.

An explicit --engines list is the whole plan. It is never expanded into the default cascade, and every selected source receives the same SearchQuery.

a3s-search "Rust async runtime guidance" \
  --engines anysearch,tavily \
  --format json \
  --limit 10

JSON keeps the useful output and the degraded path in one document:

SearchResults
├── results[]       ranked URLs, snippets, provenance, dates, rich fields
├── answers[]       direct answers, only when the provider was asked for them
├── images[]        images, only when the provider was asked for them
├── reports[]       request IDs, timing, usage, bounded metadata
├── failures[]      typed error, transient state, retry delay
└── outcomes[]      success, empty, failure, timeout, rejected, circuit-open

The default CLI also emits cascade_receipt and cascade_receipt_binding.

Embedded in Rust

[dependencies]
a3s-search = "3"
tokio = { version = "1", features = ["full"] }
use a3s_search::{
    engines::{DuckDuckGo, Wikipedia},
    Search, SearchQuery,
};

#[tokio::main]
async fn main() -> a3s_search::Result<()> {
    let mut search = Search::new();
    search.add_engine(DuckDuckGo::new());
    search.add_engine(Wikipedia::new());

    let results = search
        .search(SearchQuery::new("extensible Rust search"))
        .await?;

    for result in results.items() {
        println!("{:.3} {} {:?}", result.score, result.url, result.engines);
    }
    Ok(())
}

Search is caller-owned. The host chooses engines, weights, timeouts, and shared reliability controls. The crate does not hide them in global state. Use [EngineTierPlan] plus [product::execute_cascade] for the same default cascade the CLI ships with. Use default-features = false for an HTTP/API-only build.

Choose sources

A3S Search does not keep a private web index. With no explicit selection and no ACL source selection, the CLI uses this plan and stops when structural requirements are met:

1  native API     anysearch + tavily
2  HTTP / RSS     wiki + ddg + bing
3  headless       brave_browser + bing_browser through Moli

A no-headless build adds brave to the HTTP tier instead of the browser fallback. Tiers share one deadline (20 seconds by default). Engines in a tier run concurrently. A lower tier is constructed only when the tier above it has not met the structural requirement.

Class Shortcut Source When it runs
Native API anysearch AnySearch Default primary tier
Native API tavily Tavily Default primary tier
HTTP/RSS wiki Wikipedia Default second tier
HTTP/RSS ddg DuckDuckGo Default second tier
HTTP/RSS bing Bing International RSS Default second tier
Browser brave_browser Brave Search Default final fallback
Browser bing_browser Bing International Default final fallback
HTTP/RSS brave Brave Search No-headless build only
Browser g Google Explicit
Browser baidu Baidu Explicit
HTTP/RSS sogou Sogou Explicit
HTTP/RSS 360 360 Search Explicit
HTTP/RSS bing_cn Bing China RSS Explicit
Native API tinyfish TinyFish Search Explicit, billed
Native API bocha Bocha Web Search Explicit, billed
Native API aliyun Alibaba Cloud IQS Explicit, billed
Native API tencent Tencent Cloud Search Explicit, billed
Native API firecrawl Firecrawl Search Explicit, billed

bing_cn requests www.bing.com/search with format=rss, setlang=zh-CN, and mkt=zh-CN. It does not use cn.bing.com: that host redirects some clients off /search onto the homepage, which then returns HTML.

HTML engines reject CAPTCHA, consent, and anti-bot pages as typed challenge failures. An unrelated successful page is invalid_response, not an empty result set.

Billed APIs stay out of the default plan

TinyFish, Bocha, Alibaba Cloud IQS, Tencent Cloud Search, and Firecrawl require an API key. They are omitted from the default cascade so a missing key does not fail every search and an ambient key does not start billing. Select them explicitly:

export TINYFISH_API_KEY="..."
export BOCHA_API_KEY="..."
export ALIYUN_IQS_API_KEY="..."
export TENCENTCLOUD_WSA_APIKEY="..."
export FIRECRAWL_API_KEY="..."

a3s-search "query" --engines bocha --format json

AnySearch and Tavily accept optional bearer authentication and can run without a key. --proxy applies to scraping transports. Native provider API requests stay direct.

Trust the fields you asked for

A3S Search passes one exact query through source execution, normalization, URL fusion, and weighted rank fusion while semantic policy stays with the caller

Every native API implements SearchProvider. ProviderEngine is the only adapter into the cascade, ranking, and the CLI. The public result cap is max_results in Rust and ACL. Vendor wire names (count, limit, Cnt, numResults) are not ACL attributes.

An output capability that requires a request flag follows that flag. Do not treat an omitted answer, image, usage record, or page text as available.

Provider Default request What that means for the response
AnySearch MCP search tool Full text is requested; there is no flag that turns it off
Tavily Plain source text Answers, images, and usage stay off until requested. Set include_raw_content = "none" to opt out of source text
TinyFish No thumbnails images follows include_thumbnail (default false). Paging is supported
Bocha Summaries on full_text follows summary (default true). Set summary = false to omit page summaries
Aliyun IQS LiteAdvanced, no page text full_text follows include_main_text (default false). Images may still appear; the request cannot disable them
Tencent Cnt omitted Full text and images may appear. Set premium max_results only when that billed count is intended
Firecrawl Web metadata full_text follows include_markdown (default false). That flag scrapes each result and bills extra. images follows image sources

Required-credential JSON APIs share one transport shell. The vendor module owns options, request mapping, response mapping, and error-code classification. Optional-auth and MCP codecs stay off that shell. Credentials belong in env("VARIABLE"), never in endpoint URLs, and are sealed out of success bodies before the reply is dropped.

Rank and receipt

Rank fusion is not semantic scoring. After URL normalization and deduplication, each source contributes:

engine weight × reciprocal rank × provider-local relevance factor

A provider relevance value is calibrated only inside that provider's response. Scores from unrelated APIs are not placed on one scale, and query-text matching is not substituted for them.

RetrievalRequirements::for_limit asks for one logical source on a single-result request and two independent logical sources when more than one result is requested. Browser and HTTP variants of the same upstream count as one source, so repeating a transport cannot stop fallback. The CLI then applies select_structural_window to the visible Top-K using URL validity, hosts, provenance, and consensus. It does not read the query, title, snippet, language, or publisher.

receipt_binding() is a domain-separated SHA-256 over the validated receipt. It detects substitution against a trusted digest. It does not prove who ran the search or whether the pages are true.

Configure

ACL selects sources, credentials, timeouts, ranking, and provider options. --config is a parent flag:

a3s-search --config search.acl engines
a3s-search "query" --config search.acl --format json
timeout {
  value = 20
}

provider "tavily" {
  api_key             = env("TAVILY_API_KEY")
  max_results         = 10
  include_raw_content = "markdown"
  include_answer      = "advanced"
  include_images      = true
  include_usage       = true
}

provider "bocha" {
  api_key     = env("BOCHA_API_KEY")
  max_results = 8
  summary     = true
}

provider "aliyun" {
  api_key          = env("ALIYUN_IQS_API_KEY")
  engine_type      = "lite-advanced"
  include_main_text = true
}

provider "firecrawl" {
  api_key          = env("FIRECRAWL_API_KEY")
  max_results      = 8
  include_markdown = false
}

The Tavily block above opts into answers, images, and usage. Leave those attributes unset to keep the default, which requests source text only. enabled = false records engine_disabled and does not contact the network. Unknown wire names such as count, limit, and cnt are rejected.

Typed surfaces: SearchConfig, AnySearchConfig, TavilyConfig, TinyFishConfig, BochaConfig, AliyunConfig, TencentConfig, and FirecrawlConfig.

Extend

Use Engine for a source that returns ordinary web or media results. Use SearchProvider when the source has capabilities, readiness, rich output, or a structured report, then adapt it with ProviderEngine:

let engine = ProviderEngine::new(my_provider);
search.add_engine(engine);

Direct answers, images, full text, usage, and reports stay first-class fields. They are not rewritten into synthetic web results.

Ordinary Engine implementation
#[async_trait::async_trait]
impl a3s_search::Engine for MyEngine {
    fn config(&self) -> &a3s_search::EngineConfig {
        &self.config
    }

    async fn search(
        &self,
        query: &a3s_search::SearchQuery,
    ) -> a3s_search::Result<Vec<a3s_search::SearchResult>> {
        // Call one source and map its response into SearchResult values.
        todo!()
    }
}

Operate

Control What it bounds
HealthMonitor Consecutive-failure suspension on one Search without a shared circuit (HealthSuspended)
CircuitBreaker Shared closed/open/half-open source state (CircuitOpen); owns isolation when attached
Bulkhead In-flight work and queue wait per engine
SearchCoalescer Identical overlapping requests; not a cache
Metrics In-memory counts and latency percentiles
retry::RetryBudget Browser render retry amplification only (not a Search peer)

Share controls only across compatible tenants, credentials, endpoints, and proxies. The CLI persists credential-independent challenge circuits, not queries, results, or secrets. Set A3S_SEARCH_STATE_DIR to an absolute directory for an isolated state scope.

Build Browser behavior
default / headless / moli Discover Moli from A3S_MOLI_EXECUTABLE, PATH, or the official install location
--browser chrome Explicit Chrome/Chromium backend
lightpanda Explicit backend only; Windows requires WSL2
--no-default-features No browser/CDP stack
cargo run -- "query" --browser moli
cargo run -- "query" --browser chrome
cargo run --features lightpanda -- "query" --browser lightpanda

Native providers may return full_text directly. Snippet-only results can be enriched with enrich_full_text. A failed enrichment keeps the snippet and does not change rank or cascade decisions. Scraping proxies do not apply to native provider clients.

From v2

Version 3 removed semantic policy from this crate and made Moli the default browser fallback.

  • SearchQuality, SearchQualityFloor, and query_match_score are gone. Evaluate relevance in the host.
  • Build cascades with RetrievalRequirements. Record an opaque host decision with push_tier_with_decision; the receipt marks it external_policy.
  • Consume SearchCascadeOutcomeV2 and SearchCascadeReceiptV2. None of those fields approves the pages.
  • The default feature includes Moli. --no-default-features is the HTTP/API build.
  • With no explicit selection, the CLI runs API → HTTP/RSS → headless.

Unpublished candidate identities before v3.0.9 were retired and are not reused. From v3.0.9, release assurance lives in this repository: the exact source revision and package bytes must match before crates.io, GitHub Release, or Homebrew can publish. Prerelease tags may publish GitHub CLI archives only.

Develop

Run checks from this repository, not from the A3S monorepo root:

cargo fmt --all -- --check
cargo test --no-default-features --locked
cargo test --all-features --locked
cargo clippy --all-targets --no-default-features --locked -- -D warnings
cargo clippy --all-targets --all-features --locked -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features --locked

The release workflow also runs both clippy feature sets, package and freeze checks, and the pinned Moli fixture. A 300-second reliability soak is opt-in and is not part of CI. Missing or mismatched package evidence keeps crates.io, GitHub Release, and Homebrew fail-closed.

Platform archives include the CLI and skills/a3s-search/SKILL.md. The Skill selects sources and reads structural evidence. It does not evaluate pages.

Ecosystem

  • A3S — platform entry point
  • A3S Code — coding-agent runtime
  • Moli — default headless browser runtime
  • A3S Browser — rendering contract and Chrome backend
  • A3S Science — research workflows above this kernel

Contributing

Keep semantic evaluation outside the retrieval kernel. Cover behavior changes with tests, and run both feature sets before submitting.

License

MIT

About

Embeddable Meta Search Engine

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages