Architecture
Three layers — the product model
Section titled “Three layers — the product model”The plugin is organized into three layers. Each one has a different job and gets used at a different point in the loop.
| Layer | Job | Examples |
|---|---|---|
| Workflow | Decides what to do; gates quality | /rn-feature-dev 8-phase pipeline; the rn-tester / rn-debugger / rn-code-architect agent protocols |
| Discovery | Looks at the running app to know what’s there | CDP tools (cdp_component_tree, cdp_store_state, cdp_navigation_state, cdp_evaluate); device tools (device_press, device_fill, device_snapshot, device_screenshot) |
| Reproducible actions | Replays known flows in seconds; self-repairs on UI drift | .rn-agent/actions/<name>.yaml; cdp_run_action orchestrator; cdp_repair_action selector patcher; actions guide |
┌──────────────────────────────────────┐ │ Workflow (rn-feature-dev, agents) │ │ Decides WHAT, gates quality │ └────────┬───────────────────┬─────────┘ │ "verify this" │ "produce/replay flow" ▼ ▼ ┌─────────────────────┐ ┌──────────────────────┐ │ Discovery │ │ Actions │ │ Empirical reality │ │ Replay in seconds │ │ CDP + device tools │ │ Self-repair on drift│ │ Macro-Asserts │◄─┤ │ └─────────────────────┘ └──────────────────────┘ ▲ │ │ Discovery emits │ └─── new actions ───┘ Actions repair via DiscoveryWhy this matters: replay vs. interactive walks
Section titled “Why this matters: replay vs. interactive walks”On a known 3-step task-creation wizard, an interactive agent walk took 13 minutes 55 seconds. The same wizard, replayed as a saved action, finished in ~4 seconds — a ~210× speedup. That’s the load-bearing data point behind the architecture: discovery tools are how the agent finds new ground; actions are how it replays known ground without paying that cost again.
The agent doesn’t choose all-or-nothing. If you’re on the login screen and need to do something on home, the agent runs the saved login action as a prologue (4 seconds), then discovers the new work interactively. See actions for the full hybrid composition pattern.
Implementation layers — how it’s built
Section titled “Implementation layers — how it’s built”The product layers above are organized; underneath, three implementation layers do the work.
┌─────────────────────────────────────────────────────┐│ Claude Code ││ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ ││ │ Skills │ │ Agents │ │ Commands │ ││ │ (knowledge) │ │ (protocols) │ │ (entry pts)│ ││ └──────┬───┬──┘ └──────┬───────┘ └─────┬──────┘ ││ │ │ │ │ ││ ┌──────▼───▼────────────▼─────────────────▼──────┐ ││ │ MCP Server (CDP Bridge) │ ││ │ WebSocket → Metro → Hermes CDP │ ││ │ 74 tools across 5 families │ ││ └─────────┬───────────────────────────┬───────────┘ ││ │ │ ││ ┌─────────▼──────────┐ ┌─────────▼──────────┐ ││ │ rn-fast-runner │ │ agent-device CLI │ ││ │ (iOS, in-tree) │ │ (Android, 3-tier) │ ││ │ XCTest /command │ │ daemon → CLI │ ││ └────────────────────┘ └────────────────────┘ │└─────────────────────────────────────────────────────┘ │ │ ┌────▼────┐ ┌─────▼─────┐ │ iOS Sim │ │ Android │ │ │ │ Emulator │ └─────────┘ └───────────┘| Implementation tier | Tool | Role |
|---|---|---|
| Device interaction (iOS) | In-tree rn-fast-runner XCTest rig (scripts/rn-fast-runner/) — POST /command HTTP | Native iOS device control. Always calls XCUIApplication.activate() per request (D1219, PR #164). iOS no longer requires agent-device. |
| Device interaction (Android) | agent-device CLI (auto-installed) | 3-tier dispatch: daemon socket → fast-runner → CLI fallback |
| App introspection | Custom MCP server → Hermes CDP via WebSocket | Persistent WebSocket — reads React fiber tree, store state, network, console, errors |
| E2E testing | maestro-runner (preferred) / Maestro (fallback) | YAML-based persistent test files; underlying format for actions in .rn-agent/actions/ |
Fallback: xcrun simctl (iOS) + adb (Android) for device lifecycle (boot / install / launch / terminate) — the runner doesn’t manage device state, only interaction.
MCP server (CDP bridge)
Section titled “MCP server (CDP bridge)”The MCP server is a Node.js process that maintains a persistent WebSocket connection to the React Native app’s Hermes engine through Metro’s CDP endpoint.
74 tools across five families:
- CDP — React internals via Chrome DevTools Protocol (component tree, store state, navigation, profiling, network)
- Device — Native interaction (iOS: rn-fast-runner, Android: agent-device)
- Actions — Record / replay / self-repair (
cdp_run_action,cdp_repair_action,cdp_record_test_save_as_action,cdp_record_test_*) - Testing — Proof capture, auto-login, cross-platform verify, Maestro orchestration
- Macro-Asserts — State-assertive replays (
expect_redux,expect_route,expect_visible_by_testid,expect_text)
All tools are registered through a single trackedTool() wrapper that adds telemetry via the Experience Engine — that’s the same mechanism that feeds the auto-action capture loop.
Helper injection
Section titled “Helper injection”On first CDP connect, ~2KB of JavaScript is injected into the Hermes runtime via Runtime.evaluate. This creates globalThis.__RN_AGENT with methods:
getTree(filter, depth)— Walk the React fiber treegetNavState()— Read React Navigation / Expo Router stategetStoreState(path, type)— Read Redux / Zustand / React QuerygetComponentState(testID)— Inspect hooks by testIDnavigateTo(screen, params)— Navigate via fiber tree traversalgetErrors()/clearErrors()— Error tracking
Ring buffers
Section titled “Ring buffers”Since MCP is pull-based (tools are called on demand), events that fire between calls are buffered:
| Buffer | Size | Content |
|---|---|---|
| Console | 200 entries | console.log/warn/error output |
| Network | 100 entries | HTTP request/response metadata |
| Errors | 50 entries | Unhandled exceptions and promise rejections |
Key technical decisions
Section titled “Key technical decisions”| Decision | Rationale |
|---|---|
| Inject helpers ONCE on connect | ~2KB JS, then call __RN_AGENT.* — small payloads per call |
| 5-second timeout on ALL CDP calls | Prevents hanging promises |
| RedBox detection before tree return | Check fiber root for LogBox/ErrorWindow, warn instead of returning error overlay |
Debugger.paused auto-resume | Prevents silent JS thread freeze from debugger; statements |
| Network fallback for RN < 0.83 | Try Network.enable, if fails → inject fetch/XHR monkey-patches |
| Filter mandatory on component tree | Full dumps waste 10K+ tokens — always scope to testID or component |
Device dispatch by platform
Section titled “Device dispatch by platform”Since PR #164 / D1219 the two platforms use different dispatch paths:
iOS — single-endpoint rn-fast-runner. Every iOS device_* call short-circuits through runIOS() (TS client at scripts/cdp-bridge/src/runners/rn-fast-runner-client.ts) to a POST /command HTTP endpoint exposed by an in-tree XCTest rig. Coordinate-based gestures map to .drag; direction-based swipes/scrolls are pre-computed to coords by device-interact.ts before dispatch. device_find (non-exact) and device_scrollintoview are TS-side orchestrators over runIOS('snapshot') — no Swift .findText round-trip for fuzzy matching. iOS no longer requires agent-device.
Android — 3-tier agent-device fallback. The Android path retains the original tiered dispatch via agent-device-wrapper.ts:
- fast-runner (XCTest-style HTTP server bundled with
agent-device) — lowest latency - agent-device daemon — persistent process, medium latency
- agent-device CLI — direct invocation, highest latency
Measured: iOS rn-fast-runner delivers ~216ms tap, ~5ms snapshot, ~74ms screenshot — the fast-runner path is ~13× faster than CLI fallbacks on either platform.
A stale ~/.agent-device/daemon.json can respawn the upstream AgentDeviceRunner and fight the in-tree rn-fast-runner for focus on iOS. The plugin detects the legacy daemon at session-open; set RN_DEVICE_KILL_LEGACY=1 to opt into termination automatically.
What we’re NOT using (and why)
Section titled “What we’re NOT using (and why)”| Tool | Why not |
|---|---|
| Appium | Too heavy, latency overhead, black-box (no RN sync) |
| Flipper | Deprecated for debugging in RN 0.76+ |
| Detox | Great for JS tests but not AI-agent-friendly (JS files, not YAML) |
| Facebook idb | Python + pip + companion daemon = too much setup friction |