All chapters Day 1 - May 30, 2026

Running an AI Agent on a Phone

Termuxno systemdproot aarch64build in public

Companion to the LinkedIn post "Born in a phone terminal". The post tells the story; this is the engineering.

Date: May 30, 2026 · Device: Motorola Edge 60 Pro, Android 16, 11 GB RAM (4.3 free), 221 GB storage (89 free), 8× aarch64 cores · Environment: Termux + proot Ubuntu · No root. No display. No payment methods.


1. The first message

The session opened with the question that started everything (verbatim):

"i have my telegram configured, now if i message the bot, will i recieve messages from you ?"

Status check: hermes gateway status"Gateway is not running." Telegram was configured (bot token present in config), but nothing was listening. The agent explained the two options: foreground (hermes gateway run) or a background service (hermes gateway install && start). The user said: "yes start the gateway."

2. There is no systemd on Android

$ systemctl --user status hermes-gateway
bash: systemctl: command not found

Termux runs in Android's app sandbox - no PID 1, no systemd, no service manager, no loginctl. hermes gateway install (which wires a systemd user unit on Linux) is meaningless here. The entire service layer collapses to:

nohup hermes gateway run > ~/.hermes/logs/gateway.log 2>&1 &
echo "Gateway PID: $!"

That's the whole "server room": one nohup'd process, one log file, surviving the terminal being closed. Later sessions showed the messy reality of this model - kill -9 zombies reporting in late, the Discord deps install hanging in a dead process, restarts being manual. No supervision, no auto-restart, no wake-lock guarantee (Android can still reap the process under memory pressure; termux-wake-lock is the mitigation that got added later).

Lesson: on constrained platforms, the service manager is a shell one-liner. Plan for crashes, not just for clean shutdowns.

3. The device audit (what we actually had)

Component Spec
SoC 8 cores, aarch64
RAM 11 GB (4.3 free at audit)
Storage 221 GB (89 free)
OS Android 16
Context No root, untrusted_app context, no display

The untrusted_app SELinux context is the silent killer - it's why many Linux tricks die before they start: no raw sockets in some paths, restricted /proc visibility, no kernel module access.

4. proot Ubuntu - the fake-root escape hatch

Termux is a real userland but a limited one. The escape hatch: proot-distro Ubuntu - a userspace "root" via proot (ptrace-based syscall interception, no kernel changes). This unlocked:
- apt (a real distro package manager)
- Python wheels that need glibc (Termux is bionic)
- Firefox ESR 140.11.0 + geckodriver 0.36.0 for browser automation

What it could NOT unlock: Docker.

# Docker 29.1.3 installed fine inside proot
dockerd → failed: operation not permitted

proot cannot create kernel namespaces (clone(CLONE_NEWNS) etc. get intercepted/denied) - the fundamental primitive containers need. Docker inside proot Ubuntu is a non-starter. The fallback for isolation: plain processes, setsid, and discipline.

5. Browser automation on a phone

agent-browser v0.27.0 (musl build) + Firefox ESR + geckodriver, inside proot Ubuntu. Verified end-to-end: navigate → title/URL. This became the backbone for everything that needed a real browser later (Selenium driving the DuckDuckGo HTML page, LinkedIn CDP research, etc.).

6. The search-backend saga (the day's real war)

The phone's mobile carrier IP is poisoned for search engines:

Backend Result
SearXNG public instance 429
DuckDuckGo API blocked
Google captcha wall
DuckDuckGo HTML page via Selenium ✅ ~8–10 s/query

The surviving design: a Selenium-driven fetch of DDG's non-JS HTML page (the legacy endpoint that still renders without JavaScript), parsed for results. ~/.hermes/scripts/search.py - the first homegrown search tool, later replaced by proper backends once the VPS era began (Exa, jina, agent-reach).

Lesson: on constrained networks, the "ugly" endpoint (no-JS HTML) is often the most reliable API. The web's legacy surfaces are its most resilient.

7. Spotify OAuth from a phone browser

PKCE flow, redirect URI http://127.0.0.1:43827/spotify/callback. The user authorized from the phone browser manually (the app is installed on the same device - the redirect hit the local listener). War story: port 43827 stuck in TIME_WAIT between attempts - the listener couldn't rebind; workaround was trying port 43000 until 43827 cleared. After auth: played Central Cee on device "Krash". First real "agent controls my life" moment.

8. TTS - the first voice bubble

Edge TTS (free, no key) worked on day 1 - a voice message bubble sent from Termux. This later became the STT/TTS stack (faster-whisper local + Edge/ElevenLabs).

9. Gemini CLI auth: the AQ-key trap

gemini CLI v0.44.1 installed. The user's API key from AI Studio was AQ.… format - it works via REST (gemini-3.5-flash answered "Hello" over curl) but the CLI's embedded SDK rejects it (expects AIzaSy…). Lesson: sandboxed key formats vs SDK expectations - always test the exact tool's auth path, not the provider's REST API.

10. Vision: not on day 1

DeepSeek V4 Flash is text-only - no vision tool in session. (This came back to bite later: the config's Google vision key turned out to be a dead placeholder, and the fix was a local qwen2.5vl:3b in ollama - the same "run it yourself" pattern.) On day 1, "can you read images?" → no.

11. The same evening: the migration plan

Evening session - planning the move to a new phone:
- termux-backup style tar of /data/data/com.termux/files (home + usr)
- proot-distro Ubuntu backed up separately
- OAuth tokens (Spotify, Gmail) are file-based → survive the move
- Pitfall documented: Python venvs have hardcoded paths - a fresh hermes-agent install + config/skills/memory restore is cleaner than moving venvs

This planning paid off ~11 weeks later, when the migration was to something better than a new phone: a free Oracle VPS (Aug 16 - "you from oracle now right?").


What the post glosses over (the point of this blog)

The LinkedIn post is the moment. This is the mechanism: no systemd → nohup; no kernel → proot; no namespaces → no Docker; no clean IP → Selenium-on-HTML; no display → phone-browser OAuth; no budget → free tiers everywhere.

Every constraint became a design decision that survived the migration. The VPS didn't erase the phone lessons - it inherited them: the free-model economy (Jio Google AI Pro, z.ai GLM, ollama), the local-everything stack (nomic embeddings, faster-whisper, qwen2.5vl), and the discipline of measuring what actually works before scaling what doesn't.

Next chapter: the free-model economy - running an agent 24/7 on a ~₹0 bill.