All posts

Part 1 · Getting started

Your first autonomous task, start to finish

Slapify is a browser agent you give a goal to, not a script. Here is a real run from install to answer, replayed step by step, with what it cost.

Most browser automation asks you to say how: which selector, which click, in which order. Slapify asks you to say what. It reads the page the way a screen reader does, decides what to do next, does it, and keeps going until the goal is met. Along the way it can log in, fill forms, pick from dropdowns, tick checkboxes, upload and download files, switch tabs, remember things between runs, and schedule itself to come back later.

Install and set up

npm install -g slapify
slapify init

init asks which model to use and which browser, then writes a .slapify/ folder. The only thing it needs from you is an API key:

# .slapify/config.yaml
llm:
  provider: anthropic
  model: claude-sonnet-5
  api_key: ${ANTHROPIC_API_KEY}
browser:
  headless: true

Any provider the Vercel AI SDK supports works (OpenAI, Google, Mistral, Groq, a local Ollama model), and you can list several: if one is rate-limited or down, the next one takes over.

Give it a goal

To have something we could show end to end, without touching a real shop, we built a tiny demo store called Harbor & Pine. It has six products, a search box, product pages with a size dropdown and a gift-wrap checkbox, and a cart. Then we gave the agent one sentence:

slapify task "Go to the store. Find a waterproof jacket that costs less than $150, \
add it to the cart in size M with gift wrap, and tell me the cart total."

Nothing else: no selectors, no hints about which page has what. Here is exactly what it did. Each frame is a screenshot of the page just before the step runs; the orange box marks the element the agent chose.

A few things worth noticing:

  • It checked, and changed its mind. The first jacket it opened was the Summit Shell. It read the page, saw $189, went back and opened the Drizzle Rain Jacket at $129 instead. Nobody told it the first one was too expensive; the goal did.
  • It used the right control for the job. The size went through select_option and gift wrap through set_checked, which sets a state rather than toggling it, so running it twice can't untick the box.
  • It read the result instead of assuming. After adding to cart it opened the cart and read the total from the page: "Cart total: $134", which is $129 plus $5 gift wrap.
79 swall clock, goal to answer
14tool calls
15model turns
≈ $0.05at Sonnet 5 API prices, with prompt caching

How the cost is worked out: we replay the run's log to rebuild what the model saw on every turn (system prompt, tools, goal and history, trimmed the way the harness trims it), count about four characters per token, and price it at list API rates: $2 per million input tokens and $10 per million output for Claude Sonnet 5. Without prompt caching the same run is about $0.17. About 85% of each prompt is identical to the previous one, which is what caching makes cheap.

How it sees a page

The agent doesn't look at pixels by default. It asks for the page's accessibility tree: the same list of buttons, links, inputs and headings a screen reader announces, each with a short ref.

- link "Harbor & Pine" [ref=e1]
- textbox "Search products" [ref=e2]
- button "Search" [ref=e3]
- combobox [ref=e4]: Choose a size
- checkbox "Gift wrap (+$5)" [ref=e5]
- button "Add to cart" [ref=e8]

Refs are what it acts on: select_option(e4, "M"), set_checked(e5, true), click(e8). For reading, it calls get_page_text(), because the tree lists controls but not body copy, and get_links() when it needs a real URL rather than a guess. When text isn't enough (a chart, a CAPTCHA, an image-only post) turn on vision: true and it can look at a screenshot too.

What it can do

AreaTools
Navigate and readnavigate, get_page_state, get_page_text, get_links, scroll, go_back, screenshot
Act like a personclick, double_click, hover, drag, type, press, select_option, set_checked
Files and tabsupload_file (only from an uploads folder), download_file, list_tabs, switch_tab, close_tab
AccountsSaved credential profiles, login-form filling, persistent browser profiles, CAPTCHA solving
Time and memoryremember/recall across runs, schedule itself on a cron, sleep_until
Peopleask_user for a 2FA code, status_update, and a Telegram bridge to steer it from your phone
OutputStructured JSON against a schema, an HTML report of the whole session

From code

The same agent is a library. createAgent() reads .slapify/config.yaml if there is one, and anything you pass inline is laid over it:

import { createAgent } from "slapify";

const agent = createAgent({ stealth: true });

// run to completion
const run = await agent.run("Find a waterproof jacket under $150 on the store and add it in size M");
console.log(run.finalSummary);

// or watch it work
for await (const event of agent.stream("Check the order status for #4821")) {
  if (event.type === "tool_start") console.log(event.toolName, event.args);
}

// and come back to it later, memory included
await agent.resume(run.id, "Now remove gift wrap and tell me the new total");

Want data rather than prose? Pass a JSON schema and it writes conforming output:

const run = await agent.run("List every jacket in the store with its price", {
  schema: { type: "array", items: { type: "object", properties: { name: { type: "string" }, price: { type: "number" } } } },
  outputFile: "jackets.json",
});

Where to go next

A one-minute task is the easy case. The rest of this series is about the hard one: an agent that runs for days on a real website, unattended. Part 2 is the story of building exactly that, and everything that broke along the way.