Most users don't need this. Just install the app from android-latest. This guide is for developers who want to build the Android app from source and run a debug build on a real phone.
A step-by-step guide for a first-timer to build the AgentNet Android app and run it on a real phone. No prior Android experience needed.
- How the pieces fit (where things build vs. run)
- What you need
- Step 1 — Install Android Studio
- Step 2 — Get the runtime assets (one time)
- Step 3 — Open the project
- Step 4 — Enable USB debugging
- Step 5 — Build and run on the phone
- Day-to-day: rebuild after a code change
- What happens on first launch
- Troubleshooting
This is the one thing to get straight, and it's also the thing people most often get wrong:
The app is built on your computer and installed onto the phone. Nothing is compiled on the phone.
There are two different "Claude/Codex" things and they are unrelated. Don't mix them up.
| What it is | Runs where | |
|---|---|---|
| Android Studio / Gradle | The tool that compiles the AgentNet app (APK) on your machine and installs it over USB. | Your computer. |
| claude / codex CLI | The agent that runs inside the AgentNet app, in a Linux (proot) sandbox on the phone. The app installs and signs into it on-device. | The phone, inside the app. |
So, to answer the usual questions directly:
- "Does Android Studio use the claude on my computer?" → No. Android Studio just compiles the app. The app carries its own claude/codex inside it and runs them on the phone. Your computer's claude (if any) is never involved.
- "Do we build the app in GitHub Actions / on the phone?" → No. The APK is built
locally with Gradle (
./gradlew assembleDebug, which is what the green ▶ button runs) and pushed to the phone withadb install. The phone never compiles anything. GitHub Actions is used for one narrow thing only: pre-building the heavy Linux rootfs asset (see Step 2), because that part needs an arm64 Linux machine. That asset changes rarely.
Two ways to build & install the APK — they do the same thing, pick whichever you like:
- Android Studio (GUI, clicking) — easiest for a first-timer; this guide is written around it.
- Terminal (
adb/./gradlew) — faster once you know it, and how the app is actually built and tested during development. Shown as a "⚡ Faster: terminal" note.
- A computer (macOS or Windows).
- An Android phone, arm64 (virtually all are). Both older phones (Snapdragon / Android 9–13) and recent ones that enable arm64 heap pointer tagging (TBI) are supported. The Solana Seeker (MediaTek MT6878 / Android 16) is the primary on-device test target and runs cleanly. (Tagging used to break the sandbox; we now ship a proot build that handles it — see Troubleshooting.)
- A USB cable to connect the phone.
- ~12 GB free disk space (Android Studio + SDK, the app's bundled ~1 GB Linux image, plus a debug APK that lands around ~480 MB because it carries that image).
Android Studio is the official tool for building Android apps. It also brings the Android
SDK (which includes adb, the tool that talks to your phone). You do this once.
- Go to https://developer.android.com/studio → Download Android Studio.
- Accept the terms, download the
.dmg. - Open the
.dmgand drag Android Studio into Applications. - Open Android Studio (if macOS warns, right-click → Open the first time).
- The Setup Wizard runs: choose Standard install, accept the SDK licenses, let it download the SDK (a few minutes).
- You land on the Welcome to Android Studio screen.
adb lives at: ~/Library/Android/sdk/platform-tools/adb
- Go to https://developer.android.com/studio → Download Android Studio.
- Accept the terms, download the
.exe, run the installer, click Next through the defaults. - Launch Android Studio.
- The Setup Wizard runs: choose Standard install, accept the SDK licenses, let it download the SDK (a few minutes).
- You land on the Welcome to Android Studio screen.
adb lives at: C:\Users\<YourName>\AppData\Local\Android\Sdk\platform-tools\adb.exe
# macOS
~/Library/Android/sdk/platform-tools/adb version
# Windows (PowerShell)
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" versionIf it prints Android Debug Bridge version ..., you're set.
💡 To type just
adb, add platform-tools to your PATH. Optional — this guide always shows the full path so you don't have to.
The app bundles a small Linux world inside the APK. Two things go under
surfaces/android/app/src/main/assets/ (paths from the repo root):
rootfs-arm64.tar (~1 GB: an Ubuntu/glibc rootfs with node + claude/codex installed)
agentnet-server.tar (small: our localhost node server + the React web UI it serves)
…and the proot runtime goes under surfaces/android/app/src/main/jniLibs/arm64-v8a/ as
native libraries:
libproot.so (the Android-native proot binary)
libloader.so (proot's ELF loader; libloader32.so for 32-bit)
libtalloc.so (proot's libtalloc dependency)
libandroid-shmem.so (proot's shmem shim)
Why jniLibs and not assets/? proot's files are executable ELF. Bundling them loosely under
assets/made Google Play Protect REJECT the install on some devices. Shipping them underjniLibs/(namedlib*.so) makes the OS extract them into the app's native library dir, where ELF is expected and stays executable, which clears the rejection. The on-device behavior is identical. (The versionedlibtalloc.so.2ships aslibtalloc.sobecausejniLibsonly packageslib*.so; the app recreates thelibtalloc.so.2soname with a symlink at runtime.)
These are not in git (too large — .gitignore excludes them). Check if they're
already there first — if you got this folder from someone who already built it, you can
skip this step.
# from the repo root
ls -la surfaces/android/app/src/main/assets/ surfaces/android/app/src/main/jniLibs/arm64-v8a/If both are populated, jump to Step 3. Otherwise build them.
Only
rootfs-arm64.tartruly needs an arm64 Linux machine (it installs glibc binaries for the phone's architecture). The rest are cheap and arch-independent. In practice you build the rootfs once and then almost never touch it again — day-to-day you only rebuildagentnet-server.tar(see Day-to-day).
Builds the assets in the cloud; you just download the result. No build tools needed locally.
- Repo on GitHub → Actions tab.
- android-assets workflow → Run workflow → ABI =
arm64→ Run. - Wait ~10–15 min (it builds the Linux image under arm64 emulation).
- When green, open the run → Artifacts → download android-assets-arm64 (
.zip). - Unzip. It contains
assets/andjniLibs/at the top level — copy both intosurfaces/android/app/src/main/(so the tars land in.../main/assets/and thelib*.sofiles in.../main/jniLibs/arm64-v8a/).
Old artifact? If the zip has a
proot-arm64/folder (instead ofjniLibs/), it was built before proot moved to jniLibs. Don't hand-rename it — copy thatproot-arm64/folder intosurfaces/android/app/src/main/assets/and runbash surfaces/android/scripts/relayout-proot.sh, which converts it to the layout the app expects (and removes the loose ELF from assets). The proot bytes are identical, so no rebuild is needed. Re-running the workflow also produces a current-format artifact.
Requires Docker Desktop with arm64 emulation. From the repo root:
pnpm install
pnpm --filter agentnet-localhost build
pnpm --filter agentnet-webview build
docker run --rm --platform linux/arm64 \
-v "$(pwd):/work" -w /work \
-e ABI=arm64 -e ALLOW_CROSS=1 \
ubuntu:24.04 \
bash -c 'apt-get update -qq && apt-get install -y -qq curl ca-certificates xz-utils tar coreutils && bash surfaces/android/scripts/build-assets.sh'This writes the tars into assets/ and the proot lib*.so files into jniLibs/arm64-v8a/.
(Note: it rebuilds the ~1 GB rootfs every time, ~10–15 min. If all you changed is app/web
code, you don't need this — use the lighter server-bundle refresh in
Day-to-day.)
surfaces/android folder inside the repo, not the
repo root, or Android Studio won't recognize it.
- Open Android Studio → Open (not "New Project").
- Select the
surfaces/androidfolder:- macOS:
/Users/<you>/.../AgentNet/surfaces/android - Windows:
C:\Users\<you>\...\AgentNet\surfaces\android
- macOS:
- Click Open. If asked, click Trust Project.
- Android Studio runs a Gradle sync (bottom status bar). First time it downloads Gradle + dependencies — let it finish (a few minutes, needs internet). Wait for "Gradle sync finished."
The package id is com.iqlabs.agentnet. The build pins targetSdk = 28 on purpose (the
legacy W^X exemption that lets the app execute node and the proot guest's binaries from
app storage — see the pointer-tagging note). minSdk = 24,
compileSdk = 35.
Android-native Drive login does not need a client secret or a hardcoded client ID in the
APK. In Google Cloud Console, create/register an Android OAuth client for package
com.iqlabs.agentnet and the signing certificate SHA-1 for the build you install.
For the debug APK, Android Studio can show the SHA-1 under Gradle signingReport, or run:
cd surfaces/android
./gradlew signingReportIf Google returns UNREGISTERED_ON_API_CONSOLE, that package name + SHA-1 pair is missing
from Google Cloud Console.
Only the desktop/browser fallback path needs a public OAuth client ID, supplied via
surfaces/android/local.properties (or the GOOGLE_CLIENT_ID env var; the build reads
either):
googleOAuthClientId=YOUR_PUBLIC_CLIENT_IDDo not put a Google OAuth client secret in local.properties, BuildConfig, app
assets, or Kotlin/TypeScript source. APK contents are visible to anyone who installs the
app. If Google returns client_secret is missing, the configured client ID belongs to a
secret-required desktop/web flow and Android login will not complete without changing to a
no-secret mobile/native OAuth path.
One-time setup on the phone, so your computer can install and run apps over USB.
- Turn on Developer options: Settings → About phone (Samsung: → Software information) → find Build number → tap it 7 times ("You are now a developer").
- Turn on USB debugging: Settings → Developer options → toggle USB debugging ON.
- Plug the phone into the computer with a USB cable.
- On the phone: "Allow USB debugging?" → check "Always allow from this computer" → Allow.
Confirm the computer sees it:
# macOS
~/Library/Android/sdk/platform-tools/adb devices
# Windows (PowerShell)
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" devicesYou should see your phone with device next to it:
List of devices attached
SM02G4061961289 device
unauthorized→ re-check the "Allow USB debugging?" dialog on the phone.- Nothing listed → try another cable/port (some are charge-only).
- Top toolbar: in the device dropdown (next to the green ▶ Run button), select your phone. The dropdown next to it should say app.
- Click the green ▶ Run button (Mac: Ctrl+R, Windows: Shift+F10).
- It builds the APK and installs it. The first build is slow (it packages the ~1 GB Linux image into a ~480 MB APK) — give it a few minutes.
- The app launches on the phone automatically.
From the surfaces/android folder:
# build the APK
./gradlew assembleDebug # macOS / Linux
gradlew.bat assembleDebug # Windows
# install it (-r reinstalls over the existing app, keeping its data;
# -s <serial> picks a specific device if more than one is plugged in)
~/Library/Android/sdk/platform-tools/adb install -r app/build/outputs/apk/debug/app-debug.apk
# launch it
~/Library/Android/sdk/platform-tools/adb shell monkey -p com.iqlabs.agentnet -c android.intent.category.LAUNCHER 1The APK lands at app/build/outputs/apk/debug/app-debug.apk.
You almost never rebuild the 1 GB rootfs. The two things that change while developing are
the node server (surfaces/localhost) and the web UI (surfaces/webview) — both
are plain JavaScript, so they build on macOS/Windows in seconds with no Docker and no
arm64. They're shipped together as agentnet-server.tar.
The app is built to make this cheap: the installer re-extracts only the server bundle when the APK ships a changed one (it fingerprints the bundle), and leaves the heavy rootfs untouched. So after editing surface code, the loop is:
# from the repo root — rebuild + repack just the server bundle
pnpm --filter agentnet-localhost build
pnpm --filter agentnet-webview build
STAGE=surfaces/android/.assets-build/server-bundle
rm -rf "$STAGE" && mkdir -p "$STAGE/webview"
cp -R surfaces/localhost/dist/. "$STAGE/"
cp -R surfaces/webview/dist/. "$STAGE/webview/"
tar -cf surfaces/android/app/src/main/assets/agentnet-server.tar -C "$STAGE" .
# then rebuild + reinstall the APK (or just hit ▶ in Android Studio)
cd surfaces/android
./gradlew assembleDebug
~/Library/Android/sdk/platform-tools/adb install -r app/build/outputs/apk/debug/app-debug.apk(This is exactly step 3 of surfaces/android/scripts/build-assets.sh, run on its own. If
you only changed Kotlin shell code, skip the repack entirely and just rebuild the APK.)
- A splash with the IQ logo appears (it gently pulses while booting). The first run unpacks the Linux image ("Setting up your environment… first launch only") — a few minutes. Later launches are quick.
- Connect your wallet — tap Connect, approve in your wallet app (e.g. Phantom) via the Solana Mobile Wallet Adapter. The wallet is remembered for silent reconnect on later launches.
- Connect your agent — connect Claude (or Codex). The login link opens in your external browser; sign into your subscription, then paste the code back into the app.
- You're in the chat — message the agent and it runs on your subscription, on the phone.
A couple of things you'll meet the first time you use the agent in the background:
- Battery optimization prompt — when you first enable background execution, the app asks to be exempted from battery optimization so Android doesn't kill the server mid-session. Allow it for long runs. (Asked once.)
- Notifications — Android 13+ asks for notification permission. It's used for the "approval needed" alert when the app is backgrounded (approve/reject right from the notification).
- Stuck on the splash / "Taking longer than usual": check the logs —
~/Library/Android/sdk/platform-tools/adb logcat -d | grep -E "AgentNet/|\[server\]"
proot error: ptrace(PEEKDATA): I/O error/Bad address(sandbox never starts): This is arm64 pointer tagging (TBI), not a kernel/SoC defect. Root cause proven on-device (Solana Seeker, MediaTek MT6878, Android 16):- bionic tags heap pointers with a top-byte tag (e.g.
0xb4...). proot reads/writes the guest's memory withPTRACE_PEEKDATA/POKEDATA, and the kernel rejects a tagged address withEIO(process_vm_readvstrips the tag, ptrace does not). proot's firstexecvethen fails withEFAULT→ server never comes up. - Verified: the identical address reads fine untagged but returns
EIOwith a0xb4top byte. Older phones (Snapdragon / Android 9) never tagged → they worked. - Not the fix:
android:allowNativeHeapPointerTagging="false"(tried — only affects the app's own process; the fork+exec'd proot re-enables tagging, and targetSdk=28 may ignore it anyway; targetSdk is pinned at 28 for the W^X execve exemption). - The fix (shipped): we bundle the Termux proot built with the
process_vmaccelerator (process_vm_readv/writev), which strips the top-byte tag and so never hits the tagged-PEEKDATApath.build-assets.shfetches it (pluslibtalloc.so.2+libandroid-shmem.so, which it dynamically links), andServerManager.ktpointsLD_LIBRARY_PATHat those libs. Verified on-device: the Seeker reachesserver ready (HTTP 200). - Full investigation notes live in the comment in
surfaces/android/app/src/main/AndroidManifest.xml.
- bionic tags heap pointers with a top-byte tag (e.g.
ENOSYS: uv_cwd/getcwd() failed(node dies right after proot starts): do not setPROOT_NO_SECCOMP. With seccomp acceleration on (the default),getcwdruns in the kernel and never touches the SELinux-restricted/proc; turning it off forces proot to translate every syscall and breaksgetcwd.ServerManager.ktalready keeps seccomp on andcds into a readable dir before exec'ing proot. (If a guide or older README tells you to setPROOT_NO_SECCOMP=1, that advice is stale.)- App "doesn't recognize me" /
EADDRINUSEafter backgrounding and reopening: an older build orphaned the guestnodeso a zombie kept the port. Fixed — rebuild/reinstall the current APK if you still see it. - Phone not detected (
adb devicesempty): different USB cable/port; re-accept the "Allow USB debugging" prompt; toggle USB debugging off/on. - Gradle sync fails on Open: make sure you opened
surfaces/android, not the repo root. - Build fails on missing assets /
rootfs-arm64.tar: you skipped Step 2. - Reset the app to a clean state (re-do onboarding, re-unpack the Linux image):
~/Library/Android/sdk/platform-tools/adb shell pm clear com.iqlabs.agentnet