No API, or a metered one
The portal your user needs — expenses, vendor, bank, marketplace, government — has no automation surface. The only interface is the web UI they are already signed into.
ABR is the missing layer between “I have Playwright” and “I run unattended, per-user, authenticated browser jobs in production.” Capture a session once, store it encrypted in your database, and let any cron, queue or serverless function drive the real web UI as that user — with verification, re-auth signalling, and rotated cookies handed back every time.
npm i @praveen-palanisamy/agent-browser-runtime # v0.1.1
docker pull ghcr.io/praveen-palanisamy/agent-browser-runtime:0.1.1
- uses: praveen-palanisamy/agent-browser-runtime@v0
Captured from this very page with AutoDemo (npm run site:demos) and refreshed with each release — so the demo never drifts from the product.
The portal your user needs — expenses, vendor, bank, marketplace, government — has no automation surface. The only interface is the web UI they are already signed into.
Browser profiles do not travel. Cookies rotate, MFA fires, and “just run Playwright on a server” dies the first time the user closes their lid.
Automation that cannot verify its own outcome retries blindly and double-acts. Failures, timeouts and successes get lumped together.
Three primitives — capture, post, probe — around one durable idea: the user's session is portable, encrypted by you, and refreshed on every run.
A simulated run of the runner against an expense portal that has no API. This is the exact contract you get back from /v1/post — including what happens when the session has expired.
Playwright storageState + UA + expiry hints in one AgentSessionState. Encrypt it in your DB; ABR never stores it.
Users sign in inside an embeddable, proxied live view with unguessable, expiring capture ids. MFA just works. You never see a password.
Strategies must verify. Results distinguish ok, uncertain, needsReauth and failureStage so you never double-act.
Every job hands back refreshedSession. Long-lived sessions keep working without users re-linking.
Call the runner from Cloud Functions, Lambdas, queues or cron with a tiny typed client. No Chromium in your app.
Local Chromium, any CDP endpoint, self-hosted Steel (live view), Kernel microVMs. Swap providers; strategies stay the same.
Fresh context per request, disposed afterwards. Bearer-token API. Human-pace jitter so unattended jobs do not fire at machine cadence.
npm package, multi-arch GHCR image with Chromium pinned to Playwright, and a GitHub Action. Cloud Run / ECS / compose recipes included.
Stable wire protocol, typed contracts, docs/AGENTS.md and llms.txt so coding agents can integrate ABR without guessing.
ABR gives you the execution primitive; you remain responsible for using it only with the user's explicit authorization and within each target site's terms of service.
import type { WebPostStrategy } from '@praveen-palanisamy/agent-browser-runtime';
export const expensePortal: WebPostStrategy = {
platform: 'expense-portal',
loginUrl: 'https://expenses.corp.example/login',
validate: (c) => (c.text ? null : 'Description required'),
async isAuthenticated(ctx) {
const page = await ctx.newPage();
await page.goto('https://expenses.corp.example/');
return page.getByRole('button', { name: 'New claim' }).isVisible();
},
async post(ctx, content) {
const page = await ctx.newPage();
await page.goto('https://expenses.corp.example/claims/new');
await page.getByLabel('Description').fill(content.text);
await page.getByRole('button', { name: 'Submit claim' }).click();
const claim = await page.getByTestId('claim-number').textContent().catch(() => null);
return claim
? { ok: true, platformPostId: claim, verification: 'toast' }
: { ok: false, uncertain: true, failureStage: 'verify' };
},
};
# container (Chromium included)
docker run -p 8080:8080 \
-e AGENT_RUNNER_TOKEN=$(openssl rand -base64 32) \
-v $PWD/dist:/strategies:ro \
ghcr.io/praveen-palanisamy/agent-browser-runtime:latest \
serve --strategies /strategies/strategies.js
# or in-process
import { startRunner } from '@praveen-palanisamy/agent-browser-runtime/service';
startRunner({ strategies: [expensePortal] });
import { AgentRunnerClient } from '@praveen-palanisamy/agent-browser-runtime/client';
const runner = new AgentRunnerClient({ baseUrl, token });
// once: user signs in inside the live view you embed
const { captureId, liveViewUrl } = await runner.captureStart({ platform: 'expense-portal' });
const done = await runner.captureFinish({ captureId });
if (done.ok) await vault.save(userId, encrypt(done.state));
// later, unattended
const res = await runner.post({
workspaceId, accountId: userId, platform: 'expense-portal', jobId,
session: decrypt(await vault.load(userId)),
content: { text: 'March travel', mediaUrls: receipts },
});
if (res.refreshedSession) await vault.save(userId, encrypt(res.refreshedSession));
if (res.needsReauth) notify(userId);
if (res.uncertain) queueManualReview(jobId); // never retry blindly
Prefer CI? The GitHub Action runs probe and post jobs without deploying anything.
| Raw Playwright | Hosted browser API | ABR | |
|---|---|---|---|
| Per-user authenticated sessions | DIY | Vendor profiles | Portable, encrypted by you |
| Runs from serverless / queues | No (needs Chromium) | Yes | Yes — Playwright-free client |
| Interactive sign-in with live view | DIY | Sometimes | Built in, proxied, expiring |
| Verified results / no double-act | DIY | No | Contract: ok · uncertain · needsReauth |
| Rotated cookies returned | DIY | Vendor-side | Every job |
| Where data lives | Yours | Vendor | Yours (Steel / Kernel / local — swappable) |
| License | Apache-2.0 | Proprietary | MIT |
An open-source (MIT), self-hostable runtime that lets AI agents and scheduled jobs act as a specific user inside that user's own authenticated browser session. It handles session capture, encrypted hand-off, isolated per-job browser contexts, verification contracts and re-authentication signalling on top of Playwright, Steel or Kernel.
Playwright drives a browser. ABR adds what production needs around it: portable per-user sessions, a stateless HTTP runner with a Playwright-free client, verified results that separate uncertain from failed, refreshed cookies returned after every job, interactive sign-in capture with a live view, and pluggable browser back-ends.
No. Users sign in themselves inside a live view; ABR only exports the resulting browser storage state. The runtime never persists sessions — your application encrypts and stores them and passes them in per job. Treat them with password-equivalent sensitivity.
Local Chromium via Playwright, any Chrome DevTools Protocol endpoint, self-hosted or cloud Steel Browser (with live view), and Kernel microVMs. Providers are swappable without changing your strategies.
npm i @praveen-palanisamy/agent-browser-runtime for the library and client; docker pull ghcr.io/praveen-palanisamy/agent-browser-runtime for the runner with Chromium; or uses: praveen-palanisamy/agent-browser-runtime@v0 in GitHub Actions.
That depends on the site's terms and on having the user's explicit authorization. ABR is a general execution primitive — the same one a user's own browser extension or RPA bot would use — and it exposes official-API fallbacks in its contracts so you can prefer them where they exist.
Praveen Palanisamy. Source, issues and releases live at github.com/praveen-palanisamy/agent-browser-runtime.