DEV Community

Cover image for My browser agent wasn’t slow because of GPT-5 — it was slow because I kept shoving garbage into the prompt
Lars Winstand
Lars Winstand

Posted on • Originally published at standardcompute.com

My browser agent wasn’t slow because of GPT-5 — it was slow because I kept shoving garbage into the prompt

I kept blaming the wrong thing.

When a browser agent felt slow, I blamed GPT-5. Then Claude Opus 4.6. Then Playwright. Then API rate limits. Then the browser itself.

After looking at real traces, the answer was much less flattering:

I was sending way too much junk to the model on every step.

Full page dumps. Screenshots. Repeated instructions. Full action history. Old reasoning. More screenshots. Same task text again.

If that sounds familiar, your bottleneck might not be model quality. It might be prompt bloat.

I ran into a good discussion about this in r/openclaw: a thread on more token-efficient, faster browser use. It lined up with what I was seeing in practice: a lot of "browser agent performance problems" are really context management problems.

The bad loop most of us build first

The naive browser-agent loop usually looks like this:

  1. Open page
  2. Capture screenshot
  3. Extract page text
  4. Include full task instructions
  5. Include full prior history
  6. Ask model what to do next
  7. Repeat 20 times

That works for demos.

It gets ugly in production.

By step 10 or step 20, your model is no longer deciding over a clean state. It is digging through a landfill.

That hurts three things at once:

  • latency
  • reliability
  • cost

And then people start doing rate limit debugging or llm timeout troubleshooting when the actual issue is that every prompt is bigger than the last.

What the trace usually shows

When I inspect slow agent runs, I usually find some combination of these:

  • the same system instructions repeated every turn
  • the same workflow instructions repeated every turn
  • full screenshots attached even when the page is mostly text
  • entire page text dumped instead of only relevant fragments
  • full action history preserved instead of a short summary
  • old reasoning traces dragged forward forever

That is not "more context."

That is self-inflicted latency.

Prompt caching changes the design rules

This part gets ignored way too often.

If you are using OpenAI-compatible APIs, prompt caching should affect how you structure the loop.

Two details matter a lot:

  • caching is automatically enabled at 1024+ tokens
  • cache routing typically depends on a stable prompt prefix

So if the top of your prompt keeps changing, you make cache hits less likely.

If the first part of the prompt is stable, you have a much better shot at getting the cheap and fast path.

That matters because cached input can be dramatically cheaper than uncached input.

So before you switch from GPT-5 to Claude Opus 4.6, or from Claude to some open model, ask the more embarrassing question:

Did I design the prompt so caching can work at all?

The mistake: putting dynamic junk at the top

A lot of agent loops do this:

[latest screenshot blob]
[current page dump]
[latest DOM serialization]
[task instructions rewritten slightly]
[history]
[system prompt]
Enter fullscreen mode Exit fullscreen mode

That is backwards.

If you want cache-friendly prompts, the top should be boring and stable.

More like this:

[stable system prompt]
[stable task framing]
[stable tool instructions]
[compact state summary]
[last 1-3 actions]
[current step-specific delta]
Enter fullscreen mode Exit fullscreen mode

Same model. Same browser. Very different performance.

Browser Use basically tells you this already

If you read Browser Use config with a skeptical eye, the knobs are pretty revealing.

Option Why it matters
use_vision=False Don’t send screenshots unless you actually need visual context
use_vision='auto' Keep vision available, but don’t force image input every turn
page_extraction_llm Use a smaller model for extraction instead of wasting the main model
max_history_items Stop dragging the full past into every step
flash_mode=True Reduce extra thinking/evaluation overhead when speed matters

That is not a list of model upgrades.

It is a list of ways to stop wasting context.

A practical Browser Use setup

Here is the shape I would start with:

from browser_use import Agent, ChatBrowserUse

agent = Agent(
    task="Log into Stripe, export last month's payouts CSV, upload it to Google Drive",
    llm=ChatBrowserUse(model="openai/gpt-5.5"),
    use_vision="auto",
    max_history_items=3,
    flash_mode=True,
    # page_extraction_llm=ChatBrowserUse(model="openai/gpt-5.5-mini")
)

history = await agent.run(max_steps=25)
Enter fullscreen mode Exit fullscreen mode

The key idea is simple:

  • let the main model make decisions
  • let a cheaper/smaller model do extraction work
  • keep history short
  • only use screenshots when needed

If GPT-5.5 is spending half its time rereading screenshots and giant page dumps, that is not a model problem.

What should actually go into the loop

If you are building your own harness with Playwright or Selenium, be ruthless.

Good stuff to send every step

  • a stable system prompt
  • a stable task definition
  • current URL
  • a compact structured state
  • visible elements relevant to the current goal
  • last 1-3 actions
  • a short summary of what changed

Bad stuff to send every step

  • full screenshots by default
  • entire DOM dumps
  • full page text when only one section matters
  • repeated instructions that never change
  • chain-of-thought or internal reasoning from prior turns
  • giant history objects "just in case"

That last one is a killer.

Most agents do not need long raw history. They need compressed memory.

Compression beats horsepower

This is the part I think more agent builders need to internalize.

The best browser agents are not just better at browsing.

They are better at compression.

They turn this:

Go to Stripe.
Click payouts.
Set date filter to last month.
Export CSV.
Open Google Drive.
Upload the file.
Post a summary in Discord.
Enter fullscreen mode Exit fullscreen mode

into this:

Run skill: monthly_finance_export
Enter fullscreen mode Exit fullscreen mode

That is a huge difference.

The Reddit threads around OpenClaw made this point really well. A few people described workflow persistence and reusable skills as the thing that made agents feel usable instead of fragile.

That matches my experience.

If the agent can persist a workflow or call a named skill, you stop paying the token tax for re-explaining the same routine every run.

Split extraction from reasoning

This is one of the easiest wins.

A frontier model should not be doing cheap extraction work if a smaller model can do it.

For example:

  • use a smaller model to extract visible text, labels, and form state
  • use GPT-5 or Claude Opus 4.6 for higher-level decisions

That gives you lower latency and lower cost without making the agent dumb.

In pseudocode:

state = small_extraction_model.extract(page)
decision = frontier_model.decide(task, summarized_history, state)
execute(decision)
Enter fullscreen mode Exit fullscreen mode

That pattern is a lot healthier than forcing one expensive model to do everything.

A Playwright example: send deltas, not novels

If you are building your own loop, here is the direction I would go.

from playwright.async_api import async_playwright

async def get_compact_state(page):
    return await page.evaluate("""
    () => {
      const buttons = [...document.querySelectorAll('button')]
        .slice(0, 20)
        .map(b => b.innerText.trim())
        .filter(Boolean)

      const links = [...document.querySelectorAll('a')]
        .slice(0, 20)
        .map(a => a.innerText.trim())
        .filter(Boolean)

      return {
        url: location.href,
        title: document.title,
        buttons,
        links,
        bodyText: document.body.innerText.slice(0, 2000)
      }
    }
    """)
Enter fullscreen mode Exit fullscreen mode

Then send something like this to the model:

{
  "task": "Export last month's payouts CSV",
  "url": "https://dashboard.stripe.com/payouts",
  "title": "Payouts | Stripe Dashboard",
  "buttons": ["Export", "Filter", "Download"],
  "last_actions": [
    "opened payouts page",
    "set date filter to last month"
  ],
  "change_summary": "Export button is now visible"
}
Enter fullscreen mode Exit fullscreen mode

That is usually enough.

You do not need to dump the entire world into the prompt.

Install steps if you want to test this quickly

Playwright

pip install playwright
npx playwright install
Enter fullscreen mode Exit fullscreen mode

Browser Use

pip install browser-use
Enter fullscreen mode Exit fullscreen mode

Or with uv:

uv add browser-use
Enter fullscreen mode Exit fullscreen mode

When screenshots actually do matter

I am not arguing for never using vision.

There are real cases where screenshots help a lot:

  • layout-sensitive UIs
  • canvas-heavy apps
  • visual QA
  • ambiguous states where text extraction misses the point
  • anti-bot or weird modal behavior

But that is an argument for conditional richness, not always-on screenshots.

use_vision='auto' is often the more mature default.

The 4 fixes I’d try before switching models

If a browser agent feels slow, I would try these first.

1. Freeze the prompt prefix

Keep the top of the prompt stable.

Do not keep rewriting instructions or changing serialization order.

2. Turn screenshots off by default

Start with no screenshots, then add them back only where they improve success.

3. Split extraction from reasoning

Use a cheaper model for page extraction and a stronger model for decisions.

4. Trim memory aggressively

Keep only recent actions plus a compact summary.

Do not keep replaying the whole transcript.

Where Standard Compute fits

This is also why I think pricing model matters for agents.

When you are iterating on browser loops, per-token billing punishes experimentation.

You want to test:

  • different context windows
  • different extraction models
  • more steps
  • retries
  • alternate routing strategies

That gets expensive fast if every bad prompt design decision is billed one token at a time.

Standard Compute is interesting here because it gives you an OpenAI-compatible API with flat monthly pricing instead of per-token billing. So if you are running agents in n8n, Make, Zapier, OpenClaw, or custom Playwright workflows, you can actually tune the loop without obsessing over token burn on every run.

That does not remove the need for good prompt design.

It just removes the weird tax on fixing it.

The uncomfortable truth

A lot of browser agents feel magical in demos and terrible in production for the same reason:

the prompt gets fatter every step.

Then someone says the model is slow.

Sometimes the model is slow.

A lot of the time, though, the model is just doing exactly what you asked: rereading duplicate instructions, parsing screenshots it did not need, and carrying around history that should have been compressed five steps ago.

That is not really an intelligence problem.

It is a packing problem.

If you are debugging a sluggish agent this week, inspect the payload at step 1, step 5, and step 20.

If the prompt keeps growing, start there.

Before blaming GPT-5.

Before blaming Claude.

Before blaming rate limits.

And definitely before adding even more garbage to the prompt.

Top comments (0)