{"slug": "series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring", "title": "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…", "summary": "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.", "body_md": "The machine that would not take a resident agent — until a 405-command war\nended. This notebook watches the loop on a rooted Pixel 10a (Android 17,\n\"stallion\") running Hermes in Termux: how it came to run at all, how its mind\nsurvives the app's reinstall-wipe (attributed and fixed — the app was\nreinstalling a bootstrap into a prefix it could no longer read), and how it\ncomes back across reboots — and how the resident gets a face on the home screen.\n\nThe 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\n\nThe cockpit\n\nThe 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\n\nThe 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\n\nThe lock\n\nThe 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\n\nThe keyboard\n\nTermKey: 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\n\nThe machine that would not take a resident agent — until a 405-command war\nended. This notebook watches the loop on a rooted Pixel 10a (Android 17,\n\"stallion\") running Hermes in Termux: how it came to run at all, how its mind\nsurvives the app's reinstall-wipe (attributed and fixed — the app was\nreinstalling a bootstrap into a prefix it could no longer read), and how it\ncomes back across reboots — and how the resident gets a face on the home screen.\n\nFour lanes now, all earned: the bring-up (getting Hermes to run on\nthis machine at all — payload construction, SELinux exec domains, the Android\nlinker, packaging — the 405-command war that this notebook exists for); and\nthe survival plant (state that has to survive the app's wipes and the\nboot's deletes — the resurrection and the reboot, the healed copies and the\nmeasured 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\nthe 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 —\nwill open a lane when it earns one.\n\n1.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.\n\nThe survival plant\n\n1.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.\n2.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.\n\nThe cockpit\n\n1.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.\n\nThe fleet's cable\n\n1.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.\n\n6.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.\n\n7.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.\n\n8.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).\n\n9.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.\n\n10.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.\n\n11. [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.\n\n12. [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.\n\n13. [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.\n\n14. [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.\n\n15. [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.\n\nParts land as the loop's wars do. Updated with the loop. Last updated Oct 3, 2026.", "url": "https://wpnews.pro/news/series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring", "canonical_source": "https://gist.github.com/ebowwa/231e7b232e254fc9f18595c8b1d60295", "published_at": "2026-09-28 20:51:04+00:00", "updated_at": "2026-10-12 20:50:47.880998+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools"], "entities": ["Hermes", "Termux", "Pixel 10a", "Android 17", "TermKey", "Termux:API", "SystemUI", "Node"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring", "markdown": "https://wpnews.pro/news/series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring.md", "text": "https://wpnews.pro/news/series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring.txt", "jsonld": "https://wpnews.pro/news/series-9-pixel-10a-the-android-lab-notebook-hermes-agent-on-a-rooted-pixel-bring.jsonld"}}