Web Agent Memory: Remembering Login States, Preferences, and Past Navigation Paths
Learn how to build persistent memory for web agents covering cookie and session management, navigation history tracking, preference learning, and context reuse across browsing sessions.
Why Web Agents Need Memory
A web agent without memory is like a user who clears their browser data after every single page load. Every time the agent starts a new task, it has to log in again, navigate from scratch, rediscover UI patterns, and repeat mistakes it already learned to avoid. This wastes time, burns API credits on redundant LLM calls, and makes the agent significantly less effective.
Production web agents need three types of memory: session memory (cookies, auth tokens, and login states), procedural memory (navigation paths and UI interaction patterns), and preference memory (learned site-specific configurations and user settings).
Session Memory: Cookies and Auth State
The most fundamental form of web agent memory is browser session state. Playwright makes this straightforward with its storage state API, which serializes all cookies, local storage, and session storage to a JSON file.
import json
from pathlib import Path
from playwright.async_api import async_playwright, BrowserContext
class SessionManager:
"""Persist and restore browser sessions across agent runs."""
def __init__(self, storage_dir: str = "./agent_sessions"):
self.storage_dir = Path(storage_dir)
self.storage_dir.mkdir(parents=True, exist_ok=True)
def _session_path(self, site_id: str) -> Path:
return self.storage_dir / f"{site_id}_session.json"
async def save_session(self, context: BrowserContext,
site_id: str):
"""Save browser session state to disk."""
state = await context.storage_state()
path = self._session_path(site_id)
path.write_text(json.dumps(state, indent=2))
async def load_session(self, browser, site_id: str):
"""Create a new context with saved session state."""
path = self._session_path(site_id)
if path.exists():
state = json.loads(path.read_text())
return await browser.new_context(storage_state=state)
return await browser.new_context()
async def is_logged_in(self, page, check_selector: str) -> bool:
"""Verify if the saved session is still valid."""
try:
await page.wait_for_selector(
check_selector, timeout=5000
)
return True
except Exception:
return False
Using Session Memory in an Agent Loop
With session management in place, the agent can skip login flows when it already has valid credentials.
class WebAgentWithMemory:
def __init__(self, session_mgr: SessionManager):
self.session_mgr = session_mgr
self.nav_memory = NavigationMemory()
self.pref_memory = PreferenceMemory()
async def run_task(self, site_id: str, site_url: str,
task: str, login_config: dict):
"""Execute a task with session persistence."""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
# Try to restore previous session
context = await self.session_mgr.load_session(
browser, site_id
)
page = await context.new_page()
await page.goto(site_url)
# Check if session is still valid
logged_in = await self.session_mgr.is_logged_in(
page, login_config["logged_in_selector"]
)
if not logged_in:
await self._perform_login(page, login_config)
await self.session_mgr.save_session(
context, site_id
)
# Execute the actual task
result = await self._execute_task(page, task)
# Save session after task completion
await self.session_mgr.save_session(context, site_id)
await browser.close()
return result
async def _perform_login(self, page, config: dict):
await page.fill(config["username_selector"],
config["username"])
await page.fill(config["password_selector"],
config["password"])
await page.click(config["submit_selector"])
await page.wait_for_load_state("networkidle")
Navigation Memory: Remembering How to Get Places
Navigation memory records the sequences of actions the agent took to reach specific pages or UI states. When the agent needs to reach the same destination again, it replays the known path instead of re-exploring.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class NavigationStep:
action: str # click, fill, navigate, etc.
selector: Optional[str] = None
value: Optional[str] = None
url_before: str = ""
url_after: str = ""
timestamp: Optional[datetime] = None
@dataclass
class NavigationPath:
destination: str # semantic description
steps: list[NavigationStep] = field(default_factory=list)
success_count: int = 0
fail_count: int = 0
last_used: Optional[datetime] = None
class NavigationMemory:
"""Store and retrieve navigation paths."""
def __init__(self, storage_path: str = "./nav_memory.json"):
self.storage_path = Path(storage_path)
self.paths: dict[str, NavigationPath] = {}
self._load()
def _load(self):
if self.storage_path.exists():
data = json.loads(self.storage_path.read_text())
for key, path_data in data.items():
steps = [
NavigationStep(**s) for s in path_data["steps"]
]
self.paths[key] = NavigationPath(
destination=path_data["destination"],
steps=steps,
success_count=path_data.get("success_count", 0),
fail_count=path_data.get("fail_count", 0),
)
def _save(self):
data = {}
for key, path in self.paths.items():
data[key] = {
"destination": path.destination,
"steps": [
{
"action": s.action,
"selector": s.selector,
"value": s.value,
"url_before": s.url_before,
"url_after": s.url_after,
}
for s in path.steps
],
"success_count": path.success_count,
"fail_count": path.fail_count,
}
self.storage_path.write_text(json.dumps(data, indent=2))
def record_path(self, destination: str,
steps: list[NavigationStep]):
"""Record a successful navigation path."""
key = destination.lower().strip()
if key in self.paths:
self.paths[key].steps = steps
self.paths[key].success_count += 1
else:
self.paths[key] = NavigationPath(
destination=destination,
steps=steps,
success_count=1,
)
self.paths[key].last_used = datetime.utcnow()
self._save()
def get_path(self, destination: str) -> Optional[NavigationPath]:
"""Retrieve a known navigation path."""
key = destination.lower().strip()
path = self.paths.get(key)
if path and path.success_count > path.fail_count:
return path
return None
Preference Memory: Learning Site-Specific Patterns
Preference memory captures site-specific knowledge that the agent learns through interaction — for example, that a particular site always shows a cookie consent banner, or that the search box requires pressing Enter rather than clicking a search button.
class PreferenceMemory:
"""Learn and store site-specific interaction preferences."""
def __init__(self, storage_path: str = "./pref_memory.json"):
self.storage_path = Path(storage_path)
self.preferences: dict[str, dict] = {}
self._load()
def _load(self):
if self.storage_path.exists():
self.preferences = json.loads(
self.storage_path.read_text()
)
def _save(self):
self.storage_path.write_text(
json.dumps(self.preferences, indent=2)
)
def set_preference(self, domain: str, key: str, value):
"""Store a learned preference for a domain."""
if domain not in self.preferences:
self.preferences[domain] = {}
self.preferences[domain][key] = {
"value": value,
"learned_at": datetime.utcnow().isoformat(),
}
self._save()
def get_preference(self, domain: str, key: str,
default=None):
"""Retrieve a preference for a domain."""
prefs = self.preferences.get(domain, {})
entry = prefs.get(key)
return entry["value"] if entry else default
def get_context_prompt(self, domain: str) -> str:
"""Build an LLM prompt snippet from known preferences."""
prefs = self.preferences.get(domain, {})
if not prefs:
return ""
lines = [f"Known facts about {domain}:"]
for key, entry in prefs.items():
lines.append(f"- {key}: {entry['value']}")
return "\n".join(lines)
FAQ
How long should I keep session cookies before forcing a re-login?
It depends on the target site's session expiration policy. A safe default is to save sessions for 24 hours and then force a fresh login. Always verify the session is still valid before starting a task by checking for a logged-in indicator element. If the session check fails, delete the stored state and re-authenticate.
Should I store navigation memory per-user or per-site?
Store navigation memory per-site with per-user overrides. The general navigation structure of a website is the same for all users, but some paths may differ based on user roles or permissions. Use the site domain as the primary key and append user-specific paths when you detect differences.
How do I prevent memory from becoming stale after a site redesign?
Track success and failure counts for each navigation path. When a stored path fails more than two consecutive times, mark it as stale and force the agent to re-explore. Also store timestamps on all memory entries and periodically expire entries older than a configurable threshold (for example, 30 days).
#WebAgentMemory #SessionManagement #BrowserAutomation #AgenticAI #NavigationMemory #CookieManagement #StateManagement #PythonAutomation
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.