Core Concepts
Actions
An action is a saved, parameterised flow through your app — login, navigating to a screen, completing a multi-step form. The plugin records them automatically when /test-feature verification passes; you replay them in seconds via /run-action, and the agent uses them as prologues when it needs to reach a known state before doing new work.
| What | A saved, replayable Maestro flow with a metadata header and ${KEY} placeholders. |
| Where | .rn-agent/actions/<name>.yaml. The plugin’s home in your project is .rn-agent/. |
| Create one | Run /rn-dev-agent:test-feature <description>. On clean verification, the verified walk is saved as an action. |
| Run one | List with /rn-dev-agent:list-learned-actions; replay with /rn-dev-agent:run-action <name>. The agent also picks an action automatically when it needs to reach a known state. |
| Self-repair | If a testID changes, the plugin patches the action against the live UI and retries. Small UI drift is absorbed; broken product logic is not. |
| Why | Known flows replay in seconds instead of being rediscovered interactively. Repeated setup work like login becomes one fast step. |
- Interactive walkLLM discovers the flow
- Verified ✓UI + state confirmed live
- Saved.rn-agent/actions/<name>.yaml
- Replayedas a prologue, next session
- UI driftsa testID changes
- Auto-repaired ✓cdp_repair_action patches & retries
Every verified walk becomes a replayable action; drift is absorbed by auto-repair instead of a human re-record.
Why we have actions — the LLM/pragmatic hybrid
Section titled “Why we have actions — the LLM/pragmatic hybrid”LLM agents are great at understanding intent and improvising on novel screens. They are slow and stochastic at re-deriving things they’ve already seen. A login flow that took fourteen minutes to walk interactively the first time will take fourteen minutes again the next time, every time, if we don’t record it.
Pure-script approaches (Maestro, Detox, Appium) are the opposite: fast and deterministic on the happy path, but they don’t adapt. A renamed testID breaks the script; a human re-records.
Actions sit deliberately in the middle. They are emitted by the agent, not authored by humans — the verification walk that proves the feature works is the same artefact that gets replayed next time. The agent is in charge of when to use an action versus when to discover something new.
The composition pattern
Section titled “The composition pattern”The agent never replays an entire job from a script — that would defeat the point of having an LLM in the loop. Each task is composed of two regimes:
- Pragmatic reusable actions for the predictable parts — login, “navigate to settings → security”, “create a draft task with X title”, switching locale, dismissing the subscription gate, getting back to logged-in home.
- LLM-driven discovery for the part that is actually new — verifying a specific UI state, exercising a new edge case, debugging a regression, walking a freshly built feature.
A worked example. You ask: “tap the cart badge.”
- The agent reads the navigation state. The app is on
LoginScreen, but the cart badge lives onHomeScreen. - It enters through
cdp_login_prologue, which resolves the exact saveduser-loginaction. - The prologue requires a fresh passing RunRecord before it arrives at
HomeScreen. - Then it discovers the cart badge interactively and taps it.
Measured impact
Section titled “Measured impact”A 3-step task-creation wizard took 13 min 55 s as an interactive agent walk on first run; the same wizard replayed as an action runs in ~4 seconds — a ~210× speed-up. Across 35 stories in the test app, average end-to-end time dropped from ~12 min to ~4 min once the corresponding actions existed. The latency win is most of the point, but the deeper one is determinism: replayed prologues take a fixed number of turns, so the LLM doesn’t waste context re-orienting before getting to the actually-novel work.
How this compares with the alternatives
Section titled “How this compares with the alternatives”| Failure mode | Pure script (Detox, Maestro) | Pure LLM (no actions) | This plugin |
|---|---|---|---|
testID renamed in app | Breaks; human re-records | Re-discovers slowly each run | cdp_repair_action patches the YAML via fuzzy match against the live snapshot, retries, logs the diff |
| Button moved / restyled | Breaks | Adapts but spends turns | Repair handles it; if structure changed, escalates |
| Product logic changed | Passes anyway, masking the bug | Probabilistically catches it | Refuses to auto-patch a logical break; surfaces the failure to you |
| Net-new behaviour to verify | n/a — can’t author for unknown flows | Re-derives every session | Discovers interactively, then the verified walk auto-saves as a new action |
| Cost over time | Linear (every drift needs a human) | Quadratic-ish (every session re-pays full walk) | Sub-linear (drift auto-absorbed, new flows compound the library) |
Said another way: actions are the memory of the LLM loop. Every successful verification adds one. Every drift gets quietly absorbed. Every truly broken flow escalates.
Tool surface
Section titled “Tool surface”The hybrid is implemented across five MCP tool groups (one conceptual family, “Actions”) and two slash commands.
| Tool | Role |
|---|---|
cdp_record_test_save_as_action | Convert a recorded interactive walk into a first-class .rn-agent/actions/<id>.yaml with metadata header and sidecar state file. Auto-promotes to status: active after the first clean replay. |
cdp_run_action | Replay an action by id with params. Orchestrates exact-active-device maestro_run + optional cdp_repair_action retry. Persists a RunRecord with autoRepair telemetry (passed / failed / refused / skipped, phase timings) so MTTR analysis can see which flows are stable. Maestro deviceId authority comes from direct runner evidence, not requested metadata; a runner/WDA mismatch fails closed. |
cdp_login_prologue | Enter authenticated journeys through the exact user-login action. Requires a fresh passing RunRecord and terminally blocks mutating fallbacks after failure; it is a navigation helper, not formal PR proof. |
cdp_repair_action | When a run fails with SELECTOR_NOT_FOUND, fuzzy-match the stale selector against the live snapshot, patch the YAML, retry. Refuses on human-edited files (mtime check), >3 repairs/24h, or snapshot infrastructure failure. |
cdp_record_test_* (start / stop / generate / annotate / save / load / list) | The recorder upstream of actions — captures device taps + CDP state assertions during interactive walks, before they get promoted to actions. |
| Command | Role |
|---|---|
/rn-dev-agent:list-learned-actions | Read-only inventory — feedback memories + flows + skeletons + plugin commands. Shared script (packages/rn-dev-agent-core/dist/learned-actions.js) is the single source of truth, also called by rn-tester / rn-debugger agents before they walk anything manually. |
/rn-dev-agent:run-action | Side-effecting execution — looks up the action via the same script, gates safety checks (mutates flag, appId match, ${VAR} coverage), then calls cdp_run_action. |
The artifact-first protocol
Section titled “The artifact-first protocol”Both the rn-tester and rn-debugger agents are instructed (via feedback_execute_artifacts_before_manual.md) to scan saved actions before composing any new device_* primitives. Manual primitives are the fallback, not the default — that’s the lever that keeps the LLM from paying full-walk latency on flows it has already verified once.
In practice this means a session opens with /list-learned-actions (or its programmatic equivalent), confirms through rn_session status that replay authority is not blocked, routes non-login matches through /run-action, and uses cdp_login_prologue for authentication. It only drops to interactive device_press / device_fill / cdp_interact when no non-login action covers the intent. Discovery alone grants no replay authority; blocked sessions follow parallel session authority.
Where actions live
Section titled “Where actions live”The plugin’s home in your project is .rn-agent/. Actions live in the actions/ subdirectory; sibling folders hold supporting state.
.rn-agent/├── actions/ ← saved actions (commit)│ └── *.yaml├── state/ ← run history, repair history (gitignore)├── recordings/ ← raw captures from cdp_record_test (gitignore)├── skeleton.yaml ← UI semantic-name → testID map (commit)├── nav-graph.yaml ← persisted navigation graph (commit, optional)├── fixtures/ ← seed data for replay (commit)├── proposals/ ← repair proposals queued for review (commit)└── README.md/rn-dev-agent:setup scaffolds the entire directory on first onboarding; /doctor reports on its health.
The plugin’s entire footprint is .rn-agent/. It does not read or write anywhere else in your project.
Creating an action
Section titled “Creating an action”Run /rn-dev-agent:test-feature <feature description>. The plugin walks the feature on the live simulator, verifies UI rendering and internal state, then saves the verified walk as .rn-agent/actions/<feature-slug>.yaml. Each action carries a small metadata header at the top — its intent, what it tags, whether it mutates data, its lifecycle status, and its required replay-engine pin.
# id: wizard-create-task# intent: Create a task via the 3-step wizard# tags: [task, wizard, create]# mutates: true# status: active# enginePin: maestro-runner@1.1.24appId: com.rndevagent.testapp---# Maestro YAML body...You can hand-edit actions, but consider that self-repair refuses to touch hand-edited files (mtime check) — to keep the plugin’s automatic upkeep working, prefer rerecording over hand-editing.
Listing and running
Section titled “Listing and running”/rn-dev-agent:list-learned-actions shows what’s saved in this project (with an optional keyword filter):
/rn-dev-agent:list-learned-actions taskProgrammatic action inventories keep valid actions available when one action file is corrupt and emit an ACTION_INVENTORY_ENTRY_SKIPPED warning naming that file. A corpus that changes or becomes refused during inventory remains a terminal integrity error.
/rn-dev-agent:run-action replays one by name:
/rn-dev-agent:run-action wizard-create-task -e TITLE="Buy milk" -e PRIORITY=high-e KEY=VALUE fills ${KEY} placeholders inside the action. --platform ios|android validates the exact bound session platform and never selects among booted devices. Replay always goes through cdp_run_action; there is no direct-runner dry-run path.
Running actions from the observe UI
Section titled “Running actions from the observe UI”The observability web UI (/rn-dev-agent:observe — it autostarts with the session) has an Actions tab that fronts the same library: every saved action is listed with its status badge and mutates flag, ${KEY} placeholders become input fields, and a Run button replays it through the same cdp_run_action engine — self-repair and run records included.
The replay shows up live on the device mirror as the taps play out. That makes the observe UI the quickest regression spot-check — “does user-login still pass after my refactor?” is one browser click, no agent turn or terminal round-trip. Locked e2e suites get the same treatment in the neighbouring E2E tab, including per-flow progress and run history.
Self-repair, in plain words
Section titled “Self-repair, in plain words”When a testID gets renamed in your app and the action references the old name, replay fails with SELECTOR_NOT_FOUND. The plugin doesn’t give up: it looks at the live UI, finds the most likely new testID via fuzzy matching, patches the YAML, and retries. The repair gets logged to the action’s sidecar file so you can see what changed.
Engine version pinning
Section titled “Engine version pinning”Action replay requires maestro-runner >= 1.1.24 from rn-dev-agent’s versioned pin-cache.
The authoritative pin manifest lives at
packages/rn-dev-agent-core/src/domain/maestro-runner-pin.json; setup installs and verifies
attested 1.1.24 as the default known-good and /doctor reports missing, older, unattested, checksum-mismatched,
unverified, unknown, or unsupported installations. Replay refuses every non-pinned-ok
state before UI mutation and never substitutes PATH, ~/.maestro-runner, or Maestro CLI.
Installation stages each candidate in a collision-safe sibling temporary directory, verifies
the archive and complete payload against that manifest, and atomically publishes the pin.
Concurrent or restarted installers converge on the same verified cache; an abandoned staging
directory is ignored and never requires manual recovery.
Owned actions must carry # enginePin: maestro-runner@1.1.24 or newer. Setup migrates compatible
actions; missing or older pins and unsupported regex text selectors are terminal.
Bumping the pin follows the upgrade ritual documented beside the manifest: replay the
committed action corpus on both platforms, reconcile known quirks, then update the manifest.
The pinned engine drives Android API 26+ only — its bundled UiAutomator2 server APK
declares minSdk 26. On an older emulator or phone, maestro_run refuses up front with
ANDROID_API_UNSUPPORTED instead of failing later on an opaque install error; the device_*
interaction tier still covers API 23+. See
ANDROID_API_UNSUPPORTED for the
full diagnosis and what still works.
On WDA-blind runtimes (iOS 26 bridgeless, or after a transport-blind failure on the same
device), cdp_run_action probes the component tree first and — when the action’s anchor is
visible — replays through the CDP/JS fallback directly instead of paying the doomed ~40s WDA
attempt (RunRecord.blindProbe records the routing; disable with RN_BLIND_PROBE=0). Per call,
cdp_run_action(blindProbeMode: …) overrides that default: inherit honors the env var,
allow enables the probe for this call, forbid keeps this call maestro-first.
What actions are NOT
Section titled “What actions are NOT”- Not a magic auto-tester. Self-repair handles small UI drift; it doesn’t fix broken features.
- Not a replacement for hand-written E2E tests. If you maintain a
.maestro/suite for CI, the plugin doesn’t touch it. Actions and your team’s E2E suite live separately. - Project knowledge by default. Actions live in
.rn-agent/actions/, alongside the skeleton and other plugin-managed files, and tracked corpora should be committed like__tests__/. Fully private corpora are also supported: setup can inherit onlyactions/into linked worktrees while keeping integration, session state, recordings, generated launchers, and runtime data worktree-local.
See also
Section titled “See also”/rn-dev-agent:test-feature— the command that records actions/rn-dev-agent:list-learned-actions— list saved actions/rn-dev-agent:run-action— replay an action by name/rn-dev-agent:observe— watch replays live and run actions from the browser/rn-dev-agent:setup— scaffolds.rn-agent/and the dev-bridge- Architecture — where actions sit in the three-layer model