{"slug": "i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought", "title": "I put an AI coding agent inside an Android app. Here's everything that fought back.", "summary": "A developer built AndroPI, an open-source, MIT-licensed AI coding agent that runs entirely on an Android phone with no backend, wrapping the TypeScript coding agent pi in a Flutter UI. To get Node.js, git, ripgrep, ssh and a proot-based Debian userland running on-device, the binaries ship inside the APK renamed as lib*.so files, and the developer added DNS-over-HTTPS to bypass ISP DNS hijacking and a layer that sanitizes malformed tool-call arguments from models. Trimming the agent bundle from 16.6 MB to 8 MB cut the APK from 73 MB to 63 MB, and the developer had to drop the MANAGE_EXTERNAL_STORAGE permission after Google rejected it.", "body_md": "I wanted to fix a bug from my phone. Not \"review a PR\" from my phone. Actually open a project, let an AI agent write the code, run it, see the result and ship it.\n\nEvery tool I found was a chat app that talks to someone else's server. So I built the thing I wanted: **AndroPI**, an AI coding agent that runs on the phone itself. Open source, MIT, no backend.\n\nIt works now. Getting there was a fight with Android the whole way.\n\nTake [pi](https://github.com/earendil-works/pi), an open source coding agent written in TypeScript. Put a Flutter UI in front of it. Done.\n\nThe catch: pi needs Node.js. And git. And a shell, ripgrep, ssh, curl. On a phone.\n\nFirst wall. I dropped a Node binary into the app's data folder and tried to execute it.\n\nPermission denied.\n\nSince Android 10 you can't execute files from your app's writable storage. The only place an app can run native code from is its native library directory. And the packager only puts files there if they're named `lib*.so`.\n\nSo that's what I did. Node.js ships in the APK as `libnode.so`. Git is `libgit.so`. They aren't libraries. Android doesn't check.\n\n``` php\n# package -> {binary inside the Termux package: name inside the APK}\nBINARIES = {\n    \"nodejs-lts\": {\"bin/node\": \"libnode.so\"},\n    \"ripgrep\": {\"bin/rg\": \"librg.so\"},\n    \"git\": {\"bin/git\": \"libgit.so\"},\n    \"openssh\": {\"bin/ssh\": \"libssh_cli.so\"},\n    \"proot\": {\"bin/proot\": \"libproot.so\"},\n}\n```\n\nThe binaries come from the Termux package repository. A script downloads the `.deb` files, pulls the binaries out, and rewrites their library names with `lief`, because a real dependency like `libicuuc.so.78` also has to become `libicuuc_78.so` or the packager drops it.\n\nMy favourite part: the container launcher is a shell script. It ships as `libbox.so`. The Flutter build complains every single time that it \"failed to strip debug symbols\" from it. Yeah. It's a shell script.\n\nAn agent that can't `apt install` is half an agent. It'll want Python, or a newer Node, or some build tool I didn't bundle.\n\nSo AndroPI runs a Debian (or Alpine) userland with `proot`. No root needed. The app pulls the official image straight from Docker Hub's registry API and unpacks the layers itself. The agent's shell runs inside that.\n\n`apt install python3` on a phone, from a chat. That one felt good.\n\n**My ISP hijacks DNS.** The agent's web search kept failing. Turned out the provider was redirecting lookups for sites it doesn't like. I added DNS over HTTPS inside the Node host. Now the agent can search no matter what the network thinks.\n\n**Models send garbage arguments.** One model kept calling the read tool with `offset: \"None\"`. A Python-ism, as a string, into a number field. Validation failed, the run died. I added a small layer that cleans tool arguments before they're checked.\n\n**The foreground service ate my notification.** The app shows \"Agent is working\" while it runs, and \"pi is done\" when it finishes. The done notification never showed up. Both used notification ID 1. When the service stopped, Android removed ID 1. Took me an embarrassing while to see it, and I only caught it because I was recording a demo video.\n\nThe first release APK was 73 MB. I assumed Flutter was the fat one. It wasn't.\n\nAbout 80% of the app is the runtime. Node alone is 15 MB compressed. ICU data, which Node needs for `Intl`, is another 15.\n\nMy first idea was to bundle the agent into one minified file. That broke login. pi loads its OAuth flows through `import()` paths it builds at runtime, and a bundler can't follow those.\n\nSo I kept the package layout and did the boring thing instead:\n\n`node_modules`\nAgent bundle: 16.6 MB down to 8 MB. APK: 73 down to 63. Then I added armv7 and x86_64 builds, since Termux has packages for both.\n\nTwice.\n\n**First: \"All files access\".** I had `MANAGE_EXTERNAL_STORAGE` so you could open a project in any folder on the phone. Google only allows that for file managers and a few other categories. A coding agent isn't on the list.\n\nFair. The workspace already lived in app storage, so only one feature needed it. I split the build into two flavors:\n\n``` php\n<!-- android/app/src/play/AndroidManifest.xml -->\n<uses-permission\n    android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"\n    tools:node=\"remove\"/>\n```\n\nThe Play build drops the permission. Files come in through the system picker and go out through MediaStore or the share sheet. The GitHub build keeps the full version.\n\n**Second: the foreground service.** Android 14 wants a type for every foreground service, and \"runs an AI agent\" isn't one of them. So it's `specialUse`, which means a written justification and a video showing the notification while the app is in the background. That's the video where I found the notification bug.\n\nYou bring the model: Claude, GPT, Gemini, OpenRouter. Your keys stay on your phone. There's no server of mine in the middle, because there is no server of mine.\n\nIf you've shipped something weird inside an APK, I want to hear how you got it past review. And if you find a bug, open an issue. I probably caused it.", "url": "https://wpnews.pro/news/i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought", "canonical_source": "https://dev.to/abdulm/i-put-an-ai-coding-agent-inside-an-android-app-heres-everything-that-fought-back-lpk", "published_at": "2026-10-02 08:24:51+00:00", "updated_at": "2026-10-02 08:38:34.376677+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["AndroPI", "pi", "Android", "Flutter", "Node.js", "Termux", "Google", "Docker Hub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought", "markdown": "https://wpnews.pro/news/i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought.md", "text": "https://wpnews.pro/news/i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought.txt", "jsonld": "https://wpnews.pro/news/i-put-an-ai-coding-agent-inside-an-android-app-here-s-everything-that-fought.jsonld"}}