TERX records successful CDP workflows once. Matching repeat runs replay in milliseconds with no LLM call, no tokens, and a structured audit report.
| task | llm steps | agent (cold) | terx (warm) | speedup | tokens | cost saved | hit |
|---|---|---|---|---|---|---|---|
| User Login Flow | 4 | 3.05s · $0.0065 | 0.090s · $0.000 | 34.0x | 1,985 | 100% | ✓ |
| Search and Filter | 4 | 17.82s · $0.0108 | 0.099s · $0.000 | 179.3x | 2,634 | 100% | ✓ |
| Multi-step Signup | 6 | 41.05s · $0.0142 | 0.103s · $0.000 | 399.4x | 4,339 | 100% | ✓ |
| E-commerce Product | 4 | 9.84s · $0.0123 | 0.100s · $0.000 | 98.1x | 2,798 | 100% | ✓ |
| Settings Toggles | 4 | 15.42s · $0.0082 | 0.100s · $0.000 | 154.5x | 2,131 | 100% | ✓ |
| Data Table Pagination | 3 | 11.27s · $0.0093 | 0.088s · $0.000 | 128.7x | 1,756 | 100% | ✓ |
| Support Ticket | 4 | 18.35s · $0.0078 | 0.100s · $0.000 | 184.2x | 2,130 | 100% | ✓ |
| Fuzzy Search | 3 | 10.41s · $0.0055 | 0.083s · $0.000 | 124.8x | 1,393 | 100% | ✓ |
| Profile Update | 4 | 17.20s · $0.0104 | 0.077s · $0.000 | 223.7x | 2,544 | 100% | ✓ |
| Complex Form | 4 | 16.51s · $0.0074 | 0.086s · $0.000 | 192.2x | 2,072 | 100% | ✓ |
| total / average | — | 160.93s · $0.0925 | 0.926s · $0.000 | 173.9x | 23,782 | 100% | 10/10 |
Reproduce: GROQ_API_KEY=... python -m terx.benchmarks.real_agent
No-key proof: terx demo and terx eval-local
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" }
}
}
Wrap repeatable workflows with browser_task_start and browser_task_finish. Pass variables and postconditions for safe replay. First run records. Matching repeats replay without another LLM call.
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",
variables={"email": "user@example.com", "password": "..."},
postcondition={"text_contains": "Welcome"},
) as ctx:
if ctx.hit:
await ctx.replay() # 0 tokens, ~80ms
else:
await your_agent.run(...) # agent runs, TERX records
# 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.
# local Chrome demo + deterministic eval suite
terx demo
terx eval-local
┌─────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────┘
Raw asyncio WebSocket to Chrome. No Playwright subprocess. <50ms startup, ~2MB RAM.
Reads Chrome's Accessibility Tree (not raw HTML). Computes a fuzzy structural hash — survives CSS changes and A/B tests without breaking cache hits.
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.
Chrome assigns new backendNodeIds each session. TERX re-snapshots on replay and maps old IDs to current equivalents by role + label matching.
| tool | what it does |
|---|---|
browser_task_start | Start a cacheable workflow; replays immediately on cache hit |
browser_task_finish | Store a successful workflow or discard a failed one; postconditions prevent bad hits |
browser_get_state | AX tree snapshot — stable element IDs, no hallucination-prone HTML |
browser_navigate | Navigate to URL (scheme-validated, blocks javascript: data: file:) |
browser_click | Click element by stable ID using a remappable node handle |
browser_click_at | Fallback physical click at an element center point |
browser_type | Focus an input by stable ID and insert text |
browser_screenshot | Returns hash ref, not base64 — no context window poisoning |
browser_screenshot_get | Retrieve a screenshot by hash ref when image bytes are needed |
browser_scroll | Scroll up/down |
browser_new_tab | Open new tab |
cache_stats | Hit rate, total savings, unique domains cached |
cache_invalidate | Clear cache for a domain when the UI ships a redesign |