Initialising
Skip to content
Agent / Systems Engineer

BAT — Browser Automation Tool

A study in making an autonomous agent reliable against an environment it does not control. BAT builds an accessibility tree of the live DOM with stable numeric references, unifies references across cross-origin iframes into one flat registry, and normalises the tree after every action so a no-op is detected by comparison rather than assumed successful. Failures escalate through a fixed ladder of recovery strategies — retry with a trusted CDP click, then suggest refresh or navigation, then stop and hand control back.

Cross-frame refsEscalation ladder486 tests, 0 fail

Closed-loop agent with cross-frame reference unification, fingerprint-based staleness detection, and a graceful escalation ladder. Reads the page as structured text, not pixels. 486 automated tests gate every change.

2026Agent / Systems Engineer

Most browser bots are either told what to do, click by click, or they stare at screenshots and guess. BAT does neither. It reads the page the way a screen reader would, then browses and acts on it for you.

What it is

A tiny co-pilot that lives in your browser.

Open the side panel, type a goal in plain English, and BAT takes it from there. It moves the mouse, clicks through the pages, fills in the forms, and keeps going until it is done or it hits something it cannot resolve. The interesting part is not the clicking. It is knowing, after every action, whether the page actually changed.

Your goal
BAT reads the page
BAT acts
Done
What it's actually good for

Repetitive navigation, handled reliably.

Every one of these is the same underlying problem: drive a stateful interface you don't control, and know for certain whether each action worked.

$ Compare pricing across these 5 tools and summarise it in a table
→ Opens each tab, reads the pricing page, brings back a comparison.
$ Download all my payslips from this portal
→ Navigates a repetitive multi-page admin flow so you don't have to click through 12 pages by hand.
$ Fill out this job application with my details, but let me review before submitting
→ Handles the tedious form-filling; you stay in control of what's sent.
$ Walk this admin portal and tell me which sections are incomplete
→ Traverses a deep, stateful navigation tree and reports what it found, without you clicking through every branch.
$ Summarise this long documentation page while I do something else
→ Reads and condenses without you babysitting every scroll.
$ Check if this site's checkout flow still works after my last deploy
→ A lightweight, no-setup way to click through your own site's flows.
How it sees

Text, not pixels.

Instead of taking screenshots and asking a model to squint at them, BAT hands the model a clean outline of the page — every button, link and heading, labelled and numbered.

link      "Pricing"                             #11
link      "Docs"                                #12
button    "Download report (PDF)"               #13
heading   "Your invoices — page 2 of 7"
What a screenshot gives the model: pixels
What BAT gives the model: a map

The number next to each element is a name tag. The model can say "click #13" three turns later and BAT still knows which button it means, even if the page has redrawn itself in the meantime. Cheaper than screenshots, sharper than screenshots.

How it acts

Two speeds — quick, or quiet-and-careful.

Quick
instant
Button
Careful
works even in a background tab
Button
Quick
The fast way

For simple pages, BAT just talks to the page directly — the same way JavaScript on the page would. Instant, cheap, works for the 90% case.

Careful
The trusted way

For pages that don't trust scripted clicks, BAT drives the browser at a lower level — real mouse movements, real keystrokes. Indistinguishable from a person, and it works even when the tab is in the background so you can keep using your computer.

The hard part

Pages inside pages inside pages.

A lot of dashboards, admin portals and embedded players live inside an iframe, a page nested inside another page, often served from a different origin. Browsers deliberately stop those pages reading each other. That is correct for security and a nightmare when you need to resolve a coordinate inside the embed.

L1 · Main pagey = 0 here
where does your child live inside you?+40px
L2 · Iframey = 0 here
where does your child live inside you?+120px
L3 · Nested iframey = 0 here
Running total
L1+0px
L2+40px
L3= 160px final
The problem
Everyone gets their own map

Every embedded page has its own coordinate system, starting at (0, 0) in its own corner. To click something, BAT needs coordinates for the whole window — but no page is allowed to see outside its own walls.

The fix
Ask one floor at a time

BAT asks each layer one small question — "where does your child live inside you?" — and adds up the answers. Nobody has to see through the whole building; they just point down to the next floor.

Getting unstuck

It notices when nothing happens.

After every action, BAT takes a tiny snapshot of the page. If the snapshot is identical to the one before, it means the last click did nothing — and instead of clicking harder, it tries a different tactic.

Two dud clicks — switch from a scripted click to a "real" one and try again.

Still stuck — try scrolling, refreshing, or going back a page.

Give up gracefully — tell you what it tried and hand the tab back.

When things go wrong

It bends, it doesn't break.

Every piece BAT depends on has a plan B. If the AI can't handle screenshots, BAT drops them and keeps going with text only. If streaming responses aren't allowed, it falls back to a normal request. If that's blocked too, it routes through a different part of the extension. You usually don't notice — the agent just keeps working.

If the first way fails, it quietly tries the next.
Reasoning
Extended thinking×
Dropped, retried plain
Network
Streaming×
Normal request×
Different route
A small piece of taste

The "Resume where you left off?" trap.

Stateful apps often greet you with "Resume where you left off?" the moment they open. A naive agent clicks Resume, the restore silently fails, the dialog returns, and it clicks Resume again, forever. This is the general failure mode: an action that looks successful but changes nothing. BAT counts repeats. If Resume fails twice it switches to Restart automatically, and before every click it re-reads the label under the cursor so a Restart-intended click can never land on Resume. The same counting logic drives the whole escalation ladder.

Built with

Boring, on purpose.

Chrome Extension (Manifest V3)
the platform it runs on
Side Panel UI
stays open while you browse
Chrome DevTools Protocol
lets it click and type like a real person
DeepSeek
the language model doing the thinking
Vite + @crxjs/vite-plugin
no UI framework — hand-written HTML/CSS/JS, compiled into the MV3 bundle with real hot reload

No React, no state library — hand-written HTML/CSS/JS, wired together by Vite and @crxjs/vite-plugin, which compiles the manifest into the MV3 bundle and gives content scripts real hot reload in dev. It's a one-person project, and every choice is aimed at keeping the loop between "change something" and "see it work" as short as possible.

Under the hood

The engineering detail, if you want it.

For anyone who wants the engineering detail — everyone else, feel free to skip this part.

Honest about it

What it isn't, yet.

  • Coverage stops at pure logic.
    486 assertions gate dedup, the plan/runner state machine, extractor safety, redaction, and the allowlist — but the agent loop itself (clicking, typing, the escalation ladder) is still validated by hand against representative sites.
  • Locked to one AI provider.
    The plumbing assumes DeepSeek's API shape. Swapping providers is future work, not a config toggle.
  • Screenshots are off entirely, not just fragile.
    Vision support is hard-coded off because DeepSeek's chat API doesn't accept image input at all. Canvas-rendered pages fall back to reading element geometry with JavaScript and acting on raw coordinates — not to pixels.
  • Hard stop at 200 steps.
    A genuinely long task gets cut off alongside a genuinely stuck one, because the limit counts steps rather than progress.
Where it's going

Next.

  • End-to-end tests of the agent loop itself — clicking, typing, the escalation ladder — the one thing the 486 unit tests don't cover.
  • Support for more than one AI provider.
  • Image input support, once the model API accepts it — for canvas-heavy pages tree-reading can't describe.
  • A step limit that adapts to the length of the task.
  • A friendlier first-run setup for the API key.
Next project
FarmBot Genesis — ROS 2 Digital Twin