TERX
Local, policy-enforced replay for approved browser-agent workflows.
TERX records a small, semantic workflow after a browser agent succeeds, then replays it through Chrome DevTools Protocol without another model call. It is for repeatable workflows whose starting state, caller scope, and successful outcome can be stated explicitly.
It is not a general browser automation framework, an autonomous fallback agent, or a safe way to replay arbitrary JavaScript.
When TERX earns its place
Use TERX when an agent has already completed a browser workflow and you want the next run to be cheap, local, and constrained by evidence—not rediscovered by another model call. It is a fit for repeatable internal tasks such as logging into a known account, searching an operations dashboard, or filing an approved action behind a control-plane approval.
On a cache hit, TERX re-checks the caller scope and starting conditions, re-resolves every accessible target, executes only the semantic action set below, then verifies the intended outcome. If any check is ambiguous or false, it refuses instead of guessing.
v0.4: Trustworthy Replay
Every cacheable workflow must declare:
- a
scope_idthat binds it to a tenant, account, environment, or fixture; - a
preconditionchecked before replay; - a
postconditionchecked after the first run and every replay; - a route, workflow version, TTL, and side-effect class.
Conditions support url_contains, title_contains, text_contains, and
selector_exists; every value must be a non-empty string. Their full values participate in a policy fingerprint but
are not written to the cache. Change any part of a workflow contract and bump
workflow_version to intentionally create a new replay entry.
TERX persists only three semantic actions:
TERX.navigate— anhttporhttpsorigin-and-path navigation (no query, fragment, or embedded credential);TERX.click— an exact accessible role and label;TERX.type— an exact labelled input and a named value placeholder.
Raw CDP commands, coordinate clicks, key events, and arbitrary JavaScript still run on the cold path but make the workflow non-cacheable. Ambiguous targets, failed conditions, missing variables, expiry, and unapproved destructive work produce a structured refusal; TERX does not silently invoke an LLM.
Why it stays lightweight
TERX reuses the Chrome CDP connection your application already owns. Its core runtime is an async CDP bridge, an accessibility-tree snapshot, and local SQLite—no Playwright or Selenium runtime, browser fleet, background service, or cloud account. The workflow adapter holds an accessibility snapshot only while a cold path is running and discards it before the next workflow.
Install
pip install terx
TERX needs a local Chrome or Chromium instance with remote debugging enabled:
google-chrome --remote-debugging-port=9222 --no-first-run \
--user-data-dir=/tmp/terx-chrome
Python quickstart
from terx.cache.cache import MemoryCache, session_for
from terx.cdp.session import BrowserSession
cache = MemoryCache()
async with BrowserSession() as session:
bridge = session.bridge()
async with session_for(
cache,
bridge,
"sign in to the billing dashboard",
scope_id="acme-prod:billing-service-account",
route_pattern="/login",
workflow_version=1,
side_effect="mutating",
variables={"email": "bot@acme.test", "password": "from-secret-store"},
precondition={"url_contains": "/login"},
postcondition={"text_contains": "Billing overview"},
) as replay:
if replay.hit:
await replay.replay()
else:
# Drive bridge.send(...) from your agent here. Only labelled clicks
# and named-variable text entry become replayable actions.
await your_agent.run()
print(replay.report.as_dict())
For a destructive workflow, require a host approval verifier that atomically checks and consumes a fresh approval for this exact replay identity:
from terx import ApprovalDecision
async def verify_and_consume(request):
verdict = await control_plane.consume_browser_approval(
token=request.token,
scope_hash=request.scope_hash,
task=request.task_description,
workflow_version=request.workflow_version,
policy_fingerprint=request.policy_fingerprint,
structural_hash=request.structural_hash,
command_digest=request.command_digest,
)
return ApprovalDecision(verdict.approved, verdict.consumed, verdict.reason)
# Pass approval_verifier=verify_and_consume when creating session_for(...).
await replay.replay(approval_token=approval_from_your_control_plane)
Without both a token and a verifier result with approved=True and
consumed=True, TERX refuses before running any replay action. The standalone
terx-server has no verifier by default and therefore fails closed for
destructive cache hits.
ReplayReport.status is one of miss, hit, refused, or failed. Treat a
refusal as a signal to run an approved cold-path agent flow or ask for input;
do not assume TERX completed the task.
MCP
terx-server
{
"mcpServers": {
"terx": { "command": "terx-server" }
}
}
Start a cacheable task with a complete contract, run normal browser_* tools
on a miss, then finish it:
browser_task_start(
task="sign in to billing",
scope_id="acme-prod:billing-service-account",
precondition={"url_contains": "/login"},
postcondition={"text_contains": "Billing overview"},
variables={"email": "bot@acme.test", "password": "..."}
)
browser_task_finish(success=true)
For side_effect="destructive", replay_approval is only an opaque token.
The application embedding TERXServer must configure a consume-once
approval_verifier; the standalone server refuses destructive cache hits by
default. The MCP server returns a refusal reason when it declines to act.
Print the local configuration for a client; this command only writes to stdout and does not install a daemon or change client settings:
terx mcp-config --client cursor
terx mcp-config --client codex
Lightweight application adapter
For code that already has a Chrome CDP bridge, use TERX's dependency-free workflow adapter. It adds no Playwright/Selenium runtime, browser process, or background worker. The cold path exposes only the three actions TERX can prove and replay:
from terx.integrations.workflow import TerxWorkflow
workflow = TerxWorkflow(
cache=cache,
bridge=bridge,
task="sign in to billing",
scope_id="acme-prod:billing-service-account",
variables={"email": "bot@acme.test", "password": "from-secret-store"},
precondition={"url_contains": "/login"},
postcondition={"text_contains": "Billing overview"},
)
async def sign_in(browser):
await browser.type_into("textbox", "Email", "email")
await browser.type_into("textbox", "Password", "password")
await browser.click("button", "Sign in")
await browser.wait_for({"text_contains": "Billing overview"})
result = await workflow.run(sign_in)
On a warm hit, sign_in is never called. Direct mutating bridge calls are
intentionally excluded: use the adapter's semantic actions or TERX refuses to
call it a replayable workflow. See integration guidance.
Security model
- The cache is local SQLite under
.terx/; protect that directory as application data. - Named typed values are stored as
{{placeholders}}; typed text without a named variable is intentionally not cacheable. - Password, token, key, and similar named fields are redacted at cache and audit boundaries. Cached response payloads are not retained.
- Scope IDs are represented by a digest in the cache; scope is verified before lookup and replay.
- The experimental
SelfHealeris disabled by default and is never called by a replay. Enabling it can send the supplied diagnostic request to a LiteLLM provider.
See SECURITY.md for the full boundary and reporting policy.
Supported integration surface
Python, the dependency-free workflow adapter, and the built-in MCP server are supported in v0.4. The Browser Use adapter is experimental: it can only record agents that intentionally drive the TERX CDP bridge supplied to it. A normal Browser Use session is not a drop-in capture source.
Verification
pytest tests/ -v
ruff check .
terx eval-local
terx eval-local launches a temporary local page and headless Chrome to verify
cold recording and warm semantic replay. It does not establish compatibility
with arbitrary production websites.
What TERX is not
TERX does not replace Playwright, Stagehand, Browser Use, or hosted browser automation platforms. Those products provide broader automation, browser fleet, observability, anti-bot, and agent capabilities. TERX's focused value is a local replay gate: reuse a known-good, explicitly approved workflow only when its scope and evidence still match.
Development
git clone https://github.com/ixchio/terx.git
cd terx
pip install -e ".[dev]"
pytest tests/ -v
ruff format --check .
ruff check .
python -m terx.evals.local_suite
See the quickstart, development guide, and changelog.
Release files for terx 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| terx-0.4.0.tar.gz | 158.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| terx-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 230.6 kB
Release files / terx-0.4.0.tar.gz
| Download URL | terx-0.4.0.tar.gz |
|---|---|
| Size | 158.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0893d5c2a9b38884f71961643173be726792517a66d29315b5da3f9ec862b5a9
|
|
BLAKE2b-256 checksum How to use checksums |
4b0771a337aa8056caec2579e8b51bdae03b2a9318f539fcf1bcbbac9d9a110c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / terx-0.4.0-py3-none-any.whl
| Download URL | terx-0.4.0-py3-none-any.whl |
|---|---|
| Size | 72.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5dc8df1e289f15532f73f6b29b8f3b7ebe7a13f45760a78a9b21a767f0aab96f
|
|
BLAKE2b-256 checksum How to use checksums |
7e9935c96b7bf5bb028e95d24859eaf9dbc1242ee75180522e870accb62e07d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|