Screen-understanding got good enough in 2025 that browser agents stopped struggling to read the web. Now watch one run twice: it re-discovers the same login form, the same cookie banner, the same three-click path to the export button, from scratch, every single time. Browserbase called this out this spring with Autobrowse, built to graduate a working run into a "durable, reusable skill." BrowserAct rode the same complaint to the top of Product Hunt in June. The industry has converged on a name for it: agent amnesia.
The fix doesn't require a new framework. It requires two things most teams already have half of: a browser that remembers who it is, and a cache that remembers what it did.
Part 1: An identity that survives between runs
Anchor sessions can persist a full browser profile—cookies, local storage, auth tokens—under a name you choose. Create it once, reuse it forever:
from anchor_browser import AnchorClient
anchor = AnchorClient(api_key=os.environ["ANCHOR_API_KEY"])
session = anchor.sessions.create(
browser={
"profile": {
"name": "acme-dashboard-agent",
"persist": True,
}
},
proxy_country="us",
)
Every future session that requests acme-dashboard-agent starts already logged in. That solves who the agent is. It does nothing for what the agent knows how to do—which is the half of amnesia that costs the most tokens.
Part 2: A skill cache your agent checks before it thinks
The idea is simple: key a small JSON store by domain plus task name, and let the agent write to it the first time it solves something and read from it every time after.
import json, pathlib
CACHE_PATH = pathlib.Path("skills.json")
def load_skill(domain: str, task: str) -> list[dict] | None:
if not CACHE_PATH.exists():
return None
store = json.loads(CACHE_PATH.read_text())
return store.get(domain, {}).get(task)
def save_skill(domain: str, task: str, steps: list[dict]) -> None:
store = json.loads(CACHE_PATH.read_text()) if CACHE_PATH.exists() else {}
store.setdefault(domain, {})[task] = steps
CACHE_PATH.write_text(json.dumps(store, indent=2))
Wire it into the agent loop: check the cache before spending a single LLM call on exploration, and only fall back to reasoning when nothing's there.
async def run_task(page, domain: str, task: str, explore_fn):
steps = load_skill(domain, task)
if steps:
for step in steps:
await page.locator(step["selector"]).click()
return "replayed cached skill"
steps = await explore_fn(page) # LLM figures it out the hard way, once
save_skill(domain, task, steps)
return "learned and cached new skill"
explore_fn is whatever agent logic you're already running—Playwright actions driven by your model of choice—instrumented to record the selectors it clicked along the way. The first run on a new domain pays full price. Every run after replays a handful of deterministic clicks instead of a full reasoning pass, which is both faster and dramatically cheaper.
Why this works well with Anchor specifically
Cached steps are only safe to replay against a DOM that looks the way it did when you recorded them. Anchor's session isolation and stealth mode mean the page an agent sees isn't quietly different run to run because of fingerprint-based A/B tests or bot-detection challenges swapping in a different layout. Pair that with a profile that keeps the agent authenticated, and a cached skill built on Monday is still valid on Friday.
Production tips
Invalidate on failure, not on a timer. If a cached selector doesn't resolve, delete that entry and fall through to explore_fn instead of hard-failing the run.
Version by a DOM fingerprint, not just domain. Hash a stable structural signal (nav item count, form field names) alongside the cache key so a redesign invalidates itself automatically.
Keep the cache out of the hot path's blast radius. Store it in Redis or S3, not local disk, once you're running more than one worker.
Get started
Anchor profile documentation · Anchor documentation · Start your free Anchor trial →



