agentdev
README.md
# agentdev — Android device tools for AI agents (Termux + proot)
[](https://github.com/lorenzz20105-del/agentdev/actions/workflows/ci.yml)
[](https://www.python.org/)
[](pyproject.toml)
[](AGENTS.md)
[](LICENSE)
A **zero-dependency** toolkit that gives an AI agent hands on an Android phone: run adb
commands, read `logcat`, dump and drive the UI, install APKs, pull files, grab screenshots.
Designed for the setup where it's actually painful: **an agent running inside a proot
distro on Termux**, where a normal `adb` binary may not exist and a USB device is never
available. agentdev ships its own implementation of the **ADB wire protocol**, so it
talks straight to the `adb` server running on your Termux host — no binary, no pip
install, pure Python stdlib (3.8+).
```
┌──────────── proot distro (where the agent lives) ─────────────┐
│ agent ──► agentdev ──TCP 127.0.0.1:5037──► adb server │
│ (socket backend, no adb binary needed) │
└───────────────────────────────────────────────────────────────┘
Termux host ▲ adb binary + wireless debugging
│ USB or TCP
📱 phone
```
Three ways to call it, same **41 tools** underneath:
| Interface | Use it for |
|---|---|
| **MCP server** (`agentdev mcp`) | Claude Desktop, Cline, Continue, any MCP client |
| **CLI** (`agentdev shell_exec --command …`) | shell-capable agents, scripts, cron |
| **Python / tool schemas** | OpenAI, Anthropic, Gemini function calling (schemas exported for you) |
---
## Quick start
```bash
# 1. On the Termux HOST
pkg install python android-tools proot-distro
adb start-server
# 2. Get the toolkit into the distro
proot-distro login debian
# (copy this directory in, or git clone it)
# 3. Verify end to end
export ADB_SERVER_SOCKET=tcp:127.0.0.1:5037
python3 -m agentdev doctor
```
`doctor` tells you exactly which link in the chain is broken — backend, server, device
authorisation, or missing on-device binaries.
Then:
```bash
python3 -m agentdev device_info # model, Android version, screen, root
python3 -m agentdev shell_exec --command "pm list packages -3" --allow-dangerous
python3 -m agentdev logcat_crashes --package com.example.app
python3 -m agentdev ui_dump --max-nodes 40
python3 -m agentdev tap_text --text "Sign in"
python3 -m agentdev screenshot --name home
python3 -m agentdev mcp # serve to an MCP client
```
No install step required — `python3 -m agentdev` works from the repo. Optional:
`pip install -e .` puts an `agentdev` command on PATH.
### Connecting the phone (wireless debugging)
```bash
# on the phone: Developer options → Wireless debugging → Pair device with pairing code
adb pair 192.168.1.5:37123 # enter the 6-digit code
adb connect 192.168.1.5:5555
adb devices -l # must say "device", not "unauthorized"
```
The phone only needs to reach the host's adb server; the proot distro never touches the
phone directly.
---
## The 41 tools
**Diagnostics** — `doctor`, `device_list`, `device_select`, `device_wait`, `adb_connect`,
`device_info`, `battery`, `net_info`, `device_unlock`
**Shell & files** — `shell_exec`*, `file_ls`, `file_read`, `file_pull`, `file_push`*,
`file_delete`*
**Logs** — `logcat_dump` (bounded snapshot, level / tag / pid / keyword filters),
`logcat_watch` (live window — trigger an action, then watch what it logs),
`logcat_crashes` (Java exceptions, ANRs, native aborts, `Force finishing activity`)
**UI & input** — `ui_dump` (accessibility tree with tap centres), `ui_find`, `ui_wait`
(poll until an element appears/disappears), `scroll_to` (swipe until visible),
`tap`, `tap_text`, `swipe`, `long_press`, `input_text`, `key_event`, `screenshot`,
`screen_state`
**Apps & intents** — `app_start`, `app_stop`*, `app_clear_data`*, `app_current`,
`app_list`, `package_info`, `app_install`*, `open_url`, `intent_send`*
**Settings** — `settings_get`, `settings_set`*
`*` = gated behind `--allow-dangerous` / `AGENTDEV_ALLOW_DANGEROUS=1`.
Every tool returns JSON. Failures come back as
`{"error": "...", "kind": "adb|timeout|internal", "retryable": bool, "hint": "..."}`
instead of raising, so one bad call never kills the agent loop — and the hint tells the
agent *what to do next* (accept the RSA prompt, reconnect the transport, wake the screen…).
---
## Wiring it into an agent
### MCP (Claude Desktop, Cline, Continue…)
```json
{
"mcpServers": {
"android": {
"command": "python3",
"args": ["-m", "agentdev", "mcp"],
"env": {
"AGENTDEV_ALLOW_DANGEROUS": "1",
"ADB_SERVER_SOCKET": "tcp:127.0.0.1:5037"
}
}
}
}
```
See `examples/claude_desktop_config.json`. Smoke-test without a chatbot:
`python3 examples/mcp_client.py device_list`
### OpenAI-compatible function calling
```bash
export OPENAI_API_KEY=sk-...
python3 examples/openai_agent.py "Open Settings, enable Airplane mode, then show me the log"
```
Works with local models too — point `OPENAI_BASE_URL` at Ollama / LM Studio / vLLM.
### Anthropic tool use
```bash
export ANTHROPIC_API_KEY=sk-ant-...
python3 examples/anthropic_agent.py "What app is in the foreground?"
```
### Any custom agent
```bash
python3 -m agentdev schema --format openai > tools.openai.json
python3 -m agentdev schema --format anthropic > tools.anthropic.json
python3 -m agentdev schema --format gemini > tools.gemini.json
```
```python
from agentdev.tools import Context, run_tool
ctx = Context() # one per agent session
run_tool("tap_text", {"text": "Sign in"}, ctx)
run_tool("logcat_dump", {"level": "E", "grep": "NullPointer", "limit": 50}, ctx)
```
`AGENTS.md` has the operating playbook to paste into your system prompt.
---
## Why agents don't get stuck
Long-tool-call agents fail on flaky transports, not on missing features. agentdev bakes in
the recovery paths:
* **Error triage** — every failure is classified (`agentdev/errors.py`) into a
`hint` + `retryable` flag: `unauthorized` → "tap Allow on the phone"; `device offline` →
"adb reconnect offline"; `uiautomator dump failed` → "wake the device, or use screenshot".
* **Transient retry** — a dropped socket / stale transport is retried once automatically
after resetting the session, so a server restart doesn't burn a turn.
* **Hierarchy cache** — repeated `ui_find` / `ui_wait` / `tap_text` calls within 1.5 s
reuse one uiautomator dump (`AGENTDEV_UI_TTL`); every mutating tool invalidates it.
* **Wait, don't sleep** — `ui_wait` and `device_wait` replace blind `sleep`s, so slow
devices cost latency instead of correctness.
* **Bounded everything** — log output, stream windows, screenshots and file reads are all
capped, so a chatty device can't blow the model's context window.
* **Secret hygiene** — `scripts/check_secrets.py` (run in CI) blocks any commit containing
GitHub/AWS/OpenAI/Anthropic tokens or private keys.
## Configuration
| Env var | Default | Meaning |
|---|---|---|
| `AGENTDEV_ADB_HOST` / `ADB_SERVER_SOCKET` | `127.0.0.1` / `tcp:127.0.0.1:5037` | where the adb server listens |
| `AGENTDEV_ADB_PORT` | `5037` | adb server port |
| `AGENTDEV_SERIAL` / `ANDROID_SERIAL` | first online device | target serial |
| `AGENTDEV_ALLOW_DANGEROUS` | `0` | unlock `*`-gated tools |
| `AGENTDEV_SHELL_TIMEOUT` | `60` | seconds per shell call |
| `AGENTDEV_MAX_OUTPUT` | `24000` | chars returned to the model per call |
| `AGENTDEV_WORKDIR` | `~/.agentdev/out` | where screenshots / pulled files land |
| `AGENTDEV_DEVICE_TMP` | `/data/local/tmp` | on-device scratch dir |
| `AGENTDEV_UI_TTL` | `1.5` | seconds the hierarchy cache stays valid |
| `ADB_BIN` | auto | explicit path to an adb binary (binary backend) |
CLI flags `--allow-dangerous --serial --adb-host --adb-port --backend --raw` work before
*or* after the subcommand.
---
## How the two backends work
**`socket` (default, recommended)** — speaks the adb protocol directly: `%04x`-framed
requests, `OKAY`/`FAIL` status words, `host:devices-l`, `host:transport:<serial>` +
`shell:<cmd>`. Works inside proot with no `adb` binary and no privileged mounts. Exit
codes are recovered by appending a marker echo to each command (the adb shell service
doesn't propagate them over the wire). Files move as base64 chunks over the shell channel,
so `sync:` services and `adb push` aren't needed.
**`binary` (fallback)** — shells out to `adb` from `PATH` (or
`/data/data/com.termux/files/usr/bin/adb`, or `ADB_BIN`), and drives `adb push`/`adb install`
when available. Auto-detected if the server probe fails; force with `--backend binary`.
Long-lived streams (`logcat`) reuse one socket via `iter_stream`, with per-read timeouts so
a silent device can never wedge the agent.
---
## Notes for proot
* The distro normally **shares the host network namespace**, so `127.0.0.1:5037` reaches the
Termux adb server. If it doesn't, use the host's Wi-Fi IP: `AGENTDEV_ADB_HOST=192.168.1.10`.
(Binding a server *inside* proot to a port the host can see does not work — run it on the host.)
* Termux's adb binary can't be executed inside some rootfs images (glibc vs bionic), which is
exactly why the socket backend exists.
* No root required on the phone; everything here runs through the normal adb shell user.
Root-only commands just fail with a clear error.
## Safety
* Destructive tools are gated; leave `AGENTDEV_ALLOW_DANGEROUS` unset for read-only agents.
* `screenshot` / `file_pull` write into `AGENTDEV_WORKDIR` — never into system paths.
* Every device action is logged to stderr by the MCP server, stdout stays protocol-clean.
* `shell_exec` runs as the adb shell user: it cannot silently escalate.
## Tests
```bash
python3 -m unittest discover -s tests -v # 62 tests, no phone needed
python3 scripts/check_secrets.py --all # credential scan (also runs in CI)
make test lint secrets # same thing via Makefile
```
`tests/fake_adb.py` is a real adb-protocol server that executes commands on the local
machine (and serves canned `uiautomator`/`wm`/`settings`/`screencap` payloads), so the
socket backend, framing, exit codes, file transfer, UI hierarchy parsing, tap-coordinate
math, screenshot decoding, cache invalidation, retry logic, tool registry, MCP handshake
and CLI are all exercised without hardware. Point it at a real phone any time
with `python3 tests/fake_adb.py 5038` (do not run it on 5037 next to a real server).
## Limitations
* `input_text` is ASCII-only (an Android `input` limitation); use `shell_exec` with a
clipboard helper for Unicode.
* `ui_dump` uses `uiautomator` — not available on games/Flutter canvases with no
accessibility nodes; fall back to `screenshot` + `tap`.
* Pushing files over base64 is fine for MBs, slower than `adb push` for large APKs
(the binary backend uses `adb push` automatically when present).
MIT licensed.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues