mew is a Rust-native computer-use agent that drives a real, visible Chromium window through the Chrome DevTools Protocol (CDP). It perceives pages via accessibility-tree snapshots for speed and low token cost, falls back to vision only when necessary, runs a stealth Chromium binary, and is orchestrated by a two-agent LLM harness (intent-routing ChatAgent + ReAct-loop BrowserAgent) designed to stay cheap on a $0 infrastructure budget.
All thirty phases of the work plan are shipped: from the original accessibility-first perception and the two-agent handoff, through sensitive-platform routing, live step summarization, the resilience core, long-horizon research loop, CAPTCHA handling, pure-Rust evaluation harness, production hardening, N-worker supervisor pool, domain vertical packs (Jobs & SaaS Lead-Gen), single-call intent/shape routing, local result dedup & verification, Reflexion-style episodic memory, worker health heartbeats & stuck detection, supervisor respawn policy engines with cross-worker context preservation, and user-facing self-learning surfaces.
The Tauri shell is a single chat surface docked alongside Chromium: intent classification, live mid-task steering, structured ChatAgent → BrowserAgent → ChatAgent handoffs, and live human-readable progress lines instead of raw JSON.
You type something in the chat box — "go to instagram and text my friend hi" or "find remote Rust jobs on LinkedIn and give me a contact email" — and a real Chromium window opens, navigates, clicks, types, and reports back in natural language. Sensitive platforms (Instagram, Twitter, LinkedIn, TikTok, Facebook) are entered through a search-engine referrer instead of a bare direct nav. The agent pauses for human-in-the-loop on CAPTCHA pages. Long-horizon research tasks fan out across multiple platforms, each with its own step + time budget.
- Accessibility-first perception.
Accessibility.getFullAXTreeis the primary sensory input; full-page screenshots are reserved for the vision fallback (screenshot_region) on canvas / image-only elements. Diffs between snapshots are what the LLM context sees, not full trees. - Two-agent architecture.
ChatAgenthandles intent classification, conversation, and result synthesis.BrowserAgentruns the ReAct loop. A typedHandoff(task_description,subtasks,constraints,originating_message_id) flows ChatAgent → BrowserAgent; a typedResult(status,summary,key_findings,final_snapshot_signature) flows back. No session ends without a user-facing chat message. - Deterministic pre-flight planning. Compound instructions ("go to X" + "do Y") are split into a typed
Vec<SubTask>bymew_agent::plannerbefore the first LLM call. Every subsequent system prompt carries thePLAN (pre-flight decomposition)block. - Sensitive-platform routing. Domains listed in
config/sensitive_platforms.toml(instagram.com, twitter.com, x.com, facebook.com, linkedin.com, tiktok.com and theirwww.variants) are routed through a search-engine entry instead of a bare direct nav. This dodges the "browser appears on instagram.com from nowhere" anti-bot classifier. - URL Resolution Layer. Three branches — hardcoded map → direct
.comprobe (4s timeout) →google.com/search?q=…fallback — run before anynavigatecall reaches CDP. - Live step summarization. Every tool dispatch emits a one-line human note ("Opened instagram.com", "Typed 'John' into search box"). Templated for the common tools, LLM-rewritten only at the end-of-task gate. Concise / Detailed verbosity toggle. Live ring buffer capped at the last 5 lines, with total step count preserved.
- One chat surface, no transcript panel. The Tauri UI is a single chat list: user messages right-align, everything else left-aligns. Task lifecycle (
task_started→task_progress→task_completed/task_failed) renders as a card with a semantic left-border. The raw event log is available behind a collapsible "view details" on each task card, not as a separate always-on panel. - Resilience core. Six production failure modes are hardened: stale
@eXref → bounded re-snapshot retry; cookie banners / modals / "sign in to continue" → auto-dismiss or force as first required action; mid-task login loss → explicit surfacing; 429 / Cloudflare block pages → exponential backoff; irreversible actions (submit order, send payment, delete, post publicly) →Pausedcheckpoint for confirmation; low-confidence vision fallback → re-prompt or ask the user. - Long-horizon research loop. A typed
ResearchPlannerreadsconfig/research_platforms.toml, fans a goal out across platforms (LinkedIn, Indeed, Wellfound, WeWorkRemotely, RemoteOK by default; glassdoor, monster, YC commented out), runs aFindingStorefor cross-platform deduplication, enforces per-platform step + time budgets, and emits a single consolidated answer (role + email/URL per finding) at the end. - CAPTCHA / challenge handling. Detects Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha. Default behavior on detection: pause the session, message the user, let the human solve it in the visible browser window. Optional opt-in third-party solving-service integration is wired through the
CaptchaSolvertrait (off by default). Local-only telemetry records per-domain challenge counts. - Visible ghost cursor. A fixed-position overlay follows every click target and ripples on dispatch. Zero overhead when disabled (
visible_cursor: false). - Pacing guard. Site-aware anti-ban jitter. Consecutive identical actions in a tight loop are spaced by a configurable random delay; one-off and mixed actions never get paced. Opt-in (
pacing.enabled: falseby default). - Evaluation harness + CI gate. A pure-Rust mock-page scenario runner in
mew_agent::evalreplays fixed tasks end-to-end (no live Chrome, no LLM calls) and grades the handoff contract: right task dispatched, result reflected in chat reply, failure paths still produce a user-facing message. Wired into CI ascargo test --features evalso future changes can't silently regress the Bug #1 / #2 fixes. - Production hardening. All error messages are rewritten through a plain-language
error_messagelayer routed viaChatAgent. Tracing is quiet by default in production builds (opt in viaMEW_TRACING_DIR). End-of-task LLM summary and pre-flight decomposition are cached and reused where possible. Final pass against the three motivating scenarios: (1) "visit instagram and text my friend hi" works directly, (2) any browser task shows a clean chat summary in the UI, never raw JSON, (3) a multi-platform job-search task returns one consolidated list of roles + emails/URLs. - Supervisor & Worker Pool. (Phase 13-18) BrowserAgent runs as a long-lived supervised worker managed by an orchestrator pool, processing explicit
Todoschemas with completion checks and budgets. The supervisor handles pause/resume, early cancellations, and replanning seamlessly. - Agent State Machine & Pacing. (Phase 12-17) Explicit state machine transitions (
Stopped→Running→Paused→Done|Failed). Built-in site-aware pacing guards inject random delays between rapid repetitive actions to evade bot detectors.
The workspace is a Cargo workspace with focused, modular crates:
| Crate | Purpose |
|---|---|
mew-cdp |
CDP connection, stealth launch, window-docking geometry, ghost-cursor injection, screencast capture. Wraps chromiumoxide. |
mew-perception |
Accessibility-tree extraction, stable @eX ref assignment, semantic classification, snapshot diffing. |
mew-nav |
URL Resolution Layer + sensitive-platform routing table loader. Pure-Rust library (no serde_yaml dep — uses toml). |
mew-resilience |
The six failure-mode detectors (stale ref, modal interrupts, session loss, rate limit, irreversible actions, vision confidence) + mock-page fixtures for testing without a live browser. |
mew-agent |
ChatAgent + Planner + BrowserAgent + Orchestrator, Todo / Handoff / Result types, CompletenessTracker, PacingGuard, planner, summarizer, research, eval. |
mew-cli |
Headless CLI entrypoint (still useful for debugging, transcript logging, and the no-UI examples). |
mew-ui (Tauri) |
Tauri 2 shell, IPC bridge, TauriSink (the orchestrator's TurnSink impl for Tauri events), intent router, single chat surface, Tauri Channel for live events. |
- Rust:
stable-x86_64-pc-windows-msvc(Windows). Visual Studio C++ Build Tools installed before rustup. - Chromium: A local Chromium / Chrome binary. The stealth binary at
stealth-browser/chrome.exeis preferred — vanilla Chrome trips different anti-bot paths. - LLM provider: An OpenAI-compatible endpoint (e.g. OpenCode Zen). Default config uses
https://api.xiaomimimo.com/v1withmimo-v2.5. - Node.js / npm: For building the Tauri UI frontend (Vite + TypeScript).
config.yaml at the workspace root is the main config. The two platform tables (sensitive_platforms.toml, research_platforms.toml) live in config/.
opencode_zen:
base_url: https://api.xiaomimimo.com/v1
api_key: sk-...your key here...
default_model: mimo-v2.5
max_iterations: 100
max_tokens: 740000
browser:
binary_path: c:\Users\bari2\Desktop\mew-agent\stealth-browser\chrome.exe
visible_cursor: false
agent:
planner_enabled: true # Phase 18: Planner enabled by default for improved stability and observability
allowed_domains:
- example.com
- en.wikipedia.org
- github.com
- google.com
- www.google.com
- docs.rs
- crates.io
- localhost
pacing:
enabled: false
min_delay_ms: 800
max_delay_ms: 2500
streak_threshold: 2
summarization:
verbosity: concise # concise | detailed
live_lines_cap: 5
end_of_task_llm_summary: true
research:
enabled: true
platforms_file: config/research_platforms.toml
captcha:
enabled: false # off by default. See "Ethical / ToS boundary" below
solve_kinds: [] # populated by the third-party solver provider at runtime
provider: <name> # free-form, e.g. "2captcha", "anti-captcha", "capmonster"
api_key_env: <env var> # env var holding the provider key
per_session_cap: 5Never commit config.yaml — api_key is a secret. .gitignore excludes it by default.
cd mew-ui
npm install
npm run dev # Vite + Tauri dev shell
# or
npm run build && cargo tauri buildA headed Chromium window opens, the chat panel docks to the left of it, and you type in the input box. Every browser task shows up in the chat list as a card with a "Working · N steps" header. The first user message after a quiet period triggers intent classification: conversational chat stays in the chat; anything that smells like "go to X / do Y" gets a chat-task-started card and a real Chromium session.
cargo run -p mew-cli --bin agent_testUseful for transcript logging, pacing experiments, and stale-ref / network-failure / chat-channel / completeness smoke tests (the mew-cli/src/bin/ examples).
# Phase 2: instagram phrasings
cargo run --example phase2_instagram_regression -p mew-agent
# Phase 3: ChatAgent → BrowserAgent → ChatAgent round trip
cargo run --example phase3_round_trip -p mew-agent
# Phase 5: live step summarization
cargo run --example phase5_live_progress -p mew-agent
# Phase 6: resilience detectors
cargo run --example phase6_resilience_core -p mew-agent
# Phase 7: long-horizon research loop
cargo run --example phase7_benchmarks -p mew-agent
# Phase 9: evaluation harness + CI gate
cargo test --features eval -p mew-agent
cargo run --example phase9_eval_harness -p mew-agent┌──────────────────────────────────────────────────────────┐
│ mew-ui (Tauri 2) │
│ - single chat surface │
│ - Channel<any> for live events (State/Tool/Summary/ │
│ ProgressLine) │
│ - Tauri commands: send_message, pause_session, ... │
└────────────────────────┬─────────────────────────────────┘
│ invoke('send_message') │
│ Channel<OrchestratorEvent> │
▼
┌──────────────────────────────────────────────────────────┐
│ mew-agent::orchestrator │
│ - TauriSink maps OrchestratorEvent → app.emit(...) │
│ events: chat-task-started, chat-reply, │
│ chat-task-completed, chat-steering-ack, │
│ agent-state, browser-result-ready │
└────────────────────────┬─────────────────────────────────┘
│
┌─────────────────┴──────────────────┐
▼ ▼ ▼
ChatAgent Planner BrowserAgent
- classify - split to todos - ReAct loop
- build_handoff - budget guard - perceive → act
- reply (Result) - pool dispatch - completeness gate
- -> BrowserResult - finish() → TodoResult
The Handoff struct (task_description, subtasks, constraints, originating_message_id) is the unit ChatAgent hands to BrowserAgent. The Result struct (status: Done | Partial | Failed, summary, key_findings, final_snapshot_signature, raw_transcript_ref) is what BrowserAgent hands back. ChatAgent::synthesize_reply turns the Result into the user-facing text that lands in the chat list as a chat-reply event.
The orchestrator is the only place that produces a user-facing chat message — a Failed transition always lands a ChatReply with a human-readable reason, never silent. All error paths route through the error_message layer, so raw Err / panic / JSON surfaces never reach the user as-is.
A new file, config/sensitive_platforms.toml, lists domains that need special entry. Pre-seeded entries cover the Instagram, Twitter/X, Facebook, LinkedIn, and TikTok families. Each row carries an EntryStrategy:
direct— same as not being in the table. Listed for audit clarity.via_search—navigate("instagram")becomeshttps://www.google.com/search?q=instagram. The LLM has to click the organic result to actually land on the target.via_search_confirm— likevia_searchbut with a "login" keyword appended, nudging the search results toward the canonical sign-in URL (useful for LinkedIn where the LLM usually needs the authenticated surface immediately).
Each row also carries a known_to_challenge_bots flag that downstream code (pacing, telemetry) reads to slow down before the navigation and to seed the per-domain challenge baseline.
Why a separate file? mew-nav is a pure-Rust library; pulling serde_yaml (only used by mew-agent's config) into it would be the wrong dependency direction. toml is a tiny zero-config dep. Operators can edit the list without touching config.yaml.
Where the table is loaded from. SensitivePlatforms::load_from_default_location resolves config/sensitive_platforms.toml in this order:
MEW_WORKSPACE_DIRenv var, if set — treated as the repo root.- A parent-directory walk from
std::env::current_dir(), bounded at 16 hops. - Fall back to an empty table (with a
tracing::debuglog) so pre-Phase-2 setups still run.
The Tauri shell sets MEW_WORKSPACE_DIR automatically in its setup hook (it walks up from the executable's path looking for a Cargo.toml that contains a [workspace] section, and uses the first hit as the workspace root). This makes the reroute work for both dev builds (where CWD is target/debug/ and the walk would otherwise have to hop several levels) and release bundles (where the binary lives in Program Files\mew-ui\ or mew-ui.app/Contents/MacOS/ and has no parent with the file). Operators can also set the env var manually before launching app.exe to point at a different config tree.
Phase 16.3 note. The pre-16.3 implementation used
Path::new("config/sensitive_platforms.toml")directly, which only worked when the process's CWD happened to be the workspace root. The Tauri shell's CWD istarget/debug/, so the table silently failed to load andnavigate("https://www.instagram.com")returned "domain not in allowlist" because the resolver fell through to thealready-urlbranch instead of converting to a Google search URL. Pin tests:mew-nav'sload_from_default_location_walks_parentsandload_from_default_location_honors_workspace_dir_env.
mew detects four challenge families in the rendered accessibility tree:
- Cloudflare Turnstile (the 2022+ replacement for "I'm Under Attack")
- Google reCAPTCHA v2 ("I am not a robot" checkbox + image grid)
- Google reCAPTCHA v3 (invisible / score-based — detected via the v3 script + badge)
- hCaptcha (the privacy-focused alternative)
Default behavior on detection: pause the session, message the user clearly, let the human solve it in the visible browser window. The chat list shows something like:
Cloudflare Turnstile challenge detected on instagram.com — The agent has paused. Please solve the challenge in the browser window, then tell the agent to continue.
Because mew runs a real, visible, headed browser, the human is the safest solver. The next snapshot detects the challenge is gone and the agent resumes.
Sites known to challenge bots get an extra 1.5–3.0s "settle-in" delay before each navigation. Local-only telemetry records per-domain challenge counts in <data_dir>/captcha_telemetry.json. No data leaves the machine.
The optional CaptchaSolver trait is the pluggable extension point for unattended runs. The default enabled: false keeps the pause-and-message path; an opt-in provider + api_key_env config block routes through the provider-specific implementation when the user explicitly chooses unattended behavior.
mew's default behavior on a challenge page — avoidance, slow pacing, human handoff — is the 2026 consensus path and the path that keeps you inside the Terms of Service of the sites you visit.
Many sites' ToS explicitly forbid automated solving. Cloudflare, Google, and most major hCaptcha-using sites treat solver-API traffic as a violation that can result in:
- Permanent account ban on the offending site
- IP-range level blocking that affects all users behind the same NAT / VPN
- Reputational damage if the account is associated with public-facing work
Solving services also cost money — per-challenge pricing in the $1–3 / 1000 range as of 2026, with reCAPTCHA v2/v3 cheaper than image-grid challenges.
For these reasons, the agent.captcha block in config.yaml is off by default, and enabling it is an explicit consent. The CaptchaSolver trait ships with implementations for 2captcha, anti-captcha, and capmonster — pick one in config.yaml and set the matching api_key_env. Per-provider cost and ToS guidance is in docs/phase8-captcha-handling.md.
Enable enabled: true only when:
- You have read and accept the ToS of every site your agent will visit.
- You understand the per-challenge cost.
- You have a legitimate use case (accessibility research, a personal workflow on a site you own).
If your task is to visit a public site and interact with it normally, the default behavior is what you want. Solve the one or two challenges that come up in the visible browser window; do not enable automated solving.
All eighteen phases from work.md are shipped:
- Phase 1 — structured tracing, URL Resolution span instrumentation,
SessionHandletransition spans, intent-router boundary tracing, live reproductions of Bug #1 (both phrasings) and Bug #2 captured. - Phase 1.5 — minimum
chat-replyemit fix, frontend listener, root-cause doc update, replacedbrowser_task_result_droppedtripwire with the positivebrowser_task_result_delivered. - Phase 2 — sensitive-platform routing table,
ResolutionPath::ViaSearch/ViaSearchConfirm, pre-flight decomposition viamew_agent::planner, regression fixture + 17 planner tests + 11 nav tests, 10/10 pass rate on the original failing prompt logged indocs/bug-1-fix-verification.md. - Phase 3 —
ChatAgent/BrowserAgentsplit, typedHandoff/Result,synthesize_replyintegration, mid-task steering ack channel, fullChatAgent → BrowserAgent → ChatAgentround trip verified live. - Phase 4 — single chat surface,
MessageKindtype system, collapsible "view details", "Working · N steps" header pill, ghost-cursor / docking visuals re-verified unaffected, instagram e2e re-tested clean. - Phase 5 — live step summarization,
AgentEvent::ProgressLinechannel variant, end-of-task LLM summary through theResultstruct, ring buffer cap, verbosity toggle. - Phase 6 — six failure-mode detectors (
ref_recovery,modal_interrupts,session_loss,rate_limit,irreversible_actions,vision_confidence) + mock fixtures. - Phase 7 —
ResearchPlanner, falsifiable-commitment checkpoint per subtask,FindingStorecross-platform dedup, "no result on this platform ≠ task failed", consolidated final synthesis, per-platform budgets, end-to-end benchmarks logged indocs/phase7-benchmarks.md. - Phase 8 — challenge-page detector, pause-and-message default,
CaptchaSolvertrait + 2captcha / anti-captcha / capmonster implementations,known_to_challenge_botsflag, local-only telemetry, README ethical / ToS boundary. - Phase 9 —
mew_agent::evalmock-page harness,cargo test --features eval -p mew-agentCI gate, handoff-contract assertions,docs/eval-history.mdpass-rate log. - Phase 10 — error-messaging pass through
mew_ui::error_message, consolidated config schema, quiet-by-default tracing, end-of-task summary + planner cache, README / docs final pass, three motivating-scenario acceptance pass. - Phases 11–12 — Todo schema and decomposition contract, Agent state machine.
- Phases 13–14 — Supervisor signal model (Pause, Resume, Cancel, Replan), Browser Agent as a long-lived supervised worker, TodoRejectedEvent user-cancel path.
- Phases 23–25 — Episodic Memory & Self-Learning (
mew-memory). Reflexion-style write path (LessonStore), prompt injection read path to avoid repeating mistakes, confidence scoring (confidence_score), memory decay, and contradiction resolution. - Phases 26–28 — Worker Health & Supervisor Respawn Core (
WorkerHealth,RespawnPolicy,RespawnHandoff). Heartbeat tracking, action loop stuck detection, corrective prompt nudging, proactive token budget cutoff respawns, crash recovery, and zero-duplicate subtask context transplant on respawn. - Phase 29 — Chat-Agent Awareness of Respawns & Self-Learning Surface. UI/UX layer featuring
task_recoveredcard state (cyan/teal border accent), synthesized plain-language chat reply respawn notes (no internal Rust terms exposed), and read-onlyget_recent_lessonsTauri command to surface learned insights.
See proj.md for the full phase-by-phase architecture and design history, and work.md for the authoritative checkbox state of every phase.
Built with chromiumoxide, tauri, tokio, reqwest, and standard Rust async plumbing.