Enable web agents to improve over time by curating episodic memory from navigation trajectories and dynamically injecting task-specific guidance at runtime—no retraining required, persistent improvement across sessions.
Enable web agents to improve over time by curating episodic memory from navigation trajectories and dynamically injecting task-specific guidance at runtime—no retraining required, persistent improvement across sessions.
Enable Web Agents to Self-Improve via Cross-Session Episodic Memory
Web agents (browser automation, form filling, multi-step navigation) often fail on edge cases or novel sites. Retraining on every failure is expensive. WebCoach enables self-evolution without retraining: the agent maintains an External Memory Store (EMS) of past navigation episodes, continuously refining its decision-making by retrieving and injecting relevant past experiences at runtime.
A Coach component evaluates the agent's current trajectory and injects guidance when it detects failure risk or identifies superior strategies in memory. This achieves persistent agent improvement across sessions without model updates—learning happens through retrieval and composition, not gradient descent.
Core Concept
Web agents typically operate in isolation: each session is independent, successful and failed trajectories are discarded. WebCoach breaks this pattern by maintaining a shared episodic memory across sessions. When an agent encounters a new task, the Coach retrieves similar past episodes and injects relevant guidance into the agent's context.
The system comprises three components working in tandem:
WebCondenser: Summarizes raw navigation logs into structured episodes with embeddings, success labels, and error patterns
External Memory Store (EMS): Persists completed episodes with semantic search capability
Coach: LLM-based runtime decision engine that retrieves relevant episodes and injects guidance when needed
Together, they enable agents to learn from cross-session experience without modifying the underlying policy.
Architecture Overview
Navigation Trajectory Logging: Capture observation-action-reward sequences; store only completed episodes (success or failure endpoints)
WebCondenser: Convert raw logs to structured episodes with (summary, embedding, success_label, error_patterns)
External Memory Store (EMS): Semantic index enabling fast retrieval of similar past episodes; stores up to millions of episodes
Coach Decision Engine: Evaluate current trajectory; retrieve top-k similar episodes; decide if intervention needed; inject guidance as system message
No-Retraining Integration: Coach advice appends to message history; agent processes without policy modification
Implementation Steps
Step 1: WebCondenser—Normalize Navigation Logs. Convert raw traces to structured episodes.
"""
Convert observation-action-reward trajectory to structured episode.
trajectory: list of (observation, action, reward) tuples
"""
# Format trajectory as narrative
self
# Summarize with LLM
self
150
"Concisely summarize this web navigation trajectory in 3-5 sentences, noting task, key actions, and outcome."
# Extract success/failure from final reward
1
2
0
# Extract error patterns
self
# Compute embedding
self
# (embedding_dim,)
return
'summary'
'embedding'
'success'
'error_patterns'
'trajectory_hash'
hash
str
# Deduplication
def
_format_trajectory_text
self, trajectory
"""Convert trajectory to human-readable text."""
for
in
f"Observation: {obs}"
f"Action: {action}"
f"Reward: {reward}"
return
"\n"
def
_extract_error_patterns
self, trajectory
"""Identify recurring error patterns in trajectory."""
for
in
if
0
# Error detected
# Extract action type and context
0
if
else
"unknown"
'action_type'
'observation'
'error_context'
# Deduplicate and compress
return
5
# Keep top-5 error patterns
Step 2: External Memory Store (EMS). Persist and index episodes for retrieval.
classExternalMemoryStore:
def__init__(self, embedding_dim=1536, max_episodes=1000000):
self.episodes = [] # List of structured episodesself.embeddings = np.zeros((0, embedding_dim))
self.embedding_dim = embedding_dim
self.max_episodes = max_episodes
self.index = None# FAISS index for fast retrievaldefadd_episode(self, episode):
"""
Persist episode to EMS.
episode: dict from WebCondenser.condense_trajectory
"""# Deduplication: skip if trajectory_hash already existsifany(e['trajectory_hash'] == episode['trajectory_hash'] for e inself.episodes):
returnself.episodes.append(episode)
self.embeddings = np.vstack([
self.embeddings,
episode['embedding'].reshape(1, -1)
])
# Rebuild FAISS index periodicallyiflen(self.episodes) % 1000 == 0:
self._rebuild_index()
# Evict oldest if at capacityiflen(self.episodes) > self.max_episodes:
self._evict_oldest()
defretrieve_similar(self, query_embedding, k=5):
"""
Retrieve k most similar past episodes.
query_embedding: (embedding_dim,) array
"""ifself.index isNoneorlen(self.episodes) == 0:
return []
# FAISS search
distances, indices = self.index.search(
query_embedding.reshape(1, -1).astype(np.float32),
min(k, len(self.episodes))
)
# Return episodes sorted by similarity
retrieved = [self.episodes[i] for i in indices[0]]
return retrieved
def_rebuild_index(self):
"""Build FAISS index for fast similarity search."""import faiss
self.index = faiss.IndexFlatL2(self.embedding_dim)
self.index.add(self.embeddings.astype(np.float32))
def_evict_oldest(self):
"""Remove oldest episodes when at capacity."""# Remove oldest 10% by creation timestamp
num_remove = int(0.1 * len(self.episodes))
self.episodes = self.episodes[num_remove:]
self.embeddings = self.embeddings[num_remove:]
self._rebuild_index()
Step 3: Coach Decision Engine. Evaluate trajectory and decide when to inject guidance.
classCoach:
def__init__(self, coach_llm, ems):
self.coach_llm = coach_llm # Small LLM (≤8B)self.ems = ems
defevaluate_trajectory(self, current_trajectory, current_embedding):
"""
Evaluate current trajectory for intervention need.
current_trajectory: list of (obs, action, reward)
current_embedding: embedding of current partial trajectory
"""# Retrieve similar past episodes
similar_episodes = self.ems.retrieve_similar(current_embedding, k=5)
ifnot similar_episodes:
returnNone# No guidance available# Assess failure risk
failure_risk = self._assess_failure_risk(current_trajectory)
# Identify better strategies
better_strategies = [
ep for ep in similar_episodes if ep['success'] and ep['reward'] > self._get_current_reward(current_trajectory)
]
# Decide intervention
intervene = (failure_risk > 0.6) or (len(better_strategies) > 0and failure_risk > 0.3)
if intervene:
guidance = self._generate_guidance(current_trajectory, similar_episodes, better_strategies)
return guidance
returnNonedef_assess_failure_risk(self, trajectory):
"""Estimate probability of trajectory failure."""ifnot trajectory:
return0.0# Heuristic: failure risk increases with repeated errors
recent_rewards = [r for _, _, r in trajectory[-5:]]
num_negative = sum(1for r in recent_rewards if r < 0)
failure_risk = num_negative / len(recent_rewards) if recent_rewards else0.0return failure_risk
def_generate_guidance(self, current_trajectory, similar_episodes, better_strategies):
"""
Generate actionable guidance from similar episodes.
Output as JSON for injection into agent's message history.
"""
guidance_prompt = f"""
Current trajectory (partial): {current_trajectory[-3:]}
Similar successful episodes from past:
{[ep['summary'] for ep in similar_episodes[:3]]}
Better strategies identified:
{[ep['summary'] for ep in better_strategies[:2]]}
Generate JSON guidance with:
- "suggested_action": next action to try
- "rationale": why this action
- "error_to_avoid": common mistakes in similar situations
"""
guidance_json = self.coach_llm(guidance_prompt)
return json.loads(guidance_json)
definject_guidance(self, agent_message_history, guidance):
"""
Append guidance to agent's message history as system message.
No agent retraining needed; guidance processed at inference.
"""
guidance_message = {
'role': 'system',
'content': f"""Coach guidance from past episodes:
Suggested next action: {guidance['suggested_action']}
Rationale: {guidance['rationale']}
Error to avoid: {guidance['error_to_avoid']}"""
}
agent_message_history.append(guidance_message)
return agent_message_history
Step 4: Integration Loop. Continuously update memory and guide agents.
When to Use: Web automation tasks (form filling, web navigation, data extraction) where agents encounter similar problems repeatedly across sessions; cost of retraining prohibitive.
Architecture Decisions:
Condenser LLM size: 8B is good balance of speed and quality; use smaller for latency-critical apps
EMS capacity: 100K–1M episodes depending on RAM budget; use FAISS for efficient retrieval
Guidance injection frequency: check every 3–5 steps; avoid excessive interruptions
Success criteria: define clearly (e.g., task complete, form submitted) for episode labeling
Pitfalls:
Memory corruption: Conditioning on irrelevant past episodes degrades performance; ensure similarity-based retrieval is accurate
Guidance misalignment: Coach guidance might conflict with agent's current thinking; add confidence thresholds before injection
Cold start: Agent lacks guidance on first session; seed EMS with expert demonstrations or synthetic data
When NOT to Use: One-off tasks; tasks with highly variable state spaces where similarity is hard to judge; real-time systems where retrieval latency matters.
Integration: Compatible with any web automation agent (Selenium, Playwright, LLM-based); no retraining, works with frozen policies.