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 |
| 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.