SurfSense/plans/backend/04-capabilities.md

3.9 KiB

Phase 4 — Capabilities (the scraper APIs)

Build first; every other phase calls this registry. Sibling: 05-access.md (the doors). Reuses Phases 3a/3b/3c (shipped): WebCrawlerConnector.crawl_url -> CrawlOutcome, the proxy/stealth/ captcha tiers, and the WebCrawlCreditService billing seam. Locate code by symbol/grep.

Objective

Expose the Acquisition engine as a small set of typed, stateless scraper verbs (web.*, maps.*) that return cleaned, AI-ready structured data. One capability = one function you call and get data back from.

Verb set (MVP)

Two namespaces: web.* (generic crawler) and maps.* (Google Maps actor). Future platforms slot in as their own namespace.

Verb Input → Output (cleaned) Executes over Billing unit
web.scrape(urls[]) [{url, status, content, metadata}] loop crawl_url per success
web.discover(query, top_k) [{url, title, snippet, provider}] search providers per search / free
maps.search(query, location) [place] Maps actor per place
maps.place(place_id|url) place Maps actor 1
maps.reviews(place) [review] (paged) Maps actor per page

maps.* returns typed structured objects ({name, rating, review_count, hours, price_level, …}). web.scrape takes a URL array.

Execution

Each verb is a direct async call that returns its cleaned result. Slow verbs (large web.scrape arrays, maps.reviews paging) are bounded/streamed at the endpoint.

Registry

One entry per verb, and the single source of truth the doors (05) and the agent (07) read from:

Capability {
  name            # dotted, e.g. "web.scrape", "maps.search"
  input_schema    # Pydantic
  output_schema   # Pydantic (cleaned, AI-ready)
  executor        # async fn; wraps Acquisition / Maps actor; returns data directly
  billing_unit    # how the billing service charges this call
}

Adding a verb once lights it up on every door.

Billing

A verb declares a billing_unit; charging is delegated to the billing service (03c first provider).

  • web.scrape → per SUCCESS (web_crawl unit).
  • maps.* → new per-place / per-page unit registered with the billing service.
  • captcha attempts → existing per-attempt web_crawl_captcha unit (03d).
  • web.discover → per-search unit (or free).

Location

New Apache-2 package app/capabilities/ (registry, schemas, executors) — open-source and self-hostable. Executors import the proprietary Acquisition engine and Maps extractor (app/proprietary/…).

Work items

  1. Registry package: Capability dataclass + registry, imported by 05 and 07.
  2. web.scrape executor: loop crawl_url over the URL array → per-URL cleaned rows; web_crawl unit.
  3. web.discover executor: wrap the search-provider core (SearXNG/Linkup/Baidu, env-keyed); its unit.
  4. maps.* contracts: input/output schemas + executor stubs against the Maps actor.
  5. Output normalization: each executor returns AI-ready structured output.
  6. Billing seam: honor billing_unit via the billing service.

Tests

  • web.scrape([a,b,c]) returns one cleaned row per URL inline; partial failures don't fail the batch.
  • Each SUCCESS bills one web_crawl unit; EMPTY/FAILED free; disabling the flag no-ops.
  • A door and the agent hit the same executor for a verb.
  • web.discover returns {url,title,snippet,provider}; self-disables when no provider key is set.
  • maps.* returns typed structured objects against fixtures.

Out of scope

  • Doors → 05. Keep-watching mode → 06. Agent → 07.
  • Google Maps actor build (incl. sourcing legality) — net-new, separate effort; maps.* are contracts.
  • Additional platform namespaces; recursive site crawl; a generic web.extract verb.

Open questions

  • web.discover metered vs free.
  • How large web.scrape / maps.reviews responses are bounded/streamed.