Perch is one of seven clients for a self-hosted Hermes Agent. Below: what it measures at, how it lines up against each of the others on every axis that matters for a phone, and where it is behind. Perch numbers are read off measurement artefacts. Competitor cells are from each project's README as of 2026-09-10.
| What | Perch | The bar |
|---|---|---|
| Cold start to first frame | 777 ms | Android vitals flags ≥ 5,000 ms as excessive |
| Install size on Play | 23.4 MB · 13 s download | Play measured −1 % install conversion per +6 MB; 200 MB hard limit |
| Memory at idle | 145 MB RSS | own gate: 180 MB |
| Session reconcile after reconnect, p95 | 226 ms | own gate: 1,000 ms |
| Capability probe on connect | 45 ms | own gate: 600 ms |
| Approval round-trip, answered from the phone | 10 ms loopback | the request itself expires at 300 s |
| Network traffic while backgrounded | 0 requests, 0 wakeups in a 121 s window | measured through a counting proxy the device is routed through, not asserted |
| Steady-state connections | 1 | hard cap 1 |
| Foreground service while following a run | self-stops at 30 min, one ongoing notification with Stop | Android's dataSync cap is 6 h rolling |
| Polling while a run is unsettled | every 2.5 s, foreground only; idle transcript re-read every 30 s; both stop on background | — |
| Stream liveness | declared dead after 2 missed keepalives (60 s); reconnect backoff 0.5 → 30 s with 20 % jitter, 6 attempts | — |
| Third-party runtime dependencies | 4 (Sentry, marked, MMKV, safe-area) + 20 Expo modules; 0 icon fonts; 63 KB bundled assets | own gate: 8 / 22 |
| Permissions in the shipped bundle | 27, all reviewed; none for storage, media, location, contacts or advertising | read from the AAB's merged manifest |
| Credentials in the JavaScript heap | 0; keys live in the hardware keystore | hard zero, scanned every build |
| Tests | 1,054 unit + contract (90.9 % core coverage, 69 s) · 26 device flows · live-gateway suite · visual regression | — |
| Touch targets · body contrast · reduced motion | 44 dp · 5.77 : 1 · 100 % honoured | WCAG AA is 4.5 : 1 |
| Strings · locales · themes | 416 · English + Simplified Chinese · light, dark, OLED | — |
| Budgets enforced on every build | 63, 7 of them hard zeros; a budget is never raised to pass | — |
Startup, memory and reconcile were taken on an API 36 emulator, which is where CI can take them. Background traffic was measured by routing the device through a proxy that counts request lines and open sockets. The counters were reset only after the app was off-screen.
Seven clients exist. Two are native iOS, two native Android, one a PWA behind a proxy, one an official Expo shell that wraps the desktop renderer in a WebView, and Perch. hermes-android is the strongest Android alternative; Hermex has the most stars and needs a separate third-party server.
| Perch | hermes-android | Hy4ri/hermes-mobile | goncharik/hermes-mobile | Hermex | ShresthaYaju/hermes-mobile | Nous shell (PR) | |
|---|---|---|---|---|---|---|---|
| Stack | Expo · RN + 2 Kotlin modules | Flutter | Kotlin · Compose | SwiftUI · TCA | SwiftUI | PWA + Node proxy | Expo shell · WebView |
| Platforms | Android · iOS · web | Android | Android | iOS · iPad | iOS 18+ | any browser | iPhone verified |
| Talks to | api_server + dashboard | api_server + dashboard | dashboard (9119) | dashboard (9119) | a separate hermes-webui server | dashboard via proxy | gateway via bridge |
| Reach | LAN · Tailscale · public HTTPS · CGNAT, with 5 reachability verdicts | LAN · Tailscale · HTTPS proxy | LAN only by design | private network / VPN | tunnel or Tailscale Serve | Tailscale only | URL + token |
| Auth | key in hardware keystore · QR pairing · pairing approvals | API key | basic auth (admin/hermes default) | basic · OAuth · token, Keychain | password | Tailscale identity header | token stored locally |
| Streaming | ✓ SSE, frames as they arrive | ✓ SSE | ✓ WebSocket | ✓ | ✓ | ✓ WS | ✓ (desktop renderer) |
| Approvals from the phone | ✓ two-tap · risk-ordered inbox · stale decisions named | ✓ | — | ✓ | — | ✓ | not documented |
| Approval survives a cold start | ✓ recovered from the run id | partial ("durable turn recovery") | — | — | — | no: lost if the phone is asleep, by its own README | — |
| Notified while the app is closed | ✓ your own ntfy topic · 30-min follow service · Android 16 Live Updates | ✓ background-turn notifications | foreground-service chat notifications | ✓ push (approvals, clarifications, errors) | — | ✓ push, 5 types | — |
| Offline | ✓ cached transcripts · replaying outbox · idempotent resend | — | local history DB | state restore after backgrounding | cached sessions | — | — |
| Stop · steer · queue mid-run | ✓ ✓ ✓; the queue holds after a stop, and send-now stops first | stop | — | — | stop · steer | — | — |
| Fork a conversation | ✓ | ✓ branch | — | ✓ | — | — | — |
| Search | ✓ server FTS5 + instant local filter | ✓ on-device FTS5 + AI | — | ✓ | ✓ | ✓ | — |
| Routines / cron | ✓ create · edit · pause · run-now · delete | ✓ CRUD | ✓ | — | ✓ | ✓ pause · resume · run | — |
| Skills | ✓ browse + toggle | browse | ✓ manage | slash commands | browse | — | — |
| Bots / profiles | ✓ roster · create on the gateway · per-bot key | — | ✓ manage | ✓ multiple | select | ✓ | select |
| Attachments | photo · PDF · text, 8 MiB, dashboard only | 10 files × 16 MiB | — | ✓ photos · PDF · camera | ✓ | — | — |
| Voice | push-to-talk, on-device only; audio never leaves the phone | dictation and read-aloud; where audio goes is not stated | — | dictation, transcribed by the agent | — | — | — |
| Usage & context | tokens · cost · cache reads · context % with a real denominator | — | analytics & billing | ✓ tokens · % · cost | read-only analytics | — | — |
| Tablet | ✓ two-pane ≥ 820 dp | ✓ | — | ✓ iPad sidebar | — | — | master-detail |
| Accessibility gated | ✓ 44 dp · 130 % type · RTL · reduced motion | — | — | — | — | — | — |
| Second language | ✓ zh-Hans | — | — | — | — | — | — |
| Automated tests stated | 1,054 unit · 26 device flows · live gateway · visual | 900+ (v2.1.0) | JUnit · Espresso · CI | — | — | — | — |
| Distribution | Play closed test · signed APK with published fingerprint · web | GitHub | F-Droid · Obtainium | TestFlight | App Store | self-host | unmerged |
| Published list of what it cannot do | ✓ | — | — | "MVP is feature-complete" | — | ✓ (limitations section) | — |
| Stars | new | 278 | 124 | 58 | 1.3k | 0 | — |
And the incumbent nobody lists: Telegram, through the gateway's own adapter. It wins on push: instant, everywhere, no setup. It loses on everything an approval needs: the exact command on screen, a fail-closed deadline shown as a countdown, a two-tap confirm, and a record of what was decided. That is the bar Perch sets itself.
Budgets exist for these; the numbers do not. Battery per hour of streaming and frame drops during a stream need a physical device, because an emulator renders through the host GPU. Crash-free sessions and ANR rate need real users, and the gates are set at 99.7 % and 0.2 % against Play's bad-behaviour lines of 98.91 % and 0.47 %. Memory at 10,000 messages is bounded (1,000 rendered rows, 50 per page) and unmeasured.
Privacy position in full: the privacy policy. Try it with no server at all — walkthrough mode renders every screen from captured responses.