Recipes
Copy-paste patterns for the tasks people actually run.
Overview#
Copy-paste patterns for the tasks people actually run. Replace the domains, goals and field names with your own; nothing here is specific to any one customer.
1. Download an invoice, attach it to a claim, stop before submitting#
One run handles the whole flow: the runtime downloads the file, registers it as an artifact, opens the portal, signs in, types the literal title, uploads the artifact and stops at the boundary instead of submitting.
mlola browser run --goal 'Download my latest invoice, attach it to a new reimbursement claim titled "August travel", then stop before submitting' --url https://billing.example.com/invoices --stop-before "submit claim" --yes
2. Sites that need login, MFA or a CAPTCHA#
Nothing special to configure: the runtime never types a credential. It hands the step to the person, waits, then re-observes and continues on its own.
mlola browser run --goal "Sign in and open my claims list" --url https://portal.example.com
# what you will see:
# human needed (credential)
# Take over the browser, finish this step, then hand control back.
# (press Enter when done — the runtime re-observes and resumes)
3. Multi-tab flows#
OPEN_TAB is offered only for links that really open a new tab, and SWITCH_TAB appears once more than one tab is open. Tabs the runtime opens are closed again at the end; user tabs are never touched.
mlola browser run --goal "Download my latest invoice, then open the reimbursement portal in a new tab, sign in and attach it" --url https://billing.example.com/invoices --stop-before "submit"
4. Literal text vs generated text#
Quoted values in the goal are reused verbatim, with no model involved. A text provider is only consulted when a value genuinely has to be written from scratch.
# literal: the quoted value is typed as-is (no model call)
mlola browser run --goal 'Enter "August travel" as the claim title' --url https://portal.example.com/claim
# generation: configure a text provider, otherwise the runtime asks a person
export MLOLA_TEXT_BASE_URL="https://api.openai.com/v1"
export MLOLA_TEXT_API_KEY="sk-..."
export MLOLA_TEXT_MODEL="gpt-4o-mini"
mlola browser run --goal "Write a short description of this month's travel expenses in the notes field" --url https://portal.example.com/claim
5. Scheduled runs (daemon or cron)#
The daemon owns runs that outlive the command, so a scheduler only has to fire an HTTP request. A plain cron line works too if you prefer everything in-process.
mlola browser daemon start
TOKEN=$(cat ~/.mlola/browser-runtime/daemon/token)
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"goal":"Download my monthly invoice","startUrl":"https://billing.example.com/invoices","providers":{"decision":"typesafe"}}' localhost:7717/v1/runs
# crontab -e — every 1st at 08:00
0 8 1 * * cd /srv/mlola && TYPESAFE_API_KEY=tsk_... /usr/bin/node ./node_modules/.bin/mlola browser run --no-human --yes --goal "Download my monthly invoice" --url https://billing.example.com/invoices >> /var/log/mlola-invoice.log 2>&1
6. Run it from GitHub Actions#
Headless, unattended, with the key from a secret. The run summary is your evidence; the downloaded artifacts are uploaded as a build artifact.
name: invoice
on:
schedule: [{ cron: "0 8 1 * *" }]
workflow_dispatch:
jobs:
download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 24 }
- run: npx playwright install --with-deps chromium
- name: Run the task
env:
TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
run: |
npx @mlola/browser-cli browser run \
--task task.json \
--browser chromium --headless --no-human --yes --json | tee result.json
- uses: actions/upload-artifact@v4
with:
name: invoice
path: ~/.mlola/browser-runtime/runs/*/artifacts/**
7. Safety-bounded automation#
A policy file turns "please be careful" into something the runtime enforces: two allowed origins, uploads allowed, everything that sends or pays stopped or handed over.
{
"defaults": { "upload": "allow", "send_message": "deny", "purchase": "takeover" },
"restrictNavigation": true,
"allowedOrigins": ["https://billing.example.com", "https://portal.example.com"],
"secretsRequireTakeover": true
}
mlola browser run --policy policy.json --task task.json --stop-before "submit,pay"
8. Drive it from the SDK#
Same runtime, your own process: subscribe to the events you care about and let the completion oracle decide the outcome.
import { BrowserAgent } from "@mlola/browser-runtime";
const agent = new BrowserAgent({ decision: "typesafe" });
agent.on("artifact-created", (event) => console.log("artifact:", event.payload.artifact.name));
agent.on("takeover-required", (event) => console.log("needs a person:", event.payload.prompt));
agent.on("fallback", (event) => console.log("escalated to", event.payload.to));
const run = await agent.run({
goal: "Download my latest invoice",
startUrl: "https://billing.example.com/invoices",
successCriteria: { criteria: { kind: "artifact", documentType: "invoice" } },
});
console.log(run.status, run.verification?.status, run.artifacts);