// memory layer for browser agents

Your agent rediscovers the login button
on every single run.

TERX records what it did the first time. Every run after that replays in milliseconds — no LLM, no tokens, no waiting.

TERX Live Demo
$ pip install terx
10/10 cache hits 182.7x speedup (measured) MIT Python 3.11+ Raw CDP — no Playwright
// real benchmark — Groq API, openai/gpt-oss-120b, measured
182.7x
average speedup on warm runs
100%
token savings on cache hits
10/10
cache hit rate across task suite
task llm steps agent (cold) terx (warm) speedup tokens cost saved hit
User Login Flow42.93s · $0.00760.078s · $0.00037.7x2,090100%
Search and Filter53.99s · $0.01360.101s · $0.00039.7x3,533100%
Multi-step Signup21.54s · $0.00450.062s · $0.00025.0x1,035100%
E-commerce Product525.97s · $0.01610.091s · $0.000286x3,811100%
Settings Toggles416.01s · $0.00950.103s · $0.000155.9x2,451100%
Data Table Pagination1290.84s · $0.05670.259s · $0.000350.4x12,479100%
Support Ticket415.40s · $0.00780.101s · $0.000152.2x2,135100%
Fuzzy Search311.85s · $0.00620.089s · $0.000132.9x1,459100%
Profile Update312.35s · $0.00860.094s · $0.000131.2x1,854100%
Complex Form417.74s · $0.00820.110s · $0.000161.5x2,146100%
total / average198.62s · $0.13881.087s · $0.000182.7x32,993100%10/10

Reproduce: GROQ_API_KEY=... python -m terx.benchmarks.real_agent

// quickstart

MCP server
Python library
Run benchmark

Works with Claude Desktop, Cursor, Windsurf

Start Chrome with debugging enabled:

# close all Chrome windows first
google-chrome --remote-debugging-port=9222 --no-first-run

Start TERX:

pip install terx
terx-server

Add to your MCP config:

// claude_desktop_config.json
{
  "mcpServers": {
    "terx": { "command": "terx-server" }
  }
}

Every browser task your agent runs is now cached. First run = normal. Every repeat = free.

Wrap your existing agent — 3 lines

from terx.cdp.session import BrowserSession
from terx.cache.cache import MemoryCache, session_for

cache = MemoryCache()

async with BrowserSession() as session:
    bridge = session.bridge()

    async with session_for(cache, bridge, "login to salesforce") as ctx:
        if ctx.hit:
            await ctx.replay()          # 0 tokens, ~80ms
        else:
            await your_agent.run(...)   # agent runs, TERX records

Run the real LLM benchmark yourself

# free key at console.groq.com
pip install "terx[benchmark]"
cp .env.example .env   # fill in GROQ_API_KEY
python -m terx.benchmarks.real_agent

Runs 10 real tasks. Prints the table above with your measured numbers.

// how it works

  ┌─────────────────────────────────────────────────┐
  │                  your agent                      │
  │          (browser-use / Claude / anything)        │
  └───────────────────────┬─────────────────────────┘
                          │
  ┌───────────────────────▼─────────────────────────┐
  │              TERX session_for()                │
  │  on miss → records CDP commands                  │
  │  on hit  → replays them directly                 │
  └───────────────────────┬─────────────────────────┘
                          │ raw WebSocket
  ┌───────────────────────▼─────────────────────────┐
  │                  Chrome                          │
  │           (remote debugging port 9222)            │
  └─────────────────────────────────────────────────┘
01

CDP Bridge

Raw asyncio WebSocket to Chrome. No Playwright subprocess. <50ms startup, ~2MB RAM.

02

DOM Extractor

Reads Chrome's Accessibility Tree (not raw HTML). Computes a fuzzy structural hash — survives CSS changes and A/B tests without breaking cache hits.

03

Muscle Memory Cache

SQLite. On success: stores CDP command sequence keyed by (domain, dom_hash, task). On future runs: replays directly. INSERT OR IGNORE — first successful recording is canonical.

04

Dynamic node ID translation

Chrome assigns new backendNodeIds each session. TERX re-snapshots on replay and maps old IDs to current equivalents by role + label matching.

// mcp tools

toolwhat it does
browser_get_stateAX tree snapshot — stable element IDs, no hallucination-prone HTML
browser_navigateNavigate to URL (scheme-validated, blocks javascript: data: file:)
browser_clickClick element by stable ID
browser_typeType into input — fires native setter (React/Vue/Svelte safe)
browser_screenshotReturns hash ref, not base64 — no context window poisoning
browser_scrollScroll up/down
browser_new_tabOpen new tab
cache_statsHit rate, total savings, unique domains cached
cache_invalidateClear cache for a domain when the UI ships a redesign