cd /news/ai-agents/series-9-pixel-10a-the-android-lab-n… · home › topics › ai-agents › article
[ARTICLE · art-149859] src=gist.github.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Series 9 — Pixel 10a — the Android lab notebook. Hermes Agent on a rooted Pixel (bring-up · survival plant · cockpit · the fleet's cable · the resident's runtimes · the lock · the keyboard · the NFC…

A developer documented a 405-command effort to bring up the Hermes agent in Termux on a rooted Pixel 10a running Android 17, including fixes for reinstall wipes (a bootstrap reinstalled into an unreadable prefix, resolved with chown/chcon), reboot survival, and a home-screen status app and widget. The notebook also covers two smali patches to SystemUI for iOS-style PIN auto-submit, an on-device terminal-first IME called TermKey, a runtime census across store, Termux, and system JVM layers, and raw NCI access past the Android NFC framework.

by read13 min views35 publishedSep 28, 2026

The machine that would not take a resident agent — until a 405-command war ended. This notebook watches the loop on a rooted Pixel 10a (Android 17, "stallion") running Hermes in Termux: how it came to run at all, how its mind survives the app's reinstall-wipe (attributed and fixed — the app was reinstalling a bootstrap into a prefix it could no longer read), and how it comes back across reboots — and how the resident gets a face on the home screen.

The reboot war — four controlled boots — the wipe attributed to the app itself (a 401 not readable prefix after a uid-501/system_data_file rebuild), the chown/chcon fix that ended the wipes, the retired watchdog, and the anthropic venv gap

The cockpit

The cockpit war: a face for the resident — the app that couldn't see (the two manifest lines), the widget built without Android Studio, the notifications that needed a second app (Termux:API, checksum-verified, installed as root), and the watcher that thinks

The runtimes census: what the machine can actually run — living part: three layers (store · Termux · system JVMs), every row with its proving command; founding finds: the store manifest drifts from staged reality, ffmpeg won't run even under the launcher env, and the clang/gcc set can't link; wave 2: the repo's whole language shelf (swift/kotlin/rust/bun/deno/go/ruby/php/mono — present, none installed), the C toolchain deep-dead (ld/lld orphaned, as absent), ObjC nowhere, and the Android build toolchain repo-installed since July

The lock

The lock war: the fourth digit submits — iOS-style PIN auto-submit: two one-instruction smali patches in SystemUI, the private-mount-namespace trap (su -mm), one-command revert The keyboard

TermKey: the keyboard built on the phone, for the phone — a terminal-first system IME compiled on-device: stub-signature wars, the crash-painter debug trick, dual wire formats (key events for terminals, committed text for normal apps), rotation rebuild; portrait and landscape measured

The machine that would not take a resident agent — until a 405-command war ended. This notebook watches the loop on a rooted Pixel 10a (Android 17, "stallion") running Hermes in Termux: how it came to run at all, how its mind survives the app's reinstall-wipe (attributed and fixed — the app was reinstalling a bootstrap into a prefix it could no longer read), and how it comes back across reboots — and how the resident gets a face on the home screen.

Four lanes now, all earned: the bring-up (getting Hermes to run on this machine at all — payload construction, SELinux exec domains, the Android linker, packaging — the 405-command war that this notebook exists for); and the survival plant (state that has to survive the app's wipes and the boot's deletes — the resurrection and the reboot, the healed copies and the measured state gates); and the cockpit (the resident's face — the status app and widget on the home screen, alerts as phone notifications, the watcher that trips on retry storms, session death, finished jobs); and the fleet's cable (the wire into the mini — the Pixel routed over the TB5/USB cable at ~400 Mbps single-stream / 610 multi-stream, auto-healing; it did not refuse, and the earlier "refused" call was retracted); and the resident's runtimes (what the machine actually carries — Node, logged as a no-go, found living inside the agent's own kit since day one of the canary staging; then the living census of all three layers, ledger-first); and the lock (iOS behavior won on the PIN pad — the fourth digit submits, two instructions of smali, one namespace lesson); and the keyboard (TermKey — an IME built on the machine for the machine, terminal-first, dual-wire, rotation-aware); and the NFC war (raw NCI past the framework — the user called the limits lies and was right). The agent's years — what the resident actually does — will open a lane when it earns one.

1.Getting Hermes to run on the Pixel 10a (Android 17, "stallion") at all — the 405-command war: you cannot clone and run it — the Termux bootstrap that JNI-crashes, the SELinux exec domain, the linker that ignores LD_LIBRARY_PATH, and packaging — four walls, knocked down and gated by 28 checks.

The survival plant

1.The resurrection that split the mind in two: a state.db that couldn't decide — a wiped rooted Pixel restored its 15 skill dirs (3 hand-built — android-screen, dsh, ssh-remotes; the other twelve are template dirs, a count-typo we nearly shipped as a metric) and the transcript, then the state.db bounced 266K-868K: the watcher probed processes, not state — its getprop returned the agent's own splash, and a ghost gateway minted a second identity. Cured with grow-only restore, wrapper shebang fixes, a tmux pty, and a db-size health gate. 2.The reboot war — four controlled boots — the wipe that beat an immutable canary attributed to the app itself: a rebuild left the data dir owned by uid 501 under a system_data_file label, the app got 401 not readable on its own prefix, and its bundled-bootstrap install path deleted it. chown + chcon over the whole data dir ended the wipes; boots then passed with nothing touched. Also: the watchdog loop retired for a one-shot boot start, and the anthropic venv gap (root uv can neither exec nor reach the network) fixed with the app-cgroup recipe.

The cockpit

1.The cockpit war: a face for the resident — v1.2's sockets died silently: no INTERNET permission plus the targetSdk≥28 cleartext block, both fixed and both now manifest law; the widget (AppWidgetProvider, RemoteViews, PendingIntent) built without Android Studio on hand-written stubs and the DEVICE framework-res.apk; the alerts that needed Termux:API — a separate app, fetched checksum-verified and pm-installed as root via /data/local/tmp; hermes-watch.sh as a no-LLM cron tick (retry storm ≥3, session death, job finished) whose first finding was the gateway dead behind a stale state file. Verified the same hour: app v1.3, widget receiver registered, notification confirmed in dumpsys notification, watcher at 0 fails.

The fleet's cable

1.The cable war: serving the Pixel over the mini's Thunderbolt/USB wire — the mini (the fleet's hub) serves the Mac laptops over a Thunderbolt bridge (bootpd DHCP, NAT out the wired uplink, forwarding on); the question was the same service over the TB5/USB wire physically plugged into the mini's USB port. The wire answered at USB3 SuperSpeed 5 Gbps, the phone's gadget flipped ADB→NCM (usb0 up; the mini enumerated it as a Pixel ethernet port), L2 carried real frames both ways, and the mini's Pixel pool (its own /24 — after the laptops' /24 proved owned by the TB bridge) was fully loaded (bootpd + pf NAT + forwarding). The shipable artifact is the android17-tbnetbridge Magisk module (macOS half: Gauge at secondsee.com/downloads/gauge). The first diagnosis was wrong (and the part retracts it): Android did not refuse. Three mislabeled walls fell — the NCM netdev is named ncm0 but Android's EthernetNetworkFactory only tracks (usb|eth)\d+ (rename to usb0 and the factory claimed it, agent + DHCPDISCOVER alive); macOS holds no persistent config for the re-enumerating USB NIC, so it kept link-local-ing over the manual static server address (a real Manual network service on the Pixel port fixed it, re-applied across replugs); and the pf NAT anchor existed in pf.conf but was never loaded into the live ruleset (pfctl -Nf - push fixed it, states then appeared). With the server durable, DHCP completed over the wire, the network validated, and the cable became the active default network — ~400 Mbps internet path single-stream, ~460 wire-only, 610 multi-stream (fast.com fan-out). Durability: the phone re-arms via a Magisk boot service (~12 s auto-heal on replug, verified), the mini re-applies its static address natively.

6.Node was here all along: the runtime we called a no-go — the no-go retracted from the inside out: a complete Node runtime (v26.4.0 + npm 12.0.2) sat in the agent's own tool store since the Sep 17 canary staging, byte-identical to the distro's own nodejs package; the bare shell never had it, the launcher always did, and pkg install nodejs still dies on an unrelated half-installed libexpat.

7.The runtimes census: what the machine can actually run — a living part, grown by rows: Layer 1 the agent's store (node/python are the distro's own packages; npm/ripgrep/uv are not; the manifest claims versions the binaries don't have; ffmpeg runs only with its own lib dir, which even the launcher omits), Layer 2 Termux proper (zero language runtimes; clang/gcc present-but-unlinkable, libLLVM absent; the apt route blocked by a half-installed libexpat), Layer 3 the system JVMs (two OpenJDKs, alive). Carries a corrections ledger — its first entry patches Part 6 in place (the repo index does carry libexpat 2.8.5; the suspect is apt's lists). Wave 2: the repo carries swift 6.4 / kotlin 2.4 / rust 1.98 / bun 1.4 / deno / zig / go / ruby 4 / php 8.5 / mono — none installed, all one apt-block away; the C/C++ toolchain is deeper-dead than clang knew (ld + lld are libLLVM orphans dpkg doesn't own, as is absent; only ar/nm/objcopy/strip live); Objective-C is a dead row (nothing in the repo); and the Android build toolchain (aapt2 + d8/apksigner jars + javac in both JDKs) is repo-installed, runnable, and predates the agent — the built APKs on disk are dated July.

8.The lock war: the fourth digit submits — the iPhone's auto-submit on the Pixel's PIN pad. Dead ends measured first (no setting, no flag; the 6-digit feature proves the behavior exists but changes the credential; store lock-screen skins rejected as fake locks). The smali map: per-digit requests already carry auto-confirm — the framework policy gate (isAutoPinConfirmEnabled) and the decision gate (shouldSkipAuthenticationAttempt's flow read) kill it; two const/4 0x1 inserts free the lane while the real pin-length check stays intact. War one: the build (apktool on-device, arsc STORED, broken wrappers). War two: v1 was a ghost — plain su mounts in a PRIVATE namespace; /proc//root showed stock bytes every boot; su -mm (global ns) is the fix, now the series' namespace lesson. War three: the keyguard is a secure surface (screencaps return black) — proof is behavioral + the operator's eyes; v1's shell-side 'unlocked' telemetry recorded as a false-positive scar. Deployed as Magisk module + boot re-bind, revert = one umount; the ✓ renders but is a no-op (known residue).

9.TermKey: the keyboard built on the phone, for the phone — a terminal-first system IME, compiled on-device with the census's toolchain (aapt2 + javac-against-stubs + d8 + apksigner). Four walls: stub signatures binding call sites to lies (NoSuchMethodError across ten builds — fixed by mirroring real framework signatures exactly, verified in the dex); a dead ENTER key from one missing switch case (found by auditing every label against every case); dual wire formats — terminals speak key events, normal apps speak committed text, an IME needs both and picks per focused app (Ctrl+A as 0x01 in Termux, newline in Chrome); rotation freezing the portrait layout (fixed by rebuilding the view on configuration change, 58dp keys portrait / 32dp landscape). Debug hero: the crash painter — an IME that renders its own stack trace as its own view. Typing verified end-to-end in both orientations.

10.The NFC war: talking to the chip behind Android's back — triggered by the user's correction: you lie about what you cannot do. Tested: root stops the NFC HAL, /dev/st21nfc opens as a raw device, NCI protocol exchanges flow (RESET, INIT, NFCEE discovery all answered). The ST21NFC chip's NFCEE route table extracted — AID patterns and NFCEE IDs that Android never exposes. APDU-to-eSE channel started but not completed. The single honest limit after testing: eSE key extraction is hardware-fused. Every other claimed can't was a stack limit, not a chip limit.

  1. [The board census: every chip on the phone, outside Android]](https://gist.github.com/ebowwa/e3d27538533432c1d772d058d8a13b39) — extends the NFC war from one chip to the whole board. Sysfs census: the I2C map (Maxim power block, CPS4041 wireless charging with an auth surface, CS40L26 haptics, TAS25XX amp), SPI (st54spi eSE, FocalTech touchscreen, GNSS on SPI), and the co-processor federation (AoC RISC-V, EdgeTPU rio, Trusty TEE with no citadel node, Broadcom Kepler GNSS). Wars: the kernel refuses raw I2C to claimed addresses (EBUSY, by design) but the at24 driver gave up its full 8KB EEPROM; the BT "UART" was a myth — no H4 at three bauds, no ldisc ever, and the real transport is /dev/wbrc (484:0) found in the HAL's fd list; GNSS nodes are silent until booted (clean stop/start cycle, restored); and the eSE answered a raw SPI read with an ISO-T=1 alive block (21 82 00) — the direct channel Part 10 only reached through NCI routing. Every stopped service restarted and verified. Fused stays fused.

  2. [The eSE war: making the vault's own door talk]](https://gist.github.com/ebowwa/ad1c33818d628bdde6c90c7c9c2c4620) — the secure element conversation, finished. The st54spi driver (read/write + one reset ioctl 0x40046b63) turned out to be driven by an AOSP se_gto-lineage transport, so instead of reversing full-duplex SPI frames (five-byte header 21 82 00 a3, our TX echoed back) we dlopened the vendor's own library against /dev/st54spi from a sixty-line host: open, SPI reset, ATR (15 bytes, T=1, IFSC 254 — the 'error 15' was the length), then APDUs: the vendor's own alive check (GET DATA 9F7F) returned 47 bytes and SW 9000 with the card spelling 'GTO' in its identity object; SELECT default applet answered with the public Thales ISD AID A000000151000000; the card manager hides applets at default privilege while the framework's ARF rules + direct SELECTs prove three live applets respond. HAL stopped and restored around every run. The pipe is ours; Part 10's fused verdict stands unchanged.

  3. [The wbrc war: the radio that wasn't where we looked]](https://gist.github.com/ebowwa/98b9833de078ef10c46ac8cbcd47f2a2) — Bluetooth on the board is not UART-attached: the grouped tty gave three bauds of silence and never carried a line discipline, the combo's power is an rfkill interlock with userspace GPIO stubbed out, and the real transport is /dev/wbrc (484:0) — a lifecycle coordinator inside the WiFi driver's own module, found in the HAL's fd list. The practical victory: Android's HCI snoop tap (bluetooth_hci_log) delivers the complete init conversation in clean H4 — Broadcom confirmed, the board codename written over the air, firmware retained across warm HAL restarts. A ptrace syscall-injection framework was pursued and closed by evidence: 15,388 syscall stops with zero wbrc writes — the transport writes are kernel-internal, no userspace path exists. Fused stays fused.

  4. [The BETP boot: mapping the radio's own tongue]](https://gist.github.com/ebowwa/e96c1561281b8df997204a710ed5099e) — the Kepler GNSS boot is a userspace BETP write() protocol, not the ioctl surface it first appeared to be. Nine frames captured from gnssd's syscalls (68 43 header, sequence counters), replayed standalone against /dev/gnss_ipc with the chip answering every frame — 'BETP' literal on the wire. The firmware upload only runs on true chip power-cycles (fresh-kprobe proof: zero link_load on service restarts); the modem/CP side supervises (lal_cp_monitor holds channel ownership after gnssd exits; umts_boot0 _IO('o',25/32/39) accepted from root). The decoys and the real protocol, mapped.

  5. [The context hub: CHRE on AoC, mapped]](https://gist.github.com/ebowwa/6cb77fa6399890bb362ecf35870b77b1) — the AoC's CHRE context hub mapped end to end: 94-service catalog, 13 nanoapps, and the live data framing (203 frames from the HAL's descriptors, counter/magic/ts/len/type with engine-name payloads). The TX path is single-owner gated (EBUSY on the HAL's chre_data_tx); the mbox mirror accepts writes without responses. The board-census-era 'CHRE is silent' note is corrected: the data rides the HAL's own descriptors all along.

Parts land as the loop's wars do. Updated with the loop. Last updated Oct 3, 2026.

── more in #ai-agents 4 stories · sorted by recency
── more on @hermes 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/series-9-pixel-10a-t…] indexed:0 read:13min 2026-09-28 · —