Run a browser task through an autonomous agent-browser behind one interface — and
swap or stack providers freely. One task in (EngineTask), one normalized result out
(EngineResult), with routing + fallback, a confidence gate, and a graduation
log. Providers are pluggable.
Autonomous agent-browser providers (Browser Use, Anchor, Browserbase, Bright Data, …) each
have different strengths, DX, and failure modes. This wraps them behind a single Engine
interface so you can: pick a primary, fall back to another on failure, catch an engine that
claims success while sitting on a login-failed page, and log every run so you can see which
providers are reliable for which sources over time.
| Engine | Backend | Shape | Local browser? | Login + file download? |
|---|---|---|---|---|
browser-use |
Browser Use Cloud (REST v2) | autonomous NL agent | no | ✅ (auto-captures downloads) |
anchor |
Anchor Browser (REST) | autonomous NL agent | no | ✅ (pdf-viewer off + downloads API) |
browserbase |
Browserbase + Stagehand | scripted act/extract | yes (Playwright) | ✅ (CDP download capture) |
bright-data |
Bright Data Web Scraper API | trigger→snapshot→poll | no | ❌ public extraction only* |
* Bright Data's autonomous, cloud-only surface is the Web Scraper API — public-site
structured extraction via a pre-built dataset_id; it doesn't log into your account, so for
file/auth tasks it honestly returns not_found.
browser-use and anchor are pure REST cloud — no local browser. browserbase
lazy-imports Stagehand/Playwright (install the browserbase extra).
class Engine(Protocol):
name: str
async def run(self, task: EngineTask) -> EngineResultThe router applies check_confidence() after each run — an engine claiming "success" while
sitting on a login-failed page or returning null required fields gets downgraded to
auth_failed / low_confidence before it's trusted, and the fallback chain kicks in.
from agentic_engine import build_engines, create_router, RouterPolicy, build_bill_task
router = create_router(build_engines(), RouterPolicy(primary="browser-use", fallback=["anchor"]))
task = build_bill_task(task_run_id="run_1", login_url="https://portal.example.com/login",
username=..., password=...)
report = await router.run(task) # or override={"engine": "anchor"}
print(report.result.outcome, [f.name for f in report.result.files])Providers register in a central registry. Add your own three ways:
# 1. decorate a class/factory (in-tree or your own module)
from agentic_engine import provider, EngineResult, EngineTask
@provider("my-engine")
class MyEngine:
name = "my-engine"
def __init__(self, dl_dir=None): ...
async def run(self, task: EngineTask) -> EngineResult: ...
# 2. register imperatively
from agentic_engine import register_provider
register_provider("my-engine", MyEngine)# 3. ship a separate pip package that exposes an entry point — discovered automatically
[project.entry-points."agentic_engine.providers"]
my-engine = "my_pkg.engine:MyEngine"available_providers() lists everything registered (incl. entry points); build_engine(name)
/ build_engines([...]) instantiate them. A factory must accept a dl_dir keyword (or
**kwargs); everything else it reads from the environment.
BROWSER_USE_API_KEY · ANCHOR_API_KEY · BROWSERBASE_API_KEY+BROWSERBASE_PROJECT_ID+OPENAI_API_KEY+STAGEHAND_MODEL · BRIGHTDATA_API_KEY(+BRIGHTDATA_DATASET_ID) · AGENTIC_DOWNLOAD_DIR
uv sync --extra dev
uv run pytest -q # router + confidence unit tests (no network)