droidrun/mobile-harness: Portable Instructions for Agents Driving Android and iOS
Skills for controlling Android, iOS and cloud phones
At a glance
- What is it?
- Mobile Harness is a Markdown harness plus a Python control API, not an agent runtime. It gives coding agents a documented path to tap, type, scroll and screenshot real phones, locally over ADB or through cloud devices.
- Who is it for?
- Adopt it if your agent already runs in a terminal and you can supply either ADB or a reachable Portal HTTP endpoint, because the harness deliberately refuses to configure those for you. Skip it if you want a self-contained automation framework with its own scheduler, reporting or test runner; this project is instructions plus a thin Python facade, and the README documents no such layer.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- GitHub does not report a main language for this repository.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: agents that can reason about a phone but cannot touch it
An agent with a shell and a browser can drive a web app. A phone is different. The screen is an accessibility tree, the coordinates move, some nodes are offscreen, and the control channel is either ADB or an HTTP bridge that someone has to stand up first. Mobile Harness addresses that gap by shipping two things: a set of Markdown files that tell an agent which control path exists and how to use it, and a Python package, `mobilerun_core`, that exposes one `Mobilerun` facade over Android and iOS, local and cloud.
The README is explicit that this is a compact Markdown harness, not an agent runtime. That single sentence defines the audience. If you already run Claude Code, Codex or another skill-aware coding agent, Mobile Harness is the missing operating manual plus a control library. If you are looking for a scheduler, a test runner or a dashboard, none of that is here.
How the harness and the control API fit together
The repository splits into two layers. The top level holds `AGENTS.md`, `SKILL.md`, `install.md`, `UPDATE.md`, and directories `platforms/`, `core/`, `apps/` and `local/`. `AGENTS.md` is the entry point for every runtime and routes the agent to the smallest file it needs: `platforms/android/GUIDE.md` for Android work, `platforms/ios/GUIDE.md` for iOS work, `platforms/<platform>/recovery/GUIDE.md` only when control fails, the credentials guide only when a credential or human-gated screen appears, `core/memory/GUIDE.md` only when reading or writing agent-owned memory, and an app card under `apps/android/<package>/CARD.md` or `apps/ios/<bundle-id>/CARD.md` for the foreground app. A parallel path under `local/apps/` takes precedence when the shipped card and the local card disagree. `UPDATE.md` is loaded only when the session-start update fails.
Underneath sits `mobilerun_core`. The README states that base `mobilerun-core` includes cloud support through `mobilerun-sdk`, while the `local` extra installs `mobilerun-core-local`, which core uses internally for local Android and iOS backends. Agents are told to import only `mobilerun_core`, so the backend split stays an implementation detail. The design intent is that the Markdown tells the agent what to do and the Python facade does it.
Installing Mobile Harness and connecting a first device
The README offers two install routes. The skill route uses the skills CLI and is the shorter one:
npx skills add droidrun/mobile-harnessThat installs the harness as an agent skill. The README notes that first use still requires the Python setup in `install.md`, and that an installed copy is updated with `npx skills update` from the same project.
The manual route creates a virtual environment and installs the local control API. The README specifies Python 3.11, 3.12 or 3.13 for the venv:
cd /path/to/mobile-harness
python -m venv .venv
.venv/bin/python -m pip install "mobilerun-core[local]"
.venv/bin/python -c "from mobilerun_core import Mobilerun"If that import line prints nothing, the package resolved. The README then suggests telling the agent which interpreter to use, with a line such as `Use /path/to/mobile-harness/.venv/bin/python for mobile-harness.`
A first real use is a local Android device over ADB. The README's primary API shows the connect call and three basic operations:
from mobilerun_core import Mobilerun
m = Mobilerun()
device = m.connect("R5CT123456", backend="local-android-adb")
device.ui()
device.screenshot()
device.start_app("com.android.settings")You should see `device.ui()` return the accessibility tree and `device.screenshot()` return an image. The README advises inspecting `device.capabilities` and calling `device.supports(...)` before optional operations, because not every backend implements everything. That advice matters more than it looks: `device.execute_script("<js>")` runs JavaScript in the device's foreground Chrome tab and returns its JSON result, but it is cloud-only, and local backends raise `UnsupportedOperation`.
The four local Android modes, and the one that is blocked
The README gives a table rather than a single supported configuration. With ADB present and Android Portal HTTP reachable, `backend="local-android-adb"` uses ADB and automatically uses Portal features when available. With ADB present and no Portal, the same backend falls back to ADB-native control, UI, text input, screenshots and app lifecycle. With no ADB but a reachable Portal, you pass `backend="local-android-http"` with a base URL such as `http://127.0.0.1:18080` and a bearer token. With neither, the harness is blocked and the documented instruction is to ask the user to enable ADB or provide reachable Portal HTTP access.
That last row is the honest part of the design. Without ADB the harness cannot install, enable, port-forward or fetch a token for Portal, so HTTP-only mode assumes the agent already has both the base URL and the token in hand. This is a deliberate boundary, and it means the harness will not rescue an environment where nobody has prepared the phone.
Local iOS is simpler and stricter. There is one active capability mode, `backend="local-ios-http"`, using `MOBILERUN_IOS_PORTAL_URL` or an explicit URL, with a default example of `http://127.0.0.1:6643`. The README requires that `GET /device/date`, `GET /state` and `GET /vision/screenshot` all work; if no iOS Portal is reachable, the mode is blocked. A second local iOS server, `mobilerun-ios --local <udid>`, defaults to `http://127.0.0.1:8080`.
Where the helper API stops being helpful
The device helpers are the most practical part of the README, and also where the sharp edges are. `device.find_nodes(...)` searches the accessibility tree, and `any_contains=` matches case-insensitive substrings across text, content description, resource id and accessibility identifier. Nodes may carry `offscreen: True` for outside the viewport, or `hidden: True` for reported not visible. The README states plainly that a missing flag is not proof of visibility, which is a warning that find results need verification rather than trust.
`device.tap_node(node)` taps the center of a node and raises if the node has no usable bounds; before any bounds check it raises a distinct error for a hidden node unless that node is also offscreen. `device.tap_text("label")` taps the first on-screen, non-hidden match and raises a distinct error when matches exist but none are tappable on-screen. Those distinct errors are useful for recovery files, but they also mean naive retry loops will keep hitting the same condition.
Scrolling has its own trap. `device.scroll(direction, distance=0.5, ms=..., verify=False)` is content-relative, and `verify=True` returns whether the viewport actually moved. `device.scroll_until(text_contains=..., direction="down", max_swipes=10)` returns the node or `None`, and stops early with `None` when the viewport stops moving. The README's instruction not to re-call it blindly is the correct reading: a `None` here can mean the target does not exist, not that you need more swipes. Text input is also uneven. `device.type("text", clear=True)` clears the focused field when the backend supports text input, and `device.clear_input()` is available on local Android ADB and local iOS Portal HTTP only. `device.list_apps()` excludes system apps by default, with `include_system_apps=True` where supported.
Cloud mode and what changes when the phone is not on your desk
Cloud devices reuse the same `Mobilerun` facade, which is the strongest argument for the abstraction. The README sets two environment variables and then connects with `backend="cloud"`:
export MOBILERUN_CLOUD_API_KEY="..."
export MOBILERUN_API_BASE_URL="https://api.mobilerun.ai/v1"from mobilerun_core import Mobilerun
m = Mobilerun()
device = m.connect("<cloud-device-id>", backend="cloud")
device.ui()
device.screenshot()
device.start_app("com.android.settings")The code is identical to the local example apart from the device id and backend string, which is exactly what you want from a facade. The trade-off is that cloud is the only place `execute_script` works, so a workflow that depends on running JavaScript in a foreground Chrome tab cannot be tested against a local backend at all. You have to gate it with `device.supports("execute_script")` and accept that local runs will take the unsupported branch.
The alternative worth naming is Appium. Appium is a driver-based automation server with its own client libraries and a long-standing ecosystem of language bindings; it expects you to write test scripts against a WebDriver-style session. Mobile Harness inverts that. It assumes an LLM agent is the caller, so the documentation is written for an agent to read at runtime, and the API is a small set of device helpers rather than a full driver protocol. If your team already has Appium suites in CI, Mobile Harness is not a drop-in replacement and does not try to be one.
Maintenance, licence and the update path
The repository is not archived, and the last push was on 2026-09-16, so the project is being changed right now. There are no retrieved releases, which means there is no versioned changelog to read; the update path in the README is a session-start `git pull --ff-only` or `npx skills update`, with `UPDATE.md` loaded only when that step fails. That is a lightweight model, and it puts the burden on you to notice when a guide file changed under your agent.
The licence is MIT, which is permissive and compatible with commercial use, though the repository's own LICENSE file is the authoritative text and nothing here is legal advice. One thing to check before depending on it: the README points to separate projects for the pieces it does not own. Android Portal lives at `droidrun/mobilerun-portal`, `ios-portal` at `droidrun/ios-portal`, and the `mobilerun-ios --local <udid>` setup guide at `docs.mobilerun.ai`. Those repositories carry their own licences and release cadences, and your upgrade cost is really the sum of all of them.
Editorial conclusion
Adopt it if your agent already runs in a terminal and you can supply either ADB or a reachable Portal HTTP endpoint, because the harness deliberately refuses to configure those for you. Skip it if you want a self-contained automation framework with its own scheduler, reporting or test runner; this project is instructions plus a thin Python facade, and the README documents no such layer. Before committing, verify on your own hardware that `mobilerun-core[local]` installs under Python 3.11 to 3.13, that `device.capabilities` reports the operations your workflow needs, and that your iOS Portal answers `GET /device/date`, `GET /state` and `GET /vision/screenshot`.
Frequently asked questions
What is a phone harness?
In this project's terms, it is a set of Markdown operating instructions plus a Python control API that lets an AI agent drive Android and iOS devices. The README describes Mobile Harness as a compact Markdown harness, not an agent runtime, with Python's `mobilerun_core` as the primary control path.
What is an auto harness?
The README does not use the term auto harness, so the closest supported answer is that Mobile Harness is loaded by skill-based runtimes through `SKILL.md` and by all runtimes through `AGENTS.md`. It automates device control only to the extent that the calling agent decides what to do.
What is a harness app?
Mobile Harness is not an app you install on the phone. It is a repository of guide files plus the `mobilerun_core` package that runs on your machine, and it connects to devices over ADB, Android Portal HTTP, iOS Portal HTTP or the cloud backend.
Community notes